Files
gridpilot.gg/core/identity/domain/entities/UserAchievement.ts
2026-01-16 16:46:57 +01:00

93 lines
2.3 KiB
TypeScript

/**
* Domain Entity: UserAchievement
*
* Represents an achievement earned by a specific user.
*/
import { Entity } from '@core/shared/domain/Entity';
export interface UserAchievementProps {
id: string;
userId: string;
achievementId: string;
earnedAt: Date;
notifiedAt?: Date;
progress?: number; // For partial progress tracking (0-100)
}
export class UserAchievement extends Entity<string> {
readonly userId: string;
readonly achievementId: string;
readonly earnedAt: Date;
readonly notifiedAt: Date | undefined;
readonly progress: number;
private constructor(props: UserAchievementProps) {
super(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({
id: this.id,
userId: this.userId,
achievementId: this.achievementId,
earnedAt: this.earnedAt,
notifiedAt: new Date(),
progress: this.progress,
});
}
/**
* Update progress towards achievement
*/
updateProgress(progress: number): UserAchievement {
const clampedProgress = Math.max(0, Math.min(100, progress));
return new UserAchievement({
id: this.id,
userId: this.userId,
achievementId: this.achievementId,
earnedAt: this.earnedAt,
...(this.notifiedAt ? { notifiedAt: this.notifiedAt } : {}),
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;
}
}