feat(server): API relay gateway backend M0-M4
Gin + GORM + pure-Go SQLite. Users/auth (JWT), API key management with quotas, proxy gateway with weighted channel failover and health checks, usage/billing ledger, cross-protocol conversion (Anthropic Messages / OpenAI Chat Completions / OpenAI Responses), and channel/model admin API. Channels declare native API formats and auto-convert the rest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b0c7439c01
commit
d0e31b198f
@@ -0,0 +1,107 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"openteam/server/internal/config"
|
||||
"openteam/server/internal/pkg/httpx"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
svc *Service
|
||||
cfg *config.Config
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
func NewHandler(svc *Service, cfg *config.Config, log *zap.Logger) *Handler {
|
||||
return &Handler{svc: svc, cfg: cfg, log: log}
|
||||
}
|
||||
|
||||
// Register handles POST /api/v1/auth/register.
|
||||
func (h *Handler) Register(c *gin.Context) {
|
||||
var in RegisterInput
|
||||
if !httpx.Bind(c, &in) {
|
||||
return
|
||||
}
|
||||
u, pair, err := h.svc.Register(in)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrUserExists) {
|
||||
httpx.Fail(c, http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
h.log.Warn("register failed", zap.Error(err))
|
||||
httpx.Fail(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
h.setRefreshCookie(c, pair.RefreshToken)
|
||||
httpx.Created(c, gin.H{"user": GetPublic(u), "token": pair})
|
||||
}
|
||||
|
||||
// Login handles POST /api/v1/auth/login.
|
||||
func (h *Handler) Login(c *gin.Context) {
|
||||
var in LoginInput
|
||||
if !httpx.Bind(c, &in) {
|
||||
return
|
||||
}
|
||||
u, pair, err := h.svc.Login(in)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrBadCredentials) || errors.Is(err, ErrUserDisabled) {
|
||||
httpx.Fail(c, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
h.log.Warn("login failed", zap.Error(err))
|
||||
httpx.Fail(c, http.StatusInternalServerError, "login failed")
|
||||
return
|
||||
}
|
||||
h.setRefreshCookie(c, pair.RefreshToken)
|
||||
httpx.OK(c, gin.H{"user": GetPublic(u), "token": pair})
|
||||
}
|
||||
|
||||
// Refresh handles POST /api/v1/auth/refresh.
|
||||
func (h *Handler) Refresh(c *gin.Context) {
|
||||
token, err := c.Cookie(h.cfg.Auth.RefreshCookieName)
|
||||
if err != nil || token == "" {
|
||||
httpx.Fail(c, http.StatusUnauthorized, "missing refresh token")
|
||||
return
|
||||
}
|
||||
pair, err := h.svc.Refresh(token)
|
||||
if err != nil {
|
||||
httpx.Fail(c, http.StatusUnauthorized, "invalid refresh token")
|
||||
return
|
||||
}
|
||||
h.setRefreshCookie(c, pair.RefreshToken)
|
||||
httpx.OK(c, gin.H{"token": pair})
|
||||
}
|
||||
|
||||
// Logout handles POST /api/v1/auth/logout.
|
||||
func (h *Handler) Logout(c *gin.Context) {
|
||||
c.SetCookie(h.cfg.Auth.RefreshCookieName, "", -1, "/", "", h.cfg.Auth.RefreshCookieSecure, true)
|
||||
httpx.OK(c, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// Me handles GET /api/v1/auth/me.
|
||||
func (h *Handler) Me(c *gin.Context) {
|
||||
u := Current(c)
|
||||
httpx.OK(c, GetPublic(u))
|
||||
}
|
||||
|
||||
// Profile handles GET /api/v1/user/profile.
|
||||
func (h *Handler) Profile(c *gin.Context) {
|
||||
u := Current(c)
|
||||
httpx.OK(c, GetPublic(u))
|
||||
}
|
||||
|
||||
// Balance handles GET /api/v1/user/balance.
|
||||
func (h *Handler) Balance(c *gin.Context) {
|
||||
u := Current(c)
|
||||
httpx.OK(c, gin.H{"balance": u.Balance.String()})
|
||||
}
|
||||
|
||||
func (h *Handler) setRefreshCookie(c *gin.Context, token string) {
|
||||
c.SetCookie(h.cfg.Auth.RefreshCookieName, token,
|
||||
int(h.cfg.Auth.RefreshTokenTTL.Seconds()), "/", "", h.cfg.Auth.RefreshCookieSecure, true)
|
||||
}
|
||||
Reference in New Issue
Block a user