subscription enforced

This commit is contained in:
Yared Yemane 2026-05-27 09:56:23 -07:00
parent a1c6b3c15a
commit 2e1f9432f6
4 changed files with 29 additions and 169 deletions

View File

@ -133,33 +133,6 @@ func main() {
) )
authSvc.InitGoogleOAuth(cfg.GoogleOAuthClientID, cfg.GoogleOAuthClientSecret, cfg.GoogleOAuthRedirectURL) authSvc.InitGoogleOAuth(cfg.GoogleOAuthClientID, cfg.GoogleOAuthClientSecret, cfg.GoogleOAuthRedirectURL)
// leagueSvc := league.New(repository.NewLeagueStore(store))
// eventSvc := event.New(
// cfg.Bet365Token,
// repository.NewEventStore(store),
// repository.NewEventHistoryStore(store),
// *leagueSvc,
// settingSvc,
// domain.MongoDBLogger,
// cfg,
// )
// marketSettingRepo := repository.NewMarketSettingStore(store)
// if err := marketSettingRepo.EnsureAllMarketSettingsExist(context.Background()); err != nil {
// log.Fatalf("failed to ensure market settings: %v", err)
// }
// oddsSvc := odds.New(
// repository.NewOddStore(store),
// marketSettingRepo,
// cfg,
// eventSvc,
// logger,
// domain.MongoDBLogger,
// )
// virtuaGamesRepo := repository.NewVirtualGameRepository(store)
// Initialize producer // Initialize producer
// topic := "wallet-balance-topic" // topic := "wallet-balance-topic"
@ -174,85 +147,8 @@ func main() {
userSvc, userSvc,
) )
// / := wallet.NewService(
// repository.NewWalletStore(store),
// repository.NewTransferStore(store),
// // repository.NewDirectDepositStore(store),
// notificationSvc,
// userSvc,
// domain.MongoDBLogger,
// logger,
// )
// branchSvc := branch.NewService(repository.NewBranchStore(store))
// companySvc := company.NewService(repository.NewCompanyStore(store))
// ticketSvc := ticke.NewService(
// repository.NewTicketStore(store),
// // eventSvc,
// // *oddsSvc,
// domain.MongoDBLogger,
// settingSvc,
// )
// betSvc := bet.NewService(
// repository.NewBetStore(store),
// eventSvc,
// *oddsSvc,
// ,
// *branchSvc,
// *companySvc,
// *settingSvc,
// *userSvc,
// notificationSvc,
// logger,
// domain.MongoDBLogger,
// )
// resultSvc := result.NewService(
// repository.NewResultLogStore(store),
// cfg,
// logger,
// domain.MongoDBLogger,
// // *betSvc,
// // *oddsSvc,
// // eventSvc,
// // leagueSvc,
// notificationSvc,
// messengerSvc,
// *userSvc,
// )
// bonusSvc := bonus.NewService(
// repository.NewBonusStore(store),
// settingSvc,
// notificationSvc,
// domain.MongoDBLogger,
// )
// virtualGamesRepo := repository.NewVirtualGameRepository(store)
recommendationRepo := repository.NewRecommendationRepository(store) recommendationRepo := repository.NewRecommendationRepository(store)
// referalSvc := referralservice.New(
// repository.NewReferralStore(store),
// *settingSvc,
// cfg,
// logger,
// domain.MongoDBLogger,
// )
// raffleSvc := raffle.NewService(
// repository.NewRaffleStore(store),
// )
// virtualGameSvc := virtualgameservice.New(virtualGamesRepo,, store, cfg, logger)
// aleaService := alea.NewAleaPlayService(virtualGamesRepo,, cfg, logger)
// veliCLient := veli.NewClient(cfg)
// veliVirtualGameService := veli.New(virtualGameSvc, virtualGamesRepo, *store, veliCLient, repository.NewTransferStore(store), domain.MongoDBLogger, cfg)
// orchestrationSvc := orchestration.New(
// virtualGameSvc,
// virtualGamesRepo,
// cfg,
// veliCLient,
// )
// atlasClient := atlas.NewClient(cfg)
// atlasVirtualGameService := atlas.New(virtualGameSvc, virtualGamesRepo, atlasClient, repository.NewTransferStore(store), cfg)
recommendationSvc := recommendation.NewService(recommendationRepo) recommendationSvc := recommendation.NewService(recommendationRepo)
// chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY) // chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
@ -291,15 +187,6 @@ func main() {
// cfg, // cfg,
// ) // )
// enePulseSvc := enetpulse.New(
// *cfg,
// store,
// )
// go httpserver.StartEnetPulseCron(enePulseSvc, domain.MongoDBLogger)
// go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, orchestrationSvc, "C:/Users/User/Desktop")
// go httpserver.ProcessBetCashback(context.TODO(), betSvc)
// bankRepository := repository.NewBankRepository(store) // bankRepository := repository.NewBankRepository(store)
// instSvc := institutions.New(bankRepository) // instSvc := institutions.New(bankRepository)
// Initialize report worker with CSV exporter // Initialize report worker with CSV exporter
@ -320,11 +207,6 @@ func main() {
// userSvc, // userSvc,
// ) // )
// enetPulseSvc := enetpulse.New(
// *cfg,
// store,
// )
// Initialize wallet monitoring service // Initialize wallet monitoring service
// walletMonitorSvc := monitor.NewService( // walletMonitorSvc := monitor.NewService(
// , // ,
@ -454,11 +336,6 @@ func main() {
cfg.TeamInviteExpiry, cfg.TeamInviteExpiry,
) )
// santimpayClient := santimpay.NewSantimPayClient(cfg)
// santimpaySvc := santimpay.NewSantimPayService(santimpayClient, cfg, transferStore)
// telebirrSvc := telebirr.NewTelebirrService(cfg, transferStore)
// Activity Log service // Activity Log service
activityLogSvc := activitylogservice.NewService(store, domain.MongoDBLogger) activityLogSvc := activitylogservice.NewService(store, domain.MongoDBLogger)

View File

@ -26,6 +26,11 @@ func (r Role) UsesLMSSequentialGating() bool {
return r == RoleStudent return r == RoleStudent
} }
// RequiresSubscription is true when paid subscription is required to access learning content.
func (r Role) RequiresSubscription() bool {
return r == RoleStudent
}
// IsCustomerLearnerRole is true for platform roles that sign in as customers and consume learner-facing LMS APIs. // IsCustomerLearnerRole is true for platform roles that sign in as customers and consume learner-facing LMS APIs.
func (r Role) IsCustomerLearnerRole() bool { func (r Role) IsCustomerLearnerRole() bool {
return r == RoleStudent || r == RoleOpenLearner return r == RoleStudent || r == RoleOpenLearner

View File

@ -61,7 +61,7 @@ func (h *Handler) ListExamPrepCatalogCourses(c *fiber.Ctx) error {
offset, _ := strconv.Atoi(c.Query("offset", "0")) offset, _ := strconv.Atoi(c.Query("offset", "0"))
role, _ := c.Locals("role").(domain.Role) role, _ := c.Locals("role").(domain.Role)
if role == domain.RoleStudent || role == domain.RoleOpenLearner { if role.RequiresSubscription() {
userID, ok := c.Locals("user_id").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{

View File

@ -15,8 +15,6 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
var categorySubscriptionGateDisabled = true
func (a *App) authMiddleware(c *fiber.Ctx) error { func (a *App) authMiddleware(c *fiber.Ctx) error {
ip := c.IP() ip := c.IP()
userAgent := c.Get("User-Agent") userAgent := c.Get("User-Agent")
@ -176,8 +174,8 @@ func (a *App) OnlyAdminAndAbove(c *fiber.Ctx) error {
return c.Next() return c.Next()
} }
// RequireActiveSubscription enforces an active subscription for learner accounts. // RequireActiveSubscription enforces an active subscription for STUDENT accounts.
// Staff roles (SUPER_ADMIN, ADMIN, INSTRUCTOR, SUPPORT) bypass this check. // Staff roles and OPEN_LEARNER bypass this check.
// Use after authMiddleware on routes that deliver paid learning content. // Use after authMiddleware on routes that deliver paid learning content.
func (a *App) RequireActiveSubscription() fiber.Handler { func (a *App) RequireActiveSubscription() fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
@ -185,33 +183,27 @@ func (a *App) RequireActiveSubscription() fiber.Handler {
if !ok { if !ok {
return fiber.NewError(fiber.StatusForbidden, "Role not found in context") return fiber.NewError(fiber.StatusForbidden, "Role not found in context")
} }
switch role { if bypassSubscriptionForRole(role) || !role.RequiresSubscription() {
case domain.RoleSuperAdmin, domain.RoleAdmin, domain.RoleInstructor, domain.RoleSupport:
return c.Next()
case domain.RoleStudent, domain.RoleOpenLearner:
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized")
}
active, err := a.subscriptionsSvc.HasActiveSubscription(c.Context(), userID)
if err != nil {
a.mongoLoggerSvc.Error("subscription check failed",
zap.Int64("userID", userID),
zap.String("path", c.Path()),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify subscription")
}
if !active {
// Temporary bypass: allow unsubscribed learners to access content.
// Re-enable the previous 403 response when subscription gating is turned back on.
return c.Next()
}
return c.Next()
default:
return c.Next() return c.Next()
} }
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized")
}
active, err := a.subscriptionsSvc.HasActiveSubscription(c.Context(), userID)
if err != nil {
a.mongoLoggerSvc.Error("subscription check failed",
zap.Int64("userID", userID),
zap.String("path", c.Path()),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify subscription")
}
if !active {
return fiber.NewError(fiber.StatusForbidden, "An active subscription is required")
}
return c.Next()
} }
} }
@ -221,14 +213,7 @@ func (a *App) RequireSubscriptionCategory(category domain.SubscriptionCategory)
if err != nil { if err != nil {
return err return err
} }
if bypassSubscriptionForRole(role) { if bypassSubscriptionForRole(role) || !role.RequiresSubscription() {
return c.Next()
}
if role != domain.RoleStudent && role != domain.RoleOpenLearner {
return c.Next()
}
if categorySubscriptionGateDisabled {
// Temporary bypass to disable category-aware learner access checks without changing route wiring.
return c.Next() return c.Next()
} }
active, err := a.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, category) active, err := a.subscriptionsSvc.HasActiveSubscriptionByCategory(c.Context(), userID, category)
@ -255,14 +240,7 @@ func (a *App) RequireExamPrepSubscription() fiber.Handler {
if err != nil { if err != nil {
return err return err
} }
if bypassSubscriptionForRole(role) { if bypassSubscriptionForRole(role) || !role.RequiresSubscription() {
return c.Next()
}
if role != domain.RoleStudent && role != domain.RoleOpenLearner {
return c.Next()
}
if categorySubscriptionGateDisabled {
// Temporary bypass to disable category-aware learner access checks without changing route wiring.
return c.Next() return c.Next()
} }