diff --git a/internal/domain/lms_access.go b/internal/domain/lms_access.go index 35d2035..888d053 100644 --- a/internal/domain/lms_access.go +++ b/internal/domain/lms_access.go @@ -1,7 +1,8 @@ package domain // LMSEntityAccess describes learner gating for a program, course, module, or lesson. -// It is omitted (nil) for non-learner roles in API responses. +// Included for STUDENT and OPEN_LEARNER; omitted (nil) for staff roles in API responses. +// OPEN_LEARNER always has is_accessible true; STUDENT may be false when prerequisites are unmet. // Progress fields count completed lessons vs total lessons in that entity’s scope (lesson: 0 or 1 of 1). type LMSEntityAccess struct { IsAccessible bool `json:"is_accessible"` diff --git a/internal/services/lmsprogress/service.go b/internal/services/lmsprogress/service.go index 9db1ea6..c41f7bc 100644 --- a/internal/services/lmsprogress/service.go +++ b/internal/services/lmsprogress/service.go @@ -144,16 +144,13 @@ func (s *Service) CanAccessLesson(ctx context.Context, userID, lessonID int64) ( return true, "", nil } -// ApplyAccessProgram sets p.Access for a learner. Non-learners: clears Access to omit from JSON. +// ApplyAccessProgram sets p.Access for learner roles. Staff roles omit Access from JSON. +// STUDENT: is_accessible reflects sequential prerequisites; OPEN_LEARNER: always true. func (s *Service) ApplyAccessProgram(ctx context.Context, role domain.Role, userID int64, p *domain.Program) error { - if !role.UsesLMSSequentialGating() { + if !role.IsCustomerLearnerRole() { p.Access = nil return nil } - ok, reason, err := s.CanAccessProgram(ctx, userID, p.ID) - if err != nil { - return err - } done, err := s.store.LmsUserHasProgramProgress(ctx, userID, p.ID) if err != nil { return err @@ -162,24 +159,23 @@ func (s *Service) ApplyAccessProgram(ctx context.Context, role domain.Role, user if err != nil { return err } - c, t, pct := lmsProgressCounts(comp, tot, done) - p.Access = &domain.LMSEntityAccess{ - IsAccessible: ok, IsCompleted: done, Reason: reasonIf(ok, reason), - CompletedCount: c, TotalCount: t, ProgressPercent: pct, + ok, reason := true, "" + if role.UsesLMSSequentialGating() { + ok, reason, err = s.CanAccessProgram(ctx, userID, p.ID) + if err != nil { + return err + } } + p.Access = buildLMSEntityAccess(ok, reason, done, comp, tot) return nil } -// ApplyAccessCourse sets c.Access for a learner. +// ApplyAccessCourse sets c.Access for learner roles. func (s *Service) ApplyAccessCourse(ctx context.Context, role domain.Role, userID int64, c *domain.Course) error { - if !role.UsesLMSSequentialGating() { + if !role.IsCustomerLearnerRole() { c.Access = nil return nil } - ok, reason, err := s.CanAccessCourse(ctx, userID, c.ID) - if err != nil { - return err - } done, err := s.store.LmsUserHasCourseProgress(ctx, userID, c.ID) if err != nil { return err @@ -188,24 +184,23 @@ func (s *Service) ApplyAccessCourse(ctx context.Context, role domain.Role, userI if err != nil { return err } - cc, tt, pct := lmsProgressCounts(comp, tot, done) - c.Access = &domain.LMSEntityAccess{ - IsAccessible: ok, IsCompleted: done, Reason: reasonIf(ok, reason), - CompletedCount: cc, TotalCount: tt, ProgressPercent: pct, + ok, reason := true, "" + if role.UsesLMSSequentialGating() { + ok, reason, err = s.CanAccessCourse(ctx, userID, c.ID) + if err != nil { + return err + } } + c.Access = buildLMSEntityAccess(ok, reason, done, comp, tot) return nil } -// ApplyAccessModule sets m.Access for a learner. +// ApplyAccessModule sets m.Access for learner roles. func (s *Service) ApplyAccessModule(ctx context.Context, role domain.Role, userID int64, m *domain.Module) error { - if !role.UsesLMSSequentialGating() { + if !role.IsCustomerLearnerRole() { m.Access = nil return nil } - ok, reason, err := s.CanAccessModule(ctx, userID, m.ID) - if err != nil { - return err - } done, err := s.store.LmsUserHasModuleProgress(ctx, userID, m.ID) if err != nil { return err @@ -214,24 +209,23 @@ func (s *Service) ApplyAccessModule(ctx context.Context, role domain.Role, userI if err != nil { return err } - cc, tt, pct := lmsProgressCounts(comp, tot, done) - m.Access = &domain.LMSEntityAccess{ - IsAccessible: ok, IsCompleted: done, Reason: reasonIf(ok, reason), - CompletedCount: cc, TotalCount: tt, ProgressPercent: pct, + ok, reason := true, "" + if role.UsesLMSSequentialGating() { + ok, reason, err = s.CanAccessModule(ctx, userID, m.ID) + if err != nil { + return err + } } + m.Access = buildLMSEntityAccess(ok, reason, done, comp, tot) return nil } -// ApplyAccessLesson sets l.Access for a learner. +// ApplyAccessLesson sets l.Access for learner roles. func (s *Service) ApplyAccessLesson(ctx context.Context, role domain.Role, userID int64, les *domain.Lesson) error { - if !role.UsesLMSSequentialGating() { + if !role.IsCustomerLearnerRole() { les.Access = nil return nil } - ok, reason, err := s.CanAccessLesson(ctx, userID, les.ID) - if err != nil { - return err - } done, err := s.store.LmsUserHasLessonProgress(ctx, userID, les.ID) if err != nil { return err @@ -242,14 +236,29 @@ func (s *Service) ApplyAccessLesson(ctx context.Context, role domain.Role, userI } else { comp, tot = 0, 1 } - c, t, pct := lmsProgressCounts(comp, tot, done) - les.Access = &domain.LMSEntityAccess{ - IsAccessible: ok, IsCompleted: done, Reason: reasonIf(ok, reason), - CompletedCount: c, TotalCount: t, ProgressPercent: pct, + ok, reason := true, "" + if role.UsesLMSSequentialGating() { + ok, reason, err = s.CanAccessLesson(ctx, userID, les.ID) + if err != nil { + return err + } } + les.Access = buildLMSEntityAccess(ok, reason, done, comp, tot) return nil } +func buildLMSEntityAccess(ok bool, reason string, done bool, completed, total int32) *domain.LMSEntityAccess { + c, t, pct := lmsProgressCounts(completed, total, done) + return &domain.LMSEntityAccess{ + IsAccessible: ok, + IsCompleted: done, + Reason: reasonIf(ok, reason), + CompletedCount: c, + TotalCount: t, + ProgressPercent: pct, + } +} + // lmsProgressCounts maps DB lesson completion counts to UI fields. Percent is 0–100; completed // and total are aligned with isCompleted when the entity is fully done. func lmsProgressCounts(completed, total int32, isCompleted bool) (c, t, pct int) {