package store import ( "context" "database/sql" "errors" "os" "path/filepath" "time" "g.fc.run/ai/byre/internal/api" "g.fc.run/ai/byre/internal/ids" ) const MaxBlobBytes = 5 << 20 var ImageTypes = map[string]bool{ "image/png": true, "image/jpeg": true, "image/gif": true, "image/webp": true, } func (s *Store) CreateBlob(ctx context.Context, contentType string, data []byte, actor string) (api.Blob, error) { b := api.Blob{ Id: ids.New(ids.Blob), ContentType: contentType, CreatedBy: actor, CreatedAt: time.Now().UTC(), } path := s.blobPath(b.Id) err := os.MkdirAll(filepath.Dir(path), 0o755) if err != nil { return b, err } err = os.WriteFile(path, data, 0o644) if err != nil { return b, err } _, err = s.db.ExecContext(ctx, `INSERT INTO blobs (id, content_type, created_by, created_at) VALUES (?, ?, ?, ?)`, b.Id, b.ContentType, b.CreatedBy, formatTime(b.CreatedAt)) return b, err } func (s *Store) GetBlob(ctx context.Context, id string) (api.Blob, string, error) { b := api.Blob{} createdAt := "" err := s.db.QueryRowContext(ctx, `SELECT id, content_type, created_by, created_at FROM blobs WHERE id = ?`, id). Scan(&b.Id, &b.ContentType, &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, s.blobPath(b.Id), 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) blobPath(id string) string { return filepath.Join(filepath.Dir(s.path), "blobs", id) }