import { Injectable } from '@nestjs/common'; import { InjectKysely } from 'nestjs-kysely'; import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types'; import { Users } from '@docmost/db/types/db'; import { hashPassword } from '../../../common/helpers'; import { dbOrTx } from '@docmost/db/utils'; import { InsertableUser, UpdatableUser, User, } from '@docmost/db/types/entity.types'; import { PaginationOptions } from '../../pagination/pagination-options'; import { executeWithPagination } from '@docmost/db/pagination/pagination'; import { sql } from 'kysely'; @Injectable() export class UserRepo { constructor(@InjectKysely() private readonly db: KyselyDB) {} public baseFields: Array = [ 'id', 'email', 'name', 'emailVerifiedAt', 'avatarUrl', 'role', 'workspaceId', 'locale', 'timezone', 'settings', 'lastLoginAt', 'deactivatedAt', 'createdAt', 'updatedAt', 'deletedAt', ]; async findById( userId: string, workspaceId: string, opts?: { includePassword?: boolean; trx?: KyselyTransaction; }, ): Promise { const db = dbOrTx(this.db, opts?.trx); return db .selectFrom('users') .select(this.baseFields) .$if(opts?.includePassword, (qb) => qb.select('password')) .where('id', '=', userId) .where('workspaceId', '=', workspaceId) .executeTakeFirst(); } async findByEmail( email: string, workspaceId: string, opts?: { includePassword?: boolean; trx?: KyselyTransaction; }, ): Promise { const db = dbOrTx(this.db, opts?.trx); return db .selectFrom('users') .select(this.baseFields) .$if(opts?.includePassword, (qb) => qb.select('password')) .where(sql`LOWER(email)`, '=', sql`LOWER(${email})`) .where('workspaceId', '=', workspaceId) .executeTakeFirst(); } async updateUser( updatableUser: UpdatableUser, userId: string, workspaceId: string, trx?: KyselyTransaction, ) { const db = dbOrTx(this.db, trx); return await db .updateTable('users') .set({ ...updatableUser, updatedAt: new Date() }) .where('id', '=', userId) .where('workspaceId', '=', workspaceId) .execute(); } async updateLastLogin(userId: string, workspaceId: string) { return await this.db .updateTable('users') .set({ lastLoginAt: new Date(), }) .where('id', '=', userId) .where('workspaceId', '=', workspaceId) .execute(); } async insertUser( insertableUser: InsertableUser, trx?: KyselyTransaction, ): Promise { const user: InsertableUser = { name: insertableUser.name || insertableUser.email.split('@')[0].toLowerCase(), email: insertableUser.email.toLowerCase(), password: await hashPassword(insertableUser.password), locale: 'en-US', role: insertableUser?.role, lastLoginAt: new Date(), }; const db = dbOrTx(this.db, trx); return db .insertInto('users') .values({ ...insertableUser, ...user }) .returning(this.baseFields) .executeTakeFirst(); } async roleCountByWorkspaceId( role: string, workspaceId: string, ): Promise { const { count } = await this.db .selectFrom('users') .select((eb) => eb.fn.count('role').as('count')) .where('role', '=', role) .where('workspaceId', '=', workspaceId) .executeTakeFirst(); return count as number; } async getUsersPaginated(workspaceId: string, pagination: PaginationOptions) { let query = this.db .selectFrom('users') .select(this.baseFields) .where('workspaceId', '=', workspaceId) .where('deletedAt', 'is', null) .orderBy('createdAt', 'asc'); if (pagination.query) { query = query.where((eb) => eb('users.name', 'ilike', `%${pagination.query}%`).or( 'users.email', 'ilike', `%${pagination.query}%`, ), ); } const result = executeWithPagination(query, { page: pagination.page, perPage: pagination.limit, }); return result; } async updatePreference( userId: string, prefKey: string, prefValue: string | boolean, ) { return await this.db .updateTable('users') .set({ settings: sql`COALESCE(settings, '{}'::jsonb) || jsonb_build_object('preferences', COALESCE(settings->'preferences', '{}'::jsonb) || jsonb_build_object('${sql.raw(prefKey)}', ${sql.lit(prefValue)}))`, updatedAt: new Date(), }) .where('id', '=', userId) .returning(this.baseFields) .executeTakeFirst(); } }