向宁 bcd637387a feat: add data layer with GORM models, S3 repo, PG repos
Replace old infrastructure layer with Kratos-style data layer:
- data.go: GORM connection, transaction support, Wire ProviderSet, PO models
- file_repo.go: All 12 S3 operations (upload, download, multipart, presign, buckets)
- folder_repo.go: GORM queries including recursive CTE for descendant files
- file_meta_repo.go: CRUD + move operations for file metadata
- share_repo.go: CRUD + increment download count for share links

Deleted old infrastructure/database, infrastructure/repository, infrastructure/s3.
Kept infrastructure/grpc for later integration.
2026-05-25 12:57:42 +08:00

121 lines
3.9 KiB
Go

package data
import (
"context"
"time"
"rag/file-system/internal/conf"
"github.com/go-kratos/kratos/v2/log"
"github.com/google/wire"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// Data holds the GORM database connection and provides transaction support.
type Data struct {
db *gorm.DB
log *log.Helper
}
// contextTxKey is the context key for storing the current transaction.
type contextTxKey struct{}
// NewData creates a new Data instance with GORM connected to PostgreSQL.
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
helper := log.NewHelper(logger)
db, err := gorm.Open(postgres.Open(c.Database.Source), &gorm.Config{})
if err != nil {
return nil, nil, err
}
sqlDB, err := db.DB()
if err != nil {
return nil, nil, err
}
sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(5)
if err := db.AutoMigrate(&FolderPO{}, &FileMetaPO{}, &ShareLinkPO{}); err != nil {
return nil, nil, err
}
helper.Info("connected to PostgreSQL via GORM")
cleanup := func() {
sqlDB.Close()
}
return &Data{db: db, log: helper}, cleanup, nil
}
// DB returns the *gorm.DB for the given context. If a transaction is active
// in the context, it returns the transaction DB; otherwise the global DB.
func (d *Data) DB(ctx context.Context) *gorm.DB {
tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
if ok {
return tx
}
return d.db.WithContext(ctx)
}
// Transaction is the interface for executing operations within a database transaction.
type Transaction interface {
InTx(ctx context.Context, fn func(ctx context.Context) error) error
}
// InTx executes fn inside a database transaction.
func (d *Data) InTx(ctx context.Context, fn func(ctx context.Context) error) error {
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
ctx = context.WithValue(ctx, contextTxKey{}, tx)
return fn(ctx)
})
}
// ProviderSet is the Wire provider set for the data layer.
var ProviderSet = wire.NewSet(NewData, NewFileRepo, NewFolderRepo, NewFileMetaRepo, NewShareRepo)
// --- GORM Models (Persistence Objects) ---
// FolderPO maps to the "folders" table.
type FolderPO struct {
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
ParentID *string `gorm:"type:uuid;index:idx_folders_parent"`
Name string `gorm:"type:varchar(255);not null"`
OwnerID string `gorm:"type:varchar(36);not null;index:idx_folders_owner"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (FolderPO) TableName() string { return "folders" }
// FileMetaPO maps to the "files" table.
type FileMetaPO struct {
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
FolderID string `gorm:"type:uuid;index:idx_files_folder"`
Name string `gorm:"type:varchar(255);not null"`
S3Key string `gorm:"type:varchar(512);not null;index:idx_files_s3_key"`
S3Bucket string `gorm:"type:varchar(255);not null"`
Size int64 `gorm:"default:0"`
ContentType string `gorm:"type:varchar(255);default:'application/octet-stream'"`
OwnerID string `gorm:"type:varchar(36);not null;index:idx_files_owner"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (FileMetaPO) TableName() string { return "files" }
// ShareLinkPO maps to the "share_links" table.
type ShareLinkPO struct {
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
ResourceType string `gorm:"type:varchar(10);not null"`
ResourceID string `gorm:"type:uuid;not null"`
Token string `gorm:"type:varchar(32);not null;uniqueIndex:idx_share_token"`
Password *string `gorm:"type:varchar(255)"`
ExpiresAt *time.Time `gorm:"type:timestamptz"`
DownloadCount int `gorm:"default:0"`
MaxDownloads *int
CreatedBy string `gorm:"type:varchar(36);not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
}
func (ShareLinkPO) TableName() string { return "share_links" }