Files
ez-api/cmd/server/main.go
RC-CHN 770a9fef2b feat: Add Swagger documentation for admin and master API endpoints
- Added Swagger documentation for the following admin endpoints:
  - Create a new master tenant
  - Create a new provider
  - Register a new model
  - List all models
  - Update a model
  - Force sync snapshot
  - Ingest logs

- Added Swagger documentation for the master endpoint:
  - Issue a child key

- Updated go.mod and go.sum to include necessary dependencies for Swagger.
2025-12-05 15:01:35 +08:00

196 lines
5.5 KiB
Go

package main
import (
"context"
"log"
"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/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 main() {
// 1. Load Configuration
cfg, err := config.Load()
if err != nil {
log.Fatalf("Failed to load config: %v", 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 {
log.Fatalf("Failed to connect to Redis: %v", err)
}
log.Println("Connected to Redis successfully")
// 3. Initialize GORM (PostgreSQL)
db, err := gorm.Open(postgres.Open(cfg.Postgres.DSN), &gorm.Config{})
if err != nil {
log.Fatalf("Failed to connect to PostgreSQL: %v", err)
}
sqlDB, err := db.DB()
if err != nil {
log.Fatalf("Failed to get generic database object: %v", err)
}
// Verify DB connection
if err := sqlDB.Ping(); err != nil {
log.Fatalf("Failed to ping PostgreSQL: %v", err)
}
log.Println("Connected to PostgreSQL successfully")
// Auto Migrate
if err := db.AutoMigrate(&model.Master{}, &model.Key{}, &model.Provider{}, &model.Model{}, &model.LogRecord{}); err != nil {
log.Fatalf("Failed to auto migrate: %v", 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 {
log.Fatalf("Failed to create admin service: %v", 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)
// 4.1 Prime Redis snapshots so DP can start with data
if err := syncService.SyncAll(db); err != nil {
log.Printf("Initial sync warning: %v", err)
}
// 5. Setup Gin Router
r := gin.Default()
// 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)
// Other admin routes for managing providers, models, etc.
adminGroup.POST("/providers", handler.CreateProvider)
adminGroup.POST("/models", handler.CreateModel)
adminGroup.GET("/models", handler.ListModels)
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() {
log.Printf("Starting ez-api on port %s", cfg.Server.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// Shutdown with timeout
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited properly")
}