Files
lti-api/cmd/api/main.go
T
2025-10-23 11:36:29 +07:00

183 lines
4.7 KiB
Go

package main
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"gitlab.com/mbugroup/lti-api.git/internal/cache"
"gitlab.com/mbugroup/lti-api.git/internal/config"
"gitlab.com/mbugroup/lti-api.git/internal/database"
"gitlab.com/mbugroup/lti-api.git/internal/middleware"
"gitlab.com/mbugroup/lti-api.git/internal/route"
"gitlab.com/mbugroup/lti-api.git/internal/sso"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/helmet"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
app := setupFiberApp()
db := setupDatabase()
defer closeDatabase(db)
rdb := setupRedis()
defer rdb.Close()
setupSSO(ctx)
setupRoutes(app, db, rdb)
address := fmt.Sprintf("%s:%d", config.AppHost, config.AppPort)
// Start server and handle graceful shutdown
serverErrors := make(chan error, 1)
go startServer(app, address, serverErrors)
handleGracefulShutdown(ctx, app, serverErrors)
}
func setupRedis() *redis.Client {
opt, err := redis.ParseURL(config.RedisURL)
if err != nil {
utils.Log.Fatalf("Redis URL parse error: %v", err)
}
rdb := redis.NewClient(opt)
if err := rdb.Ping(context.Background()).Err(); err != nil {
utils.Log.Fatalf("Redis ping failed: %v", err)
}
cache.SetRedis(rdb)
utils.Log.Infof("Redis connected: %s", config.RedisURL)
return rdb
}
func setupSSO(ctx context.Context) {
if err := sso.Init(ctx, config.SSOJWKSURL, config.SSOIssuer, config.SSOAllowedAudiences); err != nil {
utils.Log.Fatalf("SSO initialization failed: %v", err)
}
}
func setupFiberApp() *fiber.App {
app := fiber.New(config.FiberConfig())
// Middleware setup
app.Use(middleware.LoggerConfig())
app.Use(helmet.New())
app.Use(compress.New())
app.Use(middleware.RecoverConfig())
origins := "*"
if len(config.CORSAllowOrigins) > 0 {
origins = strings.Join(config.CORSAllowOrigins, ",")
}
if config.CORSAllowCredentials && (origins == "" || origins == "*") {
origins = "http://localhost:3000"
}
app.Use(cors.New(cors.Config{
AllowOrigins: origins,
AllowMethods: strings.Join(config.CORSAllowMethods, ","),
AllowHeaders: strings.Join(config.CORSAllowHeaders, ","),
ExposeHeaders: strings.Join(config.CORSExposeHeaders, ","),
AllowCredentials: config.CORSAllowCredentials,
MaxAge: config.CORSMaxAge,
}))
return app
}
func setupDatabase() *gorm.DB {
db := database.Connect(config.DBHost, config.DBName)
return db
}
func setupRoutes(app *fiber.App, db *gorm.DB, rdb *redis.Client) {
// route.Routes(app, db)
// app.Use(utils.NotFoundHandler)
app.Get("/healthz", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"status": "ok",
"service": "api",
"version": config.Version,
})
})
app.Get("/readyz", func(c *fiber.Ctx) error {
sqlDB, err := db.DB()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"status": "error", "db": "unavailable", "redis": "unknown",
})
}
ctx, cancel := context.WithTimeout(c.Context(), 2*time.Second)
defer cancel()
dbOK := sqlDB.PingContext(ctx) == nil
redisOK := rdb.Ping(ctx).Err() == nil
status := fiber.StatusOK
statusText := "ok"
if !dbOK || !redisOK {
status = fiber.StatusServiceUnavailable
statusText = "degraded"
}
body := fiber.Map{
"status": statusText,
"db": map[bool]string{true: "up", false: "down"}[dbOK],
"redis": map[bool]string{true: "up", false: "down"}[redisOK],
}
return c.Status(status).JSON(body)
})
route.Routes(app, db)
app.Use(utils.NotFoundHandler)
}
func startServer(app *fiber.App, address string, errs chan<- error) {
if err := app.Listen(address); err != nil {
errs <- fmt.Errorf("error starting server: %w", err)
}
}
func closeDatabase(db *gorm.DB) {
sqlDB, errDB := db.DB()
if errDB != nil {
utils.Log.Errorf("Error getting database instance: %v", errDB)
return
}
if err := sqlDB.Close(); err != nil {
utils.Log.Errorf("Error closing database connection: %v", err)
} else {
utils.Log.Info("Database connection closed successfully")
}
}
func handleGracefulShutdown(ctx context.Context, app *fiber.App, serverErrors <-chan error) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
select {
case err := <-serverErrors:
utils.Log.Fatalf("Server error: %v", err)
case <-quit:
utils.Log.Info("Shutting down server...")
if err := app.Shutdown(); err != nil {
utils.Log.Fatalf("Error during server shutdown: %v", err)
}
case <-ctx.Done():
utils.Log.Info("Server exiting due to context cancellation")
}
utils.Log.Info("Server exited")
}