2026-10-14 20:07:26 -07:00
|
|
|
package store
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"errors"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"g.fc.run/misc/byre/internal/api"
|
|
|
|
|
)
|
|
|
|
|
|
2026-10-15 19:02:33 -07:00
|
|
|
const MaxBlobBytes = 5 << 20
|
|
|
|
|
|
|
|
|
|
var ImageTypes = map[string]bool{
|
|
|
|
|
"image/png": true,
|
|
|
|
|
"image/jpeg": true,
|
|
|
|
|
"image/gif": true,
|
|
|
|
|
"image/webp": true,
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-14 20:07:26 -07:00
|
|
|
func (s *Store) CreateBlob(ctx context.Context, contentType string, data []byte, actor string) (api.Blob, error) {
|
|
|
|
|
b := api.Blob{
|
|
|
|
|
Id: NewID(),
|
|
|
|
|
ContentType: contentType,
|
|
|
|
|
CreatedBy: actor,
|
|
|
|
|
CreatedAt: time.Now().UTC(),
|
|
|
|
|
}
|
|
|
|
|
rel := filepath.Join("blobs", b.Id)
|
|
|
|
|
|
|
|
|
|
err := os.MkdirAll(filepath.Join(s.dataDir(), "blobs"), 0o755)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return b, err
|
|
|
|
|
}
|
|
|
|
|
err = os.WriteFile(filepath.Join(s.dataDir(), rel), data, 0o644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return b, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = s.db.ExecContext(ctx, `INSERT INTO blobs (id, content_type, path, created_by, created_at) VALUES (?, ?, ?, ?, ?)`,
|
|
|
|
|
b.Id, b.ContentType, rel, b.CreatedBy, formatTime(b.CreatedAt))
|
|
|
|
|
return b, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Store) GetBlob(ctx context.Context, id string) (api.Blob, string, error) {
|
|
|
|
|
b := api.Blob{}
|
|
|
|
|
rel := ""
|
|
|
|
|
createdAt := ""
|
|
|
|
|
err := s.db.QueryRowContext(ctx, `SELECT id, content_type, path, created_by, created_at FROM blobs WHERE id = ?`, id).
|
|
|
|
|
Scan(&b.Id, &b.ContentType, &rel, &b.CreatedBy, &createdAt)
|
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
|
return b, "", errors.Join(ErrNotFound, errors.New("no blob "+id))
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return b, "", err
|
|
|
|
|
}
|
|
|
|
|
b.CreatedAt, err = parseTime(createdAt)
|
|
|
|
|
return b, filepath.Join(s.dataDir(), rel), err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Store) ReadBlob(ctx context.Context, id string) (api.Blob, []byte, error) {
|
|
|
|
|
b, path, err := s.GetBlob(ctx, id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return b, nil, err
|
|
|
|
|
}
|
|
|
|
|
data, err := os.ReadFile(path)
|
|
|
|
|
return b, data, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Store) dataDir() string {
|
|
|
|
|
return filepath.Dir(s.path)
|
|
|
|
|
}
|