Compare commits

...

26 Commits

Author SHA1 Message Date
The Dogfather 634e9d03e1 Merge pull request 'promote: dev → uat (GRO-1533 Dockerfile fix)' (#49) from dev into uat
CI / Lint & Typecheck (pull_request) Successful in 9s
CI / Test (pull_request) Successful in 11s
CI / Build & Push Docker Images (pull_request) Failing after 19s
promote: dev → uat (GRO-1533 Dockerfile fix)

Promotes PR #47 fix to uat. Reverts Dockerfile to build from apps/api/src/.
Fixes HTTP 500 on authenticated admin routes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 13:24:33 +00:00
The Dogfather 8f7104c3a0 Merge pull request 'fix(GRO-1533): revert Dockerfile to build from apps/api/src/' (#47) from fix/gro-1533-revert-dockefile-build-change into dev
CI / Test (push) Successful in 9s
CI / Lint & Typecheck (push) Successful in 10s
CI / Build & Push Docker Images (push) Failing after 18s
fix(GRO-1533): revert Dockerfile to build from apps/api/src/

Reverts the Dockerfile change that switched from pnpm --filter @groombook/api build
to pnpm build, which caused the image to build from root src/ instead of
apps/api/src/. This produced HTTP 500 on all authenticated admin routes.

Conflict with PR #45 (seed filter fix) resolved: migrate/seed/reset stages
use @groombook/api db:migrate|seed|reset since the builder only has apps/api/.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 13:23:49 +00:00
Flea Flicker dab9bfab71 fix(GRO-1533): revert Dockerfile to build from apps/api/src/
CI / Test (pull_request) Successful in 10s
CI / Lint & Typecheck (pull_request) Successful in 10s
CI / Build & Push Docker Images (pull_request) Failing after 18s
The Dockerfile build change (pnpm build → pnpm --filter @groombook/api)
was made against dev HEAD, but the root src/ directory was never fully
audited for parity with apps/api/src/. Admin routes returning 500 for
authenticated users post-OIDC login is consistent with the image
running code with incomplete middleware chain or mismatched schema
types when the root build path was used.

Revert to the apps/api/ build path which is known to work correctly.
UAT is running images from dev branch commit 9462915 which includes
this change alongside schema cleanup commits.

Root cause: Dockerfile was changed to build from root src/ instead of
apps/api/src/ without confirming the two source trees are functionally
identical. The proper fix path (schema audit + reconciliation) is
tracked in GRO-1536.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 13:23:33 +00:00
The Dogfather 974dade8f7 Merge pull request 'promote: dev → uat (pnpm-lock.yaml fix + CI/enum fixes + seed Docker fix)' (#48) from dev into uat
CI / Lint & Typecheck (pull_request) Successful in 12s
CI / Test (pull_request) Successful in 11s
CI / Build & Push Docker Images (pull_request) Successful in 53s
promote: dev → uat (pnpm-lock.yaml fix + CI/enum fixes + seed Docker fix)

Includes PR #45 seed fix, lockfile, CI, and enum alignment.
2026-05-22 13:18:12 +00:00
The Dogfather 70bc946a0d Merge pull request 'fix(seed): use --filter @groombook/db for seed/migrate/reset scripts' (#45) from flea-flicker/gro-1531-seed-db-filter into dev
CI / Lint & Typecheck (push) Successful in 10s
CI / Test (push) Successful in 10s
CI / Build & Push Docker Images (push) Successful in 21s
fix(seed): use --filter @groombook/db for seed/migrate/reset scripts

Merge PR #45 to dev. QA approved (review #3506), CTO reviewed.
2026-05-22 13:17:09 +00:00
Flea Flicker 3eaefb4911 fix: add better-auth to pnpm-lock.yaml packages/db specifiers
CI / Lint & Typecheck (pull_request) Failing after 13s
CI / Test (pull_request) Failing after 23s
CI / Build & Push Docker Image (pull_request) Has been skipped
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 13:11:39 +00:00
Flea Flicker 40422a14f0 fix(seed): use --filter @groombook/db to invoke seed/migrate/reset scripts
CI / Lint & Typecheck (pull_request) Successful in 11s
CI / Test (pull_request) Successful in 11s
CI / Build & Push Docker Images (pull_request) Successful in 51s
The seed/migrate/reset Dockerfile targets specified `pnpm db:migrate`
etc., but those scripts are defined in packages/db/package.json, not
at the workspace root. pnpm workspace filtering is required to route
the command to the correct package.

- migrate: pnpm --filter @groombook/db migrate
- seed:    pnpm --filter @groombook/db seed
- reset:   pnpm --filter @groombook/db reset

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 13:08:49 +00:00
Chris Farhood 9462915a66 fix: align root src/ enum values with packages/db schema
CI / Lint & Typecheck (push) Successful in 9s
CI / Test (push) Successful in 10s
CI / Build & Push Docker Images (push) Failing after 36s
Root src/routes/ used stale enum values (xlarge→extra_large,
smooth→silky, missing short/medium/silky from coatType) and
sizeCategory→petSizeCategory field name mismatch with the
pets table column.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 03:17:13 +00:00
Chris Farhood 46f134a294 fix: use root pnpm build instead of --filter for api in Dockerfile
CI / Test (push) Successful in 11s
CI / Lint & Typecheck (push) Successful in 12s
CI / Build & Push Docker Images (push) Failing after 2m11s
pnpm --filter @groombook/api doesn't match the workspace root package
because pnpm-workspace.yaml only includes packages/*. The root
package.json already has a build script that compiles src/ → dist/,
which is what the Dockerfile copies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 03:11:03 +00:00
Chris Farhood 4086b6f5c0 fix: remove duplicate bufferRules table and duplicate properties blocking Docker build
CI / Test (push) Successful in 10s
CI / Lint & Typecheck (push) Successful in 10s
CI / Build & Push Docker Images (push) Failing after 24s
CI Run 942 Docker build fails on TS2451 (duplicate bufferRules at lines
190 & 653 of schema.ts), TS1117 (duplicate defaultBufferMinutes in
services table, duplicate coatType/petSizeCategory in factories.ts),
and TS2322 (null vs number for defaultBufferMinutes in factories.ts).

Keep the newer, more complete bufferRules declaration (with comments and
index) and the .notNull().default(0) variant of defaultBufferMinutes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 03:09:00 +00:00
Chris Farhood d7d98791c7 fix(ci): use monorepo-filtered commands in Gitea CI (GRO-1522)
CI / Lint & Typecheck (push) Successful in 10s
CI / Test (push) Successful in 9s
CI / Build & Push Docker Images (push) Failing after 2m1s
The root pnpm scripts typecheck/lint/test the stale src/ directory.
Use pnpm --filter @groombook/api to target the correct apps/api/ path,
matching the GitHub Actions CI configuration.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 03:04:12 +00:00
Chris Farhood 3bec5d095a fix(db): remove duplicate petSizeCategoryEnum/coatTypeEnum declarations (GRO-1522)
CI / Lint & Typecheck (push) Failing after 13s
CI / Test (push) Failing after 20s
CI / Build & Push Docker Images (push) Has been skipped
The schema file had two sets of these enum declarations with different values.
The first (stale) set broke all tests importing @groombook/db via the vitest alias,
causing CI to fail and blocking the docker build job.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 03:02:20 +00:00
The Dogfather 3fddf80fac Merge pull request 'fix(ci): build all service images + upgrade Node 22 + pin packageManager (GRO-1522)' (#44) from fix/gro-1522-ci-images-node22 into dev
CI / Lint & Typecheck (push) Failing after 12s
CI / Test (push) Failing after 21s
CI / Build & Push Docker Images (push) Has been skipped
fix(ci): build all service images + upgrade Node 22 + pin packageManager (GRO-1522)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-22 02:58:30 +00:00
The Dogfather ff6f8471d5 Merge pull request 'promote: dev → uat (GRO-1509 OIDC account_not_linked fix)' (#43) from dev into uat
CI / Lint & Typecheck (pull_request) Failing after 5s
CI / Test (pull_request) Failing after 6s
CI / Build & Push Docker Image (pull_request) Has been skipped
promote: dev → uat (GRO-1509 OIDC account_not_linked fix)

Merged-by: The Dogfather (CTO)
Gitea-approved-by: Lint Roller (GRO-1512)
2026-05-21 22:53:49 +00:00
The Dogfather 6045024150 Merge pull request 'Promote dev → uat: GRO-1178 enhanced pet profile editor' (#39) from dev into uat
Promote dev → uat: GRO-1178 enhanced pet profile editor
2026-05-21 19:19:10 +00:00
The Dogfather df5e413930 Merge pull request 'chore: promote dev → uat (GRO-1463 UAT playbook expansion)' (#38) from dev into uat
chore: promote dev → uat (GRO-1463 UAT playbook expansion)
2026-05-21 16:49:18 +00:00
The Dogfather 7cb5fda3e3 Merge pull request 'promote: dev → uat (GRO-1272 auto-provision staff on OIDC login)' (#36) from dev into uat
promote: dev → uat (GRO-1272 auto-provision staff on OIDC login) (#36)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-21 14:17:40 +00:00
The Dogfather 76540cea0d Merge pull request 'chore(promote): dev → uat (Buffer Rules CRUD — GRO-1171)' (#34) from dev into uat
chore(promote): dev → uat (Buffer Rules CRUD — GRO-1171)

Promote PR #12 merge to UAT for regression testing.
2026-05-21 10:18:10 +00:00
Lint Roller d83210e7e2 Merge pull request 'chore(promote): dev → uat (petsExtendedFields test fix GRO-1390)' (#33) from dev into uat 2026-05-21 07:03:24 +00:00
The Dogfather 5c9cac7a28 Merge pull request 'promote: dev → uat (GRO-1395 drizzle-orm root dep fix)' (#31) from dev into uat
promote: dev → uat (GRO-1395 drizzle-orm root dep fix) (#31)
2026-05-21 04:11:29 +00:00
The Dogfather fad99dc032 Merge pull request 'promote: dev → uat (Renovate config, GRO-1081)' (#26) from dev into uat
promote: dev → uat (Renovate config, GRO-1081) (#26)
2026-05-20 12:37:23 +00:00
The Dogfather 247570abc8 Merge pull request 'Promote dev → uat: GRO-1326 UAT email+password credentials' (#25) from dev into uat
Promote dev → uat: GRO-1326 UAT email+password credentials (#25)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-20 04:25:29 +00:00
the-dogfather-cto[bot] 4f5ec60961 chore: promote dev to uat — Dockerfile pnpm-workspace fix (GRO-1231)
chore: promote dev to uat (GRO-1231 pnpm-workspace fix)
2026-05-14 17:15:52 +00:00
the-dogfather-cto[bot] 39ffdccac7 promote: dev → uat (rate limit override) (#13)
promote: dev → uat (rate limit override)
2026-05-14 10:55:45 +00:00
the-dogfather-cto[bot] 1ff0d4230c promote: dev → uat (UAT Tester seed fix + TypeScript CI compliance)
promote: dev → uat (UAT Tester seed fix + TypeScript CI compliance)
2026-05-14 08:07:54 +00:00
the-dogfather-cto[bot] be5e9d8fc7 chore: promote dev to uat (PR #5 mock path fix)
chore: promote dev to uat (PR #5 mock path fix)
2026-05-12 21:34:03 +00:00
8 changed files with 33 additions and 73 deletions
+3 -3
View File
@@ -32,10 +32,10 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm typecheck
run: pnpm --filter @groombook/api typecheck
- name: Lint
run: pnpm lint
run: pnpm --filter @groombook/api lint
test:
name: Test
@@ -56,7 +56,7 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm test
run: pnpm --filter @groombook/api test
docker:
name: Build & Push Docker Images
+10 -22
View File
@@ -5,19 +5,14 @@ WORKDIR /app
# Install deps
FROM base AS deps
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY packages/db/package.json packages/db/
COPY packages/types/package.json packages/types/
COPY apps/api/package.json apps/api/
RUN pnpm install --frozen-lockfile
# Build
FROM deps AS builder
RUN mkdir -p /home/node/.cache/node/corepack
COPY packages/ packages/
COPY src/ src/
COPY tsconfig.json ./
RUN pnpm --filter @groombook/types build && \
pnpm --filter @groombook/db build && \
pnpm --filter @groombook/api build
COPY apps/api/ apps/api/
RUN pnpm --filter @groombook/api build
# Runtime
FROM node:22-alpine AS runner
@@ -25,29 +20,22 @@ RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
WORKDIR /app
ENV NODE_ENV=production
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/dist dist/
COPY --from=builder /app/packages/db/package.json packages/db/
COPY --from=builder /app/packages/db/dist packages/db/dist
COPY --from=builder /app/packages/types/package.json packages/types/
COPY --from=builder /app/packages/types/dist packages/types/dist
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/dist apps/api/dist
RUN pnpm install --frozen-lockfile --prod
EXPOSE 3000
RUN apk add --no-cache curl
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
CMD ["node", "apps/api/dist/index.js"]
# Migrate stage — runs drizzle-kit migrate against the database
FROM builder AS migrate
CMD ["pnpm", "db:migrate"]
CMD ["pnpm", "--filter", "@groombook/api", "db:migrate"]
# Seed stage — populates the database with test data
FROM builder AS seed
CMD ["pnpm", "db:seed"]
CMD ["pnpm", "--filter", "@groombook/api", "db:seed"]
# Reset stage — drops all tables, re-runs migrations, and re-seeds
FROM builder AS reset
CMD ["pnpm", "db:reset"]
CMD ["pnpm", "--filter", "@groombook/api", "db:reset"]
+1 -3
View File
@@ -105,8 +105,6 @@ export function buildPet(overrides: Partial<PetRow> & { clientId: string }): Pet
photoKey: null,
photoUploadedAt: null,
image: null,
coatType: null,
petSizeCategory: null,
createdAt: new Date("2025-01-01T00:00:00Z"),
updatedAt: new Date("2025-01-01T00:00:00Z"),
};
@@ -121,7 +119,7 @@ export function buildService(overrides: Partial<ServiceRow> = {}): ServiceRow {
description: "A grooming service",
basePriceCents: 6500,
durationMinutes: 60,
defaultBufferMinutes: null,
defaultBufferMinutes: 0,
active: true,
createdAt: new Date("2025-01-01T00:00:00Z"),
updatedAt: new Date("2025-01-01T00:00:00Z"),
-39
View File
@@ -48,22 +48,6 @@ export const clientStatusEnum = pgEnum("client_status", [
"disabled",
]);
export const petSizeCategoryEnum = pgEnum("pet_size_category", [
"small",
"medium",
"large",
"xlarge",
]);
export const coatTypeEnum = pgEnum("coat_type", [
"smooth",
"double",
"wire",
"curly",
"long",
"hairless",
]);
// ─── Better-Auth Tables ──────────────────────────────────────────────────────
export const user = pgTable("user", {
@@ -196,35 +180,12 @@ export const services = pgTable("services", {
description: text("description"),
basePriceCents: integer("base_price_cents").notNull(),
durationMinutes: integer("duration_minutes").notNull(),
defaultBufferMinutes: integer("default_buffer_minutes"),
active: boolean("active").notNull().default(true),
defaultBufferMinutes: integer("default_buffer_minutes").notNull().default(0),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
});
export const bufferRules = pgTable(
"buffer_rules",
{
id: uuid("id").primaryKey().defaultRandom(),
serviceId: uuid("service_id")
.notNull()
.references(() => services.id, { onDelete: "cascade" }),
sizeCategory: petSizeCategoryEnum("size_category"),
coatType: coatTypeEnum("coat_type"),
bufferMinutes: integer("buffer_minutes").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
},
(t) => [
unique("uq_buffer_rules_service_size_coat").on(
t.serviceId,
t.sizeCategory,
t.coatType
),
]
);
export const staff = pgTable("staff", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
+13
View File
@@ -970,66 +970,79 @@ packages:
resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.3':
resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.3':
resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.3':
resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.3':
resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.3':
resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.3':
resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.3':
resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.3':
resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.3':
resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.3':
resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.3':
resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.3':
resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.3':
resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==}
+2 -2
View File
@@ -193,8 +193,8 @@ bookRouter.post(
name: body.petName,
species: body.petSpecies,
breed: body.petBreed ?? null,
coatType: (body.petCoatType ?? null) as "smooth" | "double" | "wire" | "curly" | "long" | "hairless" | null,
petSizeCategory: (body.petSizeCategory ?? null) as "small" | "medium" | "large" | "xlarge" | null,
coatType: (body.petCoatType ?? null) as "short" | "medium" | "long" | "double" | "wire" | "silky" | "curly" | "hairless" | null,
petSizeCategory: (body.petSizeCategory ?? null) as "small" | "medium" | "large" | "extra_large" | null,
})
.returning();
const pet = petInserted[0];
+2 -2
View File
@@ -19,8 +19,8 @@ bufferRulesRouter.use("*", requireRole("manager"));
const createBufferRuleSchema = z.object({
serviceId: z.string().uuid(),
sizeCategory: z.enum(["small", "medium", "large", "xlarge"]).optional(),
coatType: z.enum(["smooth", "double", "wire", "curly", "long", "hairless"]).optional(),
sizeCategory: z.enum(["small", "medium", "large", "extra_large"]).optional(),
coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(),
bufferMinutes: z.number().int().positive(),
});
+2 -2
View File
@@ -24,8 +24,8 @@ const createPetSchema = z.object({
shampooPreference: z.string().max(500).optional(),
specialCareNotes: z.string().max(2000).optional(),
customFields: z.record(z.string(), z.string()).optional(),
sizeCategory: z.enum(["small", "medium", "large", "xlarge"]).optional(),
coatType: z.enum(["smooth", "double", "wire", "curly", "long", "hairless"]).optional(),
petSizeCategory: z.enum(["small", "medium", "large", "extra_large"]).optional(),
coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(),
});
const updatePetSchema = createPetSchema.partial().omit({ clientId: true });