imn@dev:~/oss/wishka$ cat scheme.md
pasted product url
L0 og / json-ld
L1 llm extract
L2 jina reader
L3 firecrawl
wish card
reserve without spoiling
// deploy: Vercel · Railway Postgres · nightly age-encrypted backup
imn@dev:~/oss/wishka$ cat services.md
- Railway Postgres
- the primary database — wishes, reservations, groups, profiles, auth tables, the per-url parse cache and per-day ai usage counters.
- OpenAI API
- html and markdown extraction inside the parsing pipeline plus generated wish images; model names are environment variables, not constants.
- UploadThing
- image storage and cdn — every parsed or generated image is re-hosted here, so nothing is ever served from a third-party host.
- Resend
- transactional mail — sign-in codes and reservation notifications, rendered through one shared paper-ledger template.
- Jina + Firecrawl
- parsing layers L2 and L3 — the free fallback when an anti-bot challenge blocks the plain fetch, then a paid last resort.
- Vercel
- hosting, and the after() runtime the fire-and-forget image-generation job runs inside.
- GitHub Actions
- lint, typecheck and test into a protected main, a nightly age-encrypted postgres backup, and a docs-to-wiki sync on push.
imn@dev:~/oss/wishka$ cat stack.md
imn@dev:~/oss/wishka$ cat highlights.md
- SCHEMA
- the surprise invariant is structural, not procedural: the wishes table has no reservation column at all, so there is no field to accidentally select into an owner-facing response.
- LOCKS
- reserving locks the wish row inside the same transaction as the owner's edit, delete and gift paths, and a partial unique index settles a concurrent double booking through the losing insert. deleting a booked wish orphans the reservation rather than deleting it, so its holder learns the gift disappeared.
- SSRF
- a hand-written guard parses ipv4 and ipv6 literals byte by byte to block private, link-local, cgnat and metadata ranges, re-validates every redirect hop, and documents dns-rebinding toctou as an accepted residual risk instead of leaving it unstated.
- IMAGES
- generation is a fire-and-forget job inside next's after(): the request returns immediately, the card polls a status column, and if nothing points at the upload by the time the job lands, the job deletes its own file.
- BACKUP
- a nightly action pg_dumps the database straight into age encryption using only a public key — the repo is public, so the workflow that produces the backup cannot decrypt it.
- TESTS
- 79 vitest suites; every data-access test spins up an in-memory postgres via pglite and replays the full drizzle migration history, so unique indexes and check constraints are exercised against real sql rather than mocks.