feat: organizer Android app and token-gated upload API
CI / lint-and-test (push) Failing after 1m18s
CI / lint-and-test (push) Failing after 1m18s
Replace Syncthing as the product upload path with a share-target app and POST /api/jobs. Web UI live-polls job progress. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,31 +6,33 @@
|
||||
|
||||
## One-Liner
|
||||
|
||||
Event photo pep pipeline: Syncthing drop -> gmic/rembg -> web. Upload a
|
||||
photo at the event, get back weird/fun filtered variants seconds later.
|
||||
Event photo pep pipeline: organizer Android app -> gmic/rembg -> web.
|
||||
Upload a photo at the event, get back weird/fun filtered variants seconds
|
||||
later.
|
||||
|
||||
## Vision
|
||||
|
||||
At an event (f12 meetup/party), someone drops a photo into a Syncthing
|
||||
folder from a phone/camera rig. A worker rips the background off, runs it
|
||||
through a random gmic filter/blend chain, and a handful of variants show
|
||||
up on a shared web page almost immediately — no app install, no login, no
|
||||
waiting for someone to "process the photos later". If a variant is fun,
|
||||
remix it with different filters right there. Finished jobs sync back to
|
||||
the phone via the same Syncthing share.
|
||||
At an event (f12 meetup/party), the organizer shares a photo into the
|
||||
Android app. The phone converts it to a 2000px JPEG, uploads it, a worker
|
||||
rips the background off, runs it through a random gmic filter/blend chain,
|
||||
and a handful of variants show up on a shared web page almost immediately —
|
||||
guests need no app, no login, no "process the photos later". If a variant
|
||||
is fun, remix it with different filters right there. Finished jobs land on
|
||||
the organizer's phone (gallery + local notification) and stay on the web
|
||||
for everyone with the link.
|
||||
|
||||
## Audience
|
||||
|
||||
Event organizers and attendees at f12 events, on their phones, often on
|
||||
bad venue wifi. Nobody signs up, nobody logs in. Whoever has the link
|
||||
(and whoever's syncing photos in) is the whole audience.
|
||||
Event organizers (upload via the Android app) and attendees on their
|
||||
phones, often on bad venue wifi. Nobody signs up, nobody logs in. Guests
|
||||
with the link see everything; only the organizer's app can upload.
|
||||
|
||||
## Tone & Wording
|
||||
|
||||
- **Voice:** direct, casual, no explaining-the-obvious. It's a fun tool
|
||||
for an event, not enterprise software.
|
||||
- **Formality:** du. Short German labels in the UI (Ansehen, Remix,
|
||||
Download, Varianten, Zwischenschritte).
|
||||
Download, Varianten, Zwischenschritte, Warteschlange, Fertig).
|
||||
- **Error messages:** say what broke, don't blame the visitor — most
|
||||
errors here are gmic filter flakiness, not user error.
|
||||
- **Forbidden words/phrases:** no corporate buzzwords, no "Erlebnis",
|
||||
@@ -42,7 +44,7 @@ bad venue wifi. Nobody signs up, nobody logs in. Whoever has the link
|
||||
|---------|------|-----|
|
||||
| Success | `Remix erzeugt.` | `Dein Kunstwerk ist fertig! 🎉` |
|
||||
| Error | `Foreground-Filter fehlgeschlagen: Timeout nach 120s` | `Etwas ist schiefgelaufen.` |
|
||||
| Empty state | `Noch keine Jobs. Bild in den Sync-Ordner legen, dann kurz warten.` | `Hier ist noch nichts los... lade doch was hoch! 😊` |
|
||||
| Empty state | `Noch keine Jobs. Foto in der App teilen, dann kurz warten.` | `Hier ist noch nichts los... lade doch was hoch! 😊` |
|
||||
|
||||
## Design
|
||||
|
||||
@@ -54,46 +56,51 @@ bad venue wifi. Nobody signs up, nobody logs in. Whoever has the link
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No web auth / accounts / login product — anyone with the link sees
|
||||
everything, by design, for this event tool.
|
||||
- No camera-to-phone client in this repo — upload path is Syncthing
|
||||
(phone/folder setup is out of band).
|
||||
- No web auth / accounts / login product — anyone with the link **sees**
|
||||
everything, by design. Upload is a separate shared token in the
|
||||
organizer app, not a user account.
|
||||
- No Play Store / FCM / ntfy dependency. Notifications are local Android.
|
||||
- No beamer/projector product built into this repo.
|
||||
- Not a DAM (digital asset manager) — no albums, tagging, search,
|
||||
retention policies. `incoming/`, `variants/`, and `intermediates/`
|
||||
are the whole data model.
|
||||
- No auto-deletion of incoming uploads. Ever. The worker only reads/copies.
|
||||
- No Syncthing container in this compose — Syncthing runs on the host.
|
||||
- No Syncthing container in this compose. A host Syncthing share can
|
||||
still write `incoming/` as a fallback; it is not the product path.
|
||||
|
||||
## Infrastructure (project-local)
|
||||
|
||||
- Workdir on server: `/home/frank/live.f12.rocks`
|
||||
- Data volume (Syncthing share): `${DATA_HOST_DIR}` — prod default
|
||||
- Data volume: `${DATA_HOST_DIR}` — prod default
|
||||
`/home/frank/sync.schwenk.online/data/livef12`, local override `./data`.
|
||||
Bind-mounted into `worker` and `web` at `/data` (see `compose.yml`).
|
||||
- Layout under the share:
|
||||
- `incoming/` — phone drop
|
||||
- Layout:
|
||||
- `incoming/` — HTTP upload (and optional Syncthing drop)
|
||||
- `variants/` — `{stem}_original.jpg`, `{stem}_rembg.png`, `{stem}_vN.png`
|
||||
- `intermediates/` — `{stem}_vN_*.png` (incl. `_post_0` …)
|
||||
- `meta/` — web bookkeeping JSON (optional `.stignore` on phone)
|
||||
- `meta/` — status + manifest JSON
|
||||
- `processed.json`
|
||||
- Domain: `live.f12.rocks`, routed via the shared external `traefik`
|
||||
network, TLS via `myresolver` (see `INFRASTRUCTURE.md`).
|
||||
- rembg model cache lives in the named Docker volume `rembg_cache`, not
|
||||
under the Syncthing share — container state, not event data.
|
||||
- rembg model cache lives in the named Docker volume `rembg_cache`.
|
||||
- Organizer Android app lives in `android/` (sideload APK).
|
||||
- `UPLOAD_TOKEN` in server `.env` — required for `POST /api/jobs`.
|
||||
|
||||
## Project-Specific Rules
|
||||
|
||||
```markdown
|
||||
- Never delete or move files under incoming/ from worker code — Syncthing
|
||||
is the writer there. (reason: uploads are the one copy of the original
|
||||
that exists outside a phone's camera roll)
|
||||
- Never delete or move files under incoming/ from worker code — the
|
||||
upload API (and optional Syncthing) is the writer there. (reason:
|
||||
uploads are the one copy of the original that exists outside a
|
||||
phone's camera roll)
|
||||
- Keep every intermediate image under intermediates/ — don't clean them
|
||||
up automatically. (reason: useful for debugging bad filter picks,
|
||||
disk is cheap compared to re-running gmic)
|
||||
- gmic/rembg subprocess calls always run under `nice` (see NICE_LEVEL) —
|
||||
the worker box needs to stay responsive for other things during an
|
||||
event. (reason: explicit requirement, avoid CPU starvation)
|
||||
- POST /api/jobs is fail-closed: empty UPLOAD_TOKEN disables uploads.
|
||||
The public web UI stays read-only.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user