refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps

- Merge all files inside openflare-server to the repository root directory.
- Relocate agent, relay, and flared subprojects from internal/ to internal/apps/.
- Combine docker-compose files and update build context paths to root.
- Update GitHub workflows and Dockerfiles to refer to new directories and package names.
- Rewrite Go package imports across all files.
- Resolve database renew test race condition and clean up docs.
This commit is contained in:
ryan
2026-06-19 14:23:29 +08:00
parent 19d476ed7f
commit 63cd906cfc
1064 changed files with 366 additions and 1397 deletions
@@ -0,0 +1,53 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package proxy_route
const (
errProxyRouteNotFound = "proxy route not found"
errProxyRouteIdentityExists = "proxy route identity already exists"
errProxyRouteSiteNameExists = "site_name already exists"
errProxyRouteDomainExists = "domain %s already exists"
errProxyRouteSiteNameEmpty = "site_name cannot be empty"
errProxyRouteDomainRequired = "at least one domain is required"
errProxyRouteDomainInvalid = "domain format is invalid"
errProxyRouteDomainMismatch = "domain must match domains[0]"
errProxyRouteOriginEmpty = "origin_url cannot be empty"
errProxyRouteOriginInvalid = "origin URL format is invalid"
errProxyRouteOriginScheme = "origin URL must start with http:// or https://"
errProxyRouteOriginHostInvalid = "origin_host format is invalid"
errProxyRouteUpstreamRequired = "at least one upstream is required"
errProxyRouteUpstreamScheme = "all upstreams must use the same scheme"
errProxyRouteUpstreamPath = "multi-upstream mode does not support origin paths"
errProxyRouteUpstreamQuery = "multi-upstream mode does not support origin query strings"
errProxyRouteOriginNotFound = "selected origin does not exist"
errProxyRouteCertNotFound = "selected certificate does not exist"
errProxyRouteCertRequired = "must select a certificate when HTTPS is enabled"
errProxyRouteCertDomainLength = "domain_cert_ids must match domains length"
errProxyRouteRedirectHTTP = "redirect_http requires enable_https"
errProxyRouteBasicAuth = "basic_auth_username and basic_auth_password cannot be empty when basic auth is enabled"
errProxyRouteLimitRate = "limit_rate must be a number or use the 512k / 1m format"
errProxyRouteCachePolicy = "cache policy is not supported"
errProxyRouteCacheSuffix = "cache suffix format is invalid"
errProxyRouteCachePath = "cache path rule format is invalid"
errProxyRouteCacheSuffixReq = "at least one suffix is required"
errProxyRouteCachePrefixReq = "at least one path prefix is required"
errProxyRouteCacheExactReq = "at least one exact path is required"
errProxyRouteHeaderKeyEmpty = "custom header key cannot be empty"
errProxyRouteHeaderKeyInvalid = "custom header key format is invalid"
errProxyRouteHeaderNewline = "custom headers cannot contain newlines"
errProxyRouteTunnelNodeReq = "tunnel_node_id is required for tunnel upstream"
errProxyRouteTunnelNodeMissing = "tunnel client node does not exist"
errProxyRouteTunnelNodeType = "tunnel_node_id must reference a tunnel_client node"
errProxyRouteTunnelAddrReq = "tunnel_target_addr is required for tunnel upstream"
errProxyRouteTunnelProtocol = "tunnel_target_protocol must be http or https"
errProxyRoutePagesProjectReq = "pages_project_id is required for Pages upstream"
errProxyRoutePagesNotFound = "Pages 项目不存在"
errProxyRoutePagesDisabled = "Pages 项目未启用"
errProxyRoutePagesNoDeploy = "Pages 项目没有激活部署"
errProxyRouteOriginSchemeOnly = "源站协议仅支持 http 或 https"
errProxyRouteOriginPort = "端口格式不合法"
errProxyRouteOriginPortEmpty = "端口不能为空"
errProxyRouteOriginURI = "源站路径需以 / 或 ? 开头"
errProxyRouteOriginURIProto = "源站路径不能包含协议"
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,430 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package proxy_route
import (
"context"
"encoding/json"
"errors"
"strings"
"time"
"github.com/Rain-kl/Wavelet/internal/model"
)
// CustomHeaderInput 自定义响应头。
type CustomHeaderInput struct {
Key string `json:"key"`
Value string `json:"value"`
}
// Input 代理规则创建/更新请求。
type Input struct {
SiteName string `json:"site_name"`
Domain string `json:"domain"`
Domains []string `json:"domains"`
OriginID *uint `json:"origin_id"`
OriginURL string `json:"origin_url"`
OriginScheme string `json:"origin_scheme"`
OriginAddress string `json:"origin_address"`
OriginPort string `json:"origin_port"`
OriginURI string `json:"origin_uri"`
OriginHost string `json:"origin_host"`
Upstreams []string `json:"upstreams"`
Enabled bool `json:"enabled"`
EnableHTTPS bool `json:"enable_https"`
CertID *uint `json:"cert_id"`
CertIDs []uint `json:"cert_ids"`
DomainCertIDs []uint `json:"domain_cert_ids"`
RedirectHTTP bool `json:"redirect_http"`
LimitConnPerServer int `json:"limit_conn_per_server"`
LimitConnPerIP int `json:"limit_conn_per_ip"`
LimitRate string `json:"limit_rate"`
CacheEnabled bool `json:"cache_enabled"`
CachePolicy string `json:"cache_policy"`
CacheRules []string `json:"cache_rules"`
CustomHeaders []CustomHeaderInput `json:"custom_headers"`
BasicAuthEnabled bool `json:"basic_auth_enabled"`
BasicAuthUsername string `json:"basic_auth_username"`
BasicAuthPassword string `json:"basic_auth_password"`
Remark string `json:"remark"`
UpstreamType string `json:"upstream_type"`
TunnelNodeID *uint `json:"tunnel_node_id"`
TunnelID *uint `json:"tunnel_id"`
TunnelTargetAddr string `json:"tunnel_target_addr"`
TunnelTargetProtocol string `json:"tunnel_target_protocol"`
PagesProjectID *uint `json:"pages_project_id"`
}
// View 代理规则视图。
type View struct {
ID uint `json:"id"`
SiteName string `json:"site_name"`
Domain string `json:"domain"`
Domains []string `json:"domains"`
PrimaryDomain string `json:"primary_domain"`
DomainCount int `json:"domain_count"`
OriginID *uint `json:"origin_id"`
OriginURL string `json:"origin_url"`
OriginHost string `json:"origin_host"`
Upstreams string `json:"upstreams"`
UpstreamList []string `json:"upstream_list"`
Enabled bool `json:"enabled"`
EnableHTTPS bool `json:"enable_https"`
CertID *uint `json:"cert_id"`
CertIDs []uint `json:"cert_ids"`
DomainCertIDs []uint `json:"domain_cert_ids"`
RedirectHTTP bool `json:"redirect_http"`
LimitConnPerServer int `json:"limit_conn_per_server"`
LimitConnPerIP int `json:"limit_conn_per_ip"`
LimitRate string `json:"limit_rate"`
CacheEnabled bool `json:"cache_enabled"`
CachePolicy string `json:"cache_policy"`
CacheRules string `json:"cache_rules"`
CacheRuleList []string `json:"cache_rule_list"`
CustomHeaders string `json:"custom_headers"`
CustomHeaderList []CustomHeaderInput `json:"custom_header_list"`
BasicAuthEnabled bool `json:"basic_auth_enabled"`
BasicAuthUsername string `json:"basic_auth_username"`
BasicAuthPassword string `json:"basic_auth_password"`
Remark string `json:"remark"`
UpstreamType string `json:"upstream_type"`
TunnelNodeID *uint `json:"tunnel_node_id"`
TunnelID *uint `json:"tunnel_id"`
TunnelTargetAddr string `json:"tunnel_target_addr"`
TunnelTargetProtocol string `json:"tunnel_target_protocol"`
PagesProjectID *uint `json:"pages_project_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ListProxyRoutes 列出全部代理规则。
func ListProxyRoutes(ctx context.Context) ([]*View, error) {
routes, err := model.ListProxyRoutes(ctx)
if err != nil {
return nil, err
}
return buildProxyRouteViews(ctx, routes)
}
// GetProxyRoute 获取代理规则详情。
func GetProxyRoute(ctx context.Context, id uint) (*View, error) {
route, err := model.GetProxyRouteByID(ctx, id)
if err != nil {
return nil, err
}
return buildProxyRouteView(ctx, route)
}
// CreateProxyRoute 创建代理规则。
func CreateProxyRoute(ctx context.Context, input Input) (*View, error) {
route, err := buildProxyRoute(ctx, nil, input)
if err != nil {
return nil, err
}
if err = model.CreateProxyRouteRecord(ctx, route); err != nil {
if isUniqueConstraintError(err) {
return nil, errors.New(errProxyRouteIdentityExists)
}
return nil, err
}
return buildProxyRouteView(ctx, route)
}
// UpdateProxyRoute 更新代理规则。
func UpdateProxyRoute(ctx context.Context, id uint, input Input) (*View, error) {
route, err := model.GetProxyRouteByID(ctx, id)
if err != nil {
return nil, err
}
route, err = buildProxyRoute(ctx, route, input)
if err != nil {
return nil, err
}
if err = model.UpdateProxyRouteRecord(ctx, route); err != nil {
if isUniqueConstraintError(err) {
return nil, errors.New(errProxyRouteIdentityExists)
}
return nil, err
}
return buildProxyRouteView(ctx, route)
}
// DeleteProxyRoute 删除代理规则。
func DeleteProxyRoute(ctx context.Context, id uint) error {
if _, err := model.GetProxyRouteByID(ctx, id); err != nil {
return err
}
return model.DeleteProxyRouteRecord(ctx, id)
}
func buildProxyRoute(ctx context.Context, route *model.ProxyRoute, input Input) (*model.ProxyRoute, error) {
domains, err := normalizeProxyRouteDomainsInput(route, input.Domain, input.Domains)
if err != nil {
return nil, err
}
domain := domains[0]
siteName := normalizeProxyRouteSiteNameInput(route, input.SiteName, domain)
upstreamType := normalizeUpstreamType(input.UpstreamType)
var originURL string
var originID *uint
var upstreams []string
if upstreamType == "tunnel" {
originURL = "http://127.0.0.1"
upstreams = []string{originURL}
} else if upstreamType == "pages" {
if err := validatePagesRouteInput(ctx, input.PagesProjectID); err != nil {
return nil, err
}
originURL = "http://127.0.0.1"
upstreams = []string{originURL}
} else {
originURL, originID, err = resolveProxyRoutePrimaryOrigin(ctx, input)
if err != nil {
return nil, err
}
upstreams, err = normalizeUpstreams(originURL, input.Upstreams)
if err != nil {
return nil, err
}
}
originHost := strings.TrimSpace(input.OriginHost)
remark := strings.TrimSpace(input.Remark)
cachePolicy := strings.TrimSpace(input.CachePolicy)
cacheRules, err := normalizeCacheRules(input.CacheEnabled, cachePolicy, input.CacheRules)
if err != nil {
return nil, err
}
customHeaders, err := normalizeCustomHeaders(input.CustomHeaders)
if err != nil {
return nil, err
}
limitConnPerServer, err := normalizeProxyRouteLimitConnValue(input.LimitConnPerServer, "limit_conn_per_server")
if err != nil {
return nil, err
}
limitConnPerIP, err := normalizeProxyRouteLimitConnValue(input.LimitConnPerIP, "limit_conn_per_ip")
if err != nil {
return nil, err
}
limitRate, err := normalizeProxyRouteLimitRate(input.LimitRate)
if err != nil {
return nil, err
}
cacheRulesJSON, err := json.Marshal(cacheRules)
if err != nil {
return nil, err
}
upstreamsJSON, err := json.Marshal(upstreams)
if err != nil {
return nil, err
}
customHeadersJSON, err := json.Marshal(customHeaders)
if err != nil {
return nil, err
}
if !input.EnableHTTPS {
input.RedirectHTTP = false
input.CertID = nil
input.CertIDs = nil
input.DomainCertIDs = nil
}
domainCertIDs, certIDs, primaryCertID, err := normalizeProxyRouteDomainCertificateIDs(
ctx,
domains,
input.EnableHTTPS,
input.DomainCertIDs,
input.CertID,
input.CertIDs,
)
if err != nil {
return nil, err
}
if err := validateProxyRouteDomainCertificateCoverage(ctx, domains, domainCertIDs); err != nil {
return nil, err
}
certIDsJSON, err := json.Marshal(certIDs)
if err != nil {
return nil, err
}
domainCertIDsJSON, err := json.Marshal(domainCertIDs)
if err != nil {
return nil, err
}
domainsJSON, err := json.Marshal(domains)
if err != nil {
return nil, err
}
if err := validateProxyRouteSiteName(siteName); err != nil {
return nil, err
}
if err := validateProxyRouteIdentityUniqueness(ctx, route, siteName, domains); err != nil {
return nil, err
}
if err := validateOriginHost(originHost); err != nil {
return nil, err
}
input.DomainCertIDs = domainCertIDs
input.CertIDs = certIDs
input.CertID = primaryCertID
if input.RedirectHTTP && !input.EnableHTTPS {
return nil, errors.New(errProxyRouteRedirectHTTP)
}
if input.BasicAuthEnabled {
input.BasicAuthUsername = strings.TrimSpace(input.BasicAuthUsername)
input.BasicAuthPassword = strings.TrimSpace(input.BasicAuthPassword)
if input.BasicAuthUsername == "" || input.BasicAuthPassword == "" {
return nil, errors.New(errProxyRouteBasicAuth)
}
} else {
input.BasicAuthUsername = ""
input.BasicAuthPassword = ""
}
if route == nil {
route = &model.ProxyRoute{}
}
route.SiteName = siteName
route.Domain = domain
route.Domains = string(domainsJSON)
route.OriginID = originID
route.OriginURL = upstreams[0]
route.OriginHost = originHost
route.Upstreams = string(upstreamsJSON)
route.Enabled = input.Enabled
route.EnableHTTPS = input.EnableHTTPS
route.CertID = input.CertID
route.CertIDs = string(certIDsJSON)
route.DomainCertIDs = string(domainCertIDsJSON)
route.RedirectHTTP = input.RedirectHTTP
route.LimitConnPerServer = limitConnPerServer
route.LimitConnPerIP = limitConnPerIP
route.LimitRate = limitRate
route.CacheEnabled = input.CacheEnabled
route.CachePolicy = normalizeCachePolicy(input.CacheEnabled, cachePolicy)
route.CacheRules = string(cacheRulesJSON)
route.CustomHeaders = string(customHeadersJSON)
route.BasicAuthEnabled = input.BasicAuthEnabled
route.BasicAuthUsername = input.BasicAuthUsername
route.BasicAuthPassword = input.BasicAuthPassword
route.Remark = remark
route.UpstreamType = upstreamType
if upstreamType == "tunnel" {
tunnelNodeID, err := normalizeTunnelNodeID(input.TunnelNodeID, input.TunnelID)
if err != nil {
return nil, err
}
if err := validateTunnelRouteInput(ctx, tunnelNodeID, input.TunnelTargetAddr, input.TunnelTargetProtocol); err != nil {
return nil, err
}
route.TunnelNodeID = tunnelNodeID
route.TunnelTargetAddr = strings.TrimSpace(input.TunnelTargetAddr)
route.TunnelTargetProtocol = normalizeTunnelTargetProtocol(input.TunnelTargetProtocol)
route.PagesProjectID = nil
} else if upstreamType == "pages" {
route.TunnelNodeID = nil
route.TunnelTargetAddr = ""
route.TunnelTargetProtocol = ""
route.PagesProjectID = input.PagesProjectID
} else {
route.TunnelNodeID = nil
route.TunnelTargetAddr = ""
route.TunnelTargetProtocol = ""
route.PagesProjectID = nil
}
return route, nil
}
func buildProxyRouteViews(ctx context.Context, routes []*model.ProxyRoute) ([]*View, error) {
views := make([]*View, 0, len(routes))
for _, route := range routes {
view, err := buildProxyRouteView(ctx, route)
if err != nil {
return nil, err
}
views = append(views, view)
}
return views, nil
}
func buildProxyRouteView(ctx context.Context, route *model.ProxyRoute) (*View, error) {
if route == nil {
return nil, errors.New("proxy route is nil")
}
domains, err := decodeStoredDomains(route.Domains, route.Domain)
if err != nil {
return nil, err
}
upstreams, err := decodeStoredUpstreams(route.Upstreams, route.OriginURL)
if err != nil {
return nil, err
}
cacheRules, err := decodeStoredCacheRules(route.CacheRules)
if err != nil {
return nil, err
}
customHeaders, err := decodeStoredCustomHeaders(route.CustomHeaders)
if err != nil {
return nil, err
}
certIDs, err := decodeStoredCertIDs(route.CertIDs, route.CertID)
if err != nil {
return nil, err
}
domainCertIDs, err := resolveProxyRouteDomainCertIDs(ctx, route, domains, certIDs)
if err != nil {
return nil, err
}
var certID *uint
if len(certIDs) > 0 {
certID = &certIDs[0]
}
primaryDomain := domains[0]
return &View{
ID: route.ID,
SiteName: normalizeProxyRouteSiteNameInput(route, route.SiteName, primaryDomain),
Domain: primaryDomain,
Domains: domains,
PrimaryDomain: primaryDomain,
DomainCount: len(domains),
OriginID: route.OriginID,
OriginURL: route.OriginURL,
OriginHost: route.OriginHost,
Upstreams: route.Upstreams,
UpstreamList: upstreams,
Enabled: route.Enabled,
EnableHTTPS: route.EnableHTTPS,
CertID: certID,
CertIDs: certIDs,
DomainCertIDs: domainCertIDs,
RedirectHTTP: route.RedirectHTTP,
LimitConnPerServer: route.LimitConnPerServer,
LimitConnPerIP: route.LimitConnPerIP,
LimitRate: route.LimitRate,
CacheEnabled: route.CacheEnabled,
CachePolicy: route.CachePolicy,
CacheRules: route.CacheRules,
CacheRuleList: cacheRules,
CustomHeaders: route.CustomHeaders,
CustomHeaderList: customHeaders,
BasicAuthEnabled: route.BasicAuthEnabled,
BasicAuthUsername: route.BasicAuthUsername,
BasicAuthPassword: route.BasicAuthPassword,
Remark: route.Remark,
UpstreamType: route.UpstreamType,
TunnelNodeID: route.TunnelNodeID,
TunnelID: route.TunnelNodeID,
TunnelTargetAddr: route.TunnelTargetAddr,
TunnelTargetProtocol: route.TunnelTargetProtocol,
PagesProjectID: route.PagesProjectID,
CreatedAt: route.CreatedAt,
UpdatedAt: route.UpdatedAt,
}, nil
}
@@ -0,0 +1,88 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package proxy_route
import (
"context"
"testing"
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
func setupProxyRouteTestDB(t *testing.T) func() {
t.Helper()
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
require.NoError(t, err)
require.NoError(t, sqliteDB.AutoMigrate(&model.ProxyRoute{}, &model.Origin{}))
db.SetDB(sqliteDB)
return func() {
db.SetDB(nil)
}
}
func TestCreateProxyRoute(t *testing.T) {
cleanup := setupProxyRouteTestDB(t)
defer cleanup()
ctx := context.Background()
view, err := CreateProxyRoute(ctx, Input{
SiteName: "example-site",
Domain: "example.com",
OriginURL: "http://origin.example.com:8080",
Enabled: true,
})
require.NoError(t, err)
assert.NotZero(t, view.ID)
assert.Equal(t, "example-site", view.SiteName)
assert.Equal(t, "example.com", view.Domain)
assert.Equal(t, []string{"example.com"}, view.Domains)
assert.Equal(t, "http://origin.example.com:8080", view.OriginURL)
assert.Equal(t, []string{"http://origin.example.com:8080"}, view.UpstreamList)
assert.True(t, view.Enabled)
_, err = CreateProxyRoute(ctx, Input{
SiteName: "duplicate-site",
Domain: "example.com",
OriginURL: "http://origin.example.com:8080",
})
require.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
}
func TestListProxyRoutes(t *testing.T) {
cleanup := setupProxyRouteTestDB(t)
defer cleanup()
ctx := context.Background()
first, err := CreateProxyRoute(ctx, Input{
SiteName: "first-site",
Domain: "first.example.com",
OriginURL: "http://origin-a.internal:80",
})
require.NoError(t, err)
second, err := CreateProxyRoute(ctx, Input{
SiteName: "second-site",
Domain: "second.example.com",
OriginURL: "http://origin-b.internal:80",
})
require.NoError(t, err)
routes, err := ListProxyRoutes(ctx)
require.NoError(t, err)
require.Len(t, routes, 2)
assert.Equal(t, second.ID, routes[0].ID)
assert.Equal(t, first.ID, routes[1].ID)
assert.Equal(t, "second.example.com", routes[0].Domain)
assert.Equal(t, "first.example.com", routes[1].Domain)
}
@@ -0,0 +1,141 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package proxy_route
import (
"net/http"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/apiutil"
"github.com/Rain-kl/Wavelet/internal/common/response"
"github.com/gin-gonic/gin"
)
func handleLogicError(c *gin.Context, err error) bool {
if err == nil {
return false
}
return apiutil.AbortNotFoundIfMissing(c, err, errProxyRouteNotFound)
}
// GetProxyRoutes 列出全部代理规则。
// @Summary 获取代理规则列表
// @Description 返回所有代理规则配置,需要管理员权限
// @Tags openflare-proxy-route
// @Produce json
// @Security SessionCookie
// @Success 200 {object} response.Any{data=[]proxy_route.View} "代理规则列表"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或不存在"
// @Router /api/v1/d/proxy-routes [get]
func GetProxyRoutes(c *gin.Context) {
routes, err := ListProxyRoutes(c.Request.Context())
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(routes))
}
// GetProxyRouteHandler 获取代理规则详情。
// @Summary 获取代理规则详情
// @Description 返回指定代理规则的完整配置,需要管理员权限
// @Tags openflare-proxy-route
// @Produce json
// @Security SessionCookie
// @Param id path int true "代理规则 ID"
// @Success 200 {object} response.Any{data=proxy_route.View} "代理规则详情"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或规则不存在"
// @Router /api/v1/d/proxy-routes/{id} [get]
func GetProxyRouteHandler(c *gin.Context) {
id, ok := apiutil.IDParam(c)
if !ok {
return
}
route, err := GetProxyRoute(c.Request.Context(), id)
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(route))
}
// CreateProxyRouteHandler 创建代理规则。
// @Summary 创建代理规则
// @Description 创建新的反向代理规则,需要管理员权限
// @Tags openflare-proxy-route
// @Accept json
// @Produce json
// @Security SessionCookie
// @Param body body proxy_route.Input true "代理规则参数"
// @Success 200 {object} response.Any{data=proxy_route.View} "创建成功"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或不存在"
// @Router /api/v1/d/proxy-routes [post]
func CreateProxyRouteHandler(c *gin.Context) {
var input Input
if !apiutil.BindJSON(c, &input) {
return
}
route, err := CreateProxyRoute(c.Request.Context(), input)
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(route))
}
// UpdateProxyRouteHandler 更新代理规则。
// @Summary 更新代理规则
// @Description 更新指定代理规则的配置,需要管理员权限
// @Tags openflare-proxy-route
// @Accept json
// @Produce json
// @Security SessionCookie
// @Param id path int true "代理规则 ID"
// @Param body body proxy_route.Input true "代理规则参数"
// @Success 200 {object} response.Any{data=proxy_route.View} "更新成功"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或规则不存在"
// @Router /api/v1/d/proxy-routes/{id}/update [post]
func UpdateProxyRouteHandler(c *gin.Context) {
id, ok := apiutil.IDParam(c)
if !ok {
return
}
var input Input
if !apiutil.BindJSON(c, &input) {
return
}
route, err := UpdateProxyRoute(c.Request.Context(), id, input)
if handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OK(route))
}
// DeleteProxyRouteHandler 删除代理规则。
// @Summary 删除代理规则
// @Description 删除指定代理规则,需要管理员权限
// @Tags openflare-proxy-route
// @Produce json
// @Security SessionCookie
// @Param id path int true "代理规则 ID"
// @Success 200 {object} response.Any{data=string} "删除成功"
// @Failure 400 {object} response.Any "参数错误"
// @Failure 401 {object} response.Any "未登录"
// @Failure 404 {object} response.Any "无权限或规则不存在"
// @Router /api/v1/d/proxy-routes/{id}/delete [post]
func DeleteProxyRouteHandler(c *gin.Context) {
id, ok := apiutil.IDParam(c)
if !ok {
return
}
if err := DeleteProxyRoute(c.Request.Context(), id); handleLogicError(c, err) {
return
}
c.JSON(http.StatusOK, response.OKNil())
}