90 lines
2.8 KiB
Go
90 lines
2.8 KiB
Go
// Package handler adapts application services to the HTTP protocol.
|
|
package handler
|
|
|
|
import (
|
|
"context"
|
|
"go.uber.org/zap"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
"git.sundynix.cn/Blizzard/Veyra/internal/adminui"
|
|
"git.sundynix.cn/Blizzard/Veyra/internal/service"
|
|
)
|
|
|
|
func NewRouter(health *service.HealthService, logger *zap.Logger, identity IdentityOptions) http.Handler {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
|
|
defer cancel()
|
|
if identity.PublicOrigin != "" {
|
|
ctx = context.WithValue(ctx, publicOriginContextKey{}, identity.PublicOrigin)
|
|
}
|
|
c.Request = c.Request.WithContext(ctx)
|
|
requestID := uuid.NewString()
|
|
c.Header("X-Request-ID", requestID)
|
|
c.Header("X-Content-Type-Options", "nosniff")
|
|
c.Header("Cache-Control", "no-store")
|
|
start := time.Now()
|
|
defer func() {
|
|
if recover() != nil {
|
|
logger.Error("request panic", zap.String("request_id", requestID))
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": gin.H{"code": "INTERNAL_ERROR", "message": "Internal server error", "request_id": requestID}})
|
|
}
|
|
route := c.FullPath()
|
|
if route == "" {
|
|
route = "/<unmatched>"
|
|
if c.Request.URL.Path == "/" {
|
|
route = "/"
|
|
} else if strings.HasPrefix(c.Request.URL.Path, "/assets/") {
|
|
route = "/assets/*"
|
|
}
|
|
}
|
|
fields := []zap.Field{zap.String("method", c.Request.Method), zap.String("route", route), zap.Int("status", c.Writer.Status()), zap.Int64("duration_ms", time.Since(start).Milliseconds()), zap.String("request_id", requestID)}
|
|
switch {
|
|
case c.Writer.Status() >= 500:
|
|
logger.Error("HTTP request", fields...)
|
|
case c.Writer.Status() >= 400:
|
|
logger.Warn("HTTP request", fields...)
|
|
default:
|
|
logger.Info("HTTP request", fields...)
|
|
}
|
|
}()
|
|
c.Next()
|
|
})
|
|
r.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "alive"}) })
|
|
check := func(c *gin.Context) {
|
|
h := health.Check(c.Request.Context())
|
|
status := http.StatusOK
|
|
if h.Status != "ready" {
|
|
status = http.StatusServiceUnavailable
|
|
}
|
|
c.JSON(status, h)
|
|
}
|
|
r.GET("/readyz", check)
|
|
r.GET("/api/v1/system/health", check)
|
|
bindIdentity(r, identity)
|
|
bindProviders(r, identity)
|
|
bindCandidates(r, identity)
|
|
bindCandidateAnalysis(r, identity)
|
|
bindTradePlans(r, identity)
|
|
bindTradeFills(r, identity)
|
|
bindNotifications(r, identity)
|
|
bindStrategies(r, identity)
|
|
bindBacktests(r, identity)
|
|
bindMarket(r, identity)
|
|
console := adminui.Handler()
|
|
r.NoRoute(func(c *gin.Context) {
|
|
if !strings.HasPrefix(c.Request.URL.Path, "/api/") && c.Request.URL.Path != "/api" {
|
|
console.ServeHTTP(c.Writer, c.Request)
|
|
return
|
|
}
|
|
c.JSON(http.StatusNotFound, gin.H{"error": gin.H{"code": "NOT_FOUND", "message": "Endpoint not found"}})
|
|
})
|
|
return r
|
|
}
|