mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
1b2e083aec
Introduce internal/repository for data access and cache-backed system config reads. Move business logic into logics.go across admin push, user, template, cache, system_config, and upload/handler packages. Remove Gin from internal/util by relocating request-scoped helpers to oauth/gin_context.go. Propagate request context for config lookups in user flows. Slim model entities and delete model-level DB/cache helpers. Wire handlers to logics/repository so targeted packages no longer call db.DB directly. Update admin router tests to use ErrorHandlerMiddleware.
199 lines
6.0 KiB
Go
199 lines
6.0 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/upload/shared"
|
|
uploadstats "github.com/Rain-kl/Wavelet/internal/apps/upload/stats"
|
|
uploadstorage "github.com/Rain-kl/Wavelet/internal/apps/upload/storage"
|
|
"github.com/Rain-kl/Wavelet/internal/db/idgen"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
|
"github.com/Rain-kl/Wavelet/internal/storage"
|
|
"github.com/Rain-kl/Wavelet/pkg/logger"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func listUploadFiles(ctx context.Context, filter repository.UploadListFilter) (int64, []model.Upload, error) {
|
|
return repository.ListUploads(ctx, filter)
|
|
}
|
|
|
|
func listMyUploadFiles(ctx context.Context, userID uint64, filter repository.UploadListFilter) (int64, []model.Upload, error) {
|
|
filter.UserID = userID
|
|
return repository.ListUploads(ctx, filter)
|
|
}
|
|
|
|
func softDeleteUpload(ctx context.Context, uploadID uint64) (model.Upload, error) {
|
|
upload, err := repository.GetActiveUploadByID(ctx, uploadID)
|
|
if err != nil {
|
|
return model.Upload{}, err
|
|
}
|
|
if err := repository.SoftDeleteUpload(ctx, &upload); err != nil {
|
|
return model.Upload{}, err
|
|
}
|
|
uploadstats.RecordUploadStatsRemove(ctx, &upload)
|
|
return upload, nil
|
|
}
|
|
|
|
func softDeleteOwnedUpload(ctx context.Context, userID, uploadID uint64) (model.Upload, error) {
|
|
upload, err := repository.GetActiveUploadByID(ctx, uploadID)
|
|
if err != nil {
|
|
return model.Upload{}, err
|
|
}
|
|
if upload.UserID != userID {
|
|
return model.Upload{}, errUploadForbidden
|
|
}
|
|
if err := repository.SoftDeleteUpload(ctx, &upload); err != nil {
|
|
return model.Upload{}, err
|
|
}
|
|
uploadstats.RecordUploadStatsRemove(ctx, &upload)
|
|
return upload, nil
|
|
}
|
|
|
|
func listDistinctUploadTypes(ctx context.Context) ([]string, error) {
|
|
types, err := repository.ListDistinctUploadTypes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Strings(types)
|
|
return types, nil
|
|
}
|
|
|
|
type updateMyUploadInput struct {
|
|
FileName string
|
|
AccessMode *int
|
|
}
|
|
|
|
func updateOwnedUpload(ctx context.Context, userID, uploadID uint64, input updateMyUploadInput) (model.Upload, error) {
|
|
upload, err := repository.GetActiveUploadByID(ctx, uploadID)
|
|
if err != nil {
|
|
return model.Upload{}, err
|
|
}
|
|
if upload.UserID != userID {
|
|
return model.Upload{}, errUploadForbidden
|
|
}
|
|
|
|
updates := make(map[string]any)
|
|
if input.FileName != "" {
|
|
updates["file_name"] = input.FileName
|
|
}
|
|
if input.AccessMode != nil {
|
|
updates["access_mode"] = *input.AccessMode
|
|
}
|
|
if err := repository.UpdateUpload(ctx, &upload, updates); err != nil {
|
|
return model.Upload{}, err
|
|
}
|
|
if name, ok := updates["file_name"].(string); ok {
|
|
upload.FileName = name
|
|
}
|
|
if mode, ok := updates["access_mode"].(int); ok {
|
|
upload.AccessMode = mode
|
|
}
|
|
return upload, nil
|
|
}
|
|
|
|
func listUploadsForBatchDownload(ctx context.Context, ids []uint64) ([]model.Upload, error) {
|
|
return repository.ListUploadsByIDs(ctx, ids)
|
|
}
|
|
|
|
type instantUploadInput struct {
|
|
UserID uint64
|
|
FileHash string
|
|
Size int64
|
|
MimeType string
|
|
Extension string
|
|
OrigName string
|
|
UploadType string
|
|
AccessMode int
|
|
}
|
|
|
|
func createInstantUpload(ctx context.Context, existing model.Upload, input instantUploadInput) (model.Upload, error) {
|
|
newUpload := model.Upload{
|
|
ID: idgen.NextUint64ID(),
|
|
UserID: input.UserID,
|
|
FileName: input.OrigName,
|
|
FilePath: existing.FilePath,
|
|
FileSize: input.Size,
|
|
MimeType: input.MimeType,
|
|
Extension: input.Extension,
|
|
Hash: input.FileHash,
|
|
StorageDriver: existing.StorageDriver,
|
|
Type: input.UploadType,
|
|
Status: model.UploadStatusUsed,
|
|
AccessMode: input.AccessMode,
|
|
Metadata: existing.Metadata,
|
|
}
|
|
if err := repository.CreateUpload(ctx, &newUpload); err != nil {
|
|
return model.Upload{}, err
|
|
}
|
|
uploadstats.RecordUploadStatsAdd(ctx, &newUpload)
|
|
logger.InfoF(ctx, "文件触发秒传成功! ID: %d, Path: %s", newUpload.ID, existing.FilePath)
|
|
return newUpload, nil
|
|
}
|
|
|
|
func findReusableUpload(ctx context.Context, hash string, size int64) (model.Upload, error) {
|
|
return repository.FindReusableUploadByHash(ctx, hash, size)
|
|
}
|
|
|
|
func saveNewUploadRecord(ctx context.Context, upload *model.Upload, storageDriver, filePath string) error {
|
|
if err := repository.CreateUpload(ctx, upload); err != nil {
|
|
backend, backendErr := storage.ForDriver(ctx, storage.Driver(storageDriver))
|
|
if backendErr == nil {
|
|
if deleteErr := backend.Delete(ctx, filePath); deleteErr != nil {
|
|
logger.WarnF(ctx, "清理未写入数据库的上传对象失败: %v", deleteErr)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
uploadstats.RecordUploadStatsAdd(ctx, upload)
|
|
return nil
|
|
}
|
|
|
|
func loadUploadStats(ctx context.Context) ([]model.UploadStat, error) {
|
|
return repository.ListUploadStats(ctx)
|
|
}
|
|
|
|
var errUploadForbidden = errors.New("upload forbidden")
|
|
|
|
func storeUploadObject(ctx context.Context, subPath string, size int64, mimeType string, buf *bytes.Buffer, meta *model.UploadMetadata) (string, string, error) {
|
|
if uploadstorage.ReadOnly(ctx) {
|
|
return "", "", errors.New(shared.ErrStorageReadOnly)
|
|
}
|
|
driver, backend, err := storage.Active(ctx)
|
|
if err != nil {
|
|
logger.ErrorF(ctx, "初始化活动存储失败: %v", err)
|
|
return "", "", errors.New(shared.ErrSaveFileFailed)
|
|
}
|
|
result, err := backend.Put(ctx, subPath, bytes.NewReader(buf.Bytes()), size, mimeType)
|
|
if err != nil {
|
|
logger.ErrorF(ctx, "写入 %s 存储失败: %v", driver, err)
|
|
return "", "", errors.New(shared.ErrSaveFileFailed)
|
|
}
|
|
meta.Bucket = result.Bucket
|
|
return string(driver), result.Key, nil
|
|
}
|
|
|
|
func validateUploadAllowedExtension(ctx context.Context, ext string) string {
|
|
sc, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyUploadAllowedExtensions)
|
|
if err != nil || sc.Value == "" {
|
|
return ""
|
|
}
|
|
allowedExts := strings.Split(strings.ToLower(sc.Value), ",")
|
|
for _, allowedExt := range allowedExts {
|
|
if strings.TrimSpace(allowedExt) == ext {
|
|
return ""
|
|
}
|
|
}
|
|
return shared.ErrUnsupportedFormat
|
|
}
|
|
|
|
func isRecordNotFound(err error) bool {
|
|
return errors.Is(err, gorm.ErrRecordNotFound)
|
|
} |