feat: add batch process for updating status of classes and enrolled_at per ticket 607#1164
feat: add batch process for updating status of classes and enrolled_at per ticket 607#1164carddev81 wants to merge 1 commit into
Conversation
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughThis PR adds a system-wide scheduled job that activates program classes whose start date arrives and backfills enrollment data. It introduces a new ChangesScheduled Program Class Activation
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@provider-middleware/program_classes.go`:
- Around line 62-68: The Set("class_ids", classIDs) call must remain because
models.ProgramClass.AfterUpdate reads tx.Get("class_ids") to drive enrollment
updates; remove or change the redundant ProgramClassEnrollment update in
provider-middleware/program_classes.go (the explicit update that backfills
enrolled_at and update_user_id after the Transaction) so it does not duplicate
the AfterUpdate hook's backfill for enrolled_at, and instead rely on the hook to
set enrolled_at for enrollment_status = Enrolled where enrolled_at IS NULL; if
you still need to update update_user_id for those rows, modify the hook
(models.ProgramClass.AfterUpdate) to also set update_user_id when it backfills
enrolled_at so the single path performs both updates atomically.
🪄 Autofix (Beta)
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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c211ba7a-654f-4445-9d96-06abadcc7bf9
📒 Files selected for processing (4)
backend/src/models/jobs.gobackend/src/tasks/scheduler.goprovider-middleware/handlers.goprovider-middleware/program_classes.go
| if err := sh.db.WithContext(batchCtx).Transaction(func(tx *gorm.DB) error { | ||
| if err := tx. | ||
| Model(&models.ProgramClass{}). | ||
| Where("id IN ?", classIDs). | ||
| Set("class_ids", classIDs). | ||
| Updates(map[string]any{"status": models.Active}).Error; err != nil { | ||
| return err |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Search for ProgramClass AfterUpdate hook that might backfill enrolled_at
rg -n -C5 'func.*ProgramClass.*AfterUpdate' --type goRepository: UnlockedLabs/UnlockEdv2
Length of output: 860
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Find where Set("class_ids", ...) is used
rg -n --hidden --no-ignore -S 'Set\("class_ids"\s*,\s*' .
# 2) Locate provider-middleware/program_classes.go if present and show around the targeted lines
# (Prefer exact filename match; otherwise search for the transaction pattern)
if [ -f "provider-middleware/program_classes.go" ]; then
echo "---- provider-middleware/program_classes.go ----"
nl -ba provider-middleware/program_classes.go | sed -n '40,100p'
else
echo "---- searching for provider-middleware/program_classes.go ----"
fd -t f -e go --hidden --no-ignore 'program_classes.go' -a . | head -n 50 || true
# fallback: search for the transaction snippet structure
rg -n --hidden --no-ignore -S 'WithContext\(batchCtx\)\.Transaction' . || true
fi
# 3) Show full AfterUpdate hook body (limited range around discovered location)
HOOK_FILE="backend/src/models/program_classes.go"
if [ -f "$HOOK_FILE" ]; then
echo "---- $HOOK_FILE: AfterUpdate ----"
# print from ~60 lines before hook to ~120 lines after (cap output)
nl -ba "$HOOK_FILE" | sed -n '50,170p'
fiRepository: UnlockedLabs/UnlockEdv2
Length of output: 331
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Print with line numbers using `cat -n` (no `nl` available in sandbox)
print_range() {
local file="$1"
local start="$2"
local end="$3"
echo "---- $file:$start-$end ----"
cat -n "$file" | sed -n "${start},${end}p"
}
# provider-middleware: inspect around the Set("class_ids", ...)
print_range "provider-middleware/program_classes.go" 40 110
# model hook: inspect full AfterUpdate body
print_range "backend/src/models/program_classes.go" 60 170
# database/enrollments: context for another Set("class_ids", ...)
if [ -f "backend/src/database/class_enrollments.go" ]; then
print_range "backend/src/database/class_enrollments.go" 230 330
fiRepository: UnlockedLabs/UnlockEdv2
Length of output: 10652
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find DatabaseFields definition and any hooks/callbacks that might auto-set update_user_id/update_user_id behavior
rg -n --hidden --no-ignore -S 'type DatabaseFields|func \\(.*DatabaseFields\\).*Before|func \\(.*DatabaseFields\\).*After|UpdateUserID|update_user_id' backend/src/models
# Find any hooks specifically on ProgramClassEnrollment for BeforeUpdate/AfterUpdate
rg -n --hidden --no-ignore -S 'type ProgramClassEnrollment|func \\(e \\*ProgramClassEnrollment\\) (BeforeUpdate|AfterUpdate|BeforeSave|AfterSave|BeforeCreate|AfterCreate)' backend/src/modelsRepository: UnlockedLabs/UnlockEdv2
Length of output: 1107
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show DatabaseFields implementation around line ~1-120
echo "---- backend/src/models/users.go: DatabaseFields ----"
cat -n backend/src/models/users.go | sed -n '1,130p'
# Search for ProgramClassEnrollment hooks beyond the one we saw (BeforeCreate)
echo "---- ProgramClassEnrollment hooks ----"
rg -n --hidden --no-ignore -S 'func \\(e \\*ProgramClassEnrollment\\) (BeforeUpdate|AfterUpdate|BeforeSave|AfterSave|BeforeCreate|AfterCreate|BeforeUpdate)' backend/src/models/program_classes.go || true
# Show ProgramClassEnrollment hook body section we already partially saw (extend range)
echo "---- backend/src/models/program_classes.go: ProgramClassEnrollment hooks ----"
cat -n backend/src/models/program_classes.go | sed -n '120,220p'Repository: UnlockedLabs/UnlockEdv2
Length of output: 9199
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Print continuation of ProgramClassEnrollment.BeforeUpdate
cat -n backend/src/models/program_classes.go | sed -n '160,320p'Repository: UnlockedLabs/UnlockEdv2
Length of output: 7052
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Print continuation of ProgramClassEnrollment.BeforeUpdate
cat -n backend/src/models/program_classes.go | sed -n '160,320p'Repository: UnlockedLabs/UnlockEdv2
Length of output: 7052
Clarify Set("class_ids", classIDs) purpose and avoid overlapping enrolled_at backfill
Set("class_ids", classIDs)is required:models.ProgramClass.AfterUpdatecallstx.Get("class_ids")and uses it to updateProgramClassEnrollmentrows whenstatuschanges toActive.- The
AfterUpdatehook already backfillsProgramClassEnrollment.enrolled_atforenrollment_status = Enrolledwhereenrolled_at IS NULL, so the subsequent explicitProgramClassEnrollmentupdate inprovider-middleware/program_classes.go(lines 70–78) is redundant forenrolled_atand may also skip settingupdate_user_iddue to theenrolled_at IS NULLpredicate running after the hook.
🤖 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 `@provider-middleware/program_classes.go` around lines 62 - 68, The
Set("class_ids", classIDs) call must remain because
models.ProgramClass.AfterUpdate reads tx.Get("class_ids") to drive enrollment
updates; remove or change the redundant ProgramClassEnrollment update in
provider-middleware/program_classes.go (the explicit update that backfills
enrolled_at and update_user_id after the Transaction) so it does not duplicate
the AfterUpdate hook's backfill for enrolled_at, and instead rely on the hook to
set enrolled_at for enrollment_status = Enrolled where enrolled_at IS NULL; if
you still need to update update_user_id for those rows, modify the hook
(models.ProgramClass.AfterUpdate) to also set update_user_id when it backfills
enrolled_at so the single path performs both updates atomically.
Pre-Submission PR Checklist
Description of the change
Added batch process that runs at 5AM every morning that will flip a classes status from Scheduled to Active when the class start date has either passed or is the current day of the run also the job searches for program_class_enrollments within that class and will set the enrolled_at to the current day of the run.
NOTE: this is an easy way to run the job, you just need to obtain the job id from the crons table.
nats pub --server=nats://127.0.0.1:4222 --user=unlocked --password=dev tasks.activate_scheduled_classes '{"job_id":"626fc009-47ad-48f2-8222-fed55ea523b7"}'
To test please create a class set to Scheduled and start date is yesterday or some day in the past. Enroll students. Then kick off job.