refactor backend API structure

This commit is contained in:
CJACK
2026-04-26 06:58:20 +08:00
parent 8a91fef6ab
commit abc96a37d8
207 changed files with 2675 additions and 1344 deletions

View File

@@ -15,14 +15,18 @@ import (
"github.com/go-chi/chi/v5/middleware"
"ds2api/internal/account"
"ds2api/internal/adapter/claude"
"ds2api/internal/adapter/gemini"
"ds2api/internal/adapter/openai"
"ds2api/internal/admin"
"ds2api/internal/auth"
"ds2api/internal/chathistory"
"ds2api/internal/config"
"ds2api/internal/deepseek"
dsclient "ds2api/internal/deepseek/client"
"ds2api/internal/httpapi/admin"
"ds2api/internal/httpapi/claude"
"ds2api/internal/httpapi/gemini"
"ds2api/internal/httpapi/openai/chat"
"ds2api/internal/httpapi/openai/embeddings"
"ds2api/internal/httpapi/openai/files"
"ds2api/internal/httpapi/openai/responses"
"ds2api/internal/httpapi/openai/shared"
"ds2api/internal/webui"
)
@@ -30,7 +34,7 @@ type App struct {
Store *config.Store
Pool *account.Pool
Resolver *auth.Resolver
DS *deepseek.Client
DS *dsclient.Client
Router http.Handler
}
@@ -40,11 +44,11 @@ func NewApp() (*App, error) {
return nil, fmt.Errorf("load config: %w", err)
}
pool := account.NewPool(store)
var dsClient *deepseek.Client
var dsClient *dsclient.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)
dsClient = dsclient.NewClient(store, resolver)
if err := dsClient.PreloadPow(context.Background()); err != nil {
config.Logger.Warn("[PoW] init failed", "error", err)
} else {
@@ -55,10 +59,14 @@ func NewApp() (*App, error) {
config.Logger.Warn("[chat_history] unavailable", "path", chatHistoryStore.Path(), "error", err)
}
openaiHandler := &openai.Handler{Store: store, Auth: resolver, DS: dsClient, ChatHistory: chatHistoryStore}
claudeHandler := &claude.Handler{Store: store, Auth: resolver, DS: dsClient, OpenAI: openaiHandler}
geminiHandler := &gemini.Handler{Store: store, Auth: resolver, DS: dsClient, OpenAI: openaiHandler}
adminHandler := &admin.Handler{Store: store, Pool: pool, DS: dsClient, OpenAI: openaiHandler, ChatHistory: chatHistoryStore}
modelsHandler := &shared.ModelsHandler{Store: store}
chatHandler := &chat.Handler{Store: store, Auth: resolver, DS: dsClient, ChatHistory: chatHistoryStore}
responsesHandler := &responses.Handler{Store: store, Auth: resolver, DS: dsClient, ChatHistory: chatHistoryStore}
filesHandler := &files.Handler{Store: store, Auth: resolver, DS: dsClient, ChatHistory: chatHistoryStore}
embeddingsHandler := &embeddings.Handler{Store: store, Auth: resolver, DS: dsClient, ChatHistory: chatHistoryStore}
claudeHandler := &claude.Handler{Store: store, Auth: resolver, DS: dsClient, OpenAI: chatHandler}
geminiHandler := &gemini.Handler{Store: store, Auth: resolver, DS: dsClient, OpenAI: chatHandler}
adminHandler := &admin.Handler{Store: store, Pool: pool, DS: dsClient, OpenAI: chatHandler, ChatHistory: chatHistoryStore}
webuiHandler := webui.NewHandler()
r := chi.NewRouter()
@@ -83,7 +91,13 @@ func NewApp() (*App, error) {
r.Head("/healthz", healthzHandler)
r.Get("/readyz", readyzHandler)
r.Head("/readyz", readyzHandler)
openai.RegisterRoutes(r, openaiHandler)
r.Get("/v1/models", modelsHandler.ListModels)
r.Get("/v1/models/{model_id}", modelsHandler.GetModel)
r.Post("/v1/chat/completions", chatHandler.ChatCompletions)
r.Post("/v1/responses", responsesHandler.Responses)
r.Get("/v1/responses/{response_id}", responsesHandler.GetResponseByID)
r.Post("/v1/files", filesHandler.UploadFile)
r.Post("/v1/embeddings", embeddingsHandler.Embeddings)
claude.RegisterRoutes(r, claudeHandler)
gemini.RegisterRoutes(r, geminiHandler)
r.Route("/admin", func(ar chi.Router) {

View File

@@ -0,0 +1,99 @@
package server
import (
"fmt"
"net/http"
"testing"
"github.com/go-chi/chi/v5"
)
func TestAPIRoutesRemainRegistered(t *testing.T) {
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[{"email":"u@example.com","password":"p"}]}`)
t.Setenv("DS2API_ENV_WRITEBACK", "0")
app, err := NewApp()
if err != nil {
t.Fatalf("NewApp() error: %v", err)
}
routes, ok := app.Router.(chi.Routes)
if !ok {
t.Fatalf("app router does not expose chi routes: %T", app.Router)
}
got := map[string]bool{}
if err := chi.Walk(routes, func(method string, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
got[fmt.Sprintf("%s %s", method, route)] = true
return nil
}); err != nil {
t.Fatalf("walk routes: %v", err)
}
for _, want := range []string{
"GET /v1/models",
"GET /v1/models/{model_id}",
"POST /v1/chat/completions",
"POST /v1/responses",
"GET /v1/responses/{response_id}",
"POST /v1/files",
"POST /v1/embeddings",
"GET /anthropic/v1/models",
"POST /anthropic/v1/messages",
"POST /anthropic/v1/messages/count_tokens",
"POST /v1/messages",
"POST /messages",
"POST /v1/messages/count_tokens",
"POST /messages/count_tokens",
"POST /v1beta/models/{model}:generateContent",
"POST /v1beta/models/{model}:streamGenerateContent",
"POST /v1/models/{model}:generateContent",
"POST /v1/models/{model}:streamGenerateContent",
"POST /admin/login",
"GET /admin/verify",
"GET /admin/config",
"POST /admin/config",
"GET /admin/settings",
"PUT /admin/settings",
"POST /admin/settings/password",
"POST /admin/config/import",
"GET /admin/config/export",
"POST /admin/keys",
"PUT /admin/keys/{key}",
"DELETE /admin/keys/{key}",
"GET /admin/proxies",
"POST /admin/proxies",
"PUT /admin/proxies/{proxyID}",
"DELETE /admin/proxies/{proxyID}",
"POST /admin/proxies/test",
"GET /admin/accounts",
"POST /admin/accounts",
"PUT /admin/accounts/{identifier}",
"DELETE /admin/accounts/{identifier}",
"PUT /admin/accounts/{identifier}/proxy",
"GET /admin/queue/status",
"POST /admin/accounts/test",
"POST /admin/accounts/test-all",
"POST /admin/accounts/sessions/delete-all",
"POST /admin/import",
"POST /admin/test",
"POST /admin/dev/raw-samples/capture",
"GET /admin/dev/raw-samples/query",
"POST /admin/dev/raw-samples/save",
"POST /admin/vercel/sync",
"GET /admin/vercel/status",
"POST /admin/vercel/status",
"GET /admin/export",
"GET /admin/dev/captures",
"DELETE /admin/dev/captures",
"GET /admin/chat-history",
"GET /admin/chat-history/{id}",
"DELETE /admin/chat-history",
"DELETE /admin/chat-history/{id}",
"PUT /admin/chat-history/settings",
"GET /admin/version",
} {
if !got[want] {
t.Fatalf("expected route %s to be registered", want)
}
}
}