32 Commits
Author SHA1 Message Date
aneurinandClaude Sonnet 5 1964fda05e Add configurable Cache-Control headers for statics, files, and API
PR Checks / checks (pull_request) Successful in 1m40s
Build / build-and-push (push) Successful in 1m16s
Send our own Cache-Control headers instead of relying entirely on
front-proxy configuration, so browsers cache appropriately even when
Zampler is reached directly. --static-max-age, --file-max-age, and
--api-max-age (all cobra Duration flags, 0 disables caching) cover the
three response categories independently; --no-immutable turns off the
immutable directive on served files, which is on by default since a
file's content never changes for a given ID. The HTML shell always
gets no-cache regardless of --static-max-age, since it isn't
content-hashed like the rest of the Vite build and must keep
revalidating so a new deploy's asset references are picked up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 11:48:53 +01:00
aneurinandClaude Sonnet 5 60ecec609d Batch scan writes into transactions instead of one per file
Build / build-and-push (push) Successful in 6m18s
Previously each scanned file was its own autocommit transaction, so a
large library meant tens of thousands of individually journaled (and,
outside WAL mode, fsynced) commits back to back. Grouping them into
batches cuts that by ~2-3 orders of magnitude and shrinks the window
during which the scanner holds the write lock, part of the fix for #17.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 09:42:13 +00:00
aneurinandClaude Sonnet 5 e9542dfda5 Harden DSN construction against an already-parameterized --sqlite path
PR Checks / checks (pull_request) Successful in 1m55s
Build / build-and-push (push) Successful in 6m52s
Address review feedback on !18: appending "?..." unconditionally broke
if the --sqlite value already carried its own query string, since the
driver treats the first "?" as the path/query boundary. Join with "&"
when one is already present.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 10:33:19 +01:00
aneurinandClaude Sonnet 5 520bf1c186 Tune SQLite connection: WAL mode, busy_timeout, single writer
Build / build-and-push (push) Skipped
PR Checks / checks (pull_request) Successful in 9m14s
Fixes SQLITE_BUSY errors seen with a large library: the connection
previously used SQLite's default journal mode (which blocks readers
during a write) and a zero busy_timeout (any lock contention failed
immediately instead of retrying), while an uncapped connection pool
let multiple connections contend for the single writer lock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 10:27:43 +01:00
aneurinandClaude Sonnet 5 7a6e1aa6a8 fix middleware races, ReadFile error paths, and lib.Coalesce
PR Checks / checks (pull_request) Successful in 1m36s
Build / build-and-push (push) Successful in 1m13s
Closes #8, #9, #10; partially addresses #11.

middlewares (#8):
- Log: log once from the Middleware closure after the handler returns
  instead of spawning a goroutine per Write (data race on the shared
  LogWriter, multiple log lines per request). Bodiless responses
  (204/304/redirects) are now logged too, and the status-class switch has
  a default branch so a stray sub-100 status can't nil-deref the event.
- logWriter forwards http.Flusher / http.Hijacker / io.ReaderFrom so
  streaming handlers and the file server keep their fast paths.
- Both middlewares use the closure idiom rather than storing next on a
  shared instance, so they're safe to register on more than one chain.
- CorsOrigin now honours its argument: a comma-separated allow-list,
  with "*" meaning allow-any. Wired to a new --allow-origin flag that
  defaults to "*" (unchanged behaviour out of the box).

ReadFile (#9):
- Add the missing returns after GetFile / open failures (the old code
  fell through to os.ReadFile(nil path) and wrote a second body).
- Hand off to http.ServeContent for Content-Type, Range and
  If-Modified-Since, and set Content-Disposition: inline so the embedded
  audio player streams instead of downloading (the download button
  already uses the HTML download attribute).

lib.Coalesce (#10):
- Constrain to comparable and compare against a fresh zero value. The
  old IsZero type switch compared an any against untyped constants, so
  every narrow numeric type (int8..int64, uint*, float32) reported
  non-zero and never fell back. IsZero is removed (was unused elsewhere).

tests (#11, high-value/easy set):
- lib.Coalesce table across every numeric width, string, bool, pointer.
- database.Migrate against a temp sqlite: schema created, recorded once,
  second call is a no-op.
- types.NewFile: id = sha256(path), hash = sha256(contents), scan fields
  copied, and the missing-file path still yields an id.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 17:22:00 +01:00
Claude CodeandClaude Sonnet 5 800404203a replace archived code.aneur.in/go/* dependencies
PR Checks / checks (pull_request) Successful in 1m43s
Build / build-and-push (push) Successful in 1m8s
Everything under code.aneur.in/go/* except validate was archived. Swap
each for its replacement; validate stays.

- rest -> internal/rest: the JSON response/error helpers zampler actually
  uses (~110 lines), same {"message","data"} wire shape and status codes.
- sqan -> internal/scan: a filepath.WalkDir walk. zampler only filtered
  by extension; sqan's path semantics (absolute root, rooted RelativePath,
  "/" RelativeDir for root-level files) are kept.
- sqimple + sqaffold query building -> github.com/doug-martin/goqu/v9,
  which also does the row scanning that record + sqaffold provided. A
  fileRow struct with db tags maps to types.File (millis <-> time.Time).
- sqaffold.Migrate + version.Migration -> a ~40-line migrator in
  internal/database/migration.go tracking applied names in a
  schema_migrations table. goose was disproportionate for one migration.
- version -> gone (only its Migration helper was used).

Net dependency change: -6 code.aneur.in/go modules, +1 (goqu).

go build / go vet / go test ./... green; manual run verified migration,
scan+insert, list/get/search/sort/paging, 404 and 400 shapes, and
rescan idempotency (on conflict replace).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 17:00:45 +01:00
claudeandClaude Sonnet 5 55d14d3193 serve /robots.txt with opt-in search indexing
Build / build-and-push (push) Successful in 6m34s
Adds a --allow-indexing / -i flag (default false). The server now handles
GET /robots.txt itself instead of relying on a static file:

  - default: block every crawler (User-agent: * / Disallow: /)
  - --allow-indexing: permissive (Disallow: empty)

Generating it in the handler means the policy is set per host at run time.
Search indexing is opt-in, as requested on the issue.

Closes #5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 12:42:26 +00:00
claudeandClaude Sonnet 5 2a79eecbff cli: read the bind address from the right flag
PR Checks / checks (pull_request) Successful in 2m57s
Build / build-and-push (push) Successful in 6m4s
RunE looked up flags.GetString("host"), but the flag is registered as
"address" (-a), so --address was silently ignored and the server always
bound to all interfaces. Read "address" instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 13:36:58 +01:00
aneurin e39d068ebe cleanup errant log lines 2026-09-01 10:52:20 +01:00
aneurin 3d8d909663 frontend, download, search fixes 2026-09-01 10:45:18 +01:00
aneurin 2f279e9ea0 api fixes, improvements 2026-09-01 10:22:48 +01:00
aneurin 0e762ac675 add --no-scan 2026-09-01 09:24:59 +01:00
aneurin da0885db92 adopt sqimple from scaffold 2026-09-01 01:30:49 +01:00
aneurin 17ce3314f5 working db scanner 2026-09-01 01:26:54 +01:00
aneurin 06ae7f16ec replacing memory db with sqlite pt1 2026-08-31 22:58:18 +01:00
aneurin 84fc57f43f simplify file scanning, skip unwanted extensions 2026-08-31 19:05:46 +01:00
aneurin fe45546e00 fix json response structure bug 2026-08-31 17:02:01 +01:00
aneurin f621b01a45 improve output formatting 2026-08-31 13:10:17 +01:00
aneurin ca1a4deaea add api querying and pagination 2026-08-31 12:03:11 +01:00
aneurin 01d5653974 improve startup with deferred scan 2026-08-30 20:02:08 +01:00
aneurin e25737b097 minor fixes 2026-07-21 22:59:38 +01:00
aneurin d33987aef9 add finishing touches, dockerfile 2026-07-21 22:55:38 +01:00
aneurin 085d185593 improve downloading 2026-07-21 01:00:53 +01:00
aneurin 8980df82bc start adding samples grid to frontend 2026-07-20 09:37:05 +01:00
aneurin 8b37a74e0d add file streaming endpoints 2026-07-17 15:42:52 +01:00
aneurin 66413c1036 reorganise code into func areas pt2 2026-07-17 15:03:35 +01:00
aneurin 3ed4bc949f reorganise code into func areas 2026-07-17 14:59:55 +01:00
aneurin 5b625b56f5 add file by path api 2026-07-17 14:30:28 +01:00
aneurin 8b85456754 progress on api, logging 2026-07-16 09:18:02 +01:00
aneurin 5e3c028140 clean up go.mod, app 2026-07-16 08:47:56 +01:00
aneurin 1328098668 add files api 2026-07-16 00:45:42 +01:00
aneurin 9e9fc32efd initial commit 2026-07-16 00:33:50 +01:00