336 lines
10 KiB
Go
336 lines
10 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"context"
|
|
|
|
"Yimaru-Backend/internal/domain"
|
|
lessonsvc "Yimaru-Backend/internal/services/lessons"
|
|
coursessvc "Yimaru-Backend/internal/services/courses"
|
|
modulesvc "Yimaru-Backend/internal/services/modules"
|
|
programssvc "Yimaru-Backend/internal/services/programs"
|
|
practicessvc "Yimaru-Backend/internal/services/practices"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func (a *App) RequireLMSSubscriptionUnlessFree() fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
role, userID, err := subscriptionScopedUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bypassSubscriptionForRole(role) {
|
|
return c.Next()
|
|
}
|
|
if role != domain.RoleStudent && role != domain.RoleOpenLearner {
|
|
return c.Next()
|
|
}
|
|
if domain.CategorySubscriptionGateDisabled {
|
|
return c.Next()
|
|
}
|
|
|
|
tier, resolved, err := a.resolveLMSEffectiveAccessTier(c)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, programssvc.ErrProgramNotFound),
|
|
errors.Is(err, coursessvc.ErrCourseNotFound),
|
|
errors.Is(err, modulesvc.ErrModuleNotFound),
|
|
errors.Is(err, lessonsvc.ErrLessonNotFound),
|
|
errors.Is(err, practicessvc.ErrPracticeNotFound):
|
|
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
|
default:
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify content access")
|
|
}
|
|
}
|
|
if !resolved || !tier.RequiresSubscription() {
|
|
return c.Next()
|
|
}
|
|
|
|
active, err := a.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, domain.SubscriptionCategoryLearnEnglish)
|
|
if err != nil {
|
|
a.mongoLoggerSvc.Error("category subscription check failed",
|
|
zap.Int64("userID", userID),
|
|
zap.String("category", string(domain.SubscriptionCategoryLearnEnglish)),
|
|
zap.String("path", c.Path()),
|
|
zap.Error(err),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify subscription")
|
|
}
|
|
if !active {
|
|
return fiber.NewError(fiber.StatusForbidden, fmt.Sprintf("An active %s subscription is required", humanizeSubscriptionCategory(domain.SubscriptionCategoryLearnEnglish)))
|
|
}
|
|
return c.Next()
|
|
}
|
|
}
|
|
|
|
func (a *App) resolveLMSEffectiveAccessTier(c *fiber.Ctx) (domain.ContentAccessTier, bool, error) {
|
|
ctx := c.Context()
|
|
routePath := c.Route().Path
|
|
|
|
if strings.Contains(routePath, "/practices/:practiceId/questions") || strings.Contains(routePath, "/practices/:id") {
|
|
practiceID, ok, err := parseRouteInt64(c, "practiceId")
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
if !ok {
|
|
practiceID, ok, err = parseRouteInt64(c, "id")
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
if !ok {
|
|
goto unresolved
|
|
}
|
|
}
|
|
return a.lmsEffectiveTierForPractice(ctx, practiceID)
|
|
}
|
|
|
|
if lessonID, ok, err := parseRouteInt64(c, "lessonId"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.lmsEffectiveTierForLesson(ctx, lessonID)
|
|
}
|
|
|
|
if moduleID, ok, err := parseRouteInt64(c, "moduleId"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.lmsEffectiveTierForModule(ctx, moduleID)
|
|
}
|
|
|
|
if _, ok, err := parseRouteInt64(c, "courseId"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return "", false, nil
|
|
}
|
|
|
|
switch {
|
|
case strings.Contains(routePath, "/lessons/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.lmsEffectiveTierForLesson(ctx, id)
|
|
}
|
|
case strings.Contains(routePath, "/modules/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.lmsEffectiveTierForModule(ctx, id)
|
|
}
|
|
case strings.Contains(routePath, "/courses/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.lmsEffectiveTierForCourse(ctx, id)
|
|
}
|
|
case strings.Contains(routePath, "/programs/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.lmsEffectiveTierForProgram(ctx, id)
|
|
}
|
|
}
|
|
|
|
unresolved:
|
|
return "", false, nil
|
|
}
|
|
|
|
func (a *App) lmsEffectiveTierForProgram(ctx context.Context, programID int64) (domain.ContentAccessTier, bool, error) {
|
|
program, err := a.programSvc.GetByID(ctx, programID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return program.AccessTier, true, nil
|
|
}
|
|
|
|
func (a *App) lmsEffectiveTierForCourse(ctx context.Context, courseID int64) (domain.ContentAccessTier, bool, error) {
|
|
course, err := a.courseSvc.GetByID(ctx, courseID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
program, err := a.programSvc.GetByID(ctx, course.ProgramID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier), true, nil
|
|
}
|
|
|
|
func (a *App) lmsEffectiveTierForModule(ctx context.Context, moduleID int64) (domain.ContentAccessTier, bool, error) {
|
|
module, err := a.moduleSvc.GetByID(ctx, moduleID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
course, err := a.courseSvc.GetByID(ctx, module.CourseID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
program, err := a.programSvc.GetByID(ctx, course.ProgramID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier, module.AccessTier), true, nil
|
|
}
|
|
|
|
func (a *App) lmsEffectiveTierForLesson(ctx context.Context, lessonID int64) (domain.ContentAccessTier, bool, error) {
|
|
lesson, err := a.lessonSvc.GetByID(ctx, lessonID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
module, err := a.moduleSvc.GetByID(ctx, lesson.ModuleID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
course, err := a.courseSvc.GetByID(ctx, module.CourseID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
program, err := a.programSvc.GetByID(ctx, course.ProgramID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return domain.EffectiveContentAccessTier(program.AccessTier, course.AccessTier, module.AccessTier, lesson.AccessTier), true, nil
|
|
}
|
|
|
|
func (a *App) resolveExamPrepEffectiveAccessTier(c *fiber.Ctx) (domain.ContentAccessTier, bool, error) {
|
|
ctx := c.Context()
|
|
routePath := c.Route().Path
|
|
|
|
if _, ok, err := parseRouteInt64(c, "catalogCourseId"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return "", false, nil
|
|
}
|
|
if _, ok, err := parseRouteInt64(c, "unitId"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return "", false, nil
|
|
}
|
|
if _, ok, err := parseRouteInt64(c, "moduleId"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return "", false, nil
|
|
}
|
|
if _, ok, err := parseRouteInt64(c, "lessonId"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return "", false, nil
|
|
}
|
|
|
|
switch {
|
|
case strings.Contains(routePath, "/catalog-courses/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.examPrepEffectiveTierForCatalogCourse(ctx, id)
|
|
}
|
|
case strings.Contains(routePath, "/units/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.examPrepEffectiveTierForUnit(ctx, id)
|
|
}
|
|
case strings.Contains(routePath, "/modules/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.examPrepEffectiveTierForModule(ctx, id)
|
|
}
|
|
case strings.Contains(routePath, "/lessons/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.examPrepEffectiveTierForLesson(ctx, id)
|
|
}
|
|
case strings.Contains(routePath, "/practices/:id"):
|
|
if id, ok, err := parseRouteInt64(c, "id"); err != nil {
|
|
return "", false, err
|
|
} else if ok {
|
|
return a.examPrepEffectiveTierForPractice(ctx, id)
|
|
}
|
|
}
|
|
return "", false, nil
|
|
}
|
|
|
|
func (a *App) examPrepEffectiveTierForCatalogCourse(ctx context.Context, catalogCourseID int64) (domain.ContentAccessTier, bool, error) {
|
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, catalogCourseID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return cc.AccessTier, true, nil
|
|
}
|
|
|
|
func (a *App) examPrepEffectiveTierForUnit(ctx context.Context, unitID int64) (domain.ContentAccessTier, bool, error) {
|
|
unit, err := a.examPrepSvc.GetUnitByID(ctx, unitID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, unit.CatalogCourseID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return domain.EffectiveContentAccessTier(cc.AccessTier, unit.AccessTier), true, nil
|
|
}
|
|
|
|
func (a *App) examPrepEffectiveTierForModule(ctx context.Context, moduleID int64) (domain.ContentAccessTier, bool, error) {
|
|
module, err := a.examPrepSvc.GetModuleByID(ctx, moduleID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
unit, err := a.examPrepSvc.GetUnitByID(ctx, module.UnitID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, unit.CatalogCourseID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return domain.EffectiveContentAccessTier(cc.AccessTier, unit.AccessTier, module.AccessTier), true, nil
|
|
}
|
|
|
|
func (a *App) examPrepEffectiveTierForLesson(ctx context.Context, lessonID int64) (domain.ContentAccessTier, bool, error) {
|
|
lesson, err := a.examPrepSvc.GetLessonByID(ctx, lessonID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
module, err := a.examPrepSvc.GetModuleByID(ctx, lesson.UnitModuleID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
unit, err := a.examPrepSvc.GetUnitByID(ctx, module.UnitID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
cc, err := a.examPrepSvc.GetCatalogCourseByID(ctx, unit.CatalogCourseID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return domain.EffectiveContentAccessTier(cc.AccessTier, unit.AccessTier, module.AccessTier, lesson.AccessTier), true, nil
|
|
}
|
|
|
|
func (a *App) examPrepEffectiveTierForPractice(ctx context.Context, practiceID int64) (domain.ContentAccessTier, bool, error) {
|
|
practice, err := a.examPrepSvc.GetExamPrepPracticeByID(ctx, practiceID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return a.examPrepEffectiveTierForLesson(ctx, practice.LessonID)
|
|
}
|
|
|
|
func (a *App) lmsEffectiveTierForPractice(ctx context.Context, practiceID int64) (domain.ContentAccessTier, bool, error) {
|
|
practice, err := a.practiceSvc.GetByID(ctx, practiceID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
switch practice.ParentKind {
|
|
case domain.ParentKindLesson:
|
|
return a.lmsEffectiveTierForLesson(ctx, practice.ParentID)
|
|
case domain.ParentKindModule:
|
|
return a.lmsEffectiveTierForModule(ctx, practice.ParentID)
|
|
case domain.ParentKindCourse:
|
|
return a.lmsEffectiveTierForCourse(ctx, practice.ParentID)
|
|
default:
|
|
return "", false, nil
|
|
}
|
|
}
|