向宁 3a18ca0579 feat: add directory structure and file sharing support
- PostgreSQL metadata overlay layer on top of existing S3 storage
- 3 new tables: folders, files, share_links
- Folder CRUD: create, get with children, tree, rename, delete (cascade)
- File operations: upload to folder, move between folders
- Share links: create with optional password/expiry/download limit, public access
- S3 compensation on PG write failure
- Existing 14 endpoints untouched
2026-05-20 20:26:19 +08:00

84 lines
2.5 KiB
Go

package database
import (
"database/sql"
"fmt"
_ "github.com/jackc/pgx/v5/stdlib"
"rag/file-system/internal/common"
)
func NewPostgresDB(databaseURL string) (*sql.DB, error) {
db, err := sql.Open("pgx", databaseURL)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("failed to ping database: %w", err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
common.Logger.Info("connected to PostgreSQL")
return db, nil
}
func RunMigrations(db *sql.DB) error {
migrations := []string{
`CREATE TABLE IF NOT EXISTS folders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
parent_id UUID REFERENCES folders(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
owner_id VARCHAR(36) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(parent_id, name, owner_id)
)`,
`CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_id)`,
`CREATE INDEX IF NOT EXISTS idx_folders_owner ON folders(owner_id)`,
`CREATE TABLE IF NOT EXISTS files (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
folder_id UUID REFERENCES folders(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
s3_key VARCHAR(512) NOT NULL,
s3_bucket VARCHAR(255) NOT NULL,
size BIGINT NOT NULL DEFAULT 0,
content_type VARCHAR(255) NOT NULL DEFAULT 'application/octet-stream',
owner_id VARCHAR(36) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`,
`CREATE INDEX IF NOT EXISTS idx_files_folder ON files(folder_id)`,
`CREATE INDEX IF NOT EXISTS idx_files_owner ON files(owner_id)`,
`CREATE INDEX IF NOT EXISTS idx_files_s3_key ON files(s3_key)`,
`CREATE TABLE IF NOT EXISTS share_links (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
resource_type VARCHAR(10) NOT NULL,
resource_id UUID NOT NULL,
token VARCHAR(32) NOT NULL UNIQUE,
password VARCHAR(255),
expires_at TIMESTAMPTZ,
download_count INT NOT NULL DEFAULT 0,
max_downloads INT,
created_by VARCHAR(36) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`,
`CREATE INDEX IF NOT EXISTS idx_share_token ON share_links(token)`,
`CREATE INDEX IF NOT EXISTS idx_share_resource ON share_links(resource_type, resource_id)`,
}
for _, m := range migrations {
if _, err := db.Exec(m); err != nil {
return fmt.Errorf("migration failed: %w\nSQL: %s", err, m)
}
}
common.Logger.Info("database migrations completed")
return nil
}