mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 22:46:38 +08:00
refactor(architecture): eliminate internal package and complete cordis single-owner model and repository migration
- Physically purged all legacy internal/ packages, centralized pkg/model/ and pkg/repository/ - Migrated domain models and database repositories into self-contained owner plugins (user, auth, message_gateway, admin, upload, risk_control) - Decoupled cross-plugin interactions via pure core/contracts and typed EventBus - Ensured 100% test coverage pass, zero data races (-race clean), and 0 lint issues in make code-check
This commit is contained in:
@@ -7,15 +7,16 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/internal/shared/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/Rain-kl/Wavelet/pkg/response"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/ingest"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/models"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/repository"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/shared"
|
||||
uploadstorage "github.com/Rain-kl/Wavelet/plugins/domain/upload/storage"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type listFilesRequest struct {
|
||||
@@ -28,10 +29,10 @@ type listFilesRequest struct {
|
||||
}
|
||||
|
||||
type listFilesResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Items []model.Upload `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Items []models.Upload `json:"items"`
|
||||
}
|
||||
|
||||
// ListFiles 获取系统上传的文件列表
|
||||
@@ -44,12 +45,11 @@ type listFilesResponse struct {
|
||||
// @Param keyword query string false "文件名关键词(模糊匹配)"
|
||||
// @Param type query string false "业务分类过滤"
|
||||
// @Param extension query string false "扩展名过滤"
|
||||
// @Param user_id query uint64 false "上传用户 ID"
|
||||
// @Param user_id query int false "上传用户 ID 过滤"
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} response.Any{data=listFilesResponse} "查询成功"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/uploads [get]
|
||||
// @Failure 400 {object} response.Any "参数错误"
|
||||
// @Router /api/v1/admin/uploads/files [get]
|
||||
func ListFiles(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
@@ -86,17 +86,16 @@ func ListFiles(c *gin.Context) {
|
||||
}))
|
||||
}
|
||||
|
||||
// DeleteFile 软删除文件记录
|
||||
// DeleteFile 软删除指定的文件记录
|
||||
// @Summary 删除文件
|
||||
// @Description 将文件状态置为 deleted(软删除),不会立即清理底层存储对象
|
||||
// @Description 将指定 ID 的文件状态置为 deleted(软删除)
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Param id path string true "文件 ID"
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} response.Any "删除成功"
|
||||
// @Failure 403 {object} response.Any "无权操作"
|
||||
// @Failure 404 {object} response.Any "文件不存在"
|
||||
// @Router /api/v1/admin/uploads/{id} [delete]
|
||||
// @Router /api/v1/admin/uploads/files/{id} [delete]
|
||||
func DeleteFile(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
if uploadstorage.ReadOnly(ctx) {
|
||||
@@ -121,21 +120,19 @@ func DeleteFile(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// GetDistinctUploadTypes 获取数据库中所有已存在的文件业务类型
|
||||
// @Summary 获取文件业务类型列表
|
||||
// @Description 返回数据库中所有已上传文件实际拥有的业务类型列表
|
||||
// GetDistinctUploadTypes 获取所有已存在的文件业务分类列表
|
||||
// @Summary 获取业务分类列表
|
||||
// @Description 查询系统内所有不重复的上传业务分类标识(如 avatar, doc 等)
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} response.Any{data=[]string} "业务类型列表"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
// @Success 200 {object} response.Any{data=[]string} "查询成功"
|
||||
// @Router /api/v1/admin/uploads/types [get]
|
||||
func GetDistinctUploadTypes(c *gin.Context) {
|
||||
types, err := listDistinctUploadTypes(c.Request.Context())
|
||||
ctx := c.Request.Context()
|
||||
types, err := listDistinctUploadTypes(ctx)
|
||||
if err != nil {
|
||||
response.AbortInternal(c, err.Error())
|
||||
response.AbortBadRequest(c, shared.ErrQueryTypeListFailed)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(types))
|
||||
@@ -150,10 +147,10 @@ type listMyFilesRequest struct {
|
||||
}
|
||||
|
||||
type listMyFilesResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Items []model.Upload `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Items []models.Upload `json:"items"`
|
||||
}
|
||||
|
||||
// ListMyFiles 获取当前用户上传的文件列表
|
||||
@@ -171,7 +168,7 @@ type listMyFilesResponse struct {
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Router /api/v1/upload/my [get]
|
||||
func ListMyFiles(c *gin.Context) {
|
||||
currUser, _ := auth.GetFromContext[*model.User](c, auth.UserObjKey)
|
||||
currUser, _ := auth.GetFromContext[*contracts.UserDTO](c, auth.UserObjKey)
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req listMyFilesRequest
|
||||
@@ -218,7 +215,7 @@ func ListMyFiles(c *gin.Context) {
|
||||
// @Failure 404 {object} response.Any "文件不存在"
|
||||
// @Router /api/v1/upload/{id} [delete]
|
||||
func DeleteMyFile(c *gin.Context) {
|
||||
currUser, _ := auth.GetFromContext[*model.User](c, auth.UserObjKey)
|
||||
currUser, _ := auth.GetFromContext[*contracts.UserDTO](c, auth.UserObjKey)
|
||||
ctx := c.Request.Context()
|
||||
if uploadstorage.ReadOnly(ctx) {
|
||||
response.AbortConflict(c, shared.ErrStorageReadOnly)
|
||||
@@ -260,12 +257,12 @@ type updateMyFileRequest struct {
|
||||
// @Param id path string true "文件 ID"
|
||||
// @Param request body updateMyFileRequest true "更新字段"
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} response.Any{data=model.Upload} "更新成功"
|
||||
// @Success 200 {object} response.Any{data=models.Upload} "更新成功"
|
||||
// @Failure 403 {object} response.Any "无权操作"
|
||||
// @Failure 404 {object} response.Any "文件不存在"
|
||||
// @Router /api/v1/upload/{id} [put]
|
||||
func UpdateMyFile(c *gin.Context) {
|
||||
currUser, _ := auth.GetFromContext[*model.User](c, auth.UserObjKey)
|
||||
currUser, _ := auth.GetFromContext[*contracts.UserDTO](c, auth.UserObjKey)
|
||||
ctx := c.Request.Context()
|
||||
if uploadstorage.ReadOnly(ctx) {
|
||||
response.AbortConflict(c, shared.ErrStorageReadOnly)
|
||||
@@ -284,7 +281,7 @@ func UpdateMyFile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
upload, err := updateOwnedUpload(ctx, currUser.ID, uploadID, updateMyUploadInput(req))
|
||||
updated, err := updateOwnedUpload(ctx, currUser.ID, uploadID, updateMyUploadInput(req))
|
||||
if err != nil {
|
||||
if isRecordNotFound(err) {
|
||||
response.AbortNotFound(c, "文件记录未找到")
|
||||
@@ -294,9 +291,9 @@ func UpdateMyFile(c *gin.Context) {
|
||||
response.AbortForbidden(c, "无权操作")
|
||||
return
|
||||
}
|
||||
response.AbortBadRequest(c, "更新文件记录失败")
|
||||
response.AbortBadRequest(c, shared.ErrUpdateFileFailed)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.OK(upload))
|
||||
c.JSON(http.StatusOK, response.OK(updated))
|
||||
}
|
||||
|
||||
@@ -9,19 +9,21 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/Rain-kl/Wavelet/pkg/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/models"
|
||||
)
|
||||
|
||||
func TestGetDistinctUploadTypes(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
user := model.User{ID: 2222, Username: "test_user_2"}
|
||||
dbConn.Create(&user)
|
||||
user := contracts.UserDTO{ID: 2222, Username: "test_user_2"}
|
||||
dbConn.Table("w_users").Create(&user)
|
||||
|
||||
customUpload := model.Upload{
|
||||
customUpload := models.Upload{
|
||||
ID: 9001,
|
||||
UserID: user.ID,
|
||||
FileName: "custom.txt",
|
||||
@@ -30,7 +32,7 @@ func TestGetDistinctUploadTypes(t *testing.T) {
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Type: "custom_type_xyz",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
}
|
||||
dbConn.Create(&customUpload)
|
||||
|
||||
|
||||
@@ -8,26 +8,27 @@ import (
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/ingest"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/ingest"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/models"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/repository"
|
||||
)
|
||||
|
||||
func listUploadFiles(ctx context.Context, filter repository.UploadListFilter) (int64, []model.Upload, error) {
|
||||
func listUploadFiles(ctx context.Context, filter repository.UploadListFilter) (int64, []models.Upload, error) {
|
||||
return repository.ListUploads(ctx, filter)
|
||||
}
|
||||
|
||||
func listMyUploadFiles(ctx context.Context, userID uint64, filter repository.UploadListFilter) (int64, []model.Upload, error) {
|
||||
func listMyUploadFiles(ctx context.Context, userID uint64, filter repository.UploadListFilter) (int64, []models.Upload, error) {
|
||||
filter.UserID = userID
|
||||
return repository.ListUploads(ctx, filter)
|
||||
}
|
||||
|
||||
func softDeleteUpload(ctx context.Context, uploadID uint64) (model.Upload, error) {
|
||||
func softDeleteUpload(ctx context.Context, uploadID uint64) (models.Upload, error) {
|
||||
return ingest.Remove(ctx, uploadID)
|
||||
}
|
||||
|
||||
func softDeleteOwnedUpload(ctx context.Context, userID, uploadID uint64) (model.Upload, error) {
|
||||
func softDeleteOwnedUpload(ctx context.Context, userID, uploadID uint64) (models.Upload, error) {
|
||||
return ingest.RemoveOwned(ctx, userID, uploadID)
|
||||
}
|
||||
|
||||
@@ -45,13 +46,13 @@ type updateMyUploadInput struct {
|
||||
AccessMode *int
|
||||
}
|
||||
|
||||
func updateOwnedUpload(ctx context.Context, userID, uploadID uint64, input updateMyUploadInput) (model.Upload, error) {
|
||||
upload, err := repository.GetActiveUploadByID(ctx, uploadID)
|
||||
func updateOwnedUpload(ctx context.Context, userID, uploadID uint64, input updateMyUploadInput) (models.Upload, error) {
|
||||
u, err := repository.GetActiveUploadByID(ctx, uploadID)
|
||||
if err != nil {
|
||||
return model.Upload{}, err
|
||||
return models.Upload{}, err
|
||||
}
|
||||
if upload.UserID != userID {
|
||||
return model.Upload{}, ingest.ErrForbidden
|
||||
if u.UserID != userID {
|
||||
return models.Upload{}, ingest.ErrForbidden
|
||||
}
|
||||
|
||||
updates := make(map[string]any)
|
||||
@@ -61,23 +62,23 @@ func updateOwnedUpload(ctx context.Context, userID, uploadID uint64, input updat
|
||||
if input.AccessMode != nil {
|
||||
updates["access_mode"] = *input.AccessMode
|
||||
}
|
||||
if err := repository.UpdateUpload(ctx, &upload, updates); err != nil {
|
||||
return model.Upload{}, err
|
||||
if err := repository.UpdateUpload(ctx, &u, updates); err != nil {
|
||||
return models.Upload{}, err
|
||||
}
|
||||
if name, ok := updates["file_name"].(string); ok {
|
||||
upload.FileName = name
|
||||
u.FileName = name
|
||||
}
|
||||
if mode, ok := updates["access_mode"].(int); ok {
|
||||
upload.AccessMode = mode
|
||||
u.AccessMode = mode
|
||||
}
|
||||
return upload, nil
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func listUploadsForBatchDownload(ctx context.Context, ids []uint64) ([]model.Upload, error) {
|
||||
func listUploadsForBatchDownload(ctx context.Context, ids []uint64) ([]models.Upload, error) {
|
||||
return repository.ListUploadsByIDs(ctx, ids)
|
||||
}
|
||||
|
||||
func loadUploadStats(ctx context.Context) ([]model.UploadStat, error) {
|
||||
func loadUploadStats(ctx context.Context) ([]models.UploadStat, error) {
|
||||
return repository.ListUploadStats(ctx)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,14 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
appshared "github.com/Rain-kl/Wavelet/internal/shared"
|
||||
"github.com/Rain-kl/Wavelet/internal/shared/response"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"github.com/Rain-kl/Wavelet/pkg/response"
|
||||
appshared "github.com/Rain-kl/Wavelet/pkg/shared"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/filesrv"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/ingest"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/models"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/shared"
|
||||
uploadstorage "github.com/Rain-kl/Wavelet/plugins/domain/upload/storage"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/util"
|
||||
@@ -50,7 +51,7 @@ type batchDownloadRequest struct {
|
||||
// @Param type formData string false "业务分类 (例如: avatar, attachment, doc,默认为 generic)"
|
||||
// @Param metadata formData string false "额外的 JSON 格式元数据"
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} response.Any{data=model.Upload} "上传成功"
|
||||
// @Success 200 {object} response.Any{data=models.Upload} "上传成功"
|
||||
// @Failure 400 {object} response.Any "请求参数错误或文件受限"
|
||||
// @Failure 401 {object} response.Any "未登录"
|
||||
// @Failure 500 {object} response.Any "内部错误"
|
||||
@@ -63,7 +64,7 @@ func UploadFile(c *gin.Context) {
|
||||
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, shared.MaxUploadSize)
|
||||
|
||||
currUser, _ := auth.GetFromContext[*model.User](c, auth.UserObjKey)
|
||||
currUser, _ := auth.GetFromContext[*contracts.UserDTO](c, auth.UserObjKey)
|
||||
ctx := c.Request.Context()
|
||||
|
||||
header, err := c.FormFile("file")
|
||||
@@ -310,8 +311,8 @@ func resolveUploadAccessMode(c *gin.Context, uploadType string) (int, string) {
|
||||
return accessMode, ""
|
||||
}
|
||||
|
||||
func parseUploadMetadata(c *gin.Context, mimeType string) (model.UploadMetadata, string) {
|
||||
var meta model.UploadMetadata
|
||||
func parseUploadMetadata(c *gin.Context, mimeType string) (models.UploadMetadata, string) {
|
||||
var meta models.UploadMetadata
|
||||
metadataStr := c.DefaultPostForm("metadata", "")
|
||||
if metadataStr != "" {
|
||||
if err := json.Unmarshal([]byte(metadataStr), &meta); err != nil {
|
||||
|
||||
@@ -19,15 +19,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/infra/objectstore"
|
||||
"github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/internal/shared/response"
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/Rain-kl/Wavelet/pkg/response"
|
||||
"github.com/Rain-kl/Wavelet/pkg/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/models"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/shared"
|
||||
uploadstats "github.com/Rain-kl/Wavelet/plugins/domain/upload/stats"
|
||||
"github.com/Rain-kl/Wavelet/plugins/infra/storage/objectstore"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -36,7 +35,7 @@ type testResponse struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
func setupTestRouter(authUser *contracts.UserDTO) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(response.ErrorHandlerMiddleware())
|
||||
@@ -106,7 +105,7 @@ func TestUploadFile(t *testing.T) {
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }() // Clean up local files created during tests
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
authUser := &contracts.UserDTO{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
// Mock Storage Client
|
||||
@@ -175,12 +174,12 @@ func TestUploadFile(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify database record
|
||||
var uploadRecord model.Upload
|
||||
var uploadRecord models.Upload
|
||||
if err := json.Unmarshal(resp.Data, &uploadRecord); err != nil {
|
||||
t.Fatalf("failed to unmarshal upload record: %v", err)
|
||||
}
|
||||
|
||||
var dbRecord model.Upload
|
||||
var dbRecord models.Upload
|
||||
if err := dbConn.First(&dbRecord, uploadRecord.ID).Error; err != nil {
|
||||
t.Fatalf("failed to retrieve database record: %v", err)
|
||||
}
|
||||
@@ -258,7 +257,7 @@ func TestUploadFile(t *testing.T) {
|
||||
t.Fatalf("second upload was unsuccessful: %s", resp2.ErrorMsg)
|
||||
}
|
||||
|
||||
var uploadRecord2 model.Upload
|
||||
var uploadRecord2 models.Upload
|
||||
if err := json.Unmarshal(resp2.Data, &uploadRecord2); err != nil {
|
||||
t.Fatalf("failed to unmarshal second upload record: %v", err)
|
||||
}
|
||||
@@ -269,7 +268,7 @@ func TestUploadFile(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check if database contains both records sharing the same FilePath
|
||||
var records []model.Upload
|
||||
var records []models.Upload
|
||||
dbConn.Where("hash = ?", uploadRecord2.Hash).Find(&records)
|
||||
if len(records) != 2 {
|
||||
t.Errorf("expected 2 database records sharing the same hash, got %d", len(records))
|
||||
@@ -289,12 +288,7 @@ func TestUploadFile(t *testing.T) {
|
||||
objectstore.IsEnabledFunc = func() bool { return false }
|
||||
|
||||
// Seed allowed extensions configuration to allow txt files
|
||||
var sc model.SystemConfig
|
||||
dbConn.Where("key = ?", model.ConfigKeyUploadAllowedExtensions).First(&sc)
|
||||
sc.Value = "jpg,png,webp,txt"
|
||||
dbConn.Save(&sc)
|
||||
_ = db.HSetJSON(context.Background(), repository.SystemConfigRedisHashKey, sc.Key, &sc)
|
||||
repository.ResetSystemConfigRAMCacheForTest()
|
||||
dbConn.Table("w_system_configs").Where("key = ?", "upload_allowed_extensions").Update("value", "jpg,png,webp,txt")
|
||||
|
||||
contentType, body := createMultipartRequest(t, "file", "doc.txt", []byte("hello world generic document file"), map[string]string{
|
||||
"type": "document",
|
||||
@@ -316,7 +310,7 @@ func TestUploadFile(t *testing.T) {
|
||||
t.Fatalf("local upload failed: %s", resp.ErrorMsg)
|
||||
}
|
||||
|
||||
var localRecord model.Upload
|
||||
var localRecord models.Upload
|
||||
if err := json.Unmarshal(resp.Data, &localRecord); err != nil {
|
||||
t.Fatalf("failed to unmarshal local upload record: %v", err)
|
||||
}
|
||||
@@ -338,11 +332,11 @@ func TestDownloadFile(t *testing.T) {
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
authUser := &contracts.UserDTO{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
// Seed upload records in DB
|
||||
localUpload := model.Upload{
|
||||
localUpload := models.Upload{
|
||||
ID: 2001,
|
||||
UserID: 1001,
|
||||
FileName: "中文文件名.txt",
|
||||
@@ -350,7 +344,7 @@ func TestDownloadFile(t *testing.T) {
|
||||
FileSize: 12,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
}
|
||||
|
||||
// Create local file
|
||||
@@ -405,10 +399,10 @@ func TestListFiles(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
authUser := &contracts.UserDTO{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
uploads := []model.Upload{
|
||||
uploads := []models.Upload{
|
||||
{
|
||||
ID: 2101,
|
||||
UserID: authUser.ID,
|
||||
@@ -417,7 +411,7 @@ func TestListFiles(t *testing.T) {
|
||||
FileSize: 10,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 2102,
|
||||
@@ -427,7 +421,7 @@ func TestListFiles(t *testing.T) {
|
||||
FileSize: 20,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 2103,
|
||||
@@ -437,7 +431,7 @@ func TestListFiles(t *testing.T) {
|
||||
FileSize: 30,
|
||||
MimeType: "text/markdown",
|
||||
Extension: "md",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 2104,
|
||||
@@ -447,7 +441,7 @@ func TestListFiles(t *testing.T) {
|
||||
FileSize: 40,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
},
|
||||
}
|
||||
for i := range uploads {
|
||||
@@ -546,7 +540,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
authUser := &contracts.UserDTO{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
// Create and write files locally
|
||||
@@ -560,7 +554,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
_ = os.WriteFile("uploads/f3.txt", []byte("duplicate name file content"), 0644)
|
||||
|
||||
// Seed upload records. Note f2 and f3 have the same FileName "file_a.txt" to trigger name collision resolution.
|
||||
uploads := []model.Upload{
|
||||
uploads := []models.Upload{
|
||||
{
|
||||
ID: 3001,
|
||||
UserID: 1001,
|
||||
@@ -569,7 +563,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 3002,
|
||||
@@ -579,7 +573,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
},
|
||||
{
|
||||
ID: 3003,
|
||||
@@ -589,7 +583,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
FileSize: 28,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -658,8 +652,8 @@ func TestUploadAccessModeAccessControl(t *testing.T) {
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
user1 := &model.User{ID: 1001, Username: "user1"}
|
||||
user2 := &model.User{ID: 1002, Username: "user2"}
|
||||
user1 := &contracts.UserDTO{ID: 1001, Username: "user1"}
|
||||
user2 := &contracts.UserDTO{ID: 1002, Username: "user2"}
|
||||
|
||||
// Seed user1
|
||||
if err := dbConn.Create(user1).Error; err != nil {
|
||||
@@ -689,7 +683,7 @@ func TestUploadAccessModeAccessControl(t *testing.T) {
|
||||
t.Logf("Raw upload response: %s", w.Body.String())
|
||||
var resp1 testResponse
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp1)
|
||||
var upload1 model.Upload
|
||||
var upload1 models.Upload
|
||||
_ = json.Unmarshal(resp1.Data, &upload1)
|
||||
|
||||
if upload1.AccessMode != 0 {
|
||||
@@ -707,7 +701,7 @@ func TestUploadAccessModeAccessControl(t *testing.T) {
|
||||
router.ServeHTTP(w2, req2)
|
||||
var resp2 testResponse
|
||||
_ = json.Unmarshal(w2.Body.Bytes(), &resp2)
|
||||
var upload2 model.Upload
|
||||
var upload2 models.Upload
|
||||
_ = json.Unmarshal(resp2.Data, &upload2)
|
||||
|
||||
if upload2.AccessMode != 1 {
|
||||
@@ -744,11 +738,11 @@ func TestGetFileStats(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
authUser := &contracts.UserDTO{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
// Insert some dummy uploads
|
||||
uploads := []model.Upload{
|
||||
uploads := []models.Upload{
|
||||
{
|
||||
ID: 3101,
|
||||
UserID: authUser.ID,
|
||||
@@ -758,7 +752,7 @@ func TestGetFileStats(t *testing.T) {
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Type: "generic",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
@@ -770,7 +764,7 @@ func TestGetFileStats(t *testing.T) {
|
||||
MimeType: "video/mp4",
|
||||
Extension: "mp4",
|
||||
Type: "generic",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
CreatedAt: time.Now().AddDate(0, 0, -2), // 2 days ago
|
||||
},
|
||||
{
|
||||
@@ -782,7 +776,7 @@ func TestGetFileStats(t *testing.T) {
|
||||
MimeType: "application/pdf",
|
||||
Extension: "pdf",
|
||||
Type: "avatar", // different type
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
CreatedAt: time.Now().AddDate(0, 0, -10), // older than 7 days
|
||||
},
|
||||
}
|
||||
@@ -854,8 +848,8 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
user1 := &model.User{ID: 1001, Username: "user1"}
|
||||
user2 := &model.User{ID: 1002, Username: "user2"}
|
||||
user1 := &contracts.UserDTO{ID: 1001, Username: "user1"}
|
||||
user2 := &contracts.UserDTO{ID: 1002, Username: "user2"}
|
||||
|
||||
_ = dbConn.Create(user1)
|
||||
_ = dbConn.Create(user2)
|
||||
@@ -864,7 +858,7 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
router2 := setupTestRouter(user2)
|
||||
|
||||
// Seed upload records
|
||||
upload1 := model.Upload{
|
||||
upload1 := models.Upload{
|
||||
ID: 4001,
|
||||
UserID: 1001,
|
||||
FileName: "user1-file.txt",
|
||||
@@ -872,10 +866,10 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
FileSize: 100,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
upload2 := model.Upload{
|
||||
upload2 := models.Upload{
|
||||
ID: 4002,
|
||||
UserID: 1002,
|
||||
FileName: "user2-file.png",
|
||||
@@ -883,7 +877,7 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
FileSize: 200,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
Status: model.UploadStatusUsed,
|
||||
Status: models.UploadStatusUsed,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
@@ -927,7 +921,7 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
t.Fatalf("expected status 200, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var updated model.Upload
|
||||
var updated models.Upload
|
||||
dbConn.First(&updated, 4001)
|
||||
if updated.FileName != "renamed.txt" {
|
||||
t.Errorf("expected file name renamed.txt, got %s", updated.FileName)
|
||||
@@ -970,9 +964,9 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var deleted model.Upload
|
||||
var deleted models.Upload
|
||||
dbConn.First(&deleted, 4001)
|
||||
if deleted.Status != model.UploadStatusDeleted {
|
||||
if deleted.Status != models.UploadStatusDeleted {
|
||||
t.Errorf("expected status deleted, got %s", deleted.Status)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/shared/response"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/shared"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/pkg/response"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/upload/shared"
|
||||
)
|
||||
|
||||
type trendItem struct {
|
||||
@@ -79,22 +79,22 @@ func GetFileStats(c *gin.Context) {
|
||||
|
||||
for _, stat := range stats {
|
||||
switch stat.Dimension {
|
||||
case model.UploadStatDimensionTotal:
|
||||
case shared.UploadStatDimensionTotal:
|
||||
totalCount = stat.FileCount
|
||||
totalSize = stat.FileSize
|
||||
case model.UploadStatDimensionType:
|
||||
case shared.UploadStatDimensionType:
|
||||
types = append(types, distributionItem{
|
||||
Name: stat.StatKey,
|
||||
Count: stat.FileCount,
|
||||
Size: stat.FileSize,
|
||||
})
|
||||
case model.UploadStatDimensionCategory:
|
||||
case shared.UploadStatDimensionCategory:
|
||||
if item, ok := categoryMap[stat.StatKey]; ok {
|
||||
item.Count = stat.FileCount
|
||||
item.Size = stat.FileSize
|
||||
categoryMap[stat.StatKey] = item
|
||||
}
|
||||
case model.UploadStatDimensionTrend:
|
||||
case shared.UploadStatDimensionTrend:
|
||||
if _, ok := trendCountMap[stat.StatKey]; ok {
|
||||
trendCountMap[stat.StatKey] = stat.FileCount
|
||||
trendSizeMap[stat.StatKey] = stat.FileSize
|
||||
|
||||
Reference in New Issue
Block a user