Compare commits

..

14 Commits

Author SHA1 Message Date
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
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
12 changed files with 82 additions and 82 deletions
+7 -51
View File
@@ -25,17 +25,17 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @groombook/api typecheck
run: pnpm typecheck
- name: Lint
run: pnpm --filter @groombook/api lint
run: pnpm lint
test:
name: Test
@@ -49,17 +49,17 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm --filter @groombook/api test
run: pnpm test
docker:
name: Build & Push Docker Images
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: [lint-typecheck, test]
steps:
@@ -91,53 +91,9 @@ jobs:
with:
context: .
file: Dockerfile
target: runner
push: true
provenance: false
tags: |
git.farh.net/groombook/api:${{ steps.version.outputs.tag }}
${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/api:latest' || '' }}
cache-from: type=registry,ref=git.farh.net/groombook/cache:api
cache-to: type=registry,ref=git.farh.net/groombook/cache:api,mode=max
- name: Build and push Migrate image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
target: migrate
push: true
provenance: false
tags: |
git.farh.net/groombook/migrate:${{ steps.version.outputs.tag }}
${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/migrate:latest' || '' }}
cache-from: type=registry,ref=git.farh.net/groombook/cache:migrate
cache-to: type=registry,ref=git.farh.net/groombook/cache:migrate,mode=max
- name: Build and push Seed image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
target: seed
push: true
provenance: false
tags: |
git.farh.net/groombook/seed:${{ steps.version.outputs.tag }}
${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/seed:latest' || '' }}
cache-from: type=registry,ref=git.farh.net/groombook/cache:seed
cache-to: type=registry,ref=git.farh.net/groombook/cache:seed,mode=max
- name: Build and push Reset image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
target: reset
push: true
provenance: false
tags: |
git.farh.net/groombook/reset:${{ steps.version.outputs.tag }}
${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/reset:latest' || '' }}
cache-from: type=registry,ref=git.farh.net/groombook/cache:reset
cache-to: type=registry,ref=git.farh.net/groombook/cache:reset,mode=max
cache-to: type=registry,ref=git.farh.net/groombook/cache:api,mode=max
+3 -3
View File
@@ -25,7 +25,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
cache: pnpm
- name: Install dependencies
@@ -49,7 +49,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
cache: pnpm
- name: Install dependencies
@@ -71,7 +71,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
cache: pnpm
- name: Install dependencies
+6 -6
View File
@@ -1,4 +1,4 @@
FROM node:22-alpine AS base
FROM node:20-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
WORKDIR /app
@@ -17,10 +17,10 @@ COPY src/ src/
COPY tsconfig.json ./
RUN pnpm --filter @groombook/types build && \
pnpm --filter @groombook/db build && \
pnpm build
pnpm --filter @groombook/api build
# Runtime
FROM node:22-alpine AS runner
FROM node:20-alpine AS runner
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
WORKDIR /app
ENV NODE_ENV=production
@@ -42,12 +42,12 @@ CMD ["node", "dist/index.js"]
# Migrate stage — runs drizzle-kit migrate against the database
FROM builder AS migrate
CMD ["pnpm", "--filter", "@groombook/db", "migrate"]
CMD ["pnpm", "db:migrate"]
# Seed stage — populates the database with test data
FROM builder AS seed
CMD ["pnpm", "--filter", "@groombook/db", "seed"]
CMD ["pnpm", "db:seed"]
# Reset stage — drops all tables, re-runs migrations, and re-seeds
FROM builder AS reset
CMD ["pnpm", "--filter", "@groombook/db", "reset"]
CMD ["pnpm", "db:reset"]
@@ -135,7 +135,7 @@ function makeDeleteChainable(): unknown {
}
if (prop === "returning") {
return () => {
const row = petRows[0]!;
const row = petRows[0];
deletedId = row.id as string;
return [row];
};
@@ -165,10 +165,10 @@ vi.mock("../db", async (importOriginal) => {
}),
pets,
appointments,
and: vi.fn(),
eq: vi.fn(),
exists: vi.fn(),
or: vi.fn(),
and: db.and,
eq: db.eq,
exists: db.exists,
or: db.or,
};
});
-9
View File
@@ -67,11 +67,6 @@ vi.mock("../db", () => {
{ get: (t, p) => (p === "_name" ? "impersonationSessions" : { table: "impersonationSessions", column: p }) }
);
const impersonationAuditLogs = new Proxy(
{ _name: "impersonationAuditLogs" },
{ get: (t, p) => (p === "_name" ? "impersonationAuditLogs" : { table: "impersonationAuditLogs", column: p }) }
);
const appointments = new Proxy(
{ _name: "appointments" },
{ get: (t, p) => (p === "_name" ? "appointments" : { table: "appointments", column: p }) }
@@ -104,12 +99,8 @@ vi.mock("../db", () => {
}),
}),
}),
insert: () => ({
values: () => ({ returning: () => [{}] }),
}),
}),
impersonationSessions,
impersonationAuditLogs,
appointments,
eq: vi.fn(),
and: vi.fn(),
-1
View File
@@ -3,7 +3,6 @@
"version": "0.0.1",
"private": true,
"type": "module",
"packageManager": "pnpm@9.15.4",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc --project .",
+3 -1
View File
@@ -105,6 +105,8 @@ 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"),
};
@@ -119,7 +121,7 @@ export function buildService(overrides: Partial<ServiceRow> = {}): ServiceRow {
description: "A grooming service",
basePriceCents: 6500,
durationMinutes: 60,
defaultBufferMinutes: 0,
defaultBufferMinutes: null,
active: true,
createdAt: new Date("2025-01-01T00:00:00Z"),
updatedAt: new Date("2025-01-01T00:00:00Z"),
+39
View File
@@ -48,6 +48,22 @@ 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", {
@@ -180,12 +196,35 @@ 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 "short" | "medium" | "long" | "double" | "wire" | "silky" | "curly" | "hairless" | null,
petSizeCategory: (body.petSizeCategory ?? null) as "small" | "medium" | "large" | "extra_large" | null,
coatType: (body.petCoatType ?? null) as "smooth" | "double" | "wire" | "curly" | "long" | "hairless" | null,
petSizeCategory: (body.petSizeCategory ?? null) as "small" | "medium" | "large" | "xlarge" | 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", "extra_large"]).optional(),
coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(),
sizeCategory: z.enum(["small", "medium", "large", "xlarge"]).optional(),
coatType: z.enum(["smooth", "double", "wire", "curly", "long", "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(),
petSizeCategory: z.enum(["small", "medium", "large", "extra_large"]).optional(),
coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(),
sizeCategory: z.enum(["small", "medium", "large", "xlarge"]).optional(),
coatType: z.enum(["smooth", "double", "wire", "curly", "long", "hairless"]).optional(),
});
const updatePetSchema = createPetSchema.partial().omit({ clientId: true });