xpeditis2.0/apps/backend/src/domain/entities/user.entity.ts
David 7dadd951bb
All checks were successful
CI/CD Pipeline / Backend - Build, Test & Push (push) Successful in 16m18s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Successful in 30m58s
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Deployment Summary (push) Successful in 2s
CI/CD Pipeline / Discord Notification (Failure) (push) Has been skipped
CI/CD Pipeline / Discord Notification (Success) (push) Successful in 1s
fix portainer deploy
2025-11-19 15:17:53 +01:00

254 lines
5.5 KiB
TypeScript

/**
* User Entity
*
* Represents a user account in the Xpeditis platform.
*
* Business Rules:
* - Email must be valid and unique
* - Password must meet complexity requirements (enforced at application layer)
* - Users belong to an organization
* - Role-based access control (Admin, Manager, User, Viewer)
*/
export enum UserRole {
ADMIN = 'ADMIN', // Full system access
MANAGER = 'MANAGER', // Manage bookings and users within organization
USER = 'USER', // Create and view bookings
VIEWER = 'VIEWER', // Read-only access
}
export interface UserProps {
id: string;
organizationId: string;
email: string;
passwordHash: string;
role: UserRole;
firstName: string;
lastName: string;
phoneNumber?: string;
totpSecret?: string; // For 2FA
isEmailVerified: boolean;
isActive: boolean;
lastLoginAt?: Date;
createdAt: Date;
updatedAt: Date;
}
export class User {
private readonly props: UserProps;
private constructor(props: UserProps) {
this.props = props;
}
/**
* Factory method to create a new User
*/
static create(
props: Omit<
UserProps,
'createdAt' | 'updatedAt' | 'isEmailVerified' | 'isActive' | 'lastLoginAt'
>
): User {
const now = new Date();
// Validate email format (basic validation)
if (!User.isValidEmail(props.email)) {
throw new Error('Invalid email format.');
}
return new User({
...props,
isEmailVerified: false,
isActive: true,
createdAt: now,
updatedAt: now,
});
}
/**
* Factory method to reconstitute from persistence
*/
static fromPersistence(props: UserProps): User {
return new User(props);
}
/**
* Validate email format
*/
private static isValidEmail(email: string): boolean {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailPattern.test(email);
}
// Getters
get id(): string {
return this.props.id;
}
get organizationId(): string {
return this.props.organizationId;
}
get email(): string {
return this.props.email;
}
get passwordHash(): string {
return this.props.passwordHash;
}
get role(): UserRole {
return this.props.role;
}
get firstName(): string {
return this.props.firstName;
}
get lastName(): string {
return this.props.lastName;
}
get fullName(): string {
return `${this.props.firstName} ${this.props.lastName}`;
}
get phoneNumber(): string | undefined {
return this.props.phoneNumber;
}
get totpSecret(): string | undefined {
return this.props.totpSecret;
}
get isEmailVerified(): boolean {
return this.props.isEmailVerified;
}
get isActive(): boolean {
return this.props.isActive;
}
get lastLoginAt(): Date | undefined {
return this.props.lastLoginAt;
}
get createdAt(): Date {
return this.props.createdAt;
}
get updatedAt(): Date {
return this.props.updatedAt;
}
// Business methods
has2FAEnabled(): boolean {
return !!this.props.totpSecret;
}
isAdmin(): boolean {
return this.props.role === UserRole.ADMIN;
}
isManager(): boolean {
return this.props.role === UserRole.MANAGER;
}
isRegularUser(): boolean {
return this.props.role === UserRole.USER;
}
isViewer(): boolean {
return this.props.role === UserRole.VIEWER;
}
canManageUsers(): boolean {
return this.props.role === UserRole.ADMIN || this.props.role === UserRole.MANAGER;
}
canCreateBookings(): boolean {
return (
this.props.role === UserRole.ADMIN ||
this.props.role === UserRole.MANAGER ||
this.props.role === UserRole.USER
);
}
updatePassword(newPasswordHash: string): void {
this.props.passwordHash = newPasswordHash;
this.props.updatedAt = new Date();
}
updateRole(newRole: UserRole): void {
this.props.role = newRole;
this.props.updatedAt = new Date();
}
updateFirstName(firstName: string): void {
if (!firstName || firstName.trim().length === 0) {
throw new Error('First name cannot be empty.');
}
this.props.firstName = firstName.trim();
this.props.updatedAt = new Date();
}
updateLastName(lastName: string): void {
if (!lastName || lastName.trim().length === 0) {
throw new Error('Last name cannot be empty.');
}
this.props.lastName = lastName.trim();
this.props.updatedAt = new Date();
}
updateProfile(firstName: string, lastName: string, phoneNumber?: string): void {
if (!firstName || firstName.trim().length === 0) {
throw new Error('First name cannot be empty.');
}
if (!lastName || lastName.trim().length === 0) {
throw new Error('Last name cannot be empty.');
}
this.props.firstName = firstName.trim();
this.props.lastName = lastName.trim();
this.props.phoneNumber = phoneNumber;
this.props.updatedAt = new Date();
}
verifyEmail(): void {
this.props.isEmailVerified = true;
this.props.updatedAt = new Date();
}
enable2FA(totpSecret: string): void {
this.props.totpSecret = totpSecret;
this.props.updatedAt = new Date();
}
disable2FA(): void {
this.props.totpSecret = undefined;
this.props.updatedAt = new Date();
}
recordLogin(): void {
this.props.lastLoginAt = new Date();
}
deactivate(): void {
this.props.isActive = false;
this.props.updatedAt = new Date();
}
activate(): void {
this.props.isActive = true;
this.props.updatedAt = new Date();
}
/**
* Convert to plain object for persistence
*/
toObject(): UserProps {
return { ...this.props };
}
}