93 lines
2.3 KiB
TypeScript
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;
|
|
}
|
|
} |