Initial LisgloSIPS V2 implementation
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { PASSWORD_ALGO_ARGON2ID, hashPasswordArgon2id } from '@lisglosips/auth';
|
||||
import { USERS_REPOSITORY, type UpdateUserInput, type UserSummary, type UsersRepository } from './users.repository.js';
|
||||
|
||||
interface CreateUserDto {
|
||||
username?: unknown;
|
||||
displayName?: unknown;
|
||||
phone?: unknown;
|
||||
email?: unknown;
|
||||
password?: unknown;
|
||||
requirePasswordChange?: unknown;
|
||||
roleIds?: unknown;
|
||||
}
|
||||
|
||||
interface UpdateUserDto {
|
||||
displayName?: unknown;
|
||||
phone?: unknown;
|
||||
email?: unknown;
|
||||
status?: unknown;
|
||||
roleIds?: unknown;
|
||||
}
|
||||
|
||||
interface ResetPasswordDto {
|
||||
password?: unknown;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(@Inject(USERS_REPOSITORY) private readonly users: UsersRepository) {}
|
||||
|
||||
list(): Promise<UserSummary[]> {
|
||||
return this.users.list();
|
||||
}
|
||||
|
||||
async create(body: CreateUserDto, actorId?: string): Promise<UserSummary> {
|
||||
const username = this.requiredString(body.username, 'username').trim().toLowerCase();
|
||||
const displayName = this.requiredString(body.displayName, 'displayName').trim();
|
||||
const password = this.password(body.password);
|
||||
const roleIds = this.roleIds(body.roleIds);
|
||||
|
||||
return this.users.create({
|
||||
username,
|
||||
displayName,
|
||||
phone: this.optionalString(body.phone),
|
||||
email: this.optionalString(body.email),
|
||||
passwordHash: await hashPasswordArgon2id(password),
|
||||
passwordAlgo: PASSWORD_ALGO_ARGON2ID,
|
||||
requirePasswordChange: body.requirePasswordChange === undefined ? true : body.requirePasswordChange === true,
|
||||
roleIds,
|
||||
actorId
|
||||
});
|
||||
}
|
||||
|
||||
update(userId: string, body: UpdateUserDto, actorId?: string): Promise<UserSummary> {
|
||||
const input: UpdateUserInput = {
|
||||
displayName: body.displayName === undefined ? undefined : this.requiredString(body.displayName, 'displayName').trim(),
|
||||
phone: body.phone === undefined ? undefined : this.nullableString(body.phone),
|
||||
email: body.email === undefined ? undefined : this.nullableString(body.email),
|
||||
status: body.status === undefined ? undefined : this.status(body.status),
|
||||
roleIds: body.roleIds === undefined ? undefined : this.roleIds(body.roleIds),
|
||||
actorId
|
||||
};
|
||||
|
||||
return this.users.update(userId, input);
|
||||
}
|
||||
|
||||
async resetPassword(userId: string, body: ResetPasswordDto, actorId?: string): Promise<UserSummary> {
|
||||
return this.users.resetPassword(userId, await hashPasswordArgon2id(this.password(body.password)), PASSWORD_ALGO_ARGON2ID, actorId);
|
||||
}
|
||||
|
||||
private requiredString(value: unknown, field: string): string {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {
|
||||
throw new BadRequestException({ code: 'VALIDATION_ERROR', message: `${field} is required.` });
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private optionalString(value: unknown): string | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.requiredString(value, 'value').trim();
|
||||
}
|
||||
|
||||
private nullableString(value: unknown): string | null {
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.requiredString(value, 'value').trim();
|
||||
}
|
||||
|
||||
private password(value: unknown): string {
|
||||
const password = this.requiredString(value, 'password');
|
||||
if (password.length < 12) {
|
||||
throw new BadRequestException({ code: 'PASSWORD_TOO_SHORT', message: 'Password must be at least 12 characters.' });
|
||||
}
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
private roleIds(value: unknown): string[] {
|
||||
if (!Array.isArray(value) || value.length === 0 || !value.every((roleId) => typeof roleId === 'string' && roleId.length > 0)) {
|
||||
throw new BadRequestException({ code: 'ROLE_IDS_REQUIRED', message: 'At least one role is required.' });
|
||||
}
|
||||
|
||||
return [...new Set(value as string[])];
|
||||
}
|
||||
|
||||
private status(value: unknown): 'ENABLED' | 'DISABLED' {
|
||||
if (value !== 'ENABLED' && value !== 'DISABLED') {
|
||||
throw new BadRequestException({ code: 'STATUS_INVALID', message: 'Status is invalid.' });
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user