Commit Graph

110 Commits

Author SHA1 Message Date
Pawla Abdul (CMO) 2f1dcb4bca docs: update license reference in README to AGPL-3.0
The project license was changed to AGPL-3.0 in the recent license
update. Update the README to reflect the correct license.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-27 18:18:45 +00:00
groombook-engineer[bot] 619ff204d1 docs: update README with marketing positioning and current shipped features
Update the groombook/groombook README to reflect shipped features and serve
as a compelling entry point for users and contributors.

- Add positioning statement and tagline ('Built for groomers, not corporations.')
- Add live demo link (groombook.farh.net)
- Feature list updated to shipped features: iCal feed, waitlist, quick-find
  search, customer portal, appointment notes, RBAC
- Docker Compose quick-start, tech stack, contributing guide retained

Closes GRO-66

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-27 17:12:13 +00:00
groombook-cto[bot] d576876f56 fix(ci): use correct postgres secret for dev deploys
Fixes CI workflow to reference groombook-postgres-credentials-dev in groombook-dev namespace. Unblocks all dev PR deploys.
2026-03-27 16:54:03 +00:00
groombook-engineer[bot] 9eb0c3d151 fix(gro66): E2E selector fix + groomer isolation + portal confirm/cancel
* Implement confirm/cancel in customer portal (GRO-50)

Backend:
- Add POST /api/portal/appointments/:id/confirm endpoint
  - Validates impersonation session auth and ownership
  - Rejects past/in-progress, non-pending, or already-cancelled/completed
  - Sets confirmationStatus="confirmed", confirmedAt, updatedAt
- Add POST /api/portal/appointments/:id/cancel endpoint
  - Same auth/ownership pattern
  - Rejects past/in-progress or already-cancelled/completed
  - Sets status="cancelled", confirmationStatus="cancelled", cancelledAt, updatedAt

Frontend (Appointments.tsx):
- Add confirmationStatus field to Appointment type and mock data
- Add ConfirmationSection component: shows status badge + confirm button
- Add CancelAppointmentButton: wires to cancel API with loading/error state
- Wire existing Cancel button to CancelAppointmentButton
- Show confirmation status badge in expanded view for upcoming appointments

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(gro-48): row-level data scoping for groomer role (RBAC Phase 2)

Filter query results at the route handler level when staff role is groomer:

- GET /api/appointments: WHERE staffId = groomer OR batherStaffId = groomer
- GET /api/appointments/🆔 403 if not assigned to groomer (as staff or bather)
- GET /api/clients: Clients with ≥1 appointment for this groomer (via exists subquery)
- GET /api/clients/🆔 403 if no appointment linkage
- GET /api/pets: Pets owned by groomer-linked clients (via exists subquery)
- GET /api/pets/:petId: 403 if no appointment linkage

Managers and receptionists: no change.

Added exists to @groombook/db exports (was missing from re-export).
Added groomerIsolation unit tests for role guard and filter logic.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-50): add portal confirm/cancel tests and fix ConfirmationSection state

- Add test coverage for POST /portal/appointments/:id/confirm endpoint
- Add test coverage for POST /portal/appointments/:id/cancel endpoint
- Fix ConfirmationSection not updating local status after successful confirm
- Remove unused onCancel prop from ConfirmationSection call site
- Fix Appointments.test.tsx missing confirmationStatus field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(gro-50): add ConfirmationSection UI component tests

Add tests for the ConfirmationSection component:
- Renders correct badge for each confirmationStatus state
- Shows/hides Confirm button based on status
- Calls confirm API with correct headers
- Handles sessionId null case
- Shows error messages for 401/403/422 responses
- Shows loading state while confirming
- Shows success message briefly after confirm
- Does not call API if user cancels confirm dialog

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-48): address QA review feedback — staffRow?.role and portal TS guards

- appointments.ts: use staffRow?.role (consistent with clients.ts/pets.ts)
  to handle undefined staff context safely
- portal.ts: add null guards on .returning() results for confirm and cancel
  endpoints (TS18048: 'updated' is possibly undefined)
- All 188 tests passing; TypeScript typecheck clean

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro66): use specific selector for banner visibility assertion

Replace ambiguous `getByText("STAFF VIEW")` that matched both the
ImpersonationBanner and the CustomerPortal watermark with a precise
`getByTestId("impersonation-banner")` selector to eliminate strict
mode violations.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-66): add missing afterEach to vitest import

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-48): add icalToken to MANAGER mock after rebase

After rebasing onto origin/main (which added icalToken to the staff
schema via GRO-107), the MANAGER mock in groomerIsolation.test.ts was
missing the new required field. Added icalToken: null to the MANAGER
constant. factories.ts is clean (no duplicate icalToken after rebase).

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-47): add non-null assertions on Drizzle RETURNING results

Drizzle's update().returning() types the array element as T | undefined.
After the if (!appt) guard, updated is still typed as possibly undefined
because RETURNING can succeed with no rows. Add ! assertions since
we already guard with the existence check.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Flea Flicker <fleaflicker@groombook.ai>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Flea Flicker <flea-flicker@paperclip.ing>
2026-03-27 14:23:19 +00:00
groombook-cto[bot] 8ab6319311 feat: quick-find search for clients and pets (GRO-46)
Closes #119

- Debounced typeahead search on clients/pets pages
- Auto-select client from GlobalSearch highlight param
- Mobile-friendly with big touch targets
- 141-line test suite, all 70 web tests pass

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-27 07:16:52 +00:00
groombook-engineer[bot] 6539eb4554 feat: iCal calendar feed (GRO-107)
feat: iCal calendar feed (GRO-107)

Closes GRO-107
2026-03-27 02:37:06 +00:00
groombook-engineer[bot] e3220af9ce fix(gro-38): prod/demo auth and API-based seed (#117)
Closes GRO-38. Adds POST /api/admin/seed (manager-only, gated by SEED_KNOWN_USERS_ONLY) and separates dev vs prod seeding paths. Reviewed and approved by CTO and QA.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-26 20:51:08 +00:00
groombook-engineer[bot] d0b4baf5aa feat: customer-facing appointment notes (GRO-106) (#109)
* feat: add customer-facing appointment notes (GRO-106)

- Migration 0014: add customer_notes column to appointments
- Schema update: add customerNotes field to appointments table
- Factory update: include customerNotes in buildAppointment
- Portal route: PATCH /api/portal/appointments/:id/notes
  - Ownership validation via impersonation session
  - Future-only validation (no edits after start)
  - 500 character limit
- Register portal router in index.ts

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* Fix confirmationToken leak and add unit tests for portal notes endpoint

- Return only id, customerNotes, updatedAt instead of full appointment row
- Add comprehensive unit tests covering auth, ownership, time-gating, and validation
- Fix: confirmationToken no longer returned to portal session

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat: add customer notes UI to portal and staff views (GRO-178)

- Add customerNotes field to Appointment type
- Add read-only customer notes display in staff appointment detail modal
- Add customer notes textarea with save, char counter (500 max), and disabled state
- Wire up PATCH /api/portal/appointments/:id/notes in portal UI
- Update mockData with customerNotes field

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: address QA review feedback - null check and portal route auth

- Add null check after db.update().returning() in portal notes endpoint
- Move portal router registration before auth middleware so clients can access it
- Remove unused ENDED_SESSION variable from test file

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(portal): address QA review - isUpcoming time parsing and session header

- Fixed parseTimeTo24Hour to handle 12-hour AM/PM format correctly
- Added X-Impersonation-Session-Id header to CustomerNotesSection fetch
- Added comprehensive tests for CustomerNotesSection and time parsing
- Fixed TypeScript strict null checks for parseTimeTo24Hour

Fixes QA review issues:
- isUpcoming() now correctly parses 12-hour time format
- CustomerNotesSection sends session ID header for auth
- Added unit tests for new UI component

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: thread sessionId as prop instead of sessionStorage

CustomerNotesSection was reading sessionStorage for the impersonation
session ID, but CustomerPortal stores it in React state. Pass sessionId
as a prop through AppointmentsSection and AppointmentCard instead.

Also update tests to pass sessionId prop and add test for null sessionId
case.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Scrubs McBarkley <scrubs@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: groombook-cto[bot] <269737991+groombook-cto[bot]@users.noreply.github.com>
2026-03-26 08:24:21 +00:00
groombook-engineer[bot] 553fa435ed feat: add PR preview deployment to groombook-dev (#113)
feat: add PR preview deployment to groombook-dev
2026-03-26 03:56:50 +00:00
Groom Book CTO f1235c6d3d fix: install kubectl on ARC runner for deploy-dev job
ARC self-hosted runners don't include kubectl. Download it before
running the deployment commands.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-25 11:03:38 +00:00
Groom Book CTO 2b6c437446 feat: add PR preview deployment to groombook-dev
Extend CI to build PR-tagged Docker images and auto-deploy them to
groombook-dev when all checks pass. This unblocks Flea Flicker UAT
validation for open PRs.

Changes:
- Docker build job now runs on PRs (tagged as pr-{number}) and main
- New deploy-dev job uses self-hosted runners with kubectl access
- Runs migration, updates api/web deployments, comments on PR

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-25 11:03:38 +00:00
groombook-engineer[bot] 4fc84ae891 chore: remove docs/ folder (migrated to groombook.github.io) (#112)
Refs GRO-195

Co-authored-by: Scrubs McBarkley <scrubs@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-25 06:39:51 +00:00
Chris Farhood 7e782d0b7f Merge pull request #108 from groombook/docs/github-pages-marketing-site
Add marketing docs for GitHub Pages site
2026-03-24 22:03:16 -04:00
Groom Book CTO ad87183259 fix: correct double verb grammar in why-we-built.html
"you've basically have" → "you basically have" — QA item #5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 22:05:09 +00:00
Groom Book CTO 735c0f09fd fix(docs): address CTO review feedback on marketing site
- Convert mobile-groomers.html from Markdown to proper HTML
- Fix "Client/p pet profiles" typo → "Client/pet profiles"
- Add missing trailing newlines to all files
- Remove duplicate "Back to Home" link in getting-started/index.html
- Fix link paths: use relative URLs instead of /groombook/docs/ absolute paths
- Remove placeholder testimonials with fake names

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:56:16 +00:00
Groom Book CTO 6945efdbed Add marketing docs for GitHub Pages site
Add landing page, blog posts (launch announcement, why we built),
getting-started guide, mobile groomers page, and Jekyll config
to docs/ folder for GitHub Pages publishing.

Closes groombook/groombook#168

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:41:44 +00:00
groombook-ceo[bot] 0c1ec6dade feat: appointment confirmation and cancellation (GH #98)
feat: appointment confirmation and cancellation (GH #98)
2026-03-24 21:15:07 +00:00
Scrubs McBarkley f3923ddf54 fix(email): remove unused baseUrl variable in buildReminderEmail
Fixes ESLint @typescript-eslint/no-unused-vars error in CI.
baseUrl was declared but never used; confirmUrl/cancelUrl correctly
use apiUrl instead.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 17:25:09 +00:00
Scrubs McBarkley d1ab91adfa feat: appointment confirmation and cancellation (GH #98, GRO-153)
Add customer confirmation/cancellation flow for appointments:

- DB migration (0013): add confirmation_status, confirmed_at, cancelled_at,
  confirmation_token to appointments table with index on token column
- schema.ts + factories.ts + types: expose new columns and ConfirmationStatus type
- GET /api/book/confirm/:token — tokenized confirm via email link (redirects)
- GET /api/book/cancel/:token — tokenized cancel via email link, single-use token
- POST /api/appointments/:id/confirm — portal/staff confirm endpoint
- POST /api/appointments/:id/cancel — portal/staff cancel endpoint
- Reminder emails now include Confirm/Cancel CTA buttons with tokenized links
- Reminder service generates confirmation token if missing before sending
- Staff calendar shows confirmation status indicator on appointment cards
  and in the detail modal (confirmed ✓ / customer cancelled ✗)
- /booking/confirmed, /booking/cancelled, /booking/error redirect pages
- 23 new unit tests covering all new endpoints and edge cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:02:58 +00:00
groombook-ceo[bot] 75d0e4c3e6 feat: pet photo upload via presigned S3 URLs (GH #93)
feat: pet photo upload via presigned S3 URLs (GH #93)
2026-03-22 19:45:41 +00:00
groombook-ceo[bot] 020e758916 Merge branch 'main' into feat/pet-photo-upload-gh93 2026-03-22 19:45:35 +00:00
groombook-ceo[bot] 86cc5829e4 test(e2e): add login and impersonation test coverage (GRO-77)
test(e2e): add login and impersonation test coverage (GRO-77)
2026-03-22 15:43:00 +00:00
groombook-ceo[bot] e9c8bff784 Merge branch 'main' into feat/e2e-login-impersonation-gro-77 2026-03-22 15:42:45 +00:00
Scrubs McBarkley 90abb28a0d fix: address PR #102 review feedback (GRO-145)
- factories.ts: add photoKey/photoUploadedAt null defaults to buildPet (TS regression fix)
- s3.ts: lazy singleton S3Client to avoid re-instantiation per call
- routes/pets.ts: server-side 5MB file size limit, explicit content-type allowlist (drops image/svg+xml etc), validate confirm key ownership against pets/${petId}/ prefix, delete old S3 object on re-upload, fix RBAC comment on DELETE photo
- PetPhotoUpload.tsx: bypass canvas resize for GIFs (preserves animation), pass fileSizeBytes in upload-url request
- Add PetPhotoDisplay.test.tsx: 7 tests covering fetch states, placeholder, refetch on petId change, custom size
- Add PetPhotoUpload.test.tsx: 8 tests covering idle state, type validation, upload flow, progress, GIF bypass
- Update petPhotos.test.ts: add SVG rejection, 5MB limit, key ownership, and old-photo deletion tests (18 total)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 15:41:44 +00:00
Lint Roller b3514626a1 fix(e2e): fix test failures after CTO review
- Scope STAFF VIEW locator to impersonation-banner testid
- Fix loading state test: unroute before setting delayed handler

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 11:42:32 +00:00
Lint Roller 355f11fdaa fix(e2e): address CTO review feedback on PR #101
- Fix route mismatch: mock /api/impersonation/sessions/session-1 (plural)
- Navigate to /?sessionId=session-1 so CustomerPortal fetches session
- Replace .bg-amber-500 with data-testid="impersonation-banner"
- Remove waitForTimeout(1100), use proper waitFor
- Fix locale-dependent time regex in "banner shows started time" test
- Fix loading state race by waiting for response before fulfilling
- Add data-testid to ImpersonationBanner component
- Add trailing newlines to both spec files

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 11:36:07 +00:00
groombook-ceo[bot] 871830183a Merge pull request #103 from groombook/feat/quick-find-search-gh97
feat: quick-find search for clients and pets (GH #97)
2026-03-22 08:22:46 +00:00
groombook-ceo[bot] 6ca09d739b Merge branch 'main' into feat/quick-find-search-gh97 2026-03-22 08:20:02 +00:00
groombook-ceo[bot] a732a3c47b Merge pull request #100 from groombook/feat/site-theming-unification-gh91
feat: unify site theming via CSS custom properties (GH #91)
2026-03-22 04:17:30 +00:00
Scrubs McBarkley 0c182da366 fix: address CTO review feedback on quick-find search (GH #97, GRO-134)
- Remove unused makeSelectChain function from search.test.ts (lint blocker)
- Fix handleClientClick/handlePetClick to navigate to /admin/clients?highlight={id}
  so the target client is identified in the URL rather than silently ignored
- Add console.warn for fetch errors in GlobalSearch instead of swallowing silently

Auth middleware verified: searchRouter is registered on the api Hono instance
which applies authMiddleware + resolveStaffMiddleware globally — no coverage gap.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 04:10:54 +00:00
Scrubs McBarkley c826f65bd6 feat: quick-find search for clients and pets (GH #97, GRO-140)
Backend:
- GET /api/search?q={query} — returns up to 10 matching active clients and 10
  matching pets in a single request; clients matched on name/email/phone,
  pets matched on name/breed with owner name included
- Special chars (%, _, \) escaped before ILIKE to prevent injection/accidents
- Disabled clients excluded; pets from disabled client owners excluded via JOIN filter
- Route registered under protected API (auth + RBAC middleware applies automatically)
- Export `ilike` from @groombook/db alongside existing drizzle-orm helpers

Frontend:
- GlobalSearch component in sticky admin header: debounced input (300ms),
  grouped dropdown (Clients / Pets sections), loading/empty states
- Client results show name + phone; pet results show name, breed, owner name
- Touch-friendly: 44px input height, 48px min row height, full-width dropdown
- Outside-click closes dropdown; selecting a result navigates to /admin/clients

Tests (apps/api/src/__tests__/search.test.ts):
- 400 on missing/empty/whitespace q
- Returns matching clients and pets
- Empty arrays on no match
- Response shape always has clients/pets keys
- Special character inputs handled without errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:16:28 +00:00
Scrubs McBarkley a15585a8e6 fix: address QA feedback on site theming PR (GH #91)
- Fix gradient regression in ReportCards.tsx: use distinct color stops
  (--color-accent-lighter → --color-accent-light) to restore subtle gradient
- Fix BrandingContext meta tag accumulation: cache ref with useRef instead
  of querying DOM on every render to prevent duplicate elements on remount
- Add BrandingContext.test.tsx: verify CSS vars applied, theme-color meta
  created/updated, and no duplicate meta tags on rerender

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:12:57 +00:00
Scrubs McBarkley 1380848aea feat: pet photo upload via presigned S3 URLs (GH #93, GRO-123)
- DB migration 0012: add photo_key and photo_uploaded_at columns to pets table
- S3 client utility (apps/api/src/lib/s3.ts): presigned PUT/GET, delete via Rook-Ceph RGW
- API photo routes on petsRouter:
  - POST /:petId/photo/upload-url — returns presigned PUT URL + object key
  - POST /:petId/photo/confirm    — records key in DB after successful upload
  - DELETE /:petId/photo          — deletes from storage and clears DB
  - GET /:petId/photo             — returns presigned GET URL
- RBAC: all staff roles (manager, receptionist, groomer) may upload/delete photos;
  restructured index.ts guards so groomer-accessible photo paths don't overlap
  with the manager/receptionist-only general pets write guard
- Frontend PetPhotoDisplay: responsive image with shimmer skeleton and paw placeholder
- Frontend PetPhotoUpload: client-side resize to max 1200px, XHR with progress,
  presigned PUT flow — binary data never passes through the API server
- Wired both components into Clients.tsx staff portal pet cards
- Unit tests: 14 test cases covering all four routes (happy path + error cases)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 00:07:48 +00:00
groombook-ceo[bot] 4060d5b515 Merge branch 'main' into feat/site-theming-unification-gh91 2026-03-22 00:05:08 +00:00
groombook-ceo[bot] c625104bfd Merge pull request #99 from groombook/feat/factory-unit-tests-gh94
test(db): add unit tests for test factories
2026-03-22 00:04:38 +00:00
groombook-ceo[bot] 83ee230975 Merge branch 'main' into feat/factory-unit-tests-gh94 2026-03-22 00:01:43 +00:00
Scrubs McBarkley afde6b7857 feat: unify site theming via CSS custom properties (GH #91)
Replace all hardcoded brand color hex values with CSS custom properties
so BrandingContext drives both the customer portal and staff site.

- index.css: add derived accent/primary vars using color-mix()
  (--color-accent-hover, --color-accent-dark, --color-accent-light,
  --color-accent-lighter, --color-primary-dark); fix focus ring styles
  to use var(--color-primary) instead of hardcoded hex
- BrandingContext.tsx: also update <meta name="theme-color"> in sync
  with primaryColor so PWA theme-color tracks branding at runtime
- portal/sections: replace bg-[#8b7355], text-[#6b5a42], bg-[#f0ebe4],
  bg-[#faf5ef], hover:bg-[#7a6549] etc. with Tailwind v4 CSS var
  utilities (bg-(--color-accent), text-(--color-accent-dark), etc.)
- pages: replace inline style "#4f8a6f"/"#3d7a5f" with
  var(--color-primary) / var(--color-primary-dark) across Appointments,
  Book, Clients, GroupBooking, Invoices, Reports, Services, Staff, and
  DevSessionIndicator

Closes #91

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:50:43 +00:00
Scrubs McBarkley 891cc39ae1 fix: remove stale vitest entry from packages/db lockfile
vitest was erroneously added to the packages/db importer in
pnpm-lock.yaml during factory test setup, but packages/db/package.json
does not declare vitest as a dependency. This caused CI to fail with
ERR_PNPM_OUTDATED_LOCKFILE.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:47:15 +00:00
Lint Roller a466053000 E2E tests: add login and impersonation test coverage (GRO-77)
- apps/e2e/tests/login.spec.ts: 8 tests for DevLoginSelector page
  - renders staff and clients sections
  - shows loading state
  - displays staff with role/email, clients with pet count
  - clicking staff navigates to /admin with dev-user stored
  - clicking client navigates to / with dev-user stored
  - skip login removes dev-user and navigates to /admin
  - handles empty users response

- apps/e2e/tests/impersonation.spec.ts: 8 tests for ImpersonationBanner
  - banner displays when session is active
  - shows reason and started time
  - End Session and Audit buttons visible
  - clicking End Session calls API and hides banner
  - Extend button appears when time < 5 mins and not extended
  - URL is cleaned when session ends

- apps/e2e/tests/fixtures.ts: added /api/dev/users mock for login tests
2026-03-21 23:47:01 +00:00
groombook-ceo[bot] 8fdffb9564 Merge pull request #96 from groombook/feat/impersonation-indexes-gh95
feat(db): add indexes on impersonation tables
2026-03-21 23:44:18 +00:00
Scrubs McBarkley 1f50fdff54 test(db): add unit tests for test factories (GitHub #94)
Tests cover resetFactoryCounters(), counter determinism, override
merging, and compile-time enforcement of required fields on
buildAppointment. All 16 new tests pass (92 total).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:43:47 +00:00
Scrubs McBarkley 11c4f0a07b feat(db): add indexes on impersonation tables (GitHub #95)
Add three indexes to prevent full table scans as session volume grows:
- impersonation_sessions(staff_id, status) for active-session lookup
- impersonation_sessions(client_id) for existing-session check
- impersonation_audit_logs(session_id) for audit log lookup by session

Migration 0011 applied and verified on dev database.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:28:09 +00:00
groombook-ceo[bot] 4f233e7bd3 Merge pull request #92 from groombook/feat/dev-data-strategy-gro-110
feat: deterministic seed, impersonation migration, test factories (GRO-110)
2026-03-21 23:18:36 +00:00
Scrubs McBarkley ad6024f3d9 feat: deterministic seed, impersonation migration, test factories (GRO-110)
Phase 1 — Seed Hardening:
- Replace all Math.random() calls in seed.ts with a Mulberry32 seeded PRNG
  (seed 42) so the same data set is reproduced on every run
- Replace crypto.randomUUID() with a PRNG-based UUID v4 generator
- Add manager (Jordan Lee) and receptionist (Sam Rivera) staff members
  to seed — previously all staff were groomers
- New packages/db/src/reset.ts drops all tables/enums and re-runs
  migrate + seed; exposed as `pnpm db:reset` at root
- Generate migration 0010_impersonation_sessions.sql for the
  impersonation_sessions and impersonation_audit_logs tables that were
  already in schema.ts but had no corresponding migration

Phase 2 — Test Factories:
- New packages/db/src/factories.ts with buildStaff, buildClient, buildPet,
  buildService, buildAppointment and resetFactoryCounters helpers
- Exported via @groombook/db/factories subpath (package.json + vitest alias)
- impersonation.test.ts updated to use buildStaff instead of hand-rolled
  fixture objects

Closes #90 (Phases 1 + 2)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 19:34:52 +00:00
groombook-ceo[bot] 49c6550bf9 Merge pull request #89 from groombook/feat/rbac-middleware-gro-103
feat: RBAC middleware and role-based route guards (Phase 1)
2026-03-21 19:31:18 +00:00
Scrubs McBarkley 543c13f182 fix: correct TypeScript types in rbac.test.ts
Use StaffRow type for all staff fixture objects so groomer/receptionist
variants don't cause type errors. Simplify buildApp/buildWithStaff helper
signatures to MiddlewareHandler<AppEnv> / Context<AppEnv> — no more
Parameters<...> inference gymnastics.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 18:52:10 +00:00
Scrubs McBarkley 93a9ae4461 feat: add RBAC middleware with role-based route guards (GRO-103)
- New `apps/api/src/middleware/rbac.ts` with `resolveStaffMiddleware`
  (resolves staff from DB by OIDC sub, supports AUTH_DISABLED dev mode)
  and `requireRole(...roles)` factory for per-route role enforcement
- Wire `resolveStaffMiddleware` after `authMiddleware` on api basePath
- Route guards per permission matrix:
  - Manager only: /staff/*, /admin/*, /reports/*, /invoices/*, /impersonation/*
  - Manager + Receptionist only: /appointment-groups/*, /grooming-logs/*
  - Groomers read-only on /clients/*, /pets/*, /appointments/* (write requires manager/receptionist)
  - Services: all roles read, manager-only write
- Refactor impersonation router to use AppEnv and c.get("staff") instead
  of inline staff resolution; role check delegated to requireRole middleware
- Unit tests in rbac.test.ts covering resolveStaffMiddleware and requireRole
- Update impersonation.test.ts to inject staff directly via context

Closes #88 (Phase 1)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 15:50:45 +00:00
groombook-ceo[bot] 1ac037a20d Merge pull request #85 from groombook/feat/gro-76-unit-tests
test: Phase 1 unit tests for API routes and web components
2026-03-21 03:05:38 +00:00
groombook-ceo[bot] c6a8adc164 Merge pull request #83 from groombook/docs/readme-add-impersonation-feature
docs: add staff impersonation to README features list
2026-03-21 03:05:31 +00:00
Scrubs McBarkley b7145271fb fix: assert on deletedId in DELETE test to resolve unused-vars lint error
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:52:18 +00:00