Remove the MinIO/S3 storage backend entirely. The filesystem backend is fully implemented, already used in production, and a migrate-storage tool exists for any remaining MinIO deployments to migrate beforehand. Changes: - Delete MinIO client implementation (internal/storage/storage.go) - Delete migrate-storage tool (cmd/migrate-storage, scripts/migrate-storage.sh) - Remove MinIO service, volumes, and env vars from all Docker Compose files - Simplify StorageConfig: remove Endpoint, AccessKey, SecretKey, Bucket, UseSSL, Region fields; add SILO_STORAGE_ROOT_DIR env override - Change all SQL COALESCE defaults from 'minio' to 'filesystem' - Add migration 020 to update column defaults to 'filesystem' - Remove minio-go/v7 dependency (go mod tidy) - Update all config examples, setup scripts, docs, and tests
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// ItemFile represents a file attachment on an item.
|
|
type ItemFile struct {
|
|
ID string
|
|
ItemID string
|
|
Filename string
|
|
ContentType string
|
|
Size int64
|
|
ObjectKey string
|
|
StorageBackend string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
// ItemFileRepository provides item_files database operations.
|
|
type ItemFileRepository struct {
|
|
db *DB
|
|
}
|
|
|
|
// NewItemFileRepository creates a new item file repository.
|
|
func NewItemFileRepository(db *DB) *ItemFileRepository {
|
|
return &ItemFileRepository{db: db}
|
|
}
|
|
|
|
// Create inserts a new item file record.
|
|
func (r *ItemFileRepository) Create(ctx context.Context, f *ItemFile) error {
|
|
if f.StorageBackend == "" {
|
|
f.StorageBackend = "filesystem"
|
|
}
|
|
err := r.db.pool.QueryRow(ctx,
|
|
`INSERT INTO item_files (item_id, filename, content_type, size, object_key, storage_backend)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING id, created_at`,
|
|
f.ItemID, f.Filename, f.ContentType, f.Size, f.ObjectKey, f.StorageBackend,
|
|
).Scan(&f.ID, &f.CreatedAt)
|
|
if err != nil {
|
|
return fmt.Errorf("creating item file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListByItem returns all file attachments for an item.
|
|
func (r *ItemFileRepository) ListByItem(ctx context.Context, itemID string) ([]*ItemFile, error) {
|
|
rows, err := r.db.pool.Query(ctx,
|
|
`SELECT id, item_id, filename, content_type, size, object_key,
|
|
COALESCE(storage_backend, 'filesystem'), created_at
|
|
FROM item_files WHERE item_id = $1 ORDER BY created_at`,
|
|
itemID,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listing item files: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var files []*ItemFile
|
|
for rows.Next() {
|
|
f := &ItemFile{}
|
|
if err := rows.Scan(&f.ID, &f.ItemID, &f.Filename, &f.ContentType, &f.Size, &f.ObjectKey, &f.StorageBackend, &f.CreatedAt); err != nil {
|
|
return nil, fmt.Errorf("scanning item file: %w", err)
|
|
}
|
|
files = append(files, f)
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
// Get returns a single item file by ID.
|
|
func (r *ItemFileRepository) Get(ctx context.Context, id string) (*ItemFile, error) {
|
|
f := &ItemFile{}
|
|
err := r.db.pool.QueryRow(ctx,
|
|
`SELECT id, item_id, filename, content_type, size, object_key,
|
|
COALESCE(storage_backend, 'filesystem'), created_at
|
|
FROM item_files WHERE id = $1`,
|
|
id,
|
|
).Scan(&f.ID, &f.ItemID, &f.Filename, &f.ContentType, &f.Size, &f.ObjectKey, &f.StorageBackend, &f.CreatedAt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting item file: %w", err)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// Delete removes an item file record.
|
|
func (r *ItemFileRepository) Delete(ctx context.Context, id string) error {
|
|
tag, err := r.db.pool.Exec(ctx, `DELETE FROM item_files WHERE id = $1`, id)
|
|
if err != nil {
|
|
return fmt.Errorf("deleting item file: %w", err)
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return fmt.Errorf("item file not found")
|
|
}
|
|
return nil
|
|
}
|