128 lines
3.3 KiB
TypeScript
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,
|
|
}
|
|
}
|