This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
groombook-engineer[bot] 2d88f18f75 feat(GRO-106): inbound Telnyx webhook + persistence (#378)
* feat(GRO-106): messaging schema + migrations

- Add conversations, messages, message_attachments, message_consent_events tables
- Add messagingChannelEnum, messageDirectionEnum, messageStatusEnum, messageConsentKindEnum
- Extend business_settings with messagingPhoneNumber and telnyxMessagingProfileId columns
- Add required indexes and unique constraints with cascade-on-delete FKs
- Add migration 0030_messaging.sql

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

* fix(GRO-981): restore journal entries and add DESC to indexes

- _journal.json: restore idx 28 (0028_sms_reminders), add idx 29
  (0029_db_indexes_constraints), renumber 0030_messaging to idx 30
  (was missing 0028 and 0029 entries — they were silently skipped)
- schema.ts: add .desc() to conversations.lastMessageAt and
  messages.createdAt indexes per spec
- 0030_messaging.sql: add DESC to both generated index statements

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

* feat(GRO-106): inbound Telnyx webhook + persistence

- Add POST /api/webhooks/telnyx/messaging route with HMAC signature verification
- Add services/messaging/inbound.ts: findOrCreateConversation, upsertMessage (idempotent on providerMessageId), delivery receipt handling
- Register telnyxWebhooksRouter in index.ts (before auth middleware)
- Add unit tests for signature validation, find-or-create, idempotent insert, delivery receipt

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

* fix(GRO-982): address all QA blocking failures

- #7: Extract validateTelnyxSignature in sms.ts as standalone exported fn,
  reuse in TelnyxProvider.validateWebhookSignature and telnyx.ts route
- #1: Replace uuid v4 import with crypto.randomUUID() (built-in, no dep)
- #2: Remove updatedAt from messages update in handleMessageFinalized
  (no such column exists)
- #3: Fix test import path ../../ → ../../../ for telnyx route import
- #4: validateTelnyxSignature accepts string | undefined | null to match
  Hono c.req.header() return type
- #5&6: Add null guards for .returning() results in findOrCreateConversation
  and upsertMessage
- #8: Remove dead buildFindOrCreateConversationParams function
- #9: Remove unused imports (messageDirectionEnum, messageStatusEnum,
  resolveBusinessIdByMessagingNumber in test)
- #10: Wrap upsertMessage insert in try/catch; unique violation returns
  {isNew: false} instead of crashing
- #11: Add EOF newlines to all modified files

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

* chore: add uuid dependency for messaging services

* fix(GRO-982): address 5 test failures in inbound webhook

- Fix signature route tests: use /messaging not full mount path
- Fix handleMessageReceived mock order: business lookup first
- Fix stale mock state: add full mockReset in handleMessageFinalized beforeEach
- Fix delivery logic: set delivered for all message.finalized events
- Deduplicate test that was accidentally added twice

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

* fix(GRO-982): look up or create client by phone before inserting conversation

Fixes FK constraint violation where clientId was set to businessSettings.id
or a random UUID. Now looks up clients.phone = clientPhone first; if no match,
creates a placeholder client with phone as name and a placeholder email.

* fix(GRO-982): address QA round 4 blocking failures

- Fix URL in signature tests: use /messaging not full path
- Reorder mocks: businessSettings first, then conversations, clients, messages
- Add mockDb.mockReset in handleMessageFinalized beforeEach
- Remove direction guard: set delivered for any message.finalized

* fix(GRO-982): add missing message insert mock in handleMessageReceived test

* fix(GRO-982): simplify test mocks to match actual code flow

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-11 00:43:40 +00:00
2026-04-14 08:27:03 +00:00

GroomBook

The open-source scheduling and client management platform built specifically for independent pet groomers — giving you the tools of enterprise software without the enterprise price tag or vendor lock-in.

Built for groomers, not corporations.


Key Features

Stop chasing confirmations

  • Customer portal — Clients confirm or cancel appointments on their own. Reduce no-shows with an automated waitlist.

Your calendar, your way

  • iCal calendar feed — Push GroomBook appointments directly into Google Calendar or Apple Calendar. No app switching.

Know every pet at a glance

  • Client & pet records — Detailed profiles with grooming history, preferences, and breed-specific notes. Full appointment notes for context on every regular.
  • Quick-find search — Find clients and pets instantly without digging through spreadsheets.

Staff access without stress

  • Role-based access control (RBAC) — Front desk sees bookings; only you see financials. Right access for every role.

Everything else

  • Appointment scheduling — Calendar management for single or multiple groomers
  • Service management — Pricing, duration, and service catalog
  • POS & invoicing — Payments, tips, and receipt generation
  • Automated reminders — SMS and email notifications
  • Reporting dashboard — Revenue, utilization, and trend analytics
  • Staff impersonation — Managers can view the customer portal as any client, with full audit logging and session controls
  • PWA — Installable on mobile devices, works offline

🚀 Try the Demo

Live Demo — explore GroomBook without installing anything.


Quick Start

Run GroomBook on your own hardware in minutes. Everything you need is in the box — no subscription, no vendor lock-in.

git clone https://github.com/groombook/groombook.git
cd groombook

# Start everything (Postgres + database migrations + API + web UI)
docker compose up --build

The default docker-compose.yml sets AUTH_DISABLED=true so you can explore the app without configuring an OIDC provider. Important: Disable this in any internet-facing deployment.


Tech Stack

Layer Technology
Backend Hono (TypeScript, Node.js)
Frontend React 19 + Vite + vite-plugin-pwa
Database PostgreSQL via CNPG + Drizzle ORM
Auth OIDC via Authentik
Infra Kubernetes (namespace: groombook), Flux GitOps
CI GitHub Actions (self-hosted groombook-runners)

Repository Structure

groombook/
├── apps/
│   ├── api/          # Hono REST API
│   └── web/          # React PWA
├── packages/
│   ├── db/           # Drizzle schema + migrations
│   └── types/        # Shared TypeScript types
├── .github/
│   └── workflows/    # CI/CD pipelines
└── docker-compose.yml

Getting Started

Prerequisites

  • Node.js >= 20
  • pnpm >= 9 (npm install -g pnpm)
  • Docker & Docker Compose (for local Postgres)

Local Development

# Clone the repo
git clone https://github.com/groombook/groombook.git
cd groombook

# Install dependencies
pnpm install

# Start local Postgres
docker compose up postgres -d

# Run database migrations
DATABASE_URL=postgres://groombook:groombook@localhost:5432/groombook pnpm db:migrate

# Start API and Web in parallel
pnpm dev

API will be available at http://localhost:3000 Web will be available at http://localhost:5173

Environment Variables

API (apps/api/.env)

DATABASE_URL=postgres://groombook:groombook@localhost:5432/groombook
OIDC_ISSUER=https://authentik.example.com
OIDC_AUDIENCE=groombook
CORS_ORIGIN=http://localhost:5173
PORT=3000

Running Tests

# Unit tests (vitest)
pnpm test

# E2E tests (Playwright) — requires the full Docker Compose stack to be running
docker compose up -d --wait
pnpm --filter @groombook/e2e test

# Open the Playwright UI (interactive test runner)
pnpm --filter @groombook/e2e test:ui

# View the last E2E test report
pnpm --filter @groombook/e2e test:report

E2E tests target the Docker Compose stack (http://localhost:8080). They use API route mocking where needed so happy-path tests are deterministic without requiring seed data.

Building

pnpm build

Self-Hosting

Production Configuration

Copy .env.example to .env and configure:

cp .env.example .env

Key variables to update for production:

Variable Description
DATABASE_URL PostgreSQL connection string
AUTH_DISABLED Set to false in production
OIDC_ISSUER Authentik issuer URL
OIDC_AUDIENCE OAuth2 audience (default: groombook)
CORS_ORIGIN Public URL of the web frontend

To use your .env file with Docker Compose:

docker compose --env-file .env up --build

Kubernetes (production-grade deployments)

See the groombook/infra repository for Kubernetes manifests and Flux configuration.

Groom Book is deployed in the groombook Kubernetes namespace using:

  • CNPG for PostgreSQL
  • Authentik for OIDC authentication
  • Flux for GitOps-managed deployments

Contributing

GroomBook thrives on contributions from the grooming community. Whether you're a groomer with a feature request, a developer fixing a bug, or someone improving docs — we'd love your help.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes
  4. Open a pull request

All PRs require CI to pass before merge. See CONTRIBUTING.md for details.


Why GroomBook?

  • Open source — You own your data. No vendor lock-in.
  • Purpose-built — Features designed for grooming workflows, not generic scheduling.
  • Self-hosted or managed — Run it yourself for free, or pay for hosted support (coming soon).
  • Community-driven — Used and built by actual groomers.

License

AGPL-3.0

S
Description
Open source, self-hostable pet grooming business management & CRM
Readme AGPL-3.0 49 MiB
Languages
TypeScript 98.5%
Python 0.7%
Go Template 0.3%
Dockerfile 0.2%
CSS 0.2%