You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
4.1 KiB
Go
133 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/gofiber/fiber/v3/middleware/adaptor"
|
|
|
|
appq "knowfoolery/backend/services/question-bank-service/internal/application/question"
|
|
qcache "knowfoolery/backend/services/question-bank-service/internal/infra/cache"
|
|
qbconfig "knowfoolery/backend/services/question-bank-service/internal/infra/config"
|
|
qent "knowfoolery/backend/services/question-bank-service/internal/infra/persistence/ent"
|
|
httpapi "knowfoolery/backend/services/question-bank-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() {
|
|
cfg := qbconfig.FromEnv()
|
|
|
|
logger := logging.NewLogger(cfg.Logging)
|
|
metrics := sharedmetrics.NewMetrics(cfg.Metrics)
|
|
|
|
tracer, err := tracing.NewTracer(cfg.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 := qent.NewClient(ctx, cfg.Postgres)
|
|
if err != nil {
|
|
logger.WithError(err).Fatal("failed to initialize postgres client")
|
|
}
|
|
defer persistence.Close()
|
|
|
|
repo := qent.NewQuestionRepository(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(cfg.Redis); redisErr == nil {
|
|
redisClient = c
|
|
defer func() { _ = redisClient.Close() }()
|
|
} else {
|
|
logger.WithError(redisErr).Warn("redis unavailable; running with local fallback cache")
|
|
}
|
|
|
|
randomCache := qcache.NewRandomQuestionCache(redisClient)
|
|
service := appq.NewService(repo, randomCache, cfg.CacheTTL, cfg.MaxExclude)
|
|
|
|
validator := validation.NewValidator()
|
|
handler := httpapi.NewHandler(service, validator, logger, metrics, cfg.BulkMax)
|
|
|
|
bootCfg := serviceboot.Config{
|
|
AppName: cfg.AppName,
|
|
ServiceSlug: "question-bank",
|
|
PortEnv: "QUESTION_BANK_PORT",
|
|
DefaultPort: cfg.Port,
|
|
}
|
|
app := serviceboot.NewFiberApp(bootCfg)
|
|
serviceboot.RegisterHealth(app, bootCfg.ServiceSlug)
|
|
registerReadiness(app, persistence, redisClient)
|
|
app.Get("/metrics", adaptor.HTTPHandler(sharedmetrics.Handler()))
|
|
|
|
adminMiddleware := buildAdminMiddleware(cfg)
|
|
httpapi.RegisterRoutes(app, handler, adminMiddleware)
|
|
|
|
addr := serviceboot.ListenAddress(bootCfg.PortEnv, bootCfg.DefaultPort)
|
|
log.Fatal(serviceboot.Run(app, addr))
|
|
}
|
|
|
|
func buildAdminMiddleware(cfg qbconfig.Config) fiber.Handler {
|
|
if cfg.ZitadelBaseURL == "" {
|
|
return nil
|
|
}
|
|
|
|
client := zitadel.NewClient(zitadel.Config{
|
|
BaseURL: cfg.ZitadelBaseURL,
|
|
ClientID: cfg.ZitadelClientID,
|
|
ClientSecret: cfg.ZitadelSecret,
|
|
Issuer: cfg.ZitadelIssuer,
|
|
Audience: cfg.ZitadelAudience,
|
|
Timeout: 10 * time.Second,
|
|
})
|
|
|
|
return zitadel.JWTMiddleware(zitadel.JWTMiddlewareConfig{
|
|
Client: client,
|
|
Issuer: cfg.ZitadelIssuer,
|
|
Audience: cfg.ZitadelAudience,
|
|
RequiredClaims: []string{"sub"},
|
|
AdminEndpoints: []string{"/admin"},
|
|
})
|
|
}
|
|
|
|
func registerReadiness(app *fiber.App, persistence *qent.Client, redisClient *sharedredis.Client) {
|
|
app.Get("/ready", func(c fiber.Ctx) error {
|
|
ctx, cancel := context.WithTimeout(c.Context(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
checks := map[string]string{"postgres": "ok", "redis": "ok"}
|
|
if err := persistence.Pool.Ping(ctx); err != nil {
|
|
checks["postgres"] = "down"
|
|
}
|
|
if redisClient != nil {
|
|
if err := redisClient.HealthCheck(ctx); err != nil {
|
|
checks["redis"] = "down"
|
|
}
|
|
}
|
|
|
|
status := fiber.StatusOK
|
|
if checks["postgres"] != "ok" {
|
|
status = fiber.StatusServiceUnavailable
|
|
}
|
|
return c.Status(status).JSON(fiber.Map{
|
|
"status": "ready",
|
|
"checks": checks,
|
|
})
|
|
})
|
|
}
|