Commit Graph
28 Commits
Author SHA1 Message Date
JakeBreath cd490b0a23 Duplicates, delete & storage, users page with J-ID avatars
Backend:
- Perceptual hashes (aHash/dHash/pHash/wHash via imagehash, no imgdd)
  stored on items, computed on upload/download and by the new
  compute_visual_hashes command
- Duplicates API: exact duplicates (multi-location items), visual matches
  for one item, union-find similarity groups with pagination
- Delete API with ownership/staff checks, per-item and per-copy deletion,
  watched-folder path validation; storage overview and temp cleanup;
  file list accepts j_ids batches
- Staged uploads are flagged visual_match with their library matches
  (threshold via VISUAL_MATCH_THRESHOLD)
- Staff users API: list with upload counts, set role and avatar by J-ID;
  User.avatar FK with signed avatar URLs
- Download threads close their DB connection and stale tasks are reaped,
  keeping behaviour Gunicorn-friendly

Frontend:
- /duplicates: exact duplicate groups with per-copy delete, visual
  similarity controls, search similar to a J-ID, paginated groups with
  selection, bulk delete and dismiss
- /delete: storage cards, delete by J-ID with preview grid, temp cleanup
- /users: staff directory with role selects and avatar J-ID inputs
- Nav + command palette entries; top-bar avatar; upload cards and the
  metadata modal show library visual matches
2026-09-17 12:49:10 -05:00
JakeBreath 75b7ed35eb Live worker counts and an e621 request-history dropdown
- Status polling drops from 30s to 5s, and download start/finish/cancel
  invalidates it immediately, so the footer's Active Workers reflects
  running download tasks in near real time
- The e621 time in the status pill is now a button: it opens a dropdown
  with the session's request history (clock time, endpoint, duration,
  colour-coded) plus totals; closes on outside click or Escape
- Metrics store keeps the last 50 requests
2026-09-17 12:20:27 -05:00
JakeBreath 6962e483fc Async Download to Library with progress; Download to client
Backend:
- DownloadTask model + background thread runner: streams the file with
  progress (%, bytes, speed) and a cancel flag, then indexes it, names it
  J-<id>.<ext> and applies the e621 metadata
- DownloadTaskViewSet (create/retrieve/cancel) replaces the synchronous
  endpoint; the status footer's worker counts now reflect download jobs
- Client download proxy (/api/online/file/) streams an e621 original to
  the browser with Content-Disposition: attachment, restricted to the
  configured e621 CDN hosts so it cannot be used as an open proxy

Frontend:
- Online detail: progress bar with percentage, transferred size, speed
  and cancel while downloading; success links to the new J-ID
- New 'Download to client' button available to everyone (guests too)
2026-09-17 12:06:15 -05:00
JakeBreath bf00cf36a2 Name uploaded and downloaded files J-<id>.<ext>
Files added through the upload pipeline and Download to Library are
renamed to their J-ID right after indexing, so every new library file is
traceable by its identifier (scanned files keep their existing names).
index_file now returns the created location so callers can rename it;
the location record is updated to the new path.
2026-09-17 11:56:01 -05:00
JakeBreath d4f5df2e4e Metadata modal: size the preview to the image's own ratio
The modal preview used a fixed 200px column with object-contain, so wide
images rendered as a slim letterboxed rectangle with dead space. The
preview now sizes naturally (max 320px wide / 55vh tall, aspect ratio
preserved) and the modal column follows it.
2026-09-17 11:51:04 -05:00
JakeBreath a3d1063946 Upload page: auto-upload and a 5x3 progress grid
- Files upload as soon as they are dropped or selected; a sequential
  queue processes them and picks up files added while uploading
- The progress list is now a grid: 5 columns with about three rows
  visible (scrolls beyond) showing thumbnails, per-file progress bars
  and status; 'clear finished' remains, object URLs are revoked
- Videos show an icon instead of a thumbnail
2026-09-17 11:35:10 -05:00
JakeBreath 1086beb974 Upload board: dismiss all, real previews for indexed records
- 'dismiss all' clears every indexed record at once
- Indexed cards show the actual file preview: completed records now get a
  signed library media URL (raw for images, thumbnail for videos) so
  <img>/<video> tags can load it, including items hidden from guests
- Media raw/thumbnail endpoints accept the signature for anonymous
  requests and fall back to the normal guest-filtered path otherwise
- Guest blacklist keeps a persistent Redis mirror: an expired TTL or an
  unreachable e621 keeps the last successful list instead of falling
  back to the small local list
2026-09-17 11:29:56 -05:00
JakeBreath b71ec729e0 Enrich IQDB candidates; link downloads the e621 original
- IQDB responses carry no preview/file data, so candidates only showed an
  ID; the SPA now enriches them with one batched posts lookup (preview,
  rating, score, favourites, dimensions, tag preview)
- Candidate tiles are selectable instead of instantly resolving: picking
  one shows its info and an explicit 'Link selected post' button
- Linking a post now fetches the e621 original into the library and
  drops the staged upload; when the staged file's MD5 already equals the
  post's file, the staged copy is moved instead (identical bytes)
- Keep the file URL in stored e621 metadata; sanitize the new candidate
  fields server-side
2026-09-17 11:24:01 -05:00
JakeBreath 7deb6084b6 Fix staged upload previews and allow WebP
- Staged files are now served through a signed URL (Django signing, 24h)
  so <img>/<video> tags can load previews without an Authorization
  header; the file endpoint accepts header auth or a valid signature,
  rejects tampered signatures, and still scopes access to the owner
- Serializer responses now carry the request context so URLs are signed
  per user
- Add .webp to the allowed extensions (backend + upload hint)
2026-09-17 11:17:04 -05:00
JakeBreath d0e2901c92 Upload pipeline: staging, MD5 auto-match, IQDB, three-column board
Backend:
- TempUpload model: staged files (pending / visual_match / completed /
  error) with resolution, e621 payload, custom metadata and IQDB data
- Files land in a temp folder and only move into the watched library
  folder once resolved; duplicates resolve immediately without a copy
- Endpoints: stage (multipart), list, retrieve, temp file, IQDB save,
  resolve (link to post or custom metadata), discard/dismiss
- cleanup_temp_uploads command for old staged files
- Replaces the old direct-to-library upload endpoint

Frontend:
- Upload page is now a three-column board (Pending & Unmatched /
  Visual Similarity Detected / Auto-uploaded & Indexed)
- After upload: MD5s are batch-checked against e621 and matches
  auto-complete with full post metadata; remaining files run through
  IQDB and move to the similarity column when candidates exist
- Metadata modal with IQDB candidates, post-ID linking and custom
  tags/rating/notes; discard and dismiss actions
- e621 client gains fetchPostsByMd5 and iqdbSearch helpers

Roadmap updated with the completed upload items.
2026-09-17 11:12:03 -05:00
JakeBreath 60e1113231 Add roadmap with the remaining work
Includes the newly identified gaps: a download progress bar for
Download to Library, and the upload pipeline rework (staging storage,
MD5 auto-match, IQDB/visual similarity, three-column board).
2026-09-17 10:59:18 -05:00
JakeBreath afca89de8f Show SPA e621 request times in the status pill
- New e621 metrics store tracks request count, cumulative time and the
  last request (path + duration) for the session
- The e621 client measures each request around fetch and JSON parsing
- Status pill appends a teal 'e621: <total>' segment with a tooltip
  showing request count and the last request; a server-side e621 time
  is shown separately if the backend ever reports one
2026-09-17 10:32:51 -05:00
JakeBreath ebac3ac922 Keep e621 metadata on downloaded posts; clear up account roles
Roles:
- JakeBreathild is now staff + superuser (the real account); the 'jake'
  smoke-test account was demoted to a regular user
- /me exposes is_superuser and the account page shows an admin badge

e621 metadata:
- MediaItem gains e621_post_id and e621_data (trimmed post payload:
  tags by category, rating, score, favourites, comments, sources,
  description, pools, relationships, file info, uploader)
- Download to Library accepts the post payload from the SPA and stores
  it; the item's custom rating is seeded from the e621 rating when empty
- Library detail shows an e621 metadata card: link to the in-app post,
  score/favourites/comments, taxonomy-coloured tags, DText description,
  sources and pools; grid cards get an e621 badge and fall back to the
  e621 rating for their colour (display_rating)
- Guest visibility now also considers e621 tags, so downloaded explicit
  content is hidden from anonymous visitors
2026-09-17 10:27:06 -05:00
JakeBreath e5cc63b0cc Phase 3: J-IDs, ownership, roles, guest safety, adaptive detail, download
Backend:
- User.role (user/uploader/staff) with can_upload; uploads and downloads
  gated to uploader+; owners and staff can edit their items
- MediaItem.uploaded_by plus J-<id> identity (serializer, admin,
  scan_files --user, first superuser as default owner)
- API resolves J-<id>, bare numeric ids and MD5s; neighbors and lookup
  return j_ids
- Guest safety: mirror e621's anonymous default blacklist into Redis
  (parses comments, negations and wildcards), flag hidden_from_guests
  and filter lists, details and lookups for anonymous users
- POST /api/online/downloads/ writes an e621 file into the watched
  folder and indexes it for the uploader
- MariaDB + Redis via docker compose (host ports 3307/6380), PyMySQL
  driver shim, Redis cache replacing the file cache; SQLite data
  dumped and loaded into MariaDB

Frontend:
- Single /detail/:itemId route with an adaptive shell: J-<id> renders
  the library item, bare numbers render the e621 post
- Legacy /view/<md5> and /online/view/<id> redirect to canonical URLs
- Cards expose J-IDs; library custom-data editor is read-only for
  non-owners
- Role gating: Upload hidden/blocked for regular users, account shows
  the role, guest hint on the library
2026-09-17 10:16:45 -05:00
JakeBreath d6c3f90c1f Render thumb #id as an inline post thumbnail in DText
- New PostThumb component fetches the post through the e621 client
  (cached 30 minutes, no retries) and renders its preview image linked
  to the in-app post page, with the post id underneath
- Falls back to an external e621 link while loading or when the post
  cannot be fetched
2026-09-17 09:28:18 -05:00
JakeBreath 8e2f5e3395 Render DText to the official spec
Checked against https://e621.net/help/dtext and filled the gaps:
- backtick inline code spans and backslash-backtick escaping
- [color=...] accepts tag category names (artist, copyright, species,
  ...) alongside CSS colour names and 3/6/8-digit hex
- links: <url> brackets, "title":[url], "title":/relative paths,
  wiki links with custom titles and #anchors, {{tag search}} and entity
  references (post/topic/pool/set/comment/... #id)
- [quote=red] colours the bar instead of being read as an author
- nested lists via repeated * / # markers
- [section,expanded=Title] renders expanded
- [table] thead/tr/th/td tables and [ltable] pipe tables
- [#anchor] targets and [[#anchor]] in-page links
- headings always start their own block
2026-09-17 09:22:14 -05:00
JakeBreath 81939d8ca3 e621 polish: cached results, search context, DText descriptions
- e621 list/post queries use a 5 minute staleTime and 30 minute gcTime,
  so opening a post and coming back shows the same results with no
  refetch; global gcTime raised to 30 minutes
- post card links carry their originating search in route state; the
  detail back link returns to it and related-post links keep it
- new navigations scroll to top while history back/forward keeps the
  previous position
- DText renderer for e621 descriptions (b/i/u/s, sup/sub, code, spoiler,
  quote, color, url/wiki/thumb, headings, lists, sections, expand
  blocks, named and bare links) built as React elements, no raw HTML
- Spoiler moved to its own component; trailing punctuation no longer
  swallowed into links
2026-09-17 09:19:25 -05:00
JakeBreath 04448bf615 Online browser + post detail: SPA talks to e621 directly
Backend:
- POST /api/files/lookup/ reports which MD5s are already in the library

Frontend:
- e621 client extended with post/tag/favorite types and helpers: post
  search, post detail, batch posts by id, tag autocomplete, toggle
  favorite
- /online: tag search with autocomplete, post grid with rating colors
  and in-library badges, numbered pagination, page tag cloud, blacklist
  panel and filtered counts from /users/me.json, anonymous hint
- /online/view/🆔 media viewer (sample or original, video support),
  taxonomy-colored tags by category, specs sheet, favorite/unfavorite,
  description, sources, pools and parent/children thumbnails
- Shared CollapsibleSidebar extracted from the library page; Online
  added to the nav, command palette and sidebar toggle
2026-09-17 09:00:17 -05:00
JakeBreath b6409523e6 e621 credentials: user fields, API endpoint, Account screen
Backend:
- User model gains e621_username / e621_api_key / e621_base_url
- GET/PUT /api/auth/e621/ for the owner's credentials; /me exposes only
  the username and a configured flag, never the key

Frontend:
- e621 client core: Basic auth, _client param (browsers cannot set a
  User-Agent), serialized queue throttled to 1 request/second, readable
  error mapping
- Account screen (/account): username, API key with reveal toggle,
  base URL (e621 / e926 / custom), Save + Test connection
- Credentials are fetched from the backend and held in memory only,
  cleared on logout
2026-09-17 08:49:00 -05:00
JakeBreath a09ab8a160 Shell shortcuts: /, D, [ ], Ctrl+K palette + neighbors endpoint
Backend:
- GET /api/files/{md5}/neighbors/ returns previous/next items in the
  current ordering (name, size, created_at) for keyboard navigation

Frontend:
- / focuses the library search input
- D downloads the file on the detail page
- [ and ] navigate to the previous/next item; hint shown on the page
- Ctrl/Cmd+K opens a command palette (navigation, toggle filters,
  focus search, log in/out); Escape closes it
- Sort order now persists alongside the other library filters so
  prev/next stays consistent
2026-09-17 08:33:22 -05:00
JakeBreath 063305c552 Remove frontend build-version plumbing
The backend status endpoint already reports the shared git hash and
environment, so the SPA no longer needs its own baked copy.
2026-09-17 08:33:22 -05:00
JakeBreath 8a81764401 Footer: drop the duplicate build version, keep it in the header only
- Footer now shows just the storage path and active workers
- Version/env stays in the header status pill (spec updated to match)
2026-09-17 08:28:23 -05:00
JakeBreath cc625d1e10 Fix shell details: distro name, storage path only in footer
- OS name comes from /etc/os-release (e.g. 'CachyOS Linux'), mirroring
  the original app's os_utils; macOS/Windows get proper names too
- Status pill shows the name as-is instead of uppercasing it
- Removed the watched-folder path next to the logo; it now appears only
  in the footer strip (spec updated to match)
2026-09-17 08:26:14 -05:00
JakeBreath 3aa42f3703 Shell polish: storage line, status pill, footer strip, sidebar
Backend:
- apps.core with GET /api/status/ (env, git hash, OS, watched folder,
  worker counts) and a TimingMiddleware adding X-Server-Time-Ms
- status endpoint reports its own server-side assembly time

Frontend:
- top bar shows the storage line (watched folder) and a status pill
  with ENV, git hash, host OS, server time (e621 time once it exists)
- fixed 32px footer strip: storage path, active workers, build version
- collapsible filter sidebar: closed by default, Ctrl/Cmd+B toggle,
  overlay drawer with backdrop blur under 1024px
- rating filters, per-page and sidebar state persist in localStorage
2026-09-17 08:19:27 -05:00
JakeBreath 0ff6232ba5 docs: drop the invented 'Mocha Archive Terminal' name
- DESIGN.md frontmatter no longer names the design system
- The spec now references the local DESIGN.md file instead of the
  Stitch-internal design system placeholder
2026-09-17 08:14:02 -05:00
JakeBreath 3c66629ed2 Use the git commit hash as the app version
- Spec header, status pill and footer now document '<env> @ <hash>'
  instead of invented release numbers
- Backend settings expose GIT_COMMIT_HASH / APP_ENV / APP_VERSION,
  mirroring the original app
- Frontend bakes the short hash in at build time and shows it in the
  top bar
2026-09-17 08:12:54 -05:00
JakeBreath 3f353307cf docs: correct spec assumptions (storage line, status pill, footer workers)
- 'NAS UI' tag was a misinterpretation of the watched-folder path; the
  spec now calls it the storage line and notes J621 is folder-based
- Status pill documents the real backend fields: environment, actual
  host OS, page generation time, e621 API request time
- Footer strip is marked as a proposal; the workers count is real data
  from the stats page, not invented thread counts
- Removed the remaining 'Write to NAS' / 'Ingest to NAS' phrasing
2026-09-17 08:10:35 -05:00
JakeBreath 555c25d77b M0: scaffold SPA + API monorepo
Backend (Django 6.1 + DRF):
- Token auth with a custom User model (register/login/logout/me)
- Library models (MediaItem, MediaLocation) and REST endpoints
- File list/detail with search, rating filter, sorting, pagination
- Multipart upload with optional rating/tags/notes
- Range-aware media serving (video seeking) and ffmpeg thumbnails
- scan_files management command for the watched folder

Frontend (React 19 + Vite + TypeScript):
- Catppuccin Mocha design tokens from the design docs
- App shell, token persistence, protected routes
- Library grid with filters, file detail with custom data editor
- Upload page with per-file progress via XHR
- Dev proxy to the Django API
2026-09-17 07:56:47 -05:00