Update lesson and practice completion flows to cascade module/course/program progress only when both lesson completion and related published practice completion criteria are met, and align progress counters with the new rule. Made-with: Cursor
193 lines
5.2 KiB
Go
193 lines
5.2 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
dbgen "Yimaru-Backend/gen/db"
|
|
)
|
|
|
|
// CompleteLessonForUser records lesson completion and cascades completion upward when
|
|
// both lesson and related practice requirements are satisfied.
|
|
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 completion upward when
|
|
// both lesson and related practice requirements are satisfied.
|
|
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
|
|
}
|
|
if !scope.ModuleID.Valid {
|
|
return fmt.Errorf("practice %d is not linked to a module", questionSetID)
|
|
}
|
|
|
|
mod, err := q.GetModuleByID(ctx, scope.ModuleID.Int64)
|
|
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
|
|
}
|
|
|
|
func (s *Store) cascadeLMSCompletion(ctx context.Context, q *dbgen.Queries, userID, moduleID, courseID, programID int64) error {
|
|
moduleLessonsTotal, err := q.CountLessonsInModule(ctx, moduleID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
moduleLessonsDone, err := q.CountUserCompletedLessonsInModule(ctx, dbgen.CountUserCompletedLessonsInModuleParams{
|
|
ModuleID: moduleID,
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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
|
|
}
|
|
|
|
moduleLessonsComplete := moduleLessonsTotal > 0 && moduleLessonsDone >= moduleLessonsTotal
|
|
modulePracticesComplete := modulePracticesDone >= modulePracticesTotal
|
|
if !moduleLessonsComplete || !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 := 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 := programPracticesDone >= programPracticesTotal
|
|
if !programCoursesComplete || !programPracticesComplete {
|
|
return nil
|
|
}
|
|
|
|
if err := q.InsertUserProgramProgress(ctx, dbgen.InsertUserProgramProgressParams{UserID: userID, ProgramID: programID}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|