Derive blob file paths from their IDs
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
## Data
|
## Data
|
||||||
|
|
||||||
State lives in the directory given by `byre serve --data`, by default the `byre` directory under Go's `os.UserConfigDir()` (on macOS, `~/Library/Application Support/byre`): `byre.db`, `worktrees/<project-id>/<task-id>`, `worktrees/<project-id>/<task-id>-media` (the task's media folder, see "Screenshots from implementers"), `leads/<login>` (each lead agent's working directory, the login path-escaped), `hands/<session-id>/byre-hands` (each running implementer session's copy of `byre-hands`, see "Screen and app control"), `blobs/<blob-id>` (uploaded images; the `blobs` table maps each id to its content type and its path relative to this directory). Task branches are `byre/<task-id>` in the project's repo.
|
State lives in the directory given by `byre serve --data`, by default the `byre` directory under Go's `os.UserConfigDir()` (on macOS, `~/Library/Application Support/byre`): `byre.db`, `worktrees/<project-id>/<task-id>`, `worktrees/<project-id>/<task-id>-media` (the task's media folder, see "Screenshots from implementers"), `leads/<login>` (each lead agent's working directory, the login path-escaped), `hands/<session-id>/byre-hands` (each running implementer session's copy of `byre-hands`, see "Screen and app control"), `blobs/<blob-id>` (uploaded images; the `blobs` table stores image metadata; `Store.blobPath` derives the file path from the id, with no separate path column). Task branches are `byre/<task-id>` in the project's repo.
|
||||||
|
|
||||||
Schema changes are the files in `internal/store/migrations/`, embedded in the binary, each named `NNN-name.sql`: a three-digit number, then a short kebab-case name saying what it does. A data migration SQL can't express is a Go function in `goMigrations` (`internal/store/store.go`), keyed `NNN-name` the same way and ordered among the files; `071-workflow-steps` (`internal/store/migrateworkflows.go`) is one. On open, byre applies, in file name order, every file whose name (without the number) isn't in the `migrations` table, each in its own transaction that also records the name. The number only orders: a migration that needs another's tables or columns takes a higher number, and migrations with the same number must not depend on each other. Each runs with foreign keys off, so a migration can rebuild a table other tables reference, and `PRAGMA foreign_key_check` must come back empty before it commits. A migration's SQL is fixed once applied anywhere: write it out in full rather than sharing Go constants that later change. byre is deployed from task branches that each add their own migrations, so after a merge a migration can land before others the database has already applied, and it still runs. Give a new migration a name no other branch will use, and make any data migration safe to run twice. `sqlite3 byre.db 'SELECT * FROM migrations'` lists what has been applied.
|
Schema changes are the files in `internal/store/migrations/`, embedded in the binary, each named `NNN-name.sql`: a three-digit number, then a short kebab-case name saying what it does. A data migration SQL can't express is a Go function in `goMigrations` (`internal/store/store.go`), keyed `NNN-name` the same way and ordered among the files; `071-workflow-steps` (`internal/store/migrateworkflows.go`) is one. On open, byre applies, in file name order, every file whose name (without the number) isn't in the `migrations` table, each in its own transaction that also records the name. The number only orders: a migration that needs another's tables or columns takes a higher number, and migrations with the same number must not depend on each other. Each runs with foreign keys off, so a migration can rebuild a table other tables reference, and `PRAGMA foreign_key_check` must come back empty before it commits. A migration's SQL is fixed once applied anywhere: write it out in full rather than sharing Go constants that later change. byre is deployed from task branches that each add their own migrations, so after a merge a migration can land before others the database has already applied, and it still runs. Give a new migration a name no other branch will use, and make any data migration safe to run twice. `sqlite3 byre.db 'SELECT * FROM migrations'` lists what has been applied.
|
||||||
|
|
||||||
|
|||||||
+10
-11
@@ -27,28 +27,27 @@ func (s *Store) CreateBlob(ctx context.Context, contentType string, data []byte,
|
|||||||
CreatedBy: actor,
|
CreatedBy: actor,
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
rel := filepath.Join("blobs", b.Id)
|
path := s.blobPath(b.Id)
|
||||||
|
|
||||||
err := os.MkdirAll(filepath.Join(s.dataDir(), "blobs"), 0o755)
|
err := os.MkdirAll(filepath.Dir(path), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
err = os.WriteFile(filepath.Join(s.dataDir(), rel), data, 0o644)
|
err = os.WriteFile(path, data, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.db.ExecContext(ctx, `INSERT INTO blobs (id, content_type, path, created_by, created_at) VALUES (?, ?, ?, ?, ?)`,
|
_, err = s.db.ExecContext(ctx, `INSERT INTO blobs (id, content_type, created_by, created_at) VALUES (?, ?, ?, ?)`,
|
||||||
b.Id, b.ContentType, rel, b.CreatedBy, formatTime(b.CreatedAt))
|
b.Id, b.ContentType, b.CreatedBy, formatTime(b.CreatedAt))
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetBlob(ctx context.Context, id string) (api.Blob, string, error) {
|
func (s *Store) GetBlob(ctx context.Context, id string) (api.Blob, string, error) {
|
||||||
b := api.Blob{}
|
b := api.Blob{}
|
||||||
rel := ""
|
|
||||||
createdAt := ""
|
createdAt := ""
|
||||||
err := s.db.QueryRowContext(ctx, `SELECT id, content_type, path, created_by, created_at FROM blobs WHERE id = ?`, id).
|
err := s.db.QueryRowContext(ctx, `SELECT id, content_type, created_by, created_at FROM blobs WHERE id = ?`, id).
|
||||||
Scan(&b.Id, &b.ContentType, &rel, &b.CreatedBy, &createdAt)
|
Scan(&b.Id, &b.ContentType, &b.CreatedBy, &createdAt)
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return b, "", errors.Join(ErrNotFound, errors.New("no blob "+id))
|
return b, "", errors.Join(ErrNotFound, errors.New("no blob "+id))
|
||||||
}
|
}
|
||||||
@@ -56,7 +55,7 @@ func (s *Store) GetBlob(ctx context.Context, id string) (api.Blob, string, error
|
|||||||
return b, "", err
|
return b, "", err
|
||||||
}
|
}
|
||||||
b.CreatedAt, err = parseTime(createdAt)
|
b.CreatedAt, err = parseTime(createdAt)
|
||||||
return b, filepath.Join(s.dataDir(), rel), err
|
return b, s.blobPath(b.Id), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ReadBlob(ctx context.Context, id string) (api.Blob, []byte, error) {
|
func (s *Store) ReadBlob(ctx context.Context, id string) (api.Blob, []byte, error) {
|
||||||
@@ -68,6 +67,6 @@ func (s *Store) ReadBlob(ctx context.Context, id string) (api.Blob, []byte, erro
|
|||||||
return b, data, err
|
return b, data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) dataDir() string {
|
func (s *Store) blobPath(id string) string {
|
||||||
return filepath.Dir(s.path)
|
return filepath.Join(filepath.Dir(s.path), "blobs", id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE blobs DROP COLUMN path;
|
||||||
Reference in New Issue
Block a user