/** * User Domain Entity * * Represents a user account in the dashboard system. * Encapsulates user authentication, profile data, and authorization logic. * * Domain invariants enforced: * - Email must be valid format * - Name must be non-empty * - Role-based permission checks * * @layer Domain */ /** * User Role Enum * * Defines the available roles for users in the system. * - ADMIN: Full system access and user management * - MANAGER: Can manage projects and users within their scope * - DEVELOPER: Can work on projects and tasks * - DESIGNER: Can work on design tasks and assets * - VIEWER: Read-only access to projects and data */ export enum UserRole { ADMIN = 'admin', MANAGER = 'manager', DEVELOPER = 'developer', DESIGNER = 'designer', VIEWER = 'viewer', } /** * User Status Enum * * Indicates the current status of a user account. * - ACTIVE: User can access the system * - INACTIVE: User account is deactivated * - PENDING: User invitation is pending acceptance */ export enum UserStatus { ACTIVE = 'active', INACTIVE = 'inactive', PENDING = 'pending', } /** * Permission types for authorization */ export enum Permission { // User management CREATE_USER = 'create_user', UPDATE_USER = 'update_user', DELETE_USER = 'delete_user', VIEW_USER = 'view_user', // Tenant management CREATE_TENANT = 'create_tenant', UPDATE_TENANT = 'update_tenant', DELETE_TENANT = 'delete_tenant', VIEW_TENANT = 'view_tenant', // Integration key management CREATE_INTEGRATION_KEY = 'create_integration_key', REVOKE_INTEGRATION_KEY = 'revoke_integration_key', VIEW_INTEGRATION_KEY = 'view_integration_key', // Company management UPDATE_COMPANY = 'update_company', VIEW_COMPANY = 'view_company', // Info Center VIEW_INFO_CENTER = 'view_info_center', UPDATE_INFO_CENTER = 'update_info_center', } interface UserProps { id: string; email: string; name: string; companyId: string; role: UserRole; status: UserStatus; avatar?: string | null; createdAt: Date; updatedAt: Date; } /** * User Entity * * Encapsulates user business logic and authorization rules. * Use factory methods (create, fromPersistence) to construct instances. */ export class User { private constructor(private readonly props: UserProps) { this.validate(); } /** * Creates a new User instance * * @param data - User creation data * @returns User domain entity * @throws Error if validation fails */ static create(data: { id: string; email: string; name: string; companyId: string; role: UserRole; status?: UserStatus; avatar?: string | null; }): User { const now = new Date(); return new User({ id: data.id, email: data.email.toLowerCase().trim(), name: data.name, companyId: data.companyId, role: data.role, status: data.status ?? UserStatus.PENDING, avatar: data.avatar, createdAt: now, updatedAt: now, }); } /** * Reconstructs User from persistence layer * * @param data - Persisted user data * @returns User domain entity */ static fromPersistence(data: { id: string; email: string; name: string; companyId: string; role: UserRole; status: UserStatus; avatar?: string | null; createdAt: string | Date; updatedAt: string | Date; }): User { return new User({ id: data.id, email: data.email, name: data.name, companyId: data.companyId, role: data.role, status: data.status, avatar: data.avatar, createdAt: typeof data.createdAt === 'string' ? new Date(data.createdAt) : data.createdAt, updatedAt: typeof data.updatedAt === 'string' ? new Date(data.updatedAt) : data.updatedAt, }); } /** * Validates domain invariants * * @throws Error if validation fails */ private validate(): void { if (!this.props.email || !this.isValidEmail(this.props.email)) { throw new Error('Invalid email format'); } if (!this.props.name || this.props.name.trim().length === 0) { throw new Error('User name cannot be empty'); } if (!this.props.companyId) { throw new Error('User must belong to a company'); } } /** * Validates email format * * @param email - Email to validate * @returns true if email is valid */ private isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // Getters get id(): string { return this.props.id; } get email(): string { return this.props.email; } get name(): string { return this.props.name; } get companyId(): string { return this.props.companyId; } get role(): UserRole { return this.props.role; } get status(): UserStatus { return this.props.status; } get avatar(): string | null | undefined { return this.props.avatar; } get createdAt(): Date { return this.props.createdAt; } get updatedAt(): Date { return this.props.updatedAt; } /** * Checks if user is active * * @returns true if user status is ACTIVE */ isActive(): boolean { return this.props.status === UserStatus.ACTIVE; } /** * Checks if user is pending * * @returns true if user status is PENDING */ isPending(): boolean { return this.props.status === UserStatus.PENDING; } /** * Checks if user is inactive * * @returns true if user status is INACTIVE */ isInactive(): boolean { return this.props.status === UserStatus.INACTIVE; } /** * Checks if user is an admin * * @returns true if user role is ADMIN */ isAdmin(): boolean { return this.props.role === UserRole.ADMIN; } /** * Checks if user is a manager * * @returns true if user role is MANAGER */ isManager(): boolean { return this.props.role === UserRole.MANAGER; } /** * Checks if user has a specific permission * * Role-based permission matrix: * - ADMIN: All permissions * - MANAGER: All except user management * - DEVELOPER/DESIGNER: View and update within scope * - VIEWER: Read-only access * * @param permission - Permission to check * @returns true if user has permission */ hasPermission(permission: Permission): boolean { // Admins have all permissions if (this.isAdmin()) { return true; } // Inactive/pending users have no permissions if (!this.isActive()) { return false; } const rolePermissions: Record = { [UserRole.ADMIN]: Object.values(Permission), [UserRole.MANAGER]: [ Permission.VIEW_USER, Permission.CREATE_TENANT, Permission.UPDATE_TENANT, Permission.DELETE_TENANT, Permission.VIEW_TENANT, Permission.CREATE_INTEGRATION_KEY, Permission.REVOKE_INTEGRATION_KEY, Permission.VIEW_INTEGRATION_KEY, Permission.VIEW_COMPANY, Permission.VIEW_INFO_CENTER, Permission.UPDATE_INFO_CENTER, ], [UserRole.DEVELOPER]: [ Permission.VIEW_USER, Permission.VIEW_TENANT, Permission.VIEW_INTEGRATION_KEY, Permission.VIEW_COMPANY, Permission.VIEW_INFO_CENTER, Permission.UPDATE_INFO_CENTER, ], [UserRole.DESIGNER]: [ Permission.VIEW_USER, Permission.VIEW_TENANT, Permission.VIEW_COMPANY, Permission.VIEW_INFO_CENTER, ], [UserRole.VIEWER]: [ Permission.VIEW_USER, Permission.VIEW_TENANT, Permission.VIEW_INTEGRATION_KEY, Permission.VIEW_COMPANY, Permission.VIEW_INFO_CENTER, ], }; return rolePermissions[this.props.role]?.includes(permission) ?? false; } /** * Gets initials for avatar placeholder * * @returns User initials (e.g., "JD" for "John Doe") */ getInitials(): string { const names = this.props.name.trim().split(' '); if (names.length === 1) { return names[0].substring(0, 2).toUpperCase(); } return (names[0][0] + names[names.length - 1][0]).toUpperCase(); } /** * Updates user properties * * @param updates - Properties to update * @returns New User instance with updates */ update(updates: { name?: string; email?: string; role?: UserRole; status?: UserStatus; avatar?: string | null; }): User { return new User({ ...this.props, ...updates, email: updates.email ? updates.email.toLowerCase().trim() : this.props.email, updatedAt: new Date(), }); } /** * Activates user account * * @returns New User instance with ACTIVE status */ activate(): User { return this.update({ status: UserStatus.ACTIVE }); } /** * Deactivates user account * * @returns New User instance with INACTIVE status */ deactivate(): User { return this.update({ status: UserStatus.INACTIVE }); } /** * Converts entity to persistence format * * @returns Plain object for storage */ toPersistence(): { id: string; email: string; name: string; companyId: string; role: UserRole; status: UserStatus; avatar?: string | null; createdAt: string; updatedAt: string; } { return { id: this.props.id, email: this.props.email, name: this.props.name, companyId: this.props.companyId, role: this.props.role, status: this.props.status, avatar: this.props.avatar, createdAt: this.props.createdAt.toISOString(), updatedAt: this.props.updatedAt.toISOString(), }; } }