manicanldes-backend/src/identity/user.store.ts

89 lines
2.5 KiB
TypeScript

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;
});
}
}