Skip to content

tylerlangties/srq-hpn

Repository files navigation

SRQ Happenings (srq-hpn)

A local events platform for Sarasota, FL, focused on aggregating, organizing, and presenting events in a clean, user-friendly way.

This repository is a monorepo containing:

  • a Next.js + React + TypeScript frontend
  • a FastAPI + Python backend
  • a PostgreSQL database (via Docker)
  • Celery for background tasks (event ingestion, scrapers)
  • shared tooling for linting, formatting, and development

🧱 Tech Stack

Frontend

  • Next.js 16 (React 19 + TypeScript)
  • Tailwind CSS v4
  • Decap CMS (headless CMS for articles)
  • Runs on http://localhost:3000

Backend

  • FastAPI
  • SQLAlchemy 2.0 (ORM)
  • Alembic (database migrations)
  • Celery (background tasks)
  • Redis (message broker for Celery)
  • PostgreSQL 16
  • Runs on http://localhost:8000

Infrastructure

  • Caddy (reverse proxy, automatic HTTPS in production)
  • Docker Compose (database, full dev stack, production)
  • Flower (Celery monitoring at http://localhost:5555 in dev)

Tooling

  • pnpm (monorepo package manager)
  • Ruff (Python linting & formatting)
  • pre-commit (git hooks)

πŸ“ Repository Structure

srq-hpn/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ web/            # Next.js frontend
β”‚   └── api/            # FastAPI backend
β”œβ”€β”€ caddy/              # Caddy reverse proxy config (production)
β”œβ”€β”€ db/                 # PostgreSQL init scripts
β”œβ”€β”€ docs/               # Documentation (database, Celery, etc.)
β”œβ”€β”€ compose.yml         # Production stack (Caddy, Web, API, DB, Redis, Celery)
β”œβ”€β”€ compose.dev.yml     # Development stack (+ Redis, Celery, Flower)
β”œβ”€β”€ compose.db.yml      # Database only (for local dev without Docker)
β”œβ”€β”€ pnpm-workspace.yaml
β”œβ”€β”€ package.json        # Root scripts
└── README.md

πŸš€ Quick Start

Option A: Full Docker (recommended)

Requires .env.local with database credentials. See docs/database-guide.md for details.

pnpm docker:dev

This starts: DB, Redis, API, Celery worker, Celery beat, Flower, and Web.

API Routing and Namespaces

The app uses a reverse proxy (Caddy) in Docker environments. Route ownership is:

  • FastAPI public API: /api/* (for example /api/events/range)
  • Next.js route handlers: /content-api/* (for example /content-api/articles)

Why this split exists:

  • Both Next.js and FastAPI can define /api/* routes.
  • Reserving /api/* for FastAPI avoids conflicts and keeps backend endpoints conventional.
  • Internal Next.js handlers use /content-api/* to stay explicit and collision-free.

In Docker dev:

  • Open the app at http://localhost:3000 (served through Caddy).
  • Browser calls to /api/* are proxied to FastAPI.
  • Browser calls to /content-api/* are handled by Next.js.
  • http://localhost:8000 is still exposed for direct API debugging.

Option B: Local (DB + API + Web separately)

# 1. Start database
pnpm db:up

# 2. Create API venv and run migrations (see apps/api/)
pnpm dev:api

# 3. In another terminal: frontend
pnpm dev:web

Admin auth bootstrap

Admin routes are protected with role-based auth (admin, user) using a first-party httpOnly cookie.

Set these API env vars in your runtime env:

  • Docker dev (compose.dev.yml): ./.env.local

  • API-only local runs (pnpm dev:api): apps/api/.env (or shell env)

  • JWT_SECRET (required)

  • JWT_ALGORITHM (optional, defaults to HS256)

  • JWT_EXPIRES_MINUTES (optional, defaults to 60)

  • COOKIE_SECURE (optional, true in production)

  • COOKIE_SAMESITE (optional, defaults to lax)

Production expects a same-origin deployment (frontend + /api behind a reverse proxy). Keep COOKIE_SAMESITE=lax and COOKIE_SECURE=true.

Create your first admin user:

python apps/api/scripts/create_admin_user.py --email admin@example.com --password "change-me-now"

🚒 Deployment (Production)

For full production setup, use docs/database-guide.md as the source of truth. This section gives the minimum launch-critical steps.

1) Set production environment variables

In your production env file or secret manager, set at least:

DOMAIN=srqhappenings.com
NEXT_PUBLIC_API_BASE_URL=
NEXT_PUBLIC_SITE_URL=https://srqhappenings.com
NEXT_PUBLIC_UMAMI_WEBSITE_ID=<umami_website_id>
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://cloud.umami.is/script.js
NEXT_PUBLIC_ANALYTICS_DEBUG=false
POSTGRES_PASSWORD=<secure-value>
POSTGRES_APP_PASSWORD=<secure-value>

Why NEXT_PUBLIC_SITE_URL matters:

  • It powers canonical URLs, sitemap URLs, robots host/sitemap references, and JSON-LD absolute URLs.
  • If this is wrong, SEO signals can point to the wrong origin.

Why Umami vars matter:

  • NEXT_PUBLIC_UMAMI_WEBSITE_ID enables client-side analytics capture in the web app.
  • NEXT_PUBLIC_UMAMI_SCRIPT_URL points to your Umami tracking script host (Cloud default shown above).
  • If NEXT_PUBLIC_UMAMI_WEBSITE_ID is missing, analytics capture is safely disabled.
  • Set NEXT_PUBLIC_ANALYTICS_DEBUG=true locally to print each tracked event to the browser console.

2) Deploy services

./scripts/deploy-prod.sh .env.production main

This script performs the production-safe sequence automatically:

  • Pull latest branch
  • Validate compose config
  • Build images
  • Start db + redis and wait for readiness
  • Run alembic upgrade head
  • Start full stack (caddy, web, api, db, redis, celery-worker, celery-beat)
  • Run post-deploy health checks

2.1) First-time production setup (Droplet)

cp .env.production.example .env.production
# edit .env.production with real secrets and domain values
chmod +x scripts/deploy-prod.sh scripts/check-prod-health.sh

2.2) Health check only

./scripts/check-prod-health.sh .env.production

3) Verify after deploy

curl -I https://srqhappenings.com
curl https://srqhappenings.com/api/health
curl https://srqhappenings.com/robots.txt
curl https://srqhappenings.com/sitemap.xml

Then complete the SEO launch checks in docs/seo-implementation-checklist.md (Search Console property + sitemap submission + URL inspection).


Analytics (Umami)

Step 4 launch analytics is now wired in via the Umami browser script in the web app.

Tracked launch events:

  • event_viewed
  • event_link_clicked
  • featured_event_impression
  • featured_event_clicked

Current instrumentation points:

  • Homepage featured card impression and click
  • Event detail page view
  • Event detail external link click

Common tracked properties:

  • event_id, event_slug, event_title
  • source, source_page, source_component
  • venue_id, venue_slug, venue_name (only when present)

Quick verify:

  1. Set NEXT_PUBLIC_UMAMI_WEBSITE_ID and optionally NEXT_PUBLIC_UMAMI_SCRIPT_URL.
  2. Open homepage and click featured event card.
  3. Open an event detail page and click the outbound event link.
  4. Confirm those events appear in your Umami event dashboard.

Local-only debugging:

  • Set NEXT_PUBLIC_ANALYTICS_DEBUG=true in local env.
  • Keep NEXT_PUBLIC_UMAMI_WEBSITE_ID unset to avoid sending events while still seeing console logs.

πŸ“œ Scripts

Command Description
pnpm dev:web Start Next.js dev server
pnpm dev:cms Start Next.js + Decap CMS
pnpm dev:api Start FastAPI with uvicorn
pnpm lint:web Lint frontend
pnpm lint:api Lint API (Ruff)
pnpm format:api Format API (Ruff)
pnpm db:up Start Postgres (compose.db.yml)
pnpm db:down Stop Postgres
pnpm docker:dev Start full dev stack
pnpm docker:dev:build Build and start dev stack
pnpm docker:dev:down Stop dev stack
pnpm docker:migrate Run migrations (dev container)

Adding a Source

When adding a new event source, use a stable slug (not numeric IDs) so Celery scheduling works the same across local and production.

  1. Add the source row in the database with a unique slug in sources.
  2. Add a slug constant in apps/api/app/constants/sources.py.
  3. Wire the source into task scheduling in apps/api/app/celery_app.py using source_slug in task kwargs.
  4. Ensure the corresponding task in apps/api/app/tasks.py resolves via _resolve_source(...) (slug-first, source_id fallback).
  5. If the source has a new collector module, implement it under apps/api/app/collectors/ and register any required task wrapper in apps/api/app/tasks.py.
  6. Run migrations and restart workers/beat:
docker compose --env-file .env.production -f compose.yml exec api alembic upgrade head
docker compose --env-file .env.production -f compose.yml restart celery-worker celery-beat

Quick verification query:

SELECT id, slug, name, type FROM sources ORDER BY id;

Manual task call example (slug-based):

docker compose --env-file .env.production -f compose.yml exec celery-worker \
  celery -A app.celery_app call app.tasks.collect_asolorep \
  --kwargs='{"source_slug":"asolorep","delay":3.0}'

πŸ“š Documentation

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors