diff --git a/internal/config/manager.go b/internal/config/manager.go index a086ebd..d468c28 100644 --- a/internal/config/manager.go +++ b/internal/config/manager.go @@ -176,7 +176,7 @@ func (m *Manager) Validate() error { for _, err := range validationErrors { logrus.Errorf(" - %s", err) } - return errors.NewAppErrorWithDetails(errors.ErrConfigValidation, "Configuration validation failed", strings.Join(validationErrors, "; ")) + return errors.NewAPIError(errors.ErrValidation, strings.Join(validationErrors, "; ")) } return nil diff --git a/internal/errors/errors.go b/internal/errors/errors.go index f4f0bb9..6353e57 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -1,129 +1,67 @@ -// Package errors defines custom error types for the application package errors import ( - "fmt" + "errors" "net/http" + + "github.com/go-sql-driver/mysql" + "gorm.io/gorm" ) -// ErrorCode represents different types of errors -type ErrorCode int +// APIError defines a standard error structure for API responses. +type APIError struct { + HTTPStatus int + Code string + Message string +} -const ( - // Configuration errors - ErrConfigInvalid ErrorCode = iota + 1000 - ErrConfigMissing - ErrConfigValidation +// Error implements the error interface. +func (e *APIError) Error() string { + return e.Message +} - // Key management errors - ErrNoKeysAvailable ErrorCode = iota + 2000 - ErrKeyFileNotFound - ErrKeyFileInvalid - ErrAllKeysBlacklisted - - // Proxy errors - ErrProxyRequest ErrorCode = iota + 3000 - ErrProxyResponse - ErrProxyTimeout - ErrProxyRetryExhausted - - // Authentication errors - ErrAuthInvalid ErrorCode = iota + 4000 - ErrAuthMissing - ErrAuthExpired - - // Server errors - ErrServerInternal ErrorCode = iota + 5000 - ErrServerUnavailable +// Predefined API errors +var ( + ErrBadRequest = &APIError{HTTPStatus: http.StatusBadRequest, Code: "BAD_REQUEST", Message: "Invalid request parameters"} + ErrInvalidJSON = &APIError{HTTPStatus: http.StatusBadRequest, Code: "INVALID_JSON", Message: "Invalid JSON format"} + ErrValidation = &APIError{HTTPStatus: http.StatusBadRequest, Code: "VALIDATION_FAILED", Message: "Input validation failed"} + ErrDuplicateResource = &APIError{HTTPStatus: http.StatusConflict, Code: "DUPLICATE_RESOURCE", Message: "Resource already exists"} + ErrResourceNotFound = &APIError{HTTPStatus: http.StatusNotFound, Code: "NOT_FOUND", Message: "Resource not found"} + ErrInternalServer = &APIError{HTTPStatus: http.StatusInternalServerError, Code: "INTERNAL_SERVER_ERROR", Message: "An unexpected error occurred"} + ErrDatabase = &APIError{HTTPStatus: http.StatusInternalServerError, Code: "DATABASE_ERROR", Message: "Database operation failed"} + ErrUnauthorized = &APIError{HTTPStatus: http.StatusUnauthorized, Code: "UNAUTHORIZED", Message: "Authentication failed"} + ErrForbidden = &APIError{HTTPStatus: http.StatusForbidden, Code: "FORBIDDEN", Message: "You do not have permission to access this resource"} ) -// AppError represents a custom application error -type AppError struct { - Code ErrorCode `json:"code"` - Message string `json:"message"` - Details string `json:"details,omitempty"` - HTTPStatus int `json:"-"` - Cause error `json:"-"` -} - -// Error implements the error interface -func (e *AppError) Error() string { - if e.Details != "" { - return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Details) - } - return fmt.Sprintf("[%d] %s", e.Code, e.Message) -} - -// Unwrap returns the underlying error -func (e *AppError) Unwrap() error { - return e.Cause -} - -// NewAppError creates a new application error -func NewAppError(code ErrorCode, message string) *AppError { - return &AppError{ - Code: code, +// NewAPIError creates a new APIError with a custom message. +func NewAPIError(base *APIError, message string) *APIError { + return &APIError{ + HTTPStatus: base.HTTPStatus, + Code: base.Code, Message: message, - HTTPStatus: getHTTPStatusForCode(code), } } -// NewAppErrorWithDetails creates a new application error with details -func NewAppErrorWithDetails(code ErrorCode, message, details string) *AppError { - return &AppError{ - Code: code, - Message: message, - Details: details, - HTTPStatus: getHTTPStatusForCode(code), +// ParseDBError intelligently converts a GORM error into a standard APIError. +func ParseDBError(err error) *APIError { + if err == nil { + return nil } -} -// NewAppErrorWithCause creates a new application error with underlying cause -func NewAppErrorWithCause(code ErrorCode, message string, cause error) *AppError { - return &AppError{ - Code: code, - Message: message, - HTTPStatus: getHTTPStatusForCode(code), - Cause: cause, + // Handle record not found error + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrResourceNotFound } -} -// getHTTPStatusForCode maps error codes to HTTP status codes -func getHTTPStatusForCode(code ErrorCode) int { - switch { - case code >= 1000 && code < 2000: // Configuration errors - return http.StatusInternalServerError - case code >= 2000 && code < 3000: // Key management errors - return http.StatusServiceUnavailable - case code >= 3000 && code < 4000: // Proxy errors - return http.StatusBadGateway - case code >= 4000 && code < 5000: // Authentication errors - return http.StatusUnauthorized - case code >= 5000 && code < 6000: // Server errors - return http.StatusInternalServerError - default: - return http.StatusInternalServerError - } -} - -// IsRetryable determines if an error is retryable -func IsRetryable(err error) bool { - if appErr, ok := err.(*AppError); ok { - switch appErr.Code { - case ErrProxyTimeout, ErrServerUnavailable: - return true - default: - return false + // Handle MySQL specific errors + var mysqlErr *mysql.MySQLError + if errors.As(err, &mysqlErr) { + switch mysqlErr.Number { + case 1062: // Duplicate entry for unique key + return ErrDuplicateResource } } - return false -} -// Common error instances -var ( - ErrNoAPIKeysAvailable = NewAppError(ErrNoKeysAvailable, "No API keys available") - ErrAllAPIKeysBlacklisted = NewAppError(ErrAllKeysBlacklisted, "All API keys are blacklisted") - ErrInvalidConfiguration = NewAppError(ErrConfigInvalid, "Invalid configuration") - ErrAuthenticationRequired = NewAppError(ErrAuthMissing, "Authentication required") - ErrInvalidAuthToken = NewAppError(ErrAuthInvalid, "Invalid authentication token") -) + // Default to a generic database error + return ErrDatabase +} diff --git a/internal/handler/group_handler.go b/internal/handler/group_handler.go index e9c7692..232f584 100644 --- a/internal/handler/group_handler.go +++ b/internal/handler/group_handler.go @@ -3,9 +3,9 @@ package handler import ( "encoding/json" + app_errors "gpt-load/internal/errors" "gpt-load/internal/models" "gpt-load/internal/response" - "net/http" "regexp" "strconv" @@ -26,26 +26,26 @@ func isValidGroupName(name string) bool { func (s *Server) CreateGroup(c *gin.Context) { var group models.Group if err := c.ShouldBindJSON(&group); err != nil { - response.Error(c, http.StatusBadRequest, "Invalid request body") + response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) return } // Validation if !isValidGroupName(group.Name) { - response.Error(c, http.StatusBadRequest, "Invalid group name format. Use lowercase letters and underscores, and do not start with an underscore.") + response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "Invalid group name format. Use 3-30 lowercase letters, numbers, and underscores.")) return } if len(group.Upstreams) == 0 { - response.Error(c, http.StatusBadRequest, "At least one upstream is required") + response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "At least one upstream is required")) return } if group.ChannelType == "" { - response.Error(c, http.StatusBadRequest, "Channel type is required") + response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "Channel type is required")) return } if err := s.DB.Create(&group).Error; err != nil { - response.Error(c, http.StatusInternalServerError, "Failed to create group") + response.Error(c, app_errors.ParseDBError(err)) return } @@ -56,7 +56,7 @@ func (s *Server) CreateGroup(c *gin.Context) { func (s *Server) ListGroups(c *gin.Context) { var groups []models.Group if err := s.DB.Order("sort asc, id desc").Find(&groups).Error; err != nil { - response.Error(c, http.StatusInternalServerError, "Failed to list groups") + response.Error(c, app_errors.ParseDBError(err)) return } response.Success(c, groups) @@ -66,32 +66,32 @@ func (s *Server) ListGroups(c *gin.Context) { func (s *Server) UpdateGroup(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { - response.Error(c, http.StatusBadRequest, "Invalid group ID") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Invalid group ID format")) return } var group models.Group if err := s.DB.First(&group, id).Error; err != nil { - response.Error(c, http.StatusNotFound, "Group not found") + response.Error(c, app_errors.ParseDBError(err)) return } var updateData models.Group if err := c.ShouldBindJSON(&updateData); err != nil { - response.Error(c, http.StatusBadRequest, "Invalid request body") + response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) return } // Validate group name if it's being updated if updateData.Name != "" && !isValidGroupName(updateData.Name) { - response.Error(c, http.StatusBadRequest, "Invalid group name format. Use lowercase letters and underscores, and do not start with an underscore.") + response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "Invalid group name format. Use 3-30 lowercase letters, numbers, and underscores.")) return } // Use a transaction to ensure atomicity tx := s.DB.Begin() if tx.Error != nil { - response.Error(c, http.StatusInternalServerError, "Failed to start transaction") + response.Error(c, app_errors.ErrDatabase) return } @@ -99,7 +99,7 @@ func (s *Server) UpdateGroup(c *gin.Context) { var updateMap map[string]interface{} updateBytes, _ := json.Marshal(updateData) if err := json.Unmarshal(updateBytes, &updateMap); err != nil { - response.Error(c, http.StatusBadRequest, "Failed to process update data") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Failed to process update data")) return } @@ -108,7 +108,7 @@ func (s *Server) UpdateGroup(c *gin.Context) { if configMap, isMap := config.(map[string]interface{}); isMap { configJSON, err := json.Marshal(configMap) if err != nil { - response.Error(c, http.StatusBadRequest, "Failed to process config data") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Failed to process config data")) return } updateMap["config"] = string(configJSON) @@ -120,7 +120,7 @@ func (s *Server) UpdateGroup(c *gin.Context) { if upstreamsSlice, isSlice := upstreams.([]interface{}); isSlice { upstreamsJSON, err := json.Marshal(upstreamsSlice) if err != nil { - response.Error(c, http.StatusBadRequest, "Failed to process upstreams data") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Failed to process upstreams data")) return } updateMap["upstreams"] = string(upstreamsJSON) @@ -136,20 +136,20 @@ func (s *Server) UpdateGroup(c *gin.Context) { // Use Updates with a map to only update provided fields, including zero values if err := tx.Model(&group).Updates(updateMap).Error; err != nil { tx.Rollback() - response.Error(c, http.StatusInternalServerError, "Failed to update group") + response.Error(c, app_errors.ParseDBError(err)) return } if err := tx.Commit().Error; err != nil { tx.Rollback() - response.Error(c, http.StatusInternalServerError, "Failed to commit transaction") + response.Error(c, app_errors.ErrDatabase) return } // Re-fetch the group to return the updated data var updatedGroup models.Group if err := s.DB.First(&updatedGroup, id).Error; err != nil { - response.Error(c, http.StatusNotFound, "Failed to fetch updated group data") + response.Error(c, app_errors.ParseDBError(err)) return } @@ -160,14 +160,14 @@ func (s *Server) UpdateGroup(c *gin.Context) { func (s *Server) DeleteGroup(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { - response.Error(c, http.StatusBadRequest, "Invalid group ID") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Invalid group ID format")) return } // Use a transaction to ensure atomicity tx := s.DB.Begin() if tx.Error != nil { - response.Error(c, http.StatusInternalServerError, "Failed to start transaction") + response.Error(c, app_errors.ErrDatabase) return } defer func() { @@ -179,23 +179,23 @@ func (s *Server) DeleteGroup(c *gin.Context) { // Also delete associated API keys if err := tx.Where("group_id = ?", id).Delete(&models.APIKey{}).Error; err != nil { tx.Rollback() - response.Error(c, http.StatusInternalServerError, "Failed to delete associated API keys") + response.Error(c, app_errors.ErrDatabase) return } if result := tx.Delete(&models.Group{}, id); result.Error != nil { tx.Rollback() - response.Error(c, http.StatusInternalServerError, "Failed to delete group") + response.Error(c, app_errors.ParseDBError(result.Error)) return } else if result.RowsAffected == 0 { tx.Rollback() - response.Error(c, http.StatusNotFound, "Group not found") + response.Error(c, app_errors.ErrResourceNotFound) return } if err := tx.Commit().Error; err != nil { tx.Rollback() - response.Error(c, http.StatusInternalServerError, "Failed to commit transaction") + response.Error(c, app_errors.ErrDatabase) return } diff --git a/internal/handler/key_handler.go b/internal/handler/key_handler.go index 1795bd4..5b2a64b 100644 --- a/internal/handler/key_handler.go +++ b/internal/handler/key_handler.go @@ -2,9 +2,9 @@ package handler import ( + app_errors "gpt-load/internal/errors" "gpt-load/internal/models" "gpt-load/internal/response" - "net/http" "strconv" "github.com/gin-gonic/gin" @@ -18,13 +18,13 @@ type CreateKeysRequest struct { func (s *Server) CreateKeysInGroup(c *gin.Context) { groupID, err := strconv.Atoi(c.Param("id")) if err != nil { - response.Error(c, http.StatusBadRequest, "Invalid group ID") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Invalid group ID format")) return } var req CreateKeysRequest if err := c.ShouldBindJSON(&req); err != nil { - response.Error(c, http.StatusBadRequest, "Invalid request body") + response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) return } @@ -38,7 +38,7 @@ func (s *Server) CreateKeysInGroup(c *gin.Context) { } if err := s.DB.Create(&newKeys).Error; err != nil { - response.Error(c, http.StatusInternalServerError, "Failed to create keys") + response.Error(c, app_errors.ParseDBError(err)) return } @@ -49,13 +49,13 @@ func (s *Server) CreateKeysInGroup(c *gin.Context) { func (s *Server) ListKeysInGroup(c *gin.Context) { groupID, err := strconv.Atoi(c.Param("id")) if err != nil { - response.Error(c, http.StatusBadRequest, "Invalid group ID") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Invalid group ID format")) return } var keys []models.APIKey if err := s.DB.Where("group_id = ?", groupID).Find(&keys).Error; err != nil { - response.Error(c, http.StatusInternalServerError, "Failed to list keys") + response.Error(c, app_errors.ParseDBError(err)) return } @@ -66,19 +66,19 @@ func (s *Server) ListKeysInGroup(c *gin.Context) { func (s *Server) UpdateKey(c *gin.Context) { groupID, err := strconv.Atoi(c.Param("id")) if err != nil { - response.Error(c, http.StatusBadRequest, "Invalid group ID") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Invalid group ID format")) return } keyID, err := strconv.Atoi(c.Param("key_id")) if err != nil { - response.Error(c, http.StatusBadRequest, "Invalid key ID") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Invalid key ID format")) return } var key models.APIKey if err := s.DB.Where("group_id = ? AND id = ?", groupID, keyID).First(&key).Error; err != nil { - response.Error(c, http.StatusNotFound, "Key not found in this group") + response.Error(c, app_errors.ParseDBError(err)) return } @@ -86,13 +86,13 @@ func (s *Server) UpdateKey(c *gin.Context) { Status string `json:"status"` } if err := c.ShouldBindJSON(&updateData); err != nil { - response.Error(c, http.StatusBadRequest, "Invalid request body") + response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) return } key.Status = updateData.Status if err := s.DB.Save(&key).Error; err != nil { - response.Error(c, http.StatusInternalServerError, "Failed to update key") + response.Error(c, app_errors.ParseDBError(err)) return } @@ -107,18 +107,18 @@ type DeleteKeysRequest struct { func (s *Server) DeleteKeys(c *gin.Context) { groupID, err := strconv.Atoi(c.Param("id")) if err != nil { - response.Error(c, http.StatusBadRequest, "Invalid group ID") + response.Error(c, app_errors.NewAPIError(app_errors.ErrBadRequest, "Invalid group ID format")) return } var req DeleteKeysRequest if err := c.ShouldBindJSON(&req); err != nil { - response.Error(c, http.StatusBadRequest, "Invalid request body") + response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) return } if len(req.KeyIDs) == 0 { - response.Error(c, http.StatusBadRequest, "No key IDs provided") + response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "No key IDs provided")) return } @@ -129,23 +129,23 @@ func (s *Server) DeleteKeys(c *gin.Context) { var count int64 if err := tx.Model(&models.APIKey{}).Where("id IN ? AND group_id = ?", req.KeyIDs, groupID).Count(&count).Error; err != nil { tx.Rollback() - response.Error(c, http.StatusInternalServerError, "Failed to verify keys") + response.Error(c, app_errors.ParseDBError(err)) return } if count != int64(len(req.KeyIDs)) { tx.Rollback() - response.Error(c, http.StatusForbidden, "One or more keys do not belong to the specified group") + response.Error(c, app_errors.NewAPIError(app_errors.ErrForbidden, "One or more keys do not belong to the specified group")) return } // Delete the keys if err := tx.Where("id IN ?", req.KeyIDs).Delete(&models.APIKey{}).Error; err != nil { tx.Rollback() - response.Error(c, http.StatusInternalServerError, "Failed to delete keys") + response.Error(c, app_errors.ParseDBError(err)) return } tx.Commit() response.Success(c, gin.H{"message": "Keys deleted successfully"}) -} \ No newline at end of file +} diff --git a/internal/handler/log_handler.go b/internal/handler/log_handler.go index d2a138f..a46523f 100644 --- a/internal/handler/log_handler.go +++ b/internal/handler/log_handler.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "gpt-load/internal/db" + app_errors "gpt-load/internal/errors" "gpt-load/internal/models" "gpt-load/internal/response" ) @@ -66,7 +67,7 @@ func GetLogs(c *gin.Context) { query.Count(&total) err := query.Order("timestamp desc").Offset(offset).Limit(size).Find(&logs).Error if err != nil { - response.Error(c, http.StatusInternalServerError, "Failed to get logs") + response.Error(c, app_errors.ParseDBError(err)) return } @@ -76,4 +77,4 @@ func GetLogs(c *gin.Context) { "size": size, "data": logs, }) -} \ No newline at end of file +} diff --git a/internal/handler/settings_handler.go b/internal/handler/settings_handler.go index c2207f9..bf3fd03 100644 --- a/internal/handler/settings_handler.go +++ b/internal/handler/settings_handler.go @@ -2,6 +2,7 @@ package handler import ( "gpt-load/internal/config" + app_errors "gpt-load/internal/errors" "gpt-load/internal/models" "gpt-load/internal/response" @@ -40,7 +41,7 @@ func GetSettings(c *gin.Context) { func UpdateSettings(c *gin.Context) { var settingsMap map[string]any if err := c.ShouldBindJSON(&settingsMap); err != nil { - response.BadRequest(c, "Invalid request body") + response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error())) return } @@ -53,14 +54,14 @@ func UpdateSettings(c *gin.Context) { // 更新配置 if err := settingsManager.UpdateSettings(settingsMap); err != nil { - response.InternalError(c, "Failed to update settings: "+err.Error()) + response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, err.Error())) return } // 重载系统配置 if err := settingsManager.LoadFromDatabase(); err != nil { logrus.Errorf("Failed to reload system settings: %v", err) - response.InternalError(c, "Failed to reload system settings") + response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, "Failed to reload system settings after update")) return } diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index c3c6e91..aa506e6 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -6,9 +6,11 @@ import ( "strings" "time" - "gpt-load/internal/errors" + "gpt-load/internal/response" "gpt-load/internal/types" + app_errors "gpt-load/internal/errors" + "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) @@ -140,20 +142,14 @@ func Auth(config types.AuthConfig) gin.HandlerFunc { // Extract key from multiple sources key := extractKey(c) if key == "" { - c.JSON(401, gin.H{ - "error": "Authorization required", - "code": errors.ErrAuthMissing, - }) + response.Error(c, app_errors.ErrUnauthorized) c.Abort() return } // Validate key if key != config.Key { - c.JSON(401, gin.H{ - "error": "Invalid authentication token", - "code": errors.ErrAuthInvalid, - }) + response.Error(c, app_errors.ErrUnauthorized) c.Abort() return } @@ -165,19 +161,8 @@ func Auth(config types.AuthConfig) gin.HandlerFunc { // Recovery creates a recovery middleware with custom error handling func Recovery() gin.HandlerFunc { return gin.CustomRecovery(func(c *gin.Context, recovered any) { - if err, ok := recovered.(string); ok { - logrus.Errorf("Panic recovered: %s", err) - c.JSON(500, gin.H{ - "error": "Internal server error", - "code": errors.ErrServerInternal, - }) - } else { - logrus.Errorf("Panic recovered: %v", recovered) - c.JSON(500, gin.H{ - "error": "Internal server error", - "code": errors.ErrServerInternal, - }) - } + logrus.Errorf("Panic recovered: %v", recovered) + response.Error(c, app_errors.ErrInternalServer) c.Abort() }) } @@ -193,10 +178,7 @@ func RateLimiter(config types.PerformanceConfig) gin.HandlerFunc { defer func() { <-semaphore }() c.Next() default: - c.JSON(429, gin.H{ - "error": "Too many concurrent requests", - "code": errors.ErrServerUnavailable, - }) + response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, "Too many concurrent requests")) c.Abort() } } @@ -212,20 +194,14 @@ func ErrorHandler() gin.HandlerFunc { err := c.Errors.Last().Err // Check if it's our custom error type - if appErr, ok := err.(*errors.AppError); ok { - c.JSON(appErr.HTTPStatus, gin.H{ - "error": appErr.Message, - "code": appErr.Code, - }) + if apiErr, ok := err.(*app_errors.APIError); ok { + response.Error(c, apiErr) return } // Handle other errors logrus.Errorf("Unhandled error: %v", err) - c.JSON(500, gin.H{ - "error": "Internal server error", - "code": errors.ErrServerInternal, - }) + response.Error(c, app_errors.ErrInternalServer) } } } diff --git a/internal/proxy/server.go b/internal/proxy/server.go index 9f07f9d..4407386 100644 --- a/internal/proxy/server.go +++ b/internal/proxy/server.go @@ -4,9 +4,9 @@ package proxy import ( "fmt" "gpt-load/internal/channel" + app_errors "gpt-load/internal/errors" "gpt-load/internal/models" "gpt-load/internal/response" - "net/http" "sync" "sync/atomic" "time" @@ -40,21 +40,21 @@ func (ps *ProxyServer) HandleProxy(c *gin.Context) { // 1. Find the group by name var group models.Group if err := ps.DB.Preload("APIKeys").Where("name = ?", groupName).First(&group).Error; err != nil { - response.Error(c, http.StatusNotFound, fmt.Sprintf("Group '%s' not found", groupName)) + response.Error(c, app_errors.ParseDBError(err)) return } // 2. Select an available API key from the group apiKey, err := ps.selectAPIKey(&group) if err != nil { - response.Error(c, http.StatusServiceUnavailable, err.Error()) + response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, err.Error())) return } // 3. Get the appropriate channel handler from the factory channelHandler, err := channel.GetChannel(&group) if err != nil { - response.Error(c, http.StatusInternalServerError, fmt.Sprintf("Failed to get channel for group '%s': %v", groupName, err)) + response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, fmt.Sprintf("Failed to get channel for group '%s': %v", groupName, err))) return } diff --git a/internal/response/response.go b/internal/response/response.go index 3b733c6..72b5b1b 100644 --- a/internal/response/response.go +++ b/internal/response/response.go @@ -2,47 +2,38 @@ package response import ( + app_errors "gpt-load/internal/errors" "net/http" "github.com/gin-gonic/gin" ) -// Response defines the standard JSON response structure. -type Response struct { +// SuccessResponse defines the standard JSON success response structure. +type SuccessResponse struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } +// ErrorResponse defines the standard JSON error response structure. +type ErrorResponse struct { + Code string `json:"code"` + Message string `json:"message"` +} + // Success sends a standardized success response. func Success(c *gin.Context, data interface{}) { - c.JSON(http.StatusOK, Response{ + c.JSON(http.StatusOK, SuccessResponse{ Code: 0, Message: "Success", Data: data, }) } -// Error sends a standardized error response. -func Error(c *gin.Context, code int, message string) { - c.JSON(code, Response{ - Code: code, - Message: message, - Data: nil, +// Error sends a standardized error response using an APIError. +func Error(c *gin.Context, apiErr *app_errors.APIError) { + c.JSON(apiErr.HTTPStatus, ErrorResponse{ + Code: apiErr.Code, + Message: apiErr.Message, }) } - -// BadRequest sends a 400 Bad Request error response. -func BadRequest(c *gin.Context, message string) { - Error(c, http.StatusBadRequest, message) -} - -// NotFound sends a 404 Not Found error response. -func NotFound(c *gin.Context, message string) { - Error(c, http.StatusNotFound, message) -} - -// InternalError sends a 500 Internal Server Error response. -func InternalError(c *gin.Context, message string) { - Error(c, http.StatusInternalServerError, message) -} \ No newline at end of file diff --git a/web/src/views/Settings.vue b/web/src/views/Settings.vue index 7624557..77799ab 100644 --- a/web/src/views/Settings.vue +++ b/web/src/views/Settings.vue @@ -55,7 +55,7 @@ function handleSubmit() { try { loading.value = true; - await http.post("/settings", form.value); + await http.put("/settings", form.value); fetchSettings(); } finally { loading.value = false;