1
0
Fork 0

wip(backend): Working on migration of user data

This commit is contained in:
joostdecock 2023-08-06 18:27:36 +02:00
parent 0b441eed58
commit 550830310c
18 changed files with 734 additions and 79 deletions

View file

@ -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,
})
}