mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 23:16:37 +08:00
prettier
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
|
||||
package auth_source
|
||||
|
||||
import ("bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -15,7 +16,8 @@ import ("bytes"
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
r := testhelper.NewTestGinEngine()
|
||||
|
||||
Vendored
+1
-1
@@ -11,4 +11,4 @@ import (
|
||||
|
||||
func saveOrUpdateConfig(ctx context.Context, key, value string) error {
|
||||
return repository.SaveOrUpdateSystemConfig(ctx, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -97,4 +97,4 @@ func ClearCache(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
// overview information, and executing custom SQL queries.
|
||||
package db_manage
|
||||
|
||||
import ("database/sql"
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
@@ -19,7 +20,8 @@ import ("database/sql"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
const (
|
||||
binaryKB = 0
|
||||
|
||||
@@ -427,4 +427,4 @@ func GetLogsAnalytics(c *gin.Context) {
|
||||
Browsers: browserList,
|
||||
TopUsers: topUsers,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ func TestWebSocketCheckOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
origin string
|
||||
host string
|
||||
wantOK bool
|
||||
name string
|
||||
origin string
|
||||
host string
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
name: "empty origin (non-browser clients)",
|
||||
|
||||
@@ -275,4 +275,4 @@ func renderCustomPayload(template string, req CustomPushRequest) string {
|
||||
result = strings.ReplaceAll(result, "$url", escapeJSONString(req.URL))
|
||||
result = strings.ReplaceAll(result, "$to", escapeJSONString(req.To))
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +35,4 @@ func handleAdminLogin(ctx context.Context, event listener.AdminLoggedIn) {
|
||||
"time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
push.DefaultTrigger.Trigger(ctx, AdminLogin, body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ import (
|
||||
func Register() {
|
||||
push.RegisterBuiltInEvent(AdminLogin)
|
||||
listener.OnAdminLoggedIn(handleAdminLogin)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/internal/task"
|
||||
@@ -339,4 +340,4 @@ func resolveDynamicKeyword(target string, flatBody map[string]any) string {
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,4 +410,4 @@ func getEventInfo(req CreateEventRequest) (string, string, []byte, error) {
|
||||
return "", "", nil, err
|
||||
}
|
||||
return req.EventKey, meta.Name, defaultTemplateBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,4 +289,4 @@ func TestPush(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +121,4 @@ func extractUsername(data map[string]any) string {
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
// Package status 提供系统状态查询接口
|
||||
package status
|
||||
|
||||
import ("context"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
@@ -19,7 +20,8 @@ import ("context"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
// startTime 记录服务启动时间
|
||||
var startTime = time.Now()
|
||||
|
||||
@@ -7,9 +7,9 @@ package system_config
|
||||
|
||||
// 系统配置错误消息常量
|
||||
const (
|
||||
SystemConfigNotFound = "系统配置不存在"
|
||||
ConfigKeyRequired = "配置键不能为空"
|
||||
ConfigValueRequired = "配置值不能为空"
|
||||
ConfigKeyExists = "配置键已存在"
|
||||
SystemConfigNotFound = "系统配置不存在"
|
||||
ConfigKeyRequired = "配置键不能为空"
|
||||
ConfigValueRequired = "配置值不能为空"
|
||||
ConfigKeyExists = "配置键已存在"
|
||||
StorageDriverSwitchRequiresMigration = "存在存量文件,请通过存储迁移任务切换存储引擎"
|
||||
)
|
||||
|
||||
@@ -125,4 +125,4 @@ func resolveStorageMigrationTasksOnDirectDriverUpdate(
|
||||
}).Error; err != nil {
|
||||
logger.ErrorF(ctx, "自动更新迁移任务状态失败: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
package task
|
||||
|
||||
import ("fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -18,7 +19,8 @@ import ("fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
// ListTaskTypes 获取支持的任务类型列表
|
||||
// @Summary 获取支持的任务类型
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
package task
|
||||
|
||||
import ("bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -25,7 +26,8 @@ import ("bytes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
func setupTaskTestEnvironment(t *testing.T) func() {
|
||||
_, mr, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
|
||||
@@ -75,4 +75,4 @@ func deleteTemplate(ctx context.Context, key string) error {
|
||||
return errors.New(SystemTemplateCannotDelete)
|
||||
}
|
||||
return repository.DeleteTemplate(ctx, &tmpl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,4 +173,4 @@ func DeleteTemplate(c *gin.Context) {
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
package template
|
||||
|
||||
import ("bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -14,7 +15,8 @@ import ("bytes"
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
r := testhelper.NewTestGinEngine()
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
|
||||
package updater
|
||||
|
||||
import ("context"
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
// GetUpdateStatus 获取应用更新状态
|
||||
// @Summary 获取应用更新状态
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
package user
|
||||
|
||||
const (
|
||||
userNotFound = "用户不存在"
|
||||
cannotDisable = "不能禁用管理员用户"
|
||||
cannotDelete = "不能删除管理员用户"
|
||||
cannotDeleteSelf = "不能删除当前登录用户"
|
||||
updateUserFailed = "更新用户状态失败"
|
||||
deleteUserFailed = "删除用户失败"
|
||||
usernameExists = "用户名已存在"
|
||||
usernameRequired = "用户名不能为空"
|
||||
passwordTooShort = "密码长度不能少于 8 位" //nolint:gosec // false positive: this is an error message, not hardcoded credentials
|
||||
userNotFound = "用户不存在"
|
||||
cannotDisable = "不能禁用管理员用户"
|
||||
cannotDelete = "不能删除管理员用户"
|
||||
cannotDeleteSelf = "不能删除当前登录用户"
|
||||
updateUserFailed = "更新用户状态失败"
|
||||
deleteUserFailed = "删除用户失败"
|
||||
usernameExists = "用户名已存在"
|
||||
usernameRequired = "用户名不能为空"
|
||||
passwordTooShort = "密码长度不能少于 8 位" //nolint:gosec // false positive: this is an error message, not hardcoded credentials
|
||||
createUserFailed = "创建用户失败"
|
||||
emailRequired = "邮箱不能为空"
|
||||
emailExists = "邮箱已被注册"
|
||||
|
||||
@@ -193,4 +193,4 @@ func GetDefaultManager() *Manager {
|
||||
defaultManager = NewManager(secret, store)
|
||||
})
|
||||
return defaultManager
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,4 +159,4 @@ func TestVerifyTokenConcurrentRace(t *testing.T) {
|
||||
if got := success.Load(); got != 1 {
|
||||
t.Fatalf("successful VerifyToken count = %d, want %d", got, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,4 @@ func VerifyMiddleware(mgr *Manager, scope string) gin.HandlerFunc {
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ import "github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
|
||||
func init() {
|
||||
testhelper.RegisterCleanup(ResetRuntimeSettingsForTest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ func TestListVisibleSystemConfigsUsesStoreCache(t *testing.T) {
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
repository.ResetSystemConfigRAMCacheForTest()
|
||||
if err := repository.InvalidateVisibleSystemConfigsCache(ctx); err != nil {
|
||||
t.Fatalf("InvalidateVisibleSystemConfigsCache() error = %v", err)
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
package config
|
||||
|
||||
import ("encoding/json"
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -13,7 +14,8 @@ import ("encoding/json"
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
func TestGetPublicConfigUsesVisibility(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
|
||||
@@ -19,7 +19,6 @@ func TestSystemConfigRAMCacheServesUntilInvalidated(t *testing.T) {
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
repository.ResetSystemConfigRAMCacheForTest()
|
||||
if err := repository.InvalidateAllSystemConfigCaches(ctx); err != nil {
|
||||
t.Fatalf("InvalidateAllSystemConfigCaches() error = %v", err)
|
||||
@@ -72,7 +71,6 @@ func TestInvalidateSystemConfigCacheBroadcast(t *testing.T) {
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
repository.ResetSystemConfigRAMCacheForTest()
|
||||
|
||||
// Initially seed in cache
|
||||
@@ -109,4 +107,4 @@ func TestInvalidateSystemConfigCacheBroadcast(t *testing.T) {
|
||||
// Just a sanity check: we can publish a new manual update and verify subscription triggers
|
||||
// which was done implicitly above.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
// Package custom provides custom business handlers
|
||||
package custom
|
||||
|
||||
import ("net/http"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
// Hello is a sample handler for custom business logic
|
||||
// @Summary Sample Hello API
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
// Package health 提供健康检查端点
|
||||
package health
|
||||
|
||||
import ("net/http"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
// Health 健康检查
|
||||
// @Summary 健康检查
|
||||
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
|
||||
// Session 用户信息字段 Key
|
||||
const (
|
||||
UserNameKey = "username"
|
||||
UserIDKey = "user_id"
|
||||
UserObjKey = "user_obj"
|
||||
TokenAuthKey = "token_auth" // 标记当前请求是否通过 Access Token 鉴权
|
||||
TokenAdminKey = "token_admin" // Access Token 本身是否具有管理员权限
|
||||
SessionTokenKey = "oauth_session_token" //nolint:gosec // false positive: this is a session key, not hardcoded credentials
|
||||
PasswordHashKey = "password_hash"
|
||||
UserNameKey = "username"
|
||||
UserIDKey = "user_id"
|
||||
UserObjKey = "user_obj"
|
||||
TokenAuthKey = "token_auth" // 标记当前请求是否通过 Access Token 鉴权
|
||||
TokenAdminKey = "token_admin" // Access Token 本身是否具有管理员权限
|
||||
SessionTokenKey = "oauth_session_token" //nolint:gosec // false positive: this is a session key, not hardcoded credentials
|
||||
PasswordHashKey = "password_hash"
|
||||
)
|
||||
|
||||
// OAuth State 缓存 Key 格式与过期时间
|
||||
@@ -54,4 +54,3 @@ func decodeOAuthStatePayload(value string) (oauthStatePayload, error) {
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@ func GetFromContext[T any](c *gin.Context, key string) (T, bool) {
|
||||
// SetToContext 设置值到 Gin 请求上下文。
|
||||
func SetToContext[T any](c *gin.Context, key string, value T) {
|
||||
c.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,4 +170,4 @@ func Authorize(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(OAuthAuthorizeResponse{AuthorizeURL: authorizeURL}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,4 +62,4 @@ func DeleteExternalAccount(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -140,4 +140,4 @@ func parseCommaSeparatedWhitelist(value string) []string {
|
||||
}
|
||||
}
|
||||
return whitelist
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -284,4 +284,4 @@ func TestGetUploadByIDWorksWithRedisDisabled(t *testing.T) {
|
||||
if gotCached.ID != upload.ID {
|
||||
t.Fatal("expected RAM cache hit when redis is disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +35,4 @@ const (
|
||||
ErrS3KeyStartsWithSlash = shared.ErrS3KeyStartsWithSlash
|
||||
ErrS3KeyContainsNullBytes = shared.ErrS3KeyContainsNullBytes
|
||||
ErrQueryUnusedUploadsFailed = shared.ErrQueryUnusedUploadsFailed
|
||||
)
|
||||
)
|
||||
|
||||
@@ -22,15 +22,15 @@ func TestGetDistinctUploadTypes(t *testing.T) {
|
||||
dbConn.Create(&user)
|
||||
|
||||
customUpload := model.Upload{
|
||||
ID: 9001,
|
||||
UserID: user.ID,
|
||||
FileName: "custom.txt",
|
||||
FilePath: "uploads/custom.txt",
|
||||
FileSize: 10,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Type: "custom_type_xyz",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 9001,
|
||||
UserID: user.ID,
|
||||
FileName: "custom.txt",
|
||||
FilePath: "uploads/custom.txt",
|
||||
FileSize: 10,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Type: "custom_type_xyz",
|
||||
Status: model.UploadStatusUsed,
|
||||
}
|
||||
dbConn.Create(&customUpload)
|
||||
|
||||
@@ -61,4 +61,4 @@ func TestGetDistinctUploadTypes(t *testing.T) {
|
||||
if len(resp.Data) != 1 || resp.Data[0] != "custom_type_xyz" {
|
||||
t.Errorf("expected only custom_type_xyz in types list, got: %v", resp.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,4 +83,4 @@ func loadUploadStats(ctx context.Context) ([]model.UploadStat, error) {
|
||||
|
||||
func isRecordNotFound(err error) bool {
|
||||
return errors.Is(err, gorm.ErrRecordNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,14 +343,14 @@ func TestDownloadFile(t *testing.T) {
|
||||
|
||||
// Seed upload records in DB
|
||||
localUpload := model.Upload{
|
||||
ID: 2001,
|
||||
UserID: 1001,
|
||||
FileName: "中文文件名.txt",
|
||||
FilePath: "uploads/test_download.txt",
|
||||
FileSize: 12,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 2001,
|
||||
UserID: 1001,
|
||||
FileName: "中文文件名.txt",
|
||||
FilePath: "uploads/test_download.txt",
|
||||
FileSize: 12,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
}
|
||||
|
||||
// Create local file
|
||||
@@ -410,44 +410,44 @@ func TestListFiles(t *testing.T) {
|
||||
|
||||
uploads := []model.Upload{
|
||||
{
|
||||
ID: 2101,
|
||||
UserID: authUser.ID,
|
||||
FileName: "first-report.txt",
|
||||
FilePath: "uploads/first-report.txt",
|
||||
FileSize: 10,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 2101,
|
||||
UserID: authUser.ID,
|
||||
FileName: "first-report.txt",
|
||||
FilePath: "uploads/first-report.txt",
|
||||
FileSize: 10,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 2102,
|
||||
UserID: authUser.ID,
|
||||
FileName: "Second-Photo.PNG",
|
||||
FilePath: "uploads/second-photo.png",
|
||||
FileSize: 20,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 2102,
|
||||
UserID: authUser.ID,
|
||||
FileName: "Second-Photo.PNG",
|
||||
FilePath: "uploads/second-photo.png",
|
||||
FileSize: 20,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 2103,
|
||||
UserID: authUser.ID,
|
||||
FileName: "third-notes.md",
|
||||
FilePath: "uploads/third-notes.md",
|
||||
FileSize: 30,
|
||||
MimeType: "text/markdown",
|
||||
Extension: "md",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 2103,
|
||||
UserID: authUser.ID,
|
||||
FileName: "third-notes.md",
|
||||
FilePath: "uploads/third-notes.md",
|
||||
FileSize: 30,
|
||||
MimeType: "text/markdown",
|
||||
Extension: "md",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 2104,
|
||||
UserID: 2002,
|
||||
FileName: "other-user.txt",
|
||||
FilePath: "uploads/other-user.txt",
|
||||
FileSize: 40,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 2104,
|
||||
UserID: 2002,
|
||||
FileName: "other-user.txt",
|
||||
FilePath: "uploads/other-user.txt",
|
||||
FileSize: 40,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
}
|
||||
for i := range uploads {
|
||||
@@ -562,34 +562,34 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
// Seed upload records. Note f2 and f3 have the same FileName "file_a.txt" to trigger name collision resolution.
|
||||
uploads := []model.Upload{
|
||||
{
|
||||
ID: 3001,
|
||||
UserID: 1001,
|
||||
FileName: "file_a.txt",
|
||||
FilePath: "uploads/f1.txt",
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 3001,
|
||||
UserID: 1001,
|
||||
FileName: "file_a.txt",
|
||||
FilePath: "uploads/f1.txt",
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 3002,
|
||||
UserID: 1001,
|
||||
FileName: "file_b.txt",
|
||||
FilePath: "uploads/f2.txt",
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 3002,
|
||||
UserID: 1001,
|
||||
FileName: "file_b.txt",
|
||||
FilePath: "uploads/f2.txt",
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 3003,
|
||||
UserID: 1001,
|
||||
FileName: "file_a.txt", // COLLISION with 3001!
|
||||
FilePath: "uploads/f3.txt",
|
||||
FileSize: 28,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 3003,
|
||||
UserID: 1001,
|
||||
FileName: "file_a.txt", // COLLISION with 3001!
|
||||
FilePath: "uploads/f3.txt",
|
||||
FileSize: 28,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -750,40 +750,40 @@ func TestGetFileStats(t *testing.T) {
|
||||
// Insert some dummy uploads
|
||||
uploads := []model.Upload{
|
||||
{
|
||||
ID: 3101,
|
||||
UserID: authUser.ID,
|
||||
FileName: "photo.png",
|
||||
FilePath: "uploads/photo.png",
|
||||
FileSize: 100,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Type: "generic",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
ID: 3101,
|
||||
UserID: authUser.ID,
|
||||
FileName: "photo.png",
|
||||
FilePath: "uploads/photo.png",
|
||||
FileSize: 100,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Type: "generic",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
ID: 3102,
|
||||
UserID: authUser.ID,
|
||||
FileName: "video.mp4",
|
||||
FilePath: "uploads/video.mp4",
|
||||
FileSize: 500,
|
||||
MimeType: "video/mp4",
|
||||
Extension: "mp4",
|
||||
Type: "generic",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now().AddDate(0, 0, -2), // 2 days ago
|
||||
ID: 3102,
|
||||
UserID: authUser.ID,
|
||||
FileName: "video.mp4",
|
||||
FilePath: "uploads/video.mp4",
|
||||
FileSize: 500,
|
||||
MimeType: "video/mp4",
|
||||
Extension: "mp4",
|
||||
Type: "generic",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now().AddDate(0, 0, -2), // 2 days ago
|
||||
},
|
||||
{
|
||||
ID: 3103,
|
||||
UserID: authUser.ID,
|
||||
FileName: "document.pdf",
|
||||
FilePath: "uploads/document.pdf",
|
||||
FileSize: 200,
|
||||
MimeType: "application/pdf",
|
||||
Extension: "pdf",
|
||||
Type: "avatar", // different type
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now().AddDate(0, 0, -10), // older than 7 days
|
||||
ID: 3103,
|
||||
UserID: authUser.ID,
|
||||
FileName: "document.pdf",
|
||||
FilePath: "uploads/document.pdf",
|
||||
FileSize: 200,
|
||||
MimeType: "application/pdf",
|
||||
Extension: "pdf",
|
||||
Type: "avatar", // different type
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now().AddDate(0, 0, -10), // older than 7 days
|
||||
},
|
||||
}
|
||||
|
||||
@@ -865,26 +865,26 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
|
||||
// Seed upload records
|
||||
upload1 := model.Upload{
|
||||
ID: 4001,
|
||||
UserID: 1001,
|
||||
FileName: "user1-file.txt",
|
||||
FilePath: "uploads/user1-file.txt",
|
||||
FileSize: 100,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
ID: 4001,
|
||||
UserID: 1001,
|
||||
FileName: "user1-file.txt",
|
||||
FilePath: "uploads/user1-file.txt",
|
||||
FileSize: 100,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
upload2 := model.Upload{
|
||||
ID: 4002,
|
||||
UserID: 1002,
|
||||
FileName: "user2-file.png",
|
||||
FilePath: "uploads/user2-file.png",
|
||||
FileSize: 200,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
ID: 4002,
|
||||
UserID: 1002,
|
||||
FileName: "user2-file.png",
|
||||
FilePath: "uploads/user2-file.png",
|
||||
FileSize: 200,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
_ = dbConn.Create(&upload1)
|
||||
|
||||
@@ -123,4 +123,4 @@ func GetFileStats(c *gin.Context) {
|
||||
Categories: categories,
|
||||
Types: types,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,4 @@ import (
|
||||
var ErrForbidden = errors.New("upload forbidden")
|
||||
|
||||
// ErrStorageReadOnly indicates the storage backend is in migration read-only mode.
|
||||
var ErrStorageReadOnly = errors.New(shared.ErrStorageReadOnly)
|
||||
var ErrStorageReadOnly = errors.New(shared.ErrStorageReadOnly)
|
||||
|
||||
@@ -195,4 +195,4 @@ func createNewUpload(ctx context.Context, req Request) (Result, error) {
|
||||
Created: true,
|
||||
Stored: true,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,4 @@ func ingestWithHashPolicy(ctx context.Context, req Request) (Result, error) {
|
||||
}
|
||||
|
||||
return createNewUpload(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,4 +326,4 @@ func setupMockStorage(t *testing.T, putCount *int) (restore func(), disable func
|
||||
storage.ResetCache()
|
||||
}
|
||||
return restore, disable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,4 @@ func softDeleteUploadWithStats(ctx context.Context, upload *model.Upload) error
|
||||
}
|
||||
uploadcache.InvalidateUploadMetaCache(ctx, upload.ID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,4 @@ type Result struct {
|
||||
Created bool
|
||||
Stored bool
|
||||
Resolved bool
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,16 @@ package shared
|
||||
|
||||
// Upload size, path, media quality, and cache constants shared across subpackages.
|
||||
const (
|
||||
MaxUploadSize = 32 * 1024 * 1024 // 32MB
|
||||
DetectContentBytes = 512 // http.DetectContentType 需要的最小字节数
|
||||
UploadDirPerm = 0755 // 上传目录权限
|
||||
UploadFilePerm = 0644 // 上传文件权限
|
||||
ImageQualityLow = "low"
|
||||
ImageQualityMedium = "medium"
|
||||
ImageQualityHigh = "high"
|
||||
ImageQualityOrigin = "origin"
|
||||
MaxUploadSize = 32 * 1024 * 1024 // 32MB
|
||||
DetectContentBytes = 512 // http.DetectContentType 需要的最小字节数
|
||||
UploadDirPerm = 0755 // 上传目录权限
|
||||
UploadFilePerm = 0644 // 上传文件权限
|
||||
ImageQualityLow = "low"
|
||||
ImageQualityMedium = "medium"
|
||||
ImageQualityHigh = "high"
|
||||
ImageQualityOrigin = "origin"
|
||||
DefaultPublicUploadType = "avatar"
|
||||
FileStatsTrendDays = 7
|
||||
MaxS3KeyLength = 1024
|
||||
AccessCacheTTL = 5 // seconds; multiplied by time.Second at use site
|
||||
)
|
||||
)
|
||||
|
||||
@@ -38,4 +38,4 @@ const (
|
||||
ErrInvalidImageCacheWarmupQuality = "图片质量仅支持 low、medium、high"
|
||||
ErrParseImageCacheWarmupPayload = "解析图片缓存预热参数失败: %w"
|
||||
ErrQueryImagesForCacheWarmup = "查询待预热图片失败: %w"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -40,4 +40,4 @@ func GetFileCategory(mimeType, ext string) string {
|
||||
return catDocument
|
||||
}
|
||||
return catOther
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,4 +128,4 @@ func RecordUploadStatsRemove(ctx context.Context, upload *model.Upload) {
|
||||
|
||||
func isActiveUploadStatus(status model.UploadStatus) bool {
|
||||
return status == model.UploadStatusPending || status == model.UploadStatusUsed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +100,4 @@ func loadUploadStats(ctx context.Context) (uploadStatsSnapshot, error) {
|
||||
TotalCount: rows[0].FileCount,
|
||||
TotalSize: rows[0].FileSize,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,4 +85,4 @@ func buildMigrationAccessState(ctx context.Context) MigrationAccessState {
|
||||
state.Target = target
|
||||
state.HasTarget = true
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +90,4 @@ func NormalizeMigrationPayload(ctx context.Context, payload []byte) ([]byte, sto
|
||||
return nil, storage.Config{}, fmt.Errorf("marshal storage migration payload: %w", err)
|
||||
}
|
||||
return normalized, target, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,4 @@ func OpenStoredObject(ctx context.Context, upload *model.Upload) (*storage.Objec
|
||||
return nil, err
|
||||
}
|
||||
return backend.Get(ctx, upload.FilePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,4 +374,4 @@ func isNotFoundError(err error) bool {
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,16 +60,16 @@ func TestMigrationHandlerExecute(t *testing.T) {
|
||||
}
|
||||
|
||||
upload := model.Upload{
|
||||
ID: 99101,
|
||||
UserID: 1,
|
||||
FileName: "test.txt",
|
||||
FilePath: "uploads/test.txt",
|
||||
FileSize: int64(len(content)),
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Hash: "hash",
|
||||
Type: "attachment",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 99101,
|
||||
UserID: 1,
|
||||
FileName: "test.txt",
|
||||
FilePath: "uploads/test.txt",
|
||||
FileSize: int64(len(content)),
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Hash: "hash",
|
||||
Type: "attachment",
|
||||
Status: model.UploadStatusUsed,
|
||||
}
|
||||
if err := dbConn.Create(&upload).Error; err != nil {
|
||||
t.Fatalf("Create(upload) returned error: %v", err)
|
||||
@@ -157,16 +157,16 @@ func TestMigrationHandlerExecuteWithHashValidation(t *testing.T) {
|
||||
|
||||
// Case 1: Incorrect Hash (should fail validation)
|
||||
uploadIncorrect := model.Upload{
|
||||
ID: 99102,
|
||||
UserID: 1,
|
||||
FileName: "test-hash.txt",
|
||||
FilePath: "uploads/test-hash.txt",
|
||||
FileSize: int64(len(content)),
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // Invalid hash
|
||||
Type: "attachment",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 99102,
|
||||
UserID: 1,
|
||||
FileName: "test-hash.txt",
|
||||
FilePath: "uploads/test-hash.txt",
|
||||
FileSize: int64(len(content)),
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // Invalid hash
|
||||
Type: "attachment",
|
||||
Status: model.UploadStatusUsed,
|
||||
}
|
||||
if err := dbConn.Create(&uploadIncorrect).Error; err != nil {
|
||||
t.Fatalf("Create(uploadIncorrect) returned error: %v", err)
|
||||
|
||||
@@ -181,4 +181,4 @@ func (h *WarmImageCacheHandler) Execute(ctx context.Context, payload []byte) (*t
|
||||
)
|
||||
task.AppendLog(ctx, "%s", msg)
|
||||
return &task.TaskResult{Message: msg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,40 +280,40 @@ func TestWarmImageCacheHandlerExecute(t *testing.T) {
|
||||
|
||||
records := []model.Upload{
|
||||
{
|
||||
ID: 4101,
|
||||
UserID: 1001,
|
||||
FileName: "first.png",
|
||||
FilePath: firstPath,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 4101,
|
||||
UserID: 1001,
|
||||
FileName: "first.png",
|
||||
FilePath: firstPath,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 4102,
|
||||
UserID: 1001,
|
||||
FileName: "second.jpg",
|
||||
FilePath: secondPath,
|
||||
MimeType: "application/octet-stream",
|
||||
Extension: "jpg",
|
||||
Status: model.UploadStatusPending,
|
||||
ID: 4102,
|
||||
UserID: 1001,
|
||||
FileName: "second.jpg",
|
||||
FilePath: secondPath,
|
||||
MimeType: "application/octet-stream",
|
||||
Extension: "jpg",
|
||||
Status: model.UploadStatusPending,
|
||||
},
|
||||
{
|
||||
ID: 4103,
|
||||
UserID: 1001,
|
||||
FileName: "notes.txt",
|
||||
FilePath: filepath.Join(testDir, "notes.txt"),
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
ID: 4103,
|
||||
UserID: 1001,
|
||||
FileName: "notes.txt",
|
||||
FilePath: filepath.Join(testDir, "notes.txt"),
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 4104,
|
||||
UserID: 1001,
|
||||
FileName: "deleted.png",
|
||||
FilePath: firstPath,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusDeleted,
|
||||
ID: 4104,
|
||||
UserID: 1001,
|
||||
FileName: "deleted.png",
|
||||
FilePath: firstPath,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusDeleted,
|
||||
},
|
||||
}
|
||||
for i := range records {
|
||||
|
||||
@@ -47,4 +47,4 @@ func NormalizeImageQuality(quality string) string {
|
||||
default:
|
||||
return shared.ImageQualityOrigin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,4 @@ func CompressImageToWebP(srcReader io.Reader, quality string) ([]byte, error) {
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,4 @@ func TestInitSyncsPushEventsOnce(t *testing.T) {
|
||||
if adminLogin.Name != "管理员登录" {
|
||||
t.Fatalf("admin_login name = %q, want %q", adminLogin.Name, "管理员登录")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ func runBootstrap(opts bootstrap.Options) {
|
||||
ctx, span := trace.Start(context.Background(), "bootstrap.Init")
|
||||
defer span.End()
|
||||
bootstrap.Init(ctx, opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestResetPasswdCmd_WithUserAndPassword(t *testing.T) {
|
||||
|
||||
// Execute command with args
|
||||
rootCmd.SetArgs([]string{"reset-passwd", "--user", "testuser1", "--password", "newpassword123"})
|
||||
|
||||
|
||||
// Capture output
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
@@ -225,4 +225,3 @@ func TestResetPasswdCmd_InteractiveMode(t *testing.T) {
|
||||
t.Errorf("expected password to change, but it matches the old one")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,4 +42,4 @@ func AbortTooManyRequests(c *gin.Context, msg string) {
|
||||
// AbortConflict 以 409 中断请求并将错误挂载到 Gin Error 链。
|
||||
func AbortConflict(c *gin.Context, msg string) {
|
||||
AbortWithError(c, http.StatusConflict, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,4 @@ func ErrorHandlerMiddleware() gin.HandlerFunc {
|
||||
|
||||
c.JSON(http.StatusInternalServerError, Err("内部系统错误"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,4 +165,4 @@ func TestErrorHandlerMiddleware_RecordsSpanOnAPIError(t *testing.T) {
|
||||
assert.Equal(t, codes.Error, spans[0].Status().Code)
|
||||
assert.Equal(t, "bad request", spans[0].Status().Description)
|
||||
require.NotEmpty(t, spans[0].Events())
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ package db
|
||||
const (
|
||||
errRedisHashSetFailed = "failed to set redis hash: %w"
|
||||
errRedisHashDeleteFailed = "failed to delete redis hash field: %w"
|
||||
errUnmarshalDataFailed = "failed to unmarshal data: %w"
|
||||
errMarshalDataFailed = "failed to marshal data: %w"
|
||||
errRedisKeySetFailed = "failed to set redis key: %w"
|
||||
errUnmarshalDataFailed = "failed to unmarshal data: %w"
|
||||
errMarshalDataFailed = "failed to marshal data: %w"
|
||||
errRedisKeySetFailed = "failed to set redis key: %w"
|
||||
)
|
||||
|
||||
@@ -43,4 +43,4 @@ func NextUint64ID() uint64 {
|
||||
log.Printf("[Snowflake] generated negative ID: %d (attempt %d/%d)", id, attempt, maxNegativeIDRetries)
|
||||
}
|
||||
panic(fmt.Sprintf("[Snowflake] generated negative ID after %d attempts", maxNegativeIDRetries))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ import (
|
||||
func TestNextUint64ID(t *testing.T) {
|
||||
id := NextUint64ID()
|
||||
assert.NotZero(t, id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,4 +48,4 @@ func TestMigrateClickHouseSkipsWhenDisabled(t *testing.T) {
|
||||
})
|
||||
|
||||
MigrateClickHouse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,4 @@ func EmitAdminLoggedIn(ctx context.Context, user *model.User, ip string) {
|
||||
for _, handler := range adminLoggedInHandlers {
|
||||
handler(ctx, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +41,4 @@ func (UserAccessLog) InsertColumns() string {
|
||||
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
|
||||
func (UserAccessLog) BatchInsertSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (%s)", userAccessLogTableName, userAccessLogInsertColumns)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +96,4 @@ func validateJSON(s string) error {
|
||||
return nil
|
||||
}
|
||||
return errors.New("payload schema must be a valid JSON format")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
// PushEvent 系统通知事件模型
|
||||
type PushEvent struct {
|
||||
ID uint64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
EventKey string `json:"event_key" gorm:"uniqueIndex;size:80;not null"` // 如 admin_login
|
||||
Name string `json:"name" gorm:"size:100;not null"` // 如 管理员登录
|
||||
EventKey string `json:"event_key" gorm:"uniqueIndex;size:80;not null"` // 如 admin_login
|
||||
Name string `json:"name" gorm:"size:100;not null"` // 如 管理员登录
|
||||
TaskType string `json:"task_type" gorm:"size:100;index;not null;default:''"` // 关联的异步任务类型
|
||||
Channels []string `json:"channels" gorm:"type:text;serializer:json;not null"` // 推送渠道列表,如 ["lark"]
|
||||
Targets []string `json:"targets" gorm:"type:text;serializer:json;not null"` // 推送目标用户/邮箱列表
|
||||
Template string `json:"template" gorm:"type:text;not null"` // 消息模板 JSON
|
||||
Enabled bool `json:"enabled" gorm:"index;not null;default:false"` // 是否启用
|
||||
Channels []string `json:"channels" gorm:"type:text;serializer:json;not null"` // 推送渠道列表,如 ["lark"]
|
||||
Targets []string `json:"targets" gorm:"type:text;serializer:json;not null"` // 推送目标用户/邮箱列表
|
||||
Template string `json:"template" gorm:"type:text;not null"` // 消息模板 JSON
|
||||
Enabled bool `json:"enabled" gorm:"index;not null;default:false"` // 是否启用
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime;index"`
|
||||
}
|
||||
@@ -47,4 +47,4 @@ func (pe *PushEvent) Validate() error {
|
||||
return errors.New("cannot enable event without any push channels configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +60,4 @@ type SystemConfig struct {
|
||||
// TableName 表名
|
||||
func (SystemConfig) TableName() string {
|
||||
return "w_system_configs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,4 @@ type UploadStat struct {
|
||||
// TableName returns the upload stats table name.
|
||||
func (UploadStat) TableName() string {
|
||||
return "w_upload_stats"
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -32,20 +32,20 @@ type UploadMetadata struct {
|
||||
|
||||
// Upload 上传文件记录
|
||||
type Upload struct {
|
||||
ID uint64 `json:"id,string" gorm:"primaryKey"`
|
||||
UserID uint64 `json:"user_id,string" gorm:"index;not null"`
|
||||
FileName string `json:"file_name" gorm:"size:255;not null"` // 原始文件名 (例如: image.png)
|
||||
FilePath string `json:"file_path" gorm:"size:500;not null;index"` // 文件相对路径 / S3 Key
|
||||
FileSize int64 `json:"file_size" gorm:"not null"` // 文件大小(字节)
|
||||
MimeType string `json:"mime_type" gorm:"size:100;not null"` // 媒体类型 (MIME, 如 image/png)
|
||||
Extension string `json:"extension" gorm:"size:50;not null"` // 文件后缀名 (不含点,如 png, pdf)
|
||||
Hash string `json:"hash" gorm:"size:64;index"` // 文件哈希 (SHA-256/MD5,可用于排重)
|
||||
Type string `json:"type" gorm:"column:type;size:50;not null;index"` // 业务标识类型 (如 avatar, doc, attachment)
|
||||
Status UploadStatus `json:"status" gorm:"type:varchar(20);not null"` // 状态
|
||||
AccessMode int `json:"access_mode" gorm:"column:access_mode;not null;default:0"`
|
||||
Metadata UploadMetadata `json:"metadata" gorm:"serializer:json;type:jsonb"` // 业务扩展元数据
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
ID uint64 `json:"id,string" gorm:"primaryKey"`
|
||||
UserID uint64 `json:"user_id,string" gorm:"index;not null"`
|
||||
FileName string `json:"file_name" gorm:"size:255;not null"` // 原始文件名 (例如: image.png)
|
||||
FilePath string `json:"file_path" gorm:"size:500;not null;index"` // 文件相对路径 / S3 Key
|
||||
FileSize int64 `json:"file_size" gorm:"not null"` // 文件大小(字节)
|
||||
MimeType string `json:"mime_type" gorm:"size:100;not null"` // 媒体类型 (MIME, 如 image/png)
|
||||
Extension string `json:"extension" gorm:"size:50;not null"` // 文件后缀名 (不含点,如 png, pdf)
|
||||
Hash string `json:"hash" gorm:"size:64;index"` // 文件哈希 (SHA-256/MD5,可用于排重)
|
||||
Type string `json:"type" gorm:"column:type;size:50;not null;index"` // 业务标识类型 (如 avatar, doc, attachment)
|
||||
Status UploadStatus `json:"status" gorm:"type:varchar(20);not null"` // 状态
|
||||
AccessMode int `json:"access_mode" gorm:"column:access_mode;not null;default:0"`
|
||||
Metadata UploadMetadata `json:"metadata" gorm:"serializer:json;type:jsonb"` // 业务扩展元数据
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
|
||||
@@ -93,4 +93,4 @@ func applyFilter(query *gorm.DB, filter AccessLogFilter) *gorm.DB {
|
||||
query = query.Where("created_at <= ?", *filter.EndTime)
|
||||
}
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ type AccessLogFilter struct {
|
||||
StartTime *time.Time
|
||||
// EndTime filters created_at <= EndTime when non-nil.
|
||||
EndTime *time.Time
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,4 +156,4 @@ func GetTopActiveUsers(ctx context.Context, startTime time.Time, limit int) ([]T
|
||||
return nil, fmt.Errorf("get top active users: %w", err)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +141,10 @@ func TestBatchInsert_UsesModelBatchSQL(t *testing.T) {
|
||||
}
|
||||
|
||||
type mockConn struct {
|
||||
batch driver.Batch
|
||||
batchQuery string
|
||||
prepareCalled bool
|
||||
preparedQuery string
|
||||
batch driver.Batch
|
||||
batchQuery string
|
||||
prepareCalled bool
|
||||
preparedQuery string
|
||||
}
|
||||
|
||||
func (m *mockConn) Contributors() []string { return nil }
|
||||
@@ -204,4 +204,4 @@ func (m *mockBatch) Rows() int { return len(m.rows) }
|
||||
|
||||
func (m *mockBatch) Columns() []column.Interface { return nil }
|
||||
|
||||
func (m *mockBatch) Close() error { return nil }
|
||||
func (m *mockBatch) Close() error { return nil }
|
||||
|
||||
@@ -46,4 +46,4 @@ func BatchInsert(ctx context.Context, logs []analyticsmodel.UserAccessLog) error
|
||||
return fmt.Errorf("send clickhouse batch: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,4 +27,4 @@ func ParseBrowserName(ua string) string {
|
||||
return "Safari"
|
||||
}
|
||||
return "Other"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func setupAuthSourceCacheTest(t *testing.T) (*gorm.DB, *miniredis.Miniredis, fun
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
db.Redis = redis.NewClient(&redis.Options{
|
||||
Addr: miniRedis.Addr(),
|
||||
Addr: miniRedis.Addr(),
|
||||
MaintNotificationsConfig: &maintnotifications.Config{
|
||||
Mode: maintnotifications.ModeDisabled,
|
||||
},
|
||||
@@ -237,4 +237,4 @@ func TestAuthSourceInvalidationPubSubClearsPeerRAM(t *testing.T) {
|
||||
if _, ok := authSourceActiveRAM.GetIfPresent(authSourceActiveRAMKey); ok {
|
||||
t.Fatal("expected peer RAM cache to be cleared by pub/sub")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,4 +102,4 @@ func DeleteActivePushChannelCache(ctx context.Context, name string) {
|
||||
if db.Redis != nil {
|
||||
_ = db.Redis.Del(ctx, db.PrefixedKey("push:channel:active:"+name)).Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +121,4 @@ func DeleteActivePushEventCache(ctx context.Context, key string) {
|
||||
if db.Redis != nil {
|
||||
_ = db.Redis.Del(ctx, db.PrefixedKey("push:event:active:"+key)).Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,4 @@ func CreatePushHistory(ctx context.Context, history *model.PushHistory) error {
|
||||
// PushHistoryQuery returns a scoped query builder for push histories.
|
||||
func PushHistoryQuery(ctx context.Context) *gorm.DB {
|
||||
return db.DB(ctx).Model(&model.PushHistory{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
const (
|
||||
configTypeSystem = "system"
|
||||
errDatabaseNotInitialized = "database not initialized"
|
||||
errConfigIntParseFailed = "配置 %s 的值 '%s' 无法转换为整数: %w"
|
||||
errConfigDecimalParseFailed = "配置 %s 的值 '%s' 无法转换为decimal: %w"
|
||||
errConfigBoolParseFailed = "配置 %s 的值 '%s' 无法转换为布尔值: %w"
|
||||
errParseMenuDisplayConfigFailed = "解析目录显示配置失败: %w"
|
||||
errConfigIntParseFailed = "配置 %s 的值 '%s' 无法转换为整数: %w"
|
||||
errConfigDecimalParseFailed = "配置 %s 的值 '%s' 无法转换为decimal: %w"
|
||||
errConfigBoolParseFailed = "配置 %s 的值 '%s' 无法转换为布尔值: %w"
|
||||
errParseMenuDisplayConfigFailed = "解析目录显示配置失败: %w"
|
||||
)
|
||||
|
||||
// PreheatSystemConfigs loads all system configs from database.
|
||||
@@ -198,7 +198,6 @@ func ListVisibleSystemConfigs(ctx context.Context) ([]model.SystemConfig, error)
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
|
||||
// GetIntByKey queries config and converts to int.
|
||||
func GetIntByKey(ctx context.Context, key string) (int, error) {
|
||||
sc, err := GetSystemConfigByKey(ctx, key)
|
||||
@@ -261,4 +260,4 @@ func GetMenuDisplayConfig(ctx context.Context) (map[string]bool, error) {
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,4 @@ func SaveOrUpdateSystemConfig(ctx context.Context, key, value string) error {
|
||||
}
|
||||
}
|
||||
return InvalidateSystemConfigCache(ctx, key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ func setupSystemConfigTest(t *testing.T) (*gorm.DB, func()) {
|
||||
db.SetDB(sqliteDB)
|
||||
db.Redis = redisClient
|
||||
|
||||
|
||||
cleanup := func() {
|
||||
StopSystemConfigCacheListener()
|
||||
ResetSystemConfigRAMCacheForTest()
|
||||
@@ -156,4 +155,4 @@ func TestGetSystemConfigByGroupAndInvalidation(t *testing.T) {
|
||||
if updated.Value != "new_site_name" {
|
||||
t.Fatalf("value = %q, want %q", updated.Value, "new_site_name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,4 +56,4 @@ func SaveTemplate(ctx context.Context, tmpl *model.Template) error {
|
||||
// DeleteTemplate removes a template record.
|
||||
func DeleteTemplate(ctx context.Context, tmpl *model.Template) error {
|
||||
return db.DB(ctx).Delete(tmpl).Error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,4 +127,4 @@ func ListUploadsByIDs(ctx context.Context, ids []uint64) ([]model.Upload, error)
|
||||
// UploadQuery returns a scoped GORM query for uploads.
|
||||
func UploadQuery(ctx context.Context) *gorm.DB {
|
||||
return db.DB(ctx).Model(&model.Upload{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@ func ListUploadStats(ctx context.Context) ([]model.UploadStat, error) {
|
||||
return nil, err
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,4 +108,3 @@ func init() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
package root
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterFrontend is a package-level variable overridden by frontend.go when built with embed_frontend.
|
||||
var RegisterFrontend = func(_ *gin.Engine) {
|
||||
// No-op by default
|
||||
// No-op by default
|
||||
}
|
||||
|
||||
// RegisterRootRoutes registers custom business routes that belong to the root path.
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func TestStorageCache(t *testing.T) {
|
||||
|
||||
@@ -14,4 +14,4 @@ func runExtraCleanups() {
|
||||
for _, fn := range extraCleanups {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user