291 lines
11 KiB
TypeScript
291 lines
11 KiB
TypeScript
/**
|
|
* Infrastructure Adapter: InMemoryAchievementRepository
|
|
*
|
|
* In-memory implementation of IAchievementRepository
|
|
*/
|
|
|
|
import { Logger } from '@core/shared/application';
|
|
import {
|
|
Achievement,
|
|
AchievementCategory,
|
|
DRIVER_ACHIEVEMENTS,
|
|
STEWARD_ACHIEVEMENTS,
|
|
ADMIN_ACHIEVEMENTS,
|
|
COMMUNITY_ACHIEVEMENTS,
|
|
} from '../../domain/entities/Achievement';
|
|
import { UserAchievement } from '../../domain/entities/UserAchievement';
|
|
import type { IAchievementRepository } from '../../domain/repositories/IAchievementRepository';
|
|
|
|
export class InMemoryAchievementRepository implements IAchievementRepository {
|
|
private achievements: Map<string, Achievement> = new Map();
|
|
private userAchievements: Map<string, UserAchievement> = new Map();
|
|
private readonly logger: Logger;
|
|
|
|
constructor(logger: Logger) {
|
|
this.logger = logger;
|
|
this.logger.info('InMemoryAchievementRepository initialized.');
|
|
// Seed with predefined achievements
|
|
this.seedAchievements();
|
|
}
|
|
|
|
private seedAchievements(): void {
|
|
this.logger.debug('Seeding predefined achievements.');
|
|
const allAchievements = [
|
|
...DRIVER_ACHIEVEMENTS,
|
|
...STEWARD_ACHIEVEMENTS,
|
|
...ADMIN_ACHIEVEMENTS,
|
|
...COMMUNITY_ACHIEVEMENTS,
|
|
];
|
|
|
|
for (const props of allAchievements) {
|
|
const achievement = Achievement.create(props);
|
|
this.achievements.set(achievement.id, achievement);
|
|
this.logger.debug(`Seeded achievement: ${achievement.id} (${achievement.name}).`);
|
|
}
|
|
this.logger.info(`Seeded ${allAchievements.length} predefined achievements.`);
|
|
}
|
|
|
|
// Achievement operations
|
|
async findAchievementById(id: string): Promise<Achievement | null> {
|
|
this.logger.debug(`Finding achievement by id: ${id}`);
|
|
try {
|
|
const achievement = this.achievements.get(id) ?? null;
|
|
if (achievement) {
|
|
this.logger.info(`Found achievement: ${id}.`);
|
|
} else {
|
|
this.logger.warn(`Achievement with id ${id} not found.`);
|
|
}
|
|
return achievement;
|
|
} catch (error) {
|
|
this.logger.error(`Error finding achievement by id ${id}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findAllAchievements(): Promise<Achievement[]> {
|
|
this.logger.debug('Finding all achievements.');
|
|
try {
|
|
const achievements = Array.from(this.achievements.values());
|
|
this.logger.info(`Found ${achievements.length} achievements.`);
|
|
return achievements;
|
|
} catch (error) {
|
|
this.logger.error('Error finding all achievements:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findAchievementsByCategory(category: AchievementCategory): Promise<Achievement[]> {
|
|
this.logger.debug(`Finding achievements by category: ${category}`);
|
|
try {
|
|
const achievements = Array.from(this.achievements.values())
|
|
.filter(a => a.category === category);
|
|
this.logger.info(`Found ${achievements.length} achievements for category: ${category}.`);
|
|
return achievements;
|
|
} catch (error) {
|
|
this.logger.error(`Error finding achievements by category ${category}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async createAchievement(achievement: Achievement): Promise<Achievement> {
|
|
this.logger.debug(`Creating achievement: ${achievement.id}`);
|
|
try {
|
|
if (this.achievements.has(achievement.id)) {
|
|
this.logger.warn(`Achievement with ID ${achievement.id} already exists.`);
|
|
throw new Error('Achievement with this ID already exists');
|
|
}
|
|
this.achievements.set(achievement.id, achievement);
|
|
this.logger.info(`Achievement ${achievement.id} created successfully.`);
|
|
return achievement;
|
|
} catch (error) {
|
|
this.logger.error(`Error creating achievement ${achievement.id}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// UserAchievement operations
|
|
async findUserAchievementById(id: string): Promise<UserAchievement | null> {
|
|
this.logger.debug(`Finding user achievement by id: ${id}`);
|
|
try {
|
|
const userAchievement = this.userAchievements.get(id) ?? null;
|
|
if (userAchievement) {
|
|
this.logger.info(`Found user achievement: ${id}.`);
|
|
} else {
|
|
this.logger.warn(`User achievement with id ${id} not found.`);
|
|
}
|
|
return userAchievement;
|
|
} catch (error) {
|
|
this.logger.error(`Error finding user achievement by id ${id}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findUserAchievementsByUserId(userId: string): Promise<UserAchievement[]> {
|
|
this.logger.debug(`Finding user achievements by user id: ${userId}`);
|
|
try {
|
|
const userAchievements = Array.from(this.userAchievements.values())
|
|
.filter(ua => ua.userId === userId);
|
|
this.logger.info(`Found ${userAchievements.length} user achievements for user id: ${userId}.`);
|
|
return userAchievements;
|
|
} catch (error) {
|
|
this.logger.error(`Error finding user achievements by user id ${userId}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findUserAchievementByUserAndAchievement(
|
|
userId: string,
|
|
achievementId: string
|
|
): Promise<UserAchievement | null> {
|
|
this.logger.debug(`Finding user achievement for user: ${userId}, achievement: ${achievementId}`);
|
|
try {
|
|
for (const ua of this.userAchievements.values()) {
|
|
if (ua.userId === userId && ua.achievementId === achievementId) {
|
|
this.logger.info(`Found user achievement for user: ${userId}, achievement: ${achievementId}.`);
|
|
return ua;
|
|
}
|
|
}
|
|
this.logger.warn(`User achievement for user ${userId}, achievement ${achievementId} not found.`);
|
|
return null;
|
|
} catch (error) {
|
|
this.logger.error(`Error finding user achievement for user ${userId}, achievement ${achievementId}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async hasUserEarnedAchievement(userId: string, achievementId: string): Promise<boolean> {
|
|
this.logger.debug(`Checking if user ${userId} earned achievement ${achievementId}`);
|
|
try {
|
|
const ua = await this.findUserAchievementByUserAndAchievement(userId, achievementId);
|
|
const hasEarned = ua !== null && ua.isComplete();
|
|
this.logger.debug(`User ${userId} earned achievement ${achievementId}: ${hasEarned}.`);
|
|
return hasEarned;
|
|
} catch (error) {
|
|
this.logger.error(`Error checking if user ${userId} earned achievement ${achievementId}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async createUserAchievement(userAchievement: UserAchievement): Promise<UserAchievement> {
|
|
this.logger.debug(`Creating user achievement: ${userAchievement.id}`);
|
|
try {
|
|
if (this.userAchievements.has(userAchievement.id)) {
|
|
this.logger.warn(`UserAchievement with ID ${userAchievement.id} already exists.`);
|
|
throw new Error('UserAchievement with this ID already exists');
|
|
}
|
|
this.userAchievements.set(userAchievement.id, userAchievement);
|
|
this.logger.info(`UserAchievement ${userAchievement.id} created successfully.`);
|
|
return userAchievement;
|
|
} catch (error) {
|
|
this.logger.error(`Error creating user achievement ${userAchievement.id}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateUserAchievement(userAchievement: UserAchievement): Promise<UserAchievement> {
|
|
this.logger.debug(`Updating user achievement: ${userAchievement.id}`);
|
|
try {
|
|
if (!this.userAchievements.has(userAchievement.id)) {
|
|
this.logger.warn(`UserAchievement with ID ${userAchievement.id} not found for update.`);
|
|
throw new Error('UserAchievement not found');
|
|
}
|
|
this.userAchievements.set(userAchievement.id, userAchievement);
|
|
this.logger.info(`UserAchievement ${userAchievement.id} updated successfully.`);
|
|
return userAchievement;
|
|
} catch (error) {
|
|
this.logger.error(`Error updating user achievement ${userAchievement.id}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Stats
|
|
async getAchievementLeaderboard(limit: number): Promise<{ userId: string; points: number; count: number }[]> {
|
|
this.logger.debug(`Getting achievement leaderboard with limit: ${limit}`);
|
|
try {
|
|
const userStats = new Map<string, { points: number; count: number }>();
|
|
|
|
for (const ua of this.userAchievements.values()) {
|
|
if (!ua.isComplete()) continue;
|
|
|
|
const achievement = this.achievements.get(ua.achievementId);
|
|
if (!achievement) {
|
|
this.logger.warn(`Achievement ${ua.achievementId} not found while building leaderboard.`);
|
|
continue;
|
|
}
|
|
|
|
const existing = userStats.get(ua.userId) ?? { points: 0, count: 0 };
|
|
userStats.set(ua.userId, {
|
|
points: existing.points + achievement.points,
|
|
count: existing.count + 1,
|
|
});
|
|
}
|
|
|
|
const leaderboard = Array.from(userStats.entries())
|
|
.map(([userId, stats]) => ({ userId, ...stats }))
|
|
.sort((a, b) => b.points - a.points)
|
|
.slice(0, limit);
|
|
this.logger.info(`Generated achievement leaderboard with ${leaderboard.length} entries.`);
|
|
return leaderboard;
|
|
} catch (error) {
|
|
this.logger.error(`Error getting achievement leaderboard:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getUserAchievementStats(userId: string): Promise<{
|
|
total: number;
|
|
points: number;
|
|
byCategory: Record<AchievementCategory, number>
|
|
}> {
|
|
this.logger.debug(`Getting achievement stats for user: ${userId}`);
|
|
try {
|
|
const userAchievements = await this.findUserAchievementsByUserId(userId);
|
|
const completedAchievements = userAchievements.filter(ua => ua.isComplete());
|
|
this.logger.debug(`Found ${completedAchievements.length} completed achievements for user ${userId}.`);
|
|
|
|
const byCategory: Record<AchievementCategory, number> = {
|
|
driver: 0,
|
|
steward: 0,
|
|
admin: 0,
|
|
community: 0,
|
|
};
|
|
|
|
let points = 0;
|
|
|
|
for (const ua of completedAchievements) {
|
|
const achievement = this.achievements.get(ua.achievementId);
|
|
if (achievement) {
|
|
points += achievement.points;
|
|
byCategory[achievement.category]++;
|
|
} else {
|
|
this.logger.warn(`Achievement ${ua.achievementId} not found while calculating user stats for user ${userId}.`);
|
|
}
|
|
}
|
|
|
|
const stats = {
|
|
total: completedAchievements.length,
|
|
points,
|
|
byCategory,
|
|
};
|
|
this.logger.info(`Generated achievement stats for user ${userId}:`, stats);
|
|
return stats;
|
|
} catch (error) {
|
|
this.logger.error(`Error getting user achievement stats for user ${userId}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Test helpers
|
|
clearUserAchievements(): void {
|
|
this.logger.debug('Clearing all user achievements.');
|
|
this.userAchievements.clear();
|
|
this.logger.info('All user achievements cleared.');
|
|
}
|
|
|
|
clear(): void {
|
|
this.logger.debug('Clearing all achievement data.');
|
|
this.achievements.clear();
|
|
this.userAchievements.clear();
|
|
this.logger.info('All achievement data cleared.');
|
|
}
|
|
} |