wip(backend): Working on migration of user data
This commit is contained in:
parent
0b441eed58
commit
550830310c
18 changed files with 734 additions and 79 deletions
|
@ -1,9 +1,10 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
import { log } from '../utils/log.mjs'
|
||||
import { hash, hashPassword, randomString, verifyPassword } from '../utils/crypto.mjs'
|
||||
import { setUserAvatar } from '../utils/sanity.mjs'
|
||||
import { setUserAvatar, downloadImage } from '../utils/sanity.mjs'
|
||||
import { clean, asJson, i18nUrl } from '../utils/index.mjs'
|
||||
import { ConfirmationModel } from './confirmation.mjs'
|
||||
import { SetModel } from './set.mjs'
|
||||
|
||||
export function UserModel(tools) {
|
||||
this.config = tools.config
|
||||
|
@ -16,6 +17,8 @@ export function UserModel(tools) {
|
|||
this.Confirmation = new ConfirmationModel(tools)
|
||||
this.encryptedFields = ['bio', 'github', 'email', 'initial', 'img', 'mfaSecret']
|
||||
this.clear = {} // For holding decrypted data
|
||||
// Only used for import, can be removed after v3 is released
|
||||
this.Set = new SetModel(tools)
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -542,7 +545,7 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
|||
}
|
||||
// Image (img)
|
||||
if (typeof body.img === 'string') {
|
||||
const img = await setUserAvatar(this.record.id, body.img)
|
||||
const img = await setUserAvatar(this.record.ihash, body.img, data.username)
|
||||
data.img = img.url
|
||||
}
|
||||
|
||||
|
@ -840,3 +843,119 @@ UserModel.prototype.isLusernameAvailable = async function (lusername) {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
const migrateUser = (v2) => {
|
||||
const email = clean(v2.email)
|
||||
const initial = clean(v2.initial)
|
||||
const data = {
|
||||
bio: v2.bio,
|
||||
consent: 0,
|
||||
createdAt: v2.time?.created ? new Date(v2.time.created) : new Date(),
|
||||
email,
|
||||
ehash: hash(email),
|
||||
github: v2.social?.github,
|
||||
ihash: hash(initial),
|
||||
img:
|
||||
v2.picture.slice(-4).toLowerCase() === '.svg' // Don't bother with default avatars
|
||||
? ''
|
||||
: v2.picture,
|
||||
initial,
|
||||
imperial: v2.units === 'imperial',
|
||||
language: v2.settings.language,
|
||||
lastSignIn: v2.time.login,
|
||||
lusername: v2.username.toLowerCase(),
|
||||
mfaEnabled: false,
|
||||
newsletter: false,
|
||||
password: JSON.stringify({
|
||||
type: 'v2',
|
||||
data: v2.password,
|
||||
}),
|
||||
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
|
||||
}
|
||||
|
||||
const lastLoginInDays = (user) => {
|
||||
const now = new Date()
|
||||
if (!user.time) console.log(user)
|
||||
const then = new Date(user.time.login)
|
||||
|
||||
const delta = Math.floor((now - then) / (1000 * 60 * 60 * 24))
|
||||
|
||||
return delta
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a special route not available for API users
|
||||
*/
|
||||
UserModel.prototype.import = async function (list) {
|
||||
let created = 0
|
||||
const skipped = []
|
||||
for (const sub of list) {
|
||||
if (sub.status === 'active') {
|
||||
const days = lastLoginInDays(sub)
|
||||
if (days < 370) {
|
||||
const data = migrateUser(sub)
|
||||
await this.read({ ehash: data.ehash })
|
||||
if (!this.record) {
|
||||
/*
|
||||
* Grab the image from the FreeSewing server and upload it to Sanity
|
||||
*/
|
||||
if (data.img) {
|
||||
const imgUrl =
|
||||
'https://static.freesewing.org/users/' +
|
||||
encodeURIComponent(sub.handle.slice(0, 1)) +
|
||||
'/' +
|
||||
encodeURIComponent(sub.handle) +
|
||||
'/' +
|
||||
encodeURIComponent(data.img)
|
||||
console.log('Grabbing', imgUrl)
|
||||
const [contentType, imgData] = await downloadImage(imgUrl)
|
||||
// Do not import the default SVG avatar
|
||||
if (contentType !== 'image/svg+xml') {
|
||||
const img = await setUserAvatar(data.ihash, [contentType, imgData], data.username)
|
||||
data.img = img
|
||||
}
|
||||
}
|
||||
let cloaked = await this.cloak(data)
|
||||
try {
|
||||
this.record = await this.prisma.user.create({ data: cloaked })
|
||||
created++
|
||||
} catch (err) {
|
||||
if (
|
||||
err.toString().indexOf('Unique constraint failed on the fields: (`lusername`)') !== -1
|
||||
) {
|
||||
// Just add a '+' to the username
|
||||
data.username += '+'
|
||||
data.lusername += '+'
|
||||
cloaked = await this.cloak(data)
|
||||
try {
|
||||
this.record = await this.prisma.user.create({ data: cloaked })
|
||||
created++
|
||||
} catch (err) {
|
||||
log.warn(err, 'Could not create user record')
|
||||
console.log(sub)
|
||||
return this.setResponse(500, 'createUserFailed')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else skipped.push(sub.email)
|
||||
// That's the user, not load their people as sets
|
||||
if (sub.people) await this.Set.import(sub, this.record.id)
|
||||
} else skipped.push(sub.email)
|
||||
}
|
||||
|
||||
return this.setResponse(200, 'success', {
|
||||
skipped,
|
||||
total: list.length,
|
||||
imported: created,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue