89 lines
2.5 KiB
TypeScript
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;
|
|
});
|
|
}
|
|
}
|