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:
ryan
2026-06-15 16:38:44 +08:00
parent b3ed94342c
commit 953af7d8db
43 changed files with 1833 additions and 1853 deletions
+41 -42
View File
@@ -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)
+24 -25
View File
@@ -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 {
+34 -35
View File
@@ -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))
}
+25 -26
View File
@@ -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,
+35 -36
View File
@@ -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 外部公开推送请求参数
+8 -8
View File
@@ -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)
+47 -48
View File
@@ -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())
}
+17 -18
View File
@@ -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
}
+43 -44
View File
@@ -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)
+80 -81
View File
@@ -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())
}
+16 -16
View File
@@ -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)
+51 -52
View File
@@ -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())
}
+4 -4
View File
@@ -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)
+16 -17
View File
@@ -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)
+62 -62
View File
@@ -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)))
}
+11 -11
View File
@@ -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)