mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
189 lines
5.0 KiB
Go
189 lines
5.0 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/modules/sso/session"
|
|
"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, rdb)
|
|
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, rdb *redis.Client) {
|
|
if err := sso.Init(ctx, config.SSOJWKSURL, config.SSOIssuer, config.SSOAllowedAudiences); err != nil {
|
|
utils.Log.Fatalf("SSO initialization failed: %v", err)
|
|
}
|
|
if rdb != nil {
|
|
session.SetRevocationStore(session.NewRevocationStore(rdb, config.SSOTokenBlacklistPrefix))
|
|
} else {
|
|
session.SetRevocationStore(nil)
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|