159 lines
3.2 KiB
TypeScript
159 lines
3.2 KiB
TypeScript
/**
|
|
* InvitationToken Entity
|
|
*
|
|
* Represents an invitation token for user registration.
|
|
*
|
|
* Business Rules:
|
|
* - Tokens expire after 7 days by default
|
|
* - Token can only be used once
|
|
* - Email must be unique per active (non-used) invitation
|
|
*/
|
|
|
|
import { UserRole } from './user.entity';
|
|
|
|
export interface InvitationTokenProps {
|
|
id: string;
|
|
token: string; // Unique random token (e.g., UUID)
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
role: UserRole;
|
|
organizationId: string;
|
|
invitedById: string; // User ID who created the invitation
|
|
expiresAt: Date;
|
|
usedAt?: Date;
|
|
isUsed: boolean;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export class InvitationToken {
|
|
private readonly props: InvitationTokenProps;
|
|
|
|
private constructor(props: InvitationTokenProps) {
|
|
this.props = props;
|
|
}
|
|
|
|
/**
|
|
* Factory method to create a new InvitationToken
|
|
*/
|
|
static create(
|
|
props: Omit<InvitationTokenProps, 'createdAt' | 'isUsed' | 'usedAt'>
|
|
): InvitationToken {
|
|
const now = new Date();
|
|
|
|
// Validate token
|
|
if (!props.token || props.token.trim().length === 0) {
|
|
throw new Error('Invitation token cannot be empty.');
|
|
}
|
|
|
|
// Validate email format
|
|
if (!InvitationToken.isValidEmail(props.email)) {
|
|
throw new Error('Invalid email format.');
|
|
}
|
|
|
|
// Validate expiration date
|
|
if (props.expiresAt <= now) {
|
|
throw new Error('Expiration date must be in the future.');
|
|
}
|
|
|
|
return new InvitationToken({
|
|
...props,
|
|
isUsed: false,
|
|
createdAt: now,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Factory method to reconstitute from persistence
|
|
*/
|
|
static fromPersistence(props: InvitationTokenProps): InvitationToken {
|
|
return new InvitationToken(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 token(): string {
|
|
return this.props.token;
|
|
}
|
|
|
|
get email(): string {
|
|
return this.props.email;
|
|
}
|
|
|
|
get firstName(): string {
|
|
return this.props.firstName;
|
|
}
|
|
|
|
get lastName(): string {
|
|
return this.props.lastName;
|
|
}
|
|
|
|
get role(): UserRole {
|
|
return this.props.role;
|
|
}
|
|
|
|
get organizationId(): string {
|
|
return this.props.organizationId;
|
|
}
|
|
|
|
get invitedById(): string {
|
|
return this.props.invitedById;
|
|
}
|
|
|
|
get expiresAt(): Date {
|
|
return this.props.expiresAt;
|
|
}
|
|
|
|
get usedAt(): Date | undefined {
|
|
return this.props.usedAt;
|
|
}
|
|
|
|
get isUsed(): boolean {
|
|
return this.props.isUsed;
|
|
}
|
|
|
|
get createdAt(): Date {
|
|
return this.props.createdAt;
|
|
}
|
|
|
|
// Business methods
|
|
isExpired(): boolean {
|
|
return new Date() > this.props.expiresAt;
|
|
}
|
|
|
|
isValid(): boolean {
|
|
return !this.props.isUsed && !this.isExpired();
|
|
}
|
|
|
|
markAsUsed(): void {
|
|
if (this.props.isUsed) {
|
|
throw new Error('Invitation token has already been used.');
|
|
}
|
|
|
|
if (this.isExpired()) {
|
|
throw new Error('Invitation token has expired.');
|
|
}
|
|
|
|
this.props.isUsed = true;
|
|
this.props.usedAt = new Date();
|
|
}
|
|
|
|
/**
|
|
* Convert to plain object for persistence
|
|
*/
|
|
toObject(): InvitationTokenProps {
|
|
return { ...this.props };
|
|
}
|
|
}
|