| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- package admin
- import (
- "context"
- "strconv"
- "strings"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/handler/dto"
- "github.com/Wei-Shaw/sub2api/internal/pkg/response"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- )
- // ProxyHandler handles admin proxy management
- type ProxyHandler struct {
- adminService service.AdminService
- }
- // NewProxyHandler creates a new admin proxy handler
- func NewProxyHandler(adminService service.AdminService) *ProxyHandler {
- return &ProxyHandler{
- adminService: adminService,
- }
- }
- // CreateProxyRequest represents create proxy request
- type CreateProxyRequest struct {
- Name string `json:"name" binding:"required"`
- Protocol string `json:"protocol" binding:"required,oneof=http https socks5 socks5h"`
- Host string `json:"host" binding:"required"`
- Port int `json:"port" binding:"required,min=1,max=65535"`
- Username string `json:"username"`
- Password string `json:"password"`
- ExpiresAt *int64 `json:"expires_at"`
- FallbackMode string `json:"fallback_mode" binding:"omitempty,oneof=none proxy direct"`
- BackupProxyID *int64 `json:"backup_proxy_id"`
- ExpiryWarnDays int `json:"expiry_warn_days" binding:"omitempty,min=0"`
- }
- // UpdateProxyRequest represents update proxy request
- type UpdateProxyRequest struct {
- Name string `json:"name"`
- Protocol string `json:"protocol" binding:"omitempty,oneof=http https socks5 socks5h"`
- Host string `json:"host"`
- Port int `json:"port" binding:"omitempty,min=1,max=65535"`
- Username string `json:"username"`
- Password string `json:"password"`
- Status string `json:"status" binding:"omitempty,oneof=active inactive"`
- ExpiresAt dto.NullableInt64Field `json:"expires_at"`
- FallbackMode string `json:"fallback_mode" binding:"omitempty,oneof=none proxy direct"`
- BackupProxyID dto.NullableInt64Field `json:"backup_proxy_id"`
- ExpiryWarnDays *int `json:"expiry_warn_days" binding:"omitempty,min=0"`
- }
- // List handles listing all proxies with pagination
- // GET /api/v1/admin/proxies
- func (h *ProxyHandler) List(c *gin.Context) {
- page, pageSize := response.ParsePagination(c)
- protocol := c.Query("protocol")
- status := c.Query("status")
- search := c.Query("search")
- sortBy := c.DefaultQuery("sort_by", "id")
- sortOrder := c.DefaultQuery("sort_order", "desc")
- // 标准化和验证 search 参数
- search = strings.TrimSpace(search)
- if len(search) > 100 {
- search = search[:100]
- }
- proxies, total, err := h.adminService.ListProxiesWithAccountCount(c.Request.Context(), page, pageSize, protocol, status, search, sortBy, sortOrder)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- out := make([]dto.AdminProxyWithAccountCount, 0, len(proxies))
- for i := range proxies {
- out = append(out, *dto.ProxyWithAccountCountFromServiceAdmin(&proxies[i]))
- }
- response.Paginated(c, out, total, page, pageSize)
- }
- // GetAll handles getting all active proxies without pagination
- // GET /api/v1/admin/proxies/all
- // Optional query param: with_count=true to include account count per proxy
- func (h *ProxyHandler) GetAll(c *gin.Context) {
- withCount := c.Query("with_count") == "true"
- if withCount {
- proxies, err := h.adminService.GetAllProxiesWithAccountCount(c.Request.Context())
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- out := make([]dto.AdminProxyWithAccountCount, 0, len(proxies))
- for i := range proxies {
- out = append(out, *dto.ProxyWithAccountCountFromServiceAdmin(&proxies[i]))
- }
- response.Success(c, out)
- return
- }
- proxies, err := h.adminService.GetAllProxies(c.Request.Context())
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- out := make([]dto.AdminProxy, 0, len(proxies))
- for i := range proxies {
- out = append(out, *dto.ProxyFromServiceAdmin(&proxies[i]))
- }
- response.Success(c, out)
- }
- // GetByID handles getting a proxy by ID
- // GET /api/v1/admin/proxies/:id
- func (h *ProxyHandler) GetByID(c *gin.Context) {
- proxyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil {
- response.BadRequest(c, "Invalid proxy ID")
- return
- }
- proxy, err := h.adminService.GetProxy(c.Request.Context(), proxyID)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, dto.ProxyFromServiceAdmin(proxy))
- }
- // Create handles creating a new proxy
- // POST /api/v1/admin/proxies
- func (h *ProxyHandler) Create(c *gin.Context) {
- var req CreateProxyRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- executeAdminIdempotentJSON(c, "admin.proxies.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
- var expiresAt *time.Time
- if req.ExpiresAt != nil && *req.ExpiresAt > 0 {
- t := time.Unix(*req.ExpiresAt, 0).UTC()
- expiresAt = &t
- }
- proxy, err := h.adminService.CreateProxy(ctx, &service.CreateProxyInput{
- Name: strings.TrimSpace(req.Name),
- Protocol: strings.TrimSpace(req.Protocol),
- Host: strings.TrimSpace(req.Host),
- Port: req.Port,
- Username: strings.TrimSpace(req.Username),
- Password: strings.TrimSpace(req.Password),
- ExpiresAt: expiresAt,
- FallbackMode: strings.TrimSpace(req.FallbackMode),
- BackupProxyID: req.BackupProxyID,
- ExpiryWarnDays: req.ExpiryWarnDays,
- })
- if err != nil {
- return nil, err
- }
- return dto.ProxyFromServiceAdmin(proxy), nil
- })
- }
- // Update handles updating a proxy
- // PUT /api/v1/admin/proxies/:id
- func (h *ProxyHandler) Update(c *gin.Context) {
- proxyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil {
- response.BadRequest(c, "Invalid proxy ID")
- return
- }
- var req UpdateProxyRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- var expiresAt *time.Time
- if req.ExpiresAt.Value != nil && *req.ExpiresAt.Value > 0 {
- t := time.Unix(*req.ExpiresAt.Value, 0).UTC()
- expiresAt = &t
- }
- proxy, err := h.adminService.UpdateProxy(c.Request.Context(), proxyID, &service.UpdateProxyInput{
- Name: strings.TrimSpace(req.Name),
- Protocol: strings.TrimSpace(req.Protocol),
- Host: strings.TrimSpace(req.Host),
- Port: req.Port,
- Username: strings.TrimSpace(req.Username),
- Password: strings.TrimSpace(req.Password),
- Status: strings.TrimSpace(req.Status),
- ExpiresAt: expiresAt,
- ClearExpiresAt: req.ExpiresAt.Set && expiresAt == nil,
- FallbackMode: strings.TrimSpace(req.FallbackMode),
- BackupProxyID: req.BackupProxyID.Value,
- ClearBackupID: req.BackupProxyID.Set && req.BackupProxyID.Value == nil,
- ExpiryWarnDays: req.ExpiryWarnDays,
- })
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, dto.ProxyFromServiceAdmin(proxy))
- }
- // Delete handles deleting a proxy
- // DELETE /api/v1/admin/proxies/:id
- func (h *ProxyHandler) Delete(c *gin.Context) {
- proxyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil {
- response.BadRequest(c, "Invalid proxy ID")
- return
- }
- err = h.adminService.DeleteProxy(c.Request.Context(), proxyID)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, gin.H{"message": "Proxy deleted successfully"})
- }
- // BatchDelete handles batch deleting proxies
- // POST /api/v1/admin/proxies/batch-delete
- func (h *ProxyHandler) BatchDelete(c *gin.Context) {
- type BatchDeleteRequest struct {
- IDs []int64 `json:"ids" binding:"required,min=1"`
- }
- var req BatchDeleteRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- result, err := h.adminService.BatchDeleteProxies(c.Request.Context(), req.IDs)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, result)
- }
- // Test handles testing proxy connectivity
- // POST /api/v1/admin/proxies/:id/test
- func (h *ProxyHandler) Test(c *gin.Context) {
- proxyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil {
- response.BadRequest(c, "Invalid proxy ID")
- return
- }
- result, err := h.adminService.TestProxy(c.Request.Context(), proxyID)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, result)
- }
- // CheckQuality handles checking proxy quality across common AI targets.
- // POST /api/v1/admin/proxies/:id/quality-check
- func (h *ProxyHandler) CheckQuality(c *gin.Context) {
- proxyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil {
- response.BadRequest(c, "Invalid proxy ID")
- return
- }
- result, err := h.adminService.CheckProxyQuality(c.Request.Context(), proxyID)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, result)
- }
- // GetStats handles getting proxy statistics
- // GET /api/v1/admin/proxies/:id/stats
- func (h *ProxyHandler) GetStats(c *gin.Context) {
- proxyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil {
- response.BadRequest(c, "Invalid proxy ID")
- return
- }
- // Return mock data for now
- _ = proxyID
- response.Success(c, gin.H{
- "total_accounts": 0,
- "active_accounts": 0,
- "total_requests": 0,
- "success_rate": 100.0,
- "average_latency": 0,
- })
- }
- // GetProxyAccounts handles getting accounts using a proxy
- // GET /api/v1/admin/proxies/:id/accounts
- func (h *ProxyHandler) GetProxyAccounts(c *gin.Context) {
- proxyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil {
- response.BadRequest(c, "Invalid proxy ID")
- return
- }
- accounts, err := h.adminService.GetProxyAccounts(c.Request.Context(), proxyID)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- out := make([]dto.ProxyAccountSummary, 0, len(accounts))
- for i := range accounts {
- out = append(out, *dto.ProxyAccountSummaryFromService(&accounts[i]))
- }
- response.Success(c, out)
- }
- // BatchCreateProxyItem represents a single proxy in batch create request
- type BatchCreateProxyItem struct {
- Protocol string `json:"protocol" binding:"required,oneof=http https socks5 socks5h"`
- Host string `json:"host" binding:"required"`
- Port int `json:"port" binding:"required,min=1,max=65535"`
- Username string `json:"username"`
- Password string `json:"password"`
- }
- // BatchCreateRequest represents batch create proxies request
- type BatchCreateRequest struct {
- Proxies []BatchCreateProxyItem `json:"proxies" binding:"required,min=1"`
- }
- // BatchCreate handles batch creating proxies
- // POST /api/v1/admin/proxies/batch
- func (h *ProxyHandler) BatchCreate(c *gin.Context) {
- var req BatchCreateRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- created := 0
- skipped := 0
- for _, item := range req.Proxies {
- // Trim all string fields
- host := strings.TrimSpace(item.Host)
- protocol := strings.TrimSpace(item.Protocol)
- username := strings.TrimSpace(item.Username)
- password := strings.TrimSpace(item.Password)
- // Check for duplicates (same host, port, username, password)
- exists, err := h.adminService.CheckProxyExists(c.Request.Context(), host, item.Port, username, password)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- if exists {
- skipped++
- continue
- }
- // Create proxy with default name
- _, err = h.adminService.CreateProxy(c.Request.Context(), &service.CreateProxyInput{
- Name: "default",
- Protocol: protocol,
- Host: host,
- Port: item.Port,
- Username: username,
- Password: password,
- })
- if err != nil {
- // If creation fails due to duplicate, count as skipped
- skipped++
- continue
- }
- created++
- }
- response.Success(c, gin.H{
- "created": created,
- "skipped": skipped,
- })
- }
|