package main import ( "context" "log" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" appsession "knowfoolery/backend/services/game-session-service/internal/application/session" qbclient "knowfoolery/backend/services/game-session-service/internal/infra/clients/questionbank" userclient "knowfoolery/backend/services/game-session-service/internal/infra/clients/usersvc" gsconfig "knowfoolery/backend/services/game-session-service/internal/infra/config" gsent "knowfoolery/backend/services/game-session-service/internal/infra/persistence/ent" gsstate "knowfoolery/backend/services/game-session-service/internal/infra/state" httpapi "knowfoolery/backend/services/game-session-service/internal/interfaces/http" "knowfoolery/backend/shared/infra/auth/zitadel" sharedredis "knowfoolery/backend/shared/infra/database/redis" "knowfoolery/backend/shared/infra/observability/logging" sharedmetrics "knowfoolery/backend/shared/infra/observability/metrics" "knowfoolery/backend/shared/infra/observability/tracing" "knowfoolery/backend/shared/infra/utils/serviceboot" "knowfoolery/backend/shared/infra/utils/validation" ) func main() { serviceCfg := gsconfig.FromEnv() logger := logging.NewLogger(serviceCfg.Logging) metrics := sharedmetrics.NewMetrics(serviceCfg.Metrics) tracer, err := tracing.NewTracer(serviceCfg.Tracing) if err != nil { logger.Fatal("failed to initialize tracer") } defer func() { _ = tracer.Shutdown(context.Background()) }() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() persistence, err := gsent.NewClient(ctx, serviceCfg.Postgres) if err != nil { logger.WithError(err).Fatal("failed to initialize postgres client") } defer persistence.Close() repo := gsent.NewSessionRepository(persistence) if err := repo.EnsureSchema(ctx); err != nil { logger.WithError(err).Fatal("failed to ensure schema") } var redisClient *sharedredis.Client if c, redisErr := sharedredis.NewClient(serviceCfg.Redis); redisErr == nil { redisClient = c defer func() { _ = redisClient.Close() }() } else { logger.WithError(redisErr).Warn("redis unavailable; running with postgres-backed flow") } qb := qbclient.NewClient(serviceCfg.QuestionBankBaseURL, serviceCfg.UpstreamTimeout) users := userclient.NewClient(serviceCfg.UserServiceBaseURL, serviceCfg.UpstreamTimeout) stateStore := gsstate.NewStore(redisClient) service := appsession.NewService(repo, qb, users, stateStore, appsession.Config{ SessionDuration: serviceCfg.SessionDuration, MaxAttempts: serviceCfg.MaxAttempts, MinAnswerLatencyMs: serviceCfg.MinAnswerLatencyMs, LockTTL: serviceCfg.LockTTL, ActiveSessionKeyTTL: serviceCfg.ActiveKeyTTL, EndReasonDefault: serviceCfg.EndReasonDefault, }) handler := httpapi.NewHandler(service, validation.NewValidator(), logger, metrics) bootCfg := serviceboot.Config{ AppName: serviceCfg.AppName, ServiceSlug: "game-session", PortEnv: "GAME_SESSION_PORT", DefaultPort: serviceCfg.Port, } app := serviceboot.NewFiberApp(bootCfg) serviceboot.RegisterHealth(app, bootCfg.ServiceSlug) serviceboot.RegisterReadiness( app, 2*time.Second, serviceboot.ReadyCheck{ Name: "postgres", Required: true, Probe: persistence.Pool.Ping, }, serviceboot.ReadyCheck{ Name: "redis", Required: false, Probe: func(ctx context.Context) error { if redisClient == nil { return nil } return redisClient.HealthCheck(ctx) }, }, ) app.Get("/metrics", adaptor.HTTPHandler(sharedmetrics.Handler())) authMiddleware := buildAuthMiddleware(serviceCfg) httpapi.RegisterRoutes(app, handler, authMiddleware) addr := serviceboot.ListenAddress(bootCfg.PortEnv, bootCfg.DefaultPort) log.Fatal(serviceboot.Run(app, addr)) } func buildAuthMiddleware(cfg gsconfig.Config) fiber.Handler { return zitadel.BuildJWTMiddleware(zitadel.MiddlewareFactoryConfig{ BaseURL: cfg.ZitadelBaseURL, ClientID: cfg.ZitadelClientID, ClientSecret: cfg.ZitadelSecret, Issuer: cfg.ZitadelIssuer, Audience: cfg.ZitadelAudience, RequiredClaims: []string{ "sub", }, Timeout: 10 * time.Second, }) }