feat: Add Interview Streak Tracking Feature (#625) - #1533
Conversation
📝 WalkthroughWalkthroughThe PR adds streak fields and milestone achievements. Session creation updates current and longest streaks. Profile and achievement reads reset the current streak after more than one missed UTC calendar day. ChangesInterview practice streak tracking
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant SessionController
participant Session
participant User
SessionController->>Session: create session and questions
SessionController->>User: load user in transaction
SessionController->>User: update streak and achievements
User-->>SessionController: save completed
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Hi @KaranUnique, I have recreated this PR from a clean branch to keep it scoped only to Issue #625. Changes made Kindly review the changes. Thank you! now you can merge> pls give me 150 points.....>thankyou!! |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/constants/achievements.js`:
- Around line 13-15: Remove "3-Day Streak", "7-Day Streak", and "30-Day Streak"
from VALID_ACHIEVEMENTS, or explicitly reject them in saveAchievements, so
clients cannot write server-managed streak badges. Preserve sessionController as
the only code path that awards these identifiers.
In `@backend/controllers/achievementController.js`:
- Around line 16-18: Replace the direct streak reset and save in both
achievementController.js (lines 16-18) and authController.js (lines 412-414)
with an atomic conditional update that matches the user only when
lastPracticeDate remains before the UTC yesterday boundary. In getUserProfile,
reload the user after the conditional update and before res.json(user) so
skipped resets return the latest streak.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3977c7fe-cfeb-46b7-aa19-863367418479
📒 Files selected for processing (5)
backend/constants/achievements.jsbackend/controllers/achievementController.jsbackend/controllers/authController.jsbackend/controllers/sessionController.jsbackend/models/User.js
| "3-Day Streak", | ||
| "7-Day Streak", | ||
| "30-Day Streak", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Keep streak badges out of the client-writable allowlist.
saveAchievements accepts every value in VALID_ACHIEVEMENTS. A client can submit "3-Day Streak", "7-Day Streak", or "30-Day Streak" without completing the required streak.
Reject server-managed streak badges in saveAchievements, or remove these identifiers from VALID_ACHIEVEMENTS. Keep sessionController as the only writer for these badges.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/constants/achievements.js` around lines 13 - 15, Remove "3-Day
Streak", "7-Day Streak", and "30-Day Streak" from VALID_ACHIEVEMENTS, or
explicitly reject them in saveAchievements, so clients cannot write
server-managed streak badges. Preserve sessionController as the only code path
that awards these identifiers.
| if (diffDays > 1) { | ||
| user.currentStreak = 0; | ||
| await user.save(); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Inspect the affected handlers and Mongoose concurrency configuration.
ast-grep outline backend/controllers/achievementController.js --items all
ast-grep outline backend/controllers/authController.js --items all
ast-grep outline backend/models/User.js --items all
# Expect no custom protection to be required after the reset becomes conditional.
rg -n -C 3 'optimisticConcurrency|versionKey|pre\(|post\(|currentStreak|lastPracticeDate' \
backend/controllers/achievementController.js \
backend/controllers/authController.js \
backend/controllers/sessionController.js \
backend/models/User.jsRepository: Canopus-Labs/PrepPilot
Length of output: 8050
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- achievement handler ---'
sed -n '1,80p' backend/controllers/achievementController.js
printf '%s\n' '--- profile handler ---'
sed -n '390,455p' backend/controllers/authController.js
printf '%s\n' '--- session write path ---'
sed -n '70,155p' backend/controllers/sessionController.js
printf '%s\n' '--- User schema ---'
sed -n '1,110p' backend/models/User.js
printf '%s\n' '--- dependency and schema configuration ---'
rg -n -C 2 '"mongoose"|optimisticConcurrency|versionKey|findOneAndUpdate|findByIdAndUpdate|save\(' \
package.json backend package-lock.json yarn.lock 2>/dev/null || trueRepository: Canopus-Labs/PrepPilot
Length of output: 35642
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- authentication middleware ---'
sed -n '1,180p' backend/middlewares/authMiddleware.js
printf '%s\n' '--- routes for the affected handlers ---'
rg -n -C 4 'getUserProfile|getAchievements|createSession|profile|achievements' \
backend/routes backend/controllers backend/middlewares
printf '%s\n' '--- relevant controller tests ---'
sed -n '1,240p' backend/tests/achievementController.unit.test.js
rg -n -C 5 'getUserProfile|currentStreak|lastPracticeDate|createSession' backend/testsRepository: Canopus-Labs/PrepPilot
Length of output: 27972
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
achievement = Path("backend/controllers/achievementController.js").read_text()
auth = Path("backend/controllers/authController.js").read_text()
session = Path("backend/controllers/sessionController.js").read_text()
user = Path("backend/models/User.js").read_text()
checks = {
"achievement reads then saves reset": re.search(
r"findById\(req\.user\._id\).*?currentStreak.*?user\.currentStreak\s*=\s*0;\s*await user\.save\(\)",
achievement, re.S
),
"profile uses middleware-loaded document": re.search(
r"const user = req\.user;.*?user\.currentStreak\s*=\s*0;\s*await user\.save\(\)",
auth, re.S
),
"session updates streak and practice date": re.search(
r"user\.currentStreak.*?user\.lastPracticeDate\s*=\s*now;.*?await user\.save",
session, re.S
),
"User schema has no optimisticConcurrency": not re.search(
r"optimisticConcurrency\s*:\s*true", user
),
}
for name, result in checks.items():
print(f"{'PASS' if result else 'FAIL'}: {name}")
if not all(checks.values()):
raise SystemExit(1)
print("Both reset handlers can issue an unconditional save after a stale read.")
print("The session path can write a new currentStreak and lastPracticeDate between that read and save.")
PYRepository: Canopus-Labs/PrepPilot
Length of output: 509
Prevent stale streak resets from overwriting new practice data.
Use an atomic conditional update in both handlers. Match the user only when lastPracticeDate is still before the UTC yesterday boundary. In getUserProfile, reload the user before res.json(user) so a skipped reset returns the current streak.
📍 Affects 2 files
backend/controllers/achievementController.js#L16-L18(this comment)backend/controllers/authController.js#L412-L414
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/controllers/achievementController.js` around lines 16 - 18, Replace
the direct streak reset and save in both achievementController.js (lines 16-18)
and authController.js (lines 412-414) with an atomic conditional update that
matches the user only when lastPracticeDate remains before the UTC yesterday
boundary. In getUserProfile, reload the user after the conditional update and
before res.json(user) so skipped resets return the latest streak.
Summary
This PR implements the Interview Streak Tracking feature.
Changes made
Fixes #625
Summary
Ready to merge. The PR adds interview practice streak tracking with milestone achievements, streak persistence, and session-flow integration.
Key changes