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:
Sakurasan
2026-08-15 21:05:02 +08:00
co-authored by Claude Sonnet 5
parent b0c7439c01
commit d0e31b198f
45 changed files with 6222 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
package apikey
import (
"errors"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"openteam/server/internal/pkg/httpx"
"openteam/server/internal/user"
)
type Handler struct {
svc *Service
log *zap.Logger
}
func NewHandler(svc *Service, log *zap.Logger) *Handler {
return &Handler{svc: svc, log: log}
}
// List handles GET /api/v1/keys.
func (h *Handler) List(c *gin.Context) {
u := user.Current(c)
keys, err := h.svc.List(u.ID)
if err != nil {
h.log.Warn("list keys failed", zap.Error(err))
httpx.Fail(c, http.StatusInternalServerError, "list keys failed")
return
}
out := make([]map[string]any, 0, len(keys))
for i := range keys {
out = append(out, Public(&keys[i]))
}
httpx.OK(c, out)
}
// Create handles POST /api/v1/keys.
func (h *Handler) Create(c *gin.Context) {
u := user.Current(c)
var in CreateInput
if !httpx.Bind(c, &in) {
return
}
rec, plain, err := h.svc.Create(u.ID, in)
if err != nil {
h.log.Warn("create key failed", zap.Error(err))
httpx.Fail(c, http.StatusInternalServerError, "create key failed")
return
}
data := Public(rec)
data["key"] = plain // shown only once
httpx.Created(c, data)
}
// Update handles PATCH /api/v1/keys/:id.
func (h *Handler) Update(c *gin.Context) {
u := user.Current(c)
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
httpx.Fail(c, http.StatusBadRequest, "invalid key id")
return
}
var in UpdateInput
if !httpx.Bind(c, &in) {
return
}
rec, err := h.svc.Update(u.ID, id, in)
if err != nil {
if errors.Is(err, ErrKeyNotFound) {
httpx.Fail(c, http.StatusNotFound, "api key not found")
return
}
httpx.Fail(c, http.StatusInternalServerError, "update key failed")
return
}
httpx.OK(c, Public(rec))
}
// Delete handles DELETE /api/v1/keys/:id.
func (h *Handler) Delete(c *gin.Context) {
u := user.Current(c)
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
httpx.Fail(c, http.StatusBadRequest, "invalid key id")
return
}
if err := h.svc.Delete(u.ID, id); err != nil {
if errors.Is(err, ErrKeyNotFound) {
httpx.Fail(c, http.StatusNotFound, "api key not found")
return
}
httpx.Fail(c, http.StatusInternalServerError, "revoke key failed")
return
}
httpx.OK(c, gin.H{"ok": true})
}