package server import ( "context" "encoding/json" "net/http" "strings" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "ds2api/internal/account" "ds2api/internal/adapter/claude" "ds2api/internal/adapter/openai" "ds2api/internal/admin" "ds2api/internal/auth" "ds2api/internal/config" "ds2api/internal/deepseek" "ds2api/internal/webui" ) type App struct { Store *config.Store Pool *account.Pool Resolver *auth.Resolver DS *deepseek.Client Router http.Handler } func NewApp() *App { store := config.LoadStore() pool := account.NewPool(store) var dsClient *deepseek.Client resolver := auth.NewResolver(store, pool, func(ctx context.Context, acc config.Account) (string, error) { return dsClient.Login(ctx, acc) }) dsClient = deepseek.NewClient(store, resolver) if err := dsClient.PreloadPow(context.Background()); err != nil { config.Logger.Warn("[WASM] preload failed", "error", err) } else { config.Logger.Info("[WASM] module preloaded", "path", config.WASMPath()) } openaiHandler := &openai.Handler{Store: store, Auth: resolver, DS: dsClient} claudeHandler := &claude.Handler{Store: store, Auth: resolver, DS: dsClient} adminHandler := &admin.Handler{Store: store, Pool: pool, DS: dsClient} webuiHandler := webui.NewHandler() r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(cors) r.Use(timeout(0)) r.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"status":"ok"}`)) }) r.Get("/readyz", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"status":"ready"}`)) }) openai.RegisterRoutes(r, openaiHandler) claude.RegisterRoutes(r, claudeHandler) r.Route("/admin", func(ar chi.Router) { admin.RegisterRoutes(ar, adminHandler) }) webui.RegisterRoutes(r, webuiHandler) r.NotFound(func(w http.ResponseWriter, req *http.Request) { if strings.HasPrefix(req.URL.Path, "/admin/") && webuiHandler.HandleAdminFallback(w, req) { return } http.NotFound(w, req) }) return &App{Store: store, Pool: pool, Resolver: resolver, DS: dsClient, Router: r} } func timeout(d time.Duration) func(http.Handler) http.Handler { if d <= 0 { return func(next http.Handler) http.Handler { return next } } return middleware.Timeout(d) } func cors(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } func WriteUnhandledError(w http.ResponseWriter, err error) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(map[string]any{"error": map[string]any{"type": "api_error", "message": "Internal Server Error", "detail": err.Error()}}) }