Download backend/internal/handler/api_key_handler.go from cjovs/su2a: direct link, hf CLI and curl.
- Browser
- Download file 8.78 kB
-
https://huggingface.co/spaces/cjovs/su2a/resolve/main/backend/internal/handler/api_key_handler.go
- Command line
-
hf download hf://spaces/cjovs/su2a/backend/internal/handler/api_key_handler.go
-
curl -L -o api_key_handler.go https://huggingface.co/spaces/cjovs/su2a/resolve/main/backend/internal/handler/api_key_handler.go
8.78 kB
| // Package handler provides HTTP request handlers for the application. | |
| package handler | |
| import ( | |
| "context" | |
| "strconv" | |
| "strings" | |
| "time" | |
| "github.com/Wei-Shaw/sub2api/internal/handler/dto" | |
| "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" | |
| "github.com/Wei-Shaw/sub2api/internal/pkg/response" | |
| middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware" | |
| "github.com/Wei-Shaw/sub2api/internal/service" | |
| "github.com/gin-gonic/gin" | |
| ) | |
| // APIKeyHandler handles API key-related requests | |
| type APIKeyHandler struct { | |
| apiKeyService *service.APIKeyService | |
| } | |
| // NewAPIKeyHandler creates a new APIKeyHandler | |
| func NewAPIKeyHandler(apiKeyService *service.APIKeyService) *APIKeyHandler { | |
| return &APIKeyHandler{ | |
| apiKeyService: apiKeyService, | |
| } | |
| } | |
| // CreateAPIKeyRequest represents the create API key request payload | |
| type CreateAPIKeyRequest struct { | |
| Name string `json:"name" binding:"required"` | |
| GroupID *int64 `json:"group_id"` // nullable | |
| CustomKey *string `json:"custom_key"` // ๅฏ้็่ชๅฎไนkey | |
| IPWhitelist []string `json:"ip_whitelist"` // IP ็ฝๅๅ | |
| IPBlacklist []string `json:"ip_blacklist"` // IP ้ปๅๅ | |
| Quota *float64 `json:"quota"` // ้ ้ข้ๅถ (USD) | |
| ExpiresInDays *int `json:"expires_in_days"` // ่ฟๆๅคฉๆฐ | |
| // Rate limit fields (0 = unlimited) | |
| RateLimit5h *float64 `json:"rate_limit_5h"` | |
| RateLimit1d *float64 `json:"rate_limit_1d"` | |
| RateLimit7d *float64 `json:"rate_limit_7d"` | |
| } | |
| // UpdateAPIKeyRequest represents the update API key request payload | |
| type UpdateAPIKeyRequest struct { | |
| Name string `json:"name"` | |
| GroupID *int64 `json:"group_id"` | |
| Status string `json:"status" binding:"omitempty,oneof=active inactive"` | |
| IPWhitelist []string `json:"ip_whitelist"` // IP ็ฝๅๅ | |
| IPBlacklist []string `json:"ip_blacklist"` // IP ้ปๅๅ | |
| Quota *float64 `json:"quota"` // ้ ้ข้ๅถ (USD), 0=ๆ ้ๅถ | |
| ExpiresAt *string `json:"expires_at"` // ่ฟๆๆถ้ด (ISO 8601) | |
| ResetQuota *bool `json:"reset_quota"` // ้็ฝฎๅทฒ็จ้ ้ข | |
| // Rate limit fields (nil = no change, 0 = unlimited) | |
| RateLimit5h *float64 `json:"rate_limit_5h"` | |
| RateLimit1d *float64 `json:"rate_limit_1d"` | |
| RateLimit7d *float64 `json:"rate_limit_7d"` | |
| ResetRateLimitUsage *bool `json:"reset_rate_limit_usage"` // ้็ฝฎ้้็จ้ | |
| } | |
| // List handles listing user's API keys with pagination | |
| // GET /api/v1/api-keys | |
| func (h *APIKeyHandler) List(c *gin.Context) { | |
| subject, ok := middleware2.GetAuthSubjectFromContext(c) | |
| if !ok { | |
| response.Unauthorized(c, "User not authenticated") | |
| return | |
| } | |
| page, pageSize := response.ParsePagination(c) | |
| params := pagination.PaginationParams{ | |
| Page: page, | |
| PageSize: pageSize, | |
| SortBy: c.DefaultQuery("sort_by", "created_at"), | |
| SortOrder: c.DefaultQuery("sort_order", "desc"), | |
| } | |
| // Parse filter parameters | |
| var filters service.APIKeyListFilters | |
| if search := strings.TrimSpace(c.Query("search")); search != "" { | |
| if len(search) > 100 { | |
| search = search[:100] | |
| } | |
| filters.Search = search | |
| } | |
| filters.Status = c.Query("status") | |
| if groupIDStr := c.Query("group_id"); groupIDStr != "" { | |
| gid, err := strconv.ParseInt(groupIDStr, 10, 64) | |
| if err == nil { | |
| filters.GroupID = &gid | |
| } | |
| } | |
| keys, result, err := h.apiKeyService.List(c.Request.Context(), subject.UserID, params, filters) | |
| if err != nil { | |
| response.ErrorFrom(c, err) | |
| return | |
| } | |
| out := make([]dto.APIKey, 0, len(keys)) | |
| for i := range keys { | |
| out = append(out, *dto.APIKeyFromService(&keys[i])) | |
| } | |
| response.Paginated(c, out, result.Total, page, pageSize) | |
| } | |
| // GetByID handles getting a single API key | |
| // GET /api/v1/api-keys/:id | |
| func (h *APIKeyHandler) GetByID(c *gin.Context) { | |
| subject, ok := middleware2.GetAuthSubjectFromContext(c) | |
| if !ok { | |
| response.Unauthorized(c, "User not authenticated") | |
| return | |
| } | |
| keyID, err := strconv.ParseInt(c.Param("id"), 10, 64) | |
| if err != nil { | |
| response.BadRequest(c, "Invalid key ID") | |
| return | |
| } | |
| key, err := h.apiKeyService.GetByID(c.Request.Context(), keyID) | |
| if err != nil { | |
| response.ErrorFrom(c, err) | |
| return | |
| } | |
| // ้ช่ฏๆๆๆ | |
| if key.UserID != subject.UserID { | |
| response.Forbidden(c, "Not authorized to access this key") | |
| return | |
| } | |
| response.Success(c, dto.APIKeyFromService(key)) | |
| } | |
| // Create handles creating a new API key | |
| // POST /api/v1/api-keys | |
| func (h *APIKeyHandler) Create(c *gin.Context) { | |
| subject, ok := middleware2.GetAuthSubjectFromContext(c) | |
| if !ok { | |
| response.Unauthorized(c, "User not authenticated") | |
| return | |
| } | |
| var req CreateAPIKeyRequest | |
| if err := c.ShouldBindJSON(&req); err != nil { | |
| response.BadRequest(c, "Invalid request: "+err.Error()) | |
| return | |
| } | |
| svcReq := service.CreateAPIKeyRequest{ | |
| Name: req.Name, | |
| GroupID: req.GroupID, | |
| CustomKey: req.CustomKey, | |
| IPWhitelist: req.IPWhitelist, | |
| IPBlacklist: req.IPBlacklist, | |
| ExpiresInDays: req.ExpiresInDays, | |
| } | |
| if req.Quota != nil { | |
| svcReq.Quota = *req.Quota | |
| } | |
| if req.RateLimit5h != nil { | |
| svcReq.RateLimit5h = *req.RateLimit5h | |
| } | |
| if req.RateLimit1d != nil { | |
| svcReq.RateLimit1d = *req.RateLimit1d | |
| } | |
| if req.RateLimit7d != nil { | |
| svcReq.RateLimit7d = *req.RateLimit7d | |
| } | |
| executeUserIdempotentJSON(c, "user.api_keys.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) { | |
| key, err := h.apiKeyService.Create(ctx, subject.UserID, svcReq) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return dto.APIKeyFromService(key), nil | |
| }) | |
| } | |
| // Update handles updating an API key | |
| // PUT /api/v1/api-keys/:id | |
| func (h *APIKeyHandler) Update(c *gin.Context) { | |
| subject, ok := middleware2.GetAuthSubjectFromContext(c) | |
| if !ok { | |
| response.Unauthorized(c, "User not authenticated") | |
| return | |
| } | |
| keyID, err := strconv.ParseInt(c.Param("id"), 10, 64) | |
| if err != nil { | |
| response.BadRequest(c, "Invalid key ID") | |
| return | |
| } | |
| var req UpdateAPIKeyRequest | |
| if err := c.ShouldBindJSON(&req); err != nil { | |
| response.BadRequest(c, "Invalid request: "+err.Error()) | |
| return | |
| } | |
| svcReq := service.UpdateAPIKeyRequest{ | |
| IPWhitelist: req.IPWhitelist, | |
| IPBlacklist: req.IPBlacklist, | |
| Quota: req.Quota, | |
| ResetQuota: req.ResetQuota, | |
| RateLimit5h: req.RateLimit5h, | |
| RateLimit1d: req.RateLimit1d, | |
| RateLimit7d: req.RateLimit7d, | |
| ResetRateLimitUsage: req.ResetRateLimitUsage, | |
| } | |
| if req.Name != "" { | |
| svcReq.Name = &req.Name | |
| } | |
| svcReq.GroupID = req.GroupID | |
| if req.Status != "" { | |
| svcReq.Status = &req.Status | |
| } | |
| // Parse expires_at if provided | |
| if req.ExpiresAt != nil { | |
| if *req.ExpiresAt == "" { | |
| // Empty string means clear expiration | |
| svcReq.ExpiresAt = nil | |
| svcReq.ClearExpiration = true | |
| } else { | |
| t, err := time.Parse(time.RFC3339, *req.ExpiresAt) | |
| if err != nil { | |
| response.BadRequest(c, "Invalid expires_at format: "+err.Error()) | |
| return | |
| } | |
| svcReq.ExpiresAt = &t | |
| } | |
| } | |
| key, err := h.apiKeyService.Update(c.Request.Context(), keyID, subject.UserID, svcReq) | |
| if err != nil { | |
| response.ErrorFrom(c, err) | |
| return | |
| } | |
| response.Success(c, dto.APIKeyFromService(key)) | |
| } | |
| // Delete handles deleting an API key | |
| // DELETE /api/v1/api-keys/:id | |
| func (h *APIKeyHandler) Delete(c *gin.Context) { | |
| subject, ok := middleware2.GetAuthSubjectFromContext(c) | |
| if !ok { | |
| response.Unauthorized(c, "User not authenticated") | |
| return | |
| } | |
| keyID, err := strconv.ParseInt(c.Param("id"), 10, 64) | |
| if err != nil { | |
| response.BadRequest(c, "Invalid key ID") | |
| return | |
| } | |
| err = h.apiKeyService.Delete(c.Request.Context(), keyID, subject.UserID) | |
| if err != nil { | |
| response.ErrorFrom(c, err) | |
| return | |
| } | |
| response.Success(c, gin.H{"message": "API key deleted successfully"}) | |
| } | |
| // GetAvailableGroups ่ทๅ็จๆทๅฏไปฅ็ปๅฎ็ๅ็ปๅ่กจ | |
| // GET /api/v1/groups/available | |
| func (h *APIKeyHandler) GetAvailableGroups(c *gin.Context) { | |
| subject, ok := middleware2.GetAuthSubjectFromContext(c) | |
| if !ok { | |
| response.Unauthorized(c, "User not authenticated") | |
| return | |
| } | |
| groups, err := h.apiKeyService.GetAvailableGroups(c.Request.Context(), subject.UserID) | |
| if err != nil { | |
| response.ErrorFrom(c, err) | |
| return | |
| } | |
| out := make([]dto.Group, 0, len(groups)) | |
| for i := range groups { | |
| out = append(out, *dto.GroupFromService(&groups[i])) | |
| } | |
| response.Success(c, out) | |
| } | |
| // GetUserGroupRates ่ทๅๅฝๅ็จๆท็ไธๅฑๅ็ปๅ็้ ็ฝฎ | |
| // GET /api/v1/groups/rates | |
| func (h *APIKeyHandler) GetUserGroupRates(c *gin.Context) { | |
| subject, ok := middleware2.GetAuthSubjectFromContext(c) | |
| if !ok { | |
| response.Unauthorized(c, "User not authenticated") | |
| return | |
| } | |
| rates, err := h.apiKeyService.GetUserGroupRates(c.Request.Context(), subject.UserID) | |
| if err != nil { | |
| response.ErrorFrom(c, err) | |
| return | |
| } | |
| response.Success(c, rates) | |
| } | |