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 {
|
model Person {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
img String @default("https://freesewing.org/avatar.svg")
|
||||||
name String @default("")
|
name String @default("")
|
||||||
notes String @default("")
|
notes String @default("")
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
|
|
@ -18,21 +18,30 @@ const envToBool = (input = 'no') => {
|
||||||
|
|
||||||
// Construct config object
|
// Construct config object
|
||||||
const config = {
|
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,
|
api,
|
||||||
port,
|
apikeys: {
|
||||||
website: {
|
levels: [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
domain: process.env.BACKEND_WEBSITE_DOMAIN || 'freesewing.org',
|
expiryMaxSeconds: 365 * 24 * 3600,
|
||||||
scheme: process.env.BACKEND_WEBSITE_SCHEME || 'https',
|
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
url: process.env.BACKEND_DB_URL,
|
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: {
|
encryption: {
|
||||||
key: process.env.BACKEND_ENC_KEY,
|
key: process.env.BACKEND_ENC_KEY,
|
||||||
},
|
},
|
||||||
|
@ -42,10 +51,9 @@ const config = {
|
||||||
audience: process.env.BACKEND_JWT_ISSUER || 'freesewing.org',
|
audience: process.env.BACKEND_JWT_ISSUER || 'freesewing.org',
|
||||||
expiresIn: process.env.BACKEND_JWT_EXPIRY || '7d',
|
expiresIn: process.env.BACKEND_JWT_EXPIRY || '7d',
|
||||||
},
|
},
|
||||||
apikeys: {
|
languages: ['en', 'de', 'es', 'fr', 'nl'],
|
||||||
levels: [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
measies: measurements,
|
||||||
expiryMaxSeconds: 365 * 24 * 3600,
|
port,
|
||||||
},
|
|
||||||
roles: {
|
roles: {
|
||||||
levels: {
|
levels: {
|
||||||
user: 4,
|
user: 4,
|
||||||
|
@ -55,49 +63,21 @@ const config = {
|
||||||
},
|
},
|
||||||
base: 'user',
|
base: 'user',
|
||||||
},
|
},
|
||||||
languages: ['en', 'de', 'es', 'fr', 'nl'],
|
website: {
|
||||||
measies: measurements,
|
domain: process.env.BACKEND_WEBSITE_DOMAIN || 'freesewing.org',
|
||||||
aws: {
|
scheme: process.env.BACKEND_WEBSITE_SCHEME || 'https',
|
||||||
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: {},
|
||||||
sanity: {
|
github: {},
|
||||||
use: process.env.BACKEND_USE_SANITY || false,
|
}
|
||||||
project: process.env.SANITY_PROJECT,
|
|
||||||
dataset: process.env.SANITY_DATASET || 'production',
|
/*
|
||||||
token: process.env.SANITY_TOKEN,
|
* Config behind feature flags
|
||||||
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'
|
// Github config
|
||||||
}`,
|
if (config.use.github)
|
||||||
},
|
config.github = {
|
||||||
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: {
|
|
||||||
token: process.env.BACKEND_GITHUB_TOKEN,
|
token: process.env.BACKEND_GITHUB_TOKEN,
|
||||||
api: 'https://api.github.com',
|
api: 'https://api.github.com',
|
||||||
bot: {
|
bot: {
|
||||||
|
@ -125,11 +105,64 @@ const config = {
|
||||||
},
|
},
|
||||||
dflt: [process.env.BACKEND_GITHUB_NOTIFY_DEFAULT_USER || 'joostdecock'],
|
dflt: [process.env.BACKEND_GITHUB_NOTIFY_DEFAULT_USER || 'joostdecock'],
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Stand-alone config
|
// Unit test config
|
||||||
export const sanity = config.sanity
|
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
|
export const website = config.website
|
||||||
|
|
||||||
const vars = {
|
const vars = {
|
||||||
|
|
|
@ -6,6 +6,7 @@ export function PersonModel(tools) {
|
||||||
this.prisma = tools.prisma
|
this.prisma = tools.prisma
|
||||||
this.decrypt = tools.decrypt
|
this.decrypt = tools.decrypt
|
||||||
this.encrypt = tools.encrypt
|
this.encrypt = tools.encrypt
|
||||||
|
this.encryptedFields = ['measies', 'img', 'name', 'notes']
|
||||||
this.clear = {} // For holding decrypted data
|
this.clear = {} // For holding decrypted data
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
@ -20,23 +21,29 @@ PersonModel.prototype.create = async function ({ body, user }) {
|
||||||
const data = { name: body.name }
|
const data = { name: body.name }
|
||||||
if (body.notes || typeof body.notes === 'string') data.notes = body.notes
|
if (body.notes || typeof body.notes === 'string') data.notes = body.notes
|
||||||
if (body.public === true) data.public = true
|
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
|
data.userId = user.uid
|
||||||
|
|
||||||
// Create record
|
// Create record
|
||||||
try {
|
try {
|
||||||
this.record = await this.prisma.person.create({ data })
|
this.record = await this.prisma.person.create({ data: this.cloak(data) })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.warn(err, 'Could not create person')
|
log.warn(err, 'Could not create person')
|
||||||
return this.setResponse(500, 'createPersonFailed')
|
return this.setResponse(500, 'createPersonFailed')
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.setResponse(201, 'created', {
|
// Update img? (now that we have the ID)
|
||||||
person: {
|
const img =
|
||||||
...this.record,
|
this.config.use.sanity &&
|
||||||
measies: this.decrypt(this.record.measies),
|
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
|
* Helper method to decrypt at-rest data
|
||||||
*/
|
*/
|
||||||
//PersonModel.prototype.reveal = async function (where) {
|
PersonModel.prototype.reveal = async function () {
|
||||||
// this.clear = {}
|
this.clear = {}
|
||||||
// if (this.record) {
|
if (this.record) {
|
||||||
// this.clear.bio = this.decrypt(this.record.bio)
|
for (const field of this.encryptedFields) {
|
||||||
// this.clear.github = this.decrypt(this.record.github)
|
// Default avatar is not encrypted
|
||||||
// this.clear.email = this.decrypt(this.record.email)
|
if (field === 'img' && this.record.img.slice(0, 4) === 'http')
|
||||||
// this.clear.initial = this.decrypt(this.record.initial)
|
this.clear.img = this.record.img
|
||||||
// }
|
else this.clear[field] = this.decrypt(this.record[field])
|
||||||
//
|
}
|
||||||
// return this
|
} else console.log('no record')
|
||||||
//}
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Helper method to encrypt at-rest data
|
* Helper method to encrypt at-rest data
|
||||||
*/
|
*/
|
||||||
//UserModel.prototype.cloak = function (data) {
|
PersonModel.prototype.cloak = function (data) {
|
||||||
// for (const field of ['bio', 'github', 'email']) {
|
for (const field of this.encryptedFields) {
|
||||||
// if (typeof data[field] !== 'undefined') data[field] = this.encrypt(data[field])
|
if (typeof data[field] !== 'undefined') data[field] = this.encrypt(data[field])
|
||||||
// }
|
}
|
||||||
// if (typeof data.password === 'string') data.password = asJson(hashPassword(data.password))
|
|
||||||
//
|
return data
|
||||||
// return data
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loads a user from the database based on the where clause you pass it
|
* 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
|
* so we know it's safe
|
||||||
*/
|
*/
|
||||||
//UserModel.prototype.safeUpdate = async function (data) {
|
PersonModel.prototype.safeUpdate = async function (data) {
|
||||||
// try {
|
try {
|
||||||
// this.record = await this.prisma.user.update({
|
this.record = await this.prisma.person.update({
|
||||||
// where: { id: this.record.id },
|
where: { id: this.record.id },
|
||||||
// data,
|
data,
|
||||||
// })
|
})
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
// log.warn(err, 'Could not update user record')
|
log.warn(err, 'Could not update person record')
|
||||||
// process.exit()
|
process.exit()
|
||||||
// return this.setResponse(500, 'updateUserFailed')
|
return this.setResponse(500, 'updatePersonFailed')
|
||||||
// }
|
}
|
||||||
// await this.reveal()
|
await this.reveal()
|
||||||
//
|
|
||||||
// return this.setResponse(200)
|
return this.setResponse(200)
|
||||||
//}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Updates the user data - Used when we pass through user-provided data
|
* Updates the user data - Used when we pass through user-provided data
|
||||||
|
@ -263,7 +271,10 @@ PersonModel.prototype.setExists = function () {
|
||||||
* Returns record data
|
* Returns record data
|
||||||
*/
|
*/
|
||||||
PersonModel.prototype.asPerson = function () {
|
PersonModel.prototype.asPerson = function () {
|
||||||
return this.reveal()
|
return {
|
||||||
|
...this.record,
|
||||||
|
...this.clear,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { log } from '../utils/log.mjs'
|
import { log } from '../utils/log.mjs'
|
||||||
import { hash, hashPassword, randomString, verifyPassword } from '../utils/crypto.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 { clean, asJson, i18nUrl } from '../utils/index.mjs'
|
||||||
import { ConfirmationModel } from './confirmation.mjs'
|
import { ConfirmationModel } from './confirmation.mjs'
|
||||||
|
|
||||||
|
@ -346,7 +346,7 @@ UserModel.prototype.unsafeUpdate = async function (body) {
|
||||||
}
|
}
|
||||||
// Image (img)
|
// Image (img)
|
||||||
if (typeof body.img === 'string') {
|
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
|
data.img = img.url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { cat } from './cat.mjs'
|
||||||
/*
|
/*
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
@ -20,6 +21,7 @@ export const personTests = async (chai, config, expect, store) => {
|
||||||
neck: 420,
|
neck: 420,
|
||||||
},
|
},
|
||||||
public: true,
|
public: true,
|
||||||
|
unittest: true,
|
||||||
},
|
},
|
||||||
key: {
|
key: {
|
||||||
name: 'Sorcha',
|
name: 'Sorcha',
|
||||||
|
@ -29,6 +31,8 @@ export const personTests = async (chai, config, expect, store) => {
|
||||||
neck: 360,
|
neck: 360,
|
||||||
},
|
},
|
||||||
public: false,
|
public: false,
|
||||||
|
img: cat,
|
||||||
|
unittest: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,11 +53,11 @@ export const personTests = async (chai, config, expect, store) => {
|
||||||
)
|
)
|
||||||
.send(data[auth])
|
.send(data[auth])
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
console.log(res.body)
|
|
||||||
expect(err === null).to.equal(true)
|
expect(err === null).to.equal(true)
|
||||||
expect(res.status).to.equal(201)
|
expect(res.status).to.equal(201)
|
||||||
expect(res.body.result).to.equal(`success`)
|
expect(res.body.result).to.equal(`success`)
|
||||||
for (const [key, val] of Object.entries(data[auth])) {
|
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.person[key]).to.equal(val)
|
||||||
}
|
}
|
||||||
done()
|
done()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue