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" }