# Maloca Berlin — Platform Reference for AI Crawlers (llms-full.txt) Deep technical reference for the maloca.art platform: pages, data schemas, ticketing flow and the full `/api/*` surface. For a concise overview read [`llms.txt`](https://maloca.art/llms.txt) first. > **Maloca Berlin** — Brazilian events agency & holding ecosystem in Berlin. > maloca.art is also a self-service events platform: promoters create events, > sell tickets with built-in Stripe checkout, run digital guestlists, and get > automated sales notifications. Humans scan QR tickets at the door with a > web-based scanner. ## Site map (public pages) | Route | What it is | |---|---| | `/` | Home — hero, agenda, promoter events, picks teaser | | `/events` | All events (Maloca + promoter), searchable | | `/events/:slug` | Event detail — media lightbox (poster + YouTube), tiers, checkout, map, JSON-LD `Event` schema | | `/picks` | Editorial picks (today / tomorrow / weekend) | | `/tickets` | Instagram-style grid of every event selling tickets on-site | | `/parties` | Maloca signature parties | | `/guestlist/:eventId` | Guestlist signup — name, email, phone, +1; live spots counter | | `/crowdfunding` | Community line-up voting: suggest/vote artists per round | | `/shop`, `/shop/:id` | Merch store (Printful fulfillment, Stripe checkout) | | `/user` | Personal area — tickets wallet, orders | | `/scan` | Door QR scanner (web, no app) — validates `TKT-…` codes | | `/production`, `/workshops`, `/gallery`, `/videos`, `/about`, `/team`, `/contact`, `/dub-siren`, `/capitals` | Content & services | | `/pro` | Promoter platform (auth): dashboard, event form | | `/pro/events/:eventId` | Per-event console (tiers, guestlist, collaborators, analytics) | | `/pro/ticketing` | Ticketing dashboard — buyers, guestlist, revenue (works for promoters AND admins, data scoped per role) | | `/pro/marketing` | Ad products: site banners, story ads, ad + ticket-link bundles | | `/pro/account` | Profile, VAT ID, Stripe Connect status, sign out | | `/admin` | Admin panel (allowlist): approvals, crawler, sales logs, marketing, crowdfunding, picks | | `/privacy`, `/terms`, `/refunds`, `/delete-my-data` | Legal / GDPR (self-service account + data deletion) | ## Event data schema (`events` table) Exposed to the client by `src/lib/eventService.ts` (`PromoterEvent`). Key fields: ```ts { id: string; // uuid slug: string | null; // SEO url segment (/events/:slug) title: string; event_date: string | null; // YYYY-MM-DD event_start: string | null; // ISO timestamp event_end: string | null; // ISO timestamp address: string | null; // venue line, geocoded for the map embed description: string | null; image_url: string | null; // Supabase Storage (event-images bucket) price: string | null; // free-form display price ("€15", "Free") ticket_price_cents: number | null; // legacy single-price Stripe ticket ticket_stock: number | null; // null = unlimited sales_paused: boolean | null; // temporarily stops checkout link: string | null; // external ticket page (RA, Eventbrite, …) guestlist_url: string | null; // internal /guestlist/ or external form approved: boolean | null; // false = pending admin approval curated: boolean | null; // featured on /picks curation_sort: number | null; badge_label: string | null; // card pill ("SOLD OUT") badge_link: string | null; tour_id: string | null; // groups EURO TOUR stops vat_rate: number | null; // German VAT applied at checkout fee_waived: boolean | null; // Maloca fee waiver flag pixel_code: string | null; // Meta Pixel id injected on the event page promoter_name / promoter_image / promoter_slug: string | null; collaborators: string | null; // comma-separated co-promoter emails created_by: string | null; // promoter email (owner) spotify_url / youtube_url / instagram_url / soundcloud_url: string | null; crowdfunding_pick / crowdfunding_sort: boolean | number | null; } ``` Ticket tiers live in a separate table (`ticket_tiers`): `id`, `event_id`, `name`, `description`, `price_cents`, `minimum_purchase_quantity` (e.g. 2 = combo ticket), `stock` (null = unlimited), sort order. Managed from `/pro/events/:eventId`. **Image serving**: all storage URLs go through `eventImageUrl()` which appends Supabase transform params — `thumb` 300px (cards), `card` 600px (grids), `preview` 800px (og:image / JSON-LD), `hero` 1400px (banners, lightbox). Objects are immutable (unique timestamped filename per upload) and served with `cache-control: max-age=31536000` (see `STORAGE_CACHE_GUIDE.md`). ## Ticketing flow (end to end) 1. **Discovery** — event pages render tier cards (name, price, VAT line, combo minimum). Quantities are chosen client-side (max 10/tier); the cart mirrors to a mobile buy bar via the `maloca:panel-cart` DOM event. 2. **Checkout** — `POST /api/event-checkout` with `{ eventId, origin, items: [{ tierId, quantity }] }`. Legacy single-price events send `items: []` and the API uses `ticket_price_cents`. The API validates stock, tier minimums and `sales_paused`, computes the Maloca service fee (5% + €0.50/ticket; waivable via `fee_waived`) and creates a **Stripe Checkout Session** settled into Maloca's account. Response: `{ url }` → the browser redirects to Stripe. 3. **Fulfillment** — Stripe calls `POST /api/stripe-webhook` (`checkout.session.completed`). Idempotency: a checkout-key guard makes retries never double-fulfill. The webhook: - writes the sale into `ticket_sales` (buyer email, event, tiers, amounts, `fee_cents`, `email_status`); - mints one `tickets` row per ticket with a code `` `TKT-${8-byte hex}` ``; - emails the buyer the QR ticket PDF via **Resend** (`GET /api/ticket-pdf?session=`, QR + venue map); - emails the event's promoter a sale notification (event name, date, buyer email, tiers, total) — skipped for self-purchases. 4. **Door check** — staff open `/scan` and scan the QR; the page calls `GET /api/ticket-verify?code=TKT-…` which returns the ticket's status (`valid` / already-redeemed / unknown). Redeemed state is updated on first scan. 5. **Guestlist** — `GET /api/guestlist?event_id=…` returns `{ count, max_spots, spots_left }`; `POST /api/guestlist` joins the list (`{ event_id, name, email, phone?, plus_one? }`). ## Promoter platform - **Sign in**: Google OAuth (Supabase Auth). Promoter row created lazily; admins are matched by an email allowlist. - **Create/edit events** at `/pro` → `POST /api/create-event` (auth); uploads go through `uploadEventImage()` (client-side compression → Supabase Storage, 1-year CDN cache TTL). - **Stripe Connect**: promoters onboard at Stripe via `/api/stripe-connect-onboard`; connection status surfaced in `/pro/account` (`/api/stripe-status`). - **Ticketing dashboard** `/pro/ticketing` reads `GET /api/pro-sales` — returns sales (with tier variants + refunded flag), guestlist signups, buy clicks and capacities. **Scoping**: promoters see only events they own (`created_by`); admin-allowlisted callers see everything platform-wide. - **Automated emails**: buyer ticket + promoter sale notification per sale (transactional via Resend; marketing/newsletter via Brevo). ## Admin platform (`/admin`, allowlist) Sections: overview stats, event approvals, crawled-events manager (Berlin crawler: Eventbrite/Instagram/Ticketmaster sources, approve → public events), ticket sales log, shop sales log, monthly stats, marketing campaign manager (Brevo), crowdfunding manager, picks curator, business-collab banner requests. ## API surface (`/api/*` — all under a single serverless dispatcher) **Ticketing & payments** - `POST /api/event-checkout` — `{eventId, origin, items[]}` → `{url}` (Stripe) - `POST /api/stripe-webhook` — fulfillment, emails (Stripe-signed) - `GET /api/ticket-pdf?session=…` — buyer QR ticket PDF - `GET /api/ticket-verify?code=TKT-…` — door validation - `POST /api/membership-checkout` — recurring supporter memberships - `POST /api/shop-checkout` / `GET /api/shop-products` / Printful status handlers — merch orders - `POST /api/paypal` — PayPal alternative rail **Guestlist & events** - `GET/POST/DELETE /api/guestlist` — read/join/remove signups - `POST /api/create-event` — promoter event creation (auth) - `GET /api/pro-sales` — role-scoped sales + guestlist data (auth) - `GET /api/promoter-by-slug?slug=…` — public promoter profile - `GET/POST /api/collaborators` — resolve co-promoter profiles - `POST /api/buy-link-click` — records external buy-button clicks (per-day series powers the "users per day" chart) **Crowdfunding / community** - `GET/POST /api/crowdfunding` — voting rounds, suggestions, votes - `POST /api/artist-suggest`, `GET /api/spotify-charts`, `GET /api/spotify-search` **Content & misc** - `GET /api/news`, `GET /api/instagram-media`, `GET /api/instagram-stats`, `GET /api/soundcloud-sets`, `GET /api/printful-store`, `GET /api/site-content`, `GET /api/theme`, `GET /api/news` (editorial), `POST /api/newsletter-subscribe`, `POST /api/contact`, `POST /api/pageview` - `POST /api/account-deletion` — GDPR self-service data deletion - `POST /api/vat-verify` — VIES VAT-ID validation - `GET /api/ticket-pdf` + `invoice-pdf-gen` — PDF documents - Admin-only: `admin-events`, `admin-stats`, `admin-ticket-sales`, `admin-shop-sales`, `admin-shop-status`, `admin-monthly-stats`, `admin-marketing`, `crawl-berlin`, `login-logs`, `users` ## Conventions & constraints - **Stack**: React 19 + TypeScript + Vite, Tailwind, framer-motion, GSAP; API: TypeScript serverless functions on Vercel (`api/[name].ts` dispatcher); Supabase (Postgres + RLS, Auth, Storage); Stripe; Resend; Brevo; Printful; ImageKit (gallery); Cloudflare-fronted CDN. - **Auth model**: public pages anonymous; `/pro` and `/api/pro-sales` require a Supabase session; `/admin` + admin APIs check an email allowlist; service-role key used only server-side. - **SEO**: per-page meta + canonical via `usePageMeta`; JSON-LD `Event` on event pages; sitemap at `/sitemap.xml`; this file at `/llms-full.txt`, overview at `/llms.txt`. - **Emails**: Resend = transactional (tickets, promoter notifications, order confirmations); Brevo = marketing (buyer list sync, campaigns, newsletters). - **Design language**: black background, neon-lime accent (`--theme-bright`), "thunder-font" display face; dark glass cards.