fix(edge-fn): replace getClaims with adminClient.auth.getUser(token)
fix(edge-fn): use user.id instead of claims.sub; fixes 500s and false cert_required fix(migrations): drop broad reservations SELECT policy; add reservation_slots view with security_invoker=false fix(tests): correct weekSlot() keys from start/end to start_time/end_time fix(tests): spread overlap test slots across separate ISO weeks fix(tests): update e2e assertion to match actual authenticated home text fix(app): hide IonMenu before user is authenticated feat(dx): add test:all script running unit, integration, and e2e in sequence docs(claude-md): document SELinux fix, Edge Function auth pattern, security_invoker behaviour
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Session Handoff: Auth Tests + Backend Ansible Plan
|
||||
**Date:** 2026-04-12
|
||||
**Session Focus:** Fix broken auth unit tests; plan bab-backend-ansible rewrite; update EE dependencies
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
1. **Deleted `tests/unit/auth-callback.test.ts`** — low-value component test per agreed test strategy; E2E covers this
|
||||
2. **Extracted pure auth logic** → `app/utils/auth.ts` (`checkAuthRedirect(userValue, path): string | null`)
|
||||
3. **Simplified `app/middleware/auth.ts`** — delegates to `checkAuthRedirect`; Nuxt-specific code is now minimal
|
||||
4. **Rewrote `tests/unit/auth-middleware.test.ts`** — tests `checkAuthRedirect` directly, no mocking, node env; **7/7 passing**
|
||||
5. **Documented Nuxt testing lessons** → memory `feedback_nuxt_testing.md`
|
||||
6. **Wrote backend rewrite plan** → `docs/summaries/plan-bab-backend-ansible-rewrite.md`
|
||||
7. **Updated `ee-demo` EE** with: `amazon.aws` collection, `boto3`/`botocore`, `postgresql` RPM, `supabase` CLI install via `SUPABASE_VERSION` build arg; updated `build.sh` (user also added `--redhat` flag)
|
||||
8. **Resolved all plan open questions** except one (see below)
|
||||
|
||||
## Exact State of Work in Progress
|
||||
|
||||
- `tests/integration/auth-session.test.ts` — written last session, not yet run (requires local Supabase + `SUPABASE_SERVICE_ROLE_KEY`)
|
||||
- Playwright E2E — not yet set up; `tests/e2e/` directory does not exist
|
||||
- `.gitea/workflows/build.yaml` — not yet created
|
||||
|
||||
## Key Technical Decisions This Session
|
||||
|
||||
- **Extract-don't-mock pattern**: Nuxt auto-imports compile to concrete dist paths; `vi.mock('#imports')` doesn't intercept them. Pattern: extract logic to `app/utils/` with no Nuxt deps, test directly. CONFIRMED.
|
||||
- **Skip unit tests for simple page components**: Ionic component registration + Supabase init failures make `mountSuspended` too brittle. Cover with Playwright E2E instead. CONFIRMED.
|
||||
|
||||
## Files Created or Modified
|
||||
|
||||
| File Path | Action | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `app/utils/auth.ts` | Created | `checkAuthRedirect` pure function; `PUBLIC_ROUTES` constant |
|
||||
| `app/middleware/auth.ts` | Modified | Now delegates to `checkAuthRedirect` |
|
||||
| `tests/unit/auth-middleware.test.ts` | Rewritten | Tests pure function; 7/7 passing |
|
||||
| `tests/unit/auth-callback.test.ts` | Deleted | Low-value component test |
|
||||
| `docs/summaries/plan-bab-backend-ansible-rewrite.md` | Created | Full rewrite plan with implementation sequence |
|
||||
| `docs/context/sdlc-architecture.md` | Updated | Dev URL, nginx webroot, Gitea URL, artifact token path |
|
||||
| `/home/ptoal/Dev/ExecutionEnvironments/ee-demo/execution-environment.yml` | Modified | Added `postgresql` RPM, `supabase` CLI build step |
|
||||
| `/home/ptoal/Dev/ExecutionEnvironments/ee-demo/requirements.yml` | Modified | Added `amazon.aws >= 9.0.0` |
|
||||
| `/home/ptoal/Dev/ExecutionEnvironments/ee-demo/requirements.txt` | Modified | Added `boto3`, `botocore` |
|
||||
| `/home/ptoal/Dev/ExecutionEnvironments/ee-demo/build.sh` | Modified | Added `SUPABASE_VERSION` guard; user added `--redhat` flag |
|
||||
|
||||
## What the NEXT Session Should Do
|
||||
|
||||
**If continuing oysqn.app frontend:**
|
||||
1. Set up Playwright — `yarn add -D @playwright/test`, create `tests/e2e/`, write login flow E2E test
|
||||
2. Create `.gitea/workflows/build.yaml` following bab-app pattern (semantic-release + artifact)
|
||||
|
||||
**If starting bab-backend-ansible rewrite:**
|
||||
1. Read `docs/summaries/plan-bab-backend-ansible-rewrite.md` — full scope and implementation sequence
|
||||
2. Start with step 3: `sync_gitea_secrets.yml` (lowest risk, standalone)
|
||||
3. Note: work in `/home/ptoal/Dev/Projects/bab-backend-ansible`, not oysqn.app
|
||||
|
||||
## Open Questions Requiring User Input
|
||||
|
||||
- [ ] **`kv/oys/dev/supabase/postgres_url`** — not in Vault; needed before `migrate_supabase.yml` can run rollback SQL via `psql` against dev. Add to Vault before first dev migration run.
|
||||
|
||||
## Confirmed Infrastructure Values
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Dev URL | `https://bab.toal.ca` |
|
||||
| nginx webroot (bab1) | `/usr/share/nginx/html/` |
|
||||
| Gitea URL | `https://gitea.toal.ca/` |
|
||||
| Gitea artifact token | `kv/oys/bab_gitea` |
|
||||
| Backup path (bab1) | `/var/backups/oysqn/` (assumed — confirm before first prod backup) |
|
||||
|
||||
## Files to Load Next Session
|
||||
|
||||
- **Frontend session:** `docs/summaries/handoff-2026-04-12-splash-and-login.md` (prior UI work context)
|
||||
- **Backend session:** `docs/summaries/plan-bab-backend-ansible-rewrite.md`; `docs/context/sdlc-architecture.md`
|
||||
@@ -0,0 +1,66 @@
|
||||
# Session Handoff: Playwright E2E Setup
|
||||
**Date:** 2026-04-19
|
||||
**Session Focus:** Set up Playwright E2E testing; write and pass auth flow tests
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
1. **Installed `@playwright/test` 1.59.1** + Chromium headless shell binary
|
||||
2. **Created `playwright.config.ts`** — Pixel 5 viewport, `reuseExistingServer` for local dev, `github` reporter in CI, 30s timeout, 1 worker (serial)
|
||||
3. **Created `tests/e2e/helpers/mailpit.ts`** — `deleteAllMail()` and `getMagicLink(email)` against Mailpit at `http://127.0.0.1:54324`
|
||||
4. **Created `tests/e2e/auth.spec.ts`** — 2 tests: full magic link flow + protected route redirect
|
||||
5. **Fixed `supabase/config.toml`** — `site_url` changed from `http://127.0.0.1:3000` to `http://localhost:3000`; `additional_redirect_urls` updated to include `http://localhost:3000/auth/callback` and `http://127.0.0.1:3000/auth/callback`
|
||||
6. **Added scripts to `package.json`**: `test:e2e`, `test:e2e:ui`, `test:e2e:headed`
|
||||
7. **Both tests pass: 2/2**
|
||||
|
||||
## Key Debugging Discoveries
|
||||
|
||||
| Problem | Root Cause | Fix |
|
||||
|---------|-----------|-----|
|
||||
| `getByRole('button', { name: 'Log In' })` not found | `IonButton` with `router-link` renders as `<a>` (role=`link`) | Changed to `getByRole('link', { name: 'Log In' })` |
|
||||
| `getByLabel('Email address')` not found | `IonLabel` is not associated via `for`/`id` with `IonInput` | Changed to `getByPlaceholder('you@example.com')` |
|
||||
| `page.goto(magicLink)` → `ERR_CONNECTION_REFUSED` to `127.0.0.1:54321` | Playwright's Chromium headless shell cannot reach Supabase local auth server directly | Use Node.js `fetch(magicLink, { redirect: 'manual' })` to follow redirect server-side; navigate browser to the resulting app callback URL |
|
||||
| Callback URL was `/?code=` instead of `/auth/callback?code=` | `emailRedirectTo` not whitelisted in `supabase/config.toml`; Supabase fell back to `site_url` | Fixed `config.toml` redirect URLs; required `supabase stop && supabase start` |
|
||||
|
||||
## Ionic-Specific Playwright Patterns (confirmed working)
|
||||
|
||||
- `IonButton` with `router-link` → use `getByRole('link', { name: '...' })`
|
||||
- `IonButton` without `router-link` → use `getByRole('button', { name: '...' })`
|
||||
- `IonInput` → use `getByPlaceholder(...)` (label association not standard HTML)
|
||||
- Never navigate browser directly to Supabase local auth URL — follow redirect server-side first
|
||||
|
||||
## Files Created or Modified
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `playwright.config.ts` | Created |
|
||||
| `tests/e2e/helpers/mailpit.ts` | Created |
|
||||
| `tests/e2e/auth.spec.ts` | Created |
|
||||
| `supabase/config.toml` | Modified — site_url and redirect URLs |
|
||||
| `package.json` | Modified — added test:e2e scripts |
|
||||
|
||||
## What the NEXT Session Should Do
|
||||
|
||||
**Option A — CI pipeline:**
|
||||
1. Create `.gitea/workflows/build.yaml` — unit tests + build + E2E (read bab-app pipeline as reference)
|
||||
2. Note: E2E in CI requires local Supabase running in the pipeline — confirm if feasible or skip E2E in CI for now
|
||||
|
||||
**Option B — Home page content:**
|
||||
1. Implement authenticated home content in `app/pages/index.vue` (currently "Welcome to OYS Borrow a Boat" placeholder)
|
||||
2. Likely a boat list or dashboard — check `docs/planning/` for persona requirements
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] Should E2E tests run in CI (requires Supabase in pipeline) or local-only? — OPEN
|
||||
- [ ] What is the authenticated home content? Boat list, dashboard, or something else? — check planning docs
|
||||
|
||||
## Dev Environment Reference
|
||||
|
||||
```bash
|
||||
# Required before running E2E:
|
||||
DOCKER_HOST=unix:///run/user/1000/podman/podman.sock npx supabase start
|
||||
|
||||
# Run E2E:
|
||||
yarn test:e2e # headless
|
||||
yarn test:e2e:headed # visible browser
|
||||
yarn test:e2e:ui # Playwright UI mode
|
||||
```
|
||||
73
docs/archive/handoffs/handoff-2026-04-20-home-page.md
Normal file
73
docs/archive/handoffs/handoff-2026-04-20-home-page.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Session Handoff: Home Page Content + E2E Fixes
|
||||
**Date:** 2026-04-20
|
||||
**Session Duration:** ~1.5 hours
|
||||
**Session Focus:** Build authenticated home page; fix E2E test issues from prior session
|
||||
**Context Usage at Handoff:** Low
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
1. **Playwright E2E: fixed selector issues** — `IonButton router-link` renders as role=`link` not `button`; `IonInput` label not associated via `for`/`id`, use `getByPlaceholder` instead
|
||||
2. **Playwright E2E: fixed magic link redirect** — `supabase/config.toml` had wrong `site_url` (`127.0.0.1` → `localhost`) and missing `/auth/callback` in `additional_redirect_urls`; also added server-side redirect follow via `fetch(magicLink, { redirect: 'manual' })` to avoid browser connecting to Supabase local port
|
||||
3. **E2E tests passing: 2/2** — splash→login→magic link→home flow + protected route redirect
|
||||
4. **Home page built** → `app/pages/index.vue` — Welcome heading, upcoming reservations card, Create Reservation button, inline PrimeVue DatePicker calendar
|
||||
5. **WeatherWidget component created** → `app/components/WeatherWidget.vue` — Open-Meteo (free, no key), shows temp/conditions/wind in knots; 10-minute `localStorage` cache to avoid API overload
|
||||
6. **Marina coordinates set**: `43.4412629, -79.6696725` (Oakville/Bronte Harbour)
|
||||
7. **`IonSplitPane` added** to `app/app.vue` — sidebar open by default on desktop (≥992px); user reverted `auto-hide="false"` on `IonMenuButton` in `index.vue` — desktop sidebar toggle behavior is OPEN
|
||||
|
||||
## Exact State of Work in Progress
|
||||
|
||||
- Desktop sidebar toggle: `IonSplitPane` is in `app.vue` but `auto-hide="false"` was reverted from `index.vue`; sidebar opens on desktop but hamburger icon hides — revisit next session if needed
|
||||
- `/reservations/create` route: wired up as `router-link` on Create Reservation button but page does not exist yet
|
||||
|
||||
## Decisions Made This Session
|
||||
|
||||
- **Open-Meteo for weather** BECAUSE free, no API key, client-side CORS allowed, updates every 15 min — STATUS: confirmed
|
||||
- **10-minute `localStorage` cache for weather** BECAUSE Open-Meteo updates every 15 min; survives page refresh — STATUS: confirmed
|
||||
- **Server-side redirect follow for E2E magic link** BECAUSE Playwright Chromium headless shell cannot reach `127.0.0.1:54321` (Supabase local auth); Node.js `fetch` can — STATUS: confirmed
|
||||
|
||||
## Key Numbers Generated or Discovered This Session
|
||||
|
||||
- Weather cache TTL: 10 minutes (`600_000` ms)
|
||||
- Open-Meteo wind unit: knots (`wind_speed_unit=kn`)
|
||||
- Marina coordinates: `43.4412629, -79.6696725`
|
||||
- `IonSplitPane` desktop breakpoint: `lg` = 992px (Ionic default)
|
||||
- E2E tests: 2/2 passing
|
||||
|
||||
## Files Created or Modified
|
||||
|
||||
| File Path | Action | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `app/pages/index.vue` | Modified | Full home content: welcome, reservations card, create button, calendar |
|
||||
| `app/components/WeatherWidget.vue` | Created | Open-Meteo weather card with localStorage cache |
|
||||
| `app/app.vue` | Modified | Added `IonSplitPane` wrapping menu + router outlet |
|
||||
| `supabase/config.toml` | Modified | `site_url` → `http://localhost:3000`; added `/auth/callback` to `additional_redirect_urls` |
|
||||
| `tests/e2e/auth.spec.ts` | Modified | Fixed selectors; added server-side redirect follow for magic link |
|
||||
| `docs/summaries/handoff-2026-04-19-playwright-e2e-setup.md` | Created | Prior session handoff (Playwright setup) |
|
||||
|
||||
## What the NEXT Session Should Do
|
||||
|
||||
1. **Build `app/pages/reservations/create.vue`** — reservation creation form
|
||||
- Fields needed: boat selection, date/time (start + end), reason, passengers (member_ids)
|
||||
- Schema ref: `app/types/supabase.ts` → `reservations` table Insert type
|
||||
- On submit: `supabase.from('reservations').insert(...)` with `user_id = user.value.id`, default `status = 'pending'`
|
||||
- On success: navigate to `/` or a reservation detail page
|
||||
2. Read `app/types/supabase.ts` for boats + reservations schema before writing the form
|
||||
3. Read `app/stores/auth.ts` for `user` ref and `member` data (member_ids on the reservation)
|
||||
|
||||
## Open Questions Requiring User Input
|
||||
|
||||
- [ ] **Desktop hamburger behaviour** — `IonSplitPane` is active but user reverted `auto-hide="false"`; should the hamburger be visible on desktop to allow closing the sidebar, or hidden when sidebar is open? Impacts `IonMenuButton` props on every page
|
||||
- [ ] **Reservation form: boat selection** — should the user pick from all `booking_available = true` boats, or filtered by their certifications (`member.certifications` vs `boat.required_certs`)? Impacts query in create form
|
||||
- [ ] **Reservation form: time selection** — free-form time pickers, or constrained to `interval_templates`? Impacts form UI significantly
|
||||
- [ ] **After reservation created** — navigate to reservation detail, home page, or show confirmation inline?
|
||||
|
||||
## Assumptions That Need Validation
|
||||
|
||||
- ASSUMED: `supabase stop && supabase start` was run after `config.toml` change — E2E tests pass so this is confirmed
|
||||
- ASSUMED: PrimeVue `DatePicker` inline renders correctly in Ionic card on mobile — not manually tested in browser since session end
|
||||
|
||||
## Files to Load Next Session
|
||||
|
||||
- `app/types/supabase.ts` — boats + reservations schema for form fields
|
||||
- `app/stores/auth.ts` — user/member refs for form submission
|
||||
- `app/pages/index.vue` — reference for page structure pattern
|
||||
@@ -0,0 +1,101 @@
|
||||
# Session Handoff: Integration Test Debugging
|
||||
**Date:** 2026-04-20
|
||||
**Session Duration:** ~1.5 hours
|
||||
**Session Focus:** Debug and fix failing integration tests for auth and booking-constraints suites; fix Edge Function boot failure; fix members RLS infinite recursion
|
||||
**Context Usage at Handoff:** High
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
1. **Fixed `verifyOtp` API misuse in both test files** — `token` param is for 6-digit OTP codes; `token_hash` param is for hashed magic link tokens. Changed in both test files.
|
||||
- `tests/integration/auth-session.test.ts` (2 call sites)
|
||||
- `tests/integration/booking-constraints.test.ts` (1 call site in `getSessionToken`)
|
||||
|
||||
2. **Fixed `futureSlot()` key mismatch** in booking-constraints test — returned `{ start, end }` but `callCreateReservation` expected `{ start_time, end_time }`. Renamed keys in `futureSlot()` return value; updated 2 `directInsertReservation` call sites that used `slot.start`/`slot.end`.
|
||||
|
||||
3. **Added `@types/node` and test tsconfig** — Nuxt-generated tsconfig has `"types": []`, causing `process` to be unresolved in test files.
|
||||
- Added `@types/node` as dev dependency
|
||||
- Created `tests/tsconfig.json` extending root with `"types": ["node", "vitest/globals"]`
|
||||
- Added `typecheck: { tsconfig: './tests/tsconfig.json' }` to `vitest.integration.config.ts`
|
||||
|
||||
4. **Fixed members RLS infinite recursion (42P17)** — Policies "Admins can read all members" and "Admins can manage all members" queried `members` from within a `members` RLS policy, causing infinite recursion. Created new migration with two `SECURITY DEFINER` helper functions:
|
||||
- `public.current_user_role() → text`
|
||||
- `public.current_user_has_role(roles text[]) → boolean`
|
||||
- Replaced all inline `EXISTS (SELECT 1 FROM members WHERE ...)` admin checks in ALL tables with `public.current_user_has_role(...)` calls
|
||||
- Migration: `supabase/migrations/20260420170418_fix_members_rls_recursion.sql`
|
||||
|
||||
5. **Removed broken import from Edge Function** — `import "@supabase/functions-js/edge-runtime.d.ts"` is not a valid Deno runtime import. Removed it. Also tried `/// <reference types="..." />` which also failed.
|
||||
|
||||
6. **Switched Edge Function import from `jsr:` to `npm:`** — `jsr:@supabase/supabase-js@2` cache is lost on container restart in the podman-based local dev setup. Changed to `npm:@supabase/supabase-js@2`.
|
||||
|
||||
7. **Removed `entrypoint` from config.toml** — `entrypoint` field may not be supported/correctly handled by supabase-edge-runtime 1.73.3 (podman variant). Removed it; function now relies on default `index.ts` convention.
|
||||
|
||||
8. **Set `verify_jwt = false` in config.toml** — Edge runtime 1.73.3 `verifyHybridJWT` fails with `TypeError: Invalid Token or Protected Header formatting` on ES256 JWTs (new `sb_publishable_*` / `sb_secret_*` key format). Disabled edge-level JWT verification; the function handles auth itself via `userClient.auth.getUser()`.
|
||||
|
||||
## Exact State of Work in Progress
|
||||
|
||||
- **Edge Function still returns BOOT_ERROR** — Even after all the above changes, the edge runtime container still fails with `worker boot error: failed to bootstrap runtime: failed to determine entrypoint`. Investigation showed the function source files are NOT mounted inside the container (`find / -name index.ts` inside the container showed only Deno npm cache, no project files). Root cause: likely a podman volume mount issue specific to this host environment. Session was interrupted before resolving this.
|
||||
|
||||
- **Integration tests NOT yet passing** — All 15 booking-constraints tests still fail with 503 (Edge Function BOOT_ERROR). The auth-session tests (5 tests) status is unknown for this session.
|
||||
|
||||
## Decisions Made This Session
|
||||
|
||||
- **`verify_jwt = false` for create-reservation** BECAUSE: edge-runtime 1.73.3 with podman does not correctly verify ES256 JWTs from new `sb_publishable_*` key format; function verifies auth internally via `auth.getUser()` anyway — STATUS: confirmed
|
||||
- **`npm:` over `jsr:` for supabase-js import** BECAUSE: JSR cache is not persistent across podman container restarts in this local dev setup — STATUS: confirmed
|
||||
- **SECURITY DEFINER functions for role checks** BECAUSE: inline `EXISTS (SELECT 1 FROM members ...)` in members RLS policies causes infinite recursion — STATUS: confirmed, migration applied
|
||||
|
||||
## Key Numbers Generated or Discovered This Session
|
||||
|
||||
- supabase-js SDK version installed: **2.100.0**
|
||||
- Supabase CLI version: **2.92.1**
|
||||
- supabase-edge-runtime version: **1.73.3** (compatible with Deno v2.1.4)
|
||||
- Publishable key prefix: `sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH`
|
||||
- Secret key prefix: `sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz`
|
||||
- Access tokens are **ES256** (not HS256) — `eyJhbGciOiJFUzI1NiIs...`
|
||||
- New migration timestamp: **20260420170418**
|
||||
|
||||
## Files Created or Modified
|
||||
|
||||
| File Path | Action | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `tests/integration/auth-session.test.ts` | Modified | Fixed `verifyOtp` to use `token_hash` (2 call sites) |
|
||||
| `tests/integration/booking-constraints.test.ts` | Modified | Fixed `verifyOtp` to use `token_hash`; renamed `futureSlot` keys to `start_time`/`end_time`; removed unused `beforeEach` import |
|
||||
| `tests/tsconfig.json` | Created | Test-scoped tsconfig adding `node` + `vitest/globals` types |
|
||||
| `vitest.integration.config.ts` | Modified | Added `typecheck.tsconfig` pointing to `tests/tsconfig.json` |
|
||||
| `supabase/migrations/20260420170418_fix_members_rls_recursion.sql` | Created | SECURITY DEFINER helpers + fixed RLS policies for all tables |
|
||||
| `supabase/functions/create-reservation/index.ts` | Modified | Removed bad `import`; changed `jsr:` → `npm:` for supabase-js |
|
||||
| `supabase/functions/create-reservation/deno.json` | Modified | Updated import map to `npm:@supabase/supabase-js@^2` |
|
||||
| `supabase/config.toml` | Modified | `verify_jwt = false`; removed `entrypoint` field |
|
||||
|
||||
## What the NEXT Session Should Do
|
||||
|
||||
1. **Diagnose Edge Function mount issue first** — Run `podman inspect supabase_edge_runtime_oysqn.app` and check the `Mounts` array. The function source files (`supabase/functions/`) must be visible inside the container. If they are not mounted, this is likely a podman rootless socket or SELinux label issue on the host. Try `npx supabase stop && npx supabase start` to re-create the container with correct mounts.
|
||||
|
||||
2. **If supabase restart doesn't fix mounts** — Try `npx supabase functions serve` in a separate terminal (it spins up its own Deno process outside Docker, bypassing the container). Then run `yarn test:integration` — this tests the exact same function code via the same URL. If this works, the issue is confirmed as a container mount problem, not a code problem.
|
||||
|
||||
3. **After Edge Function works** — Run `yarn test:integration` and expect all 15 booking-constraints tests + all auth-session tests to pass. Fix any remaining failures (expected to be minor after the above fixes).
|
||||
|
||||
4. **Then**: build `/admin/reservations` view — list all upcoming reservations with boat/time/member/status; ability to confirm, cancel, or modify
|
||||
|
||||
5. **Then**: test full member booking flow in browser (`yarn dev`, create test intervals via admin, attempt booking as member, verify Edge Function error messages surface correctly)
|
||||
|
||||
## Open Questions Requiring User Input
|
||||
|
||||
- [ ] **Why are supabase function files not mounted in podman container?** — May require `--security-opt label=disable` or `:z` volume label for SELinux compatibility. Impacts all Edge Function development.
|
||||
- [ ] **Adjacent-session double-booking (Rule 6)** — exact logic needed (carried over from previous session)
|
||||
- [ ] **`useSupabaseClient` typing** — all admin pages use `as any` cast (carried over)
|
||||
- [ ] **Crew requirement (Rule 8)** — not implemented (carried over)
|
||||
- [ ] **PCOC / cert code strings** — values for `boats.required_certs` not defined (carried over)
|
||||
|
||||
## Assumptions That Need Validation
|
||||
|
||||
- ASSUMED: `npm:@supabase/supabase-js@2` is resolvable in the podman edge runtime container (npm cache may also be missing) — validate by getting the function to boot
|
||||
- ASSUMED: `verify_jwt = false` is acceptable for production deployment (auth is validated in function code) — validate with security review before prod
|
||||
- ASSUMED: `tests/tsconfig.json` approach for `@types/node` is the right fix and doesn't break other test types — validate by running full test suite
|
||||
- All assumptions from previous session still apply (half-open interval semantics, ISO week definition, 2026-01-05 period epoch)
|
||||
|
||||
## Files to Load Next Session
|
||||
|
||||
- `supabase/functions/create-reservation/index.ts` — to debug boot issue or extend
|
||||
- `supabase/config.toml` — to review/revert `verify_jwt` setting after boot is fixed
|
||||
- `supabase/migrations/20260420170418_fix_members_rls_recursion.sql` — if RLS issues resurface
|
||||
- `tests/integration/booking-constraints.test.ts` — to run and review after boot fix
|
||||
135
docs/archive/handoffs/handoff-2026-04-20-reservations.md
Normal file
135
docs/archive/handoffs/handoff-2026-04-20-reservations.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Session Handoff: Boat Reservation System
|
||||
**Date:** 2026-04-20
|
||||
**Session Duration:** ~3 hours
|
||||
**Session Focus:** Implement full boat reservation system: member booking flow, admin slot/template management, configurable booking rules via Edge Function + DB, and integration tests
|
||||
**Context Usage at Handoff:** High
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
1. **DB migration 1: Overlap constraint + cert trigger** → `supabase/migrations/20260420115333_reservations_overlap_and_cert_check.sql`
|
||||
- `btree_gist` extension enabled
|
||||
- `EXCLUDE USING gist (boat_id WITH =, tstzrange(start_time, end_time, '[)') WITH &&)` on `reservations`
|
||||
- `enforce_reservation_cert_check()` trigger (BEFORE INSERT) — raises exception if `member.certifications` does not contain all `boat.required_certs`
|
||||
- `member_has_cert_for_boat(uuid, uuid)` helper function
|
||||
|
||||
2. **DB migration 2: Booking rules + RBAC** → `supabase/migrations/20260420132336_booking_rules_and_rbac.sql`
|
||||
- `booking_config` table (key/value, admin-editable) — RLS: authenticated read, admin write
|
||||
- `holidays` table (date, name) — RLS same as above
|
||||
- `is_weekend_or_holiday(date)` DB helper function
|
||||
- Dropped `"Users can create own reservations"` RLS policy — direct INSERT blocked for `authenticated` role
|
||||
- `reservation_slots` view (security_invoker=true) — exposes only `id, boat_id, start_time, end_time, status`
|
||||
- Default config seeded: max_sessions_per_week=2, max_weekend_sessions_per_period=1, weekend_period_weeks=2, open_session_advance_hours=24
|
||||
- 2026 Ontario sailing-season holidays seeded (Victoria Day through Thanksgiving)
|
||||
|
||||
3. **Edge Function: create-reservation** → `supabase/functions/create-reservation/index.ts`
|
||||
- POST endpoint enforcing: cert check, boat availability, weekly pre-booking limit, weekend/holiday limit, open-session window bypass
|
||||
- Reads all rule parameters from `booking_config` at runtime (no redeployment needed for config changes)
|
||||
- Uses service_role key for insert (bypasses RLS); caller authenticated via JWT in Authorization header
|
||||
- DB overlap exclusion constraint + cert trigger remain as final safety net
|
||||
- Error response shape: `{ error: { code: string, message: string } }` with specific codes: `cert_required`, `slot_taken`, `booking_limit_weekly`, `booking_limit_weekend`, `boat_unavailable`
|
||||
|
||||
4. **Member reservation creation page** → `app/pages/reservations/create.vue`
|
||||
- Step 1: 14-day horizontal date strip → available slots grouped by boat (reads `intervals` + `reservation_slots` view)
|
||||
- Boats the member is not certified for shown with warning and slots disabled
|
||||
- Step 2: Confirm slot → reason (select) + comment (textarea) → calls Edge Function
|
||||
- Error handling maps all Edge Function error codes to user-friendly messages
|
||||
|
||||
5. **Admin: Manage Slots page** → `app/pages/admin/intervals.vue`
|
||||
- 14-day date strip + per-boat accordion showing intervals
|
||||
- Inline boat out-of-service toggle (IonToggle → updates `boats.booking_available`)
|
||||
- Apply template dropdown per boat (creates intervals from `interval_templates.time_tuples`)
|
||||
- Add Slot modal (boat select + time range inputs)
|
||||
- Delete slot (trash button per row)
|
||||
|
||||
6. **Admin: Interval Templates page** → `app/pages/admin/templates.vue`
|
||||
- Full CRUD: create/edit (modal with dynamic time-tuple list) / delete (IonAlert confirm)
|
||||
- Time tuples displayed as chips showing HH:MM–HH:MM
|
||||
|
||||
7. **Admin: Booking Rules config page** → `app/pages/admin/config.vue`
|
||||
- Editable numeric fields for all 4 config keys
|
||||
- Holidays list with add/delete
|
||||
- Save button with dirty tracking
|
||||
|
||||
8. **Nav wired up** → `app/app.vue`
|
||||
- Boatswain+Admin: "Manage Slots" → `/admin/intervals`, "Templates" → `/admin/templates`
|
||||
- Admin only: "Booking Rules" → `/admin/config`
|
||||
- New icons added: `layersOutline`, `settingsOutline`
|
||||
|
||||
9. **Types updated** → `app/types/supabase.ts`
|
||||
- Added `booking_config`, `holidays` to `Database.public.Tables`
|
||||
- Added `reservation_slots` to `Database.public.Views`
|
||||
|
||||
10. **Integration tests** → `tests/integration/booking-constraints.test.ts`
|
||||
- 6 describe blocks: overlap constraint, certification check, out-of-service, weekly limit, weekend limit, open-session window bypass, RBAC visibility
|
||||
- Each creates isolated test users/boats, cleans up in afterAll
|
||||
- Runs against local Supabase + Edge Functions via `yarn test:integration`
|
||||
- Requires: `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_KEY` env vars; Edge Functions served with `npx supabase functions serve`
|
||||
|
||||
## Exact State of Work in Progress
|
||||
|
||||
- Edge Function not yet tested end-to-end in browser (requires `npx supabase functions serve`)
|
||||
- Integration tests written but not yet run — need `supabase functions serve` running
|
||||
- Adjacent-session double-booking rule (Rule 6 from guidelines) NOT YET IMPLEMENTED — marked OPEN below
|
||||
|
||||
## Decisions Made This Session
|
||||
|
||||
- **Option C hybrid architecture** BECAUSE: rule logic in TypeScript (maintainable, no SQL migrations for rule changes), DB constraints for integrity invariants (overlap exclusion, cert check) that must be atomic and bypass-proof — STATUS: confirmed
|
||||
- **Direct INSERT locked for authenticated role** BECAUSE: members must go through Edge Function to enforce booking rules; admins retain direct access via "Admins can manage all reservations" policy — STATUS: confirmed
|
||||
- **`reservation_slots` view** BECAUSE: column-level RBAC — members can see that a slot is taken (boat/time/status) without seeing user_id, reason, comment, member_ids of other members' bookings — STATUS: confirmed
|
||||
- **`useSupabaseClient() as any`** in all new admin pages BECAUSE: `useSupabaseClient<Database>()` generic does not propagate through mutations in this Nuxt/@nuxtjs/supabase v1.5 setup; existing codebase uses cast pattern — STATUS: confirmed workaround
|
||||
- **Open-session bookings counted separately** BECAUSE: Rule 7 says "pre-book 2 sessions per week" — bookings made within `open_session_advance_hours` of start time bypass the weekly and weekend limits; pre-booking count inferred from `(start_time - created_at) > advance_hours` — STATUS: confirmed
|
||||
|
||||
## Key Numbers Generated or Discovered This Session
|
||||
|
||||
- Default max_sessions_per_week: **2**
|
||||
- Default max_weekend_sessions_per_period: **1**
|
||||
- Default weekend_period_weeks: **2**
|
||||
- Default open_session_advance_hours: **24**
|
||||
- Period epoch (fixed): **2026-01-05** (Monday of ISO week 2, 2026)
|
||||
- Holidays seeded: **5** (Victoria Day, Canada Day, Civic Holiday, Labour Day, Thanksgiving)
|
||||
- Overlap constraint type: `tstzrange(start_time, end_time, '[)')` — half-open interval (end exclusive)
|
||||
- Integration test describe blocks: **6**
|
||||
- Migration files created this session: **2**
|
||||
|
||||
## Files Created or Modified
|
||||
|
||||
| File Path | Action | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `supabase/migrations/20260420115333_reservations_overlap_and_cert_check.sql` | Created | Overlap exclusion constraint, cert check trigger, member_has_cert_for_boat() |
|
||||
| `supabase/migrations/20260420132336_booking_rules_and_rbac.sql` | Created | booking_config, holidays tables, is_weekend_or_holiday(), reservation_slots view, drop direct INSERT policy |
|
||||
| `supabase/functions/create-reservation/index.ts` | Created | Edge Function — all booking rule enforcement in TypeScript |
|
||||
| `app/pages/reservations/create.vue` | Created | Member reservation creation: date strip → slot picker → confirm form → Edge Function call |
|
||||
| `app/pages/admin/intervals.vue` | Created | Admin interval management: date strip, per-boat slot list, service toggle, template apply |
|
||||
| `app/pages/admin/templates.vue` | Created | Admin interval template CRUD |
|
||||
| `app/pages/admin/config.vue` | Created | Admin booking rules config: numeric params + holidays management |
|
||||
| `app/app.vue` | Modified | Added nav links for /admin/intervals, /admin/templates, /admin/config; added layersOutline, settingsOutline icons |
|
||||
| `app/types/supabase.ts` | Modified | Added booking_config, holidays tables; reservation_slots view |
|
||||
| `tests/integration/booking-constraints.test.ts` | Created | Integration tests for all booking constraints |
|
||||
|
||||
## What the NEXT Session Should Do
|
||||
|
||||
1. **Run integration tests**: `npx supabase functions serve` in one terminal, then `yarn test:integration` — expect all 6 describe blocks to pass; fix any failures before proceeding
|
||||
2. **Implement adjacent-session rule (Rule 6)**: A member may book a second adjoining session for the same boat on the day of sail only if no other member has booked it. Add check in Edge Function: if `(start_time - now) <= open_session_advance_hours AND booking is adjacent to caller's existing booking for same boat AND no other user has the adjacent slot` → allow double-booking. Otherwise, a member booking an adjacent session is subject to normal rules.
|
||||
3. **Build admin reservations view**: `/admin/reservations` — list all upcoming reservations with boat/time/member/status; ability to confirm, cancel, or modify
|
||||
4. **Test the full member booking flow in browser** — run `yarn dev`, create test intervals via admin, attempt to book as member, verify Edge Function error messages surface correctly
|
||||
|
||||
## Open Questions Requiring User Input
|
||||
|
||||
- [ ] **Adjacent-session double-booking (Rule 6)** — exact logic needed: does "day of sail" mean within `open_session_advance_hours`? Does the system automatically allow it if the adjacent slot is free, or does the member explicitly opt in? Impacts Edge Function logic
|
||||
- [ ] **`useSupabaseClient` typing** — should we investigate properly typing the client (e.g., by configuring `supabase.types` in nuxt.config.ts to point at `types/supabase.ts`)? Currently all new pages use `as any`. Impacts type safety
|
||||
- [ ] **Crew requirement (Rule 8)** — "must have at least one experienced crew member certified as helmsperson on board." Can this be enforced in the app? Members would need to name their crew at booking time. Currently not implemented. Impacts reservation form UX
|
||||
- [ ] **PCOC / Basic Cruising cert codes** — what strings go in `boats.required_certs` and `members.certifications`? (e.g., `'pcoc'`, `'cya-basic-cruising'`?) Impacts cert check behavior
|
||||
|
||||
## Assumptions That Need Validation
|
||||
|
||||
- ASSUMED: `npx supabase functions serve` is available in local dev and tests will reach `http://localhost:54321/functions/v1/create-reservation` — validate by running tests
|
||||
- ASSUMED: `tstzrange(start_time, end_time, '[)')` half-open intervals match the club's intent (booking a morning slot 09:00–12:30 does not block a 12:30 afternoon slot) — validate with Patrick
|
||||
- ASSUMED: ISO week (Monday–Sunday) is the correct "week" definition for the 2/week limit — validate with club guidelines
|
||||
- ASSUMED: 2-week period epoch of 2026-01-05 aligns correctly with the club's alternating weekend schedule — validate with program administrator
|
||||
|
||||
## Files to Load Next Session
|
||||
|
||||
- `supabase/functions/create-reservation/index.ts` — to debug test failures or extend with adjacent-session rule
|
||||
- `tests/integration/booking-constraints.test.ts` — to run and review test results
|
||||
- `app/pages/reservations/create.vue` — to test browser flow and fix any UX issues
|
||||
- `app/types/supabase.ts` — if adding new tables or fixing type issues
|
||||
Reference in New Issue
Block a user