JakeBreath f8667c1037 Add a Random image endpoint and SPA page (with fastfetch mode)
Backend: GET /api/random/ (aliases /random and /random/) returns a random
library image with:
- rating=s,q,e filtering (comma separated, default any);
- fastfetch mode (?fastfetch=1 or any User-Agent containing "fastfetch")
  that only considers png/jpg/gif - what terminal viewers can show;
- JSON with j_id, filename, extension, rating, size, e621 id plus absolute
  url/download_url/thumbnail_url. Authenticated callers get signed URLs so
  fastfetch and image viewers can load them without headers; guests get
  unsigned URLs and never receive hidden_from_guests items.

Tests: apps/library/tests/test_random.py (8 tests) covering the response
contract, guest signatures, image-only default, the fastfetch format
restriction (flag and User-Agent), rating filters, guest visibility and the
short alias.

Frontend: /random page with rating pills, R to roll, Open/Download and a
library link, plus navigation and command palette entries; needs a backend,
hidden in local mode.

nginx: /random negotiates on Accept so browsers keep getting the SPA while
scripts get the JSON (verified with the proxy and frontend containers).

Also fixes a regression from the SSRF change: the guest download proxy
still referenced the removed 'parsed' variable on its success path, so
every proxied download would have 500'd. Redirect hops are now covered by
tests with a mocked requests.get.
2026-09-18 13:06:36 -05:00
2026-09-17 07:56:47 -05:00

J621

Self-hosted media library and e621 archive manager, rebuilt as a React SPA + Django REST API.

Structure

backend/    Django 6 + DRF API (MariaDB + Redis via docker compose)
frontend/   Vite + React + TypeScript SPA

Development

Backend

cd backend
source venv/bin/activate
python manage.py migrate
python manage.py scan_files            # index the watched folder
python manage.py runserver

Copy .env.example to .env and set WATCHED_FOLDER before scanning.

Frontend

cd frontend
npm install
npm run dev                            # http://localhost:5173, proxies /api to :8000

Production

Docker: see deploy/ for the two images (SPA on static nginx, API on gunicorn), the three compose variants (both / frontend-only / backend-only) behind a shared nginx service and a Tailscale sidecar, and the public-funnel or tailnet-only serve configs. deploy/push_*.sh builds and pushes the multi-arch images to the Gitea registry.

Random image endpoint

Used by the SPA's Random page and by shell greetings (fish_greeting + fastfetch):

curl -H "Authorization: Token <token>" \
  "https://j621.example.ts.net/api/random/?rating=s,q&fastfetch=1"
{
  "j_id": "J-59",
  "filename": "J-59.jpg",
  "extension": "jpg",
  "rating": "e",
  "url": "https://j621.example.ts.net/api/files/J-59/raw/?sig=…",
  "download_url": "https://j621.example.ts.net/api/files/J-59/raw/?sig=…&download=1",
  "thumbnail_url": "https://j621.example.ts.net/api/files/J-59/thumbnail/?sig=…",
  "fastfetch": true
}
  • rating — comma separated subset of s, q, e (default: any).
  • fastfetch=1, or any request whose User-Agent contains fastfetch, limits the roll to png/jpg/gif so terminals can display it. Images are the only candidates in both modes.
  • url is absolute, and signed for authenticated callers, so fastfetch can load it without headers. Guests get an unsigned URL and only see guest-visible items.
  • /random and /random/ are aliases of /api/random/ for scripts; 404 when nothing matches the filters.

Licence

Source-available, non-commercial: personal and other non-commercial use is welcome under the Jake Labs Non-Commercial Software Licence, which requires attribution and keeps derivative works under the same licence. Commercial use is not permitted. Third-party dependencies keep their own licences (all permissive: MIT, BSD, Apache-2.0, ISC).

S
Description
No description provided
Readme
1.3 MiB
2026-09-23 22:35:56 -05:00
Languages
TypeScript 55.8%
Python 37.7%
Shell 3.3%
PowerShell 2.5%
Rust 0.5%
Other 0.1%