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>
108 lines
2.7 KiB
Go
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)
|
|
}
|