brf/server/services/users/queries.ts
2025-12-18 07:31:37 +01:00

128 lines
3.3 KiB
TypeScript

import EventEmitter from 'events'
import type { Knex } from 'knex'
import _ from 'lodash'
import type { User, NewUser } from './types.ts'
import RestQueriesFactory from '../../lib/knex_rest_queries.ts'
export const columns = [
'bannedAt',
'bannedById',
'blockedAt',
'blockedById',
'createdAt',
'email',
'emailVerifiedAt',
'id',
'lastActivityAt',
'lastLoginAt',
'lastLoginAttemptAt',
'loginAttempts',
'password',
]
export const Selects = (knex: Knex) => ({
roles: knex
.select(knex.raw('json_agg(roles)'))
.from(
knex
.select('id', 'name')
.from('role')
.innerJoin('users_roles', 'role.id', 'users_roles.roleId')
.where('users_roles.userId', knex.ref('user.id'))
.as('roles'),
),
})
interface Options {
emitter: EventEmitter
knex: Knex
}
export default ({ emitter, knex }: Options) => {
const userQueries = RestQueriesFactory({
knex,
emitter,
omit: ['replace'],
table: 'user',
columns,
selects: Selects(knex),
})
async function create(json: NewUser, client: Knex | Knex.Transaction = knex) {
const trx = (client.isTransaction ? client : await client.transaction()) as Knex.Transaction
const user = await userQueries.create(json, trx)
if (json.roles) {
await trx('users_roles').insert(json.roles.map((role) => ({ userId: user.id, roleId: role })))
}
if (trx !== client) {
await trx.commit()
}
return userQueries.findById(user.id, trx)
}
function onActivity(id: number, client = knex) {
return update(id, { lastActivityAt: 'now()' }, client)
}
function onLogin(id: number, client = knex) {
return update(id, { lastLoginAt: 'now()', loginAttempts: 0, lastLoginAttemptAt: null }, client)
}
function onLoginAttempt(id: number, client = knex) {
return client('user')
.update({ last_login_attempt_at: knex.fn.now(), login_attempts: knex.raw('login_attempts + 1') })
.where('id', id)
}
function replace(id: number, json: Partial<User>, client = knex) {
return update(id, json, client)
}
function update(id: number, json: Partial<User>, client = knex) {
return client.transaction((trx) =>
userQueries
.update(id, _.omit(json, ['roles']), trx)
.then(() => {
const roles = json.roles
if (roles && roles.length) {
const roleIds = roles.map((role) => role.id)
return trx.raw(
`WITH deleted_rows AS (
DELETE FROM users_roles
WHERE "userId" = :userId AND "roleId" NOT IN
(SELECT "roleIds" FROM unnest(:roleIds::int[]) AS "roleIds")
RETURNING "roleId"
), inserted_rows AS (
INSERT INTO users_roles("userId", "roleId")
SELECT :userId, "roleIds" FROM unnest(:roleIds::int[]) AS "roleIds" WHERE NOT EXISTS
(SELECT 1 FROM users_roles WHERE user_id = :userId AND "roleId" = "roleIds")
RETURNING "roleId", "userId"
)
SELECT "roleId", "userId" FROM inserted_rows;`,
[id, roleIds],
)
}
})
.then(() => userQueries.findById(id)),
)
}
return {
...userQueries,
create,
onActivity,
onLogin,
onLoginAttempt,
replace,
update,
}
}