1
0
Fork 0

feat(backend): No more people, sets instead

This commit is contained in:
joostdecock 2023-02-26 16:04:12 +01:00
parent 3c31901c2a
commit e691253d51
21 changed files with 308 additions and 316 deletions

View file

@ -40,8 +40,8 @@ BACKEND_WEBSITE_SCHEME=https
# For users
#BACKEND_AVATAR_USER=https://freesewing.org/avatar.svg
# For people
#BACKEND_AVATAR_PERSON=https://freesewing.org/avatar.svg
# For measurement sets
#BACKEND_AVATAR_SET=https://freesewing.org/avatar.svg
# For patterns
#BACKEND_AVATAR_PATTERN=https://freesewing.org/avatar.svg

View file

@ -3,7 +3,7 @@ import { schemas } from './lib.mjs'
import { paths as apikeyPaths } from './apikeys.mjs'
//import { paths as confirmationPaths, schemas as confirmationSchemas } from './confirmations.mjs'
import { paths as patternPaths } from './patterns.mjs'
import { paths as personPaths } from './people.mjs'
import { paths as setPaths } from './sets.mjs'
import { paths as userPaths } from './users.mjs'
const description = `
@ -49,7 +49,7 @@ export const openapi = {
paths: {
...apikeyPaths,
...patternPaths,
...personPaths,
...setPaths,
...userPaths,
},
}

View file

@ -28,8 +28,8 @@ export const errors = {
mfaTokenMissing: 'The `token` field is missing from the request body',
nameMissing: 'The `name` field was missing from the request body',
passwordMissing: 'The `password` field is missing from the request body',
personMissing: 'The request lacks a `person` value in the POST body',
personNotNumeric: 'The `person` field in the POST body is not of type `integer`',
setMissing: 'The request lacks a `set` value in the POST body',
setNotNumeric: 'The `set` field in the POST body is not of type `integer`',
postBodyMissing: 'The request lacks a POST body',
settingsNotAnObject:
'The `settings` field in the POST body does not contain a value of type `object`',
@ -221,8 +221,8 @@ export const response = {
type: 'string',
example: 'These are my notes. I can keep them alongside the pattern. Handy!',
},
personId: {
description: `The unique ID of the Person this pattern was drafted for`,
setId: {
description: `The unique ID of the Measurements Set this pattern was drafted for`,
type: 'integer',
example: 33,
},
@ -248,39 +248,39 @@ export const response = {
},
},
},
person: {
description: 'Object holding the data of the person',
set: {
description: 'Object holding the data of the measurements set',
type: 'object',
properties: {
id: {
description: `The Person's unique ID`,
description: `The Measurements Set's unique ID`,
type: 'integer',
example: 666,
},
createdAt: {
description: 'Timestamp of when the Person was created, in ISO 8601 format.',
description: 'Timestamp of when the Measurement Set was created, in ISO 8601 format.',
type: 'string',
example: '2022-12-18T18:14:30.460Z',
},
img: {
description: `An image that was stored with this person`,
description: `An image that was stored with this measurements set`,
type: 'string',
example: 'https://freesewing.org/avatar.svg',
},
imperial: {
description: `Whether or not to use imperial units for this person`,
description: `Whether or not to use imperial units for this measurements set`,
type: 'boolean',
example: false,
},
name: {
description: `The name of the Person exists solely to help you differentiate between your people.`,
description: `The name of the Measurements Set exists solely to help you differentiate between your people.`,
type: 'string',
example: 'My bestie Ronda',
},
notes: {
description: `Any notes to be stored with the person`,
description: `Any notes to be stored with the measurements set`,
type: 'string',
example: 'These are my notes. I can keep them alongside the person. Handy!',
example: 'These are my notes. I can keep them alongside the measurements set. Handy!',
},
public: {
description: `Whether or not this pattern can be viewed/used by others`,
@ -288,7 +288,7 @@ export const response = {
example: false,
},
measies: {
description: `The measurements for this person`,
description: `The measurements of this set`,
type: 'object',
example: { neck: 420 },
},
@ -314,7 +314,7 @@ export const response = {
ehash String @unique
ihash String
patterns Pattern[]
people Person[]
people Set[]
*/
id: {
description: `The User's unique ID`,
@ -336,8 +336,8 @@ Also: Introvert 🙊
description: `This field is about data protection. It indicates the level of consent the user has given to process their data.
- \`0\`: No consent given
- \`1\`: Consent given to process account data
- \`2\`: Consent given to process account data and person data
- \`3\`: Consent given to process account data and person data, and use anonymized data for research
- \`2\`: Consent given to process account data and measurement data
- \`3\`: Consent given to process account data and measurement data, and use anonymized data for research
`,
type: 'integer',
enum: [0, 1, 2, 3],
@ -371,7 +371,7 @@ Also: Introvert 🙊
example: 'joostdecock',
},
img: {
description: `An image that was stored with this person`,
description: `An image that was stored with this measurements set`,
type: 'string',
example: 'https://freesewing.org/avatar.svg',
},
@ -486,7 +486,7 @@ export const token = {
export const schemas = {
apikey: response.body.apikey,
pattern: response.body.pattern,
person: response.body.person,
set: response.body.set,
userAccount: response.body.userAccount,
userProfile: response.body.userProfile,
}

View file

@ -10,7 +10,7 @@ import {
} from './lib.mjs'
const common = {
tags: ['People'],
tags: ['Measurements Sets'],
security: [jwt, key],
}
@ -20,7 +20,7 @@ const local = {
in: 'path',
name: 'id',
required: true,
description: "The Person's unique ID",
description: "The Set's unique ID",
schema: {
example: 666,
type: 'integer',
@ -32,12 +32,12 @@ const local = {
// Paths
export const paths = {}
// Create Person
paths['/people/{auth}'] = {
// Create set
paths['/sets/{auth}'] = {
post: {
...common,
summary: 'Create a new Person',
description: 'Creates a new Person and returns it.',
summary: 'Create a new Measurements Set',
description: 'Creates a new Measurements Set and returns it.',
parameters: [parameters.auth],
requestBody: {
required: true,
@ -47,11 +47,11 @@ paths['/people/{auth}'] = {
type: 'object',
properties: {
img: uploadImg,
imperial: response.body.person.properties.imperial,
name: response.body.person.properties.name,
notes: response.body.person.properties.notes,
public: response.body.person.properties.public,
measies: response.body.person.properties.measies,
imperial: response.body.set.properties.imperial,
name: response.body.set.properties.name,
notes: response.body.set.properties.notes,
public: response.body.set.properties.public,
measies: response.body.set.properties.measies,
},
},
},
@ -62,7 +62,7 @@ paths['/people/{auth}'] = {
...response.status['201'],
...jsonResponse({
result: fields.result,
person: response.body.person,
set: response.body.set,
}),
},
400: {
@ -82,25 +82,25 @@ paths['/people/{auth}'] = {
},
}
// Get/Remove Person
paths['/people/{id}/{auth}'] = {
// Get a Person
// Get/Remove Set
paths['/sets/{id}/{auth}'] = {
// Get a Set
get: {
...common,
summary: 'Retrieve a Person',
description: 'Retrieves information about Person `id`.',
summary: 'Retrieve a Measurements Set',
description: 'Retrieves information about Measurements Set `id`.',
parameters: [parameters.auth, local.params.id],
responses: {
200: {
description:
'**Success - Person returned**\n\n' +
'**Success - Measurements Set returned**\n\n' +
'Status code `200` indicates that the resource was returned successfully.',
...jsonResponse({
result: {
...fields.result,
example: 'success',
},
person: response.body.person,
set: response.body.set,
}),
},
401: response.status['401'],
@ -114,11 +114,11 @@ paths['/people/{id}/{auth}'] = {
500: response.status['500'],
},
},
// Update a Person
// Update a Set
patch: {
...common,
summary: 'Update a Person',
description: 'Updates information about Person `id`.',
summary: 'Update a Measurements Set',
description: 'Updates information about Measurements Set `id`.',
parameters: [parameters.auth, local.params.id],
requestBody: {
required: true,
@ -128,11 +128,11 @@ paths['/people/{id}/{auth}'] = {
type: 'object',
properties: {
img: uploadImg,
imperial: response.body.person.properties.imperial,
name: response.body.person.properties.name,
notes: response.body.person.properties.notes,
public: response.body.person.properties.public,
measies: response.body.person.properties.measies,
imperial: response.body.set.properties.imperial,
name: response.body.set.properties.name,
notes: response.body.set.properties.notes,
public: response.body.set.properties.public,
measies: response.body.set.properties.measies,
},
},
},
@ -141,14 +141,14 @@ paths['/people/{id}/{auth}'] = {
responses: {
200: {
description:
'**Success - Person returned**\n\n' +
'**Success - Measurements Set returned**\n\n' +
'Status code `200` indicates that the resource was returned successfully.',
...jsonResponse({
result: {
...fields.result,
example: 'success',
},
person: response.body.person,
set: response.body.set,
}),
},
401: response.status['401'],
@ -162,11 +162,11 @@ paths['/people/{id}/{auth}'] = {
500: response.status['500'],
},
},
// Remove a Person
// Remove a Set
delete: {
...common,
summary: 'Remove a Person',
description: 'Removes the Person `id`.',
summary: 'Remove a Set',
description: 'Removes the Measurements Set `id`.',
parameters: [parameters.auth, local.params.id],
responses: {
204: response.status['204'],
@ -183,19 +183,19 @@ paths['/people/{id}/{auth}'] = {
},
}
// Clone Person
paths['/people/{id}/clone/{auth}'] = {
// Clone a Set
paths['/sets/{id}/clone/{auth}'] = {
post: {
...common,
summary: 'Clone a Person',
description: 'Creates a new Person by cloning an existing one.',
summary: 'Clone a Measurements Set',
description: 'Creates a new Measurments Set by cloning an existing one.',
parameters: [parameters.auth],
responses: {
201: {
...response.status['201'],
...jsonResponse({
result: fields.result,
person: response.body.person,
set: response.body.set,
}),
},
401: response.status['401'],

View file

@ -62,7 +62,7 @@ model User {
password String
patron Int @default(0)
patterns Pattern[]
people Person[]
sets Set[]
role String @default("user")
status Int @default(0)
updatedAt DateTime? @updatedAt
@ -78,18 +78,18 @@ model Pattern {
img String?
name String @default("")
notes String
person Person? @relation(fields: [personId], references: [id])
personId Int?
set Set? @relation(fields: [setId], references: [id])
setId Int?
public Boolean @default(false)
settings String
user User @relation(fields: [userId], references: [id])
userId Int
updatedAt DateTime @updatedAt
@@index([userId, personId])
@@index([userId, setId])
}
model Person {
model Set {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
img String?

View file

@ -50,7 +50,7 @@ const baseConfig = {
},
avatars: {
user: process.env.BACKEND_AVATAR_USER || 'https://freesewing.org/avatar.svg',
person: process.env.BACKEND_AVATAR_PERSON || 'https://freesewing.org/avatar.svg',
set: process.env.BACKEND_AVATAR_SET || 'https://freesewing.org/avatar.svg',
pattern: process.env.BACKEND_AVATAR_PATTERN || 'https://freesewing.org/avatar.svg',
},
db: {

View file

@ -1,58 +0,0 @@
import { PersonModel } from '../models/person.mjs'
export function PeopleController() {}
/*
* Create a person for the authenticated user
* See: https://freesewing.dev/reference/backend/api
*/
PeopleController.prototype.create = async (req, res, tools) => {
const Person = new PersonModel(tools)
await Person.guardedCreate(req)
return Person.sendResponse(res)
}
/*
* Read a person
* See: https://freesewing.dev/reference/backend/api
*/
PeopleController.prototype.read = async (req, res, tools) => {
const Person = new PersonModel(tools)
await Person.guardedRead(req)
return Person.sendResponse(res)
}
/*
* Update a person
* See: https://freesewing.dev/reference/backend/api
*/
PeopleController.prototype.update = async (req, res, tools) => {
const Person = new PersonModel(tools)
await Person.guardedUpdate(req)
return Person.sendResponse(res)
}
/*
* Remove a person
* See: https://freesewing.dev/reference/backend/api
*/
PeopleController.prototype.delete = async (req, res, tools) => {
const Person = new PersonModel(tools)
await Person.guardedDelete(req)
return Person.sendResponse(res)
}
/*
* Clone a person
* See: https://freesewing.dev/reference/backend/api
*/
PeopleController.prototype.clone = async (req, res, tools) => {
const Person = new PersonModel(tools)
await Person.guardedClone(req)
return Person.sendResponse(res)
}

View file

@ -0,0 +1,58 @@
import { SetModel } from '../models/set.mjs'
export function SetsController() {}
/*
* Create a measurements set for the authenticated user
* See: https://freesewing.dev/reference/backend/api
*/
SetsController.prototype.create = async (req, res, tools) => {
const Set = new SetModel(tools)
await Set.guardedCreate(req)
return Set.sendResponse(res)
}
/*
* Read a measurements set
* See: https://freesewing.dev/reference/backend/api
*/
SetsController.prototype.read = async (req, res, tools) => {
const Set = new SetModel(tools)
await Set.guardedRead(req)
return Set.sendResponse(res)
}
/*
* Update a measurements set
* See: https://freesewing.dev/reference/backend/api
*/
SetsController.prototype.update = async (req, res, tools) => {
const Set = new SetModel(tools)
await Set.guardedUpdate(req)
return Set.sendResponse(res)
}
/*
* Remove a measurements set
* See: https://freesewing.dev/reference/backend/api
*/
SetsController.prototype.delete = async (req, res, tools) => {
const Set = new SetModel(tools)
await Set.guardedDelete(req)
return Set.sendResponse(res)
}
/*
* Clone a measurements set
* See: https://freesewing.dev/reference/backend/api
*/
SetsController.prototype.clone = async (req, res, tools) => {
const Set = new SetModel(tools)
await Set.guardedClone(req)
return Set.sendResponse(res)
}

View file

@ -15,8 +15,8 @@ export function PatternModel(tools) {
PatternModel.prototype.guardedCreate = async function ({ body, user }) {
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
if (Object.keys(body).length < 2) return this.setResponse(400, 'postBodyMissing')
if (!body.person) return this.setResponse(400, 'personMissing')
if (typeof body.person !== 'number') return this.setResponse(400, 'personNotNumeric')
if (!body.set) return this.setResponse(400, 'setMissing')
if (typeof body.set !== 'number') return this.setResponse(400, 'setNotNumeric')
if (typeof body.settings !== 'object') return this.setResponse(400, 'settingsNotAnObject')
if (body.data && typeof body.data !== 'object') return this.setResponse(400, 'dataNotAnObject')
if (!body.design && !body.data?.design) return this.setResponse(400, 'designMissing')
@ -25,7 +25,7 @@ PatternModel.prototype.guardedCreate = async function ({ body, user }) {
// Prepare data
const data = {
design: body.design,
personId: body.person,
setId: body.set,
settings: body.settings,
}
// Data (will be encrypted, so always set _some_ value)

View file

@ -1,7 +1,7 @@
import { log } from '../utils/log.mjs'
import { setPersonAvatar } from '../utils/sanity.mjs'
import { setSetAvatar } from '../utils/sanity.mjs'
export function PersonModel(tools) {
export function SetModel(tools) {
this.config = tools.config
this.prisma = tools.prisma
this.decrypt = tools.decrypt
@ -12,7 +12,7 @@ export function PersonModel(tools) {
return this
}
PersonModel.prototype.guardedCreate = async function ({ body, user }) {
SetModel.prototype.guardedCreate = async function ({ body, user }) {
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing')
if (!body.name || typeof body.name !== 'string') return this.setResponse(400, 'nameMissing')
@ -30,7 +30,7 @@ PersonModel.prototype.guardedCreate = async function ({ body, user }) {
data.imperial = body.imperial === true ? true : false
data.userId = user.uid
// Set this one initially as we need the ID to create a custom img via Sanity
data.img = this.config.avatars.person
data.img = this.config.avatars.set
// Create record
await this.unguardedCreate(data)
@ -40,36 +40,36 @@ PersonModel.prototype.guardedCreate = async function ({ body, user }) {
this.config.use.sanity &&
typeof body.img === 'string' &&
(!body.unittest || (body.unittest && this.config.use.tests?.sanity))
? await setPersonAvatar(this.record.id, body.img)
? await setSetAvatar(this.record.id, body.img)
: false
if (img) await this.unguardedUpdate(this.cloak({ img: img.url }))
else await this.read({ id: this.record.id })
return this.setResponse(201, 'created', { person: this.asPerson() })
return this.setResponse(201, 'created', { set: this.asSet() })
}
PersonModel.prototype.unguardedCreate = async function (data) {
SetModel.prototype.unguardedCreate = async function (data) {
try {
this.record = await this.prisma.person.create({ data: this.cloak(data) })
this.record = await this.prisma.set.create({ data: this.cloak(data) })
} catch (err) {
log.warn(err, 'Could not create person')
return this.setResponse(500, 'createPersonFailed')
log.warn(err, 'Could not create set')
return this.setResponse(500, 'createSetFailed')
}
return this
}
/*
* Loads a person from the database based on the where clause you pass it
* Loads a measurements set from the database based on the where clause you pass it
*
* Stores result in this.record
*/
PersonModel.prototype.read = async function (where) {
SetModel.prototype.read = async function (where) {
try {
this.record = await this.prisma.person.findUnique({ where })
this.record = await this.prisma.set.findUnique({ where })
} catch (err) {
log.warn({ err, where }, 'Could not read person')
log.warn({ err, where }, 'Could not read measurements set')
}
this.reveal()
@ -78,12 +78,12 @@ PersonModel.prototype.read = async function (where) {
}
/*
* Loads a person from the database based on the where clause you pass it
* In addition prepares it for returning the person data
* Loads a measurements set from the database based on the where clause you pass it
* In addition prepares it for returning the set data
*
* Stores result in this.record
*/
PersonModel.prototype.guardedRead = async function ({ params, user }) {
SetModel.prototype.guardedRead = async function ({ params, user }) {
if (user.level < 1) return this.setResponse(403, 'insufficientAccessLevel')
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
@ -94,17 +94,17 @@ PersonModel.prototype.guardedRead = async function ({ params, user }) {
return this.setResponse(200, false, {
result: 'success',
person: this.asPerson(),
set: this.asSet(),
})
}
/*
* Clones a person
* In addition prepares it for returning the person data
* Clones a measurements set
* In addition prepares it for returning the set data
*
* Stores result in this.record
*/
PersonModel.prototype.guardedClone = async function ({ params, user }) {
SetModel.prototype.guardedClone = async function ({ params, user }) {
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
@ -113,11 +113,11 @@ PersonModel.prototype.guardedClone = async function ({ params, user }) {
return this.setResponse(403, 'insufficientAccessLevel')
}
// Clone person
const data = this.asPerson()
// Clone set
const data = this.asSet()
delete data.id
data.name += ` (cloned from #${this.record.id})`
data.notes += ` (Note: This person was cloned from person #${this.record.id})`
data.notes += ` (Note: This measurements set was cloned from set #${this.record.id})`
await this.unguardedCreate(data)
// Update unencrypted data
@ -125,14 +125,14 @@ PersonModel.prototype.guardedClone = async function ({ params, user }) {
return this.setResponse(200, false, {
result: 'success',
person: this.asPerson(),
set: this.asSet(),
})
}
/*
* Helper method to decrypt at-rest data
*/
PersonModel.prototype.reveal = async function () {
SetModel.prototype.reveal = async function () {
this.clear = {}
if (this.record) {
for (const field of this.encryptedFields) {
@ -146,7 +146,7 @@ PersonModel.prototype.reveal = async function () {
/*
* Helper method to encrypt at-rest data
*/
PersonModel.prototype.cloak = function (data) {
SetModel.prototype.cloak = function (data) {
for (const field of this.encryptedFields) {
if (typeof data[field] !== 'undefined') {
data[field] = this.encrypt(data[field])
@ -162,26 +162,26 @@ PersonModel.prototype.cloak = function (data) {
*
* Stores result in this.exists
*/
PersonModel.prototype.setExists = function () {
SetModel.prototype.setExists = function () {
this.exists = this.record ? true : false
return this
}
/*
* Updates the person data - Used when we create the data ourselves
* Updates the set data - Used when we create the data ourselves
* so we know it's safe
*/
PersonModel.prototype.unguardedUpdate = async function (data) {
SetModel.prototype.unguardedUpdate = async function (data) {
try {
this.record = await this.prisma.person.update({
this.record = await this.prisma.set.update({
where: { id: this.record.id },
data,
})
} catch (err) {
log.warn(err, 'Could not update person record')
log.warn(err, 'Could not update set record')
process.exit()
return this.setResponse(500, 'updatePersonFailed')
return this.setResponse(500, 'updateSetFailed')
}
await this.reveal()
@ -189,10 +189,10 @@ PersonModel.prototype.unguardedUpdate = async function (data) {
}
/*
* Updates the person data - Used when we pass through user-provided data
* Updates the set data - Used when we pass through user-provided data
* so we can't be certain it's safe
*/
PersonModel.prototype.guardedUpdate = async function ({ params, body, user }) {
SetModel.prototype.guardedUpdate = async function ({ params, body, user }) {
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
await this.read({ id: parseInt(params.id) })
@ -224,21 +224,21 @@ PersonModel.prototype.guardedUpdate = async function ({ params, body, user }) {
// Image (img)
if (typeof body.img === 'string') {
const img = await setPersonAvatar(params.id, body.img)
const img = await setSetAvatar(params.id, body.img)
data.img = img.url
}
// Now update the record
await this.unguardedUpdate(this.cloak(data))
return this.setResponse(200, false, { person: this.asPerson() })
return this.setResponse(200, false, { set: this.asSet() })
}
/*
* Removes the person - No questions asked
* Removes the set - No questions asked
*/
PersonModel.prototype.unguardedDelete = async function () {
await this.prisma.person.delete({ here: { id: this.record.id } })
SetModel.prototype.unguardedDelete = async function () {
await this.prisma.set.delete({ here: { id: this.record.id } })
this.record = null
this.clear = null
@ -246,9 +246,9 @@ PersonModel.prototype.unguardedDelete = async function () {
}
/*
* Removes the person - Checks permissions
* Removes the set - Checks permissions
*/
PersonModel.prototype.guardedDelete = async function ({ params, body, user }) {
SetModel.prototype.guardedDelete = async function ({ params, body, user }) {
if (user.level < 3) return this.setResponse(403, 'insufficientAccessLevel')
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
@ -265,7 +265,7 @@ PersonModel.prototype.guardedDelete = async function ({ params, body, user }) {
/*
* Returns record data
*/
PersonModel.prototype.asPerson = function () {
SetModel.prototype.asSet = function () {
return {
...this.record,
...this.clear,
@ -277,7 +277,7 @@ PersonModel.prototype.asPerson = function () {
*
* Will be used by this.sendResponse()
*/
PersonModel.prototype.setResponse = function (status = 200, error = false, data = {}) {
SetModel.prototype.setResponse = function (status = 200, error = false, data = {}) {
this.response = {
status,
body: {
@ -297,7 +297,7 @@ PersonModel.prototype.setResponse = function (status = 200, error = false, data
/*
* Helper method to send response
*/
PersonModel.prototype.sendResponse = async function (res) {
SetModel.prototype.sendResponse = async function (res) {
return res.status(this.response.status).send(this.response.body)
}
@ -331,7 +331,7 @@ PersonModel.prototype.sendResponse = async function (res) {
//}
/* Helper method to parse user-supplied measurements */
PersonModel.prototype.sanitizeMeasurements = function (input) {
SetModel.prototype.sanitizeMeasurements = function (input) {
const measies = {}
if (typeof input !== 'object') return measies
for (const [m, val] of Object.entries(input)) {

View file

@ -151,7 +151,7 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
await this.read({ ehash })
if (this.exists) {
/*
* User already exists. However, if we return an error, then people can
* User already exists. However, if we return an error, then baddies can
* spam the signup endpoint to figure out who has a FreeSewing account
* which would be a privacy leak. So instead, pretend there is no user
* with that account, and that signup is proceeding as normal.
@ -253,7 +253,6 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
},
userId: this.record.id,
})
console.log(check)
// Send signup email
if (!this.isUnitTest(body) || this.config.tests.sendEmail)
@ -497,7 +496,8 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
result: 'success',
account: this.asAccount(),
}
if (isUnitTest) returnData.confirmation = this.Confirmation.record.id
if (isUnitTest && this.Confirmation.record?.id)
returnData.confirmation = this.Confirmation.record.id
return this.setResponse(200, false, returnData)
}
@ -519,7 +519,6 @@ UserModel.prototype.guardedMfaUpdate = async function ({ body, user, ip }) {
// Check password
const [valid] = verifyPassword(body.password, this.record.password)
if (!valid) {
console.log('password check failed')
log.warn(`Wrong password for existing user while disabling MFA: ${user.uid} from ${ip}`)
return this.setResponse(401, 'authenticationFailed')
}
@ -537,7 +536,6 @@ UserModel.prototype.guardedMfaUpdate = async function ({ body, user, ip }) {
account: this.asAccount(),
})
} else {
console.log('token check failed')
return this.setResponse(401, 'authenticationFailed')
}
}
@ -713,7 +711,7 @@ UserModel.prototype.isLusernameAvailable = async function (lusername) {
user = await this.prisma.user.findUnique({ where: { lusername } })
} catch (err) {
log.warn({ err, lusername }, 'Could not search for free username')
return true
return false
}
if (user) return false

View file

@ -19,7 +19,7 @@ export function apikeysRoutes(tools) {
app.get('/apikeys/jwt', passport.authenticate(...jwt), (req, res) =>
Apikeys.list(req, res, tools)
)
app.get('/apikeys/:id/key', passport.authenticate(...bsc), (req, res) =>
app.get('/apikeys/key', passport.authenticate(...bsc), (req, res) =>
Apikeys.list(req, res, tools)
)

View file

@ -1,13 +1,13 @@
import { apikeysRoutes } from './apikeys.mjs'
import { usersRoutes } from './users.mjs'
import { peopleRoutes } from './people.mjs'
import { setsRoutes } from './sets.mjs'
import { patternsRoutes } from './patterns.mjs'
import { confirmationsRoutes } from './confirmations.mjs'
export const routes = {
apikeysRoutes,
usersRoutes,
peopleRoutes,
setsRoutes,
patternsRoutes,
confirmationsRoutes,
}

View file

@ -1,49 +0,0 @@
import { PeopleController } from '../controllers/people.mjs'
const People = new PeopleController()
const jwt = ['jwt', { session: false }]
const bsc = ['basic', { session: false }]
export function peopleRoutes(tools) {
const { app, passport } = tools
// Create person
app.post('/people/jwt', passport.authenticate(...jwt), (req, res) =>
People.create(req, res, tools)
)
app.post('/people/key', passport.authenticate(...bsc), (req, res) =>
People.create(req, res, tools)
)
// Clone person
app.post('/people/:id/clone/jwt', passport.authenticate(...jwt), (req, res) =>
People.clone(req, res, tools)
)
app.post('/people/:id/clone/key', passport.authenticate(...bsc), (req, res) =>
People.clone(req, res, tools)
)
// Read person
app.get('/people/:id/jwt', passport.authenticate(...jwt), (req, res) =>
People.read(req, res, tools)
)
app.get('/people/:id/key', passport.authenticate(...bsc), (req, res) =>
People.read(req, res, tools)
)
// Update person
app.patch('/people/:id/jwt', passport.authenticate(...jwt), (req, res) =>
People.update(req, res, tools)
)
app.patch('/people/:id/key', passport.authenticate(...bsc), (req, res) =>
People.update(req, res, tools)
)
// Delete person
app.delete('/people/:id/jwt', passport.authenticate(...jwt), (req, res) =>
People.delete(req, res, tools)
)
app.delete('/people/:id/key', passport.authenticate(...bsc), (req, res) =>
People.delete(req, res, tools)
)
}

View file

@ -0,0 +1,41 @@
import { SetsController } from '../controllers/sets.mjs'
const Sets = new SetsController()
const jwt = ['jwt', { session: false }]
const bsc = ['basic', { session: false }]
export function setsRoutes(tools) {
const { app, passport } = tools
// Create a measurments set
app.post('/sets/jwt', passport.authenticate(...jwt), (req, res) => Sets.create(req, res, tools))
app.post('/sets/key', passport.authenticate(...bsc), (req, res) => Sets.create(req, res, tools))
// Clone a measurements set
app.post('/sets/:id/clone/jwt', passport.authenticate(...jwt), (req, res) =>
Sets.clone(req, res, tools)
)
app.post('/sets/:id/clone/key', passport.authenticate(...bsc), (req, res) =>
Sets.clone(req, res, tools)
)
// Read a measurments set
app.get('/sets/:id/jwt', passport.authenticate(...jwt), (req, res) => Sets.read(req, res, tools))
app.get('/sets/:id/key', passport.authenticate(...bsc), (req, res) => Sets.read(req, res, tools))
// Update a measurements set
app.patch('/sets/:id/jwt', passport.authenticate(...jwt), (req, res) =>
Sets.update(req, res, tools)
)
app.patch('/sets/:id/key', passport.authenticate(...bsc), (req, res) =>
Sets.update(req, res, tools)
)
// Delete a measurements set
app.delete('/sets/:id/jwt', passport.authenticate(...jwt), (req, res) =>
Sets.delete(req, res, tools)
)
app.delete('/sets/:id/key', passport.authenticate(...bsc), (req, res) =>
Sets.delete(req, res, tools)
)
}

View file

@ -15,7 +15,7 @@ function imageUrl(ref) {
* Retrieval of avatar images from the Sanity API
*/
export const getUserAvatar = async (id) => getAvatar('user', id)
export const getPersonAvatar = async (id) => getAvatar('person', id)
export const getSetAvatar = async (id) => getAvatar('set', id)
async function getAvatar(type, id) {
const url =
`${config.api}/data/query/${config.dataset}?query=` +
@ -32,7 +32,7 @@ async function getAvatar(type, id) {
* Uploads an image to sanity
*/
export const setUserAvatar = async (id, data) => setAvatar('user', id, data)
export const setPersonAvatar = async (id, data) => setAvatar('person', id, data)
export const setSetAvatar = async (id, data) => setAvatar('set', id, data)
export const setPatternAvatar = async (id, data) => setAvatar('pattern', id, data)
export async function setAvatar(type, id, data) {
// Step 1: Upload the image as asset

View file

@ -2,7 +2,7 @@ import { userTests } from './user.mjs'
import { mfaTests } from './mfa.mjs'
import { accountTests } from './account.mjs'
import { apikeyTests } from './apikey.mjs'
import { personTests } from './person.mjs'
import { setTests } from './set.mjs'
import { patternTests } from './pattern.mjs'
import { setup } from './shared.mjs'
@ -11,7 +11,7 @@ const runTests = async (...params) => {
await mfaTests(...params)
await apikeyTests(...params)
await accountTests(...params)
await personTests(...params)
await setTests(...params)
await patternTests(...params)
}

View file

@ -25,7 +25,7 @@ export const patternTests = async (chai, config, expect, store) => {
name: 'Just a test',
notes: 'These are my notes',
public: true,
person: store.account.people.her.id,
set: store.account.sets.her.id,
data: {
some: 'value',
},
@ -37,7 +37,7 @@ export const patternTests = async (chai, config, expect, store) => {
expect(res.body.result).to.equal(`success`)
expect(typeof res.body.pattern?.id).to.equal('number')
expect(res.body.pattern.userId).to.equal(store.account.id)
expect(res.body.pattern.personId).to.equal(store.account.people.her.id)
expect(res.body.pattern.setId).to.equal(store.account.sets.her.id)
expect(res.body.pattern.design).to.equal('aaron')
expect(res.body.pattern.public).to.equal(true)
store.account.patterns[auth] = res.body.pattern
@ -73,7 +73,7 @@ export const patternTests = async (chai, config, expect, store) => {
})
}
it(`${store.icon('person', auth)} Should update the public field (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should update the public field (${auth})`, (done) => {
chai
.request(config.api)
.patch(`/patterns/${store.account.patterns[auth].id}/${auth}`)
@ -96,7 +96,7 @@ export const patternTests = async (chai, config, expect, store) => {
})
})
it(`${store.icon('person', auth)} Should not update the design field (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should not update the design field (${auth})`, (done) => {
chai
.request(config.api)
.patch(`/patterns/${store.account.patterns[auth].id}/${auth}`)
@ -119,7 +119,7 @@ export const patternTests = async (chai, config, expect, store) => {
})
})
it(`${store.icon('person', auth)} Should not update the person field (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should not update the set field (${auth})`, (done) => {
chai
.request(config.api)
.patch(`/patterns/${store.account.patterns[auth].id}/${auth}`)
@ -132,18 +132,18 @@ export const patternTests = async (chai, config, expect, store) => {
'base64'
)
)
.send({ person: 1 })
.send({ set: 1 })
.end((err, res) => {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(res.body.pattern.personId).to.equal(store.account.people.her.id)
expect(res.body.pattern.setId).to.equal(store.account.sets.her.id)
done()
})
})
for (const field of ['data', 'settings']) {
it(`${store.icon('person', auth)} Should update the ${field} field (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should update the ${field} field (${auth})`, (done) => {
const data = {}
data[field] = { test: { value: 'hello' } }
chai
@ -192,7 +192,7 @@ export const patternTests = async (chai, config, expect, store) => {
})
it(`${store.icon(
'person',
'set',
auth
)} Should not allow reading another user's pattern (${auth})`, (done) => {
chai
@ -217,7 +217,7 @@ export const patternTests = async (chai, config, expect, store) => {
})
it(`${store.icon(
'person',
'set',
auth
)} Should not allow updating another user's pattern (${auth})`, (done) => {
chai
@ -245,7 +245,7 @@ export const patternTests = async (chai, config, expect, store) => {
})
it(`${store.icon(
'person',
'set',
auth
)} Should not allow removing another user's pattern (${auth})`, (done) => {
chai
@ -270,10 +270,10 @@ export const patternTests = async (chai, config, expect, store) => {
})
/*
it(`${store.icon('person', auth)} Should clone a person (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should clone a set (${auth})`, (done) => {
chai
.request(config.api)
.post(`/people/${store.person[auth].id}/clone/${auth}`)
.post(`/sets/${store.set[auth].id}/clone/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -288,18 +288,18 @@ export const patternTests = async (chai, config, expect, store) => {
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(typeof res.body.error).to.equal(`undefined`)
expect(typeof res.body.person.id).to.equal(`number`)
expect(typeof res.body.set.id).to.equal(`number`)
done()
})
})
it(`${store.icon(
'person',
'set',
auth
)} Should (not) clone a public person across accounts (${auth})`, (done) => {
)} Should (not) clone a public set across accounts (${auth})`, (done) => {
chai
.request(config.api)
.post(`/people/${store.person[auth].id}/clone/${auth}`)
.post(`/sets/${store.set[auth].id}/clone/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -310,12 +310,12 @@ export const patternTests = async (chai, config, expect, store) => {
).toString('base64')
)
.end((err, res) => {
if (store.person[auth].public) {
if (store.set[auth].public) {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(typeof res.body.error).to.equal(`undefined`)
expect(typeof res.body.person.id).to.equal(`number`)
expect(typeof res.body.set.id).to.equal(`number`)
} else {
expect(err === null).to.equal(true)
expect(res.status).to.equal(403)
@ -327,8 +327,8 @@ export const patternTests = async (chai, config, expect, store) => {
})
// TODO:
// - Clone person
// - Clone person accross accounts of they are public
// - Clone set
// - Clone set accross accounts of they are public
*/
})
}

View file

@ -1,6 +1,6 @@
import { cat } from './cat.mjs'
export const personTests = async (chai, config, expect, store) => {
export const setTests = async (chai, config, expect, store) => {
const data = {
jwt: {
name: 'Joost',
@ -26,21 +26,21 @@ export const personTests = async (chai, config, expect, store) => {
imperial: false,
},
}
store.person = {
store.set = {
jwt: {},
key: {},
}
store.altperson = {
store.altset = {
jwt: {},
key: {},
}
for (const auth of ['jwt', 'key']) {
describe(`${store.icon('person', auth)} Person tests (${auth})`, () => {
step(`${store.icon('person', auth)} Should create a new person (${auth})`, (done) => {
describe(`${store.icon('set', auth)} Set tests (${auth})`, () => {
step(`${store.icon('set', auth)} Should create a new set (${auth})`, (done) => {
chai
.request(config.api)
.post(`/people/${auth}`)
.post(`/sets/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -57,21 +57,21 @@ export const personTests = async (chai, config, expect, store) => {
expect(res.body.result).to.equal(`success`)
for (const [key, val] of Object.entries(data[auth])) {
if (!['measies', 'img', 'unittest'].includes(key))
expect(res.body.person[key]).to.equal(val)
expect(res.body.set[key]).to.equal(val)
}
store.person[auth] = res.body.person
store.set[auth] = res.body.set
done()
})
}).timeout(5000)
for (const field of ['name', 'notes']) {
it(`${store.icon('person', auth)} Should update the ${field} field (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should update the ${field} field (${auth})`, (done) => {
const data = {}
const val = store.person[auth][field] + '_updated'
const val = store.set[auth][field] + '_updated'
data[field] = val
chai
.request(config.api)
.patch(`/people/${store.person[auth].id}/${auth}`)
.patch(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -86,20 +86,20 @@ export const personTests = async (chai, config, expect, store) => {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(res.body.person[field]).to.equal(val)
expect(res.body.set[field]).to.equal(val)
done()
})
})
}
for (const field of ['imperial', 'public']) {
it(`${store.icon('person', auth)} Should update the ${field} field (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should update the ${field} field (${auth})`, (done) => {
const data = {}
const val = !store.person[auth][field]
const val = !store.set[auth][field]
data[field] = val
chai
.request(config.api)
.patch(`/people/${store.person[auth].id}/${auth}`)
.patch(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -114,8 +114,8 @@ export const personTests = async (chai, config, expect, store) => {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(res.body.person[field]).to.equal(val)
store.person[auth][field] = val
expect(res.body.set[field]).to.equal(val)
store.set[auth][field] = val
done()
})
})
@ -123,7 +123,7 @@ export const personTests = async (chai, config, expect, store) => {
for (const field of ['chest', 'neck', 'ankle']) {
it(`${store.icon(
'person',
'set',
auth
)} Should update the ${field} measurement (${auth})`, (done) => {
const data = { measies: {} }
@ -131,7 +131,7 @@ export const personTests = async (chai, config, expect, store) => {
data.measies[field] = val
chai
.request(config.api)
.patch(`/people/${store.person[auth].id}/${auth}`)
.patch(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -146,19 +146,19 @@ export const personTests = async (chai, config, expect, store) => {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(res.body.person.measies[field]).to.equal(val)
expect(res.body.set.measies[field]).to.equal(val)
done()
})
})
}
it(`${store.icon(
'person',
'set',
auth
)} Should not set an non-existing measurement (${auth})`, (done) => {
chai
.request(config.api)
.patch(`/people/${store.person[auth].id}/${auth}`)
.patch(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -178,16 +178,16 @@ export const personTests = async (chai, config, expect, store) => {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(res.body.person.measies.ankle).to.equal(320)
expect(typeof res.body.person.measies.potatoe).to.equal('undefined')
expect(res.body.set.measies.ankle).to.equal(320)
expect(typeof res.body.set.measies.potatoe).to.equal('undefined')
done()
})
})
it(`${store.icon('person', auth)} Should clear a measurement (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should clear a measurement (${auth})`, (done) => {
chai
.request(config.api)
.patch(`/people/${store.person[auth].id}/${auth}`)
.patch(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -206,15 +206,15 @@ export const personTests = async (chai, config, expect, store) => {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(typeof res.body.person.measies.chest).to.equal('undefined')
expect(typeof res.body.set.measies.chest).to.equal('undefined')
done()
})
})
it(`${store.icon('person', auth)} Should read a person (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should read a set (${auth})`, (done) => {
chai
.request(config.api)
.get(`/people/${store.person[auth].id}/${auth}`)
.get(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -228,18 +228,18 @@ export const personTests = async (chai, config, expect, store) => {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(typeof res.body.person.measies).to.equal('object')
expect(typeof res.body.set.measies).to.equal('object')
done()
})
})
it(`${store.icon(
'person',
'set',
auth
)} Should not allow reading another user's person (${auth})`, (done) => {
)} Should not allow reading another user's set (${auth})`, (done) => {
chai
.request(config.api)
.get(`/people/${store.person[auth].id}/${auth}`)
.get(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -259,12 +259,12 @@ export const personTests = async (chai, config, expect, store) => {
})
it(`${store.icon(
'person',
'set',
auth
)} Should not allow updating another user's person (${auth})`, (done) => {
)} Should not allow updating another user's set (${auth})`, (done) => {
chai
.request(config.api)
.patch(`/people/${store.person[auth].id}/${auth}`)
.patch(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -287,12 +287,12 @@ export const personTests = async (chai, config, expect, store) => {
})
it(`${store.icon(
'person',
'set',
auth
)} Should not allow removing another user's person (${auth})`, (done) => {
)} Should not allow removing another user's set (${auth})`, (done) => {
chai
.request(config.api)
.delete(`/people/${store.person[auth].id}/${auth}`)
.delete(`/sets/${store.set[auth].id}/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -311,10 +311,10 @@ export const personTests = async (chai, config, expect, store) => {
})
})
it(`${store.icon('person', auth)} Should clone a person (${auth})`, (done) => {
it(`${store.icon('set', auth)} Should clone a set (${auth})`, (done) => {
chai
.request(config.api)
.post(`/people/${store.person[auth].id}/clone/${auth}`)
.post(`/sets/${store.set[auth].id}/clone/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -329,18 +329,18 @@ export const personTests = async (chai, config, expect, store) => {
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(typeof res.body.error).to.equal(`undefined`)
expect(typeof res.body.person.id).to.equal(`number`)
expect(typeof res.body.set.id).to.equal(`number`)
done()
})
})
it(`${store.icon(
'person',
'set',
auth
)} Should (not) clone a public person across accounts (${auth})`, (done) => {
)} Should (not) clone a public set across accounts (${auth})`, (done) => {
chai
.request(config.api)
.post(`/people/${store.person[auth].id}/clone/${auth}`)
.post(`/sets/${store.set[auth].id}/clone/${auth}`)
.set(
'Authorization',
auth === 'jwt'
@ -351,12 +351,12 @@ export const personTests = async (chai, config, expect, store) => {
).toString('base64')
)
.end((err, res) => {
if (store.person[auth].public) {
if (store.set[auth].public) {
expect(err === null).to.equal(true)
expect(res.status).to.equal(200)
expect(res.body.result).to.equal(`success`)
expect(typeof res.body.error).to.equal(`undefined`)
expect(typeof res.body.person.id).to.equal(`number`)
expect(typeof res.body.set.id).to.equal(`number`)
} else {
expect(err === null).to.equal(true)
expect(res.status).to.equal(403)
@ -368,8 +368,8 @@ export const personTests = async (chai, config, expect, store) => {
})
// TODO:
// - Clone person
// - Clone person accross accounts of they are public
// - Clone set
// - Clone set accross accounts of they are public
})
}
}

View file

@ -14,7 +14,7 @@ dotenv.config()
const config = verifyConfig(true)
const expect = chai.expect
chai.use(http)
const people = { her, him }
const sets = { her, him }
export const setup = async () => {
// Initial store contents
@ -26,20 +26,20 @@ export const setup = async () => {
email: `test_${randomString()}@${config.tests.domain}`,
language: 'en',
password: randomString(),
people: {},
sets: {},
},
altaccount: {
email: `test_${randomString()}@${config.tests.domain}`,
language: 'en',
password: randomString(),
people: {},
sets: {},
},
icons: {
user: '🧑 ',
mfa: '🔒 ',
jwt: '🎫 ',
key: '🎟️ ',
person: '🧕 ',
set: '🧕 ',
pattern: '👕 ',
},
randomString,
@ -95,15 +95,15 @@ export const setup = async () => {
}
store[acc].apikey = result.data.apikey
// Create people key
for (const name in people) {
// Create sets key
for (const name in sets) {
try {
result = await axios.post(
`${store.config.api}/people/jwt`,
`${store.config.api}/sets/jwt`,
{
name: `This is ${name} name`,
notes: `These are ${name} notes`,
measies: people[name],
measies: sets[name],
},
{
headers: {
@ -115,7 +115,7 @@ export const setup = async () => {
console.log('Failed at API key creation request', err)
process.exit()
}
store[acc].people[name] = result.data.person
store[acc].sets[name] = result.data.set
}
}

View file

@ -265,7 +265,8 @@ export const userTests = async (chai, config, expect, store) => {
it(`${store.icon('user')} Should find a username is available`, (done) => {
chai
.request(config.api)
.post(`/available/username`)
.post(`/available/username/jwt`)
.set('Authorization', 'Bearer ' + store.token)
.send({
username: 'haochi',
})
@ -278,7 +279,8 @@ export const userTests = async (chai, config, expect, store) => {
it(`${store.icon('user')} Should find a username is not available`, (done) => {
chai
.request(config.api)
.post(`/available/username`)
.post(`/available/username/jwt`)
.set('Authorization', 'Bearer ' + store.token)
.send({
username: store.account.username,
})