ajout app
This commit is contained in:
315
Voltaserve/idp/src/user/repo.ts
Normal file
315
Voltaserve/idp/src/user/repo.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
import { ErrorCode, newError } from '@/infra/error'
|
||||
import { client } from '@/infra/postgres'
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
fullName: string
|
||||
username: string
|
||||
email: string
|
||||
passwordHash: string
|
||||
refreshTokenValue?: string
|
||||
refreshTokenExpiry?: string
|
||||
resetPasswordToken?: string
|
||||
emailConfirmationToken?: string
|
||||
isEmailConfirmed: boolean
|
||||
emailUpdateToken?: string
|
||||
emailUpdateValue?: string
|
||||
picture?: string
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export type InsertOptions = {
|
||||
id: string
|
||||
fullName?: string
|
||||
username?: string
|
||||
email?: string
|
||||
passwordHash?: string
|
||||
refreshTokenValue?: string
|
||||
refreshTokenExpiry?: string
|
||||
resetPasswordToken?: string
|
||||
emailConfirmationToken?: string
|
||||
isEmailConfirmed?: boolean
|
||||
picture?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export type UpdateOptions = {
|
||||
id: string
|
||||
fullName?: string
|
||||
username?: string
|
||||
email?: string
|
||||
passwordHash?: string
|
||||
refreshTokenValue?: string
|
||||
refreshTokenExpiry?: string
|
||||
resetPasswordToken?: string
|
||||
emailConfirmationToken?: string
|
||||
isEmailConfirmed?: boolean
|
||||
emailUpdateToken?: string
|
||||
emailUpdateValue?: string
|
||||
picture?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export interface UserRepo {
|
||||
findByID(id: string): Promise<User>
|
||||
findByUsername(username: string): Promise<User>
|
||||
findByEmail(email: string): Promise<User>
|
||||
findByRefreshTokenValue(refreshTokenValue: string): Promise<User>
|
||||
findByResetPasswordToken(resetPasswordToken: string): Promise<User>
|
||||
findByEmailConfirmationToken(emailConfirmationToken: string): Promise<User>
|
||||
findByEmailUpdateToken(emailUpdateToken: string): Promise<User>
|
||||
findByPicture(picture: string): Promise<User>
|
||||
isUsernameAvailable(username: string): Promise<boolean>
|
||||
insert(data: InsertOptions): Promise<User>
|
||||
update(data: UpdateOptions): Promise<User>
|
||||
delete(id: string): Promise<void>
|
||||
}
|
||||
|
||||
class UserRepoImpl {
|
||||
async findByID(id: string): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE id = $1`,
|
||||
[id]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with id=${id} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async findByUsername(username: string): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE username = $1`,
|
||||
[username]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with username=${username} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async findByEmail(email: string): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE email = $1`,
|
||||
[email]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with email=${email} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async findByRefreshTokenValue(refreshTokenValue: string): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE refresh_token_value = $1`,
|
||||
[refreshTokenValue]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with refresh_token_value=${refreshTokenValue} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async findByResetPasswordToken(resetPasswordToken: string): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE reset_password_token = $1`,
|
||||
[resetPasswordToken]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with reset_password_token=${resetPasswordToken} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async findByEmailConfirmationToken(
|
||||
emailConfirmationToken: string
|
||||
): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE email_confirmation_token = $1`,
|
||||
[emailConfirmationToken]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with email_confirmation_token=${emailConfirmationToken} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async findByEmailUpdateToken(emailUpdateToken: string): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE email_update_token = $1`,
|
||||
[emailUpdateToken]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with email_update_token=${emailUpdateToken} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async findByPicture(picture: string): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`SELECT * FROM "user" WHERE picture = $1`,
|
||||
[picture]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.ResourceNotFound,
|
||||
error: `User with picture=${picture} not found`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async isUsernameAvailable(username: string): Promise<boolean> {
|
||||
const { rowCount } = await client.query(
|
||||
`SELECT * FROM "user" WHERE username = $1`,
|
||||
[username]
|
||||
)
|
||||
return rowCount === 0
|
||||
}
|
||||
|
||||
async insert(data: InsertOptions): Promise<User> {
|
||||
const { rowCount, rows } = await client.query(
|
||||
`INSERT INTO "user" (
|
||||
id,
|
||||
full_name,
|
||||
username,
|
||||
email,
|
||||
password_hash,
|
||||
refresh_token_value,
|
||||
refresh_token_expiry,
|
||||
reset_password_token,
|
||||
email_confirmation_token,
|
||||
is_email_confirmed,
|
||||
picture,
|
||||
create_time
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
|
||||
[
|
||||
data.id,
|
||||
data.fullName,
|
||||
data.username,
|
||||
data.email,
|
||||
data.passwordHash,
|
||||
data.refreshTokenValue,
|
||||
data.refreshTokenExpiry,
|
||||
data.resetPasswordToken,
|
||||
data.emailConfirmationToken,
|
||||
data.isEmailConfirmed || false,
|
||||
data.picture,
|
||||
new Date().toISOString(),
|
||||
]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.InternalServerError,
|
||||
error: `Inserting user with id=${data.id} failed`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async update(data: UpdateOptions): Promise<User> {
|
||||
const entity = await this.findByID(data.id)
|
||||
if (!entity) {
|
||||
throw newError({
|
||||
code: ErrorCode.InternalServerError,
|
||||
error: `User with id=${data.id} not found`,
|
||||
})
|
||||
}
|
||||
Object.assign(entity, data)
|
||||
entity.updateTime = new Date().toISOString()
|
||||
const { rowCount, rows } = await client.query(
|
||||
`UPDATE "user"
|
||||
SET
|
||||
full_name = $1,
|
||||
username = $2,
|
||||
email = $3,
|
||||
password_hash = $4,
|
||||
refresh_token_value = $5,
|
||||
refresh_token_expiry = $6,
|
||||
reset_password_token = $7,
|
||||
email_confirmation_token = $8,
|
||||
is_email_confirmed = $9,
|
||||
email_update_token = $10,
|
||||
email_update_value = $11,
|
||||
picture = $12,
|
||||
update_time = $13
|
||||
WHERE id = $14
|
||||
RETURNING *`,
|
||||
[
|
||||
entity.fullName,
|
||||
entity.username,
|
||||
entity.email,
|
||||
entity.passwordHash,
|
||||
entity.refreshTokenValue,
|
||||
entity.refreshTokenExpiry,
|
||||
entity.resetPasswordToken,
|
||||
entity.emailConfirmationToken,
|
||||
entity.isEmailConfirmed,
|
||||
entity.emailUpdateToken,
|
||||
entity.emailUpdateValue,
|
||||
entity.picture,
|
||||
new Date().toISOString(),
|
||||
entity.id,
|
||||
]
|
||||
)
|
||||
if (rowCount < 1) {
|
||||
throw newError({
|
||||
code: ErrorCode.InternalServerError,
|
||||
error: `Inserting user with id=${data.id} failed`,
|
||||
})
|
||||
}
|
||||
return this.mapRow(rows[0])
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await client.query('DELETE FROM "user" WHERE id = $1', [id])
|
||||
}
|
||||
|
||||
private mapRow(row: any): User {
|
||||
return {
|
||||
id: row.id,
|
||||
fullName: row.full_name,
|
||||
username: row.username,
|
||||
email: row.email,
|
||||
passwordHash: row.password_hash,
|
||||
refreshTokenValue: row.refresh_token_value,
|
||||
refreshTokenExpiry: row.refresh_token_expiry,
|
||||
resetPasswordToken: row.reset_password_token,
|
||||
emailConfirmationToken: row.email_confirmation_token,
|
||||
isEmailConfirmed: row.is_email_confirmed,
|
||||
emailUpdateToken: row.email_update_token,
|
||||
emailUpdateValue: row.email_update_value,
|
||||
picture: row.picture,
|
||||
createTime: row.create_time,
|
||||
updateTime: row.update_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userRepo: UserRepo = new UserRepoImpl()
|
||||
|
||||
export default userRepo
|
183
Voltaserve/idp/src/user/router.ts
Normal file
183
Voltaserve/idp/src/user/router.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import fs from 'fs/promises'
|
||||
import os from 'os'
|
||||
import { NextFunction, Router, Response } from 'express'
|
||||
import { body, validationResult } from 'express-validator'
|
||||
import multer from 'multer'
|
||||
import passport from 'passport'
|
||||
import { parseValidationError } from '@/infra/error'
|
||||
import { PassportRequest } from '@/infra/passport-request'
|
||||
import {
|
||||
deleteUser,
|
||||
getUser,
|
||||
updateFullName,
|
||||
updatePicture,
|
||||
updatePassword,
|
||||
UserDeleteOptions,
|
||||
UserUpdateFullNameOptions,
|
||||
UserUpdatePasswordOptions,
|
||||
deletePicture,
|
||||
UserUpdateEmailRequestOptions,
|
||||
UserUpdateEmailConfirmationOptions,
|
||||
updateEmailRequest,
|
||||
updateEmailConfirmation,
|
||||
} from './service'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
res.json(await getUser(req.user.id))
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/update_full_name',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
body('fullName').isString().notEmpty().trim().escape().isLength({ max: 255 }),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const result = validationResult(req)
|
||||
if (!result.isEmpty()) {
|
||||
throw parseValidationError(result)
|
||||
}
|
||||
res.json(
|
||||
await updateFullName(req.user.id, req.body as UserUpdateFullNameOptions)
|
||||
)
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/update_email_request',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
body('email').isEmail().isLength({ max: 255 }),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const result = validationResult(req)
|
||||
if (!result.isEmpty()) {
|
||||
throw parseValidationError(result)
|
||||
}
|
||||
res.json(
|
||||
await updateEmailRequest(
|
||||
req.user.id,
|
||||
req.body as UserUpdateEmailRequestOptions
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/update_email_confirmation',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
body('token').isString().notEmpty().trim(),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const result = validationResult(req)
|
||||
if (!result.isEmpty()) {
|
||||
throw parseValidationError(result)
|
||||
}
|
||||
res.json(
|
||||
await updateEmailConfirmation(
|
||||
req.body as UserUpdateEmailConfirmationOptions
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/update_password',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
body('currentPassword').notEmpty(),
|
||||
body('newPassword').isStrongPassword(),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const result = validationResult(req)
|
||||
if (!result.isEmpty()) {
|
||||
throw parseValidationError(result)
|
||||
}
|
||||
res.json(
|
||||
await updatePassword(req.user.id, req.body as UserUpdatePasswordOptions)
|
||||
)
|
||||
} catch (err) {
|
||||
if (err === 'invalid_password') {
|
||||
res.status(400).json({ error: err })
|
||||
return
|
||||
} else {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/update_picture',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
multer({
|
||||
dest: os.tmpdir(),
|
||||
limits: { fileSize: 3000000, fields: 0, files: 1 },
|
||||
}).single('file'),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const user = await updatePicture(
|
||||
req.user.id,
|
||||
req.file.path,
|
||||
req.file.mimetype
|
||||
)
|
||||
await fs.rm(req.file.path)
|
||||
res.json(user)
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/delete_picture',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
res.json(await deletePicture(req.user.id))
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
body('password').isString().notEmpty(),
|
||||
async (req: PassportRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const result = validationResult(req)
|
||||
if (!result.isEmpty()) {
|
||||
throw parseValidationError(result)
|
||||
}
|
||||
await deleteUser(req.user.id, req.body as UserDeleteOptions)
|
||||
res.sendStatus(200)
|
||||
} catch (err) {
|
||||
if (err === 'invalid_password') {
|
||||
res.status(400).json({ error: err })
|
||||
return
|
||||
} else {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default router
|
191
Voltaserve/idp/src/user/service.ts
Normal file
191
Voltaserve/idp/src/user/service.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import fs from 'fs/promises'
|
||||
import { ErrorCode, newError } from '@/infra/error'
|
||||
import { hashPassword, verifyPassword } from '@/infra/password'
|
||||
import search, { USER_SEARCH_INDEX } from '@/infra/search'
|
||||
import userRepo, { User } from '@/user/repo'
|
||||
import { newHyphenlessUuid } from '@/infra/id'
|
||||
import { sendTemplateMail } from '@/infra/mail'
|
||||
import { getConfig } from '@/config/config'
|
||||
|
||||
export type UserDTO = {
|
||||
id: string
|
||||
fullName: string
|
||||
picture: string
|
||||
email: string
|
||||
username: string
|
||||
pendingEmail?: string
|
||||
}
|
||||
|
||||
export type UserUpdateEmailRequestOptions = {
|
||||
email: string
|
||||
}
|
||||
|
||||
export type UserUpdateEmailConfirmationOptions = {
|
||||
token: string
|
||||
}
|
||||
|
||||
export type UserUpdateFullNameOptions = {
|
||||
fullName: string
|
||||
}
|
||||
|
||||
export type UserUpdatePasswordOptions = {
|
||||
currentPassword: string
|
||||
newPassword: string
|
||||
}
|
||||
|
||||
export type UserDeleteOptions = {
|
||||
password: string
|
||||
}
|
||||
|
||||
export async function getUser(id: string): Promise<UserDTO> {
|
||||
return mapEntity(await userRepo.findByID(id))
|
||||
}
|
||||
|
||||
export async function getByPicture(picture: string): Promise<UserDTO> {
|
||||
return mapEntity(await userRepo.findByPicture(picture))
|
||||
}
|
||||
|
||||
export async function updateFullName(
|
||||
id: string,
|
||||
options: UserUpdateFullNameOptions
|
||||
): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
user = await userRepo.update({ id: user.id, fullName: options.fullName })
|
||||
await search.index(USER_SEARCH_INDEX).updateDocuments([
|
||||
{
|
||||
...user,
|
||||
fullName: user.fullName,
|
||||
},
|
||||
])
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function updateEmailRequest(
|
||||
id: string,
|
||||
options: UserUpdateEmailRequestOptions
|
||||
): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
if (options.email === user.email) {
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
})
|
||||
return mapEntity(user)
|
||||
} else {
|
||||
let usernameUnavailable = false
|
||||
try {
|
||||
await userRepo.findByUsername(options.email)
|
||||
usernameUnavailable = true
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
if (usernameUnavailable) {
|
||||
throw newError({ code: ErrorCode.UsernameUnavailable })
|
||||
}
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
emailUpdateToken: newHyphenlessUuid(),
|
||||
emailUpdateValue: options.email,
|
||||
})
|
||||
try {
|
||||
await sendTemplateMail('email-update', options.email, {
|
||||
'EMAIL': options.email,
|
||||
'UI_URL': getConfig().publicUIURL,
|
||||
'TOKEN': user.emailUpdateToken,
|
||||
})
|
||||
return mapEntity(user)
|
||||
} catch (error) {
|
||||
await userRepo.update({
|
||||
id,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
})
|
||||
throw newError({ code: ErrorCode.InternalServerError, error })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateEmailConfirmation(
|
||||
options: UserUpdateEmailConfirmationOptions
|
||||
) {
|
||||
let user = await userRepo.findByEmailUpdateToken(options.token)
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
email: user.emailUpdateValue,
|
||||
username: user.emailUpdateValue,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
})
|
||||
await search.index(USER_SEARCH_INDEX).updateDocuments([
|
||||
{
|
||||
...user,
|
||||
email: user.email,
|
||||
username: user.email,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
},
|
||||
])
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function updatePassword(
|
||||
id: string,
|
||||
options: UserUpdatePasswordOptions
|
||||
): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
if (verifyPassword(options.currentPassword, user.passwordHash)) {
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
passwordHash: hashPassword(options.newPassword),
|
||||
})
|
||||
return mapEntity(user)
|
||||
} else {
|
||||
throw newError({ code: ErrorCode.PasswordValidationFailed })
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePicture(
|
||||
id: string,
|
||||
path: string,
|
||||
contentType: string
|
||||
): Promise<UserDTO> {
|
||||
const picture = await fs.readFile(path, { encoding: 'base64' })
|
||||
const { id: userId } = await userRepo.findByID(id)
|
||||
const user = await userRepo.update({
|
||||
id: userId,
|
||||
picture: `data:${contentType};base64,${picture}`,
|
||||
})
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function deletePicture(id: string): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
user = await userRepo.update({ id: user.id, picture: null })
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function deleteUser(id: string, options: UserDeleteOptions) {
|
||||
const user = await userRepo.findByID(id)
|
||||
if (verifyPassword(options.password, user.passwordHash)) {
|
||||
await userRepo.delete(user.id)
|
||||
await search.index(USER_SEARCH_INDEX).deleteDocuments([user.id])
|
||||
} else {
|
||||
throw newError({ code: ErrorCode.InvalidPassword })
|
||||
}
|
||||
}
|
||||
|
||||
export function mapEntity(entity: User): UserDTO {
|
||||
const user: UserDTO = {
|
||||
id: entity.id,
|
||||
email: entity.email,
|
||||
username: entity.username,
|
||||
fullName: entity.fullName,
|
||||
picture: entity.picture,
|
||||
pendingEmail: entity.emailUpdateValue,
|
||||
}
|
||||
Object.keys(user).forEach(
|
||||
(index) => !user[index] && user[index] !== undefined && delete user[index]
|
||||
)
|
||||
return user
|
||||
}
|
Reference in New Issue
Block a user