feat(1d): wire oapi-codegen, generate types/server/client, add 501 stubs — tsk_Kp80nvHV2yLc
- Fix openapi.yaml: quote bearerAuth description that contained unquoted `: ` plain-scalar YAML
- Add server/api/cfg-{types,server,client}.yaml — oapi-codegen v2 config for three split outputs
- Add server/api/gen/gen.go — three //go:generate directives; run `make generate`
- Generate server/api/gen/{types,server,client}.gen.go from the contract (all 15 operations)
- Add github.com/oapi-codegen/runtime v1.4.1 to go.mod (runtime types needed by generated code)
- Add server/internal/httpapi/unimplemented.go — UnimplementedServer satisfies gen.ServerInterface;
every method writes HTTP 501 + {code,message_zh,message_en} JSON body (no dep on task-1f apierr)
- Update server/cmd/server/main.go — mount /v1 API via gen.HandlerFromMuxWithBaseURL; keep /healthz
- Update server/Makefile — `generate` target now runs `go generate ./...`;
add `check-generate` CI guard (regenerate + git diff --exit-code)
- Add server/internal/httpapi/unimplemented_test.go — 19 httptest cases covering all 15 API routes
(501+body), undefined routes (404), and /healthz isolation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -1,4 +1,4 @@
|
||||
.PHONY: build test test-unit test-integration vet lint generate migrate-up migrate-down build-codegen deps
|
||||
.PHONY: build test test-unit test-integration vet lint generate check-generate migrate-up migrate-down build-codegen deps
|
||||
|
||||
# ── deps ───────────────────────────────────────────────────────────────────────
|
||||
deps:
|
||||
@@ -30,7 +30,11 @@ lint:
|
||||
|
||||
# ── generate ───────────────────────────────────────────────────────────────────
|
||||
generate:
|
||||
@echo "generate: not yet configured (see task 1d)"
|
||||
go generate ./...
|
||||
|
||||
check-generate: ## CI guard: regenerate and fail if generated files changed
|
||||
go generate ./...
|
||||
git diff --exit-code server/api/gen
|
||||
|
||||
# ── migrate ────────────────────────────────────────────────────────────────────
|
||||
migrate-up:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package: gen
|
||||
generate:
|
||||
client: true
|
||||
embedded-spec: false
|
||||
output: client.gen.go
|
||||
@@ -0,0 +1,5 @@
|
||||
package: gen
|
||||
generate:
|
||||
chi-server: true
|
||||
embedded-spec: true
|
||||
output: server.gen.go
|
||||
@@ -0,0 +1,5 @@
|
||||
package: gen
|
||||
generate:
|
||||
models: true
|
||||
embedded-spec: false
|
||||
output: types.gen.go
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
// Package gen contains oapi-codegen generated files for the Pangolin API.
|
||||
// Run `go generate ./...` (or `make generate`) to regenerate.
|
||||
package gen
|
||||
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=../cfg-types.yaml ../openapi.yaml
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=../cfg-server.yaml ../openapi.yaml
|
||||
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=../cfg-client.yaml ../openapi.yaml
|
||||
@@ -0,0 +1,903 @@
|
||||
// Package gen provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.7.1 DO NOT EDIT.
|
||||
package gen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/oapi-codegen/runtime"
|
||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||
)
|
||||
|
||||
// ServerInterface represents all server handlers.
|
||||
type ServerInterface interface {
|
||||
// 激励视频广告解锁当日时长
|
||||
// (POST /ads/unlock)
|
||||
AdsUnlock(w http.ResponseWriter, r *http.Request)
|
||||
// 发送邮箱验证码
|
||||
// (POST /auth/code)
|
||||
SendVerificationCode(w http.ResponseWriter, r *http.Request)
|
||||
// 登录
|
||||
// (POST /auth/login)
|
||||
Login(w http.ResponseWriter, r *http.Request)
|
||||
// 刷新 Access Token
|
||||
// (POST /auth/refresh)
|
||||
RefreshToken(w http.ResponseWriter, r *http.Request)
|
||||
// 注册账户
|
||||
// (POST /auth/register)
|
||||
Register(w http.ResponseWriter, r *http.Request)
|
||||
// 获取当前账户信息
|
||||
// (GET /me)
|
||||
GetMe(w http.ResponseWriter, r *http.Request)
|
||||
// 获取设备列表
|
||||
// (GET /me/devices)
|
||||
ListDevices(w http.ResponseWriter, r *http.Request)
|
||||
// 移除设备
|
||||
// (DELETE /me/devices/{id})
|
||||
DeleteDevice(w http.ResponseWriter, r *http.Request, id openapi_types.UUID)
|
||||
// 获取节点目录
|
||||
// (GET /nodes)
|
||||
ListNodes(w http.ResponseWriter, r *http.Request, params ListNodesParams)
|
||||
// 连接节点 — 下发连接凭证
|
||||
// (POST /nodes/{id}/connect)
|
||||
ConnectNode(w http.ResponseWriter, r *http.Request, id openapi_types.UUID)
|
||||
// 断开节点连接
|
||||
// (POST /nodes/{id}/disconnect)
|
||||
DisconnectNode(w http.ResponseWriter, r *http.Request, id openapi_types.UUID)
|
||||
// 获取公告列表
|
||||
// (GET /notices)
|
||||
ListNotices(w http.ResponseWriter, r *http.Request)
|
||||
// 获取套餐目录
|
||||
// (GET /plans)
|
||||
ListPlans(w http.ResponseWriter, r *http.Request)
|
||||
// 兑换激活码
|
||||
// (POST /redeem)
|
||||
RedeemCode(w http.ResponseWriter, r *http.Request)
|
||||
// 获取用量曲线
|
||||
// (GET /usage)
|
||||
GetUsage(w http.ResponseWriter, r *http.Request, params GetUsageParams)
|
||||
}
|
||||
|
||||
// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint.
|
||||
|
||||
type Unimplemented struct{}
|
||||
|
||||
// 激励视频广告解锁当日时长
|
||||
// (POST /ads/unlock)
|
||||
func (_ Unimplemented) AdsUnlock(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 发送邮箱验证码
|
||||
// (POST /auth/code)
|
||||
func (_ Unimplemented) SendVerificationCode(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 登录
|
||||
// (POST /auth/login)
|
||||
func (_ Unimplemented) Login(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 刷新 Access Token
|
||||
// (POST /auth/refresh)
|
||||
func (_ Unimplemented) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 注册账户
|
||||
// (POST /auth/register)
|
||||
func (_ Unimplemented) Register(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 获取当前账户信息
|
||||
// (GET /me)
|
||||
func (_ Unimplemented) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 获取设备列表
|
||||
// (GET /me/devices)
|
||||
func (_ Unimplemented) ListDevices(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 移除设备
|
||||
// (DELETE /me/devices/{id})
|
||||
func (_ Unimplemented) DeleteDevice(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 获取节点目录
|
||||
// (GET /nodes)
|
||||
func (_ Unimplemented) ListNodes(w http.ResponseWriter, r *http.Request, params ListNodesParams) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 连接节点 — 下发连接凭证
|
||||
// (POST /nodes/{id}/connect)
|
||||
func (_ Unimplemented) ConnectNode(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 断开节点连接
|
||||
// (POST /nodes/{id}/disconnect)
|
||||
func (_ Unimplemented) DisconnectNode(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 获取公告列表
|
||||
// (GET /notices)
|
||||
func (_ Unimplemented) ListNotices(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 获取套餐目录
|
||||
// (GET /plans)
|
||||
func (_ Unimplemented) ListPlans(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 兑换激活码
|
||||
// (POST /redeem)
|
||||
func (_ Unimplemented) RedeemCode(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// 获取用量曲线
|
||||
// (GET /usage)
|
||||
func (_ Unimplemented) GetUsage(w http.ResponseWriter, r *http.Request, params GetUsageParams) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// ServerInterfaceWrapper converts contexts to parameters.
|
||||
type ServerInterfaceWrapper struct {
|
||||
Handler ServerInterface
|
||||
HandlerMiddlewares []MiddlewareFunc
|
||||
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||
}
|
||||
|
||||
type MiddlewareFunc func(http.Handler) http.Handler
|
||||
|
||||
// AdsUnlock operation middleware
|
||||
func (siw *ServerInterfaceWrapper) AdsUnlock(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.AdsUnlock(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// SendVerificationCode operation middleware
|
||||
func (siw *ServerInterfaceWrapper) SendVerificationCode(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.SendVerificationCode(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Login operation middleware
|
||||
func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.Login(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// RefreshToken operation middleware
|
||||
func (siw *ServerInterfaceWrapper) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.RefreshToken(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Register operation middleware
|
||||
func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.Register(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetMe operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetMe(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListDevices operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListDevices(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ListDevices(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// DeleteDevice operation middleware
|
||||
func (siw *ServerInterfaceWrapper) DeleteDevice(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
// ------------- Path parameter "id" -------------
|
||||
var id openapi_types.UUID
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "uuid"})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.DeleteDevice(w, r, id)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListNodes operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListNodes(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params ListNodesParams
|
||||
|
||||
// ------------- Optional query parameter "if_version" -------------
|
||||
|
||||
err = runtime.BindQueryParameterWithOptions("form", true, false, "if_version", r.URL.Query(), ¶ms.IfVersion, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"})
|
||||
if err != nil {
|
||||
var requiredError *runtime.RequiredParameterError
|
||||
if errors.As(err, &requiredError) {
|
||||
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "if_version"})
|
||||
} else {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "if_version", Err: err})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ListNodes(w, r, params)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ConnectNode operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ConnectNode(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
// ------------- Path parameter "id" -------------
|
||||
var id openapi_types.UUID
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "uuid"})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ConnectNode(w, r, id)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// DisconnectNode operation middleware
|
||||
func (siw *ServerInterfaceWrapper) DisconnectNode(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
// ------------- Path parameter "id" -------------
|
||||
var id openapi_types.UUID
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "uuid"})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.DisconnectNode(w, r, id)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListNotices operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListNotices(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ListNotices(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListPlans operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListPlans(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ListPlans(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// RedeemCode operation middleware
|
||||
func (siw *ServerInterfaceWrapper) RedeemCode(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.RedeemCode(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetUsage operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetUsage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params GetUsageParams
|
||||
|
||||
// ------------- Optional query parameter "days" -------------
|
||||
|
||||
err = runtime.BindQueryParameterWithOptions("form", true, false, "days", r.URL.Query(), ¶ms.Days, runtime.BindQueryParameterOptions{Type: "integer", Format: ""})
|
||||
if err != nil {
|
||||
var requiredError *runtime.RequiredParameterError
|
||||
if errors.As(err, &requiredError) {
|
||||
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "days"})
|
||||
} else {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "days", Err: err})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetUsage(w, r, params)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
type UnescapedCookieParamError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *UnescapedCookieParamError) Error() string {
|
||||
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
|
||||
}
|
||||
|
||||
func (e *UnescapedCookieParamError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type UnmarshalingParamError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *UnmarshalingParamError) Error() string {
|
||||
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
|
||||
}
|
||||
|
||||
func (e *UnmarshalingParamError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type RequiredParamError struct {
|
||||
ParamName string
|
||||
}
|
||||
|
||||
func (e *RequiredParamError) Error() string {
|
||||
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
|
||||
}
|
||||
|
||||
type RequiredHeaderError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *RequiredHeaderError) Error() string {
|
||||
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
|
||||
}
|
||||
|
||||
func (e *RequiredHeaderError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type InvalidParamFormatError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *InvalidParamFormatError) Error() string {
|
||||
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
|
||||
}
|
||||
|
||||
func (e *InvalidParamFormatError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type TooManyValuesForParamError struct {
|
||||
ParamName string
|
||||
Count int
|
||||
}
|
||||
|
||||
func (e *TooManyValuesForParamError) Error() string {
|
||||
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
|
||||
}
|
||||
|
||||
// Handler creates http.Handler with routing matching OpenAPI spec.
|
||||
func Handler(si ServerInterface) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{})
|
||||
}
|
||||
|
||||
type ChiServerOptions struct {
|
||||
BaseURL string
|
||||
BaseRouter chi.Router
|
||||
Middlewares []MiddlewareFunc
|
||||
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||
}
|
||||
|
||||
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
|
||||
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseURL: baseURL,
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
// HandlerWithOptions creates http.Handler with additional options
|
||||
func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler {
|
||||
r := options.BaseRouter
|
||||
|
||||
if r == nil {
|
||||
r = chi.NewRouter()
|
||||
}
|
||||
if options.ErrorHandlerFunc == nil {
|
||||
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
wrapper := ServerInterfaceWrapper{
|
||||
Handler: si,
|
||||
HandlerMiddlewares: options.Middlewares,
|
||||
ErrorHandlerFunc: options.ErrorHandlerFunc,
|
||||
}
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/ads/unlock", wrapper.AdsUnlock)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/code", wrapper.SendVerificationCode)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/login", wrapper.Login)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/refresh", wrapper.RefreshToken)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/register", wrapper.Register)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/me", wrapper.GetMe)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/me/devices", wrapper.ListDevices)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Delete(options.BaseURL+"/me/devices/{id}", wrapper.DeleteDevice)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/nodes", wrapper.ListNodes)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/nodes/{id}/connect", wrapper.ConnectNode)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/nodes/{id}/disconnect", wrapper.DisconnectNode)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/notices", wrapper.ListNotices)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/plans", wrapper.ListPlans)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/redeem", wrapper.RedeemCode)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/usage", wrapper.GetUsage)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Base64 encoded, compressed with deflate, json marshaled OpenAPI spec.
|
||||
// Stored as a slice of fixed-width chunks rather than one concatenated
|
||||
// const string: with thousands of chunks the chained `+` fold is several
|
||||
// times slower for the Go compiler than parsing a slice literal.
|
||||
var swaggerSpec = []string{
|
||||
"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",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
// after base64-decoding and flate-decompressing the embedded blob.
|
||||
func decodeSpec() ([]byte, error) {
|
||||
encoded := strings.Join(swaggerSpec, "")
|
||||
compressed, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error base64 decoding spec: %w", err)
|
||||
}
|
||||
zr := flate.NewReader(bytes.NewReader(compressed))
|
||||
var buf bytes.Buffer
|
||||
if _, err := buf.ReadFrom(zr); err != nil {
|
||||
return nil, fmt.Errorf("read flate: %w", err)
|
||||
}
|
||||
if err := zr.Close(); err != nil {
|
||||
return nil, fmt.Errorf("close flate reader: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var rawSpec = decodeSpecCached()
|
||||
|
||||
// a naive cache of the decoded OpenAPI spec
|
||||
func decodeSpecCached() func() ([]byte, error) {
|
||||
data, err := decodeSpec()
|
||||
return func() ([]byte, error) {
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
|
||||
// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
|
||||
func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
|
||||
res := make(map[string]func() ([]byte, error))
|
||||
if len(pathToFile) > 0 {
|
||||
res[pathToFile] = rawSpec
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// GetSpec returns the OpenAPI specification corresponding to the generated
|
||||
// code in this file. External references in the spec are resolved through
|
||||
// PathToRawSpec; externally-referenced files must be embedded in their
|
||||
// corresponding Go packages (via the import-mapping feature). URL-based
|
||||
// external refs are not supported.
|
||||
func GetSpec() (swagger *openapi3.T, err error) {
|
||||
resolvePath := PathToRawSpec("")
|
||||
|
||||
loader := openapi3.NewLoader()
|
||||
loader.IsExternalRefsAllowed = true
|
||||
loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
|
||||
pathToFile := url.String()
|
||||
pathToFile = path.Clean(pathToFile)
|
||||
getSpec, ok := resolvePath[pathToFile]
|
||||
if !ok {
|
||||
err1 := fmt.Errorf("path not found: %s", pathToFile)
|
||||
return nil, err1
|
||||
}
|
||||
return getSpec()
|
||||
}
|
||||
var specData []byte
|
||||
specData, err = rawSpec()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
swagger, err = loader.LoadFromData(specData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetSpecJSON returns the raw JSON bytes of the embedded OpenAPI
|
||||
// specification: decompressed but not unmarshaled. External references
|
||||
// are not resolved here; the bytes are the spec exactly as embedded by
|
||||
// codegen. The result is cached at package init time, so repeated calls
|
||||
// are cheap.
|
||||
func GetSpecJSON() ([]byte, error) {
|
||||
return rawSpec()
|
||||
}
|
||||
|
||||
// GetSwagger returns the OpenAPI specification corresponding to the
|
||||
// generated code in this file.
|
||||
//
|
||||
// Deprecated: GetSwagger predates kin-openapi renaming openapi3.Swagger
|
||||
// to openapi3.T. Use [GetSpec] instead. This wrapper is retained for
|
||||
// backwards compatibility.
|
||||
func GetSwagger() (*openapi3.T, error) {
|
||||
return GetSpec()
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
// Package gen provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.7.1 DO NOT EDIT.
|
||||
package gen
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||
)
|
||||
|
||||
const (
|
||||
BearerAuthScopes bearerAuthContextKey = "bearerAuth.Scopes"
|
||||
)
|
||||
|
||||
// Defines values for ConnectCredentialProtocol.
|
||||
const (
|
||||
Hysteria2 ConnectCredentialProtocol = "hysteria2"
|
||||
Reality ConnectCredentialProtocol = "reality"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the ConnectCredentialProtocol enum.
|
||||
func (e ConnectCredentialProtocol) Valid() bool {
|
||||
switch e {
|
||||
case Hysteria2:
|
||||
return true
|
||||
case Reality:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for DevicePlatform.
|
||||
const (
|
||||
Android DevicePlatform = "android"
|
||||
Ios DevicePlatform = "ios"
|
||||
Linux DevicePlatform = "linux"
|
||||
Macos DevicePlatform = "macos"
|
||||
Windows DevicePlatform = "windows"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the DevicePlatform enum.
|
||||
func (e DevicePlatform) Valid() bool {
|
||||
switch e {
|
||||
case Android:
|
||||
return true
|
||||
case Ios:
|
||||
return true
|
||||
case Linux:
|
||||
return true
|
||||
case Macos:
|
||||
return true
|
||||
case Windows:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for NodeTier.
|
||||
const (
|
||||
NodeTierFree NodeTier = "free"
|
||||
NodeTierPro NodeTier = "pro"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the NodeTier enum.
|
||||
func (e NodeTier) Valid() bool {
|
||||
switch e {
|
||||
case NodeTierFree:
|
||||
return true
|
||||
case NodeTierPro:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for PlanCode.
|
||||
const (
|
||||
PlanCodeFree PlanCode = "free"
|
||||
PlanCodePro PlanCode = "pro"
|
||||
PlanCodeTeam PlanCode = "team"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the PlanCode enum.
|
||||
func (e PlanCode) Valid() bool {
|
||||
switch e {
|
||||
case PlanCodeFree:
|
||||
return true
|
||||
case PlanCodePro:
|
||||
return true
|
||||
case PlanCodeTeam:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for RedeemResultPlanCode.
|
||||
const (
|
||||
RedeemResultPlanCodeFree RedeemResultPlanCode = "free"
|
||||
RedeemResultPlanCodePro RedeemResultPlanCode = "pro"
|
||||
RedeemResultPlanCodeTeam RedeemResultPlanCode = "team"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the RedeemResultPlanCode enum.
|
||||
func (e RedeemResultPlanCode) Valid() bool {
|
||||
switch e {
|
||||
case RedeemResultPlanCodeFree:
|
||||
return true
|
||||
case RedeemResultPlanCodePro:
|
||||
return true
|
||||
case RedeemResultPlanCodeTeam:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for SubscriptionInfoPlanCode.
|
||||
const (
|
||||
SubscriptionInfoPlanCodeFree SubscriptionInfoPlanCode = "free"
|
||||
SubscriptionInfoPlanCodePro SubscriptionInfoPlanCode = "pro"
|
||||
SubscriptionInfoPlanCodeTeam SubscriptionInfoPlanCode = "team"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the SubscriptionInfoPlanCode enum.
|
||||
func (e SubscriptionInfoPlanCode) Valid() bool {
|
||||
switch e {
|
||||
case SubscriptionInfoPlanCodeFree:
|
||||
return true
|
||||
case SubscriptionInfoPlanCodePro:
|
||||
return true
|
||||
case SubscriptionInfoPlanCodeTeam:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for SubscriptionInfoSource.
|
||||
const (
|
||||
Admin SubscriptionInfoSource = "admin"
|
||||
Code SubscriptionInfoSource = "code"
|
||||
Trial SubscriptionInfoSource = "trial"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the SubscriptionInfoSource enum.
|
||||
func (e SubscriptionInfoSource) Valid() bool {
|
||||
switch e {
|
||||
case Admin:
|
||||
return true
|
||||
case Code:
|
||||
return true
|
||||
case Trial:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for UserInfoStatus.
|
||||
const (
|
||||
Active UserInfoStatus = "active"
|
||||
Suspended UserInfoStatus = "suspended"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the UserInfoStatus enum.
|
||||
func (e UserInfoStatus) Valid() bool {
|
||||
switch e {
|
||||
case Active:
|
||||
return true
|
||||
case Suspended:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectCredential sing-box 连接凭证,支持 REALITY 主线 + Hysteria2 备线。
|
||||
// 每用户一个数据面 UUID(dp_uuid),与账号 UUID 解耦、可独立轮换;
|
||||
// 节点侧只见 dp_uuid,零账号信息,单节点被抄不泄露用户库。
|
||||
//
|
||||
// **字段稳定性**:本 schema 可能随数据面树(任务 #5)最终敲定后 bump,
|
||||
// 在正式实现前以该任务产出为权威依据。
|
||||
type ConnectCredential struct {
|
||||
// Flow VLESS flow 参数,REALITY 协议时使用(通常为 `xtls-rprx-vision`)
|
||||
Flow *string `json:"flow,omitempty"`
|
||||
|
||||
// Hysteria2 Hysteria2 备线参数(仅 protocol=hysteria2 或客户端协议降级时使用)
|
||||
Hysteria2 *Hysteria2Params `json:"hysteria2,omitempty"`
|
||||
|
||||
// Port 主协议端口(REALITY 用 TCP 443;Hysteria2 用 UDP 443)
|
||||
Port int `json:"port"`
|
||||
|
||||
// Protocol 优先协议(客户端按网络状况自动选;服务端返回当前最优)
|
||||
Protocol ConnectCredentialProtocol `json:"protocol"`
|
||||
|
||||
// Reality REALITY 协议参数(仅 protocol=reality 时有意义)
|
||||
Reality *RealityParams `json:"reality,omitempty"`
|
||||
|
||||
// Server 节点服务器地址(IP 或域名)
|
||||
Server string `json:"server"`
|
||||
|
||||
// TtlSeconds 凭证有效秒数。免费版 = 当日剩余分钟数 × 60;付费版 = 86400(24h,在线自动续期)。
|
||||
TtlSeconds int `json:"ttl_seconds"`
|
||||
|
||||
// Uuid 用户数据面 UUID(dp_uuid),即 VLESS uuid 字段。
|
||||
// Hysteria2 auth password 由同一 UUID 派生(客户端无需单独字段)。
|
||||
Uuid openapi_types.UUID `json:"uuid"`
|
||||
}
|
||||
|
||||
// ConnectCredentialProtocol 优先协议(客户端按网络状况自动选;服务端返回当前最优)
|
||||
type ConnectCredentialProtocol string
|
||||
|
||||
// Device defines model for Device.
|
||||
type Device struct {
|
||||
// LastSeen 最后活跃时间(UTC ISO-8601)
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
|
||||
// Name 设备名称(客户端上报,如「iPhone 15 Pro」)
|
||||
Name string `json:"name"`
|
||||
|
||||
// Platform 设备平台
|
||||
Platform DevicePlatform `json:"platform"`
|
||||
|
||||
// Uuid 设备 UUID
|
||||
Uuid openapi_types.UUID `json:"uuid"`
|
||||
}
|
||||
|
||||
// DevicePlatform 设备平台
|
||||
type DevicePlatform string
|
||||
|
||||
// Error 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type Error struct {
|
||||
// Code 机器可读错误码,例如 `auth.code_expired` / `redeem.already_redeemed`
|
||||
Code string `json:"code"`
|
||||
|
||||
// MessageEn 英文错误文案(面向用户展示)
|
||||
MessageEn string `json:"message_en"`
|
||||
|
||||
// MessageZh 中文错误文案(面向用户展示)
|
||||
MessageZh string `json:"message_zh"`
|
||||
}
|
||||
|
||||
// Hysteria2Params Hysteria2 备线参数(仅 protocol=hysteria2 或客户端协议降级时使用)
|
||||
type Hysteria2Params struct {
|
||||
// Obfs 混淆模式(可选,如 `salamander`);null 表示不启用混淆
|
||||
Obfs *string `json:"obfs,omitempty"`
|
||||
|
||||
// Password Hysteria2 auth password(由 dp_uuid 派生,服务端同源生成)
|
||||
Password string `json:"password"`
|
||||
|
||||
// Port Hysteria2 UDP 端口(通常 443)
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
// Me defines model for Me.
|
||||
type Me struct {
|
||||
Subscription SubscriptionInfo `json:"subscription"`
|
||||
TodayUsage TodayUsageSummary `json:"today_usage"`
|
||||
User UserInfo `json:"user"`
|
||||
}
|
||||
|
||||
// Node 节点目录条目。**不含任何密钥或连接参数**(reality_pbk/sni/password 等),
|
||||
// 这些信息仅在 POST /nodes/{id}/connect 成功后下发。
|
||||
type Node struct {
|
||||
// Load 节点当前负载百分比(0~100),由 agent 每 30s 上报到 Redis
|
||||
Load int `json:"load"`
|
||||
|
||||
// NameEn 节点英文名称
|
||||
NameEn string `json:"name_en"`
|
||||
|
||||
// NameZh 节点中文名称
|
||||
NameZh string `json:"name_zh"`
|
||||
|
||||
// Region 地区代码,2 字母大写(HK/JP/SG/US …)
|
||||
Region string `json:"region"`
|
||||
|
||||
// Signal 信号格数(0~4),由 agent 上报延迟换算,客户端展示信号条
|
||||
Signal int `json:"signal"`
|
||||
|
||||
// Tier 节点等级(free 节点免费版可用,pro 节点需付费订阅)
|
||||
Tier NodeTier `json:"tier"`
|
||||
|
||||
// Uuid 节点 UUID(connect 时作为 path 参数)
|
||||
Uuid openapi_types.UUID `json:"uuid"`
|
||||
}
|
||||
|
||||
// NodeTier 节点等级(free 节点免费版可用,pro 节点需付费订阅)
|
||||
type NodeTier string
|
||||
|
||||
// NodeDirectory defines model for NodeDirectory.
|
||||
type NodeDirectory struct {
|
||||
Nodes []Node `json:"nodes"`
|
||||
|
||||
// Version 目录全局版本号;任何节点变更 bump 此值,客户端据此决定是否刷新缓存
|
||||
Version int64 `json:"version"`
|
||||
}
|
||||
|
||||
// Notice defines model for Notice.
|
||||
type Notice struct {
|
||||
// BodyEn 公告英文正文(Markdown)
|
||||
BodyEn string `json:"body_en"`
|
||||
|
||||
// BodyZh 公告中文正文(Markdown)
|
||||
BodyZh string `json:"body_zh"`
|
||||
|
||||
// Id 公告 UUID
|
||||
Id openapi_types.UUID `json:"id"`
|
||||
|
||||
// PublishedAt 发布时间(UTC ISO-8601)
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
|
||||
// TitleEn 公告英文标题
|
||||
TitleEn string `json:"title_en"`
|
||||
|
||||
// TitleZh 公告中文标题
|
||||
TitleZh string `json:"title_zh"`
|
||||
}
|
||||
|
||||
// Plan defines model for Plan.
|
||||
type Plan struct {
|
||||
// AdGate 首次连接是否需要激励广告解锁(仅 free=true)
|
||||
AdGate bool `json:"ad_gate"`
|
||||
|
||||
// Code 套餐唯一代码
|
||||
Code PlanCode `json:"code"`
|
||||
|
||||
// DailyMinutes 每日可用分钟数上限;null 表示无限制(pro/team)
|
||||
DailyMinutes *int `json:"daily_minutes,omitempty"`
|
||||
|
||||
// MaxDevices 最大同时在线设备数(free=1, pro=5, team=10)
|
||||
MaxDevices int `json:"max_devices"`
|
||||
}
|
||||
|
||||
// PlanCode 套餐唯一代码
|
||||
type PlanCode string
|
||||
|
||||
// RealityParams REALITY 协议参数(仅 protocol=reality 时有意义)
|
||||
type RealityParams struct {
|
||||
// PublicKey REALITY 服务端公钥(x25519)
|
||||
PublicKey string `json:"public_key"`
|
||||
|
||||
// ShortId REALITY short ID(十六进制字符串)
|
||||
ShortId string `json:"short_id"`
|
||||
|
||||
// Sni TLS SNI 伪装域名
|
||||
Sni string `json:"sni"`
|
||||
}
|
||||
|
||||
// RedeemResult 激活码兑换结果。幂等设计:同一用户重复提交同一码返回首次结果(非报错)。
|
||||
type RedeemResult struct {
|
||||
// ExpiresAt 兑换后的订阅到期时间(UTC ISO-8601)
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
|
||||
// ExtendedDays 本次顺延的天数(叠加而非覆盖)
|
||||
ExtendedDays int `json:"extended_days"`
|
||||
|
||||
// PlanCode 本次兑换激活的套餐代码
|
||||
PlanCode RedeemResultPlanCode `json:"plan_code"`
|
||||
}
|
||||
|
||||
// RedeemResultPlanCode 本次兑换激活的套餐代码
|
||||
type RedeemResultPlanCode string
|
||||
|
||||
// SubscriptionInfo defines model for SubscriptionInfo.
|
||||
type SubscriptionInfo struct {
|
||||
// ExpiresAt 订阅到期时间(UTC ISO-8601),免费版无到期时间时为 null
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
|
||||
// PlanCode 当前套餐代码
|
||||
PlanCode SubscriptionInfoPlanCode `json:"plan_code"`
|
||||
|
||||
// Source 订阅来源(试用 / 激活码 / 管理员)
|
||||
Source SubscriptionInfoSource `json:"source"`
|
||||
}
|
||||
|
||||
// SubscriptionInfoPlanCode 当前套餐代码
|
||||
type SubscriptionInfoPlanCode string
|
||||
|
||||
// SubscriptionInfoSource 订阅来源(试用 / 激活码 / 管理员)
|
||||
type SubscriptionInfoSource string
|
||||
|
||||
// TodayUsageSummary defines model for TodayUsageSummary.
|
||||
type TodayUsageSummary struct {
|
||||
// AdUnlocked 免费版今日是否已完成激励广告解锁
|
||||
AdUnlocked *bool `json:"ad_unlocked,omitempty"`
|
||||
|
||||
// MinutesRemaining 今日剩余分钟数,付费无限制时为 null
|
||||
MinutesRemaining *int `json:"minutes_remaining"`
|
||||
|
||||
// MinutesUsed 今日已使用分钟数
|
||||
MinutesUsed int `json:"minutes_used"`
|
||||
}
|
||||
|
||||
// TokenPair defines model for TokenPair.
|
||||
type TokenPair struct {
|
||||
// AccessToken JWT access token,有效期 15 分钟
|
||||
AccessToken string `json:"access_token"`
|
||||
|
||||
// ExpiresIn access_token 有效秒数(固定 900)
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
|
||||
// RefreshToken Refresh token,有效期 30 天
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// UsagePoint 单日用量数据点(无目的地/DNS/流量内容,仅字节数与分钟数,符合无日志承诺)
|
||||
type UsagePoint struct {
|
||||
// BytesDown 当日下行字节数
|
||||
BytesDown int64 `json:"bytes_down"`
|
||||
|
||||
// BytesUp 当日上行字节数
|
||||
BytesUp int64 `json:"bytes_up"`
|
||||
|
||||
// Date 日期(UTC,YYYY-MM-DD)
|
||||
Date openapi_types.Date `json:"date"`
|
||||
|
||||
// MinutesUsed 当日已用分钟数
|
||||
MinutesUsed int `json:"minutes_used"`
|
||||
}
|
||||
|
||||
// UserInfo defines model for UserInfo.
|
||||
type UserInfo struct {
|
||||
Email openapi_types.Email `json:"email"`
|
||||
|
||||
// Status 账户状态
|
||||
Status UserInfoStatus `json:"status"`
|
||||
|
||||
// Uuid 用户全局唯一标识(对外口径,永不暴露自增 ID)
|
||||
Uuid openapi_types.UUID `json:"uuid"`
|
||||
}
|
||||
|
||||
// UserInfoStatus 账户状态
|
||||
type UserInfoStatus string
|
||||
|
||||
// BadRequest 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type BadRequest = Error
|
||||
|
||||
// Conflict 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type Conflict = Error
|
||||
|
||||
// Forbidden 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type Forbidden = Error
|
||||
|
||||
// Internal 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type Internal = Error
|
||||
|
||||
// NotFound 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type NotFound = Error
|
||||
|
||||
// TooManyRequests 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type TooManyRequests = Error
|
||||
|
||||
// Unauthorized 统一错误响应体,三字段均 required。文案遵守脱敏口径(不含红线词)。
|
||||
type Unauthorized = Error
|
||||
|
||||
// bearerAuthContextKey is the context key for bearerAuth security scheme
|
||||
type bearerAuthContextKey string
|
||||
|
||||
// AdsUnlockJSONBody defines parameters for AdsUnlock.
|
||||
type AdsUnlockJSONBody struct {
|
||||
// AdToken 广告 SDK 签发的服务端回执 token
|
||||
AdToken string `json:"ad_token"`
|
||||
|
||||
// DeviceId 发起广告的设备 UUID
|
||||
DeviceId openapi_types.UUID `json:"device_id"`
|
||||
}
|
||||
|
||||
// SendVerificationCodeJSONBody defines parameters for SendVerificationCode.
|
||||
type SendVerificationCodeJSONBody struct {
|
||||
Email openapi_types.Email `json:"email"`
|
||||
}
|
||||
|
||||
// LoginJSONBody defines parameters for Login.
|
||||
type LoginJSONBody struct {
|
||||
Email openapi_types.Email `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// RefreshTokenJSONBody defines parameters for RefreshToken.
|
||||
type RefreshTokenJSONBody struct {
|
||||
// RefreshToken 有效的 refresh token(30 天有效期)
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// RegisterJSONBody defines parameters for Register.
|
||||
type RegisterJSONBody struct {
|
||||
// Code 6 位数字验证码
|
||||
Code string `json:"code"`
|
||||
Email openapi_types.Email `json:"email"`
|
||||
|
||||
// Password 用户密码,argon2id 存储
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// ListNodesParams defines parameters for ListNodes.
|
||||
type ListNodesParams struct {
|
||||
// IfVersion 客户端已缓存的目录版本号;命中则返回 304
|
||||
IfVersion *int64 `form:"if_version,omitempty" json:"if_version,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectNodeJSONBody defines parameters for ConnectNode.
|
||||
type ConnectNodeJSONBody struct {
|
||||
// DeviceId 发起连接的设备 UUID
|
||||
DeviceId openapi_types.UUID `json:"device_id"`
|
||||
}
|
||||
|
||||
// DisconnectNodeJSONBody defines parameters for DisconnectNode.
|
||||
type DisconnectNodeJSONBody struct {
|
||||
// DeviceId 发起断开的设备 UUID
|
||||
DeviceId openapi_types.UUID `json:"device_id"`
|
||||
}
|
||||
|
||||
// RedeemCodeJSONBody defines parameters for RedeemCode.
|
||||
type RedeemCodeJSONBody struct {
|
||||
// Code Crockford Base32 激活码,16 位含校验位
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GetUsageParams defines parameters for GetUsage.
|
||||
type GetUsageParams struct {
|
||||
// Days 查询最近几天,默认 7,最大 90
|
||||
Days *int `form:"days,omitempty" json:"days,omitempty"`
|
||||
}
|
||||
|
||||
// AdsUnlockJSONRequestBody defines body for AdsUnlock for application/json ContentType.
|
||||
type AdsUnlockJSONRequestBody AdsUnlockJSONBody
|
||||
|
||||
// SendVerificationCodeJSONRequestBody defines body for SendVerificationCode for application/json ContentType.
|
||||
type SendVerificationCodeJSONRequestBody SendVerificationCodeJSONBody
|
||||
|
||||
// LoginJSONRequestBody defines body for Login for application/json ContentType.
|
||||
type LoginJSONRequestBody LoginJSONBody
|
||||
|
||||
// RefreshTokenJSONRequestBody defines body for RefreshToken for application/json ContentType.
|
||||
type RefreshTokenJSONRequestBody RefreshTokenJSONBody
|
||||
|
||||
// RegisterJSONRequestBody defines body for Register for application/json ContentType.
|
||||
type RegisterJSONRequestBody RegisterJSONBody
|
||||
|
||||
// ConnectNodeJSONRequestBody defines body for ConnectNode for application/json ContentType.
|
||||
type ConnectNodeJSONRequestBody ConnectNodeJSONBody
|
||||
|
||||
// DisconnectNodeJSONRequestBody defines body for DisconnectNode for application/json ContentType.
|
||||
type DisconnectNodeJSONRequestBody DisconnectNodeJSONBody
|
||||
|
||||
// RedeemCodeJSONRequestBody defines body for RedeemCode for application/json ContentType.
|
||||
type RedeemCodeJSONRequestBody RedeemCodeJSONBody
|
||||
@@ -557,7 +557,7 @@ components:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: JWT RS256,`Authorization: Bearer <access_token>`
|
||||
description: "JWT RS256,`Authorization: Bearer <access_token>`"
|
||||
|
||||
# ── 统一错误响应 ────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
|
||||
"github.com/wangjia/pangolin/server/api/gen"
|
||||
"github.com/wangjia/pangolin/server/internal/httpapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -27,12 +30,18 @@ func main() {
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// Health check — outside the versioned API.
|
||||
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
})
|
||||
|
||||
// Mount all /v1/... routes. HandlerFromMuxWithBaseURL registers every route
|
||||
// from the OpenAPI spec onto the provided chi router with the given prefix,
|
||||
// so the router itself is the http.Handler we serve.
|
||||
gen.HandlerFromMuxWithBaseURL(&httpapi.UnimplementedServer{}, r, "/v1")
|
||||
|
||||
log.Printf("pangolin server listening on %s", *addr)
|
||||
if err := http.ListenAndServe(*addr, r); err != nil {
|
||||
log.Fatalf("server error: %v", err)
|
||||
|
||||
+9
-7
@@ -4,10 +4,12 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/alicebob/miniredis/v2 v2.38.0
|
||||
github.com/getkin/kin-openapi v0.135.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.7.1
|
||||
github.com/oapi-codegen/runtime v1.4.1
|
||||
github.com/redis/go-redis/v9 v9.20.1
|
||||
github.com/testcontainers/testcontainers-go v0.34.0
|
||||
github.com/testcontainers/testcontainers-go/modules/mysql v0.34.0
|
||||
@@ -45,9 +47,10 @@ require (
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/apache/arrow/go/v10 v10.0.1 // indirect
|
||||
github.com/apache/thrift v0.16.0 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.49.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect
|
||||
@@ -87,8 +90,7 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
|
||||
github.com/getkin/kin-openapi v0.135.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
@@ -96,7 +98,7 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -138,8 +140,8 @@ require (
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.0.0 // indirect
|
||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
|
||||
@@ -217,7 +219,7 @@ require (
|
||||
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/tools/godoc v0.1.0-deprecated // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
|
||||
+26
-12
@@ -677,20 +677,24 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
|
||||
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
|
||||
github.com/alicebob/miniredis/v2 v2.38.0 h1:nZAzCR+Lj+Vxk4ZXzm2NuKq2O33RXj1XxJ2e2uP9jiw=
|
||||
github.com/alicebob/miniredis/v2 v2.38.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI=
|
||||
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
|
||||
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
|
||||
github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY=
|
||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/aws/aws-sdk-go v1.49.6 h1:yNldzF5kzLBRvKlKz1S0bkvc2+04R1kt13KfBWQBfFA=
|
||||
github.com/aws/aws-sdk-go v1.49.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk=
|
||||
@@ -735,6 +739,7 @@ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYE
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
@@ -862,8 +867,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsouza/fake-gcs-server v1.17.0 h1:OeH75kBZcZa3ZE+zz/mFdJ2btt9FgqfjI7gIh9+5fvk=
|
||||
github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
|
||||
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/getkin/kin-openapi v0.135.0 h1:751SjYfbiwqukYuVjwYEIKNfrSwS5YpA7DZnKSwQgtg=
|
||||
github.com/getkin/kin-openapi v0.135.0/go.mod h1:6dd5FJl6RdX4usBtFBaQhk9q62Yb2J0Mk5IhUO/QqFI=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -909,8 +914,9 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 h1:N/MD/sr6o61X+iZBAT2qEUF023s4KbA8RWfKzl0L6MQ=
|
||||
github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
|
||||
@@ -1166,6 +1172,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
|
||||
@@ -1224,16 +1231,18 @@ github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUt
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
@@ -1283,8 +1292,12 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
|
||||
github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.7.1 h1:a7Ab7YlpqkVG5HKrTaeFstm32Z5QOnyjnbsCO0jiMYM=
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.7.1/go.mod h1:qzFy6iuobJw/hD1aRILee4G87/ShmhR0xYCwcUtZMCw=
|
||||
github.com/oapi-codegen/runtime v1.4.1 h1:9nwLoI+KrWxzbBcp0jO/R8uXqbik/HUyCvPeU68Y/qo=
|
||||
github.com/oapi-codegen/runtime v1.4.1/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU=
|
||||
github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48=
|
||||
github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM=
|
||||
github.com/oasdiff/yaml3 v0.0.9 h1:rWPrKccrdUm8J0F3sGuU+fuh9+1K/RdJlWF7O/9yw2g=
|
||||
@@ -1383,6 +1396,7 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
@@ -1415,8 +1429,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
|
||||
github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
|
||||
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
|
||||
@@ -1546,8 +1560,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -1851,8 +1865,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// Package httpapi contains HTTP API handler stubs for the Pangolin v1 API.
|
||||
// All endpoints return HTTP 501 with a structured bilingual error body until
|
||||
// real business logic is wired in (tasks #2-#8).
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||
|
||||
"github.com/wangjia/pangolin/server/api/gen"
|
||||
)
|
||||
|
||||
// notImplError is the fixed JSON error body returned by every 501 stub.
|
||||
// Intentionally hand-written here (not from internal/apierr) so that this
|
||||
// package stays free of dependencies on task-1f which runs in parallel.
|
||||
var notImplError = struct {
|
||||
Code string `json:"code"`
|
||||
MessageZH string `json:"message_zh"`
|
||||
MessageEn string `json:"message_en"`
|
||||
}{
|
||||
Code: "NOT_IMPLEMENTED",
|
||||
MessageZH: "接口尚未实现",
|
||||
MessageEn: "Not implemented yet",
|
||||
}
|
||||
|
||||
// writeNotImpl writes a 501 Not Implemented response with the structured error body.
|
||||
func writeNotImpl(w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
_ = json.NewEncoder(w).Encode(notImplError)
|
||||
}
|
||||
|
||||
// UnimplementedServer satisfies gen.ServerInterface and returns 501 for every
|
||||
// endpoint. Individual methods should be replaced as each module is implemented.
|
||||
type UnimplementedServer struct{}
|
||||
|
||||
// Compile-time assertion: UnimplementedServer must satisfy ServerInterface.
|
||||
var _ gen.ServerInterface = (*UnimplementedServer)(nil)
|
||||
|
||||
// ── Auth ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (s *UnimplementedServer) SendVerificationCode(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) Register(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) Login(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
// ── Account ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func (s *UnimplementedServer) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) ListDevices(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) DeleteDevice(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
// ── Commerce ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func (s *UnimplementedServer) RedeemCode(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) AdsUnlock(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) ListPlans(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
// ── Nodes ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func (s *UnimplementedServer) ListNodes(w http.ResponseWriter, r *http.Request, params gen.ListNodesParams) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) ConnectNode(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) DisconnectNode(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
// ── Usage & Notices ──────────────────────────────────────────────────────────
|
||||
|
||||
func (s *UnimplementedServer) GetUsage(w http.ResponseWriter, r *http.Request, params gen.GetUsageParams) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
|
||||
func (s *UnimplementedServer) ListNotices(w http.ResponseWriter, r *http.Request) {
|
||||
writeNotImpl(w)
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package httpapi_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
|
||||
"github.com/wangjia/pangolin/server/api/gen"
|
||||
"github.com/wangjia/pangolin/server/internal/httpapi"
|
||||
)
|
||||
|
||||
func newTestServer() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Recoverer)
|
||||
gen.HandlerFromMuxWithBaseURL(&httpapi.UnimplementedServer{}, r, "/v1")
|
||||
return r
|
||||
}
|
||||
|
||||
type errorBody struct {
|
||||
Code string `json:"code"`
|
||||
MessageZH string `json:"message_zh"`
|
||||
MessageEn string `json:"message_en"`
|
||||
}
|
||||
|
||||
func Test501StructuredBody(t *testing.T) {
|
||||
srv := newTestServer()
|
||||
|
||||
// All defined /v1 routes should return 501 with the structured body.
|
||||
routes := []struct {
|
||||
method string
|
||||
path string
|
||||
}{
|
||||
{"GET", "/v1/plans"},
|
||||
{"GET", "/v1/nodes"},
|
||||
{"GET", "/v1/me"},
|
||||
{"GET", "/v1/me/devices"},
|
||||
{"GET", "/v1/usage"},
|
||||
{"GET", "/v1/notices"},
|
||||
{"POST", "/v1/auth/code"},
|
||||
{"POST", "/v1/auth/register"},
|
||||
{"POST", "/v1/auth/login"},
|
||||
{"POST", "/v1/auth/refresh"},
|
||||
{"POST", "/v1/redeem"},
|
||||
{"POST", "/v1/ads/unlock"},
|
||||
{"DELETE", "/v1/me/devices/00000000-0000-0000-0000-000000000001"},
|
||||
{"POST", "/v1/nodes/00000000-0000-0000-0000-000000000001/connect"},
|
||||
{"POST", "/v1/nodes/00000000-0000-0000-0000-000000000001/disconnect"},
|
||||
}
|
||||
|
||||
for _, tc := range routes {
|
||||
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
||||
req := httptest.NewRequest(tc.method, tc.path, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotImplemented {
|
||||
t.Errorf("expected 501, got %d", rec.Code)
|
||||
}
|
||||
|
||||
var body errorBody
|
||||
if err := json.NewDecoder(rec.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("could not decode body: %v", err)
|
||||
}
|
||||
if body.Code != "NOT_IMPLEMENTED" {
|
||||
t.Errorf("code: want NOT_IMPLEMENTED, got %q", body.Code)
|
||||
}
|
||||
if body.MessageZH == "" {
|
||||
t.Error("message_zh is empty")
|
||||
}
|
||||
if body.MessageEn == "" {
|
||||
t.Error("message_en is empty")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test404ForUndefinedRoutes(t *testing.T) {
|
||||
srv := newTestServer()
|
||||
|
||||
paths := []string{
|
||||
"/v1/nonexistent",
|
||||
"/v2/plans",
|
||||
"/totally/wrong",
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
t.Run(p, func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", p, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Errorf("path %s: expected 404, got %d", p, rec.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthzNotAffected(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"status":"ok"}`))
|
||||
})
|
||||
gen.HandlerFromMuxWithBaseURL(&httpapi.UnimplementedServer{}, r, "/v1")
|
||||
|
||||
req := httptest.NewRequest("GET", "/healthz", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Errorf("healthz: expected 200, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user