Remove lesson completion from learner progress percentages, access completion snapshots, and LMS rollups while keeping generated SQLC and Swagger artifacts in sync. Co-authored-by: Cursor <cursoragent@cursor.com>
205 lines
5.4 KiB
Go
205 lines
5.4 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
dbgen "Yimaru-Backend/gen/db"
|
|
)
|
|
|
|
// CompleteLessonForUser records lesson completion for sequential lesson gating and
|
|
// re-evaluates higher-level practice-based rollups.
|
|
func (s *Store) CompleteLessonForUser(ctx context.Context, userID, lessonID int64) error {
|
|
q, tx, err := s.BeginTx(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("begin tx: %w", err)
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
|
|
if err := q.InsertUserLessonProgress(ctx, dbgen.InsertUserLessonProgressParams{UserID: userID, LessonID: lessonID}); err != nil {
|
|
return err
|
|
}
|
|
lesson, err := q.GetLessonByID(ctx, lessonID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mod, err := q.GetModuleByID(ctx, lesson.ModuleID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
crs, err := q.GetCourseByID(ctx, mod.CourseID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := s.cascadeLMSCompletion(ctx, q, userID, &mod.ID, crs.ID, crs.ProgramID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return fmt.Errorf("commit: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CompletePracticeForUser records practice completion and cascades practice-based
|
|
// completion upward when all published practices in scope are complete.
|
|
func (s *Store) CompletePracticeForUser(ctx context.Context, userID, questionSetID int64) error {
|
|
q, tx, err := s.BeginTx(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("begin tx: %w", err)
|
|
}
|
|
defer func() { _ = tx.Rollback(ctx) }()
|
|
|
|
if _, err := q.MarkPracticeCompleted(ctx, dbgen.MarkPracticeCompletedParams{
|
|
UserID: userID,
|
|
QuestionSetID: questionSetID,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
scope, err := q.GetPracticeScopeByQuestionSetID(ctx, questionSetID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
moduleID *int64
|
|
courseID int64
|
|
)
|
|
switch {
|
|
case scope.ModuleID.Valid:
|
|
mid := scope.ModuleID.Int64
|
|
moduleID = &mid
|
|
mod, err := q.GetModuleByID(ctx, mid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
courseID = mod.CourseID
|
|
case scope.LessonID.Valid:
|
|
lesson, err := q.GetLessonByID(ctx, scope.LessonID.Int64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mid := lesson.ModuleID
|
|
moduleID = &mid
|
|
mod, err := q.GetModuleByID(ctx, mid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
courseID = mod.CourseID
|
|
case scope.CourseID.Valid:
|
|
courseID = scope.CourseID.Int64
|
|
default:
|
|
return fmt.Errorf("practice %d is not linked to lesson/module/course", questionSetID)
|
|
}
|
|
|
|
crs, err := q.GetCourseByID(ctx, courseID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := s.cascadeLMSCompletion(ctx, q, userID, moduleID, crs.ID, crs.ProgramID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return fmt.Errorf("commit: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) cascadeLMSCompletion(ctx context.Context, q *dbgen.Queries, userID int64, moduleID *int64, courseID, programID int64) error {
|
|
if moduleID != nil {
|
|
modulePracticesTotal, err := q.CountPublishedPracticesInModule(ctx, toPgInt8(moduleID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
modulePracticesDone, err := q.CountUserCompletedPublishedPracticesInModule(ctx, dbgen.CountUserCompletedPublishedPracticesInModuleParams{
|
|
ModuleID: toPgInt8(moduleID),
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
modulePracticesComplete := modulePracticesTotal > 0 && modulePracticesDone >= modulePracticesTotal
|
|
if !modulePracticesComplete {
|
|
return nil
|
|
}
|
|
|
|
if err := q.InsertUserModuleProgress(ctx, dbgen.InsertUserModuleProgressParams{UserID: userID, ModuleID: *moduleID}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
nMods, err := q.CountModulesInCourse(ctx, courseID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nDoneMods, err := q.CountUserCompletedModulesInCourse(ctx, dbgen.CountUserCompletedModulesInCourseParams{
|
|
CourseID: courseID,
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
coursePracticesTotal, err := q.CountPublishedPracticesInCourse(ctx, toPgInt8(&courseID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
coursePracticesDone, err := q.CountUserCompletedPublishedPracticesInCourse(ctx, dbgen.CountUserCompletedPublishedPracticesInCourseParams{
|
|
CourseID: toPgInt8(&courseID),
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
courseModulesComplete := nMods > 0 && nDoneMods >= nMods
|
|
coursePracticesComplete := coursePracticesTotal > 0 && coursePracticesDone >= coursePracticesTotal
|
|
if !courseModulesComplete || !coursePracticesComplete {
|
|
return nil
|
|
}
|
|
|
|
if err := q.InsertUserCourseProgress(ctx, dbgen.InsertUserCourseProgressParams{UserID: userID, CourseID: courseID}); err != nil {
|
|
return err
|
|
}
|
|
|
|
nCr, err := q.CountCoursesInProgram(ctx, programID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nCrDone, err := q.CountUserCompletedCoursesInProgram(ctx, dbgen.CountUserCompletedCoursesInProgramParams{
|
|
ProgramID: programID,
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
programPracticesTotal, err := q.CountPublishedPracticesInProgram(ctx, programID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
programPracticesDone, err := q.CountUserCompletedPublishedPracticesInProgram(ctx, dbgen.CountUserCompletedPublishedPracticesInProgramParams{
|
|
ProgramID: programID,
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
programCoursesComplete := nCr > 0 && nCrDone >= nCr
|
|
programPracticesComplete := programPracticesTotal > 0 && programPracticesDone >= programPracticesTotal
|
|
if !programCoursesComplete || !programPracticesComplete {
|
|
return nil
|
|
}
|
|
|
|
if err := q.InsertUserProgramProgress(ctx, dbgen.InsertUserProgramProgressParams{UserID: userID, ProgramID: programID}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|