Merge pull request #5191 from freesewing/joost
chore(backend): Remove (c)set from patterns, img from users
This commit is contained in:
commit
e0838da197
14 changed files with 55 additions and 622 deletions
|
@ -64,7 +64,6 @@ model User {
|
|||
ehash String @unique
|
||||
email String
|
||||
ihash String
|
||||
img String?
|
||||
initial String
|
||||
imperial Boolean @default(false)
|
||||
jwtCalls Int @default(0)
|
||||
|
@ -94,17 +93,13 @@ model Pattern {
|
|||
img String?
|
||||
name String @default("")
|
||||
notes String
|
||||
set Set? @relation(fields: [setId], references: [id])
|
||||
setId Int?
|
||||
cset CuratedSet? @relation(fields: [csetId], references: [id])
|
||||
csetId Int?
|
||||
public Boolean @default(false)
|
||||
settings String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId, setId])
|
||||
@@index([userId, design])
|
||||
}
|
||||
|
||||
model Set {
|
||||
|
@ -117,7 +112,6 @@ model Set {
|
|||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
measies String @default("{}")
|
||||
patterns Pattern[]
|
||||
public Boolean @default(false)
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
|
@ -127,7 +121,7 @@ model Set {
|
|||
model CuratedSet {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
img String?
|
||||
info String @default("")
|
||||
nameDe String @default("")
|
||||
nameEn String @default("")
|
||||
nameEs String @default("")
|
||||
|
@ -142,7 +136,6 @@ model CuratedSet {
|
|||
notesUk String @default("")
|
||||
tags String @default("{}")
|
||||
measies String @default("[]")
|
||||
patterns Pattern[]
|
||||
updatedAt DateTime @updatedAt
|
||||
published Boolean @default(false)
|
||||
}
|
||||
|
@ -152,6 +145,7 @@ model OptionPack {
|
|||
createdAt DateTime @default(now())
|
||||
design String @default("")
|
||||
img String?
|
||||
info String @default("")
|
||||
nameDe String @default("")
|
||||
nameEn String @default("")
|
||||
nameEs String @default("")
|
||||
|
|
|
@ -58,9 +58,9 @@ CuratedSetModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
else data.measies = '{}'
|
||||
|
||||
/*
|
||||
* Set an initial img as we need the record ID to store an image on cloudflare
|
||||
* Add the info
|
||||
*/
|
||||
data.img = this.config.avatars.set
|
||||
if (body.info) data.info = body.info
|
||||
|
||||
/*
|
||||
* Create the database record
|
||||
|
@ -68,9 +68,9 @@ CuratedSetModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
await this.createRecord(data)
|
||||
|
||||
/*
|
||||
* Now that we have a record and ID, we can update the image after uploading it to cloudflare
|
||||
* Now that we have a record and ID, we can upload the image to cloudflare and set its id
|
||||
*/
|
||||
const img = await storeImage(
|
||||
await storeImage(
|
||||
{
|
||||
id: `cset-${this.record.id}`,
|
||||
metadata: { user: user.uid },
|
||||
|
@ -79,14 +79,6 @@ CuratedSetModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
this.isTest(body)
|
||||
)
|
||||
|
||||
/*
|
||||
* If an image was uploaded, update the record with the image ID
|
||||
*/
|
||||
if (img) await this.update({ img })
|
||||
/*
|
||||
* If not, just read the record from the datbasa
|
||||
*/ else await this.read({ id: this.record.id })
|
||||
|
||||
/*
|
||||
* Record created, return data in the proper format
|
||||
*/
|
||||
|
@ -147,6 +139,7 @@ CuratedSetModel.prototype.allCuratedSets = async function () {
|
|||
*/
|
||||
asPojo.measies = JSON.parse(asPojo.measies)
|
||||
asPojo.tags = JSON.parse(asPojo.tags)
|
||||
delete asPojo.info
|
||||
list.push(asPojo)
|
||||
}
|
||||
|
||||
|
@ -229,6 +222,11 @@ CuratedSetModel.prototype.guardedUpdate = async function ({ params, body, user }
|
|||
*/
|
||||
if ([true, false].includes(body.published)) data.published = body.published
|
||||
|
||||
/*
|
||||
* Handle the info field
|
||||
*/
|
||||
if (typeof body.info === 'string') data.info = body.info
|
||||
|
||||
/*
|
||||
* Unlike a regular set, curated set have notes and name in each language
|
||||
*/
|
||||
|
@ -255,7 +253,7 @@ CuratedSetModel.prototype.guardedUpdate = async function ({ params, body, user }
|
|||
* Handle the image, if there is one
|
||||
*/
|
||||
if (typeof body.img === 'string') {
|
||||
const img = await storeImage(
|
||||
await storeImage(
|
||||
{
|
||||
id: `cset-${this.record.id}`,
|
||||
metadata: { user: this.user.uid },
|
||||
|
@ -263,7 +261,6 @@ CuratedSetModel.prototype.guardedUpdate = async function ({ params, body, user }
|
|||
},
|
||||
this.isTest(body)
|
||||
)
|
||||
data.img = img
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -365,10 +362,7 @@ CuratedSetModel.prototype.suggest = async function ({ body, user }) {
|
|||
/*
|
||||
* If an image was uploaded, update the record with the image ID
|
||||
*/
|
||||
if (img) {
|
||||
data.img = img
|
||||
await this.Confirmation.update({ data })
|
||||
}
|
||||
if (img) await this.Confirmation.update({ data })
|
||||
|
||||
/*
|
||||
* Return id
|
||||
|
@ -438,8 +432,6 @@ CuratedSetModel.prototype.fromSuggestion = async function ({ params, user }) {
|
|||
* Now create the curated set
|
||||
*/
|
||||
await this.createRecord({
|
||||
// This image will need to be replaced
|
||||
img: this.Confirmation.clear.data.img,
|
||||
nameDe: name,
|
||||
nameEn: name,
|
||||
nameEs: name,
|
||||
|
@ -476,13 +468,14 @@ CuratedSetModel.prototype.fromSuggestion = async function ({ params, user }) {
|
|||
* @returns {curatedSet} object - The Cureated Set as a plain object
|
||||
*/
|
||||
CuratedSetModel.prototype.asCuratedSet = function () {
|
||||
return {
|
||||
...this.record,
|
||||
measies:
|
||||
typeof this.record.measies === 'string'
|
||||
? JSON.parse(this.record.measies)
|
||||
: this.record.measies,
|
||||
const data = { ...this.record }
|
||||
for (const field of this.jsonFields) {
|
||||
data[field] =
|
||||
typeof this.record[field] === 'string' ? JSON.parse(this.record[field]) : this.record[field]
|
||||
}
|
||||
delete data.info
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -499,6 +492,7 @@ CuratedSetModel.prototype.asData = function () {
|
|||
}
|
||||
data.measurements = data.measies
|
||||
delete data.measies
|
||||
delete data.info
|
||||
|
||||
return data
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ OptionPackModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
* Is design set?
|
||||
*/
|
||||
if (!body.design || typeof body.design !== 'string') return this.setResponse(400, 'designMissing')
|
||||
|
||||
/*
|
||||
* Is nameEn set?
|
||||
*/
|
||||
|
@ -57,6 +58,11 @@ OptionPackModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
}
|
||||
if (body.tags && Array.isArray(body.tags)) data.tags = JSON.stringify(body.tags)
|
||||
|
||||
/*
|
||||
* Add the info
|
||||
*/
|
||||
if (body.info) data.info = body.info
|
||||
|
||||
/*
|
||||
* Add the options if there are any
|
||||
*/
|
||||
|
@ -187,6 +193,11 @@ OptionPackModel.prototype.guardedUpdate = async function ({ params, body, user }
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the info field
|
||||
*/
|
||||
if (typeof body.info === 'string') data.info = body.info
|
||||
|
||||
/*
|
||||
* Handle the options
|
||||
*/
|
||||
|
@ -305,7 +316,10 @@ OptionPackModel.prototype.suggest = async function ({ body, user }) {
|
|||
* @returns {optionPack} object - The Option Pack as a plain object
|
||||
*/
|
||||
OptionPackModel.prototype.asOptionPack = function () {
|
||||
return this.record
|
||||
const data = { ...this.record }
|
||||
delete data.info
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -10,7 +10,6 @@ export function PatternModel(tools) {
|
|||
name: 'pattern',
|
||||
encryptedFields: ['data', 'name', 'notes', 'settings'],
|
||||
jsonFields: ['data'],
|
||||
models: ['set'],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -33,10 +32,6 @@ PatternModel.prototype.userPatterns = async function (uid) {
|
|||
try {
|
||||
patterns = await this.prisma.pattern.findMany({
|
||||
where: { userId: uid },
|
||||
include: {
|
||||
set: true,
|
||||
cset: true,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
log.warn(`Failed to search patterns for user ${uid}: ${err}`)
|
||||
|
@ -95,11 +90,9 @@ PatternModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
* Create initial record
|
||||
*/
|
||||
await this.createRecord({
|
||||
csetId: body.cset ? body.cset : null,
|
||||
data: typeof body.data === 'object' ? body.data : {},
|
||||
design: body.design,
|
||||
img: this.config.avatars.pattern,
|
||||
setId: body.set ? body.set : null,
|
||||
settings: {
|
||||
...body.settings,
|
||||
measurements:
|
||||
|
@ -129,7 +122,7 @@ PatternModel.prototype.guardedCreate = async function ({ body, user }) {
|
|||
* If not, just update the record from the database
|
||||
*/
|
||||
await this.update({ img })
|
||||
} else await this.read({ id: this.record.id }, { set: true, cset: true })
|
||||
} else await this.read({ id: this.record.id })
|
||||
|
||||
/*
|
||||
* Now return 201 and the record data
|
||||
|
@ -147,7 +140,7 @@ PatternModel.prototype.publicRead = async function ({ params }) {
|
|||
/*
|
||||
* Attempt to read the database record
|
||||
*/
|
||||
await this.read({ id: parseInt(params.id) }, { set: true, cset: true })
|
||||
await this.read({ id: parseInt(params.id) })
|
||||
|
||||
/*
|
||||
* Ensure it is public and if it is not public, return 404
|
||||
|
@ -184,7 +177,7 @@ PatternModel.prototype.guardedRead = async function ({ params, user }) {
|
|||
/*
|
||||
* Attempt to read record from database
|
||||
*/
|
||||
await this.read({ id: parseInt(params.id) }, { set: true, cset: true })
|
||||
await this.read({ id: parseInt(params.id) })
|
||||
|
||||
/*
|
||||
* Return 404 if it cannot be found
|
||||
|
@ -272,7 +265,7 @@ PatternModel.prototype.guardedUpdate = async function ({ params, body, user }) {
|
|||
/*
|
||||
* Attempt to read record from the database
|
||||
*/
|
||||
await this.read({ id: parseInt(params.id) }, { set: true, cset: true })
|
||||
await this.read({ id: parseInt(params.id) })
|
||||
|
||||
/*
|
||||
* Only admins can update other people's patterns
|
||||
|
@ -322,7 +315,7 @@ PatternModel.prototype.guardedUpdate = async function ({ params, body, user }) {
|
|||
/*
|
||||
* Now update the record
|
||||
*/
|
||||
await this.update(data, { set: true, cset: true })
|
||||
await this.update(data)
|
||||
|
||||
/*
|
||||
* Return 200 and the data
|
||||
|
@ -391,8 +384,6 @@ PatternModel.prototype.revealPattern = function (pattern) {
|
|||
//console.log(err)
|
||||
}
|
||||
}
|
||||
if (pattern.set) delete pattern.set.measies
|
||||
if (pattern.cset) delete pattern.cset.measies
|
||||
|
||||
return { ...pattern, ...clear }
|
||||
}
|
||||
|
@ -410,219 +401,3 @@ PatternModel.prototype.asPublicPattern = function () {
|
|||
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Everything below this comment is v2 => v3 migration code
|
||||
* And can be removed after the migration
|
||||
*/
|
||||
|
||||
const migratePattern = (v2, userId) => ({
|
||||
createdAt: new Date(v2.created ? v2.created : v2.createdAt),
|
||||
data: { version: v2.data.version, notes: ['Migrated from version 2'] },
|
||||
design: v2.design || v2.data.design,
|
||||
name: v2.name || '--',
|
||||
notes: v2.notes ? v2.notes + '\n\nMigrated from v2' : 'Migrated from v2',
|
||||
settings: v2.data.settings,
|
||||
userId,
|
||||
})
|
||||
|
||||
const v2lut = {
|
||||
'size 28, with breasts': 1,
|
||||
'size 30, with breasts': 2,
|
||||
'size 32, with breasts': 3,
|
||||
'size 34, with breasts': 4,
|
||||
'size 36, with breasts': 5,
|
||||
'size 38, with breasts': 6,
|
||||
'size 40, with breasts': 7,
|
||||
'size 42, with breasts': 8,
|
||||
'size 44, with breasts': 9,
|
||||
'size 46, with breasts': 10,
|
||||
'size-28-b': 1,
|
||||
'size-30-b': 2,
|
||||
'size-32-b': 3,
|
||||
'size-34-b': 4,
|
||||
'size-36-b': 5,
|
||||
'size-38-b': 6,
|
||||
'size-40-b': 7,
|
||||
'size-42-b': 8,
|
||||
'size-44-b': 9,
|
||||
'size-46-b': 10,
|
||||
'size-28-with-breasts': 1,
|
||||
'size-30-with-breasts': 2,
|
||||
'size-32-with-breasts': 3,
|
||||
'size-34-with-breasts': 4,
|
||||
'size-36-with-breasts': 5,
|
||||
'size-38-with-breasts': 6,
|
||||
'size-40-with-breasts': 7,
|
||||
'size-42-with-breasts': 8,
|
||||
'size-44-with-breasts': 9,
|
||||
'size-46-with-breasts': 10,
|
||||
'größe 28, mit brüsten': 1,
|
||||
'größe 30, mit brüsten': 2,
|
||||
'größe 32, mit brüsten': 3,
|
||||
'größe 34, mit brüsten': 4,
|
||||
'größe 36, mit brüsten': 5,
|
||||
'größe 38, mit brüsten': 6,
|
||||
'größe 40, mit brüsten': 7,
|
||||
'größe 42, mit brüsten': 8,
|
||||
'größe 44, mit brüsten': 9,
|
||||
'größe 46, mit brüsten': 10,
|
||||
'maat 28, met borsten': 1,
|
||||
'maat 30, met borsten': 2,
|
||||
'maat 32, met borsten': 3,
|
||||
'maat 34, met borsten': 4,
|
||||
'maat 36, met borsten': 5,
|
||||
'maat 38, met borsten': 6,
|
||||
'maat 40, met borsten': 7,
|
||||
'maat 42, met borsten': 8,
|
||||
'maat 44, met borsten': 9,
|
||||
'maat 46, met borsten': 10,
|
||||
'taille 28, avec des seins': 1,
|
||||
'taille 30, avec des seins': 2,
|
||||
'taille 32, avec des seins': 3,
|
||||
'taille 34, avec des seins': 4,
|
||||
'taille 36, avec des seins': 5,
|
||||
'taille 38, avec des seins': 6,
|
||||
'taille 40, avec des seins': 7,
|
||||
'taille 42, avec des seins': 8,
|
||||
'taille 44, avec des seins': 9,
|
||||
'taille 46, avec des seins': 10,
|
||||
'size 28, avec des seins': 1,
|
||||
'size 30, avec des seins': 2,
|
||||
'size 32, avec des seins': 3,
|
||||
'size 34, avec des seins': 4,
|
||||
'size 36, avec des seins': 5,
|
||||
'size 38, avec des seins': 6,
|
||||
'size 40, avec des seins': 7,
|
||||
'size 42, avec des seins': 8,
|
||||
'size 44, avec des seins': 9,
|
||||
'size 46, avec des seins': 10,
|
||||
'tamaño 28, con pechos': 1,
|
||||
'tamaño 30, con pechos': 2,
|
||||
'tamaño 32, con pechos': 3,
|
||||
'tamaño 34, con pechos': 4,
|
||||
'tamaño 36, con pechos': 5,
|
||||
'tamaño 38, con pechos': 6,
|
||||
'tamaño 40, con pechos': 7,
|
||||
'tamaño 42, con pechos': 8,
|
||||
'tamaño 44, con pechos': 9,
|
||||
'tamaño 46, con pechos': 10,
|
||||
|
||||
'size 32, without breasts': 11,
|
||||
'size 34, without breasts': 12,
|
||||
'size 36, without breasts': 13,
|
||||
'size 38, without breasts': 14,
|
||||
'size 40, without breasts': 15,
|
||||
'size 42, without breasts': 16,
|
||||
'size 44, without breasts': 17,
|
||||
'size 46, without breasts': 18,
|
||||
'size 48, without breasts': 19,
|
||||
'size 50, without breasts': 20,
|
||||
'taille 32, sans seins': 11,
|
||||
'taille 34, sans seins': 12,
|
||||
'taille 36, sans seins': 13,
|
||||
'taille 38, sans seins': 14,
|
||||
'taille 40, sans seins': 15,
|
||||
'taille 42, sans seins': 16,
|
||||
'taille 44, sans seins': 17,
|
||||
'taille 46, sans seins': 18,
|
||||
'taille 48, sans seins': 19,
|
||||
'taille 50, sans seins': 20,
|
||||
'size 32, sans seins': 11,
|
||||
'size 34, sans seins': 12,
|
||||
'size 36, sans seins': 13,
|
||||
'size 38, sans seins': 14,
|
||||
'size 40, sans seins': 15,
|
||||
'size 42, sans seins': 16,
|
||||
'size 44, sans seins': 17,
|
||||
'size 46, sans seins': 18,
|
||||
'size 48, sans seins': 19,
|
||||
'size 50, sans seins': 20,
|
||||
'size-28-without-breasts': 11,
|
||||
'size-30-without-breasts': 12,
|
||||
'size-32-without-breasts': 13,
|
||||
'size-34-without-breasts': 14,
|
||||
'size-36-without-breasts': 15,
|
||||
'size-38-without-breasts': 16,
|
||||
'size-40-without-breasts': 17,
|
||||
'size-42-without-breasts': 18,
|
||||
'size-44-without-breasts': 19,
|
||||
'size-46-without-breasts': 20,
|
||||
'size-32-a': 11,
|
||||
'size-34-a': 12,
|
||||
'size-36-a': 13,
|
||||
'size-38-a': 14,
|
||||
'size-40-a': 15,
|
||||
'size-42-a': 16,
|
||||
'size-44-a': 17,
|
||||
'size-46-a': 18,
|
||||
'size-48-a': 19,
|
||||
'size-50-a': 20,
|
||||
'maat 32, zonder borsten': 11,
|
||||
'maat 34, zonder borsten': 12,
|
||||
'maat 36, zonder borsten': 13,
|
||||
'maat 38, zonder borsten': 14,
|
||||
'maat 40, zonder borsten': 15,
|
||||
'maat 42, zonder borsten': 16,
|
||||
'maat 44, zonder borsten': 17,
|
||||
'maat 46, zonder borsten': 18,
|
||||
'maat 48, zonder borsten': 19,
|
||||
'maat 50, zonder borsten': 20,
|
||||
'größe 32, ohne brüste': 11,
|
||||
'größe 34, ohne brüste': 12,
|
||||
'größe 36, ohne brüste': 13,
|
||||
'größe 38, ohne brüste': 14,
|
||||
'größe 40, ohne brüste': 15,
|
||||
'größe 42, ohne brüste': 16,
|
||||
'größe 44, ohne brüste': 17,
|
||||
'größe 46, ohne brüste': 18,
|
||||
'größe 48, ohne brüste': 19,
|
||||
'größe 50, ohne brüste': 20,
|
||||
'grösse 32, ohne brüste': 11,
|
||||
'grösse 34, ohne brüste': 12,
|
||||
'grösse 36, ohne brüste': 13,
|
||||
'grösse 38, ohne brüste': 14,
|
||||
'grösse 40, ohne brüste': 15,
|
||||
'grösse 42, ohne brüste': 16,
|
||||
'grösse 44, ohne brüste': 17,
|
||||
'grösse 46, ohne brüste': 18,
|
||||
'grösse 48, ohne brüste': 19,
|
||||
'grösse 50, ohne brüste': 20,
|
||||
'tamaño 32, sin pechos': 11,
|
||||
'tamaño 34, sin pechos': 12,
|
||||
'tamaño 36, sin pechos': 13,
|
||||
'tamaño 38, sin pechos': 14,
|
||||
'tamaño 40, sin pechos': 15,
|
||||
'tamaño 42, sin pechos': 16,
|
||||
'tamaño 44, sin pechos': 17,
|
||||
'tamaño 46, sin pechos': 18,
|
||||
'tamaño 48, sin pechos': 19,
|
||||
'tamaño 50, sin pechos': 20,
|
||||
}
|
||||
/*
|
||||
* This is a special route not available for API users
|
||||
*/
|
||||
PatternModel.prototype.import = async function (v2user, lut, userId) {
|
||||
for (const pattern of Object.values(v2user.patterns)) {
|
||||
let skip = false
|
||||
const data = { ...migratePattern(pattern, userId), userId }
|
||||
if (lut[pattern.person]) data.setId = lut[pattern.person]
|
||||
else if (v2lut[pattern.person]) data.csetId = v2lut[pattern.person]
|
||||
else if (pattern.person.length !== 5 && !['any', 'original'].includes(pattern.person)) {
|
||||
console.log(`Cannot find ${pattern.person}`, pattern, { lut, v2lut })
|
||||
process.exit()
|
||||
}
|
||||
if (!data.design || ['theo', 'ursula', 'unice'].includes(data.design)) skip = true
|
||||
if (!skip) {
|
||||
// V2 does not support images for patterns
|
||||
data.img = 'default-avatar'
|
||||
try {
|
||||
this.createRecord(data)
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create pattern')
|
||||
console.log(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { log } from '../utils/log.mjs'
|
||||
import { replaceImage, storeImage, importImage } from '../utils/cloudflare-images.mjs'
|
||||
import { replaceImage, storeImage } from '../utils/cloudflare-images.mjs'
|
||||
import { decorateModel } from '../utils/model-decorator.mjs'
|
||||
|
||||
/*
|
||||
|
@ -386,79 +386,3 @@ SetModel.prototype.asPublicSet = function () {
|
|||
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Everything below this comment is part of the v2 => v3 migration code
|
||||
* and can be removed once that migration is complete
|
||||
*/
|
||||
|
||||
const migratePerson = (v2) => ({
|
||||
createdAt: new Date(v2.created ? v2.created : v2.createdAt),
|
||||
imperial: v2.units === 'imperial',
|
||||
name: v2.name || '--', // Encrypted, so always set _some_ value
|
||||
notes: v2.notes || '--', // Encrypted, so always set _some_ value
|
||||
measies: v2.measurements || {}, // Encrypted, so always set _some_ value
|
||||
updatedAt: new Date(v2.updatedAt),
|
||||
})
|
||||
|
||||
/*
|
||||
* This is a special import route
|
||||
*/
|
||||
SetModel.prototype.migrate = async function (v2user, userId) {
|
||||
const lut = {} // lookup tabel for v2 handle to v3 id
|
||||
for (const [handle, person] of Object.entries(v2user.people)) {
|
||||
const data = { ...migratePerson(person), userId }
|
||||
data.img = 'default-avatar'
|
||||
try {
|
||||
await this.createRecord(data)
|
||||
lut[handle] = this.record.id
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create set')
|
||||
console.log(person)
|
||||
}
|
||||
}
|
||||
|
||||
return lut
|
||||
}
|
||||
/*
|
||||
* This is a special route not available for API users
|
||||
*/
|
||||
SetModel.prototype.import = async function (v2user, userId) {
|
||||
const lut = {} // lookup tabel for v2 handle to v3 id
|
||||
for (const [handle, person] of Object.entries(v2user.people)) {
|
||||
const data = { ...migratePerson(person), userId }
|
||||
await this.createRecord(data)
|
||||
// Now that we have an ID, we can handle the image
|
||||
if (person.picture && person.picture.slice(-4) !== '.svg') {
|
||||
const imgId = `set-${this.record.id}`
|
||||
const imgUrl =
|
||||
'https://static.freesewing.org/users/' +
|
||||
encodeURIComponent(v2user.handle.slice(0, 1)) +
|
||||
'/' +
|
||||
encodeURIComponent(v2user.handle) +
|
||||
'/people/' +
|
||||
encodeURIComponent(person.handle) +
|
||||
'/' +
|
||||
encodeURIComponent(person.picture)
|
||||
data.img = await importImage({
|
||||
id: imgId,
|
||||
metadata: {
|
||||
user: userId,
|
||||
v2PersonHandle: handle,
|
||||
},
|
||||
url: imgUrl,
|
||||
})
|
||||
data.img = imgId
|
||||
} else data.img = 'default-avatar'
|
||||
try {
|
||||
await this.createRecord(data)
|
||||
lut[handle] = this.record.id
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create set')
|
||||
console.log(person)
|
||||
}
|
||||
}
|
||||
|
||||
return lut
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { hash } from '../utils/crypto.mjs'
|
||||
import { log } from '../utils/log.mjs'
|
||||
import { clean, i18nUrl } from '../utils/index.mjs'
|
||||
import { decorateModel } from '../utils/model-decorator.mjs'
|
||||
|
||||
|
@ -192,42 +191,3 @@ SubscriberModel.prototype.verifySubscription = async function (body) {
|
|||
|
||||
return this
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Anything below this comment is migration code for the v2 => v3 migration
|
||||
* and can be safely removed after the migration is done
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a special route not available for API users
|
||||
*/
|
||||
SubscriberModel.prototype.import = async function (list) {
|
||||
let created = 0
|
||||
for (const sub of list) {
|
||||
const email = clean(sub)
|
||||
const ehash = hash(email)
|
||||
await this.read({ ehash })
|
||||
|
||||
if (!this.record) {
|
||||
const data = await this.cloak({
|
||||
ehash,
|
||||
email,
|
||||
language: 'en',
|
||||
active: true,
|
||||
})
|
||||
try {
|
||||
this.record = await this.prisma.subscriber.create({ data })
|
||||
created++
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create subscriber record')
|
||||
return this.setResponse(500, 'createSubscriberFailed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.setResponse(200, 'success', {
|
||||
total: list.length,
|
||||
imported: created,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
import { log } from '../utils/log.mjs'
|
||||
import { hash, hashPassword, randomString, verifyPassword } from '../utils/crypto.mjs'
|
||||
import { replaceImage, importImage, removeImage } from '../utils/cloudflare-images.mjs'
|
||||
import { replaceImage, removeImage } from '../utils/cloudflare-images.mjs'
|
||||
import { clean, asJson, i18nUrl, writeExportedData } from '../utils/index.mjs'
|
||||
import { decorateModel } from '../utils/model-decorator.mjs'
|
||||
import { userCard } from '../templates/svg/user-card.mjs'
|
||||
|
@ -171,18 +171,16 @@ UserModel.prototype.oauthSignIn = async function ({ body }) {
|
|||
*/
|
||||
if (oauthData.img) {
|
||||
try {
|
||||
const img = await replaceImage({
|
||||
await replaceImage({
|
||||
id: `user-${ihash}`,
|
||||
metadata: { ihash },
|
||||
url: oauthData.img,
|
||||
})
|
||||
if (img) data.img = this.encrypt(img)
|
||||
else data.img = this.encrypt(this.config.avatars.user)
|
||||
} catch (err) {
|
||||
log.info(err, `Unable to update image post-oauth signup for user ${email}`)
|
||||
return this.setResponse(500, 'createAccountFailed')
|
||||
}
|
||||
} else data.img = this.config.avatars.user
|
||||
}
|
||||
|
||||
/*
|
||||
* Now attempt to create the record in the database
|
||||
|
@ -763,7 +761,6 @@ UserModel.prototype.guardedCreate = async function ({ body }) {
|
|||
*/
|
||||
data: this.encrypt({}),
|
||||
bio: this.encrypt(''),
|
||||
img: this.config.avatars.user,
|
||||
}
|
||||
/*
|
||||
* During tests, users can set their own permission level so you can test admin stuff
|
||||
|
@ -1295,7 +1292,7 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
|||
* Image (img)
|
||||
*/
|
||||
if (typeof body.img === 'string')
|
||||
data.img = await replaceImage({
|
||||
await replaceImage({
|
||||
id: `uid-${this.record.ihash}`,
|
||||
data: body.img,
|
||||
})
|
||||
|
@ -1593,7 +1590,6 @@ UserModel.prototype.asProfile = function () {
|
|||
return {
|
||||
id: this.record.id,
|
||||
bio: this.clear.bio,
|
||||
img: this.record.img,
|
||||
ihash: this.record.ihash,
|
||||
patron: this.record.patron,
|
||||
role: this.record.role,
|
||||
|
@ -1620,7 +1616,6 @@ UserModel.prototype.asAccount = function () {
|
|||
email: this.clear.email,
|
||||
data: this.clear.data,
|
||||
ihash: this.record.ihash,
|
||||
img: this.record.img,
|
||||
imperial: this.record.imperial,
|
||||
initial: this.clear.initial,
|
||||
jwtCalls: this.record.jwtCalls,
|
||||
|
@ -1901,150 +1896,3 @@ UserModel.prototype.papersPlease = async function (id, type, payload) {
|
|||
*/
|
||||
return [true, false]
|
||||
}
|
||||
|
||||
/*
|
||||
* Everything below this comment is migration code.
|
||||
* This can all be removed after v3 is in production and all users have been migrated.
|
||||
*/
|
||||
const migrateUser = (v2) => {
|
||||
const email = clean(v2.email)
|
||||
const initial = v2.initial ? clean(v2.initial) : email
|
||||
const data = {
|
||||
bio: v2.bio || '--',
|
||||
consent: 0,
|
||||
createdAt: v2.time?.created ? new Date(v2.time.created) : new Date(),
|
||||
email,
|
||||
ehash: hash(email),
|
||||
data: {},
|
||||
ihash: hash(initial),
|
||||
img: 'default-avatar',
|
||||
initial,
|
||||
imperial: v2.settings.units === 'imperial',
|
||||
language: v2.settings.language,
|
||||
lastSeen: new Date(),
|
||||
lusername: v2.username.toLowerCase(),
|
||||
mfaEnabled: false,
|
||||
newsletter: v2.newsletter === true ? true : false,
|
||||
patron: v2.patron,
|
||||
role: v2._id === '5d62aa44ce141a3b816a3dd9' ? 'admin' : 'user',
|
||||
status: v2.status === 'active' ? 1 : 0,
|
||||
username: v2.username,
|
||||
}
|
||||
if (data.consent.profile) data.consent++
|
||||
if (data.consent.measurements) data.consent++
|
||||
if (data.consent.openData) data.consent++
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a special migration route
|
||||
*/
|
||||
UserModel.prototype.migrate = async function ({ password, v2 }) {
|
||||
//let lut = false
|
||||
const data = migrateUser(v2.account)
|
||||
if (v2.account.consent.profile && (v2.account.consent.model || v2.account.consent.measurements)) {
|
||||
data.consent++
|
||||
if (v2.account.consent.openData) v2.account.consent++
|
||||
}
|
||||
data.password = password
|
||||
await this.read({ ehash: data.ehash })
|
||||
if (!this.record) {
|
||||
/*
|
||||
* Skip images for now
|
||||
*/
|
||||
data.img = 'default-avatar'
|
||||
let available = await this.isLusernameAvailable(data.lusername)
|
||||
while (!available) {
|
||||
data.username += '+'
|
||||
data.lusername += '+'
|
||||
available = await this.isLusernameAvailable(data.lusername)
|
||||
}
|
||||
try {
|
||||
await this.createRecord(data)
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create user record')
|
||||
return this.setResponse(500, 'createUserFailed')
|
||||
}
|
||||
// That's the user, now load their people as sets
|
||||
const user = {
|
||||
...v2.account,
|
||||
people: v2.people,
|
||||
patterns: v2.patterns,
|
||||
}
|
||||
if (user.people) await this.Set.migrate(user, this.record.id)
|
||||
//if (user.people) lut = await this.Set.migrate(user, this.record.id)
|
||||
//if (user.patterns) await this.Pattern.import(user, lut, this.record.id)
|
||||
} else {
|
||||
return this.setResponse(400, 'userExists')
|
||||
}
|
||||
|
||||
/*
|
||||
* Decrypt data so we can return it
|
||||
*/
|
||||
await this.reveal()
|
||||
|
||||
/*
|
||||
* Looks like the migration was a success. Return a passwordless sign in
|
||||
*/
|
||||
return this.signInOk()
|
||||
}
|
||||
/*
|
||||
* This is a special route not available for API users
|
||||
*/
|
||||
UserModel.prototype.import = async function (user) {
|
||||
if (user.status === 'active') {
|
||||
const data = migrateUser(user)
|
||||
if (user.consent.profile && (user.consent.model || user.consent.measurements)) {
|
||||
data.consent++
|
||||
if (user.consent.openData) data.consent++
|
||||
}
|
||||
|
||||
await this.read({ ehash: data.ehash })
|
||||
if (!this.record) {
|
||||
/*
|
||||
* Skip images for now
|
||||
*/
|
||||
if (data.img) {
|
||||
/*
|
||||
* Figure out what image to grab from the FreeSewing v2 backend server
|
||||
*/
|
||||
const imgId = `user-${data.ihash}`
|
||||
const imgUrl =
|
||||
'https://static.freesewing.org/users/' +
|
||||
encodeURIComponent(user.handle.slice(0, 1)) +
|
||||
'/' +
|
||||
encodeURIComponent(user.handle) +
|
||||
'/' +
|
||||
encodeURIComponent(data.img)
|
||||
data.img = await importImage({
|
||||
id: imgId,
|
||||
metadata: {
|
||||
user: `v2-${user.handle}`,
|
||||
ihash: data.ihash,
|
||||
},
|
||||
url: imgUrl,
|
||||
})
|
||||
data.img = imgId
|
||||
} else data.img = 'default-avatar'
|
||||
let available = await this.isLusernameAvailable(data.lusername)
|
||||
while (!available) {
|
||||
data.username += '+'
|
||||
data.lusername += '+'
|
||||
available = await this.isLusernameAvailable(data.lusername)
|
||||
}
|
||||
try {
|
||||
await this.createRecord(data)
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create user record')
|
||||
return this.setResponse(500, 'createUserFailed')
|
||||
}
|
||||
// That's the user, now load their people as sets
|
||||
let lut = false
|
||||
if (user.people) lut = await this.Set.import(user, this.record.id)
|
||||
if (user.patterns) await this.Pattern.import(user, lut, this.record.id)
|
||||
}
|
||||
}
|
||||
|
||||
return this.setResponse200()
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { ImportsController } from '../controllers/imports.mjs'
|
||||
|
||||
const Import = new ImportsController()
|
||||
|
||||
export function importsRoutes(tools) {
|
||||
const { app } = tools
|
||||
|
||||
/*
|
||||
* All these routes use hard-coded credentials because they should never be used
|
||||
* outside the v2-v3 migration which is handled by joost
|
||||
*/
|
||||
|
||||
// Import newsletter subscriptions
|
||||
app.post('/import/subscribers', (req, res) => Import.subscribers(req, res, tools))
|
||||
|
||||
// Import users
|
||||
app.post('/import/user', (req, res) => Import.user(req, res, tools))
|
||||
|
||||
// Migrate user
|
||||
app.post('/migrate', (req, res) => Import.migrate(req, res, tools))
|
||||
}
|
|
@ -9,7 +9,6 @@ import { optionPacksRoutes } from './option-packs.mjs'
|
|||
import { subscribersRoutes } from './subscribers.mjs'
|
||||
import { flowsRoutes } from './flows.mjs'
|
||||
import { adminRoutes } from './admin.mjs'
|
||||
import { importsRoutes } from './imports.mjs'
|
||||
|
||||
export const routes = {
|
||||
apikeysRoutes,
|
||||
|
@ -23,5 +22,4 @@ export const routes = {
|
|||
subscribersRoutes,
|
||||
flowsRoutes,
|
||||
adminRoutes,
|
||||
importsRoutes,
|
||||
}
|
||||
|
|
|
@ -92,30 +92,6 @@ export async function removeImage(id) {
|
|||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
* Method that imports and image from URL and does not bother waiting for the answer
|
||||
*/
|
||||
export async function importImage(props, isTest = false) {
|
||||
if (isTest) return props.id || false
|
||||
// Bypass slow ass upload when testing import
|
||||
if (!config.import) return `default-avatar`
|
||||
|
||||
// Only upload user images for now
|
||||
if (props.id.slice(0, 5) !== 'user-') return `default-avatar`
|
||||
|
||||
const form = getFormData(props)
|
||||
/*
|
||||
* The image may already exist, so swallow the error
|
||||
*/
|
||||
try {
|
||||
await axios.post(config.api, form, { headers })
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return props.id
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to construct the form data for cloudflare
|
||||
*/
|
||||
|
|
|
@ -217,13 +217,13 @@ export const accountTests = 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.account.img).to.equal('string')
|
||||
done()
|
||||
})
|
||||
}).timeout(5000)
|
||||
}
|
||||
|
||||
let confirmation
|
||||
// eslint-disable-next-line no-undef
|
||||
step(
|
||||
`${store.icon('user', auth)} Should update the account email address (${auth})`,
|
||||
(done) => {
|
||||
|
@ -247,13 +247,13 @@ export const accountTests = 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.account.img).to.equal('string')
|
||||
confirmation = res.body.confirmation
|
||||
done()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
step(`${store.icon('user', auth)} Should confirm the email change (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
|
@ -275,12 +275,12 @@ export const accountTests = 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.account.img).to.equal('string')
|
||||
confirmation = res.body.confirmation
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
step(`${store.icon('user', auth)} Restore email address (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
|
@ -302,12 +302,12 @@ export const accountTests = 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.account.img).to.equal('string')
|
||||
confirmation = res.body.confirmation
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
step(
|
||||
`${store.icon('user', auth)} Should confirm the (restore) email change (${auth})`,
|
||||
(done) => {
|
||||
|
@ -331,7 +331,6 @@ export const accountTests = 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.account.img).to.equal('string')
|
||||
confirmation = res.body.confirmation
|
||||
done()
|
||||
})
|
||||
|
|
|
@ -42,7 +42,6 @@ export const curatedSetTests = async (chai, config, expect, store) => {
|
|||
chest: 930,
|
||||
neck: 360,
|
||||
},
|
||||
img: cat,
|
||||
},
|
||||
}
|
||||
store.curatedSet = {
|
||||
|
@ -77,7 +76,7 @@ export const curatedSetTests = async (chai, config, expect, store) => {
|
|||
expect(res.status).to.equal(201)
|
||||
expect(res.body.result).to.equal(`created`)
|
||||
for (const [key, val] of Object.entries(data[auth])) {
|
||||
if (!['measies', 'img', 'test'].includes(key)) {
|
||||
if (!['measies', 'test'].includes(key)) {
|
||||
expect(JSON.stringify(res.body.curatedSet[key])).to.equal(JSON.stringify(val))
|
||||
}
|
||||
}
|
||||
|
@ -226,8 +225,8 @@ export const curatedSetTests = async (chai, config, expect, store) => {
|
|||
set: 1,
|
||||
notes: 'These are the notes',
|
||||
name: 'me',
|
||||
img: 'https://images.pexels.com/photos/1404819/pexels-photo-1404819.jpeg?cs=srgb&dl=pexels-cong-h-1404819.jpg&fm=jpg&w=640&h=427&_gl=1*nyz31t*_ga*MTM0OTk5OTY4NS4xNjYxMjUyMjc0*_ga_8JE65Q40S6*MTY5Mzg0MzAwNi4yNC4xLjE2OTM4NDMwMjIuMC4wLjA.',
|
||||
height: '166cm',
|
||||
img: cat,
|
||||
})
|
||||
.end((err, res) => {
|
||||
expect(err === null).to.equal(true)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { cat } from './cat.mjs'
|
||||
import { capitalize } from '../src/utils/index.mjs'
|
||||
|
||||
export const optionPackTests = async (chai, config, expect, store) => {
|
||||
|
@ -46,7 +45,6 @@ export const optionPackTests = async (chai, config, expect, store) => {
|
|||
necklineBend: 0.7,
|
||||
necklineDrop: 0.4,
|
||||
},
|
||||
img: cat,
|
||||
},
|
||||
}
|
||||
store.apack = {
|
||||
|
@ -81,7 +79,7 @@ export const optionPackTests = async (chai, config, expect, store) => {
|
|||
expect(res.status).to.equal(201)
|
||||
expect(res.body.result).to.equal(`created`)
|
||||
for (const [key, val] of Object.entries(data[auth])) {
|
||||
if (!['options', 'img', 'test'].includes(key)) {
|
||||
if (!['options', 'test'].includes(key)) {
|
||||
expect(JSON.stringify(res.body.optionPack[key])).to.equal(JSON.stringify(val))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ export const patternTests = async (chai, config, expect, store) => {
|
|||
name: 'Just a test',
|
||||
notes: 'These are my notes',
|
||||
public: true,
|
||||
set: store.account.sets.her.id,
|
||||
data: {
|
||||
some: 'value',
|
||||
},
|
||||
|
@ -39,7 +38,6 @@ export const patternTests = async (chai, config, expect, store) => {
|
|||
expect(res.body.result).to.equal(`created`)
|
||||
expect(typeof res.body.pattern?.id).to.equal('number')
|
||||
expect(res.body.pattern.userId).to.equal(store.account.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
|
||||
|
@ -121,29 +119,6 @@ export const patternTests = async (chai, config, expect, store) => {
|
|||
})
|
||||
})
|
||||
|
||||
it(`${store.icon('pattern', auth)} Should not update the set field (${auth})`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.patch(`/patterns/${store.account.patterns[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({ 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.setId).to.equal(store.account.sets.her.id)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
for (const field of ['data', 'settings']) {
|
||||
it(`${store.icon('pattern', auth)} Should update the ${field} field (${auth})`, (done) => {
|
||||
const data = {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue