Compare commits

...

11 Commits

Author SHA1 Message Date
The Dogfather fba29e605b fix: align types/index.ts with dev to resolve merge conflict
CI / Lint & Typecheck (pull_request) Failing after 13s
CI / Test (pull_request) Failing after 20s
CI / Build (pull_request) Has been skipped
CI / Build & Push Docker Images (pull_request) Has been skipped
CI / Update Infra Image Tags (pull_request) Has been skipped
2026-05-21 03:48:11 +00:00
Chris Farhood e4a6e2e540 fix(GRO-1365): address QA review findings on api/#21
CI / Lint & Typecheck (pull_request) Failing after 13s
CI / Test (pull_request) Failing after 20s
CI / Build & Push Docker Images (pull_request) Has been skipped
CI / Build (pull_request) Has been skipped
CI / Update Infra Image Tags (pull_request) Has been skipped
1. Fix vi.mock factory: importOriginal -> db.and/eq/exists/or stubs
   (removes ReferenceError from undeclared imports in test)
2. Remove MedicalAlert.id — not in schema/migration/DB, only in types
3. Replace z.string().max(100) coatType with z.enum for CoatType union
4. Fix test expecting coatType "smooth" (invalid) -> "double" (valid)
5. Add TC-API-3.8 through TC-API-3.15 to UAT_PLAYBOOK.md §4.3

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:31:43 +00:00
Chris Farhood 9787a1a442 fix: add missing extended pet profile fields to buildPet factory
CI / Lint & Typecheck (pull_request) Failing after 17s
CI / Test (pull_request) Failing after 22s
CI / Build (pull_request) Has been skipped
CI / Build & Push Docker Images (pull_request) Has been skipped
CI / Update Infra Image Tags (pull_request) Has been skipped
- Add coatType, temperamentScore, temperamentFlags, medicalAlerts, preferredCuts
- Fixes TypeScript error on factories.ts:89 where extended profile fields were missing
- GRO-1346

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-20 10:55:09 +00:00
Chris Farhood 22457ac361 GRO-1178: add extended pet fields to api types
CI / Lint & Typecheck (pull_request) Failing after 14s
CI / Test (pull_request) Failing after 21s
CI / Build (pull_request) Has been skipped
CI / Build & Push Docker Images (pull_request) Has been skipped
CI / Update Infra Image Tags (pull_request) Has been skipped
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-20 00:23:16 +00:00
The Dogfather f12ec4f8d3 Merge pull request 'feat(api): add extended pet profile fields — schema, migration, CRUD, Zod validation' (#10) from flea-flicker/pet-profile-extended-fields into dev
CI / Lint & Typecheck (push) Failing after 1m15s
CI / Test (push) Failing after 1m18s
CI / Build (push) Has been skipped
CI / Build & Push Docker Images (push) Has been skipped
CI / Update Infra Image Tags (push) Has been skipped
feat(api): add extended pet profile fields — schema, migration, CRUD, Zod validation (GRO-1176)

Merge groombook/api#10
2026-05-19 23:42:32 +00:00
groombook-engineer[bot] 2c928ca4d7 fix(gro-1261): correct infra paths in CI Update Infra Image Tags job (#16)
The CI workflow referenced wrong paths in groombook/infra:
- apps/groombook/overlays/dev/ → apps/overlays/dev/
- apps/groombook/base/ → apps/base/

These paths don't exist in groombook/infra — the correct structure
is apps/overlays/dev/ and apps/base/.

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-14 17:29:06 +00:00
the-dogfather-cto[bot] af75fecb66 Merge pull request #14 from groombook/flea-flicker/gro-1231-pnpm-workspace-dockerfile
fix(docker): add missing pnpm-workspace.yaml COPY in deps and runner stages (GRO-1231)
2026-05-14 17:10:25 +00:00
Chris Farhood 2d4df6fe1e fix(docker): add missing pnpm-workspace.yaml COPY in deps and runner stages
Without pnpm-workspace.yaml, pnpm install --frozen-lockfile can't discover
the apps/api workspace member, causing "Already up to date" and tsc not found.

Also removes stale packages/* entry from pnpm-workspace.yaml (no packages/
directory exists in the dev branch).

Fixes: GRO-1231

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-14 16:50:52 +00:00
the-dogfather-cto[bot] db10320c8f fix(auth): override Better Auth sign-in rate limit defaults (#11)
fix(auth): override Better Auth sign-in rate limit defaults
2026-05-14 10:52:31 +00:00
Chris Farhood 40a4023c65 feat(GRO-1202): add sign-in/sign-up rate limit overrides
Port rate limit customRules from groombook/app PR #392 to groombook/api.
Adds per-route limits for /sign-in/social, /sign-in/email, and /sign-up/email
to both AUTH_DISABLED and production better-auth() instances.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-14 10:34:32 +00:00
groombook-engineer[bot] d598511b75 fix: resolve pre-existing TypeScript errors for CI compliance (#9)
Merge PR #9: fix pre-existing TypeScript errors for CI compliance

All Lint & Typecheck and Test checks pass. Ready to merge.

cc @cpfarhood
2026-05-14 07:50:28 +00:00
36 changed files with 66 additions and 52 deletions
+4 -4
View File
@@ -202,20 +202,20 @@ jobs:
echo "Updating dev overlay image tags to: $TAG" echo "Updating dev overlay image tags to: $TAG"
echo "Updating migration/seed Job names with SHA: $SHORT_SHA" echo "Updating migration/seed Job names with SHA: $SHORT_SHA"
cd /tmp/infra cd /tmp/infra
DEV_KUST="apps/groombook/overlays/dev/kustomization.yaml" DEV_KUST="apps/overlays/dev/kustomization.yaml"
yq -i '(.images[] | select(.name == "ghcr.io/groombook/api")).newTag = env(TAG)' "$DEV_KUST" yq -i '(.images[] | select(.name == "ghcr.io/groombook/api")).newTag = env(TAG)' "$DEV_KUST"
yq -i '(.images[] | select(.name == "ghcr.io/groombook/migrate")).newTag = env(TAG)' "$DEV_KUST" yq -i '(.images[] | select(.name == "ghcr.io/groombook/migrate")).newTag = env(TAG)' "$DEV_KUST"
yq -i '(.images[] | select(.name == "ghcr.io/groombook/seed")).newTag = env(TAG)' "$DEV_KUST" yq -i '(.images[] | select(.name == "ghcr.io/groombook/seed")).newTag = env(TAG)' "$DEV_KUST"
yq -i '(.images[] | select(.name == "ghcr.io/groombook/reset")).newTag = env(TAG)' "$DEV_KUST" yq -i '(.images[] | select(.name == "ghcr.io/groombook/reset")).newTag = env(TAG)' "$DEV_KUST"
MIGRATE_JOB="apps/groombook/base/migrate-job.yaml" MIGRATE_JOB="apps/base/migrate-job.yaml"
if [ -f "$MIGRATE_JOB" ]; then if [ -f "$MIGRATE_JOB" ]; then
yq -i '.metadata.name = "migrate-schema-" + env(SHORT_SHA)' "$MIGRATE_JOB" yq -i '.metadata.name = "migrate-schema-" + env(SHORT_SHA)' "$MIGRATE_JOB"
yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$MIGRATE_JOB" yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$MIGRATE_JOB"
yq -i '.spec.ttlSecondsAfterFinished = (.spec.ttlSecondsAfterFinished // 86400)' "$MIGRATE_JOB" yq -i '.spec.ttlSecondsAfterFinished = (.spec.ttlSecondsAfterFinished // 86400)' "$MIGRATE_JOB"
fi fi
SEED_JOB="apps/groombook/base/seed-job.yaml" SEED_JOB="apps/base/seed-job.yaml"
if [ -f "$SEED_JOB" ]; then if [ -f "$SEED_JOB" ]; then
yq -i '.metadata.name = "seed-test-data-" + env(SHORT_SHA)' "$SEED_JOB" yq -i '.metadata.name = "seed-test-data-" + env(SHORT_SHA)' "$SEED_JOB"
yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$SEED_JOB" yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$SEED_JOB"
@@ -237,7 +237,7 @@ jobs:
git config user.name "groombook-engineer[bot]" git config user.name "groombook-engineer[bot]"
git config user.email "3141748+groombook-engineer[bot]@users.noreply.github.com" git config user.email "3141748+groombook-engineer[bot]@users.noreply.github.com"
git checkout -b "chore/update-image-tags-${TAG}" git checkout -b "chore/update-image-tags-${TAG}"
git add apps/groombook/overlays/dev/ apps/groombook/base/migrate-job.yaml apps/groombook/base/seed-job.yaml git add apps/overlays/dev/ apps/base/migrate-job.yaml apps/base/seed-job.yaml
git commit -m "chore: update image tags and migration/seed Job names to ${TAG}" git commit -m "chore: update image tags and migration/seed Job names to ${TAG}"
git push -u origin "chore/update-image-tags-${TAG}" git push -u origin "chore/update-image-tags-${TAG}"
+2 -2
View File
@@ -3,7 +3,7 @@ RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
WORKDIR /app WORKDIR /app
FROM base AS deps FROM base AS deps
COPY package.json pnpm-lock.yaml ./ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/api/package.json apps/api/ COPY apps/api/package.json apps/api/
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
@@ -17,7 +17,7 @@ RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
COPY package.json pnpm-lock.yaml ./ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY --from=builder /app/apps/api/package.json apps/api/ COPY --from=builder /app/apps/api/package.json apps/api/
COPY --from=builder /app/apps/api/dist apps/api/dist COPY --from=builder /app/apps/api/dist apps/api/dist
RUN pnpm install --frozen-lockfile --prod RUN pnpm install --frozen-lockfile --prod
+8
View File
@@ -51,6 +51,14 @@ GroomBook API is a Hono-based REST service (TypeScript/Node.js) powering the pet
| TC-API-3.5 | Delete pet | DELETE /api/pets/{id} | 200 OK, pet deleted | | TC-API-3.5 | Delete pet | DELETE /api/pets/{id} | 200 OK, pet deleted |
| TC-API-3.6 | Upload pet photo | POST /api/pets/{id}/photo/upload-url, then confirm | 200 OK, photo uploaded and key stored | | TC-API-3.6 | Upload pet photo | POST /api/pets/{id}/photo/upload-url, then confirm | 200 OK, photo uploaded and key stored |
| TC-API-3.7 | View pet photo | GET /api/pets/{id}/photo | 200 OK, presigned URL returned | | TC-API-3.7 | View pet photo | GET /api/pets/{id}/photo | 200 OK, presigned URL returned |
| TC-API-3.8 | Create pet with extended fields | POST /api/pets with coatType, temperamentScore, temperamentFlags, medicalAlerts, preferredCuts | 201 Created, all extended fields stored and returned |
| TC-API-3.9 | Update pet extended fields | PATCH /api/pets/{id} with coatType, temperamentScore, medicalAlerts | 200 OK, extended fields updated |
| TC-API-3.10 | Reject invalid coatType | POST /api/pets with coatType: "smooth" | 400 Bad Request, invalid coatType rejected |
| TC-API-3.11 | Reject out-of-range temperamentScore | POST /api/pets with temperamentScore: 0 or 6 | 400 Bad Request, score out of range rejected |
| TC-API-3.12 | Reject invalid medicalAlert severity | POST /api/pets with medicalAlerts severity: "critical" | 400 Bad Request, invalid severity rejected |
| TC-API-3.13 | Reject too many temperamentFlags | POST /api/pets with 21 temperamentFlags | 400 Bad Request, max 20 flags enforced |
| TC-API-3.14 | Reject too many preferredCuts | POST /api/pets with 21 preferredCuts | 400 Bad Request, max 20 cuts enforced |
| TC-API-3.15 | Reject too many medicalAlerts | POST /api/pets with 51 medicalAlerts | 400 Bad Request, max 50 alerts enforced |
### 4.4 Appointment Scheduling ### 4.4 Appointment Scheduling
@@ -145,7 +145,8 @@ function makeDeleteChainable(): unknown {
return chain; return chain;
} }
vi.mock("../db", () => { vi.mock("../db", async (importOriginal) => {
const db = await importOriginal<typeof import("../db/index.js")>();
const pets = new Proxy({ _name: "pets" }, { get: (t, p) => p === "_name" ? "pets" : {} }); const pets = new Proxy({ _name: "pets" }, { get: (t, p) => p === "_name" ? "pets" : {} });
const appointments = new Proxy({ _name: "appointments" }, { get: (t, p) => p === "_name" ? "appointments" : {} }); const appointments = new Proxy({ _name: "appointments" }, { get: (t, p) => p === "_name" ? "appointments" : {} });
return { return {
@@ -163,10 +164,10 @@ vi.mock("../db", () => {
}), }),
pets, pets,
appointments, appointments,
and, and: db.and,
eq, eq: db.eq,
exists, exists: db.exists,
or, or: db.or,
}; };
}); });
@@ -322,11 +323,11 @@ describe("Extended pet profile fields — update", () => {
const res = await app.request(`/pets/${PET_ID}`, { const res = await app.request(`/pets/${PET_ID}`, {
method: "PATCH", method: "PATCH",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ coatType: "smooth" }), body: JSON.stringify({ coatType: "double" }),
}); });
expect(res.status).toBe(200); expect(res.status).toBe(200);
const body = await res.json(); const body = await res.json();
expect(body.coatType).toBe("smooth"); expect(body.coatType).toBe("double");
}); });
it("updates temperamentScore", async () => { it("updates temperamentScore", async () => {
+5
View File
@@ -103,6 +103,11 @@ export function buildPet(overrides: Partial<PetRow> & { clientId: string }): Pet
photoKey: null, photoKey: null,
photoUploadedAt: null, photoUploadedAt: null,
image: null, image: null,
coatType: null,
temperamentScore: null,
temperamentFlags: [],
medicalAlerts: [],
preferredCuts: [],
createdAt: new Date("2025-01-01T00:00:00Z"), createdAt: new Date("2025-01-01T00:00:00Z"),
updatedAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"),
}; };
+1 -6
View File
@@ -94,11 +94,6 @@ function pick<T>(arr: T[]): T {
return arr[Math.floor(rand() * arr.length)]!; return arr[Math.floor(rand() * arr.length)]!;
} }
/** Return n distinct random elements from an array. */
function pickN<T>(arr: T[], n: number): T[] {
const shuffled = [...arr].sort(() => rand() - 0.5);
return shuffled.slice(0, n);
}
function randInt(min: number, max: number): number { function randInt(min: number, max: number): number {
return Math.floor(rand() * (max - min + 1)) + min; return Math.floor(rand() * (max - min + 1)) + min;
@@ -1105,7 +1100,7 @@ async function seed() {
const groomer = pick(groomers); const groomer = pick(groomers);
const bather = bathers.length > 0 && rand() < 0.6 ? pick(bathers) : null; const bather = bathers.length > 0 && rand() < 0.6 ? pick(bathers) : null;
let startTime = randDate(appointmentsBackDate, now); const startTime = randDate(appointmentsBackDate, now);
startTime.setHours(randInt(8, 16), pick([0, 15, 30, 45]), 0, 0); startTime.setHours(randInt(8, 16), pick([0, 15, 30, 45]), 0, 0);
const endTime = new Date(startTime.getTime() + svc.dur * 60 * 1000); const endTime = new Date(startTime.getTime() + svc.dur * 60 * 1000);
const effectivePrice = svc.price; const effectivePrice = svc.price;
+1 -1
View File
@@ -22,7 +22,7 @@ import { searchRouter } from "./routes/search.js";
import { getObject } from "./lib/s3.js"; import { getObject } from "./lib/s3.js";
import { calendarRouter } from "./routes/calendar.js"; import { calendarRouter } from "./routes/calendar.js";
import { setupRouter } from "./routes/setup.js"; import { setupRouter } from "./routes/setup.js";
import { getDb, businessSettings, eq, staff } from "./db"; import { getDb, businessSettings, eq, staff } from "./db/index.js";
import { authMiddleware } from "./middleware/auth.js"; import { authMiddleware } from "./middleware/auth.js";
import { resolveStaffMiddleware, requireRole, requireRoleOrSuperUser, requireSuperUser } from "./middleware/rbac.js"; import { resolveStaffMiddleware, requireRole, requireRoleOrSuperUser, requireSuperUser } from "./middleware/rbac.js";
import { devRouter } from "./routes/dev.js"; import { devRouter } from "./routes/dev.js";
+8 -2
View File
@@ -1,8 +1,8 @@
import { betterAuth } from "better-auth"; import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { genericOAuth } from "better-auth/plugins"; import { genericOAuth } from "better-auth/plugins";
import { getDb, authProviderConfig, eq } from "./db"; import { getDb, authProviderConfig, eq } from "../db/index.js";
import { decryptSecret } from "./db"; import { decryptSecret } from "../db/index.js";
import { sendEmail } from "../services/email.js"; import { sendEmail } from "../services/email.js";
const BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET; const BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET;
@@ -97,6 +97,9 @@ export async function initAuth(): Promise<void> {
window: 10, window: 10,
storage: "memory", storage: "memory",
customRules: { customRules: {
"/sign-in/social": { max: 10, window: 60 },
"/sign-in/email": { max: 10, window: 60 },
"/sign-up/email": { max: 5, window: 60 },
"/get-session": false, "/get-session": false,
}, },
}, },
@@ -247,6 +250,9 @@ export async function initAuth(): Promise<void> {
window: 10, window: 10,
storage: "memory", storage: "memory",
customRules: { customRules: {
"/sign-in/social": { max: 10, window: 60 },
"/sign-in/email": { max: 10, window: 60 },
"/sign-up/email": { max: 5, window: 60 },
"/get-session": false, "/get-session": false,
}, },
}, },
+1 -1
View File
@@ -1,5 +1,5 @@
import type { MiddlewareHandler } from "hono"; import type { MiddlewareHandler } from "hono";
import { getDb, impersonationAuditLogs } from "../db"; import { getDb, impersonationAuditLogs } from "../db/index.js";
import type { PortalEnv } from "./portalSession.js"; import type { PortalEnv } from "./portalSession.js";
/** /**
+1 -1
View File
@@ -1,5 +1,5 @@
import type { MiddlewareHandler } from "hono"; import type { MiddlewareHandler } from "hono";
import { and, eq, getDb, impersonationSessions } from "../db"; import { and, eq, getDb, impersonationSessions } from "../db/index.js";
export interface PortalEnv { export interface PortalEnv {
Variables: { Variables: {
+1 -1
View File
@@ -1,5 +1,5 @@
import type { MiddlewareHandler } from "hono"; import type { MiddlewareHandler } from "hono";
import { and, eq, getDb, sql, staff } from "../db"; import { and, eq, getDb, sql, staff } from "../db/index.js";
export type StaffRole = "groomer" | "receptionist" | "manager"; export type StaffRole = "groomer" | "receptionist" | "manager";
export type StaffRow = typeof staff.$inferSelect; export type StaffRow = typeof staff.$inferSelect;
+1 -1
View File
@@ -10,7 +10,7 @@
*/ */
import { Hono } from "hono"; import { Hono } from "hono";
import { eq, getDb, staff, clients, pets, services } from "./db"; import { eq, getDb, staff, clients, pets, services } from "../../db/index.js";
export const adminSeedRouter = new Hono(); export const adminSeedRouter = new Hono();
+1 -1
View File
@@ -15,7 +15,7 @@ import {
pets, pets,
services, services,
staff, staff,
} from "../db"; } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
export const appointmentGroupsRouter = new Hono<AppEnv>(); export const appointmentGroupsRouter = new Hono<AppEnv>();
+1 -1
View File
@@ -18,7 +18,7 @@ import {
reminderLogs, reminderLogs,
services, services,
staff, staff,
} from "../db"; } from "../db/index.js";
import { buildConfirmationEmail, sendEmail } from "../services/email.js"; import { buildConfirmationEmail, sendEmail } from "../services/email.js";
import { notifyWaitlistForAppointment } from "../services/waitlistNotify.js"; import { notifyWaitlistForAppointment } from "../services/waitlistNotify.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
+1 -1
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { eq, getDb, authProviderConfig, encryptSecret } from "../db"; import { eq, getDb, authProviderConfig, encryptSecret } from "../db/index.js";
import { requireSuperUser } from "../middleware/rbac.js"; import { requireSuperUser } from "../middleware/rbac.js";
import { reinitAuth } from "../lib/auth.js"; import { reinitAuth } from "../lib/auth.js";
+1 -1
View File
@@ -14,7 +14,7 @@ import {
appointments, appointments,
clients, clients,
pets, pets,
} from "../db"; } from "../db/index.js";
import { import {
generateAvailableSlots, generateAvailableSlots,
BUSINESS_START_HOUR, BUSINESS_START_HOUR,
+1 -1
View File
@@ -10,7 +10,7 @@ import {
pets, pets,
services, services,
staff, staff,
} from "../db"; } from "../db/index.js";
export const calendarRouter = new Hono(); export const calendarRouter = new Hono();
+1 -1
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { and, eq, exists, getDb, or, clients, appointments } from "../db"; import { and, eq, exists, getDb, or, clients, appointments } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
export const clientsRouter = new Hono<AppEnv>(); export const clientsRouter = new Hono<AppEnv>();
+1 -1
View File
@@ -1,5 +1,5 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { getDb, staff, clients, eq, sql } from "../db"; import { getDb, staff, clients, eq, sql } from "../db/index.js";
const devRouter = new Hono(); const devRouter = new Hono();
+1 -1
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { and, desc, eq, getDb, groomingVisitLogs, appointments, or } from "../db"; import { and, desc, eq, getDb, groomingVisitLogs, appointments, or } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
export const groomingLogsRouter = new Hono<AppEnv>(); export const groomingLogsRouter = new Hono<AppEnv>();
+1 -1
View File
@@ -9,7 +9,7 @@ import {
impersonationAuditLogs, impersonationAuditLogs,
clients, clients,
desc, desc,
} from "../db"; } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
export const impersonationRouter = new Hono<AppEnv>(); export const impersonationRouter = new Hono<AppEnv>();
+1 -1
View File
@@ -13,7 +13,7 @@ import {
services, services,
clients, clients,
sql, sql,
} from "../db"; } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
export const invoicesRouter = new Hono<AppEnv>(); export const invoicesRouter = new Hono<AppEnv>();
+2 -2
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { and, eq, exists, getDb, or, pets, appointments } from "../db"; import { and, eq, exists, getDb, or, pets, appointments } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
import { import {
getPresignedUploadUrl, getPresignedUploadUrl,
@@ -24,7 +24,7 @@ const createPetSchema = z.object({
shampooPreference: z.string().max(500).optional(), shampooPreference: z.string().max(500).optional(),
specialCareNotes: z.string().max(2000).optional(), specialCareNotes: z.string().max(2000).optional(),
customFields: z.record(z.string(), z.string()).optional(), customFields: z.record(z.string(), z.string()).optional(),
coatType: z.string().max(100).optional(), coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(),
temperamentScore: z.number().int().min(1).max(5).optional(), temperamentScore: z.number().int().min(1).max(5).optional(),
temperamentFlags: z.array(z.string().max(100)).max(20).optional(), temperamentFlags: z.array(z.string().max(100)).max(20).optional(),
medicalAlerts: z.array(z.object({ medicalAlerts: z.array(z.object({
+2 -2
View File
@@ -1,8 +1,8 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { eq, inArray } from "../db"; import { eq, inArray } from "../db/index.js";
import { getDb, appointments, impersonationSessions, waitlistEntries, clients, pets, services, staff, invoices, invoiceLineItems } from "../db"; import { getDb, appointments, impersonationSessions, waitlistEntries, clients, pets, services, staff, invoices, invoiceLineItems } from "../db/index.js";
import { validatePortalSession } from "../middleware/portalSession.js"; import { validatePortalSession } from "../middleware/portalSession.js";
import { portalAudit } from "../middleware/portalAudit.js"; import { portalAudit } from "../middleware/portalAudit.js";
import type { PortalEnv } from "../middleware/portalSession.js"; import type { PortalEnv } from "../middleware/portalSession.js";
+1 -1
View File
@@ -12,7 +12,7 @@ import {
invoiceTipSplits, invoiceTipSplits,
services, services,
staff, staff,
} from "../db"; } from "../db/index.js";
export const reportsRouter = new Hono(); export const reportsRouter = new Hono();
+1 -1
View File
@@ -1,5 +1,5 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { and, eq, getDb, clients, ilike, or, pets } from "../db"; import { and, eq, getDb, clients, ilike, or, pets } from "../db/index.js";
export const searchRouter = new Hono(); export const searchRouter = new Hono();
+1 -1
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { eq, getDb, services } from "../db"; import { eq, getDb, services } from "../db/index.js";
export const servicesRouter = new Hono(); export const servicesRouter = new Hono();
+1 -1
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { eq, getDb, businessSettings } from "../db"; import { eq, getDb, businessSettings } from "../db/index.js";
import { getPresignedUploadUrl, deleteObject, putObject, getObject } from "../lib/s3.js"; import { getPresignedUploadUrl, deleteObject, putObject, getObject } from "../lib/s3.js";
import { requireSuperUser } from "../middleware/rbac.js"; import { requireSuperUser } from "../middleware/rbac.js";
+1 -1
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { and, eq, getDb, sql, staff, businessSettings, authProviderConfig, encryptSecret } from "../db"; import { and, eq, getDb, sql, staff, businessSettings, authProviderConfig, encryptSecret } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
const RATE_LIMIT_WINDOW_MS = 60_000; const RATE_LIMIT_WINDOW_MS = 60_000;
+1 -1
View File
@@ -2,7 +2,7 @@ import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { randomBytes } from "node:crypto"; import { randomBytes } from "node:crypto";
import { and, eq, getDb, ne, staff, appointments } from "../db"; import { and, eq, getDb, ne, staff, appointments } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
export const staffRouter = new Hono<AppEnv>(); export const staffRouter = new Hono<AppEnv>();
+1 -1
View File
@@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import Stripe from "stripe"; import Stripe from "stripe";
import { z } from "zod/v3"; import { z } from "zod/v3";
import { eq, getDb, invoices } from "../db"; import { eq, getDb, invoices } from "../db/index.js";
import { getStripeClient } from "../services/payment.js"; import { getStripeClient } from "../services/payment.js";
export const webhooksRouter = new Hono(); export const webhooksRouter = new Hono();
+1 -1
View File
@@ -8,7 +8,7 @@ import {
clients, clients,
pets, pets,
services, services,
} from "../db"; } from "../db/index.js";
import type { AppEnv } from "../middleware/rbac.js"; import type { AppEnv } from "../middleware/rbac.js";
export const waitlistRouter = new Hono<AppEnv>(); export const waitlistRouter = new Hono<AppEnv>();
+1 -1
View File
@@ -1,5 +1,5 @@
import Stripe from "stripe"; import Stripe from "stripe";
import { getDb, clients, eq, inArray, invoices } from "../db"; import { getDb, clients, eq, inArray, invoices } from "../db/index.js";
let _stripe: Stripe | null | undefined; let _stripe: Stripe | null | undefined;
+1 -1
View File
@@ -14,7 +14,7 @@ import {
staff, staff,
reminderLogs, reminderLogs,
session, session,
} from "../db"; } from "../db/index.js";
import { import {
buildReminderEmail, buildReminderEmail,
sendEmail, sendEmail,
+1 -1
View File
@@ -1,4 +1,4 @@
import { and, eq, getDb, waitlistEntries, clients, pets, services } from "../db"; import { and, eq, getDb, waitlistEntries, clients, pets, services } from "../db/index.js";
import { buildWaitlistNotificationEmail, sendEmail } from "./email.js"; import { buildWaitlistNotificationEmail, sendEmail } from "./email.js";
export async function notifyWaitlistForAppointment( export async function notifyWaitlistForAppointment(
-1
View File
@@ -1,3 +1,2 @@
packages: packages:
- "apps/*" - "apps/*"
- "packages/*"