![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/public_html/src/lib/ |
import { prisma } from './prisma';
// XP Earning Activities
export const XP_ACTIVITIES = {
// General Legal Activities
CASE_APPLICATION_REVIEW: 10,
CLIENT_INTERVIEW: 25,
DOCUMENT_ANALYSIS: 15,
LEGAL_RESEARCH: 20,
COURT_FILING: 50,
SUCCESSFUL_OUTCOME: 100,
PRO_BONO_WORK: 30,
MENTORSHIP: 25,
SOCIETY_CONTRIBUTION: 20,
// Bordeaux Case Specific (Bonus XP)
BORDEAUX_CASE_REVIEW: 50,
BORDEAUX_CLIENT_INTERVIEW: 75,
BORDEAUX_DOCUMENT_ANALYSIS: 40,
BORDEAUX_LEGAL_RESEARCH: 60,
BORDEAUX_COURT_FILING: 100,
BORDEAUX_CASE_SUCCESS: 200,
BORDEAUX_PRO_BONO: 50,
// Society Activities
DEGREE_CEREMONY: 100,
LODGE_MEETING: 15,
MENTORSHIP_SESSION: 30,
SOCIETY_EVENT: 25,
// Achievement Milestones
FIRST_CASE: 50,
TENTH_CASE: 100,
HUNDREDTH_CASE: 500,
FIRST_DEGREE: 25,
TENTH_DEGREE: 200,
XP_MILESTONE_1000: 50,
XP_MILESTONE_5000: 100,
XP_MILESTONE_10000: 200
};
// Achievement Types
export const ACHIEVEMENT_TYPES = {
CASE_BASED: 'case_based',
XP_MILESTONE: 'xp_milestone',
DEGREE_BASED: 'degree_based',
BORDEAUX_SPECIALIST: 'bordeaux_specialist',
SOCIETY_CONTRIBUTOR: 'society_contributor'
};
// Achievement Definitions
export const ACHIEVEMENTS = {
FIRST_CASE: {
id: 'first_case',
name: 'First Case',
description: 'Successfully handled your first case',
type: ACHIEVEMENT_TYPES.CASE_BASED,
xpReward: 50,
icon: '📋'
},
TENTH_CASE: {
id: 'tenth_case',
name: 'Case Veteran',
description: 'Successfully handled 10 cases',
type: ACHIEVEMENT_TYPES.CASE_BASED,
xpReward: 100,
icon: '⚖️'
},
HUNDREDTH_CASE: {
id: 'hundredth_case',
name: 'Case Master',
description: 'Successfully handled 100 cases',
type: ACHIEVEMENT_TYPES.CASE_BASED,
xpReward: 500,
icon: '👑'
},
FIRST_DEGREE: {
id: 'first_degree',
name: 'Degree Initiate',
description: 'Achieved your first degree in the Society',
type: ACHIEVEMENT_TYPES.DEGREE_BASED,
xpReward: 25,
icon: '🎓'
},
TENTH_DEGREE: {
id: 'tenth_degree',
name: 'Degree Scholar',
description: 'Achieved 10 degrees in the Society',
type: ACHIEVEMENT_TYPES.DEGREE_BASED,
xpReward: 200,
icon: '🏛️'
},
BORDEAUX_SPECIALIST: {
id: 'bordeaux_specialist',
name: 'Bordeaux Specialist',
description: 'Worked on 5 Bordeaux case applications',
type: ACHIEVEMENT_TYPES.BORDEAUX_SPECIALIST,
xpReward: 150,
icon: '🏛️'
},
XP_MILESTONE_1000: {
id: 'xp_1000',
name: 'XP Novice',
description: 'Reached 1,000 XP points',
type: ACHIEVEMENT_TYPES.XP_MILESTONE,
xpReward: 50,
icon: '⭐'
},
XP_MILESTONE_5000: {
id: 'xp_5000',
name: 'XP Expert',
description: 'Reached 5,000 XP points',
type: ACHIEVEMENT_TYPES.XP_MILESTONE,
xpReward: 100,
icon: '🌟'
},
XP_MILESTONE_10000: {
id: 'xp_10000',
name: 'XP Master',
description: 'Reached 10,000 XP points',
type: ACHIEVEMENT_TYPES.XP_MILESTONE,
xpReward: 200,
icon: '💫'
}
};
export interface XPEarningActivity {
userId: string;
activityType: keyof typeof XP_ACTIVITIES;
xpAmount: number;
description: string;
metadata?: any;
caseId?: string;
isBordeauxCase?: boolean;
}
export interface Achievement {
id: string;
name: string;
description: string;
type: string;
xpReward: number;
icon: string;
unlockedAt?: Date;
}
export class XPSystem {
/**
* Award XP to a user for an activity
*/
static async awardXP(activity: XPEarningActivity): Promise<{
xpAwarded: number;
newTotal: number;
achievements: Achievement[];
}> {
const { userId, activityType, xpAmount, description, metadata, caseId, isBordeauxCase } = activity;
try {
// Start transaction
const result = await prisma.$transaction(async (tx) => {
// Get current user
const user = await tx.user.findUnique({
where: { id: userId },
select: { id: true, xpPoints: true }
});
if (!user) {
throw new Error('User not found');
}
// Calculate XP amount (with Bordeaux bonus if applicable)
let finalXPAmount = xpAmount;
if (isBordeauxCase && caseId) {
// Check if this is a Bordeaux case
const caseInfo = await tx.legalCase.findFirst({
where: {
id: caseId,
title: { contains: 'Bordeaux' }
}
});
if (caseInfo) {
// Apply Bordeaux bonus
const bordeauxBonus = Math.floor(xpAmount * 0.5); // 50% bonus
finalXPAmount += bordeauxBonus;
}
}
// Update user XP
const updatedUser = await tx.user.update({
where: { id: userId },
data: {
xpPoints: {
increment: finalXPAmount
}
},
select: { id: true, xpPoints: true }
});
// Log XP earning activity - commented out due to missing model
// await tx.xpEarningActivity.create({
// data: {
// userId,
// activityType,
// xpAmount: finalXPAmount,
// description,
// metadata: metadata ? JSON.stringify(metadata) : null,
// caseId,
// isBordeauxCase: isBordeauxCase || false
// }
// });
// Check for achievements
const achievements = await this.checkAchievements(tx, userId, updatedUser.xpPoints);
return {
xpAwarded: finalXPAmount,
newTotal: updatedUser.xpPoints,
achievements
};
});
return result;
} catch (error) {
console.error('Error awarding XP:', error);
throw error;
}
}
/**
* Check for new achievements based on user's current state
*/
static async checkAchievements(tx: any, userId: string, currentXP: number): Promise<Achievement[]> {
const newAchievements: Achievement[] = [];
// Get user's current achievements
const existingAchievements = await tx.userAchievement.findMany({
where: { userId },
select: { achievementId: true }
});
const existingAchievementIds = new Set(existingAchievements.map((a: any) => a.achievementId));
// Check XP milestone achievements
if (currentXP >= 1000 && !existingAchievementIds.has('xp_1000')) {
newAchievements.push(ACHIEVEMENTS.XP_MILESTONE_1000);
}
if (currentXP >= 5000 && !existingAchievementIds.has('xp_5000')) {
newAchievements.push(ACHIEVEMENTS.XP_MILESTONE_5000);
}
if (currentXP >= 10000 && !existingAchievementIds.has('xp_10000')) {
newAchievements.push(ACHIEVEMENTS.XP_MILESTONE_10000);
}
// Check case-based achievements
const caseCount = await tx.caseAssignment.count({
where: {
userId,
isActive: true
}
});
if (caseCount >= 1 && !existingAchievementIds.has('first_case')) {
newAchievements.push(ACHIEVEMENTS.FIRST_CASE);
}
if (caseCount >= 10 && !existingAchievementIds.has('tenth_case')) {
newAchievements.push(ACHIEVEMENTS.TENTH_CASE);
}
if (caseCount >= 100 && !existingAchievementIds.has('hundredth_case')) {
newAchievements.push(ACHIEVEMENTS.HUNDREDTH_CASE);
}
// Check degree-based achievements
const degreeCount = await tx.userDegree.count({
where: {
userId,
ceremonyCompleted: true
}
});
if (degreeCount >= 1 && !existingAchievementIds.has('first_degree')) {
newAchievements.push(ACHIEVEMENTS.FIRST_DEGREE);
}
if (degreeCount >= 10 && !existingAchievementIds.has('tenth_degree')) {
newAchievements.push(ACHIEVEMENTS.TENTH_DEGREE);
}
// Check Bordeaux specialist achievement
const bordeauxCaseCount = await tx.caseAssignment.count({
where: {
userId,
isActive: true,
registration: {
caseId: {
not: null
}
}
}
});
if (bordeauxCaseCount >= 5 && !existingAchievementIds.has('bordeaux_specialist')) {
newAchievements.push(ACHIEVEMENTS.BORDEAUX_SPECIALIST);
}
// Award achievements and additional XP
for (const achievement of newAchievements) {
await tx.userAchievement.create({
data: {
userId,
achievementId: achievement.id,
lastUpdated: new Date(),
currentProgress: 100,
isCompleted: true,
completedAt: new Date()
}
});
// Award XP for achievement
await tx.user.update({
where: { id: userId },
data: {
xpPoints: {
increment: achievement.xpReward
}
}
});
}
return newAchievements;
}
/**
* Get user's XP history
*/
static async getUserXPHistory(userId: string, limit: number = 20) {
// Commented out due to missing xpEarningActivity model
// return await prisma.xpEarningActivity.findMany({
// where: { userId },
// orderBy: { createdAt: 'desc' },
// take: limit,
// include: {
// case: {
// select: {
// title: true,
// caseNumber: true
// }
// }
// }
// });
return [];
}
/**
* Get user's achievements
*/
static async getUserAchievements(userId: string) {
return await prisma.userAchievement.findMany({
where: { userId },
include: {
achievement: true
},
orderBy: { lastUpdated: 'desc' }
});
}
/**
* Get leaderboard data
*/
static async getLeaderboard(limit: number = 10) {
return await prisma.user.findMany({
where: {
role: { in: ['LAWYER', 'ADMIN', 'SUPERADMIN', 'SUPERADMIN'] }
},
select: {
id: true,
name: true,
xpPoints: true,
role: true,
degrees: {
where: { ceremonyCompleted: true },
include: {
degree: {
select: {
degreeNumber: true,
name: true,
symbol: true
}
}
},
orderBy: {
degree: { degreeNumber: 'desc' }
},
take: 1
}
},
orderBy: { xpPoints: 'desc' },
take: limit
});
}
/**
* Get Bordeaux case leaderboard
*/
static async getBordeauxLeaderboard(limit: number = 10) {
// For now, return users with highest XP points as a fallback
return await prisma.user.findMany({
where: {
role: { in: ['LAWYER', 'ADMIN', 'SUPERADMIN', 'SUPERADMIN'] }
},
select: {
id: true,
name: true,
xpPoints: true,
role: true
},
orderBy: { xpPoints: 'desc' },
take: limit
});
}
}