import { AppError } from '../common/errors/app-error'; import { Injectable } from '@nestjs/common'; import { DatabaseService } from '../database/database.service'; import { AccessStore } from './access.store'; import { PasswordService } from './password.service'; import { recordAudit } from './audit'; import type { Principal } from './identity.types'; import type { CreateUserInput, PageInput } from './identity.schemas'; const publicUser = { id: true, email: true, name: true, status: true, isOwner: true, createdAt: true, } as const; @Injectable() export class UserStore { constructor( private readonly db: DatabaseService, private readonly access: AccessStore, private readonly passwords: PasswordService, ) {} list(actor: Principal, page: PageInput) { return this.db.user.findMany({ where: { organizationId: actor.organizationId }, select: publicUser, orderBy: [{ createdAt: 'desc' }, { id: 'desc' }], take: page.limit, skip: page.offset, }); } async create(actor: Principal, input: CreateUserInput) { const passwordHash = await this.passwords.hash(input.password); return this.access.mutate(actor, 'users.create', async (tx) => { const user = await tx.user.create({ data: { organizationId: actor.organizationId, email: input.email, name: input.name, passwordHash, }, select: publicUser, }); await recordAudit( tx, actor.organizationId, actor.userId, 'user.created', user.id, ); return user; }); } async setStatus( actor: Principal, id: string, status: 'ACTIVE' | 'SUSPENDED', ) { return this.access.mutate(actor, 'users.approve', async (tx) => { const user = await tx.user.findFirst({ where: { id, organizationId: actor.organizationId }, }); if (!user) throw new AppError('USER_NOT_FOUND'); if (user.isOwner || user.id === actor.userId) throw new AppError('USER_STATUS_DENIED'); const updated = await tx.user.update({ where: { id }, data: { status }, select: publicUser, }); if (status === 'SUSPENDED') { await tx.session.deleteMany({ where: { userId: id } }); await tx.recoveryToken.deleteMany({ where: { userId: id } }); } await recordAudit( tx, actor.organizationId, actor.userId, `user.${status.toLowerCase()}`, id, ); return updated; }); } }