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, client = knex) { return update(id, json, client) } function update(id: number, json: Partial, 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, } }