{"id":50294722,"url":"https://github.com/chrisrobison/panicbooking","last_synced_at":"2026-05-28T08:03:59.333Z","repository":{"id":350800960,"uuid":"1208292780","full_name":"chrisrobison/panicbooking","owner":"chrisrobison","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-12T06:55:08.000Z","size":300,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-12T07:22:10.036Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chrisrobison.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-12T04:35:35.000Z","updated_at":"2026-04-12T06:37:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chrisrobison/panicbooking","commit_stats":null,"previous_names":["chrisrobison/panicbooking"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/chrisrobison/panicbooking","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisrobison%2Fpanicbooking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisrobison%2Fpanicbooking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisrobison%2Fpanicbooking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisrobison%2Fpanicbooking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chrisrobison","download_url":"https://codeload.github.com/chrisrobison/panicbooking/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisrobison%2Fpanicbooking/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33599494,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-28T08:03:58.405Z","updated_at":"2026-05-28T08:03:59.324Z","avatar_url":"https://github.com/chrisrobison.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Panic Booking\n\nLean PHP app for San Francisco venue/band booking + ticketing MVP.\n\n## Event Sync Pipeline\n\nPanic Booking now supports an adapter-based Event Ingestion Pipeline that keeps\nlegacy `scraped_events` behavior intact while adding source-aware ingestion,\ncanonical venue identity, venue scoring, and dark-night computation.\n\n### Core scripts\n\n- `php scripts/sync_venues.php --include-discovered`\n  - sync canonical venue definitions from `config/venues.php`\n  - backfill discovered venue rows from existing `scraped_events`\n- `php scripts/sync_events.php --adapter=all`\n  - runs Event Sync adapters and merges rows into canonical `scraped_events`\n  - supports `--venue=\u003cslug\u003e`, `--adapter=\u003ckey\u003e`, `--dry-run`, `--verbose`\n- `php scripts/compute_venue_scores.php`\n  - computes deterministic venue scores and tiers (`Tier 1`..`Tier 4`)\n- `php scripts/compute_dark_nights.php --days=60`\n  - computes likely open dates with confidence and stores in `venue_dark_nights`\n\n### Legacy compatibility\n\nLegacy entrypoints are preserved and route to the new pipeline:\n\n- `scrape_foopee.php` -\u003e `scripts/sync_events.php --adapter=foopee`\n- `scrape_venues.php` -\u003e `scripts/sync_events.php --adapter=official`\n- `scrape_all.sh` / `cron_sync.sh` now run Event Sync + scoring + dark-night jobs.\n\n## Booking Workflow Setup Notes\n\n### New app pages\n- `/app/opportunities.php`\n  - venue/admin: post and manage open-date opportunities\n  - band/admin: browse open opportunities and submit inquiries\n- `/app/bookings.php`\n  - venue/band/admin: view booking pipeline, status, history, and notes\n  - permitted users can transition booking status\n\n### Booking lifecycle statuses\n- `inquiry`\n- `hold`\n- `offer_sent`\n- `accepted`\n- `contracted`\n- `canceled`\n- `completed`\n\n### State transition rules\n- `inquiry -\u003e hold|offer_sent|canceled`\n- `hold -\u003e offer_sent|canceled`\n- `offer_sent -\u003e accepted|canceled`\n- `accepted -\u003e contracted|canceled`\n- `contracted -\u003e completed|canceled`\n- `completed` and `canceled` are terminal for non-admin users\n- non-admin users can only mark `completed` on/after event date\n\n### Booking workflow API\nUnder `/api/bookings/...`:\n- `GET opportunities`\n- `POST opportunities`\n- `GET opportunities/{id}`\n- `POST|PUT opportunities/{id}/status`\n- `POST opportunities/{id}/inquiries`\n- `GET mine`\n- `GET requests`\n- `GET {id}`\n- `POST|PUT {id}/transition`\n- `POST {id}/notes`\n\nLegacy compatibility routes kept:\n- `POST interest`\n- `GET interests`\n\n## Ticketing MVP Setup Notes\n\n### Database config\nUse environment variables (single switch point):\n\n- `PB_DB_DRIVER=sqlite|mysql`\n- `PB_DB_PATH=/absolute/path/to/booking.db` (sqlite)\n- `PB_DB_HOST`, `PB_DB_PORT`, `PB_DB_NAME`, `PB_DB_USER`, `PB_DB_PASS`, `PB_DB_CHARSET` (mysql)\n- `PB_DB_BOOTSTRAP_DEBUG=1` enables verbose migration/bootstrap logging\n- `PB_DB_DEBUG=1` enables DB connection logging (secrets redacted)\n\nIf no variables are set, app defaults to SQLite at `data/booking.db`.\n\n### Environment files\n- App now loads `.env` and `.env.local` from project root automatically.\n- Existing process-level env vars still take precedence.\n- See `.env.example` for available payment/provider settings.\n\n### Demo seed data\n- `PB_ENABLE_DEMO_SEED=1` to insert demo users/profiles on bootstrap.\n- `PB_DEMO_SEED_PASSWORD=...` to set demo account password.\n- Demo users are **not** seeded unless explicitly enabled.\n\n### Payment provider\n- `PB_PAYMENT_PROVIDER=demo|stripe|square` (preferred switch; default is `demo`)\n- `PB_PAYMENT_MODE=...` remains supported as a legacy alias.\n- `PB_PUBLIC_BASE_URL=https://your-domain.test` (recommended for hosted checkout redirect/webhook URLs)\n- `PB_STRIPE_SECRET_KEY=sk_test_...`\n- `PB_STRIPE_PUBLISHABLE_KEY=pk_test_...` (optional for future client-side use)\n- `PB_STRIPE_WEBHOOK_SECRET=whsec_...`\n- `PB_STRIPE_WEBHOOK_TOLERANCE=300` (optional, seconds)\n- `PB_STRIPE_API_BASE=https://api.stripe.com/v1` (optional override)\n- `PB_SQUARE_ACCESS_TOKEN=...`\n- `PB_SQUARE_APPLICATION_ID=...` (optional; useful for future client-side flows)\n- `PB_SQUARE_LOCATION_ID=...`\n- `PB_SQUARE_WEBHOOK_SIGNATURE_KEY=...`\n- `PB_SQUARE_WEBHOOK_URL=https://your-domain.test/square-webhook.php` (must match Square dashboard webhook URL)\n- `PB_SQUARE_API_BASE=https://connect.squareup.com` (optional override)\n- `PB_SQUARE_API_VERSION=...` (optional; sends `Square-Version` header when set)\n- `PB_APP_KEY=...` recommended for receipt token HMAC and other app-level signing.\n  - If `PB_APP_KEY` is not set, app auto-generates a local key file at `data/.app_key` (or `PB_APP_KEY_FILE` path).\n\n### Session and auth hardening\n- `PB_SESSION_NAME=panicbooking_sid` (optional)\n- `PB_SESSION_IDLE_TIMEOUT=7200` seconds\n- `PB_SESSION_ABSOLUTE_TIMEOUT=86400` seconds\n- `PB_SESSION_REGEN_INTERVAL=900` seconds\n- `PB_SESSION_SAMESITE=Lax` (`Lax|Strict|None`)\n- Login/signup forms and sensitive app/API writes now require CSRF tokens.\n- API returns generic `500` errors for unhandled exceptions and logs server-side details.\n\n### Password reset flow (minimal)\n- App pages:\n  - `/app/password-reset-request.php`\n  - `/app/password-reset.php?token=...`\n- API endpoints:\n  - `POST /api/auth/password-reset-request`\n  - `POST /api/auth/password-reset-confirm`\n- Env:\n  - `PB_PASSWORD_RESET_TTL_MINUTES=30`\n  - `PB_PASSWORD_RESET_THROTTLE_SECONDS=120`\n  - `PB_PASSWORD_RESET_DEBUG=1` (optional local debug mode to return reset URL in response)\n\n## Ticketing Flow\n\n### Venue/admin management\n- `/app/events.php`: list/manage ticketed events and summary counts.\n- `/app/event-edit.php?id=...`: create/edit an event.\n- `/app/event-tickets.php?id=...`: add/edit ticket types, pricing, inventory, sales windows, activation.\n\n### Public purchase\n- `/event.php?slug=\u003cevent-slug\u003e`\n- Buyer selects quantities and enters name/email.\n- In demo mode:\n  1. pending order is created\n  2. order is marked paid\n  3. inventory is decremented safely\n  4. one ticket row is created per admission\n  5. receipt redirects to `/order-success.php`\n- In stripe mode:\n  1. pending order is created\n  2. Stripe Checkout Session is created and buyer is redirected\n  3. buyer returns to `/checkout-success.php` or `/checkout-cancel.php`\n  4. webhook verification is the source of truth for setting paid/failed/canceled/refunded\n  5. tickets are issued only when webhook-confirmed payment calls finalization\n- In square mode:\n  1. pending order is created\n  2. Square Payment Link is created and buyer is redirected\n  3. buyer returns to `/checkout-success.php`\n  4. webhook verification is the source of truth for setting paid/failed/canceled/refunded\n  5. tickets are issued only when webhook-confirmed payment calls finalization\n\n### Ticket and QR\n- `/order-success.php?order_id=...\u0026receipt=...` renders one QR per ticket.\n- QR payload is a validation URL containing an opaque `qr_token`.\n- Each ticket also has a manual fallback `short_code`.\n\n### Door check-in\n- `/app/checkin.php?event_id=...`\n- Camera scan (BarcodeDetector API) + manual token/code entry + search fallback.\n- API validates ticket/event/order/status and prevents duplicate check-ins.\n- Every check-in attempt is written to `checkins`.\n\n### Stripe webhook\n- Primary endpoint: `/stripe-webhook.php`\n- API endpoint: `POST /api/payments/stripe_webhook`\n- Signature is verified using `PB_STRIPE_WEBHOOK_SECRET`.\n- Duplicate delivery is handled idempotently by `payment_webhook_events` (`provider + event_id` unique).\n\n### Square webhook\n- Primary endpoint: `/square-webhook.php`\n- API endpoint: `POST /api/payments/square_webhook`\n- Signature is verified using `x-square-hmacsha256-signature` + `PB_SQUARE_WEBHOOK_SIGNATURE_KEY`.\n- `PB_SQUARE_WEBHOOK_URL` must match the exact webhook URL registered with Square.\n- Duplicate delivery is handled idempotently by `payment_webhook_events` (`provider + event_id` unique).\n\n## API Actions Added\n\nUnder `/api/ticketing/...`:\n\n- `POST create_event`\n- `POST|PUT update_event`\n- `POST create_ticket_type`\n- `POST|PUT update_ticket_type`\n- `POST create_order`\n- `POST mark_order_paid`\n- `POST validate_ticket`\n- `POST check_in_ticket`\n- `GET search_ticket_by_code`\n\nUnder `/api/payments/...`:\n- `POST stripe_webhook`\n- `POST square_webhook`\n\n## Security and Robustness\n\n- Prepared statements used for ticketing DB writes/reads.\n- CSRF token checks added for ticketing app forms and ticketing API mutating endpoints.\n- Permission checks enforce venue/admin ownership for event/ticket/check-in operations.\n- Inventory checks prevent overselling at payment-finalization time.\n- Conditional ticket update prevents duplicate check-ins.\n- Stripe/Square webhook signatures are verified before processing.\n- Webhook events are logged with minimal metadata and deduplicated by `(provider, event_id)`.\n- Ticket issuance remains inside a single transactional finalize path to prevent duplicate tickets.\n- Maintenance/import/Event Sync scripts are CLI-first; web execution is disabled by default.\n  - To explicitly allow web execution: `PB_ALLOW_WEB_MAINTENANCE=1` and `PB_MAINTENANCE_TOKEN=...`\n\n## Schema / Migration\n\n- Runtime DB bootstrap is centralized in:\n  - `config/database.php` (driver/env config + PDO options + singleton connection)\n  - `lib/db_bootstrap.php` (migration runner + legacy compatibility patches)\n  - `api/includes/db.php` (connect, bootstrap, optional demo seed)\n- Driver-specific bootstrap SQL lives in:\n  - `db/migrations/sqlite/20260412_000_bootstrap.sql`\n  - `db/migrations/mysql/20260412_000_bootstrap.sql`\n- Applied migrations are tracked in `schema_migrations`.\n- Existing historical SQL references remain in `db/migrations/*.sql` for context.\n\nNew tables:\n\n- `events`\n- `ticket_types`\n- `orders`\n- `order_items`\n- `tickets`\n- `checkins`\n- `payment_webhook_events`\n- `opportunities`\n- `booking_requests`\n- `bookings`\n- `booking_status_history`\n- `booking_notes`\n\n## Assumptions\n\n- `users.id` for a `type='venue'` user is used as `events.venue_id`.\n- `users.id` for a `type='venue'` user is also used as `opportunities.venue_user_id`.\n- each inquiry creates one `booking_request` + one `booking` at `inquiry` status.\n- a band cannot create more than one active inquiry for the same opportunity.\n- only one booking per opportunity can be in `accepted|contracted|completed` at a time.\n- admin users may override normal status transitions.\n- Ticketing is general admission only (no seats/transfers/resale/memberships).\n- Demo mode treats purchase submission as successful payment.\n- JSON profile data remains stored in `profiles.data` (text/longtext), and SQL JSON extraction is handled via driver-aware helpers.\n\n## SQLite/MySQL Setup Notes\n\n1. Set `PB_DB_DRIVER=sqlite` (default) or `PB_DB_DRIVER=mysql`.\n2. For SQLite:\n   - Set `PB_DB_PATH=/absolute/path/to/booking.db` (optional; defaults to `data/booking.db`).\n3. For MySQL:\n   - Set `PB_DB_HOST`, `PB_DB_PORT`, `PB_DB_NAME`, `PB_DB_USER`, `PB_DB_PASS`, and optionally `PB_DB_CHARSET`.\n4. First request/script run auto-applies bootstrap migrations for the selected driver.\n5. Maintenance scripts now use the same central DB bootstrap path as app/API.\n\n## DB Troubleshooting\n\n- Run the built-in bootstrap diagnostic:\n  - `php scripts/debug_db_bootstrap.php`\n- Force one-shot bootstrap:\n  - `php -r 'require __DIR__ . \"/api/includes/db.php\"; echo \"bootstrap ok\\n\";'`\n- Set `PB_DB_BOOTSTRAP_DEBUG=1` to emit per-migration logs via `panicLog`.\n\n## SQLite To MySQL Data Migration\n\n- Copy rows from SQLite into configured MySQL:\n  - `php scripts/migrate_sqlite_to_mysql.php --sqlite=/absolute/path/to/booking.db`\n- Default behavior clears destination tables first.\n- Use `--append` to keep destination rows and only insert non-conflicting rows.\n- Script expects MySQL env vars (`PB_DB_HOST`, `PB_DB_PORT`, `PB_DB_NAME`, `PB_DB_USER`, `PB_DB_PASS`, `PB_DB_CHARSET`) to be set.\n\n## Migration Caveats / TODOs\n\n- MySQL bootstrap targets modern MySQL 8+ behavior (including `CHECK` constraints support).\n- Profiles and other JSON-like blobs are still text-backed; a future migration can promote selected fields to native JSON columns.\n- Some maintenance scripts still assume scraped-event uniqueness on `(event_date, venue_name, bands)`; keep that unique key in MySQL.\n- Historical one-off SQL files in `db/migrations/*.sql` are not yet wired into the new runner.\n\n## Local Stripe CLI Testing\n\n1. Set env vars:\n   - `PB_PAYMENT_PROVIDER=stripe`\n   - `PB_PUBLIC_BASE_URL=http://localhost:8000` (or your local URL)\n   - `PB_STRIPE_SECRET_KEY=sk_test_...`\n2. Start PHP server (example): `php -S localhost:8000`\n3. Start Stripe listener and capture webhook secret:\n   - `stripe listen --forward-to http://localhost:8000/stripe-webhook.php`\n4. Set emitted secret in app env:\n   - `PB_STRIPE_WEBHOOK_SECRET=whsec_...`\n5. Trigger test events if needed:\n   - `stripe trigger checkout.session.completed`\n   - `stripe trigger payment_intent.payment_failed`\n   - `stripe trigger charge.refunded`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisrobison%2Fpanicbooking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchrisrobison%2Fpanicbooking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisrobison%2Fpanicbooking/lists"}