easyai-ai-gateway/apps/api/internal/httpapi/handlers.go
wangbo 6323e70e49 Initial project scaffold
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 14:36:35 +08:00

219 lines
6.3 KiB
Go

package httpapi
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
func (s *Server) health(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{
"ok": true,
"service": "easyai-ai-gateway",
"env": s.cfg.AppEnv,
})
}
func (s *Server) ready(w http.ResponseWriter, r *http.Request) {
if err := s.store.Ping(r.Context()); err != nil {
writeError(w, http.StatusServiceUnavailable, "postgres unavailable")
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}
func (s *Server) me(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
writeJSON(w, http.StatusOK, user)
}
func (s *Server) listPlatforms(w http.ResponseWriter, r *http.Request) {
platforms, err := s.store.ListPlatforms(r.Context())
if err != nil {
s.logger.Error("list platforms failed", "error", err)
writeError(w, http.StatusInternalServerError, "list platforms failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": platforms})
}
func (s *Server) createPlatform(w http.ResponseWriter, r *http.Request) {
var input store.CreatePlatformInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
if input.Provider == "" || input.Name == "" {
writeError(w, http.StatusBadRequest, "provider and name are required")
return
}
if input.AuthType == "" {
input.AuthType = "bearer"
}
platform, err := s.store.CreatePlatform(r.Context(), input)
if err != nil {
s.logger.Error("create platform failed", "error", err)
writeError(w, http.StatusInternalServerError, "create platform failed")
return
}
writeJSON(w, http.StatusCreated, platform)
}
func (s *Server) listModels(w http.ResponseWriter, r *http.Request) {
models, err := s.store.ListModels(r.Context())
if err != nil {
s.logger.Error("list models failed", "error", err)
writeError(w, http.StatusInternalServerError, "list models failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": models})
}
func (s *Server) listCatalogProviders(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListCatalogProviders(r.Context())
if err != nil {
s.logger.Error("list catalog providers failed", "error", err)
writeError(w, http.StatusInternalServerError, "list catalog providers failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) listBaseModels(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListBaseModels(r.Context())
if err != nil {
s.logger.Error("list base models failed", "error", err)
writeError(w, http.StatusInternalServerError, "list base models failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) listPricingRules(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListPricingRules(r.Context())
if err != nil {
s.logger.Error("list pricing rules failed", "error", err)
writeError(w, http.StatusInternalServerError, "list pricing rules failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) estimatePricing(w http.ResponseWriter, r *http.Request) {
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
writeJSON(w, http.StatusOK, map[string]any{
"items": []any{},
"resolver": "effective-pricing-placeholder",
"request": body,
})
}
func (s *Server) listRateLimitWindows(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListRateLimitWindows(r.Context())
if err != nil {
s.logger.Error("list rate limit windows failed", "error", err)
writeError(w, http.StatusInternalServerError, "list rate limit windows failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) createTask(kind string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
model, _ := body["model"].(string)
if model == "" {
writeError(w, http.StatusBadRequest, "model is required")
return
}
task, err := s.store.CreateTask(r.Context(), store.CreateTaskInput{
Kind: kind,
Model: model,
Request: body,
}, user)
if err != nil {
s.logger.Error("create task failed", "kind", kind, "error", err)
writeError(w, http.StatusInternalServerError, "create task failed")
return
}
writeJSON(w, http.StatusAccepted, map[string]any{
"task": task,
"next": map[string]string{
"events": fmt.Sprintf("/api/v1/tasks/%s/events", task.ID),
"detail": fmt.Sprintf("/api/v1/tasks/%s", task.ID),
},
})
})
}
func (s *Server) getTask(w http.ResponseWriter, r *http.Request) {
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err == nil {
writeJSON(w, http.StatusOK, task)
return
}
if store.IsNotFound(err) {
writeError(w, http.StatusNotFound, "task not found")
return
}
s.logger.Error("get task failed", "error", err)
writeError(w, http.StatusInternalServerError, "get task failed")
}
func (s *Server) taskEvents(w http.ResponseWriter, r *http.Request) {
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err != nil {
if store.IsNotFound(err) {
writeError(w, http.StatusNotFound, "task not found")
return
}
writeError(w, http.StatusInternalServerError, "get task failed")
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
sendSSE(w, "task.accepted", map[string]any{
"taskId": task.ID,
"status": task.Status,
})
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
timer := time.NewTimer(250 * time.Millisecond)
defer timer.Stop()
select {
case <-r.Context().Done():
return
case <-timer.C:
sendSSE(w, "task.placeholder", map[string]any{
"taskId": task.ID,
"message": "runtime worker is not wired yet",
})
}
}