All files / src/services gamificationService.ts

61.7% Statements 58/94
100% Branches 4/4
50% Functions 4/8
65.33% Lines 49/75

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 752x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 8x 8x 2x 5x 2x 2x   3x     2x       2x         2x 2x 2x 2x 5x 5x   2x 2x         2x 2x 2x 2x 2x 6x 6x   2x 2x           2x 2x 2x 2x 6x 6x   2x 2x         25x
// src/services/gamificationService.ts
 
import { gamificationRepo } from './db/index.db';
import type { Logger } from 'pino';
import { ForeignKeyConstraintError } from './db/errors.db';
 
class GamificationService {
  /**
   * Awards a specific achievement to a user.
   * @param userId The ID of the user to award the achievement.
   * @param achievementName The name of the achievement to award.
   * @param log The logger instance.
   */
  async awardAchievement(userId: string, achievementName: string, log: Logger): Promise<void> {
    try {
      await gamificationRepo.awardAchievement(userId, achievementName, log);
    } catch (error) {
      if (error instanceof ForeignKeyConstraintError) {
        // This is an expected error (e.g., achievement name doesn't exist),
        // which the repository layer should have already logged with appropriate context.
        // We re-throw it so the calling layer (e.g., an admin route) can handle it.
        throw error;
      }
      // For unexpected, generic errors, we log them at the service level before re-throwing.
      log.error(
        { error, userId, achievementName },
        'Error awarding achievement via admin endpoint:',
      );
      throw error;
    }
  }

  /**
   * Retrieves the master list of all available achievements.
   * @param log The logger instance.
   */
  async getAllAchievements(log: Logger) {
    try {
      return await gamificationRepo.getAllAchievements(log);
    } catch (error) {
      log.error({ error }, 'Error in getAllAchievements service method');
      throw error;
    }
  }

  /**
   * Retrieves the public leaderboard of top users by points.
   * @param limit The number of users to fetch.
   * @param log The logger instance.
   */
  async getLeaderboard(limit: number, log: Logger) {
    try {
      return await gamificationRepo.getLeaderboard(limit, log);
    } catch (error) {
      log.error({ error, limit }, 'Error fetching leaderboard in service method.');
      throw error;
    }
  }

  /**
   * Retrieves all achievements earned by a specific user.
   * @param userId The ID of the user.
   * @param log The logger instance.
   */
  async getUserAchievements(userId: string, log: Logger) {
    try {
      return await gamificationRepo.getUserAchievements(userId, log);
    } catch (error) {
      log.error({ error, userId }, 'Error fetching user achievements in service method.');
      throw error;
    }
  }
}

export const gamificationService = new GamificationService();