name: CI

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:
  schedule:
    # Monday 03:00 UTC — advisories land whether or not anyone opened a pull request.
    - cron: "0 3 * * 1"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  api:
    name: API — lint, types, tests
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: rsvp
          POSTGRES_PASSWORD: rsvp
          POSTGRES_DB: rsvp_test
        ports: ["5432:5432"]
        options: >-
          --health-cmd "pg_isready -U rsvp"
          --health-interval 5s
          --health-timeout 5s
          --health-retries 10
    env:
      DATABASE_URL: postgresql+asyncpg://rsvp:rsvp@localhost:5432/rsvp_test
    defaults:
      run:
        working-directory: api
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true
      - run: uv python install 3.12
      - run: uv sync --all-groups
      - name: Lint
        run: uv run ruff check .
      - name: Format check
        run: uv run ruff format --check .
      - name: Type check
        run: uv run mypy app
      - name: Migrations apply cleanly
        run: uv run alembic upgrade head
      - name: Tests
        run: uv run pytest -q

  web:
    name: Web — lint, types, build
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: web
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: web/package-lock.json
      - run: npm ci || npm install
      - run: npm run typecheck
      - run: npm run lint
      - run: npm run build

  client-freshness:
    name: Generated client is up to date
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Export the schema the API actually serves
        working-directory: api
        run: |
          uv sync --all-groups
          uv run python -m scripts.export_openapi > /tmp/openapi.json
      - name: Regenerate types and compare
        run: |
          npx --yes openapi-typescript /tmp/openapi.json -o /tmp/schema.d.ts
          if ! diff -q /tmp/schema.d.ts web/lib/api/schema.d.ts >/dev/null; then
            echo "::error::web/lib/api/schema.d.ts is stale. Run 'make client' and commit the result."
            diff -u web/lib/api/schema.d.ts /tmp/schema.d.ts || true
            exit 1
          fi

  audit:
    name: Dependency audit (task 8.7)
    runs-on: ubuntu-latest
    # Advisory on a pull request, blocking on the weekly run. A CVE published overnight is
    # a real signal, but turning every unrelated PR red on it trains everyone to ignore the
    # red X — which is worse than not running the check at all. The scheduled run is the
    # one nobody is waiting on, so it is allowed to fail loudly.
    continue-on-error: ${{ github.event_name != 'schedule' }}
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - name: Python advisories
        working-directory: api
        run: |
          uv sync --all-groups
          uv run pip-audit --strict --progress-spinner off
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: web/package-lock.json
      - name: Node advisories
        working-directory: web
        run: |
          npm ci || npm install
          npm audit --audit-level=high
