refactor(arch): decouple private imports, enforce contracts and comply with cordis architecture

This commit is contained in:
ryan
2026-09-03 10:40:02 +08:00
parent dbcf485d8a
commit 953a224245
107 changed files with 2028 additions and 1263 deletions
@@ -13,9 +13,7 @@ import (
"sync"
"Wavelet/core/contracts"
waveletupload "Wavelet/plugins/domain/upload"
"Wavelet/plugins/domain/upload/models"
"Wavelet/plugins/infra/database"
"Wavelet/openflare/plugins/server/kernel/model"
)
// ReservedPagesDeploymentType is managed exclusively by the Pages domain.
@@ -23,40 +21,77 @@ const ReservedPagesDeploymentType = "openflare_pages_deployment"
const (
// PolicyCreate always stores a new object and creates a new upload record.
PolicyCreate = waveletupload.PolicyCreate
PolicyCreate = 1
// PolicyDedupNewRecord reuses an existing object path on hash match but creates a new record.
PolicyDedupNewRecord = waveletupload.PolicyDedupNewRecord
PolicyDedupNewRecord = 2
// PolicyResolveExisting returns an existing upload on hash match.
PolicyResolveExisting = waveletupload.PolicyResolveExisting
PolicyResolveExisting = 3
)
type (
// IngestRequest is the programmatic upload ingest payload.
IngestRequest = waveletupload.IngestRequest
// IngestResult reports ingest side effects.
IngestResult = waveletupload.IngestResult
// IngestPolicy controls hash-collision behavior during ingest.
IngestPolicy = waveletupload.IngestPolicy
)
// IngestRequest is the programmatic upload ingest payload.
type IngestRequest struct {
UserID uint64
Type string
FileName string
MimeType string
Extension string
Size int64
Policy int
Hash string
Reader io.Reader
AccessMode *int
SkipExtensionCheck bool
Metadata model.UploadMetadata
}
// IngestResult reports ingest side effects.
type IngestResult struct {
Upload contracts.UploadDTO
Created bool
Stored bool
Resolved bool
}
// IngestPolicy controls hash-collision behavior during ingest.
type IngestPolicy = int
var (
storageMu sync.RWMutex
svcMu sync.RWMutex
storageSvc contracts.StorageService
uploadSvc contracts.UploadService
)
// SetStorage injects the platform StorageService used to open stored objects.
func SetStorage(s contracts.StorageService) {
storageMu.Lock()
defer storageMu.Unlock()
svcMu.Lock()
defer svcMu.Unlock()
storageSvc = s
}
func currentStorage() contracts.StorageService {
storageMu.RLock()
defer storageMu.RUnlock()
// SetUploadService injects the platform UploadService.
func SetUploadService(s contracts.UploadService) {
svcMu.Lock()
defer svcMu.Unlock()
uploadSvc = s
}
// CurrentStorage returns the currently registered storage service.
func CurrentStorage() contracts.StorageService {
svcMu.RLock()
defer svcMu.RUnlock()
return storageSvc
}
func currentStorage() contracts.StorageService {
return CurrentStorage()
}
func currentUpload() contracts.UploadService {
svcMu.RLock()
defer svcMu.RUnlock()
return uploadSvc
}
// IngestFromLocalPath ingests a local regular file through Wavelet upload ingest.
func IngestFromLocalPath(ctx context.Context, localPath string, req IngestRequest) (IngestResult, error) {
localPath = strings.TrimSpace(localPath)
@@ -79,55 +114,87 @@ func IngestFromLocalPath(ctx context.Context, localPath string, req IngestReques
if req.Size <= 0 {
req.Size = info.Size()
}
req.Reader = file
return waveletupload.Ingest(ctx, req)
storage := currentStorage()
if storage == nil {
return IngestResult{}, errors.New("storage service not available")
}
res, err := storage.Ingest(ctx, file, contracts.IngestOptions{
UserID: req.UserID,
Type: req.Type,
FileName: req.FileName,
MimeType: req.MimeType,
Extension: req.Extension,
Size: req.Size,
Policy: req.Policy,
Metadata: req.Metadata.Extra,
})
if err != nil {
return IngestResult{}, err
}
uploadRecord, err := GetActiveUpload(ctx, res.ID)
if err != nil {
return IngestResult{}, err
}
return IngestResult{
Upload: uploadRecord,
Created: res.Created,
Stored: res.Stored,
Resolved: res.Resolved,
}, nil
}
// GetActiveUpload loads an active (non-deleted) upload by ID.
func GetActiveUpload(ctx context.Context, id uint64) (models.Upload, error) {
conn := database.DB(ctx)
if conn == nil {
return models.Upload{}, errors.New("database not initialized")
func GetActiveUpload(ctx context.Context, id uint64) (contracts.UploadDTO, error) {
svc := currentUpload()
if svc == nil {
return contracts.UploadDTO{}, errors.New("upload service not available")
}
var upload models.Upload
err := conn.Where("id = ? AND status <> ?", id, models.UploadStatusDeleted).First(&upload).Error
return upload, err
u, err := svc.GetByID(ctx, id)
if err != nil {
return contracts.UploadDTO{}, err
}
if u == nil {
return contracts.UploadDTO{}, errors.New("upload not found")
}
return *u, nil
}
// OpenedUploadObject is a stored object stream plus the upload record.
type OpenedUploadObject struct {
Upload models.Upload
Upload contracts.UploadDTO
Body io.ReadCloser
ContentType string
ContentLength int64
}
// OpenStoredUpload opens the stored object for an active upload via StorageService.
// OpenStoredUpload opens the stored object for an active upload via UploadService.
func OpenStoredUpload(ctx context.Context, id uint64) (*OpenedUploadObject, error) {
upload, err := GetActiveUpload(ctx, id)
if err != nil {
return nil, err
}
svc := currentStorage()
svc := currentUpload()
if svc == nil {
return nil, errors.New("storage service not available")
return nil, errors.New("upload service not available")
}
obj, err := svc.Get(ctx, upload.FilePath)
obj, err := svc.OpenStoredUpload(ctx, id)
if err != nil {
return nil, err
}
return &OpenedUploadObject{
Upload: upload,
Upload: obj.Upload,
Body: obj.Body,
ContentType: obj.ContentType,
ContentLength: obj.ContentLength,
}, nil
}
// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob.
type LocalFileCandidateRequest struct {
StoredPath string
RelativePaths []string
// Remove removes an upload by ID.
func Remove(ctx context.Context, id uint64) error {
svc := currentUpload()
if svc == nil {
return errors.New("upload service not available")
}
return svc.Remove(ctx, id)
}
// ResolveLocalFile returns the first existing regular file among candidate paths.
@@ -147,7 +214,17 @@ func ResolveLocalFile(_ context.Context, req LocalFileCandidateRequest) (string,
return "", 0, os.ErrNotExist
}
// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob.
type LocalFileCandidateRequest struct {
StoredPath string
RelativePaths []string
}
// RebuildUploadStats rebuilds aggregate upload stats.
func RebuildUploadStats(ctx context.Context) error {
return waveletupload.RebuildUploadStats(ctx)
svc := currentUpload()
if svc == nil {
return errors.New("upload service not available")
}
return svc.RebuildStats(ctx)
}
@@ -1,40 +0,0 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package ofupload
import (
"context"
"Wavelet/plugins/domain/upload/cache"
"Wavelet/plugins/domain/upload/models"
uploadrepo "Wavelet/plugins/domain/upload/repository"
uploadstats "Wavelet/plugins/domain/upload/stats"
"gorm.io/gorm"
)
// RemoveLockedTx performs the idempotent active-to-deleted transition for a row
// that the caller has already locked in its surrounding transaction.
func RemoveLockedTx(tx *gorm.DB, upload *models.Upload) (bool, error) {
if upload == nil {
return false, nil
}
if upload.Status == models.UploadStatusDeleted {
return false, nil
}
snapshot := *upload
if err := uploadrepo.SoftDeleteUploadTx(tx, upload); err != nil {
return false, err
}
if err := uploadstats.ApplyUploadStatsDeltaTx(tx, &snapshot, -1); err != nil {
return false, err
}
upload.Status = models.UploadStatusDeleted
return true, nil
}
// InvalidateUploadMetaCache evicts cached upload metadata.
func InvalidateUploadMetaCache(ctx context.Context, id uint64) {
cache.EvictUploadMeta(ctx, id)
}