Skip to content

Commit 6a15d3f

Browse files
Feature/inactivity analysis (#207)
* feat: implement separate daily inactivity analysis script and workflow * feat: add daily inactivity analysis ignoring zero-score users * feat: complete inactivity analysis logic and update cron schedule * fix: switch inactivity tracking source to users.json and fix live profile validation * Refactor inactivity analysis logic Removed inactive user check based on solved questions. * style: auto-format code with Prettier (/format) * Remove emoji from inactivity log messages --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent bb54ea3 commit 6a15d3f

2 files changed

Lines changed: 164 additions & 0 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Scheduled Inactivity Analysis
2+
3+
on:
4+
schedule:
5+
# Triggers the inactivity scan at 05:45, 11:45, 17:45, and 23:45
6+
- cron: "45 5,11,17,23 * * *"
7+
workflow_dispatch:
8+
9+
jobs:
10+
analyze:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout Code Repository
15+
uses: actions/checkout@v4
16+
with:
17+
path: code-repo
18+
19+
- name: Checkout Data Repository
20+
uses: actions/checkout@v4
21+
with:
22+
repository: "codepvg/leetcode-ranking-data"
23+
token: ${{ secrets.DATA_REPO_TOKEN }}
24+
path: data-repo
25+
26+
- name: Set up Node.js
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: "20"
30+
cache: "npm"
31+
cache-dependency-path: "code-repo/package-lock.json"
32+
33+
- name: Install Dependencies
34+
run: |
35+
cd code-repo
36+
npm install
37+
38+
- name: Run Inactivity Analysis Script
39+
env:
40+
DATA_DIR: "${{ github.workspace }}/data-repo"
41+
run: |
42+
cd code-repo
43+
node scripts/analyze-inactivity.js
44+
45+
- name: Commit and Push to Data Repo
46+
run: |
47+
cd data-repo
48+
git config --global user.name "github-actions[bot]"
49+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
50+
51+
git add inactive-users.json
52+
53+
if git diff --staged --quiet; then
54+
echo "No changes detected in inactivity data."
55+
else
56+
git commit -m "chore: update scheduled inactive-users.json snapshot"
57+
git push
58+
fi

scripts/analyze-inactivity.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"use strict";
2+
3+
const axios = require("axios");
4+
const fs = require("fs");
5+
const path = require("path");
6+
7+
const THRESHOLD_DAYS = 90;
8+
const MS_IN_A_DAY = 1000 * 60 * 60 * 24;
9+
10+
function atomicWrite(filePath, data) {
11+
const dir = path.dirname(filePath);
12+
const ext = path.extname(filePath);
13+
const base = path.basename(filePath, ext);
14+
const tmpPath = path.join(
15+
dir,
16+
`${base}.tmp.${process.pid}.${Date.now()}${ext}`,
17+
);
18+
19+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8");
20+
fs.renameSync(tmpPath, filePath);
21+
}
22+
23+
async function fetchData(url) {
24+
try {
25+
const res = await axios.get(url, { timeout: 15000 });
26+
return res.data;
27+
} catch (err) {
28+
console.error(`API failed for ${url}: ${err.message}`);
29+
return null;
30+
}
31+
}
32+
33+
(async () => {
34+
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, "..", "data");
35+
console.log(`Using data directory: ${DATA_DIR}`);
36+
37+
console.log("Loading master user list...");
38+
const userFilePath = path.join(DATA_DIR, "users.json");
39+
let users = [];
40+
try {
41+
const rawData = fs.readFileSync(userFilePath, "utf8");
42+
users = JSON.parse(rawData);
43+
console.log(`Loaded ${users.length} users from users.json`);
44+
} catch (err) {
45+
console.error("Failed to load users.json: ", err.message);
46+
process.exit(1);
47+
}
48+
49+
const baseUrl = "https://leetcode-api-dun.vercel.app/";
50+
const inactiveUsers = [];
51+
const now = new Date();
52+
53+
console.log(" ");
54+
console.log("Starting daily full sync inactivity analysis...");
55+
56+
for (const user of users) {
57+
const username = user.id;
58+
59+
const profile = await fetchData(baseUrl + username);
60+
if (!profile) {
61+
console.log(`${username}: skipped (API error)`);
62+
continue;
63+
}
64+
65+
const calendar = profile.submissionCalendar;
66+
const timestamps = calendar ? Object.keys(calendar).map(Number) : [];
67+
68+
if (timestamps.length === 0) {
69+
console.log(`${username}: Inactive (no submission calendar history)`);
70+
inactiveUsers.push(username);
71+
continue;
72+
}
73+
74+
const latestTimestampSeconds = Math.max(...timestamps);
75+
const lastActiveDate = new Date(latestTimestampSeconds * 1000);
76+
77+
const diffTime = Math.abs(now - lastActiveDate);
78+
const diffDays = Math.floor(diffTime / MS_IN_A_DAY);
79+
80+
if (diffDays > THRESHOLD_DAYS) {
81+
console.log(`${username}: Inactive (${diffDays} days ago)`);
82+
inactiveUsers.push(username);
83+
} else {
84+
console.log(`${username}: Active (${diffDays} days ago)`);
85+
}
86+
}
87+
88+
console.log("...");
89+
console.log(" ");
90+
91+
const outputData = {
92+
generatedAt: now.toISOString(),
93+
thresholdDays: THRESHOLD_DAYS,
94+
inactiveUsers: inactiveUsers.sort(),
95+
};
96+
97+
console.log("Writing inactivity analysis data to inactive-users.json...");
98+
const outputPath = path.join(DATA_DIR, "inactive-users.json");
99+
try {
100+
atomicWrite(outputPath, outputData);
101+
console.log("Inactivity analysis data updated successfully!");
102+
} catch (err) {
103+
console.error("Failed to write inactive-users.json: ", err.message);
104+
process.exit(1);
105+
}
106+
})();

0 commit comments

Comments
 (0)