49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { EntityManager, Repository } from 'typeorm';
|
|
import { UserEntity, UserRole } from '../../database/entities/user.entity';
|
|
import { ApiError } from '../../common/errors/api-error';
|
|
import { ERROR_CODES } from '../../common/errors/error-codes';
|
|
|
|
type UserRepoLike = Repository<UserEntity> | EntityManager;
|
|
|
|
@Injectable()
|
|
export class UsersService {
|
|
constructor(@InjectRepository(UserEntity) private readonly repo: Repository<UserEntity>) {}
|
|
|
|
countAdmins(manager?: EntityManager): Promise<number> {
|
|
return this.getRepo(manager).count({ where: { role: 'admin' } });
|
|
}
|
|
|
|
findById(id: string, manager?: EntityManager): Promise<UserEntity | null> {
|
|
return this.getRepo(manager).findOne({ where: { id } });
|
|
}
|
|
|
|
listAll(): Promise<UserEntity[]> {
|
|
return this.repo.find({ order: { createdAt: 'ASC' } });
|
|
}
|
|
|
|
/**
|
|
* Verify, inside an open transaction, that demoting/deleting the given user
|
|
* would not leave the system with zero admins. Throws ApiError(LAST_ADMIN)
|
|
* if so. Must be invoked from a `dataSource.transaction(...)` callback.
|
|
*/
|
|
async enforceLastAdminInvariant(manager: EntityManager, targetId: string, nextRole?: UserRole): Promise<void> {
|
|
const repo = manager.getRepository(UserEntity);
|
|
const target = await repo.findOne({ where: { id: targetId } });
|
|
if (!target) throw ApiError.notFound('User not found');
|
|
|
|
const wouldDemoteOrDelete =
|
|
target.role === 'admin' && (nextRole === undefined || nextRole !== 'admin');
|
|
if (!wouldDemoteOrDelete) return;
|
|
|
|
const adminCount = await repo.count({ where: { role: 'admin' } });
|
|
if (adminCount <= 1) {
|
|
throw ApiError.conflict(ERROR_CODES.LAST_ADMIN, 'Cannot delete or demote the last admin');
|
|
}
|
|
}
|
|
|
|
private getRepo(manager?: EntityManager): Repository<UserEntity> {
|
|
return manager ? manager.getRepository(UserEntity) : this.repo;
|
|
}
|
|
} |