Wedding and event planner built as a Progressive Web App with a React Native companion: tiered organizer collaboration, guest management, RSVPs, notes and ToDos, photo workflows, party games, optional task pushes, and a token-protected projector slideshow.
Auch auf Deutsch verfügbar: README.de.md
Sorted from "technically interesting" to "UX polish". Each item links to the central file that implements the mechanism.
-
QR login for guests — Sanctum bearer tokens via QR code. Solo guests receive the token directly; family groups first pick a member so unused tokens don't block other family members. →
app/Http/Controllers/Api/QrAuthController.php -
Tiered per-event authorization — Owner, Event Admin and Event Manager are separate event roles with one authoritative policy/service layer. Owners can delegate operational work without exposing deep settings, access administration or ownership controls. →
app/Policies/EventPolicy.php,app/Services/EventAccessService.php -
Isolated mobile management API — approved, verified organizers pair the app separately from guests through a short-lived, one-time QR for one selected event. The resulting
management:event:{id}bearer is pinned to that event and re-authorized againstX-Event-IDon every scoped request. Organizer login is QR-only; no parallel password-token endpoint remains. →routes/api.php,app/Http/Middleware/ResolveManagementEvent.php -
Photo game with a delta-override model — global task catalogs (general + event-type specific) plus per-event overrides (
hidden/modified/added). The standard tasks stay maintainable in a single place; events only store deltas. →app/Http/Controllers/Api/PhotoGameController.php -
Drinking-game scoring with physiologically motivated multipliers — base formula
liters × % × 10, shot multiplier 2.0 for spirits (faster absorption), 50% binge penalty after three alcoholic drinks in a row, negative points for water and soft drinks. →app/Services/DrinkScoreService.php -
Configurable color system with live preview — three palette slots (primary/secondary/tertiary) and nine role fields that store keys rather than hex values. Change the palette and every role follows. Four simulated app screens react live in the settings split-screen. →
resources/js/pages/Event/Settings.vue -
Clean drink-catalog modeling — one row per drink type in
drink_catalog, one row per size indrink_catalog_sizes, the per-event selection indrinksreferences both.drink_logskeepamount_literdenormalized so historic points stay stable. →app/Models/DrinkCatalog.php,database/migrations/2026_03_29_000001_refactor_drink_catalog_to_type_only.php -
Projector slideshow — public route gated by
projector_token, auto-polls every 10 s, 5 s crossfade. Context label per album: guest name (gallery), description (presentation) or task text (photo game). →resources/js/pages/Projector/Show.vue -
Style presets for one-click theme switching — predefined palettes can be applied to an event without touching individual fields. →
app/Http/Controllers/EventStylePresetController.php -
Mobile PWA — installable on iOS/Android, custom icon set, offline support via
vite-plugin-pwa. →vite.config.ts -
i18n on the front- and backend — vue-i18n v11 for the web app,
Accept-Languagemiddleware for API responses so the React Native app gets localized drink and photo-game texts. →resources/js/plugins/i18n.ts -
Public landing page — three-step explainer and feature cards for first-time visitors, no auth required. →
resources/js/pages/Welcome.vue
| Layer | Technology |
|---|---|
| Backend | Laravel 12 (PHP 8.3) + Inertia.js + Sanctum |
| Web frontend | Vue 3 + TypeScript + Tailwind CSS 4 + Reka UI |
| Mobile | React Native (Expo) — separate repository |
| Build | Vite 6 + vite-plugin-pwa |
| Storage | Hetzner Object Storage (photos, Nürnberg) |
| Resend | |
| Push | Expo Push Service (optional organizer notifications) |
| Deploy | Docker + Coolify |
The project is fully containerized. Migrations run automatically on boot.
docker compose up -dPromote the initial admin user inside the running container:
docker exec laravel-app php artisan tinker
# > User::where('email', 'you@example.com')->update(['role' => 'admin'])Vite runs in its own container and serves assets over HMR.
Three long-lived branches, each auto-deployed by Coolify on push. Migrations run automatically.
| Branch | Environment | Domain | Dockerfile |
|---|---|---|---|
develop |
local dev | — | Dockerfile (artisan serve, port 8080) |
staging |
staging | beta.hommrich.app |
Dockerfile.prod (nginx + php-fpm) |
production |
live | eveplan.de |
Dockerfile.prod (nginx + php-fpm) |
docker-compose.yml is intentionally different per branch (different Dockerfile, different exposed ports). Never let the develop version overwrite staging or production. Every merge to staging or production resets that file to the target-branch version.
Three safety nets back this up:
-
Always merge locally, never via the GitHub UI. A server-side merge on github.com ignores the
.gitattributes merge=oursdriver and has no direction guard, so the "Create pull request" button on the wrong branch has already once overwrittendevelop's dev compose with the prod version (PR #4). All promotions run through the terminal snippets below. -
merge=oursdriver —.gitattributesmarksdocker-compose.yml(andDockerfile) asmerge=oursso git keeps the target-branch version on every local merge instead of trying a three-way merge. Enable the driver once per clone:git config --local merge.ours.driver true -
CI guard —
.github/workflows/compose-guard.ymlruns on every push tostagingandproductionand fails the build if the compose file references the dev Dockerfile orartisan serve. If a manual merge ever slips the wrong file through, this catches it before Coolify redeploys.
git checkout staging
git pull --ff-only origin staging # abort if anyone else pushed
git merge --no-ff --no-commit develop
git checkout HEAD -- docker-compose.yml # keep the staging compose file
grep -q 'Dockerfile.prod' docker-compose.yml || { echo "compose drift"; exit 1; }
git commit -m "Merge branch 'develop' into staging"
git push origin staging
git checkout developCoolify picks up the push, rebuilds and redeploys. Verify at https://beta.hommrich.app before promoting further.
Only promote once staging is green.
git checkout production
git pull --ff-only origin production # abort if anyone else pushed
git merge --no-ff --no-commit develop
git checkout HEAD -- docker-compose.yml # keep the production compose file
grep -q 'Dockerfile.prod' docker-compose.yml || { echo "compose drift"; exit 1; }
git commit -m "Merge branch 'develop' into production"
git push origin production
git checkout develop
⚠️ Never push staging and production at the same time. Two parallel Coolify redeploys exhaust the VPS RAM. Pushstagingfirst, wait untilbeta.hommrich.appresponds, then pushproduction.
The data model is centered on Event as root: guests, groups, notes, photos, drinks and photo-game tasks all hang off an event. Web organizers authenticate through a Sanctum session; the mobile management surface uses a separate User bearer minted through one-time pairing QR. Guests keep their intentionally narrower QR bearer. Web requests resolve the active event from the session, while management API requests send X-Event-ID and re-check account state, token ability, membership and policy tier every time.
Focus subsystems:
- Photo game — delta model on top of global task catalogs
- Authorization — Owner / Event Admin / Event Manager tiers with last-owner protection
- Organizer workflow — private notes, assigned ToDos, device pairing and optional generic pushes
- Drinking game — scoring service with shot multiplier and binge penalty
- Projector — token-protected slideshow with context-aware labels
- Color system — palette + role mapping with live preview
A detailed architecture description, including an ER diagram, auth layers, and subsystem internals, lives in docs/ARCHITECTURE.md.
| Stack | Command | What runs |
|---|---|---|
| Backend | composer test |
Pest against a dedicated laravel_test database with a safety guard |
| Backend | ./vendor/bin/pest --filter=DrinkScore |
A single test file |
| Frontend | npm test |
Vitest, parallel worker threads |
| Frontend | npm run test:watch |
Hot-reload tests |
| Coverage | ./vendor/bin/pest --coverage |
Backend coverage (Clover XML + text report); Vitest via --coverage |
Coverage: backend and frontend specs cover API endpoints, policies, cross-event guards, controllers, retention, services, shared components, page behavior and i18n. The current hardening working tree is locally verified with 446 passing backend cases (1 skipped) plus green Pint, Prettier, ESLint and Vue TypeScript checks.
Test isolation is enforced at the database level: a TestCase guard rejects any run that is not connected to the dedicated laravel_test database.
The mobile app lives at github.com/AHommrich/eventplaner-app and shares only the HTTP API with the web app. Guest mode covers QR login, RSVP, schedule, photos, games and privacy self-service. Organizer mode uses an isolated one-event management session, manages Notes/ToDos and photos, receives privacy-minimized assignment pushes, and renders through the same event theme contract as Guest mode. The mobile repo documents its client-side boundaries in docs/ARCHITECTURE.md.
The project is operated from Germany and is documented to be GDPR-ready:
- Imprint at
/impressum(§5 DDG) - Privacy policy at
/datenschutz(Art. 13 GDPR), with mandatory signup consent - Data export endpoint for the right of access (Art. 15)
- Cascade deletion of photos in Hetzner Object Storage on account/event removal (Art. 17)
- Scheduled retention cleanup for expired invitation tokens and declined guests (Art. 5)
- Privacy-minimized Expo pushes: generic lock-screen copy, opt-out, and receipt-driven invalid-token removal
- Sub-processor register in
docs/legal/sub-processors.md
The full plan, including stage breakdowns, is in docs/GDPR_COMPLIANCE_PLAN.md.
Beyond this README the repo carries a small set of docs, each with a clear purpose:
docs/GETTING_STARTED.md— bring the project up locally, including the common pitfallsdocs/CONTRIBUTING.md— branch model, commit convention, doc-sync ruledocs/ARCHITECTURE.md— subsystem internals (photo game, drink score, projector, color system)SECURITY.md— vulnerability disclosure
All rights reserved. See LICENSE. Publicly viewable for portfolio purposes; no reuse, fork, or redistribution without written permission.


