mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-02 06:56:36 +08:00
refactor(util): move response helper to common/response and session logic to oauth
- Relocated generic HTTP response helpers (Response, OK, Err, etc.) from internal/util/ to a dedicated internal/common/response/ package. - Renamed ResponseAny to Any to resolve revive stuttering warnings. - Moved session building options and cookie headers logic from internal/util/ to internal/apps/oauth/. - Removed all direct imports of Gin/Sessions/HTTP frameworks from internal/util/ to keep general utilities 100% pure. - Regenerated Swagger API documentation via make swagger. - All tests and make code-check compile and pass with 0 issues.
This commit is contained in:
@@ -4,16 +4,15 @@
|
||||
// Package auth_source 提供认证源管理功能
|
||||
package auth_source
|
||||
|
||||
import (
|
||||
"errors"
|
||||
import ("errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/apps/admin"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
// AuthSourceRequest 创建或更新认证源的请求参数
|
||||
type AuthSourceRequest struct {
|
||||
@@ -39,18 +38,18 @@ type ToggleAuthSourceRequest struct {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]model.AuthSource} "认证源列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=[]model.AuthSource} "认证源列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/auth-sources [get]
|
||||
func ListAuthSources(c *gin.Context) {
|
||||
sources, err := model.GetAuthSources(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OK(sources))
|
||||
c.JSON(http.StatusOK, response.OK(sources))
|
||||
}
|
||||
|
||||
// CreateAuthSource 创建认证源
|
||||
@@ -61,15 +60,15 @@ func ListAuthSources(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body auth_source.AuthSourceRequest true "创建认证源参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.AuthSource} "创建成功,返回认证源信息"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误或验证失败"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=model.AuthSource} "创建成功,返回认证源信息"
|
||||
// @Failure 400 {object} response.Any "参数错误或验证失败"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/auth-sources [post]
|
||||
func CreateAuthSource(c *gin.Context) {
|
||||
var req AuthSourceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,11 +84,11 @@ func CreateAuthSource(c *gin.Context) {
|
||||
IconURL: req.IconURL,
|
||||
}
|
||||
if err := model.CreateAuthSource(c.Request.Context(), &source); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
source.Sanitize()
|
||||
c.JSON(http.StatusOK, util.OK(source))
|
||||
c.JSON(http.StatusOK, response.OK(source))
|
||||
}
|
||||
|
||||
// UpdateAuthSource 更新认证源
|
||||
@@ -101,22 +100,22 @@ func CreateAuthSource(c *gin.Context) {
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "认证源 ID 或名称"
|
||||
// @Param request body auth_source.AuthSourceRequest true "更新认证源参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.AuthSource} "更新成功,返回更新后的认证源信息"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误或验证失败"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=model.AuthSource} "更新成功,返回更新后的认证源信息"
|
||||
// @Failure 400 {object} response.Any "参数错误或验证失败"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/auth-sources/{id} [put]
|
||||
func UpdateAuthSource(c *gin.Context) {
|
||||
id, err := parseSourceID(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var req AuthSourceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -134,16 +133,16 @@ func UpdateAuthSource(c *gin.Context) {
|
||||
}
|
||||
keepSecret := source.ClientSecret == ""
|
||||
if err := model.UpdateAuthSource(c.Request.Context(), &source, keepSecret); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
updated, err := model.GetAuthSourceByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
updated.Sanitize()
|
||||
c.JSON(http.StatusOK, util.OK(updated))
|
||||
c.JSON(http.StatusOK, response.OK(updated))
|
||||
}
|
||||
|
||||
// ToggleAuthSource 切换认证源启用状态
|
||||
@@ -155,29 +154,29 @@ func UpdateAuthSource(c *gin.Context) {
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "认证源 ID 或名称"
|
||||
// @Param request body auth_source.ToggleAuthSourceRequest true "启用状态"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "切换成功"
|
||||
// @Failure 400 {object} util.ResponseAny "验证失败或认证源不存在"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=string} "切换成功"
|
||||
// @Failure 400 {object} response.Any "验证失败或认证源不存在"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/auth-sources/{id}/toggle [put]
|
||||
func ToggleAuthSource(c *gin.Context) {
|
||||
id, err := parseSourceID(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var req ToggleAuthSourceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := model.ToggleAuthSource(c.Request.Context(), id, req.IsActive); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// DeleteAuthSource 删除认证源
|
||||
@@ -187,22 +186,22 @@ func ToggleAuthSource(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "认证源 ID 或名称"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "删除成功"
|
||||
// @Failure 400 {object} util.ResponseAny "ID 无效或删除失败"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=string} "删除成功"
|
||||
// @Failure 400 {object} response.Any "ID 无效或删除失败"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/auth-sources/{id} [delete]
|
||||
func DeleteAuthSource(c *gin.Context) {
|
||||
id, err := parseSourceID(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
if err := model.DeleteAuthSource(c.Request.Context(), id); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
func parseSourceID(c *gin.Context) (uint64, error) {
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
package auth_source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
import ("bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -16,7 +15,8 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
@@ -67,7 +67,7 @@ func TestListAuthSources(t *testing.T) {
|
||||
t.Errorf("expected 200 OK, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
|
||||
Vendored
+24
-25
@@ -4,8 +4,7 @@
|
||||
// Package cache provides HTTP handlers for managing disk cache.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
import ("context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -13,10 +12,10 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/diskcache"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
type updateCacheConfigRequest struct {
|
||||
MaxSizeMB int64 `json:"max_size_mb" binding:"required,min=1"`
|
||||
@@ -30,14 +29,14 @@ type updateCacheConfigRequest struct {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=diskcache.Status} "获取成功"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=diskcache.Status} "获取成功"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/cache/status [get]
|
||||
func GetCacheStatus(c *gin.Context) {
|
||||
status := diskcache.GetGlobalCache().Status()
|
||||
c.JSON(http.StatusOK, util.OK(status))
|
||||
c.JSON(http.StatusOK, response.OK(status))
|
||||
}
|
||||
|
||||
// UpdateCacheConfig 更新磁盘缓存策略配置
|
||||
@@ -48,16 +47,16 @@ func GetCacheStatus(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Param request body cache.updateCacheConfigRequest true "缓存配置请求体"
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny "更新成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "服务内部错误"
|
||||
// @Success 200 {object} response.Any "更新成功"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "服务内部错误"
|
||||
// @Router /api/v1/admin/cache/config [post]
|
||||
func UpdateCacheConfig(c *gin.Context) {
|
||||
var req updateCacheConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -65,26 +64,26 @@ func UpdateCacheConfig(c *gin.Context) {
|
||||
|
||||
// Update Max Size
|
||||
if err := saveOrUpdateConfig(ctx, model.ConfigKeyDiskCacheMaxSizeMB, strconv.FormatInt(req.MaxSizeMB, 10)); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Update Default TTL
|
||||
if err := saveOrUpdateConfig(ctx, model.ConfigKeyDiskCacheTTLMinutes, strconv.FormatInt(req.TTLMinutes, 10)); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Update LRU Enabled
|
||||
if err := saveOrUpdateConfig(ctx, model.ConfigKeyDiskCacheLRUEnabled, strconv.FormatBool(req.LRUEnabled)); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger hot reloading in global cache
|
||||
diskcache.GetGlobalCache().ReloadConfig(ctx)
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// ClearCache 一键清空所有磁盘缓存数据
|
||||
@@ -93,17 +92,17 @@ func UpdateCacheConfig(c *gin.Context) {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny "清理成功"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "服务内部错误"
|
||||
// @Success 200 {object} response.Any "清理成功"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "服务内部错误"
|
||||
// @Router /api/v1/admin/cache/clear [post]
|
||||
func ClearCache(c *gin.Context) {
|
||||
if err := diskcache.GetGlobalCache().Clear(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
func saveOrUpdateConfig(ctx context.Context, key string, value string) error {
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
// overview information, and executing custom SQL queries.
|
||||
package db_manage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
import ("database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
@@ -17,10 +16,10 @@ import (
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/config"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
const (
|
||||
binaryKB = 0
|
||||
@@ -206,15 +205,15 @@ func getPostgresOverview(gormDB *gorm.DB) (DBOverviewResponse, error) {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=db_manage.DBOverviewResponse} "获取成功"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=db_manage.DBOverviewResponse} "获取成功"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/db-manage/overview [get]
|
||||
func GetDBOverview(c *gin.Context) {
|
||||
gormDB := db.DB(c.Request.Context())
|
||||
if gormDB == nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("数据库未初始化"))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("数据库未初始化"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -228,11 +227,11 @@ func GetDBOverview(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(overview))
|
||||
c.JSON(http.StatusOK, response.OK(overview))
|
||||
}
|
||||
|
||||
// ListDBTables 获取数据库所有表名
|
||||
@@ -241,15 +240,15 @@ func GetDBOverview(c *gin.Context) {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]string} "获取成功"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=[]string} "获取成功"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/db-manage/tables [get]
|
||||
func ListDBTables(c *gin.Context) {
|
||||
gormDB := db.DB(c.Request.Context())
|
||||
if gormDB == nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("数据库未初始化"))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("数据库未初始化"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -263,24 +262,24 @@ func ListDBTables(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(tables))
|
||||
c.JSON(http.StatusOK, response.OK(tables))
|
||||
}
|
||||
|
||||
// GetDBTableData 获取数据表 data
|
||||
func GetDBTableData(c *gin.Context) {
|
||||
var req GetTableDataRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
gormDB := db.DB(c.Request.Context())
|
||||
if gormDB == nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("数据库未初始化"))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("数据库未初始化"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -289,7 +288,7 @@ func GetDBTableData(c *gin.Context) {
|
||||
|
||||
var total int64
|
||||
if err := gormDB.Raw("SELECT count(*) FROM " + quotedTable).Scan(&total).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -304,7 +303,7 @@ func GetDBTableData(c *gin.Context) {
|
||||
|
||||
rows, err := gormDB.Raw("SELECT * FROM "+quotedTable+" LIMIT ? OFFSET ?", limit, offset).Rows()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
@@ -313,17 +312,17 @@ func GetDBTableData(c *gin.Context) {
|
||||
|
||||
cols, err := rows.Columns()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
results, err := scanTableRows(rows, cols)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(TableDataResponse{
|
||||
c.JSON(http.StatusOK, response.OK(TableDataResponse{
|
||||
Columns: cols,
|
||||
Total: total,
|
||||
Results: results,
|
||||
@@ -441,28 +440,28 @@ func executeSQLMutation(gormDB *gorm.DB, sqlStr string, startTime time.Time) (Ex
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body db_manage.ExecuteSQLRequest true "SQL 请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=db_manage.ExecuteSQLResponse} "执行完毕"
|
||||
// @Failure 400 {object} util.ResponseAny "SQL 语句错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=db_manage.ExecuteSQLResponse} "执行完毕"
|
||||
// @Failure 400 {object} response.Any "SQL 语句错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/db-manage/query [post]
|
||||
func ExecuteSQL(c *gin.Context) {
|
||||
var req ExecuteSQLRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
gormDB := db.DB(c.Request.Context())
|
||||
if gormDB == nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("数据库未初始化"))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("数据库未初始化"))
|
||||
return
|
||||
}
|
||||
|
||||
trimmedSQL := strings.TrimSpace(req.SQL)
|
||||
if trimmedSQL == "" {
|
||||
c.JSON(http.StatusBadRequest, util.Err("SQL 语句不能为空"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("SQL 语句不能为空"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -489,9 +488,9 @@ func ExecuteSQL(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(resp))
|
||||
c.JSON(http.StatusOK, response.OK(resp))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
// Package logs 提供日志查询与分析功能
|
||||
package logs
|
||||
|
||||
import (
|
||||
"context"
|
||||
import ("context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -19,10 +18,10 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/config"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
const (
|
||||
defaultLimit = 200
|
||||
@@ -48,9 +47,9 @@ type logsResponse struct {
|
||||
// @Security SessionCookie
|
||||
// @Param cursor query int false "日志游标,0=获取最新" default(0)
|
||||
// @Param limit query int false "每页条数" default(200)
|
||||
// @Success 200 {object} util.ResponseAny{data=logs.logsResponse} "日志列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=logs.logsResponse} "日志列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/logs [get]
|
||||
func GetLogs(c *gin.Context) {
|
||||
cursorStr := c.DefaultQuery("cursor", "0")
|
||||
@@ -58,7 +57,7 @@ func GetLogs(c *gin.Context) {
|
||||
|
||||
var cursor, limit int
|
||||
if _, err := parsePositiveInt(cursorStr, &cursor); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(admin.InvalidCursorParam))
|
||||
c.JSON(http.StatusBadRequest, response.Err(admin.InvalidCursorParam))
|
||||
return
|
||||
}
|
||||
if _, err := parsePositiveInt(limitStr, &limit); err != nil || limit <= 0 {
|
||||
@@ -78,7 +77,7 @@ func GetLogs(c *gin.Context) {
|
||||
resp.NextCursor = entries[0].Index
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(resp))
|
||||
c.JSON(http.StatusOK, response.OK(resp))
|
||||
}
|
||||
|
||||
// wsMessage WebSocket 消息格式
|
||||
@@ -281,15 +280,15 @@ func fetchAccessLogDetails(ctx context.Context, whereClause string, args []inter
|
||||
// @Param path query string false "接口路径模糊搜索"
|
||||
// @Param start_time query string false "起始时间(RFC3339 或 YYYY-MM-DD HH:MM:SS)"
|
||||
// @Param end_time query string false "结束时间(RFC3339 或 YYYY-MM-DD HH:MM:SS)"
|
||||
// @Success 200 {object} util.ResponseAny{data=logs.accessLogsResponse} "访问日志列表"
|
||||
// @Failure 400 {object} util.ResponseAny "ClickHouse 未启用或参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=logs.accessLogsResponse} "访问日志列表"
|
||||
// @Failure 400 {object} response.Any "ClickHouse 未启用或参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/logs/access [get]
|
||||
func GetAccessLogs(c *gin.Context) {
|
||||
// 1. 检查 ClickHouse 是否启用
|
||||
if !config.Config.ClickHouse.Enabled || db.ChConn == nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("ClickHouse 存储服务未启用,无法检索访问日志"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("ClickHouse 存储服务未启用,无法检索访问日志"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -310,11 +309,11 @@ func GetAccessLogs(c *gin.Context) {
|
||||
// 3. 构建过滤条件
|
||||
conditions, args, userIDs, err := buildAccessLogFilters(c.Request.Context(), c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
if userIDs != nil && len(userIDs) == 0 {
|
||||
c.JSON(http.StatusOK, util.OK(accessLogsResponse{Total: 0, List: []accessLogItem{}}))
|
||||
c.JSON(http.StatusOK, response.OK(accessLogsResponse{Total: 0, List: []accessLogItem{}}))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -327,22 +326,22 @@ func GetAccessLogs(c *gin.Context) {
|
||||
var total uint64
|
||||
countQuery := fmt.Sprintf("SELECT count() FROM w_user_access_logs %s", whereClause)
|
||||
if err := db.ChConn.QueryRow(c.Request.Context(), countQuery, args...).Scan(&total); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("查询 ClickHouse 日志统计失败: "+err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("查询 ClickHouse 日志统计失败: "+err.Error()))
|
||||
return
|
||||
}
|
||||
if total == 0 {
|
||||
c.JSON(http.StatusOK, util.OK(accessLogsResponse{Total: 0, List: []accessLogItem{}}))
|
||||
c.JSON(http.StatusOK, response.OK(accessLogsResponse{Total: 0, List: []accessLogItem{}}))
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 分页查询明细数据
|
||||
list, err := fetchAccessLogDetails(c.Request.Context(), whereClause, args, pageSize, offset)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(accessLogsResponse{
|
||||
c.JSON(http.StatusOK, response.OK(accessLogsResponse{
|
||||
Total: total,
|
||||
List: list,
|
||||
}))
|
||||
@@ -381,15 +380,15 @@ type logsAnalyticsResponse struct {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=logs.logsAnalyticsResponse} "分析统计数据"
|
||||
// @Failure 400 {object} util.ResponseAny "ClickHouse 未启用"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=logs.logsAnalyticsResponse} "分析统计数据"
|
||||
// @Failure 400 {object} response.Any "ClickHouse 未启用"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/logs/analytics [get]
|
||||
func GetLogsAnalytics(c *gin.Context) {
|
||||
// 1. 检查 ClickHouse 是否启用
|
||||
if !config.Config.ClickHouse.Enabled || db.ChConn == nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("ClickHouse 存储服务未启用,无法获取分析数据"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("ClickHouse 存储服务未启用,无法获取分析数据"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -401,7 +400,7 @@ func GetLogsAnalytics(c *gin.Context) {
|
||||
browserList := queryBrowserDistribution(ctx, startTime)
|
||||
topUsers := queryTopActiveUsers(ctx, startTime)
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(logsAnalyticsResponse{
|
||||
c.JSON(http.StatusOK, response.OK(logsAnalyticsResponse{
|
||||
Trend: trendList,
|
||||
Browsers: browserList,
|
||||
TopUsers: topUsers,
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
import ("encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -12,11 +11,11 @@ import (
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
pkgpush "github.com/Rain-kl/Wavelet/pkg/push"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
// ListChannelDefinitions 获取各种消息通道的表单配置定义列表
|
||||
// @Summary 获取所有消息通道配置字段定义
|
||||
@@ -24,10 +23,10 @@ import (
|
||||
// @Tags admin-push
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]Definition} "通道配置定义列表"
|
||||
// @Success 200 {object} response.Any{data=[]Definition} "通道配置定义列表"
|
||||
// @Router /api/v1/admin/push/channels/definitions [get]
|
||||
func ListChannelDefinitions(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, util.OK(ListDefinitions()))
|
||||
c.JSON(http.StatusOK, response.OK(ListDefinitions()))
|
||||
}
|
||||
|
||||
// ListChannels 获取消息通道列表
|
||||
@@ -36,16 +35,16 @@ func ListChannelDefinitions(c *gin.Context) {
|
||||
// @Tags admin-push
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]model.PushChannel} "消息通道列表"
|
||||
// @Success 200 {object} response.Any{data=[]model.PushChannel} "消息通道列表"
|
||||
// @Router /api/v1/admin/push/channels [get]
|
||||
func ListChannels(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var channels []model.PushChannel
|
||||
if err := db.DB(ctx).Order("created_at DESC").Find(&channels).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OK(channels))
|
||||
c.JSON(http.StatusOK, response.OK(channels))
|
||||
}
|
||||
|
||||
// CreateChannelRequest 创建通道参数
|
||||
@@ -67,12 +66,12 @@ type CreateChannelRequest struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body CreateChannelRequest true "创建参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.PushChannel} "创建成功"
|
||||
// @Success 200 {object} response.Any{data=model.PushChannel} "创建成功"
|
||||
// @Router /api/v1/admin/push/channels [post]
|
||||
func CreateChannel(c *gin.Context) {
|
||||
var req CreateChannelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -80,11 +79,11 @@ func CreateChannel(c *gin.Context) {
|
||||
|
||||
var count int64
|
||||
if err := db.DB(ctx).Model(&model.PushChannel{}).Where("name = ?", req.Name).Count(&count).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, util.Err("channel name already exists"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("channel name already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -99,16 +98,16 @@ func CreateChannel(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := channel.Validate(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Create(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(channel))
|
||||
c.JSON(http.StatusOK, response.OK(channel))
|
||||
}
|
||||
|
||||
// UpdateChannelRequest 修改通道参数
|
||||
@@ -130,19 +129,19 @@ type UpdateChannelRequest struct {
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "通道ID"
|
||||
// @Param request body UpdateChannelRequest true "更新参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.PushChannel} "更新成功"
|
||||
// @Success 200 {object} response.Any{data=model.PushChannel} "更新成功"
|
||||
// @Router /api/v1/admin/push/channels/{id} [put]
|
||||
func UpdateChannel(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("invalid channel id"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("invalid channel id"))
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateChannelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,10 +150,10 @@ func UpdateChannel(c *gin.Context) {
|
||||
var channel model.PushChannel
|
||||
if err := db.DB(ctx).Where("id = ?", id).First(&channel).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err("channel not found"))
|
||||
c.JSON(http.StatusNotFound, response.Err("channel not found"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -166,16 +165,16 @@ func UpdateChannel(c *gin.Context) {
|
||||
channel.Enabled = req.Enabled
|
||||
|
||||
if err := channel.Validate(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Save(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(channel))
|
||||
c.JSON(http.StatusOK, response.OK(channel))
|
||||
}
|
||||
|
||||
// DeleteChannel 删除消息通道
|
||||
@@ -185,13 +184,13 @@ func UpdateChannel(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "通道ID"
|
||||
// @Success 200 {object} util.ResponseAny "删除成功"
|
||||
// @Success 200 {object} response.Any "删除成功"
|
||||
// @Router /api/v1/admin/push/channels/{id} [delete]
|
||||
func DeleteChannel(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("invalid channel id"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("invalid channel id"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,19 +198,19 @@ func DeleteChannel(c *gin.Context) {
|
||||
var channel model.PushChannel
|
||||
if err := db.DB(ctx).Where("id = ?", id).First(&channel).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err("channel not found"))
|
||||
c.JSON(http.StatusNotFound, response.Err("channel not found"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Delete(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// TestChannelRequest 测试通道连通性参数
|
||||
@@ -232,12 +231,12 @@ type TestChannelRequest struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body TestChannelRequest true "测试参数"
|
||||
// @Success 200 {object} util.ResponseAny "测试触发成功"
|
||||
// @Success 200 {object} response.Any "测试触发成功"
|
||||
// @Router /api/v1/admin/push/channels/test [post]
|
||||
func TestChannel(c *gin.Context) {
|
||||
var req TestChannelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -247,7 +246,7 @@ func TestChannel(c *gin.Context) {
|
||||
if req.Name != "" {
|
||||
var channel model.PushChannel
|
||||
if err := db.DB(ctx).Where("name = ?", req.Name).First(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("channel not found"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("channel not found"))
|
||||
return
|
||||
}
|
||||
url = channel.URL
|
||||
@@ -276,7 +275,7 @@ func TestChannel(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := tempChannel.Validate(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
url = tempChannel.URL
|
||||
@@ -334,11 +333,11 @@ func TestChannel(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := enqueuePushTask(ctx, payload); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// CustomPushRequest 外部公开推送请求参数
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
import ("bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
@@ -25,7 +24,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
var adminLoginEvent = EventMetadata{
|
||||
Key: "admin_login",
|
||||
@@ -344,7 +344,7 @@ func TestPushRouters(t *testing.T) {
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -415,7 +415,7 @@ func TestPushRouters(t *testing.T) {
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataMap, ok := resp.Data.(map[string]any)
|
||||
@@ -449,7 +449,7 @@ func TestPushRouters(t *testing.T) {
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -674,7 +674,7 @@ func TestPushChannelAPI(t *testing.T) {
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataMap, ok := resp.Data.(map[string]any)
|
||||
@@ -689,7 +689,7 @@ func TestPushChannelAPI(t *testing.T) {
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
list, ok := resp.Data.([]any)
|
||||
assert.True(t, ok)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
// Package push defines push notification HTTP routes.
|
||||
package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
import ("context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -16,11 +15,11 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/task"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/Rain-kl/Wavelet/pkg/push"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
// UpdateEventRequest 更新事件请求参数
|
||||
type UpdateEventRequest struct {
|
||||
@@ -70,17 +69,17 @@ func SyncEvents(ctx context.Context) error {
|
||||
// @Tags admin-push
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]model.PushEvent} "通知事件列表"
|
||||
// @Success 200 {object} response.Any{data=[]model.PushEvent} "通知事件列表"
|
||||
// @Router /api/v1/admin/push/events [get]
|
||||
func ListEvents(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var events []model.PushEvent
|
||||
if err := db.DB(ctx).Order("created_at DESC").Find(&events).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OK(events))
|
||||
c.JSON(http.StatusOK, response.OK(events))
|
||||
}
|
||||
|
||||
// CreateEventRequest 创建事件请求参数
|
||||
@@ -108,10 +107,10 @@ func findBuiltInEvent(key string) (EventMetadata, bool) {
|
||||
// @Tags admin-push
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]EventMetadata} "内置通知事件列表"
|
||||
// @Success 200 {object} response.Any{data=[]EventMetadata} "内置通知事件列表"
|
||||
// @Router /api/v1/admin/push/events/builtin [get]
|
||||
func ListBuiltInEvents(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, util.OK(BuiltInEvents))
|
||||
c.JSON(http.StatusOK, response.OK(BuiltInEvents))
|
||||
}
|
||||
|
||||
func getEventInfo(req CreateEventRequest) (string, string, []byte, error) {
|
||||
@@ -162,12 +161,12 @@ func getEventInfo(req CreateEventRequest) (string, string, []byte, error) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body CreateEventRequest true "创建参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.PushEvent} "创建成功"
|
||||
// @Success 200 {object} response.Any{data=model.PushEvent} "创建成功"
|
||||
// @Router /api/v1/admin/push/events [post]
|
||||
func CreateEvent(c *gin.Context) {
|
||||
var req CreateEventRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -175,18 +174,18 @@ func CreateEvent(c *gin.Context) {
|
||||
|
||||
eventKey, eventName, defaultTemplateBytes, err := getEventInfo(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 检查是否已经创建过该事件的配置
|
||||
var count int64
|
||||
if err := db.DB(ctx).Model(&model.PushEvent{}).Where("event_key = ?", eventKey).Count(&count).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, util.Err("this notification event is already configured"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("this notification event is already configured"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -197,7 +196,7 @@ func CreateEvent(c *gin.Context) {
|
||||
} else {
|
||||
var tempMap map[string]any
|
||||
if err := json.Unmarshal([]byte(templateStr), &tempMap); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("custom template is not a valid JSON format"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("custom template is not a valid JSON format"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -223,16 +222,16 @@ func CreateEvent(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := event.Validate(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Create(&event).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(event))
|
||||
c.JSON(http.StatusOK, response.OK(event))
|
||||
}
|
||||
|
||||
// DeleteEvent 删除通知事件配置
|
||||
@@ -242,13 +241,13 @@ func CreateEvent(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "事件 ID"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "删除成功"
|
||||
// @Success 200 {object} response.Any{data=string} "删除成功"
|
||||
// @Router /api/v1/admin/push/events/{id} [delete]
|
||||
func DeleteEvent(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("invalid event id"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("invalid event id"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -256,19 +255,19 @@ func DeleteEvent(c *gin.Context) {
|
||||
var event model.PushEvent
|
||||
if err := db.DB(ctx).First(&event, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err("notification event not found"))
|
||||
c.JSON(http.StatusNotFound, response.Err("notification event not found"))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Delete(&event).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// UpdateEvent 更新通知事件
|
||||
@@ -280,28 +279,28 @@ func DeleteEvent(c *gin.Context) {
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "事件 ID"
|
||||
// @Param request body push.UpdateEventRequest true "更新参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "修改成功"
|
||||
// @Success 200 {object} response.Any{data=string} "修改成功"
|
||||
// @Router /api/v1/admin/push/events/{id} [put]
|
||||
func UpdateEvent(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("invalid event id"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("invalid event id"))
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateEventRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var event model.PushEvent
|
||||
if err := db.DB(c.Request.Context()).First(&event, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err("notification event not found"))
|
||||
c.JSON(http.StatusNotFound, response.Err("notification event not found"))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -312,16 +311,16 @@ func UpdateEvent(c *gin.Context) {
|
||||
event.Enabled = req.Enabled
|
||||
|
||||
if err := event.Validate(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(c.Request.Context()).Save(&event).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// ToggleEvent 快捷切换通知事件启用状态
|
||||
@@ -331,33 +330,33 @@ func UpdateEvent(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "事件 ID"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "切换成功"
|
||||
// @Success 200 {object} response.Any{data=string} "切换成功"
|
||||
// @Router /api/v1/admin/push/events/{id}/toggle [post]
|
||||
func ToggleEvent(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("invalid event id"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("invalid event id"))
|
||||
return
|
||||
}
|
||||
|
||||
var event model.PushEvent
|
||||
if err := db.DB(c.Request.Context()).First(&event, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err("notification event not found"))
|
||||
c.JSON(http.StatusNotFound, response.Err("notification event not found"))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
event.Enabled = !event.Enabled
|
||||
if err := db.DB(c.Request.Context()).Model(&event).Update("enabled", event.Enabled).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(event.Enabled))
|
||||
c.JSON(http.StatusOK, response.OK(event.Enabled))
|
||||
}
|
||||
|
||||
// pushHistoriesResponse 推送历史分页响应
|
||||
@@ -378,7 +377,7 @@ type pushHistoriesResponse struct {
|
||||
// @Param page_size query int false "分页大小"
|
||||
// @Param event_key query string false "过滤事件名称"
|
||||
// @Param status query string false "过滤发送状态"
|
||||
// @Success 200 {object} util.ResponseAny{data=pushHistoriesResponse} "推送历史列表"
|
||||
// @Success 200 {object} response.Any{data=pushHistoriesResponse} "推送历史列表"
|
||||
// @Router /api/v1/admin/push/histories [get]
|
||||
func ListHistories(c *gin.Context) {
|
||||
pageStr := c.DefaultQuery("page", "1")
|
||||
@@ -405,18 +404,18 @@ func ListHistories(c *gin.Context) {
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var results []model.PushHistory
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Offset(offset).Limit(pageSize).Find(&results).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(map[string]any{
|
||||
c.JSON(http.StatusOK, response.OK(map[string]any{
|
||||
"total": total,
|
||||
"results": results,
|
||||
}))
|
||||
@@ -430,24 +429,24 @@ func ListHistories(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body push.TestPushRequest true "测试请求体"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "测试成功"
|
||||
// @Success 200 {object} response.Any{data=string} "测试成功"
|
||||
// @Router /api/v1/admin/push/test [post]
|
||||
func TestPush(c *gin.Context) {
|
||||
var req TestPushRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
pusher, err := push.GetPusher(req.Config.Channel)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 校验配置
|
||||
if err := pusher.ValidateConfig(req.Config); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(fmt.Sprintf("validation failed: %v", err)))
|
||||
c.JSON(http.StatusBadRequest, response.Err(fmt.Sprintf("validation failed: %v", err)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -479,9 +478,9 @@ func TestPush(c *gin.Context) {
|
||||
|
||||
err = pusher.Send(c.Request.Context(), req.Config, req.Target, testBody, "", nil)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, util.Err(err.Error()))
|
||||
c.JSON(http.StatusOK, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
// Package status 提供系统状态查询接口
|
||||
package status
|
||||
|
||||
import (
|
||||
"context"
|
||||
import ("context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
@@ -18,9 +17,9 @@ import (
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/config"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
// startTime 记录服务启动时间
|
||||
var startTime = time.Now()
|
||||
@@ -135,9 +134,9 @@ func formatDuration(d time.Duration) string {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=status.SystemStatusResponse} "获取成功"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=status.SystemStatusResponse} "获取成功"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/status [get]
|
||||
func GetSystemStatus(c *gin.Context) {
|
||||
var m runtime.MemStats
|
||||
@@ -194,7 +193,7 @@ func GetSystemStatus(c *gin.Context) {
|
||||
NumGC: m.NumGC,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(res))
|
||||
c.JSON(http.StatusOK, response.OK(res))
|
||||
}
|
||||
|
||||
// DatabaseInfoResponse 数据库信息响应结构体
|
||||
@@ -249,9 +248,9 @@ func getPostgresInfo(ctx context.Context) DatabaseInfoResponse {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=status.DatabaseInfoResponse} "获取成功"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=status.DatabaseInfoResponse} "获取成功"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/db-info [get]
|
||||
func GetDatabaseInfo(c *gin.Context) {
|
||||
var info DatabaseInfoResponse
|
||||
@@ -260,7 +259,7 @@ func GetDatabaseInfo(c *gin.Context) {
|
||||
} else {
|
||||
info = getPostgresInfo(c.Request.Context())
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OK(info))
|
||||
c.JSON(http.StatusOK, response.OK(info))
|
||||
}
|
||||
|
||||
// ExportDatabase 导出数据库
|
||||
@@ -270,9 +269,9 @@ func GetDatabaseInfo(c *gin.Context) {
|
||||
// @Produce application/octet-stream
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {file} binary "数据库文件"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "导出失败"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "导出失败"
|
||||
// @Router /api/v1/admin/db-export [get]
|
||||
func ExportDatabase(c *gin.Context) {
|
||||
if !config.Config.Database.Enabled {
|
||||
@@ -291,7 +290,7 @@ func exportSQLite(c *gin.Context) {
|
||||
|
||||
f, err := os.Open(path) //nolint:gosec // path is loaded from server startup configuration, not user input
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("无法打开数据库文件: "+err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("无法打开数据库文件: "+err.Error()))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
@@ -302,7 +301,7 @@ func exportSQLite(c *gin.Context) {
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("无法读取数据库文件信息: "+err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("无法读取数据库文件信息: "+err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -320,7 +319,7 @@ func exportPostgres(c *gin.Context) {
|
||||
// 检查 pg_dump 是否可用
|
||||
pgDumpPath, err := exec.LookPath("pg_dump")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("pg_dump 不可用,请确保服务器已安装 PostgreSQL 客户端工具"))
|
||||
c.JSON(http.StatusInternalServerError, response.Err("pg_dump 不可用,请确保服务器已安装 PostgreSQL 客户端工具"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
package system_config
|
||||
|
||||
import (
|
||||
"context"
|
||||
import ("context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -15,12 +14,12 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/storage"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
mail "github.com/Rain-kl/Wavelet/pkg/mail"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
const maskedConfigValue = "******"
|
||||
|
||||
@@ -48,26 +47,26 @@ type UpdateSystemConfigRequest struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body system_config.CreateSystemConfigRequest true "创建请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "创建成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误或配置键已存在"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=string} "创建成功"
|
||||
// @Failure 400 {object} response.Any "参数错误或配置键已存在"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/system-configs [post]
|
||||
func CreateSystemConfig(c *gin.Context) {
|
||||
var req CreateSystemConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查配置键是否已存在
|
||||
var existing model.SystemConfig
|
||||
if err := db.DB(c.Request.Context()).Where("key = ?", req.Key).First(&existing).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(ConfigKeyExists))
|
||||
c.JSON(http.StatusBadRequest, response.Err(ConfigKeyExists))
|
||||
return
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -91,11 +90,11 @@ func CreateSystemConfig(c *gin.Context) {
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// ListSystemConfigs 获取系统配置列表
|
||||
@@ -105,10 +104,10 @@ func CreateSystemConfig(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param type query string false "配置类型(system/business)"
|
||||
// @Success 200 {object} util.ResponseAny{data=[]model.SystemConfig} "系统配置列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=[]model.SystemConfig} "系统配置列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/system-configs [get]
|
||||
func ListSystemConfigs(c *gin.Context) {
|
||||
configType := c.Query("type")
|
||||
@@ -119,7 +118,7 @@ func ListSystemConfigs(c *gin.Context) {
|
||||
|
||||
var configs []model.SystemConfig
|
||||
if err := query.Find(&configs).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,7 +126,7 @@ func ListSystemConfigs(c *gin.Context) {
|
||||
configs[i].Value = maskSensitiveConfig(configs[i].Key, configs[i].Value)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(configs))
|
||||
c.JSON(http.StatusOK, response.OK(configs))
|
||||
}
|
||||
|
||||
// GetSystemConfig 获取单个系统配置
|
||||
@@ -137,26 +136,26 @@ func ListSystemConfigs(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param key path string true "配置键"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.SystemConfig} "系统配置详情"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "配置不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=model.SystemConfig} "系统配置详情"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "配置不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/system-configs/{key} [get]
|
||||
func GetSystemConfig(c *gin.Context) {
|
||||
var config model.SystemConfig
|
||||
if err := db.DB(c.Request.Context()).Where("key = ?", c.Param("key")).First(&config).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err(SystemConfigNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(SystemConfigNotFound))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
config.Value = maskSensitiveConfig(config.Key, config.Value)
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(config))
|
||||
c.JSON(http.StatusOK, response.OK(config))
|
||||
}
|
||||
|
||||
// UpdateSystemConfig 更新系统配置
|
||||
@@ -168,17 +167,17 @@ func GetSystemConfig(c *gin.Context) {
|
||||
// @Security SessionCookie
|
||||
// @Param key path string true "配置键"
|
||||
// @Param request body system_config.UpdateSystemConfigRequest true "更新请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "更新成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "配置不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=string} "更新成功"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "配置不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/system-configs/{key} [put]
|
||||
func UpdateSystemConfig(c *gin.Context) {
|
||||
var req UpdateSystemConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -188,9 +187,9 @@ func UpdateSystemConfig(c *gin.Context) {
|
||||
var config model.SystemConfig
|
||||
if err := db.DB(c.Request.Context()).Where("key = ?", key).First(&config).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err(SystemConfigNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(SystemConfigNotFound))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -204,7 +203,7 @@ func UpdateSystemConfig(c *gin.Context) {
|
||||
|
||||
validatedVal, err := validateAndMergeStorageConfig(c.Request.Context(), req.Value, config.Value)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
req.Value = validatedVal
|
||||
@@ -251,7 +250,7 @@ func UpdateSystemConfig(c *gin.Context) {
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -260,7 +259,7 @@ func UpdateSystemConfig(c *gin.Context) {
|
||||
storage.PublishCacheInvalidation(c.Request.Context())
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// TestSMTPRequest 测试 SMTP 配置请求
|
||||
@@ -287,13 +286,13 @@ type TestSMTPResponse struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body system_config.TestSMTPRequest true "测试请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=system_config.TestSMTPResponse} "测试执行完毕"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Success 200 {object} response.Any{data=system_config.TestSMTPResponse} "测试执行完毕"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Router /api/v1/admin/system-configs/smtp/test [post]
|
||||
func TestSMTP(c *gin.Context) {
|
||||
var req TestSMTPRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -326,7 +325,7 @@ func TestSMTP(c *gin.Context) {
|
||||
resp.Error = err.Error()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(resp))
|
||||
c.JSON(http.StatusOK, response.OK(resp))
|
||||
}
|
||||
|
||||
func maskSensitiveConfig(key, value string) string {
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
package system_config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
import ("bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
@@ -22,7 +21,8 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
const expectedDefaultConfigsCount = 30
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestListSystemConfigs(t *testing.T) {
|
||||
t.Fatalf("expected 200 OK, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -151,7 +151,7 @@ func TestListSystemConfigs(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -180,7 +180,7 @@ func TestGetSystemConfig(t *testing.T) {
|
||||
t.Errorf("expected 200 OK, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -352,7 +352,7 @@ func TestTestSMTP(t *testing.T) {
|
||||
t.Fatalf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
import ("fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -16,11 +15,11 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/task"
|
||||
taskhandlers "github.com/Rain-kl/Wavelet/internal/task/handlers"
|
||||
"github.com/Rain-kl/Wavelet/internal/task/scheduler"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
func init() {
|
||||
taskhandlers.Register()
|
||||
@@ -32,12 +31,12 @@ func init() {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]task.TaskMeta} "任务类型列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=[]task.TaskMeta} "任务类型列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/tasks/types [get]
|
||||
func ListTaskTypes(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, util.OK(task.GetDispatchableTasks()))
|
||||
c.JSON(http.StatusOK, response.OK(task.GetDispatchableTasks()))
|
||||
}
|
||||
|
||||
// DispatchTaskRequest 下发任务请求
|
||||
@@ -57,22 +56,22 @@ type DispatchTaskRequest struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body DispatchTaskRequest true "任务请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "任务已入队"
|
||||
// @Failure 400 {object} util.ResponseAny "任务类型不存在或参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "任务入队失败"
|
||||
// @Success 200 {object} response.Any{data=string} "任务已入队"
|
||||
// @Failure 400 {object} response.Any "任务类型不存在或参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "任务入队失败"
|
||||
// @Router /api/v1/admin/tasks/dispatch [post]
|
||||
func DispatchTask(c *gin.Context) {
|
||||
var req DispatchTaskRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
meta := task.GetTaskMeta(req.TaskType)
|
||||
if meta == nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(InvalidTaskType))
|
||||
c.JSON(http.StatusBadRequest, response.Err(InvalidTaskType))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,17 +82,17 @@ func DispatchTask(c *gin.Context) {
|
||||
|
||||
validated, err := task.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
taskID, err := task.DispatchTask(c.Request.Context(), req.TaskType, validated, "manual")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", TaskDispatchFailed, err)))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(fmt.Sprintf("%s: %v", TaskDispatchFailed, err)))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(taskID))
|
||||
c.JSON(http.StatusOK, response.OK(taskID))
|
||||
}
|
||||
|
||||
// ListTaskExecutions 查询任务执行记录列表
|
||||
@@ -106,14 +105,14 @@ func DispatchTask(c *gin.Context) {
|
||||
// @Param task_type query string false "任务类型筛选"
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页条数" default(20)
|
||||
// @Success 200 {object} util.ResponseAny{data=object} "任务执行记录列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=object} "任务执行记录列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/tasks/executions [get]
|
||||
func ListTaskExecutions(c *gin.Context) {
|
||||
var req model.ListTaskExecutionsRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -125,11 +124,11 @@ func ListTaskExecutions(c *gin.Context) {
|
||||
|
||||
executions, total, err := model.ListTaskExecutions(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(gin.H{
|
||||
c.JSON(http.StatusOK, response.OK(gin.H{
|
||||
"items": executions,
|
||||
"total": total,
|
||||
"page": req.Page,
|
||||
@@ -144,26 +143,26 @@ func ListTaskExecutions(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "任务执行记录 ID"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.TaskExecution} "任务执行详情"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "记录不存在"
|
||||
// @Success 200 {object} response.Any{data=model.TaskExecution} "任务执行详情"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "记录不存在"
|
||||
// @Router /api/v1/admin/tasks/executions/{id} [get]
|
||||
func GetTaskExecution(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(admin.InvalidTaskExecutionID))
|
||||
c.JSON(http.StatusBadRequest, response.Err(admin.InvalidTaskExecutionID))
|
||||
return
|
||||
}
|
||||
|
||||
execution, err := model.GetTaskExecutionByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, util.Err(TaskNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(TaskNotFound))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(execution))
|
||||
c.JSON(http.StatusOK, response.OK(execution))
|
||||
}
|
||||
|
||||
// RetryTask 重试失败的任务
|
||||
@@ -173,17 +172,17 @@ func GetTaskExecution(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "任务执行记录 ID"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "新任务的 TaskID"
|
||||
// @Failure 400 {object} util.ResponseAny "任务不支持重试或参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "记录不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "重试失败"
|
||||
// @Success 200 {object} response.Any{data=string} "新任务的 TaskID"
|
||||
// @Failure 400 {object} response.Any "任务不支持重试或参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "记录不存在"
|
||||
// @Failure 500 {object} response.Any "重试失败"
|
||||
// @Router /api/v1/admin/tasks/executions/{id}/retry [post]
|
||||
func RetryTask(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(admin.InvalidTaskExecutionID))
|
||||
c.JSON(http.StatusBadRequest, response.Err(admin.InvalidTaskExecutionID))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -192,16 +191,16 @@ func RetryTask(c *gin.Context) {
|
||||
errMsg := err.Error()
|
||||
switch {
|
||||
case strings.Contains(errMsg, "不存在"):
|
||||
c.JSON(http.StatusNotFound, util.Err(errMsg))
|
||||
c.JSON(http.StatusNotFound, response.Err(errMsg))
|
||||
case strings.Contains(errMsg, "只有失败的任务") || strings.Contains(errMsg, "不支持重试") || strings.Contains(errMsg, "已达到最大重试"):
|
||||
c.JSON(http.StatusBadRequest, util.Err(errMsg))
|
||||
c.JSON(http.StatusBadRequest, response.Err(errMsg))
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", TaskRetryFailed, err)))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(fmt.Sprintf("%s: %v", TaskRetryFailed, err)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(newTaskID))
|
||||
c.JSON(http.StatusOK, response.OK(newTaskID))
|
||||
}
|
||||
|
||||
// ListSchedules 获取定时任务列表
|
||||
@@ -210,17 +209,17 @@ func RetryTask(c *gin.Context) {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]model.Schedule} "定时任务列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Success 200 {object} response.Any{data=[]model.Schedule} "定时任务列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/tasks/schedules [get]
|
||||
func ListSchedules(c *gin.Context) {
|
||||
schedules, err := model.ListSchedules(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OK(schedules))
|
||||
c.JSON(http.StatusOK, response.OK(schedules))
|
||||
}
|
||||
|
||||
// CreateScheduleRequest 创建定时任务请求
|
||||
@@ -240,29 +239,29 @@ type CreateScheduleRequest struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body CreateScheduleRequest true "创建定时任务请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.Schedule} "创建成功的定时任务信息"
|
||||
// @Failure 400 {object} util.ResponseAny "Cron 表达式无效、异步任务类型不存在或参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "保存定时任务失败"
|
||||
// @Success 200 {object} response.Any{data=model.Schedule} "创建成功的定时任务信息"
|
||||
// @Failure 400 {object} response.Any "Cron 表达式无效、异步任务类型不存在或参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "保存定时任务失败"
|
||||
// @Router /api/v1/admin/tasks/schedules [post]
|
||||
func CreateSchedule(c *gin.Context) {
|
||||
var req CreateScheduleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 校验 Cron 表达式
|
||||
if _, err := cron.ParseStandard(req.Cron); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(InvalidCronExpression))
|
||||
c.JSON(http.StatusBadRequest, response.Err(InvalidCronExpression))
|
||||
return
|
||||
}
|
||||
|
||||
// 校验关联的异步任务类型
|
||||
meta := task.GetTaskMeta(req.TaskType)
|
||||
if meta == nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(InvalidTaskType))
|
||||
c.JSON(http.StatusBadRequest, response.Err(InvalidTaskType))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -273,7 +272,7 @@ func CreateSchedule(c *gin.Context) {
|
||||
}
|
||||
validated, err := task.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -286,7 +285,7 @@ func CreateSchedule(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := model.CreateSchedule(c.Request.Context(), schedule); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", ScheduleSaveFailed, err)))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(fmt.Sprintf("%s: %v", ScheduleSaveFailed, err)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -295,7 +294,7 @@ func CreateSchedule(c *gin.Context) {
|
||||
logger.ErrorF(c.Request.Context(), "[TaskAdmin] 重载调度器失败: %v", err)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(schedule))
|
||||
c.JSON(http.StatusOK, response.OK(schedule))
|
||||
}
|
||||
|
||||
// UpdateScheduleRequest 修改定时任务请求
|
||||
@@ -316,43 +315,43 @@ type UpdateScheduleRequest struct {
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "定时任务 ID"
|
||||
// @Param request body UpdateScheduleRequest true "修改定时任务请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.Schedule} "修改后的定时任务信息"
|
||||
// @Failure 400 {object} util.ResponseAny "Cron 表达式无效、参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "定时任务不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "修改定时任务失败"
|
||||
// @Success 200 {object} response.Any{data=model.Schedule} "修改后的定时任务信息"
|
||||
// @Failure 400 {object} response.Any "Cron 表达式无效、参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "定时任务不存在"
|
||||
// @Failure 500 {object} response.Any "修改定时任务失败"
|
||||
// @Router /api/v1/admin/tasks/schedules/{id} [put]
|
||||
func UpdateSchedule(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("无效的定时任务ID"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("无效的定时任务ID"))
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateScheduleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查定时任务是否存在
|
||||
schedule, err := model.GetScheduleByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, util.Err(ScheduleNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(ScheduleNotFound))
|
||||
return
|
||||
}
|
||||
|
||||
// 校验 Cron 表达式
|
||||
if _, err := cron.ParseStandard(req.Cron); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(InvalidCronExpression))
|
||||
c.JSON(http.StatusBadRequest, response.Err(InvalidCronExpression))
|
||||
return
|
||||
}
|
||||
|
||||
// 校验关联的异步任务类型
|
||||
meta := task.GetTaskMeta(req.TaskType)
|
||||
if meta == nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(InvalidTaskType))
|
||||
c.JSON(http.StatusBadRequest, response.Err(InvalidTaskType))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -363,7 +362,7 @@ func UpdateSchedule(c *gin.Context) {
|
||||
}
|
||||
validated, err := task.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -374,7 +373,7 @@ func UpdateSchedule(c *gin.Context) {
|
||||
schedule.IsActive = *req.IsActive
|
||||
|
||||
if err := model.UpdateSchedule(c.Request.Context(), schedule); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", ScheduleSaveFailed, err)))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(fmt.Sprintf("%s: %v", ScheduleSaveFailed, err)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -383,7 +382,7 @@ func UpdateSchedule(c *gin.Context) {
|
||||
logger.ErrorF(c.Request.Context(), "[TaskAdmin] 重载调度器失败: %v", err)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(schedule))
|
||||
c.JSON(http.StatusOK, response.OK(schedule))
|
||||
}
|
||||
|
||||
// DeleteSchedule 删除定时任务
|
||||
@@ -393,21 +392,21 @@ func UpdateSchedule(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "定时任务 ID"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "删除结果"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "删除定时任务失败"
|
||||
// @Success 200 {object} response.Any{data=string} "删除结果"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "删除定时任务失败"
|
||||
// @Router /api/v1/admin/tasks/schedules/{id} [delete]
|
||||
func DeleteSchedule(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err("无效的定时任务ID"))
|
||||
c.JSON(http.StatusBadRequest, response.Err("无效的定时任务ID"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := model.DeleteSchedule(c.Request.Context(), id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", ScheduleDeleteFailed, err)))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(fmt.Sprintf("%s: %v", ScheduleDeleteFailed, err)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -416,5 +415,5 @@ func DeleteSchedule(c *gin.Context) {
|
||||
logger.ErrorF(c.Request.Context(), "[TaskAdmin] 重载调度器失败: %v", err)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
package task
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
import ("bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -26,7 +25,8 @@ import (
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
func setupTaskTestEnvironment(t *testing.T) func() {
|
||||
_, mr, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
@@ -78,7 +78,7 @@ func TestListTaskTypes(t *testing.T) {
|
||||
t.Errorf("expected 200 OK, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -126,7 +126,7 @@ func TestDispatchTask(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Body: %s", w.Body.String())
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.Empty(t, resp.ErrorMsg)
|
||||
assert.NotNil(t, resp.Data)
|
||||
@@ -150,7 +150,7 @@ func TestDispatchTask(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Body: %s", w.Body.String())
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.Empty(t, resp.ErrorMsg)
|
||||
assert.NotNil(t, resp.Data)
|
||||
@@ -168,7 +168,7 @@ func TestDispatchTask(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.Contains(t, resp.ErrorMsg, "无效的 JSON 格式")
|
||||
})
|
||||
@@ -185,7 +185,7 @@ func TestDispatchTask(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.Contains(t, resp.ErrorMsg, "不能为空")
|
||||
})
|
||||
@@ -202,7 +202,7 @@ func TestDispatchTask(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.Equal(t, InvalidTaskType, resp.ErrorMsg)
|
||||
})
|
||||
@@ -244,7 +244,7 @@ func TestListTaskExecutions(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -261,7 +261,7 @@ func TestListTaskExecutions(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -278,7 +278,7 @@ func TestListTaskExecutions(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -295,7 +295,7 @@ func TestListTaskExecutions(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -312,7 +312,7 @@ func TestListTaskExecutions(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -355,7 +355,7 @@ func TestGetTaskExecution(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -419,7 +419,7 @@ func TestRetryTask(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.Empty(t, resp.ErrorMsg)
|
||||
assert.NotNil(t, resp.Data)
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"errors"
|
||||
import ("errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
// CreateTemplateRequest 创建模板请求
|
||||
type CreateTemplateRequest struct {
|
||||
@@ -41,26 +40,26 @@ type UpdateTemplateRequest struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body template.CreateTemplateRequest true "创建请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "创建成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误或模板标识符已存在"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=string} "创建成功"
|
||||
// @Failure 400 {object} response.Any "参数错误或模板标识符已存在"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/templates [post]
|
||||
func CreateTemplate(c *gin.Context) {
|
||||
var req CreateTemplateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查模板 Key 是否已存在
|
||||
var existing model.Template
|
||||
if err := db.DB(c.Request.Context()).Where("key = ?", req.Key).First(&existing).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(TemplateKeyExists))
|
||||
c.JSON(http.StatusBadRequest, response.Err(TemplateKeyExists))
|
||||
return
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,16 +74,16 @@ func CreateTemplate(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := tmpl.Validate(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(c.Request.Context()).Create(&tmpl).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(tmpl))
|
||||
c.JSON(http.StatusOK, response.OK(tmpl))
|
||||
}
|
||||
|
||||
// ListTemplates 获取模板列表
|
||||
@@ -93,19 +92,19 @@ func CreateTemplate(c *gin.Context) {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]model.Template} "模板列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=[]model.Template} "模板列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/templates [get]
|
||||
func ListTemplates(c *gin.Context) {
|
||||
var templates []model.Template
|
||||
if err := db.DB(c.Request.Context()).Order("is_system DESC, created_at DESC").Find(&templates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(templates))
|
||||
c.JSON(http.StatusOK, response.OK(templates))
|
||||
}
|
||||
|
||||
// GetTemplate 获取单个模板
|
||||
@@ -115,24 +114,24 @@ func ListTemplates(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param key path string true "模板标识符"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.Template} "模板详情"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "模板不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=model.Template} "模板详情"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "模板不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/templates/{key} [get]
|
||||
func GetTemplate(c *gin.Context) {
|
||||
var tmpl model.Template
|
||||
if err := db.DB(c.Request.Context()).Where("key = ?", c.Param("key")).First(&tmpl).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err(TemplateNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(TemplateNotFound))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(tmpl))
|
||||
c.JSON(http.StatusOK, response.OK(tmpl))
|
||||
}
|
||||
|
||||
// UpdateTemplate 更新模板
|
||||
@@ -144,17 +143,17 @@ func GetTemplate(c *gin.Context) {
|
||||
// @Security SessionCookie
|
||||
// @Param key path string true "模板标识符"
|
||||
// @Param request body template.UpdateTemplateRequest true "更新请求参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.Template} "更新成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "模板不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=model.Template} "更新成功"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "模板不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/templates/{key} [put]
|
||||
func UpdateTemplate(c *gin.Context) {
|
||||
var req UpdateTemplateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -164,9 +163,9 @@ func UpdateTemplate(c *gin.Context) {
|
||||
var tmpl model.Template
|
||||
if err := db.DB(c.Request.Context()).Where("key = ?", key).First(&tmpl).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err(TemplateNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(TemplateNotFound))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -178,16 +177,16 @@ func UpdateTemplate(c *gin.Context) {
|
||||
tmpl.Description = req.Description
|
||||
|
||||
if err := tmpl.Validate(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(c.Request.Context()).Save(&tmpl).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(tmpl))
|
||||
c.JSON(http.StatusOK, response.OK(tmpl))
|
||||
}
|
||||
|
||||
// DeleteTemplate 删除模板
|
||||
@@ -197,12 +196,12 @@ func UpdateTemplate(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param key path string true "模板标识符"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "删除成功"
|
||||
// @Failure 400 {object} util.ResponseAny "不可删除系统模板"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "模板不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=string} "删除成功"
|
||||
// @Failure 400 {object} response.Any "不可删除系统模板"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "模板不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/templates/{key} [delete]
|
||||
func DeleteTemplate(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
@@ -211,23 +210,23 @@ func DeleteTemplate(c *gin.Context) {
|
||||
var tmpl model.Template
|
||||
if err := db.DB(c.Request.Context()).Where("key = ?", key).First(&tmpl).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, util.Err(TemplateNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(TemplateNotFound))
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 限制系统模板删除
|
||||
if tmpl.IsSystem {
|
||||
c.JSON(http.StatusBadRequest, util.Err(SystemTemplateCannotDelete))
|
||||
c.JSON(http.StatusBadRequest, response.Err(SystemTemplateCannotDelete))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(c.Request.Context()).Delete(&tmpl).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
import ("bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -15,7 +14,8 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
@@ -122,7 +122,7 @@ func TestListTemplates(t *testing.T) {
|
||||
t.Fatalf("expected 200 OK, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
|
||||
@@ -3,15 +3,14 @@
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
import ("context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
// GetUpdateStatus 获取应用更新状态
|
||||
// @Summary 获取应用更新状态
|
||||
@@ -19,19 +18,19 @@ import (
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=updater.Status} "更新状态"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "查询失败"
|
||||
// @Success 200 {object} response.Any{data=updater.Status} "更新状态"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "查询失败"
|
||||
// @Router /api/v1/admin/update [get]
|
||||
func GetUpdateStatus(c *gin.Context) {
|
||||
status, _, err := defaultManager.status(c.Request.Context())
|
||||
if err != nil {
|
||||
logger.ErrorF(c.Request.Context(), "[Updater] check release failed: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OK(status))
|
||||
c.JSON(http.StatusOK, response.OK(status))
|
||||
}
|
||||
|
||||
// ApplyUpdate 下载并应用应用更新
|
||||
@@ -40,22 +39,22 @@ func GetUpdateStatus(c *gin.Context) {
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny "升级已准备并即将重启"
|
||||
// @Failure 400 {object} util.ResponseAny "当前版本不可升级"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "升级准备失败"
|
||||
// @Success 200 {object} response.Any "升级已准备并即将重启"
|
||||
// @Failure 400 {object} response.Any "当前版本不可升级"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "升级准备失败"
|
||||
// @Router /api/v1/admin/update/apply [post]
|
||||
func ApplyUpdate(c *gin.Context) {
|
||||
executable, stagedBinary, err := defaultManager.prepareUpgrade(c.Request.Context())
|
||||
if err != nil {
|
||||
logger.ErrorF(c.Request.Context(), "[Updater] prepare upgrade failed: %v", err)
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
logger.InfoF(c.Request.Context(), "[Updater] upgrade prepared; restarting with %s", stagedBinary)
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
import ("net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -17,7 +16,8 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
// minPasswordLength 密码最小长度
|
||||
const minPasswordLength = 8
|
||||
@@ -57,7 +57,7 @@ type listUsersResponse struct {
|
||||
func parseUserID(c *gin.Context) (uint64, bool) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.JSON(http.StatusBadRequest, util.Err(userNotFound))
|
||||
c.JSON(http.StatusBadRequest, response.Err(userNotFound))
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
@@ -90,17 +90,17 @@ func toUser(u model.User) user {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request query listUsersRequest true "查询参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=user.listUsersResponse} "用户列表"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=user.listUsersResponse} "用户列表"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/users [get]
|
||||
// ListUsers 获取用户列表
|
||||
func ListUsers(c *gin.Context) {
|
||||
var req listUsersRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func ListUsers(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -132,11 +132,11 @@ func ListUsers(c *gin.Context) {
|
||||
Offset(offset).
|
||||
Limit(req.PageSize).
|
||||
Find(&users).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(listUsersResponse{
|
||||
c.JSON(http.StatusOK, response.OK(listUsersResponse{
|
||||
Users: users,
|
||||
Total: total,
|
||||
}))
|
||||
@@ -149,12 +149,12 @@ func ListUsers(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "用户 ID"
|
||||
// @Success 200 {object} util.ResponseAny{data=user.user} "用户详情"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 404 {object} util.ResponseAny "用户不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=user.user} "用户详情"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 404 {object} response.Any "用户不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/users/{id} [get]
|
||||
func GetUser(c *gin.Context) {
|
||||
id, ok := parseUserID(c)
|
||||
@@ -169,14 +169,14 @@ func GetUser(c *gin.Context) {
|
||||
Where("id = ?", id).
|
||||
First(&targetUser).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, util.Err(userNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(userNotFound))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(toUser(targetUser)))
|
||||
c.JSON(http.StatusOK, response.OK(toUser(targetUser)))
|
||||
}
|
||||
|
||||
// updateUserStatusRequest 更新用户状态请求
|
||||
@@ -193,17 +193,17 @@ type updateUserStatusRequest struct {
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "用户 ID"
|
||||
// @Param request body updateUserStatusRequest true "状态参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "更新成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限或尝试禁用管理员"
|
||||
// @Failure 404 {object} util.ResponseAny "用户不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=string} "更新成功"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限或尝试禁用管理员"
|
||||
// @Failure 404 {object} response.Any "用户不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/users/{id}/status [put]
|
||||
func UpdateUserStatus(c *gin.Context) {
|
||||
var req updateUserStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -222,15 +222,15 @@ func UpdateUserStatus(c *gin.Context) {
|
||||
Where("id = ?", id).
|
||||
First(&targetUser).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, util.Err(userNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(userNotFound))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if !req.IsActive && targetUser.IsAdmin {
|
||||
c.JSON(http.StatusForbidden, util.Err(cannotDisable))
|
||||
c.JSON(http.StatusForbidden, response.Err(cannotDisable))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -238,11 +238,11 @@ func UpdateUserStatus(c *gin.Context) {
|
||||
Model(&model.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("is_active", req.IsActive).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(updateUserFailed))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(updateUserFailed))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
@@ -252,12 +252,12 @@ func UpdateUserStatus(c *gin.Context) {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "用户 ID"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "删除成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限、尝试删除管理员或当前用户"
|
||||
// @Failure 404 {object} util.ResponseAny "用户不存在"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=string} "删除成功"
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限、尝试删除管理员或当前用户"
|
||||
// @Failure 404 {object} response.Any "用户不存在"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/users/{id} [delete]
|
||||
func DeleteUser(c *gin.Context) {
|
||||
id, ok := parseUserID(c)
|
||||
@@ -267,7 +267,7 @@ func DeleteUser(c *gin.Context) {
|
||||
|
||||
currUser, _ := util.GetFromContext[*model.User](c, oauth.UserObjKey)
|
||||
if currUser != nil && currUser.ID == id {
|
||||
c.JSON(http.StatusForbidden, util.Err(cannotDeleteSelf))
|
||||
c.JSON(http.StatusForbidden, response.Err(cannotDeleteSelf))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -281,15 +281,15 @@ func DeleteUser(c *gin.Context) {
|
||||
Where("id = ?", id).
|
||||
First(&targetUser).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, util.Err(userNotFound))
|
||||
c.JSON(http.StatusNotFound, response.Err(userNotFound))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if targetUser.IsAdmin {
|
||||
c.JSON(http.StatusForbidden, util.Err(cannotDelete))
|
||||
c.JSON(http.StatusForbidden, response.Err(cannotDelete))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -302,11 +302,11 @@ func DeleteUser(c *gin.Context) {
|
||||
}
|
||||
return tx.Where("id = ?", id).Delete(&model.User{}).Error
|
||||
}); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(deleteUserFailed))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(deleteUserFailed))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// createUserRequest 创建用户请求
|
||||
@@ -327,16 +327,16 @@ type createUserRequest struct {
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body user.createUserRequest true "创建用户参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=user.user} "创建成功"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误或用户名已存在"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Success 200 {object} response.Any{data=user.user} "创建成功"
|
||||
// @Failure 400 {object} response.Any "参数错误或用户名已存在"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Router /api/v1/admin/users [post]
|
||||
func CreateUser(c *gin.Context) {
|
||||
var req createUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
c.JSON(http.StatusBadRequest, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -346,36 +346,36 @@ func CreateUser(c *gin.Context) {
|
||||
req.Email = strings.TrimSpace(req.Email)
|
||||
|
||||
if req.Username == "" {
|
||||
c.JSON(http.StatusBadRequest, util.Err(usernameRequired))
|
||||
c.JSON(http.StatusBadRequest, response.Err(usernameRequired))
|
||||
return
|
||||
}
|
||||
if req.Email == "" {
|
||||
c.JSON(http.StatusBadRequest, util.Err(emailRequired))
|
||||
c.JSON(http.StatusBadRequest, response.Err(emailRequired))
|
||||
return
|
||||
}
|
||||
if len(req.Password) < minPasswordLength {
|
||||
c.JSON(http.StatusBadRequest, util.Err(passwordTooShort))
|
||||
c.JSON(http.StatusBadRequest, response.Err(passwordTooShort))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
var count int64
|
||||
if err := db.DB(ctx).Model(&model.User{}).Where("username = ?", req.Username).Count(&count).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, util.Err(usernameExists))
|
||||
c.JSON(http.StatusBadRequest, response.Err(usernameExists))
|
||||
return
|
||||
}
|
||||
|
||||
var emailCount int64
|
||||
if err := db.DB(ctx).Model(&model.User{}).Where("email = ?", req.Email).Count(&emailCount).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
if emailCount > 0 {
|
||||
c.JSON(http.StatusBadRequest, util.Err(emailExists))
|
||||
c.JSON(http.StatusBadRequest, response.Err(emailExists))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -393,14 +393,14 @@ func CreateUser(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := newUser.SetEncryptedPassword(req.Password); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Create(&newUser).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, response.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(toUser(newUser)))
|
||||
c.JSON(http.StatusOK, response.OK(toUser(newUser)))
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
import ("bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -17,7 +16,8 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
@@ -90,7 +90,7 @@ func TestListUsers(t *testing.T) {
|
||||
t.Errorf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ func TestListUsers(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -136,7 +136,7 @@ func TestListUsers(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
@@ -194,7 +194,7 @@ func TestGetUser(t *testing.T) {
|
||||
t.Fatalf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
@@ -280,7 +280,7 @@ func TestUpdateUserStatus(t *testing.T) {
|
||||
t.Errorf("expected 403 Forbidden, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp.ErrorMsg != cannotDisable {
|
||||
t.Errorf("expected error message '%s', got '%s'", cannotDisable, resp.ErrorMsg)
|
||||
@@ -327,7 +327,7 @@ func TestCreateUser(t *testing.T) {
|
||||
t.Errorf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
@@ -386,7 +386,7 @@ func TestCreateUser(t *testing.T) {
|
||||
t.Errorf("expected 400 Bad Request, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp.ErrorMsg != usernameExists {
|
||||
t.Errorf("expected error '%s', got '%s'", usernameExists, resp.ErrorMsg)
|
||||
@@ -419,7 +419,7 @@ func TestCreateUser(t *testing.T) {
|
||||
t.Errorf("expected 400 Bad Request, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
var resp response.Any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp.ErrorMsg != emailExists {
|
||||
t.Errorf("expected error '%s', got '%s'", emailExists, resp.ErrorMsg)
|
||||
|
||||
Reference in New Issue
Block a user