Commit Graph

14 Commits

Author SHA1 Message Date
H1K0 48e901cac1 feat(backend): per-file review status with DSL filter and bulk endpoint
Replaces the old "untagged" sentinel tag with a proper per-file workflow
status: needs_review starts true on upload/import and is cleared by an
explicit action (no auto-clear on tagging). Surfaced as a filter token
(r=1 needs review, r=0 done) so it combines with tag/MIME conditions, and
toggled via POST /files/bulk/review (single id or many, edit-ACL enforced,
audit-logged as file_review).

needs_review lives on data.files (column added to the original 003 migration,
partial index in 006, action type seeded in 007).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:16:47 +03:00
H1K0 4d11beb296 feat(backend): import server-folder files oldest-first by mtime
os.ReadDir returns entries in name order; sort them by ascending mtime
before importing so each Upload's created_at reflects the files'
chronological order. mtimes are cached once for the sort and reused as the
content_datetime fallback, dropping the redundant per-file Info() call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:16:24 +03:00
H1K0 73ae8a046f feat(backend): record tag usage in filters to activity.tag_uses
Listing files with a tag filter now logs each referenced tag to
activity.tag_uses, flagging it included (positive) or excluded (negated
under an odd number of NOTs); the untagged pseudo-token is skipped. The
filter AST is reused to determine polarity, so grouped negations like
!(A|B) mark both tags excluded.

Recording happens only when a filter is first applied — not on cursor
pagination or an anchored return — so one browse counts once. The write
is best-effort and never fails the listing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 21:40:13 +03:00
H1K0 129cc59793 feat(backend): stream folder-import progress as NDJSON
The import endpoint did all the work in one request and returned only an
aggregate summary, so the UI couldn't show progress or per-file status.

Refactor FileService.Import to take an optional progress callback and emit
a "start" event (with the total entry count), one "file" event per entry as
it finishes (index, filename, status, optional reason), and a final "done"
event with the tallies. The handler streams these as newline-delimited JSON
and flushes after each, deferring the response headers until the first event
so a validation error raised before any file is touched is still returned as
a normal JSON error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 21:15:40 +03:00
H1K0 52c62b5c8d feat(backend): extract rich numeric metadata via exiftool
The previous goexif reader only understood EXIF in JPEG/TIFF, so videos,
PNGs and any image without an EXIF block were stored with no metadata at
all. Shell out to exiftool instead (the same tool the prior version used),
which covers images, video and audio in one pass.

Run it with `-n` so every tag comes back as a raw numeric/machine value
(FileSize in bytes, Duration in seconds, AvgBitrate as a number) rather
than human-readable strings — the metadata is the basis for analytics, not
decoration. Temp-file artifacts (SourceFile/Directory/permissions/inode
dates) are stripped and FileName is set to the original.

content_datetime now resolves from the first real capture date in the
metadata (DateTimeOriginal, then the video CreateDate atoms), still falling
back to the import mtime. When exiftool isn't on PATH the pure-Go EXIF
reader remains as a graceful fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 20:55:23 +03:00
H1K0 1b04d67e20 feat(backend): drain import folder and keep mtime on server-side import
The directory import now removes each source file after it is safely
ingested, so the import folder drains and re-running doesn't create
duplicates (a removal failure is reported per-file but doesn't undo the
import). It also captures the source file's mtime and passes it as a new
ContentDatetimeFallback on Upload, used for content_datetime only when
the file has no EXIF date — so non-photo files keep a meaningful date
instead of the zero value once the source is gone. Adds an integration
test covering ingest, directory skip, source removal and the mtime
fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 18:19:59 +03:00
H1K0 a78fc5ba9a feat(backend): log file views
deploy / deploy (push) Successful in 1m0s
The activity.file_views table existed but nothing ever wrote to it. Add a
POST /files/{id}/views endpoint: FileRepo.RecordView inserts a history row,
FileService.RecordView enforces view ACL first. The file viewer fires it
(fire-and-forget) when a file is opened, including while paging prev/next.

Documented in openapi.yaml; covered by TestRecordFileView (204 on view,
repeatable, 404 for unknown file).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 14:49:14 +03:00
H1K0 0e7890a465 style(project): format Go with gofmt, set up Prettier for the frontend
Run gofmt -w across the backend, normalising the manually-aligned := blocks
to the gofmt standard. No code behaviour changes.

Add Prettier (+ prettier-plugin-svelte) to the frontend with the SvelteKit
default config (tabs, single quotes) so formatting is reproducible, then run
it over the whole tree. Add format / format:check npm scripts and a
.prettierignore (build output, generated schema.ts, static assets).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:01:29 +03:00
H1K0 89ba6bae82 fix(backend): enforce private-by-default visibility and pool-op ACL
Listings returned every row regardless of ownership: GET /files, /tags,
/pools and /categories exposed other users' private items (while the
single-item GET correctly returned 403), and the pool file operations
(GET /pools/:id, /pools/:id/files, add/remove/reorder) skipped ACL
entirely, so any authenticated user could read and rewrite anyone's
private pool.

- List queries now filter to rows the caller may see (public, owned, or
  granted can_view) via a shared SQL condition; admins bypass. The viewer
  identity is taken from the request context by the service and passed to
  the repository in the list params.
- Tag/Category/Pool single-item Get now enforce CanView (File already did).
- Pool Get/ListFiles require pool view; AddFiles/RemoveFiles/Reorder
  require pool edit.

Adds regression tests for private-by-default listing (hidden / public /
granted / admin) and for pool operations rejecting a non-owner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:07:17 +03:00
H1K0 f4545ff107 fix(backend): invalidate thumbnail cache on replace and permanent delete
Replacing a file's content left the old {id}_thumb.jpg / {id}_preview.jpg
in the cache, and the cache-hit fast path kept serving the stale image
forever; permanent deletion left those files orphaned. FileStorage gains
InvalidateCache, which Replace and PermanentDelete now call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 14:12:38 +03:00
H1K0 945df7ef8a fix(backend): enforce file ACL on file-tag and import endpoints
Two broken-access-control holes:

- PUT/DELETE /files/:id/tags(/:tag_id) and GET /files/:id/tags went
  straight to TagService with no ACL check, letting any authenticated
  user read or rewrite tags on anyone's private files. The handlers now
  require view (list) or edit (mutate) on the target file via new
  FileService.AuthorizeView/AuthorizeEdit helpers.

- POST /files/import accepted an arbitrary host path from any user,
  turning it into an arbitrary server-side file read. It is now
  admin-only and the supplied path is confined to IMPORT_PATH.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 13:59:33 +03:00
H1K0 595b8fa671 feat(backend): implement full tag stack (repo, service, handler, routes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:29:20 +03:00
H1K0 5050dbea3c feat(backend): implement file handler and wire all /files endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:40:04 +03:00
H1K0 99508cdbf8 feat(backend): implement file service with upload, CRUD, ACL, and audit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:28:59 +03:00