From d6c46eac411f4e4691534f32d4de7cfbeaabb631 Mon Sep 17 00:00:00 2001 From: sidhdhi canopas Date: Fri, 13 Dec 2024 12:09:20 +0530 Subject: [PATCH] add function to record leaderboard data --- khelo/functions/eslint.config.mjs | 4 +- khelo/functions/package-lock.json | 2 +- khelo/functions/package.json | 2 +- khelo/functions/src/index.js | 23 ++++- .../src/leaderboard/leaderboard_repository.js | 90 ++++++++++++++++++ .../src/leaderboard/leaderboard_service.js | 94 +++++++++++++++++++ 6 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 khelo/functions/src/leaderboard/leaderboard_repository.js create mode 100644 khelo/functions/src/leaderboard/leaderboard_service.js diff --git a/khelo/functions/eslint.config.mjs b/khelo/functions/eslint.config.mjs index d875ec09..e86682f8 100644 --- a/khelo/functions/eslint.config.mjs +++ b/khelo/functions/eslint.config.mjs @@ -7,7 +7,7 @@ export default [ google, { languageOptions: { - ecmaVersion: 2018, + ecmaVersion: 2021, globals: { ...globals.browser, ...globals.node, @@ -21,7 +21,7 @@ export default [ "quotes": ["error", "double"], "import/no-unresolved": 0, "indent": ["error", 2], - "max-len": ["error", {"code": 140}], + "max-len": ["error", {"code": 200}], "new-cap": 0, "require-jsdoc": 0, "no-extend-native": 0, diff --git a/khelo/functions/package-lock.json b/khelo/functions/package-lock.json index 78dbce3c..c6ce9a1b 100644 --- a/khelo/functions/package-lock.json +++ b/khelo/functions/package-lock.json @@ -19,7 +19,7 @@ "globals": "^15.9.0" }, "engines": { - "node": "18" + "node": "20" } }, "node_modules/@ampproject/remapping": { diff --git a/khelo/functions/package.json b/khelo/functions/package.json index a85ce56b..50dc150d 100644 --- a/khelo/functions/package.json +++ b/khelo/functions/package.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "20" }, "main": "src/index.js", "dependencies": { diff --git a/khelo/functions/src/index.js b/khelo/functions/src/index.js index dc5570fa..3ba0564f 100644 --- a/khelo/functions/src/index.js +++ b/khelo/functions/src/index.js @@ -1,7 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", {value: true}); -exports.fiveMinuteCron = exports.apiv1 = exports.teamPlayerChangeObserver = exports.TIMEZONE = void 0; +exports.fiveMinuteCron = exports.apiv1 = exports.teamPlayerChangeObserver = exports.userStatWriteObserver = exports.TIMEZONE = void 0; const express = require("express"); @@ -15,11 +15,13 @@ const logger = require("firebase-functions/logger"); const team_repository = require("./team/team_repository"); const user_repository = require("./user/user_repository"); const match_repository = require("./match/match_repository"); +const leaderboard_repository = require("./leaderboard/leaderboard_repository"); const notification_service = require("./notification/notification_service"); const team_service = require("./team/team_service"); const match_service = require("./match/match_service"); const auth_service = require("./auth/auth_service"); +const leaderboard_service = require("./leaderboard/leaderboard_service"); exports.TIMEZONE = "Asia/Kolkata"; const REGION = "asia-south1"; @@ -28,11 +30,13 @@ const db = (0, firestore_1.getFirestore)(app); const userRepository = new user_repository.UserRepository(db); const teamRepository = new team_repository.TeamRepository(db); +const leaderboardRepository = new leaderboard_repository.LeaderboardRepository(db); const notificationService = new notification_service.NotificationService(userRepository); const teamService = new team_service.TeamService(userRepository, notificationService); const matchService = new match_service.MatchService(userRepository, teamRepository, notificationService); const authService = new auth_service.AuthService(userRepository); +const leaderboardService = new leaderboard_service.LeaderboardService(leaderboardRepository); const matchRepository = new match_repository.MatchRepository(db, matchService); @@ -66,6 +70,23 @@ exports.teamPlayerChangeObserver = (0, firestore_2.onDocumentUpdated)({region: R await teamService.notifyOnAddedToTeam(oldTeam, newTeam); }); +exports.userStatWriteObserver = (0, firestore_2.onDocumentWritten)({region: REGION, document: "users/{userId}/user_stat/{type}"}, async (event) => { + const snapshot = event.data; + if (!snapshot) { + (0, logger.error)("No data associated with the event"); + return; + } + + const oldStat = snapshot?.before?.data(); + const newStat = snapshot?.after?.data(); + + const {userId} = event.params; + + if (newStat) { + await leaderboardService.updateLeaderboard(userId, oldStat, newStat); + } +}); + exports.fiveMinuteCron = (0, scheduler.onSchedule)({timeZone: exports.TIMEZONE, schedule: "*/5 * * * *", region: REGION}, async () => { await matchRepository.processUpcomingMatches(); }); diff --git a/khelo/functions/src/leaderboard/leaderboard_repository.js b/khelo/functions/src/leaderboard/leaderboard_repository.js new file mode 100644 index 00000000..3e15cfd6 --- /dev/null +++ b/khelo/functions/src/leaderboard/leaderboard_repository.js @@ -0,0 +1,90 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", {value: true}); +exports.LeaderboardRepository = void 0; + +class LeaderboardRepository { + constructor(db) { + this.db = db; + } + leaderboardRef() { + return this.db.collection("leaderboard"); + } + timeBasedLeaderboardRef(timeRange) { + return this.leaderboardRef() + .doc(timeRange) + .collection("data"); + } + + async getWeeklyLeaderboardOfUser(userId) { + try { + const userRef = this.timeBasedLeaderboardRef("weekly").doc(userId); + const weeklyDoc = await userRef.get(); + if (!weeklyDoc.exists) { + return null; + } + return weeklyDoc.data(); + } catch (e) { + console.error("LeaderboardRepository: Error getting weekly data of user:", e); + return null; + } + } + + async getMonthlyLeaderboardOfUser(userId) { + try { + const userRef = this.timeBasedLeaderboardRef("monthly").doc(userId); + const monthlyDoc = await userRef.get(); + if (!monthlyDoc.exists) { + return null; + } + return monthlyDoc.data(); + } catch (e) { + console.error("LeaderboardRepository: Error getting monthly data of user:", e); + return null; + } + } + + async getAllTimeLeaderboardOfUser(userId) { + try { + const userRef = this.timeBasedLeaderboardRef("all_time").doc(userId); + const allTimeDoc = await userRef.get(); + if (!allTimeDoc.exists) { + return null; + } + return allTimeDoc.data(); + } catch (e) { + console.error("LeaderboardRepository: Error getting all time data of user:", e); + return null; + } + } + + async updateWeeklyLeaderboardOfUser(stats) { + try { + const userRef = this.timeBasedLeaderboardRef("weekly").doc(stats.id); + await userRef.set(stats); + } catch (e) { + console.error("LeaderboardRepository: Error in updating weekly data of user:", e); + return null; + } + } + + async updateMonthlyLeaderboardOfUser(stats) { + try { + const userRef = this.timeBasedLeaderboardRef("monthly").doc(stats.id); + await userRef.set(stats); + } catch (e) { + console.error("LeaderboardRepository: Error in updating monthly data of user:", e); + return null; + } + } + + async updateAllTimeLeaderboardOfUser(stats) { + try { + const userRef = this.timeBasedLeaderboardRef("all_time").doc(stats.id); + await userRef.set(stats); + } catch (e) { + console.error("LeaderboardRepository: Error in updating all time data of user:", e); + return null; + } + } +} +exports.LeaderboardRepository=LeaderboardRepository; diff --git a/khelo/functions/src/leaderboard/leaderboard_service.js b/khelo/functions/src/leaderboard/leaderboard_service.js new file mode 100644 index 00000000..48f1c9b5 --- /dev/null +++ b/khelo/functions/src/leaderboard/leaderboard_service.js @@ -0,0 +1,94 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", {value: true}); +exports.LeaderboardService = void 0; +const firestore_1 = require("firebase-admin/firestore"); + +class LeaderboardService { + constructor(leaderboardRepository) { + this.leaderboardRepository = leaderboardRepository; + } + + async updateLeaderboard(userId, oldStat, newStat) { + let weekStat = await this.leaderboardRepository.getWeeklyLeaderboardOfUser(userId); + let monthStat = await this.leaderboardRepository.getMonthlyLeaderboardOfUser(userId); + let allTimeStat = await this.leaderboardRepository.getAllTimeLeaderboardOfUser(userId); + + const runDiff = (newStat.batting?.run_scored ?? 0) - (oldStat?.batting?.run_scored ?? 0); + const wicketDiff = (newStat.bowling?.wicket_taken ?? 0) - (oldStat?.bowling?.wicket_taken ?? 0); + const catchDiff = (newStat.fielding?.catches ?? 0) - (oldStat?.fielding?.catches ?? 0); + + const runs = runDiff >= 0 ? runDiff : newStat.batting?.run_scored ?? 0; + const wickets = wicketDiff >= 0 ? wicketDiff : newStat.bowling?.wicket_taken ?? 0; + const catches = catchDiff >= 0 ? catchDiff : newStat.fielding?.catches ?? 0; + + if (!weekStat) { + weekStat = {id: userId, runs: runs, wickets: wickets, catches: catches, date: new Date()}; + } else { + if (weekStat.date instanceof firestore_1.Timestamp && this.isTimeInCurrentWeek(weekStat.date.toDate())) { + weekStat.runs += runs; + weekStat.wickets += wickets; + weekStat.catches += catches; + } else { + weekStat.runs = runs; + weekStat.wickets = wickets; + weekStat.catches = catches; + } + weekStat.date = new Date(); + } + + // Check if monthStat exists, otherwise create a new object + if (!monthStat) { + monthStat = {id: userId, runs: runs, wickets: wickets, catches: catches, date: new Date()}; + } else { + // Update the monthStat if it's within the current month, else reset it + if (monthStat.date instanceof firestore_1.Timestamp && this.isTimeInCurrentMonth(monthStat.date.toDate())) { + monthStat.runs += runs; + monthStat.wickets += wickets; + monthStat.catches += catches; + } else { + monthStat.runs = runs; + monthStat.wickets = wickets; + monthStat.catches = catches; + } + monthStat.date = new Date(); + } + + if (!allTimeStat) { + allTimeStat = {id: userId, runs: runs, wickets: wickets, catches: catches, date: new Date()}; + } else { + allTimeStat.runs += runs; + allTimeStat.wickets += wickets; + allTimeStat.catches += catches; + allTimeStat.date = new Date(); + } + + await this.leaderboardRepository.updateWeeklyLeaderboardOfUser(weekStat); + await this.leaderboardRepository.updateMonthlyLeaderboardOfUser(monthStat); + await this.leaderboardRepository.updateAllTimeLeaderboardOfUser(allTimeStat); + } + + isTimeInCurrentWeek(date) { + // Note: Current week starts from Monday and ends on Sunday + const givenDate = new Date(date); + const currentDate = new Date(); + + const startOfWeek = new Date(currentDate); + startOfWeek.setDate(currentDate.getDate() - currentDate.getDay() + 1); + + const endOfWeek = new Date(startOfWeek); + endOfWeek.setDate(startOfWeek.getDate() + 6); + + return givenDate >= startOfWeek && givenDate <= endOfWeek; + } + + isTimeInCurrentMonth(date) { + const givenDate = new Date(date); + const currentDate = new Date(); + + return ( + givenDate.getMonth() === currentDate.getMonth() && + givenDate.getFullYear() === currentDate.getFullYear() + ); + } +} +exports.LeaderboardService = LeaderboardService;