Files
openteam/server/internal/user/handler.go
T
SakurasanandClaude Sonnet 5 d0e31b198f 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>
2026-08-15 21:05:02 +08:00

108 lines
2.7 KiB
Go

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)
}