wip(backend): Person creation
This commit is contained in:
parent
41c35b2608
commit
2e938ac29f
5 changed files with 157 additions and 108 deletions
|
@ -82,6 +82,7 @@ model Pattern {
|
|||
model Person {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
img String @default("https://freesewing.org/avatar.svg")
|
||||
name String @default("")
|
||||
notes String @default("")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
|
|
@ -18,21 +18,30 @@ const envToBool = (input = 'no') => {
|
|||
|
||||
// Construct config object
|
||||
const config = {
|
||||
// Feature flags
|
||||
use: {
|
||||
github: envToBool(process.env.BACKEND_ENABLE_GITHUB),
|
||||
oauth: {
|
||||
github: envToBool(process.env.BACKEND_ENABLE_OAUTH_GITHUB),
|
||||
google: envToBool(process.env.BACKEND_ENABLE_OAUTH_GOOGLE),
|
||||
},
|
||||
sanity: envToBool(process.env.BACKEND_ENABLE_SANITY),
|
||||
ses: envToBool(process.env.BACKEND_ENABLE_AWS_SES),
|
||||
tests: {
|
||||
base: envToBool(process.env.BACKEND_ENABLE_TESTS),
|
||||
email: envToBool(process.env.BACKEND_ENABLE_TESTS_EMAIL),
|
||||
sanity: envToBool(process.env.BACKEND_ENABLE_TESTS_SANITY),
|
||||
},
|
||||
},
|
||||
// Config
|
||||
api,
|
||||
port,
|
||||
website: {
|
||||
domain: process.env.BACKEND_WEBSITE_DOMAIN || 'freesewing.org',
|
||||
scheme: process.env.BACKEND_WEBSITE_SCHEME || 'https',
|
||||
apikeys: {
|
||||
levels: [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||
expiryMaxSeconds: 365 * 24 * 3600,
|
||||
},
|
||||
db: {
|
||||
url: process.env.BACKEND_DB_URL,
|
||||
},
|
||||
tests: {
|
||||
allow: envToBool(process.env.BACKEND_TEST_ALLOW),
|
||||
domain: process.env.BACKEND_TEST_DOMAIN || 'freesewing.dev',
|
||||
sendEmail: envToBool(process.env.BACKEND_TEST_SEND_EMAIL),
|
||||
includeSanity: envToBool(process.env.BACKEND_TEST_SANITY),
|
||||
},
|
||||
encryption: {
|
||||
key: process.env.BACKEND_ENC_KEY,
|
||||
},
|
||||
|
@ -42,10 +51,9 @@ const config = {
|
|||
audience: process.env.BACKEND_JWT_ISSUER || 'freesewing.org',
|
||||
expiresIn: process.env.BACKEND_JWT_EXPIRY || '7d',
|
||||
},
|
||||
apikeys: {
|
||||
levels: [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||
expiryMaxSeconds: 365 * 24 * 3600,
|
||||
},
|
||||
languages: ['en', 'de', 'es', 'fr', 'nl'],
|
||||
measies: measurements,
|
||||
port,
|
||||
roles: {
|
||||
levels: {
|
||||
user: 4,
|
||||
|
@ -55,49 +63,21 @@ const config = {
|
|||
},
|
||||
base: 'user',
|
||||
},
|
||||
languages: ['en', 'de', 'es', 'fr', 'nl'],
|
||||
measies: measurements,
|
||||
aws: {
|
||||
ses: {
|
||||
region: process.env.BACKEND_AWS_SES_REGION || 'us-east-1',
|
||||
from: process.env.BACKEND_AWS_SES_FROM || 'FreeSewing <info@freesewing.org>',
|
||||
replyTo: process.env.BACKEND_AWS_SES_REPLY_TO
|
||||
? JSON.parse(process.env.BACKEND_AWS_SES_REPLY_TO)
|
||||
: ['FreeSewing <info@freesewing.org>'],
|
||||
feedback: process.env.BACKEND_AWS_SES_FEEDBACK,
|
||||
cc: process.env.BACKEND_AWS_SES_CC ? JSON.parse(process.env.BACKEND_AWS_SES_CC) : [],
|
||||
bcc: process.env.BACKEND_AWS_SES_BCC
|
||||
? JSON.parse(process.env.BACKEND_AWS_SES_BCC)
|
||||
: ['FreeSewing records <records@freesewing.org>'],
|
||||
},
|
||||
website: {
|
||||
domain: process.env.BACKEND_WEBSITE_DOMAIN || 'freesewing.org',
|
||||
scheme: process.env.BACKEND_WEBSITE_SCHEME || 'https',
|
||||
},
|
||||
sanity: {
|
||||
use: process.env.BACKEND_USE_SANITY || false,
|
||||
project: process.env.SANITY_PROJECT,
|
||||
dataset: process.env.SANITY_DATASET || 'production',
|
||||
token: process.env.SANITY_TOKEN,
|
||||
version: process.env.SANITY_VERSION || 'v2022-10-31',
|
||||
api: `https://${process.env.SANITY_PROJECT || 'missing-project-id'}.api.sanity.io/${
|
||||
process.env.SANITY_VERSION || 'v2022-10-31'
|
||||
}`,
|
||||
},
|
||||
oauth: {
|
||||
github: {
|
||||
clientId: process.env.BACKEND_OAUTH_GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.BACKEND_OAUTH_GITHUB_CLIENT_SECRET,
|
||||
tokenUri: 'https://github.com/login/oauth/access_token',
|
||||
dataUri: 'https://api.github.com/user',
|
||||
emailUri: 'https://api.github.com/user/emails',
|
||||
},
|
||||
google: {
|
||||
clientId: process.env.BACKEND_OAUTH_GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.BACKEND_OAUTH_GOOGLE_CLIENT_SECRET,
|
||||
tokenUri: 'https://oauth2.googleapis.com/token',
|
||||
dataUri:
|
||||
'https://people.googleapis.com/v1/people/me?personFields=emailAddresses,names,photos',
|
||||
},
|
||||
},
|
||||
github: {
|
||||
oauth: {},
|
||||
github: {},
|
||||
}
|
||||
|
||||
/*
|
||||
* Config behind feature flags
|
||||
*/
|
||||
|
||||
// Github config
|
||||
if (config.use.github)
|
||||
config.github = {
|
||||
token: process.env.BACKEND_GITHUB_TOKEN,
|
||||
api: 'https://api.github.com',
|
||||
bot: {
|
||||
|
@ -125,11 +105,64 @@ const config = {
|
|||
},
|
||||
dflt: [process.env.BACKEND_GITHUB_NOTIFY_DEFAULT_USER || 'joostdecock'],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Stand-alone config
|
||||
export const sanity = config.sanity
|
||||
// Unit test config
|
||||
if (config.use.tests.base)
|
||||
config.tests = {
|
||||
domain: process.env.BACKEND_TEST_DOMAIN || 'freesewing.dev',
|
||||
}
|
||||
|
||||
// Sanity config
|
||||
if (config.use.sanity)
|
||||
config.sanity = {
|
||||
project: process.env.SANITY_PROJECT,
|
||||
dataset: process.env.SANITY_DATASET || 'production',
|
||||
token: process.env.SANITY_TOKEN,
|
||||
version: process.env.SANITY_VERSION || 'v2022-10-31',
|
||||
api: `https://${process.env.SANITY_PROJECT || 'missing-project-id'}.api.sanity.io/${
|
||||
process.env.SANITY_VERSION || 'v2022-10-31'
|
||||
}`,
|
||||
}
|
||||
|
||||
// AWS SES config (for sending out emails)
|
||||
if (config.use.ses)
|
||||
config.aws = {
|
||||
ses: {
|
||||
region: process.env.BACKEND_AWS_SES_REGION || 'us-east-1',
|
||||
from: process.env.BACKEND_AWS_SES_FROM || 'FreeSewing <info@freesewing.org>',
|
||||
replyTo: process.env.BACKEND_AWS_SES_REPLY_TO
|
||||
? JSON.parse(process.env.BACKEND_AWS_SES_REPLY_TO)
|
||||
: ['FreeSewing <info@freesewing.org>'],
|
||||
feedback: process.env.BACKEND_AWS_SES_FEEDBACK,
|
||||
cc: process.env.BACKEND_AWS_SES_CC ? JSON.parse(process.env.BACKEND_AWS_SES_CC) : [],
|
||||
bcc: process.env.BACKEND_AWS_SES_BCC
|
||||
? JSON.parse(process.env.BACKEND_AWS_SES_BCC)
|
||||
: ['FreeSewing records <records@freesewing.org>'],
|
||||
},
|
||||
}
|
||||
|
||||
// Oauth config for Github as a provider
|
||||
if (config.use.oauth?.github)
|
||||
config.oauth.github = {
|
||||
clientId: process.env.BACKEND_OAUTH_GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.BACKEND_OAUTH_GITHUB_CLIENT_SECRET,
|
||||
tokenUri: 'https://github.com/login/oauth/access_token',
|
||||
dataUri: 'https://api.github.com/user',
|
||||
emailUri: 'https://api.github.com/user/emails',
|
||||
}
|
||||
|
||||
// Oauth config for Google as a provider
|
||||
if (config.use.oauth?.google)
|
||||
config.oauth.google = {
|
||||
clientId: process.env.BACKEND_OAUTH_GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.BACKEND_OAUTH_GOOGLE_CLIENT_SECRET,
|
||||
tokenUri: 'https://oauth2.googleapis.com/token',
|
||||
dataUri: 'https://people.googleapis.com/v1/people/me?personFields=emailAddresses,names,photos',
|
||||
}
|
||||
|
||||
// Exporting this stand-alone config
|
||||
export const sanity = config.sanity || {}
|
||||
export const website = config.website
|
||||
|
||||
const vars = {
|
||||
|
|
|
@ -6,6 +6,7 @@ export function PersonModel(tools) {
|
|||
this.prisma = tools.prisma
|
||||
this.decrypt = tools.decrypt
|
||||
this.encrypt = tools.encrypt
|
||||
this.encryptedFields = ['measies', 'img', 'name', 'notes']
|
||||
this.clear = {} // For holding decrypted data
|
||||
|
||||
return this
|
||||
|
@ -20,23 +21,29 @@ PersonModel.prototype.create = async function ({ body, user }) {
|
|||
const data = { name: body.name }
|
||||
if (body.notes || typeof body.notes === 'string') data.notes = body.notes
|
||||
if (body.public === true) data.public = true
|
||||
if (body.measies) data.measies = this.encrypt(this.sanitizeMeasurements(body.measies))
|
||||
if (body.measies) data.measies = this.sanitizeMeasurements(body.measies)
|
||||
data.userId = user.uid
|
||||
|
||||
// Create record
|
||||
try {
|
||||
this.record = await this.prisma.person.create({ data })
|
||||
this.record = await this.prisma.person.create({ data: this.cloak(data) })
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create person')
|
||||
return this.setResponse(500, 'createPersonFailed')
|
||||
}
|
||||
|
||||
return this.setResponse(201, 'created', {
|
||||
person: {
|
||||
...this.record,
|
||||
measies: this.decrypt(this.record.measies),
|
||||
},
|
||||
})
|
||||
// Update img? (now that we have the ID)
|
||||
const img =
|
||||
this.config.use.sanity &&
|
||||
typeof body.img === 'string' &&
|
||||
(!body.unittest || (body.unittest && this.config.use.tests?.sanity))
|
||||
? await setPersonAvatar(this.record.id, body.img)
|
||||
: false
|
||||
|
||||
if (img) await this.safeUpdate(this.cloak({ img: img.url }))
|
||||
else await this.read({ id: this.record.id })
|
||||
|
||||
return this.setResponse(201, 'created', { person: this.asPerson() })
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -59,29 +66,30 @@ PersonModel.prototype.read = async function (where) {
|
|||
/*
|
||||
* Helper method to decrypt at-rest data
|
||||
*/
|
||||
//PersonModel.prototype.reveal = async function (where) {
|
||||
// this.clear = {}
|
||||
// if (this.record) {
|
||||
// this.clear.bio = this.decrypt(this.record.bio)
|
||||
// this.clear.github = this.decrypt(this.record.github)
|
||||
// this.clear.email = this.decrypt(this.record.email)
|
||||
// this.clear.initial = this.decrypt(this.record.initial)
|
||||
// }
|
||||
//
|
||||
// return this
|
||||
//}
|
||||
PersonModel.prototype.reveal = async function () {
|
||||
this.clear = {}
|
||||
if (this.record) {
|
||||
for (const field of this.encryptedFields) {
|
||||
// Default avatar is not encrypted
|
||||
if (field === 'img' && this.record.img.slice(0, 4) === 'http')
|
||||
this.clear.img = this.record.img
|
||||
else this.clear[field] = this.decrypt(this.record[field])
|
||||
}
|
||||
} else console.log('no record')
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to encrypt at-rest data
|
||||
*/
|
||||
//UserModel.prototype.cloak = function (data) {
|
||||
// for (const field of ['bio', 'github', 'email']) {
|
||||
// if (typeof data[field] !== 'undefined') data[field] = this.encrypt(data[field])
|
||||
// }
|
||||
// if (typeof data.password === 'string') data.password = asJson(hashPassword(data.password))
|
||||
//
|
||||
// return data
|
||||
//}
|
||||
PersonModel.prototype.cloak = function (data) {
|
||||
for (const field of this.encryptedFields) {
|
||||
if (typeof data[field] !== 'undefined') data[field] = this.encrypt(data[field])
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads a user from the database based on the where clause you pass it
|
||||
|
@ -139,24 +147,24 @@ PersonModel.prototype.setExists = function () {
|
|||
}
|
||||
|
||||
/*
|
||||
* Updates the user data - Used when we create the data ourselves
|
||||
* Updates the person data - Used when we create the data ourselves
|
||||
* so we know it's safe
|
||||
*/
|
||||
//UserModel.prototype.safeUpdate = async function (data) {
|
||||
// try {
|
||||
// this.record = await this.prisma.user.update({
|
||||
// where: { id: this.record.id },
|
||||
// data,
|
||||
// })
|
||||
// } catch (err) {
|
||||
// log.warn(err, 'Could not update user record')
|
||||
// process.exit()
|
||||
// return this.setResponse(500, 'updateUserFailed')
|
||||
// }
|
||||
// await this.reveal()
|
||||
//
|
||||
// return this.setResponse(200)
|
||||
//}
|
||||
PersonModel.prototype.safeUpdate = async function (data) {
|
||||
try {
|
||||
this.record = await this.prisma.person.update({
|
||||
where: { id: this.record.id },
|
||||
data,
|
||||
})
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not update person record')
|
||||
process.exit()
|
||||
return this.setResponse(500, 'updatePersonFailed')
|
||||
}
|
||||
await this.reveal()
|
||||
|
||||
return this.setResponse(200)
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the user data - Used when we pass through user-provided data
|
||||
|
@ -263,7 +271,10 @@ PersonModel.prototype.setExists = function () {
|
|||
* Returns record data
|
||||
*/
|
||||
PersonModel.prototype.asPerson = function () {
|
||||
return this.reveal()
|
||||
return {
|
||||
...this.record,
|
||||
...this.clear,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
import { log } from '../utils/log.mjs'
|
||||
import { hash, hashPassword, randomString, verifyPassword } from '../utils/crypto.mjs'
|
||||
import { setPersonAvatar } from '../utils/sanity.mjs'
|
||||
import { setUserAvatar } from '../utils/sanity.mjs'
|
||||
import { clean, asJson, i18nUrl } from '../utils/index.mjs'
|
||||
import { ConfirmationModel } from './confirmation.mjs'
|
||||
|
||||
|
@ -346,7 +346,7 @@ UserModel.prototype.unsafeUpdate = async function (body) {
|
|||
}
|
||||
// Image (img)
|
||||
if (typeof body.img === 'string') {
|
||||
const img = await setPersonAvatar(this.record.id, body.img)
|
||||
const img = await setUserAvatar(this.record.id, body.img)
|
||||
data.img = img.url
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { cat } from './cat.mjs'
|
||||
/*
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
|
@ -20,6 +21,7 @@ export const personTests = async (chai, config, expect, store) => {
|
|||
neck: 420,
|
||||
},
|
||||
public: true,
|
||||
unittest: true,
|
||||
},
|
||||
key: {
|
||||
name: 'Sorcha',
|
||||
|
@ -29,6 +31,8 @@ export const personTests = async (chai, config, expect, store) => {
|
|||
neck: 360,
|
||||
},
|
||||
public: false,
|
||||
img: cat,
|
||||
unittest: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -49,12 +53,12 @@ export const personTests = async (chai, config, expect, store) => {
|
|||
)
|
||||
.send(data[auth])
|
||||
.end((err, res) => {
|
||||
console.log(res.body)
|
||||
expect(err === null).to.equal(true)
|
||||
expect(res.status).to.equal(201)
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
for (const [key, val] of Object.entries(data[auth])) {
|
||||
expect(res.body.person[key]).to.equal(val)
|
||||
if (!['measies', 'img', 'unittest'].includes(key))
|
||||
expect(res.body.person[key]).to.equal(val)
|
||||
}
|
||||
done()
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue