/** * Infrastructure Adapter: InMemoryAchievementRepository * * In-memory implementation of IAchievementRepository */ import { ADMIN_ACHIEVEMENTS, COMMUNITY_ACHIEVEMENTS, DRIVER_ACHIEVEMENTS, STEWARD_ACHIEVEMENTS } from "@core/identity/domain/AchievementConstants"; import { Achievement, type AchievementCategory } from "@core/identity/domain/entities/Achievement"; import { UserAchievement } from "@core/identity/domain/entities/UserAchievement"; import { AchievementRepository } from "@core/identity/domain/repositories/AchievementRepository"; import type { Logger } from "@core/shared/domain/Logger"; export class InMemoryAchievementRepository implements AchievementRepository { private achievements: Map = new Map(); private userAchievements: Map = 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 { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async findAllAchievements(): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async findAchievementsByCategory(category: AchievementCategory): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async createAchievement(achievement: Achievement): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } // UserAchievement operations async findUserAchievementById(id: string): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async findUserAchievementsByUserId(userId: string): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async findUserAchievementByUserAndAchievement( userId: string, achievementId: string ): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async hasUserEarnedAchievement(userId: string, achievementId: string): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async createUserAchievement(userAchievement: UserAchievement): Promise { 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 instanceof Error ? error : new Error(String(error))); throw error; } } async updateUserAchievement(userAchievement: UserAchievement): Promise { 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 instanceof Error ? error : new Error(String(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(); 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 instanceof Error ? error : new Error(String(error))); throw error; } } async getUserAchievementStats(userId: string): Promise<{ total: number; points: number; byCategory: Record }> { 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 = { 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 instanceof Error ? error : new Error(String(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.'); } }