mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
Register admin routes to handle updates for providers and models via their respective handlers.
211 lines
6.2 KiB
Go
211 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
_ "github.com/ez-api/ez-api/docs"
|
|
"github.com/ez-api/ez-api/internal/api"
|
|
"github.com/ez-api/ez-api/internal/config"
|
|
"github.com/ez-api/ez-api/internal/middleware"
|
|
"github.com/ez-api/ez-api/internal/model"
|
|
"github.com/ez-api/ez-api/internal/service"
|
|
"github.com/ez-api/foundation/logging"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/redis/go-redis/v9"
|
|
swaggerFiles "github.com/swaggo/files"
|
|
ginSwagger "github.com/swaggo/gin-swagger"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// @title EZ-API Control Plane
|
|
// @version 0.0.1
|
|
// @description Management API for EZ-API Gateway system.
|
|
// @termsOfService http://swagger.io/terms/
|
|
|
|
// @contact.name API Support
|
|
// @contact.url http://www.swagger.io/support
|
|
// @contact.email support@swagger.io
|
|
|
|
// @license.name Apache 2.0
|
|
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
|
|
|
// @host localhost:8080
|
|
// @BasePath /
|
|
|
|
// @securityDefinitions.apikey AdminAuth
|
|
// @in header
|
|
// @name Authorization
|
|
|
|
// @securityDefinitions.apikey MasterAuth
|
|
// @in header
|
|
// @name Authorization
|
|
|
|
func fatal(logger *slog.Logger, msg string, args ...any) {
|
|
logger.Error(msg, args...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func main() {
|
|
logger, _ := logging.New(logging.Options{Service: "ez-api"})
|
|
|
|
// 1. Load Configuration
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
fatal(logger, "failed to load config", "err", err)
|
|
}
|
|
|
|
// 2. Initialize Redis Client
|
|
rdb := redis.NewClient(&redis.Options{
|
|
Addr: cfg.Redis.Addr,
|
|
Password: cfg.Redis.Password,
|
|
DB: cfg.Redis.DB,
|
|
})
|
|
|
|
// Verify Redis connection
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := rdb.Ping(ctx).Err(); err != nil {
|
|
fatal(logger, "failed to connect to redis", "err", err)
|
|
}
|
|
logger.Info("connected to redis successfully")
|
|
|
|
// 3. Initialize GORM (PostgreSQL)
|
|
db, err := gorm.Open(postgres.Open(cfg.Postgres.DSN), &gorm.Config{})
|
|
if err != nil {
|
|
fatal(logger, "failed to connect to postgresql", "err", err)
|
|
}
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
fatal(logger, "failed to get generic database object", "err", err)
|
|
}
|
|
// Verify DB connection
|
|
if err := sqlDB.Ping(); err != nil {
|
|
fatal(logger, "failed to ping postgresql", "err", err)
|
|
}
|
|
logger.Info("connected to postgresql successfully")
|
|
|
|
// Auto Migrate
|
|
if err := db.AutoMigrate(&model.Master{}, &model.Key{}, &model.Provider{}, &model.Model{}, &model.LogRecord{}); err != nil {
|
|
fatal(logger, "failed to auto migrate", "err", err)
|
|
}
|
|
|
|
// 4. Setup Services and Handlers
|
|
syncService := service.NewSyncService(rdb)
|
|
logWriter := service.NewLogWriter(db, cfg.Log.QueueCapacity, cfg.Log.BatchSize, cfg.Log.FlushInterval)
|
|
logCtx, cancelLogs := context.WithCancel(context.Background())
|
|
defer cancelLogs()
|
|
logWriter.Start(logCtx)
|
|
|
|
adminService, err := service.NewAdminService()
|
|
if err != nil {
|
|
fatal(logger, "failed to create admin service", "err", err)
|
|
}
|
|
masterService := service.NewMasterService(db)
|
|
healthService := service.NewHealthCheckService(db, rdb)
|
|
|
|
handler := api.NewHandler(db, syncService, logWriter)
|
|
adminHandler := api.NewAdminHandler(masterService, syncService)
|
|
masterHandler := api.NewMasterHandler(masterService, syncService)
|
|
featureHandler := api.NewFeatureHandler(rdb)
|
|
|
|
// 4.1 Prime Redis snapshots so DP can start with data
|
|
if err := syncService.SyncAll(db); err != nil {
|
|
logger.Warn("initial sync warning", "err", err)
|
|
}
|
|
|
|
// 5. Setup Gin Router
|
|
r := gin.Default()
|
|
r.Use(middleware.RequestID())
|
|
|
|
// CORS Middleware
|
|
r.Use(func(c *gin.Context) {
|
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // TODO: Restrict this in production
|
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
|
|
|
|
if c.Request.Method == "OPTIONS" {
|
|
c.AbortWithStatus(204)
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
})
|
|
|
|
// Health Check Endpoint
|
|
r.GET("/health", func(c *gin.Context) {
|
|
status := healthService.Check(c.Request.Context())
|
|
httpStatus := http.StatusOK
|
|
if status.Status == "down" {
|
|
httpStatus = http.StatusServiceUnavailable
|
|
}
|
|
c.JSON(httpStatus, status)
|
|
})
|
|
|
|
// Swagger Documentation
|
|
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
|
|
|
// API Routes
|
|
// Admin Routes
|
|
adminGroup := r.Group("/admin")
|
|
adminGroup.Use(middleware.AdminAuthMiddleware(adminService))
|
|
{
|
|
adminGroup.POST("/masters", adminHandler.CreateMaster)
|
|
adminGroup.POST("/masters/:id/keys", adminHandler.IssueChildKeyForMaster)
|
|
adminGroup.GET("/features", featureHandler.ListFeatures)
|
|
adminGroup.PUT("/features", featureHandler.UpdateFeatures)
|
|
// Other admin routes for managing providers, models, etc.
|
|
adminGroup.POST("/providers", handler.CreateProvider)
|
|
adminGroup.PUT("/providers/:id", handler.UpdateProvider)
|
|
adminGroup.POST("/models", handler.CreateModel)
|
|
adminGroup.GET("/models", handler.ListModels)
|
|
adminGroup.PUT("/models/:id", handler.UpdateModel)
|
|
adminGroup.POST("/sync/snapshot", handler.SyncSnapshot)
|
|
}
|
|
|
|
// Master Routes
|
|
masterGroup := r.Group("/v1")
|
|
masterGroup.Use(middleware.MasterAuthMiddleware(masterService))
|
|
{
|
|
masterGroup.POST("/tokens", masterHandler.IssueChildKey)
|
|
}
|
|
|
|
// Public/General Routes (if any)
|
|
r.POST("/logs", handler.IngestLog)
|
|
|
|
srv := &http.Server{
|
|
Addr: ":" + cfg.Server.Port,
|
|
Handler: r,
|
|
}
|
|
|
|
// 6. Start Server with Graceful Shutdown
|
|
go func() {
|
|
logger.Info("starting ez-api", "port", cfg.Server.Port)
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
fatal(logger, "server failed", "err", err)
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
<-quit
|
|
logger.Info("shutting down server...")
|
|
|
|
// Shutdown with timeout
|
|
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := srv.Shutdown(ctx); err != nil {
|
|
fatal(logger, "server forced to shutdown", "err", err)
|
|
}
|
|
|
|
logger.Info("server exited properly")
|
|
}
|