From c1d54110c33761a27a32c10c391c8b42e857ba42 Mon Sep 17 00:00:00 2001
From: SysSn13 <sudesh18@iitg.ac.in>
Date: Sun, 20 Feb 2022 13:12:35 +0530
Subject: [PATCH] feat: rejudging contests

---
 models/contest.js                             |  4 ++++
 services/contests.js                          |  3 ++-
 services/job-queues/contestPredictionQueue.js | 12 +++++++++++-
 services/job-queues/jobScheduler.js           | 19 +++++++++++++++++++
 services/predict.js                           |  2 +-
 views/index.ejs                               |  9 +++++++++
 6 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/models/contest.js b/models/contest.js
index 67a9e8f..ac74c5f 100644
--- a/models/contest.js
+++ b/models/contest.js
@@ -40,6 +40,10 @@ const ContestRankingsSchema = new Schema({
         default: false,
     },
     rankings: [rankingSchema],
+    refetch_rankings:{
+        type:Boolean,
+        default:false,
+    },
     lastUpdated: {
         type: Date,
     },
diff --git a/services/contests.js b/services/contests.js
index 1f9b609..bc17023 100644
--- a/services/contests.js
+++ b/services/contests.js
@@ -8,7 +8,7 @@ const fetchContestRankings = async function (contestSlug) {
         if (!contest) {
             return [null, Error(`Contest ${contestSlug} not found in the db`)];
         }
-        if (contest.rankings_fetched) {
+        if (!contest.refetch_rankings && contest.rankings_fetched) {
             return [contest, null];
         }
         contest.rankings = [];
@@ -106,6 +106,7 @@ const fetchContestRankings = async function (contestSlug) {
 
         contest.rankings = all_rankings;
         contest.rankings_fetched = true;
+        contest.refetch_rankings = false;
         contest.user_num = all_rankings.length;
         console.time(`Saving rankings in db (${contestSlug})`);
         await contest.save();
diff --git a/services/job-queues/contestPredictionQueue.js b/services/job-queues/contestPredictionQueue.js
index 6dbc01b..4405776 100644
--- a/services/job-queues/contestPredictionQueue.js
+++ b/services/job-queues/contestPredictionQueue.js
@@ -11,13 +11,23 @@ const predictQueue = new Queue("Predictions", opts);
 predictQueue.process("predictRatings", async (job, done) => {
     try {
         console.log(`Processing ${job.name} job: ${job.id}`);
+
         const contest = await Contest.findById(job.data.contestSlug, {
             ratings_predicted: 1,
         });
-        if (contest && contest.ratings_predicted) {
+
+        const refetch = job.data.refetch && job.attemptsMade === 0; // applicable for the first attempt only
+
+        if (!refetch && contest && contest.ratings_predicted) {
             done(null, { message: "skipped (already predicted)" });
             return;
         }
+
+        if (refetch) {
+            contest.refetch_rankings = true;
+            await contest.save();
+        }
+
         const err = await predict(job);
         if (err) {
             console.error(err);
diff --git a/services/job-queues/jobScheduler.js b/services/job-queues/jobScheduler.js
index 9924491..4ca2533 100644
--- a/services/job-queues/jobScheduler.js
+++ b/services/job-queues/jobScheduler.js
@@ -37,6 +37,25 @@ scheduler.process("contestScheduler", async (job, done) => {
                 }
             );
             cnt++;
+            // refetch for the upcoming contests
+            if (remainingTime > 0) {
+                remainingTime += 55 * 60 * 1000; // 1 hour after the contest
+                contestPredictionQueue.add(
+                    "predictRatings",
+                    {
+                        contestSlug: contest._id,
+                        refetch: true,
+                    },
+                    {
+                        jobId: "refetch_" + contest._id,
+                        attempts: 5,
+                        delay: remainingTime,
+                        backoff: 10000,
+                        priority: 1,
+                    }
+                );
+                cnt++;
+            }
         }
     });
 
diff --git a/services/predict.js b/services/predict.js
index 0f81430..706b528 100644
--- a/services/predict.js
+++ b/services/predict.js
@@ -2,7 +2,7 @@ const { fetchContestRankings } = require("./contests");
 const { getContestParticipantsData } = require("./users");
 const Contest = require("../models/contest");
 const predictAddon = require("./predict-addon");
-const THREAD_CNT = process.env.THREAD_CNT || 4;
+const THREAD_CNT = Number(process.env.THREAD_CNT) || 4;
 
 const predict = async (job) => {
     try {
diff --git a/views/index.ejs b/views/index.ejs
index 9f9c2e8..50699fc 100644
--- a/views/index.ejs
+++ b/views/index.ejs
@@ -47,6 +47,7 @@
                 <th scope="col">Duration</th>
                 <th scope="col">Rankings Fetched</th>
                 <th scope="col">Predicted</th>
+                <th scope="col">Last Updated</th>
               </tr>
             </thead>
             <tbody>
@@ -73,6 +74,9 @@
                   <td>
                     <%= contest.ratings_predicted?"Yes":"No" %>
                   </td>
+                  <td class="lastUpdatedTime">
+                    <%= contest.lastUpdated %>
+                  </td>
                 </tr>
                 <% } %>
             </tbody>
@@ -88,10 +92,15 @@
   (function () {
     function localizeDate(page) {
       const startTimes = document.getElementsByClassName('startTime')
+      const lastUpdatedTimes = document.getElementsByClassName('lastUpdatedTime')
       for (const startTime of startTimes) {
         const dateString = startTime.data.trim()
         startTime.textContent = new Date(dateString).toLocaleString()
       }
+      for(const lastUpdatedTime of lastUpdatedTimes){
+        const dateString = lastUpdatedTime.data.trim()
+        lastUpdatedTime.textContent = new Date(dateString).toLocaleString()
+      }
     }
     const dataTable = new simpleDatatables.DataTable("#contest-table", {
       searchable: true,