Files
gridpilot.gg/packages/identity/domain/entities/UserAchievement.ts
2025-12-10 12:38:55 +01:00

83 lines
1.9 KiB
TypeScript

/**
* Domain Entity: UserAchievement
*
* Represents an achievement earned by a specific user.
*/
export interface UserAchievementProps {
id: string;
userId: string;
achievementId: string;
earnedAt: Date;
notifiedAt?: Date;
progress?: number; // For partial progress tracking (0-100)
}
export class UserAchievement {
readonly id: string;
readonly userId: string;
readonly achievementId: string;
readonly earnedAt: Date;
readonly notifiedAt?: Date;
readonly progress: number;
private constructor(props: UserAchievementProps) {
this.id = props.id;
this.userId = props.userId;
this.achievementId = props.achievementId;
this.earnedAt = props.earnedAt;
this.notifiedAt = props.notifiedAt;
this.progress = props.progress ?? 100;
}
static create(props: UserAchievementProps): UserAchievement {
if (!props.id || props.id.trim().length === 0) {
throw new Error('UserAchievement ID is required');
}
if (!props.userId || props.userId.trim().length === 0) {
throw new Error('UserAchievement userId is required');
}
if (!props.achievementId || props.achievementId.trim().length === 0) {
throw new Error('UserAchievement achievementId is required');
}
return new UserAchievement(props);
}
/**
* Mark achievement as notified to user
*/
markNotified(): UserAchievement {
return new UserAchievement({
...this,
notifiedAt: new Date(),
});
}
/**
* Update progress towards achievement
*/
updateProgress(progress: number): UserAchievement {
const clampedProgress = Math.max(0, Math.min(100, progress));
return new UserAchievement({
...this,
progress: clampedProgress,
});
}
/**
* Check if achievement is fully earned
*/
isComplete(): boolean {
return this.progress >= 100;
}
/**
* Check if user has been notified
*/
isNotified(): boolean {
return this.notifiedAt !== undefined;
}
}