package httpapi import ( "encoding/json" "net/http" "strings" "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" ) // listFileStorageChannels godoc // @Summary 列出文件存储通道 // @Description 返回所有未删除的文件存储通道,用于管理上传与生成资源回传策略。 // @Tags system // @Produce json // @Security BearerAuth // @Success 200 {object} FileStorageChannelListResponse // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 500 {object} ErrorEnvelope // @Router /api/admin/system/file-storage/channels [get] func (s *Server) listFileStorageChannels(w http.ResponseWriter, r *http.Request) { items, err := s.store.ListFileStorageChannels(r.Context()) if err != nil { s.logger.Error("list file storage channels failed", "error", err) writeError(w, http.StatusInternalServerError, "list file storage channels failed") return } writeJSON(w, http.StatusOK, map[string]any{"items": items}) } // getFileStorageSettings godoc // @Summary 获取文件存储设置 // @Description 返回文件存储系统设置;数据库对象尚未创建时返回默认设置。 // @Tags system // @Produce json // @Security BearerAuth // @Success 200 {object} store.FileStorageSettings // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 500 {object} ErrorEnvelope // @Router /api/admin/system/file-storage/settings [get] func (s *Server) getFileStorageSettings(w http.ResponseWriter, r *http.Request) { settings, err := s.store.GetFileStorageSettings(r.Context()) if err != nil { if store.IsUndefinedDatabaseObject(err) { writeJSON(w, http.StatusOK, store.DefaultFileStorageSettings()) return } s.logger.Error("get file storage settings failed", "error", err) writeError(w, http.StatusInternalServerError, "get file storage settings failed") return } writeJSON(w, http.StatusOK, settings) } // updateFileStorageSettings godoc // @Summary 更新文件存储设置 // @Description 更新生成资源上传策略等文件存储系统设置。 // @Tags system // @Accept json // @Produce json // @Security BearerAuth // @Param body body store.FileStorageSettingsInput true "文件存储设置" // @Success 200 {object} store.FileStorageSettings // @Failure 400 {object} ErrorEnvelope // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 500 {object} ErrorEnvelope // @Router /api/admin/system/file-storage/settings [patch] func (s *Server) updateFileStorageSettings(w http.ResponseWriter, r *http.Request) { var input store.FileStorageSettingsInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, http.StatusBadRequest, "invalid json body") return } settings, err := s.store.UpdateFileStorageSettings(r.Context(), input) if err != nil { s.logger.Error("update file storage settings failed", "error", err) writeError(w, http.StatusInternalServerError, "update file storage settings failed") return } writeJSON(w, http.StatusOK, settings) } // createFileStorageChannel godoc // @Summary 创建文件存储通道 // @Description 创建文件存储通道,当前主要用于配置 server-main OpenAPI 上传通道。 // @Tags system // @Accept json // @Produce json // @Security BearerAuth // @Param body body store.FileStorageChannelInput true "文件存储通道" // @Success 201 {object} store.FileStorageChannel // @Failure 400 {object} ErrorEnvelope // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 409 {object} ErrorEnvelope // @Failure 500 {object} ErrorEnvelope // @Router /api/admin/system/file-storage/channels [post] func (s *Server) createFileStorageChannel(w http.ResponseWriter, r *http.Request) { var input store.FileStorageChannelInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, http.StatusBadRequest, "invalid json body") return } if message := validateFileStorageChannelInput(input, nil); message != "" { writeError(w, http.StatusBadRequest, message) return } item, err := s.store.CreateFileStorageChannel(r.Context(), input) if err != nil { if store.IsUniqueViolation(err) { writeError(w, http.StatusConflict, "file storage channel key already exists") return } s.logger.Error("create file storage channel failed", "error", err) writeError(w, http.StatusInternalServerError, "create file storage channel failed") return } writeJSON(w, http.StatusCreated, item) } // updateFileStorageChannel godoc // @Summary 更新文件存储通道 // @Description 更新指定文件存储通道的名称、凭证、场景、优先级、状态和重试策略。 // @Tags system // @Accept json // @Produce json // @Security BearerAuth // @Param channelID path string true "文件存储通道 ID" // @Param body body store.FileStorageChannelInput true "文件存储通道" // @Success 200 {object} store.FileStorageChannel // @Failure 400 {object} ErrorEnvelope // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope // @Failure 409 {object} ErrorEnvelope // @Failure 500 {object} ErrorEnvelope // @Router /api/admin/system/file-storage/channels/{channelID} [patch] func (s *Server) updateFileStorageChannel(w http.ResponseWriter, r *http.Request) { var input store.FileStorageChannelInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, http.StatusBadRequest, "invalid json body") return } existing, err := s.store.GetFileStorageChannel(r.Context(), r.PathValue("channelID")) if err != nil { if store.IsNotFound(err) { writeError(w, http.StatusNotFound, "file storage channel not found") return } s.logger.Error("get file storage channel failed", "error", err) writeError(w, http.StatusInternalServerError, "get file storage channel failed") return } if message := validateFileStorageChannelInput(input, &existing); message != "" { writeError(w, http.StatusBadRequest, message) return } item, err := s.store.UpdateFileStorageChannel(r.Context(), r.PathValue("channelID"), input) if err != nil { if store.IsNotFound(err) { writeError(w, http.StatusNotFound, "file storage channel not found") return } if store.IsUniqueViolation(err) { writeError(w, http.StatusConflict, "file storage channel key already exists") return } s.logger.Error("update file storage channel failed", "error", err) writeError(w, http.StatusInternalServerError, "update file storage channel failed") return } writeJSON(w, http.StatusOK, item) } // deleteFileStorageChannel godoc // @Summary 删除文件存储通道 // @Description 软删除指定文件存储通道。 // @Tags system // @Produce json // @Security BearerAuth // @Param channelID path string true "文件存储通道 ID" // @Success 204 "No Content" // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope // @Failure 500 {object} ErrorEnvelope // @Router /api/admin/system/file-storage/channels/{channelID} [delete] func (s *Server) deleteFileStorageChannel(w http.ResponseWriter, r *http.Request) { if err := s.store.DeleteFileStorageChannel(r.Context(), r.PathValue("channelID")); err != nil { if store.IsNotFound(err) { writeError(w, http.StatusNotFound, "file storage channel not found") return } s.logger.Error("delete file storage channel failed", "error", err) writeError(w, http.StatusInternalServerError, "delete file storage channel failed") return } w.WriteHeader(http.StatusNoContent) } func validateFileStorageChannelInput(input store.FileStorageChannelInput, existing *store.FileStorageChannel) string { provider := strings.ToLower(strings.TrimSpace(input.Provider)) if provider == "" { provider = "server_main_openapi" } status := strings.ToLower(strings.TrimSpace(input.Status)) if status == "" { status = "disabled" } if strings.TrimSpace(input.ChannelKey) == "" || strings.TrimSpace(input.Name) == "" { return "channelKey and name are required" } if status != "enabled" && status != "disabled" { return "status must be enabled or disabled" } if provider == "server_main_openapi" { hasAPIKey := false if input.APIKey != nil { hasAPIKey = strings.TrimSpace(*input.APIKey) != "" } else if existing != nil { hasAPIKey = strings.TrimSpace(existing.APIKey) != "" } if status == "enabled" && !hasAPIKey { return "server-main OpenAPI channel requires API key before enabling" } } return "" }