living-room-cinema

The Living Room Cinema

A movie-night website with a shared watchlist, anonymous voting, private host controls, verified-email RSVPs, movie suggestions, and scheduled invitations and reminders. The interface runs on GitHub Pages; Supabase provides the database, authentication, and scheduled functions; Resend delivers email. TMDB supplies remote posters.

Status: implemented locally, not deployed or connected to production services. Provider accounts, DNS records, secrets, and a live acceptance test are required below. Without configuration, the site displays a read-only preview and disables submissions. The old browser-only prototype data is not imported.

Local development

Requires Node.js 22 or newer.

npm ci
npm test
npm run build
npm run preview

Open http://127.0.0.1:4173. To connect a development Supabase project, copy .env.example to .env, fill in the three public values, and build with:

node --env-file=.env scripts/build.mjs

For browser tests, build an unconfigured preview with npm run build, then run npm run test:browser. Windows uses installed Microsoft Edge. On Linux/macOS first run npx playwright install chromium. Tests mock external network services; they do not send email or use production data.

How it works

Production setup

1. Supabase database and authentication

Create a Supabase project, then apply these files in order using its SQL editor:

  1. supabase/migrations/202610020001_cinema.sql
  2. supabase/migrations/202610020002_mail.sql
  3. supabase/seed.sql

Alternatively, link the Supabase CLI to the project and use its migration/seed workflow. Migrations create new tables and should be applied only once. The seed only inserts missing movies; rerunning it does not overwrite refreshed posters or host changes.

In Authentication settings:

After each host has signed in through My email, grant their role in the SQL editor:

insert into public.hosts(user_id)
select id from auth.users
where lower(email) = lower('REPLACE_WITH_HOST_EMAIL')
  and email_confirmed_at is not null and not is_anonymous
on conflict do nothing;

Run this for both hosts. A sender address is unrelated to a host’s login address. Granting host status via user-editable metadata is intentionally unsupported.

2. Sender address and Resend

Recommended sender: The Living Room Cinema [email protected]. A new inbox is not needed just to send through Resend. For replies, configure Cloudflare Email Routing for [email protected] to either host’s existing inbox, or use a separate EMAIL_REPLY_TO. Forwarding alone does not let you send manual replies from that alias; use a mailbox provider if you want a shared inbox with send-and-reply support.

Create a Resend account and verify a domain you control. Add exactly the DNS records Resend supplies in Cloudflare, including DKIM and its sending/return-path SPF records. Do not replace unrelated MX records for an existing mailbox. If you choose a sending subdomain instead, update EMAIL_FROM to use that verified subdomain.

Use Resend’s SMTP settings in Supabase Auth: host smtp.resend.com, port 465, username resend, password your Resend API key, and the verified sender address. Adjust the Supabase SMTP sending rate to suit the small guest list and your provider limits.

3. Edge Functions and secrets

Using the Supabase CLI from this directory:

npx supabase login
npx supabase link --project-ref YOUR_PROJECT_REF
npx supabase functions deploy mailer
npx supabase functions deploy unsubscribe
npx supabase functions deploy refresh-posters

supabase/config.toml disables gateway JWT verification for these functions: mailer and refresh-posters check a separate cron bearer secret themselves; unsubscribe accepts only an unguessable per-subscriber token. Browser database requests use Supabase authentication and explicitly restricted RPCs.

Set these server secrets in the Supabase Functions dashboard:

Secret Value
RESEND_API_KEY Sending key from Resend
EMAIL_FROM The Living Room Cinema <[email protected]> or your chosen verified sender
EMAIL_REPLY_TO Optional receiving address/forwarding alias
SITE_URL https://movies.wiederhold.dev/ including trailing slash; use the complete project path if staging
CRON_SECRET Random high-entropy secret, e.g. 32 random bytes encoded as hex
TMDB_TOKEN TMDB API Read Access Token from your TMDB account

Supabase supplies SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY to deployed functions. Never put server secrets, the service-role key, or a Supabase sb_secret_ key in GitHub Pages, source control, or browser configuration.

In Supabase Vault, create cinema_function_url with https://YOUR_PROJECT.supabase.co/functions/v1, and cinema_cron_secret with the same value as CRON_SECRET. Then run supabase/schedule.sql in the SQL editor. It installs the ten-minute email schedule and Sunday 08:00 UTC poster refresh. Run refresh-posters once using the Functions dashboard with the cron Authorization header to populate remote images immediately. Do not invoke mailer with real subscribers until you are ready to send invitations.

4. GitHub Pages and Cloudflare

In the repository’s Actions variables, add these public settings:

The build fails on missing deployment settings or a recognizable privileged key. Only dist is uploaded. In Settings > Pages, select GitHub Actions. Push this implementation and run Deploy website to GitHub Pages manually from Actions. The default URL is https://shamayla38.github.io/living-room-cinema/.

Verify wiederhold.dev ownership in the GitHub owner’s Pages settings using the TXT record GitHub supplies. Set movies.wiederhold.dev as the repository’s custom domain. In Cloudflare DNS create:

Type Name Target Proxy
CNAME movies shamayla38.github.io DNS only (gray cloud)

Remove only conflicting records for the movies subdomain. Wait for GitHub’s DNS check/certificate, then enable Enforce HTTPS. A CNAME file is unnecessary for this Actions deployment. Set the final SITE_URL, Supabase Site URL, and Turnstile allowed hostname consistently. No Cloudflare proxy is needed for this setup.

5. Before sharing with friends

Use a staging project or only host-owned test email addresses for the first live run:

  1. Vote repeatedly in one browser, reload, and confirm one vote. Use a second browser to confirm shared counts and an independent ballot. Check the voting deadline.
  2. Verify an email code, grant a host role, and confirm a different verified guest cannot open host controls or query contact tables directly.
  3. Publish a screening with the correct timezone and capacity. Book/update/cancel seats, including two browsers racing for the last seat.
  4. Subscribe a test address. Invoke the mailer, verify delivery and the RSVP link, then invoke again and confirm no duplicate. Test reminders with a screening within 24 hours. Inspect Resend delivery/bounce status as well as Supabase jobs.
  5. Open an unsubscribe link: GET must only show the confirmation. Confirm it, verify reminders stop, and verify an existing seat remains booked. Test event cancellation.
  6. Confirm TMDB posters load, local fallback works, and mobile layout is usable.

Review mail_jobs failures and Supabase function/cron logs periodically. Back up the database and monitor the current provider quotas. Free-tier project availability is not a guaranteed always-on service; pick a plan appropriate for unattended reminders. The host UI reports mail status counts but does not automatically reconcile bounces. For deletion requests, remove the user through Supabase Auth administration; application rows cascade. Anonymous browser IDs and verified-email IDs are separate, so ask for the anonymous ID too if a guest wants their ballots removed. No automatic retention job is configured. Don’t prune anonymous users with active ballots or their votes disappear.

Files and validation

Automated tests exercise SQL permissions, duplicate ballots, round cutoff, suggestions, capacity/update/cancellation, email eligibility, leases, unsubscribe, and browser flows. PGlite tests stub Supabase’s auth.users/auth.uid; browser tests mock Supabase and Turnstile. They do not replace the live SMTP, DNS, concurrent-client, and Edge Function acceptance checks above. No production resources or real email delivery have been tested.

The bundled catalog retains The Drama (2026), The Fall (2006), The Stepford Wives (1975), Something Wild (1986), Charade (1963), and The Player (1992). See poster credits for source attribution. Artwork remains the property of its respective owners.

Provider references