feat(backend): Added curatedSets data type
This commit is contained in:
parent
19a81a0aed
commit
e4f2aab9d0
15 changed files with 1006 additions and 48 deletions
177
sites/backend/scripts/import-sizing-table.mjs
Normal file
177
sites/backend/scripts/import-sizing-table.mjs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
import { hash, encryption } from '../src/utils/crypto.mjs'
|
||||||
|
import { clean } from '../src/utils/index.mjs'
|
||||||
|
import { verifyConfig } from '../src/config.mjs'
|
||||||
|
import {
|
||||||
|
cisFemaleAdult,
|
||||||
|
cisMaleAdult,
|
||||||
|
cisFemaleDoll,
|
||||||
|
cisMaleDoll,
|
||||||
|
cisFemaleGiant,
|
||||||
|
cisMaleGiant,
|
||||||
|
} from '../../../packages/models/src/index.mjs'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
const config = verifyConfig()
|
||||||
|
const { encrypt, decrypt } = encryption(config.encryption.key)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: This will import the FreeSewing (v2) sizing table into the
|
||||||
|
* FreeSewing curator account.
|
||||||
|
*
|
||||||
|
* The curator account is just a regular user account, but one that
|
||||||
|
* we use to store the 'official' measurement sets.
|
||||||
|
*
|
||||||
|
* Unless you are a very involved contributor, there is probably
|
||||||
|
* no reason for you to run this script.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Holds the sets to create
|
||||||
|
const sets = []
|
||||||
|
|
||||||
|
// Helper method to create the set data
|
||||||
|
const createSetData = ({ name, measies, imperial }) => ({
|
||||||
|
imperial,
|
||||||
|
name,
|
||||||
|
measies,
|
||||||
|
userId: config.curator.id,
|
||||||
|
public: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// CIS Female Adult
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisFemaleAdult).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Metric Cis Female Adult - Size ${size} (EU)`,
|
||||||
|
measies: cisFemaleAdult[size],
|
||||||
|
imperial: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisFemaleAdult).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Imperial Cis Female Adult - Size ${size} (EU)`,
|
||||||
|
measies: cisFemaleAdult[size],
|
||||||
|
imperial: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// CIS Male Adult
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisMaleAdult).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Metric Cis Male Adult - Size ${size} (EU)`,
|
||||||
|
measies: cisMaleAdult[size],
|
||||||
|
imperial: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisMaleAdult).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Imperial Cis Male Adult - Size ${size} (EU)`,
|
||||||
|
measies: cisMaleAdult[size],
|
||||||
|
imperial: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// CIS Female Doll
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisFemaleDoll).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Metric Cis Female Doll - ${size}%`,
|
||||||
|
measies: cisFemaleDoll[size],
|
||||||
|
imperial: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisFemaleDoll).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Imperial Cis Female Doll - ${size}%`,
|
||||||
|
measies: cisFemaleDoll[size],
|
||||||
|
imperial: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// CIS Male Doll
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisMaleDoll).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Metric Cis Male Doll - ${size}%`,
|
||||||
|
measies: cisMaleDoll[size],
|
||||||
|
imperial: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisMaleDoll).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Imperial Cis Male Doll - ${size}%`,
|
||||||
|
measies: cisMaleDoll[size],
|
||||||
|
imperial: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// CIS Female Giant
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisFemaleGiant).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Metric Cis Female Giant - Size ${size}%`,
|
||||||
|
measies: cisFemaleGiant[size],
|
||||||
|
imperial: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisFemaleGiant).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Imperial Cis Female Giant - Size ${size}%`,
|
||||||
|
measies: cisFemaleGiant[size],
|
||||||
|
imperial: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// CIS Male Giant
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisMaleGiant).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Metric Cis Male Giant - Size ${size}%`,
|
||||||
|
measies: cisMaleGiant[size],
|
||||||
|
imperial: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sets.push(
|
||||||
|
...Object.keys(cisMaleGiant).map((size) =>
|
||||||
|
createSetData({
|
||||||
|
name: `Imperial Cis Male Giant - Size ${size}%`,
|
||||||
|
measies: cisMaleGiant[size],
|
||||||
|
imperial: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
importSets(sets)
|
||||||
|
|
||||||
|
async function createSet(set) {
|
||||||
|
try {
|
||||||
|
record = await prisma.user.create({ data: set })
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importSets(sets) {
|
||||||
|
for (const set of sets) {
|
||||||
|
console.log(`Importing ${set.name}`)
|
||||||
|
await createSet(set)
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,8 @@ const envToBool = (input = 'no') => {
|
||||||
|
|
||||||
// Construct config object
|
// Construct config object
|
||||||
const baseConfig = {
|
const baseConfig = {
|
||||||
|
// Environment
|
||||||
|
env: process.env.NODE_ENV || 'development',
|
||||||
// Feature flags
|
// Feature flags
|
||||||
use: {
|
use: {
|
||||||
github: envToBool(process.env.BACKEND_ENABLE_GITHUB),
|
github: envToBool(process.env.BACKEND_ENABLE_GITHUB),
|
||||||
|
@ -87,6 +89,7 @@ const baseConfig = {
|
||||||
},
|
},
|
||||||
tests: {
|
tests: {
|
||||||
domain: process.env.BACKEND_TEST_DOMAIN || 'freesewing.dev',
|
domain: process.env.BACKEND_TEST_DOMAIN || 'freesewing.dev',
|
||||||
|
production: envToBool(process.env.BACKEND_ALLOW_TESTS_IN_PRODUCTION),
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
domain: process.env.BACKEND_WEBSITE_DOMAIN || 'freesewing.org',
|
domain: process.env.BACKEND_WEBSITE_DOMAIN || 'freesewing.org',
|
||||||
|
@ -202,6 +205,7 @@ const vars = {
|
||||||
BACKEND_ENABLE_OAUTH_GITHUB: 'optional',
|
BACKEND_ENABLE_OAUTH_GITHUB: 'optional',
|
||||||
BACKEND_ENABLE_OAUTH_GOOGLE: 'optional',
|
BACKEND_ENABLE_OAUTH_GOOGLE: 'optional',
|
||||||
BACKEND_ENABLE_TESTS: 'optional',
|
BACKEND_ENABLE_TESTS: 'optional',
|
||||||
|
BACKEND_ALLOW_TESTS_IN_PRODUCTION: 'optional',
|
||||||
BACKEND_ENABLE_DUMP_CONFIG_AT_STARTUP: 'optional',
|
BACKEND_ENABLE_DUMP_CONFIG_AT_STARTUP: 'optional',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +245,7 @@ if (envToBool(process.env.BACKEND_ENABLE_OAUTH_GOOGLE)) {
|
||||||
vars.BACKEND_OAUTH_GOOGLE_CLIENT_ID = 'required'
|
vars.BACKEND_OAUTH_GOOGLE_CLIENT_ID = 'required'
|
||||||
vars.BACKEND_OAUTH_GOOGLE_CLIENT_SECRET = 'requiredSecret'
|
vars.BACKEND_OAUTH_GOOGLE_CLIENT_SECRET = 'requiredSecret'
|
||||||
}
|
}
|
||||||
// Vars for unit tests
|
// Vars for (unit) tests
|
||||||
if (envToBool(process.env.BACKEND_ENABLE_TESTS)) {
|
if (envToBool(process.env.BACKEND_ENABLE_TESTS)) {
|
||||||
vars.BACKEND_TEST_DOMAIN = 'optional'
|
vars.BACKEND_TEST_DOMAIN = 'optional'
|
||||||
vars.BACKEND_ENABLE_TESTS_EMAIL = 'optional'
|
vars.BACKEND_ENABLE_TESTS_EMAIL = 'optional'
|
||||||
|
|
79
sites/backend/src/controllers/curated-sets.mjs
Normal file
79
sites/backend/src/controllers/curated-sets.mjs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { CuratedSetModel } from '../models/curated-set.mjs'
|
||||||
|
import { SetModel } from '../models/set.mjs'
|
||||||
|
|
||||||
|
export function CuratedSetsController() {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a curated measurements set
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
CuratedSetsController.prototype.create = async (req, res, tools) => {
|
||||||
|
const CuratedSet = new CuratedSetModel(tools)
|
||||||
|
await CuratedSet.guardedCreate(req)
|
||||||
|
|
||||||
|
return CuratedSet.sendResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a curated measurements set
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
CuratedSetsController.prototype.read = async (req, res, tools, format = false) => {
|
||||||
|
const CuratedSet = new CuratedSetModel(tools)
|
||||||
|
await CuratedSet.guardedRead(req, format)
|
||||||
|
|
||||||
|
return format === 'yaml' ? CuratedSet.sendYamlResponse(res) : CuratedSet.sendResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a list of curated measurements sets
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
CuratedSetsController.prototype.list = async (req, res, tools, format = false) => {
|
||||||
|
const CuratedSet = new CuratedSetModel(tools)
|
||||||
|
const curatedSets = await CuratedSet.allCuratedSets()
|
||||||
|
|
||||||
|
if (curatedSets) {
|
||||||
|
if (!format) CuratedSet.setResponse(200, 'success', { curatedSets })
|
||||||
|
else CuratedSet.setResponse(200, 'success', curatedSets, true)
|
||||||
|
} else CuratedSet.setResponse(404, 'notFound')
|
||||||
|
|
||||||
|
return format === 'yaml' && curatedSets
|
||||||
|
? CuratedSet.sendYamlResponse(res)
|
||||||
|
: CuratedSet.sendResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update a curated measurements set
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
CuratedSetsController.prototype.update = async (req, res, tools) => {
|
||||||
|
const CuratedSet = new CuratedSetModel(tools)
|
||||||
|
await CuratedSet.guardedUpdate(req)
|
||||||
|
|
||||||
|
return CuratedSet.sendResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove a curated measurements set
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
CuratedSetsController.prototype.delete = async (req, res, tools) => {
|
||||||
|
const CuratedSet = new CuratedSetModel(tools)
|
||||||
|
await CuratedSet.guardedDelete(req)
|
||||||
|
|
||||||
|
return Set.sendResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clone a curated measurements set
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
CuratedSetsController.prototype.clone = async (req, res, tools) => {
|
||||||
|
const CuratedSet = new CuratedSetModel(tools)
|
||||||
|
const Set = new SetModel(tools)
|
||||||
|
await CuratedSet.guardedClone(req, Set)
|
||||||
|
|
||||||
|
// Note: Sending the set back
|
||||||
|
return Set.sendResponse(res)
|
||||||
|
}
|
322
sites/backend/src/models/curated-set.mjs
Normal file
322
sites/backend/src/models/curated-set.mjs
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
import { capitalize } from '../utils/index.mjs'
|
||||||
|
import { log } from '../utils/log.mjs'
|
||||||
|
import { setSetAvatar } from '../utils/sanity.mjs'
|
||||||
|
import yaml from 'js-yaml'
|
||||||
|
|
||||||
|
export function CuratedSetModel(tools) {
|
||||||
|
this.config = tools.config
|
||||||
|
this.prisma = tools.prisma
|
||||||
|
this.rbac = tools.rbac
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
CuratedSetModel.prototype.guardedCreate = async function ({ body, user }) {
|
||||||
|
if (!this.rbac.curator(user)) return this.setResponse(403, 'insufficientAccessLevel')
|
||||||
|
if (Object.keys(body).length < 1) return this.setResponse(400, 'postBodyMissing')
|
||||||
|
if (!body.nameEn || typeof body.nameEn !== 'string') return this.setResponse(400, 'nameEnMissing')
|
||||||
|
|
||||||
|
// Prepare data
|
||||||
|
const data = {}
|
||||||
|
for (const lang of this.config.languages) {
|
||||||
|
for (const field of ['name', 'notes']) {
|
||||||
|
const key = field + capitalize(lang)
|
||||||
|
if (body[key] && typeof body[key] === 'string') data[key] = body[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (body.measies) data.measies = this.sanitizeMeasurements(body.measies)
|
||||||
|
else data.measies = {}
|
||||||
|
// Set this one initially as we need the ID to create a custom img via Sanity
|
||||||
|
data.img = this.config.avatars.set
|
||||||
|
|
||||||
|
// Create record
|
||||||
|
await this.unguardedCreate(data)
|
||||||
|
|
||||||
|
// Update img? (now that we have the ID)
|
||||||
|
const img =
|
||||||
|
this.config.use.sanity &&
|
||||||
|
typeof body.img === 'string' &&
|
||||||
|
(!body.test || (body.test && this.config.use.tests?.sanity))
|
||||||
|
? await setSetAvatar(this.record.id, body.img)
|
||||||
|
: false
|
||||||
|
|
||||||
|
if (img) await this.unguardedUpdate({ img: img.url })
|
||||||
|
else await this.read({ id: this.record.id })
|
||||||
|
|
||||||
|
return this.setResponse(201, 'created', { curatedSet: this.asCuratedSet() })
|
||||||
|
}
|
||||||
|
|
||||||
|
CuratedSetModel.prototype.unguardedCreate = async function (data) {
|
||||||
|
// FIXME: See https://github.com/prisma/prisma/issues/3786
|
||||||
|
if (data.measies && typeof data.measies === 'object') data.measies = JSON.stringify(data.measies)
|
||||||
|
try {
|
||||||
|
this.record = await this.prisma.curatedSet.create({ data })
|
||||||
|
} catch (err) {
|
||||||
|
log.warn(err, 'Could not create set')
|
||||||
|
return this.setResponse(500, 'createSetFailed')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loads a measurements set from the database based on the where clause you pass it
|
||||||
|
*
|
||||||
|
* Stores result in this.record
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.read = async function (where) {
|
||||||
|
try {
|
||||||
|
this.record = await this.prisma.curatedSet.findUnique({ where })
|
||||||
|
} catch (err) {
|
||||||
|
log.warn({ err, where }, 'Could not read measurements set')
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Convert JSON to object. See https://github.com/prisma/prisma/issues/3786
|
||||||
|
this.record.measies = JSON.parse(this.record.measies)
|
||||||
|
|
||||||
|
return this.curatedSetExists()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.guardedRead = async function ({ params }, format = false) {
|
||||||
|
await this.read({ id: parseInt(params.id) })
|
||||||
|
|
||||||
|
if (!format)
|
||||||
|
return this.setResponse(200, false, {
|
||||||
|
result: 'success',
|
||||||
|
curatedSet: this.asCuratedSet(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.setResponse(200, false, this.asData(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a list of all curated sets
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.allCuratedSets = async function () {
|
||||||
|
let curatedSets
|
||||||
|
try {
|
||||||
|
curatedSets = await this.prisma.curatedSet.findMany()
|
||||||
|
} catch (err) {
|
||||||
|
log.warn(`Failed to search curated sets: ${err}`)
|
||||||
|
}
|
||||||
|
const list = []
|
||||||
|
for (const curatedSet of curatedSets) list.push(curatedSet)
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clones a curated measurements set (into a regular set)
|
||||||
|
* In addition prepares it for returning the set data
|
||||||
|
*
|
||||||
|
* Stores result in this.record
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.guardedClone = async function ({ params, user, body }, Set) {
|
||||||
|
if (!this.rbac.writeSome(user)) return this.setResponse(403, 'insufficientAccessLevel')
|
||||||
|
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
|
||||||
|
if (!body.language || !this.config.languages.includes(body.language))
|
||||||
|
return this.setResponse(403, 'languageMissing')
|
||||||
|
|
||||||
|
await this.read({ id: parseInt(params.id) })
|
||||||
|
|
||||||
|
// Clone curated set
|
||||||
|
const data = {}
|
||||||
|
const lang = capitalize(body.language.toLowerCase())
|
||||||
|
data.name = this.record[`name${lang}`]
|
||||||
|
data.notes = this.record[`notes${lang}`]
|
||||||
|
data.measies = this.record.measies
|
||||||
|
|
||||||
|
await Set.guardedCreate({ params, user, body: data })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks this.record and sets a boolean to indicate whether
|
||||||
|
* the curated set exists or not
|
||||||
|
*
|
||||||
|
* Stores result in this.exists
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.curatedSetExists = function () {
|
||||||
|
this.exists = this.record ? true : false
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Updates the set data - Used when we create the data ourselves
|
||||||
|
* so we know it's safe
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.unguardedUpdate = async function (data) {
|
||||||
|
// FIXME: Convert object to JSON. See https://github.com/prisma/prisma/issues/3786
|
||||||
|
if (data.measies && typeof data.measies === 'object') data.measies = JSON.stringify(data.measies)
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.record = await this.prisma.curatedSet.update({
|
||||||
|
where: { id: this.record.id },
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
// FIXME: Convert JSON to object. See https://github.com/prisma/prisma/issues/3786
|
||||||
|
this.record.measies = JSON.parse(this.record.measies)
|
||||||
|
} catch (err) {
|
||||||
|
log.warn(err, 'Could not update set record')
|
||||||
|
process.exit()
|
||||||
|
return this.setResponse(500, 'updateSetFailed')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.setResponse(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Updates the set data - Used when we pass through user-provided data
|
||||||
|
* so we can't be certain it's safe
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.guardedUpdate = async function ({ params, body, user }) {
|
||||||
|
if (!this.rbac.curator(user)) return this.setResponse(403, 'insufficientAccessLevel')
|
||||||
|
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
|
||||||
|
await this.read({ id: parseInt(params.id) })
|
||||||
|
|
||||||
|
// Prepare data
|
||||||
|
const data = {}
|
||||||
|
for (const lang of this.config.languages) {
|
||||||
|
for (const field of ['name', 'notes']) {
|
||||||
|
const key = field + capitalize(lang)
|
||||||
|
if (body[key] && typeof body[key] === 'string') data[key] = body[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Measurements
|
||||||
|
const measies = {}
|
||||||
|
if (typeof body.measies === 'object') {
|
||||||
|
const remove = []
|
||||||
|
for (const [key, val] of Object.entries(body.measies)) {
|
||||||
|
if (this.config.measies.includes(key)) {
|
||||||
|
if (val === null) remove.push(key)
|
||||||
|
else if (typeof val == 'number' && val > 0) measies[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.measies = { ...this.record.measies, ...measies }
|
||||||
|
for (const key of remove) delete data.measies[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image (img)
|
||||||
|
if (typeof body.img === 'string') {
|
||||||
|
const img = await setSetAvatar(params.id, body.img)
|
||||||
|
data.img = img.url
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now update the record
|
||||||
|
await this.unguardedUpdate(data)
|
||||||
|
|
||||||
|
return this.setResponse(200, false, { set: this.asCuratedSet() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Removes the set - No questions asked
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.unguardedDelete = async function () {
|
||||||
|
await this.prisma.curatedSet.delete({ where: { id: this.record.id } })
|
||||||
|
this.record = null
|
||||||
|
this.clear = null
|
||||||
|
|
||||||
|
return this.curatedSetExists()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Removes the set - Checks permissions
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.guardedDelete = async function ({ params, user }) {
|
||||||
|
if (!this.rbac.curator(user)) return this.setResponse(403, 'insufficientAccessLevel')
|
||||||
|
if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking')
|
||||||
|
|
||||||
|
await this.read({ id: parseInt(params.id) })
|
||||||
|
if (this.record.userId !== user.uid && user.level < 8) {
|
||||||
|
return this.setResponse(403, 'insufficientAccessLevel')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.unguardedDelete()
|
||||||
|
|
||||||
|
return this.setResponse(204, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns record data fit for public publishing
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.asCuratedSet = function () {
|
||||||
|
return { ...this.record }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns record data fit for public publishing
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.asData = function () {
|
||||||
|
const data = {
|
||||||
|
author: 'FreeSewing.org',
|
||||||
|
type: 'curatedMeasurementsSet',
|
||||||
|
about: 'Contains measurements in mm as well as metadata',
|
||||||
|
...this.asCuratedSet(),
|
||||||
|
}
|
||||||
|
data.measurements = data.measies
|
||||||
|
delete data.measies
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper method to set the response code, result, and body
|
||||||
|
*
|
||||||
|
* Will be used by this.sendResponse()
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.setResponse = function (
|
||||||
|
status = 200,
|
||||||
|
error = false,
|
||||||
|
data = {},
|
||||||
|
rawData = false
|
||||||
|
) {
|
||||||
|
this.response = {
|
||||||
|
status,
|
||||||
|
body: rawData
|
||||||
|
? data
|
||||||
|
: {
|
||||||
|
result: 'success',
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if (status > 201) {
|
||||||
|
this.response.body.error = error
|
||||||
|
this.response.body.result = 'error'
|
||||||
|
this.error = true
|
||||||
|
} else this.error = false
|
||||||
|
|
||||||
|
return this.curatedSetExists()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper method to send response (as JSON)
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.sendResponse = async function (res) {
|
||||||
|
return res.status(this.response.status).send(this.response.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper method to send response as YAML
|
||||||
|
*/
|
||||||
|
CuratedSetModel.prototype.sendYamlResponse = async function (res) {
|
||||||
|
return res.status(this.response.status).type('yaml').send(yaml.dump(this.response.body))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helper method to parse user-supplied measurements */
|
||||||
|
CuratedSetModel.prototype.sanitizeMeasurements = function (input) {
|
||||||
|
const measies = {}
|
||||||
|
if (typeof input !== 'object') return measies
|
||||||
|
for (const [m, val] of Object.entries(input)) {
|
||||||
|
if (this.config.measies.includes(m) && typeof val === 'number' && val > 0) measies[m] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return measies
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ PatternModel.prototype.guardedCreate = async function ({ body, user }) {
|
||||||
const img =
|
const img =
|
||||||
this.config.use.sanity &&
|
this.config.use.sanity &&
|
||||||
typeof body.img === 'string' &&
|
typeof body.img === 'string' &&
|
||||||
(!body.unittest || (body.unittest && this.config.use.tests?.sanity))
|
(!body.test || (body.test && this.config.use.tests?.sanity))
|
||||||
? await setPatternAvatar(this.record.id, body.img)
|
? await setPatternAvatar(this.record.id, body.img)
|
||||||
: false
|
: false
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ SetModel.prototype.guardedCreate = async function ({ body, user }) {
|
||||||
const img =
|
const img =
|
||||||
this.config.use.sanity &&
|
this.config.use.sanity &&
|
||||||
typeof body.img === 'string' &&
|
typeof body.img === 'string' &&
|
||||||
(!body.unittest || (body.unittest && this.config.use.tests?.sanity))
|
(!body.test || (body.test && this.config.use.tests?.sanity))
|
||||||
? await setSetAvatar(this.record.id, body.img)
|
? await setSetAvatar(this.record.id, body.img)
|
||||||
: false
|
: false
|
||||||
|
|
||||||
|
@ -393,10 +393,10 @@ SetModel.prototype.sendYamlResponse = async function (res) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update method to determine whether this request is
|
* Update method to determine whether this request is
|
||||||
* part of a unit test
|
* part of a test
|
||||||
*/
|
*/
|
||||||
//UserModel.prototype.isUnitTest = function (body) {
|
//UserModel.prototype.isTest = function (body) {
|
||||||
// if (!body.unittest) return false
|
// if (!body.test) return false
|
||||||
// if (!this.clear.email.split('@').pop() === this.config.tests.domain) return false
|
// if (!this.clear.email.split('@').pop() === this.config.tests.domain) return false
|
||||||
// if (body.email && !body.email.split('@').pop() === this.config.tests.domain) return false
|
// if (body.email && !body.email.split('@').pop() === this.config.tests.domain) return false
|
||||||
//
|
//
|
||||||
|
|
|
@ -150,6 +150,10 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
|
||||||
const ehash = hash(clean(body.email))
|
const ehash = hash(clean(body.email))
|
||||||
const check = randomString()
|
const check = randomString()
|
||||||
await this.read({ ehash })
|
await this.read({ ehash })
|
||||||
|
|
||||||
|
// Check for unit tests only once
|
||||||
|
const isTest = this.isTest(body)
|
||||||
|
|
||||||
if (this.exists) {
|
if (this.exists) {
|
||||||
/*
|
/*
|
||||||
* User already exists. However, if we return an error, then baddies can
|
* User already exists. However, if we return an error, then baddies can
|
||||||
|
@ -183,20 +187,21 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
|
||||||
userId: this.record.id,
|
userId: this.record.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Always send email
|
// Send email unless it's a test and we don't want to send test emails
|
||||||
await this.mailer.send({
|
if (!isTest || this.config.tests.sendEmail)
|
||||||
template: type,
|
await this.mailer.send({
|
||||||
language: body.language,
|
template: type,
|
||||||
to: this.clear.email,
|
language: body.language,
|
||||||
replacements: {
|
to: this.clear.email,
|
||||||
actionUrl:
|
replacements: {
|
||||||
type === 'signup-aed'
|
actionUrl:
|
||||||
? false // No actionUrl for disabled accounts
|
type === 'signup-aed'
|
||||||
: i18nUrl(body.language, `/confirm/${type}/${this.Confirmation.record.id}/${check}`),
|
? false // No actionUrl for disabled accounts
|
||||||
whyUrl: i18nUrl(body.language, `/docs/faq/email/why-${type}`),
|
: i18nUrl(body.language, `/confirm/${type}/${this.Confirmation.record.id}/${check}`),
|
||||||
supportUrl: i18nUrl(body.language, `/patrons/join`),
|
whyUrl: i18nUrl(body.language, `/docs/faq/email/why-${type}`),
|
||||||
},
|
supportUrl: i18nUrl(body.language, `/patrons/join`),
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// Now return as if everything is fine
|
// Now return as if everything is fine
|
||||||
return this.setResponse(201, false, { email: this.clear.email })
|
return this.setResponse(201, false, { email: this.clear.email })
|
||||||
|
@ -225,6 +230,8 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
|
||||||
// Set this one initially as we need the ID to create a custom img via Sanity
|
// Set this one initially as we need the ID to create a custom img via Sanity
|
||||||
img: this.encrypt(this.config.avatars.user),
|
img: this.encrypt(this.config.avatars.user),
|
||||||
}
|
}
|
||||||
|
// During tests, users can set their own permission level so you can test admin stuff
|
||||||
|
if (isTest && body.role) data.role = body.role
|
||||||
this.record = await this.prisma.user.create({ data })
|
this.record = await this.prisma.user.create({ data })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.warn(err, 'Could not create user record')
|
log.warn(err, 'Could not create user record')
|
||||||
|
@ -256,7 +263,7 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Send signup email
|
// Send signup email
|
||||||
if (!this.isUnitTest(body) || this.config.tests.sendEmail)
|
if (!this.isTest(body) || this.config.tests.sendEmail)
|
||||||
await this.mailer.send({
|
await this.mailer.send({
|
||||||
template: 'signup',
|
template: 'signup',
|
||||||
language: this.language,
|
language: this.language,
|
||||||
|
@ -271,7 +278,7 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.isUnitTest(body)
|
return this.isTest(body)
|
||||||
? this.setResponse(201, false, {
|
? this.setResponse(201, false, {
|
||||||
email: this.clear.email,
|
email: this.clear.email,
|
||||||
confirmation: this.confirmation.record.id,
|
confirmation: this.confirmation.record.id,
|
||||||
|
@ -390,8 +397,8 @@ UserModel.prototype.sendSigninlink = async function (req) {
|
||||||
},
|
},
|
||||||
userId: this.record.id,
|
userId: this.record.id,
|
||||||
})
|
})
|
||||||
const isUnitTest = this.isUnitTest(req.body)
|
const isTest = this.isTest(req.body)
|
||||||
if (!isUnitTest) {
|
if (!isTest) {
|
||||||
// Send sign-in link email
|
// Send sign-in link email
|
||||||
await this.mailer.send({
|
await this.mailer.send({
|
||||||
template: 'signinlink',
|
template: 'signinlink',
|
||||||
|
@ -522,7 +529,7 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
||||||
// Now update the record
|
// Now update the record
|
||||||
await this.unguardedUpdate(this.cloak(data))
|
await this.unguardedUpdate(this.cloak(data))
|
||||||
|
|
||||||
const isUnitTest = this.isUnitTest(body)
|
const isTest = this.isTest(body)
|
||||||
if (typeof body.email === 'string' && this.clear.email !== clean(body.email)) {
|
if (typeof body.email === 'string' && this.clear.email !== clean(body.email)) {
|
||||||
// Email change (requires confirmation)
|
// Email change (requires confirmation)
|
||||||
const check = randomString()
|
const check = randomString()
|
||||||
|
@ -538,7 +545,7 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
||||||
},
|
},
|
||||||
userId: this.record.id,
|
userId: this.record.id,
|
||||||
})
|
})
|
||||||
if (!isUnitTest || this.config.tests.sendEmail) {
|
if (!isTest || this.config.tests.sendEmail) {
|
||||||
// Send confirmation email
|
// Send confirmation email
|
||||||
await this.mailer.send({
|
await this.mailer.send({
|
||||||
template: 'emailchange',
|
template: 'emailchange',
|
||||||
|
@ -590,8 +597,7 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
||||||
result: 'success',
|
result: 'success',
|
||||||
account: this.asAccount(),
|
account: this.asAccount(),
|
||||||
}
|
}
|
||||||
if (isUnitTest && this.Confirmation.record?.id)
|
if (isTest && this.Confirmation.record?.id) returnData.confirmation = this.Confirmation.record.id
|
||||||
returnData.confirmation = this.Confirmation.record.id
|
|
||||||
|
|
||||||
return this.setResponse(200, false, returnData)
|
return this.setResponse(200, false, returnData)
|
||||||
}
|
}
|
||||||
|
@ -755,11 +761,14 @@ UserModel.prototype.sendResponse = async function (res) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update method to determine whether this request is
|
* Update method to determine whether this request is
|
||||||
* part of a unit test
|
* part of a (unit) test
|
||||||
*/
|
*/
|
||||||
UserModel.prototype.isUnitTest = function (body) {
|
UserModel.prototype.isTest = function (body) {
|
||||||
if (!body.unittest) return false
|
// Disalowing tests in prodution is hard-coded to protect people from
|
||||||
if (!this.clear.email.split('@').pop() === this.config.tests.domain) return false
|
if (this.config.env === 'production' && !this.config.tests.production) return false
|
||||||
|
if (!body.test) return false
|
||||||
|
if (this.clear?.email && !this.clear.email.split('@').pop() === this.config.tests.domain)
|
||||||
|
return false
|
||||||
if (body.email && !body.email.split('@').pop() === this.config.tests.domain) return false
|
if (body.email && !body.email.split('@').pop() === this.config.tests.domain) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
51
sites/backend/src/routes/curated-sets.mjs
Normal file
51
sites/backend/src/routes/curated-sets.mjs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { CuratedSetsController } from '../controllers/curated-sets.mjs'
|
||||||
|
|
||||||
|
const CuratedSets = new CuratedSetsController()
|
||||||
|
const jwt = ['jwt', { session: false }]
|
||||||
|
const bsc = ['basic', { session: false }]
|
||||||
|
|
||||||
|
export function curatedSetsRoutes(tools) {
|
||||||
|
const { app, passport } = tools
|
||||||
|
|
||||||
|
// Read a curated measurements set (no need to authenticate for this)
|
||||||
|
app.get('/curated-sets/:id.json', (req, res) => CuratedSets.read(req, res, tools, 'json'))
|
||||||
|
app.get('/curated-sets/:id.yaml', (req, res) => CuratedSets.read(req, res, tools, 'yaml'))
|
||||||
|
app.get('/curated-sets/:id', (req, res) => CuratedSets.read(req, res, tools))
|
||||||
|
|
||||||
|
// Get a list of all curated measurments sets (no need to authenticate for this)
|
||||||
|
app.get('/curated-sets.json', (req, res) => CuratedSets.list(req, res, tools, 'json'))
|
||||||
|
app.get('/curated-sets.yaml', (req, res) => CuratedSets.list(req, res, tools, 'yaml'))
|
||||||
|
app.get('/curated-sets', (req, res) => CuratedSets.list(req, res, tools))
|
||||||
|
|
||||||
|
// Create a curated measurements set
|
||||||
|
app.post('/curated-sets/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||||
|
CuratedSets.create(req, res, tools)
|
||||||
|
)
|
||||||
|
app.post('/curated-sets/key', passport.authenticate(...bsc), (req, res) =>
|
||||||
|
CuratedSets.create(req, res, tools)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clone a curated measurements set
|
||||||
|
app.post('/curated-sets/:id/clone/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||||
|
CuratedSets.clone(req, res, tools)
|
||||||
|
)
|
||||||
|
app.post('/curated-sets/:id/clone/key', passport.authenticate(...bsc), (req, res) =>
|
||||||
|
CuratedSets.clone(req, res, tools)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update a curated measurements set
|
||||||
|
app.patch('/curated-sets/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||||
|
CuratedSets.update(req, res, tools)
|
||||||
|
)
|
||||||
|
app.patch('/curated-sets/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||||
|
CuratedSets.update(req, res, tools)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete a curated measurements set
|
||||||
|
app.delete('/curated-sets/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||||
|
CuratedSets.delete(req, res, tools)
|
||||||
|
)
|
||||||
|
app.delete('/curated-sets/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||||
|
CuratedSets.delete(req, res, tools)
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import { usersRoutes } from './users.mjs'
|
||||||
import { setsRoutes } from './sets.mjs'
|
import { setsRoutes } from './sets.mjs'
|
||||||
import { patternsRoutes } from './patterns.mjs'
|
import { patternsRoutes } from './patterns.mjs'
|
||||||
import { confirmationsRoutes } from './confirmations.mjs'
|
import { confirmationsRoutes } from './confirmations.mjs'
|
||||||
//import { curatedSetsRoutes } from './curated-sets.mjs'
|
import { curatedSetsRoutes } from './curated-sets.mjs'
|
||||||
|
|
||||||
export const routes = {
|
export const routes = {
|
||||||
apikeysRoutes,
|
apikeysRoutes,
|
||||||
|
@ -11,5 +11,5 @@ export const routes = {
|
||||||
setsRoutes,
|
setsRoutes,
|
||||||
patternsRoutes,
|
patternsRoutes,
|
||||||
confirmationsRoutes,
|
confirmationsRoutes,
|
||||||
//curatedSetsRoutes,
|
curatedSetsRoutes,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { website } from '../config.mjs'
|
import { website } from '../config.mjs'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Capitalizes a string
|
||||||
|
*/
|
||||||
|
export const capitalize = (string) => string.charAt(0).toUpperCase() + string.slice(1)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Cleans a string (typically email) for hashing
|
* Cleans a string (typically email) for hashing
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -232,7 +232,7 @@ export const accountTests = async (chai, config, expect, store) => {
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
email: `updating_${store.randomString()}@${store.config.tests.domain}`,
|
email: `updating_${store.randomString()}@${store.config.tests.domain}`,
|
||||||
unittest: true,
|
test: true,
|
||||||
})
|
})
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
expect(err === null).to.equal(true)
|
expect(err === null).to.equal(true)
|
||||||
|
@ -287,7 +287,7 @@ export const accountTests = async (chai, config, expect, store) => {
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
email: store.account.email,
|
email: store.account.email,
|
||||||
unittest: true,
|
test: true,
|
||||||
})
|
})
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
expect(err === null).to.equal(true)
|
expect(err === null).to.equal(true)
|
||||||
|
|
309
sites/backend/tests/curated-set.mjs
Normal file
309
sites/backend/tests/curated-set.mjs
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
import { cat } from './cat.mjs'
|
||||||
|
import { capitalize } from '../src/utils/index.mjs'
|
||||||
|
|
||||||
|
export const curatedSetTests = async (chai, config, expect, store) => {
|
||||||
|
const data = {
|
||||||
|
jwt: {
|
||||||
|
nameDe: 'Beispielmessungen A',
|
||||||
|
nameEn: 'Example measurements A',
|
||||||
|
nameEs: 'Medidas de ejemplo A',
|
||||||
|
nameFr: 'Mesures exemple A',
|
||||||
|
nameNl: 'Voorbeel maten A',
|
||||||
|
notesDe: 'Das sind die Notizen A',
|
||||||
|
notesEn: 'These are the notes A',
|
||||||
|
notesEs: 'Estas son las notas A',
|
||||||
|
notesFr: 'Ce sont les notes A',
|
||||||
|
notesNl: 'Dit zijn de notities A',
|
||||||
|
measies: {
|
||||||
|
chest: 1000,
|
||||||
|
neck: 420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
nameDe: 'Beispielmessungen B',
|
||||||
|
nameEn: 'Example measurements B',
|
||||||
|
nameEs: 'Medidas de ejemplo B',
|
||||||
|
nameFr: 'Mesures exemple B',
|
||||||
|
nameNl: 'Voorbeel maten B',
|
||||||
|
notesDe: 'Das sind die Notizen B',
|
||||||
|
notesEn: 'These are the notes B',
|
||||||
|
notesEs: 'Estas son las notas B',
|
||||||
|
notesFr: 'Ce sont les notes B',
|
||||||
|
notesNl: 'Dit zijn de notities B',
|
||||||
|
measies: {
|
||||||
|
chest: 930,
|
||||||
|
neck: 360,
|
||||||
|
},
|
||||||
|
img: cat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
store.curatedSet = {
|
||||||
|
jwt: {},
|
||||||
|
key: {},
|
||||||
|
}
|
||||||
|
store.altset = {
|
||||||
|
jwt: {},
|
||||||
|
key: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auth of ['jwt', 'key']) {
|
||||||
|
describe(`${store.icon('set', auth)} Curated set tests (${auth})`, () => {
|
||||||
|
it(`${store.icon('set', auth)} Should create a new curated set (${auth})`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.post(`/curated-sets/${auth}`)
|
||||||
|
.set(
|
||||||
|
'Authorization',
|
||||||
|
auth === 'jwt'
|
||||||
|
? 'Bearer ' + store.account.token
|
||||||
|
: 'Basic ' +
|
||||||
|
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||||
|
'base64'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.send(data[auth])
|
||||||
|
.end((err, res) => {
|
||||||
|
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])) {
|
||||||
|
if (!['measies', 'img'].includes(key)) expect(res.body.curatedSet[key]).to.equal(val)
|
||||||
|
}
|
||||||
|
store.curatedSet[auth] = res.body.curatedSet
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
}).timeout(5000)
|
||||||
|
|
||||||
|
for (const field of ['name', 'notes']) {
|
||||||
|
for (const lang of config.languages) {
|
||||||
|
const langField = field + capitalize(lang)
|
||||||
|
it(`${store.icon(
|
||||||
|
'set',
|
||||||
|
auth
|
||||||
|
)} Should update the ${langField} field (${auth})`, (done) => {
|
||||||
|
const data = {}
|
||||||
|
const val = store.curatedSet[auth][langField] + '_updated'
|
||||||
|
data[langField] = val
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.patch(`/curated-sets/${store.curatedSet[auth].id}/${auth}`)
|
||||||
|
.set(
|
||||||
|
'Authorization',
|
||||||
|
auth === 'jwt'
|
||||||
|
? 'Bearer ' + store.account.token
|
||||||
|
: 'Basic ' +
|
||||||
|
new Buffer(
|
||||||
|
`${store.account.apikey.key}:${store.account.apikey.secret}`
|
||||||
|
).toString('base64')
|
||||||
|
)
|
||||||
|
.send(data)
|
||||||
|
.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.set[langField]).to.equal(val)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of ['chest', 'neck', 'ankle']) {
|
||||||
|
it(`${store.icon(
|
||||||
|
'set',
|
||||||
|
auth
|
||||||
|
)} Should update the ${field} measurement (${auth})`, (done) => {
|
||||||
|
const data = { measies: {} }
|
||||||
|
const val = Math.ceil(Math.random() * 1000)
|
||||||
|
data.measies[field] = val
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.patch(`/curated-sets/${store.curatedSet[auth].id}/${auth}`)
|
||||||
|
.set(
|
||||||
|
'Authorization',
|
||||||
|
auth === 'jwt'
|
||||||
|
? 'Bearer ' + store.account.token
|
||||||
|
: 'Basic ' +
|
||||||
|
new Buffer(
|
||||||
|
`${store.account.apikey.key}:${store.account.apikey.secret}`
|
||||||
|
).toString('base64')
|
||||||
|
)
|
||||||
|
.send(data)
|
||||||
|
.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.set.measies[field]).to.equal(val)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
it(`${store.icon(
|
||||||
|
'set',
|
||||||
|
auth
|
||||||
|
)} Should not set an non-existing measurement (${auth})`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.patch(`/curated-sets/${store.curatedSet[auth].id}/${auth}`)
|
||||||
|
.set(
|
||||||
|
'Authorization',
|
||||||
|
auth === 'jwt'
|
||||||
|
? 'Bearer ' + store.account.token
|
||||||
|
: 'Basic ' +
|
||||||
|
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||||
|
'base64'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
measies: {
|
||||||
|
ankle: 320,
|
||||||
|
potatoe: 12,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.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.set.measies.ankle).to.equal(320)
|
||||||
|
expect(typeof res.body.set.measies.potatoe).to.equal('undefined')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${store.icon('set', auth)} Should clear a measurement (${auth})`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.patch(`/curated-sets/${store.curatedSet[auth].id}/${auth}`)
|
||||||
|
.set(
|
||||||
|
'Authorization',
|
||||||
|
auth === 'jwt'
|
||||||
|
? 'Bearer ' + store.account.token
|
||||||
|
: 'Basic ' +
|
||||||
|
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||||
|
'base64'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
measies: {
|
||||||
|
chest: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(200)
|
||||||
|
expect(res.body.result).to.equal(`success`)
|
||||||
|
expect(typeof res.body.set.measies.chest).to.equal('undefined')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${store.icon('set', auth)} Should clone a set (${auth})`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.post(`/curated-sets/${store.curatedSet[auth].id}/clone/${auth}`)
|
||||||
|
.set(
|
||||||
|
'Authorization',
|
||||||
|
auth === 'jwt'
|
||||||
|
? 'Bearer ' + store.account.token
|
||||||
|
: 'Basic ' +
|
||||||
|
new Buffer(`${store.account.apikey.key}:${store.account.apikey.secret}`).toString(
|
||||||
|
'base64'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.send({ language: 'nl' })
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(201)
|
||||||
|
expect(res.body.result).to.equal(`success`)
|
||||||
|
expect(typeof res.body.error).to.equal(`undefined`)
|
||||||
|
expect(typeof res.body.set.id).to.equal(`number`)
|
||||||
|
expect(res.body.set.name).to.equal(store.curatedSet[auth].nameNl + '_updated')
|
||||||
|
expect(res.body.set.notes).to.equal(store.curatedSet[auth].notesNl + '_updated')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unauthenticated tests
|
||||||
|
describe(`${store.icon('set')} Curated set tests (unauthenticated)`, () => {
|
||||||
|
for (const auth of ['jwt', 'key']) {
|
||||||
|
it(`${store.icon('set')} Should read a curated set created with ${auth}`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.get(`/curated-sets/${store.curatedSet[auth].id}`)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(200)
|
||||||
|
expect(res.body.result).to.equal(`success`)
|
||||||
|
expect(typeof res.body.curatedSet.measies).to.equal('object')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${store.icon('set')} Should read a curated set created with ${auth} as JSON`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.get(`/curated-sets/${store.curatedSet[auth].id}.json`)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(200)
|
||||||
|
expect(typeof res.body.measurements).to.equal('object')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${store.icon('set')} Should read a curated set created with ${auth} as YAML`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.get(`/curated-sets/${store.curatedSet[auth].id}.yaml`)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(200)
|
||||||
|
expect(res.text).to.include('FreeSewing')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${store.icon('set')} Should retrieve a list of curated sets`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.get(`/curated-sets`)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(200)
|
||||||
|
expect(res.body.result).to.equal('success')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${store.icon('set')} Should retrieve a list of curated sets as JSON`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.get(`/curated-sets.json`)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${store.icon('set')} Should retrieve a list of curated sets as YAML`, (done) => {
|
||||||
|
chai
|
||||||
|
.request(config.api)
|
||||||
|
.get(`/curated-sets.yaml`)
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(err === null).to.equal(true)
|
||||||
|
expect(res.status).to.equal(200)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//console.log(`/curated-sets/${store.curatedSet[auth].id}/${auth}`)
|
||||||
|
//console.log({body: res.body, status: res.status})
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Delete Curated set
|
||||||
|
}
|
|
@ -3,16 +3,18 @@ import { mfaTests } from './mfa.mjs'
|
||||||
import { accountTests } from './account.mjs'
|
import { accountTests } from './account.mjs'
|
||||||
import { apikeyTests } from './apikey.mjs'
|
import { apikeyTests } from './apikey.mjs'
|
||||||
import { setTests } from './set.mjs'
|
import { setTests } from './set.mjs'
|
||||||
|
import { curatedSetTests } from './curated-set.mjs'
|
||||||
import { patternTests } from './pattern.mjs'
|
import { patternTests } from './pattern.mjs'
|
||||||
import { setup } from './shared.mjs'
|
import { setup } from './shared.mjs'
|
||||||
|
|
||||||
const runTests = async (...params) => {
|
const runTests = async (...params) => {
|
||||||
await userTests(...params)
|
//await userTests(...params)
|
||||||
await mfaTests(...params)
|
//await mfaTests(...params)
|
||||||
await apikeyTests(...params)
|
//await apikeyTests(...params)
|
||||||
await accountTests(...params)
|
//await accountTests(...params)
|
||||||
await setTests(...params)
|
//await setTests(...params)
|
||||||
await patternTests(...params)
|
await curatedSetTests(...params)
|
||||||
|
//await patternTests(...params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load initial data required for tests
|
// Load initial data required for tests
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const setTests = async (chai, config, expect, store) => {
|
||||||
neck: 420,
|
neck: 420,
|
||||||
},
|
},
|
||||||
public: true,
|
public: true,
|
||||||
unittest: true,
|
test: true,
|
||||||
imperial: true,
|
imperial: true,
|
||||||
},
|
},
|
||||||
key: {
|
key: {
|
||||||
|
@ -22,7 +22,7 @@ export const setTests = async (chai, config, expect, store) => {
|
||||||
},
|
},
|
||||||
public: false,
|
public: false,
|
||||||
img: cat,
|
img: cat,
|
||||||
unittest: true,
|
test: true,
|
||||||
imperial: false,
|
imperial: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,7 @@ export const setTests = async (chai, config, expect, store) => {
|
||||||
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))
|
if (!['measies', 'img', 'test'].includes(key)) expect(res.body.set[key]).to.equal(val)
|
||||||
expect(res.body.set[key]).to.equal(val)
|
|
||||||
}
|
}
|
||||||
store.set[auth] = res.body.set
|
store.set[auth] = res.body.set
|
||||||
done()
|
done()
|
||||||
|
|
|
@ -53,7 +53,8 @@ export const setup = async () => {
|
||||||
result = await axios.post(`${store.config.api}/signup`, {
|
result = await axios.post(`${store.config.api}/signup`, {
|
||||||
email: store[acc].email,
|
email: store[acc].email,
|
||||||
language: store[acc].language,
|
language: store[acc].language,
|
||||||
unittest: true,
|
test: true,
|
||||||
|
role: 'curator',
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Failed at first setup request', err)
|
console.log('Failed at first setup request', err)
|
||||||
|
@ -80,7 +81,7 @@ export const setup = async () => {
|
||||||
`${store.config.api}/apikeys/jwt`,
|
`${store.config.api}/apikeys/jwt`,
|
||||||
{
|
{
|
||||||
name: 'Test API key',
|
name: 'Test API key',
|
||||||
level: 4,
|
level: 5,
|
||||||
expiresIn: 60,
|
expiresIn: 60,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue