diff --git a/apps/web/public/demo-pets/dog-afghan-hound.png b/apps/web/public/demo-pets/dog-afghan-hound.png
new file mode 100644
index 0000000..7ce13ba
Binary files /dev/null and b/apps/web/public/demo-pets/dog-afghan-hound.png differ
diff --git a/apps/web/public/demo-pets/dog-basset-brown-white.png b/apps/web/public/demo-pets/dog-basset-brown-white.png
new file mode 100644
index 0000000..9c9fea0
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-basset-brown-white.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96C853FAECD363909C4A0
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-bichon-white-groomed.png b/apps/web/public/demo-pets/dog-bichon-white-groomed.png
new file mode 100644
index 0000000..2d214d4
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-bichon-white-groomed.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96CFC84D7A9333708F278
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-boxer-fawn-athletic.png b/apps/web/public/demo-pets/dog-boxer-fawn-athletic.png
new file mode 100644
index 0000000..4958ac7
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-boxer-fawn-athletic.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96D48D7892E37386B9ACB
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-cavalier-cream-gentle.png b/apps/web/public/demo-pets/dog-cavalier-cream-gentle.png
new file mode 100644
index 0000000..a06164b
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-cavalier-cream-gentle.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96C25663D703833F23607
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-cocker-buff-friendly.png b/apps/web/public/demo-pets/dog-cocker-buff-friendly.png
new file mode 100644
index 0000000..b4beacc
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-cocker-buff-friendly.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96D89851C843332073968
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-corgi.png b/apps/web/public/demo-pets/dog-corgi.png
new file mode 100644
index 0000000..6b659b1
Binary files /dev/null and b/apps/web/public/demo-pets/dog-corgi.png differ
diff --git a/apps/web/public/demo-pets/dog-dachshund-black-tan.png b/apps/web/public/demo-pets/dog-dachshund-black-tan.png
new file mode 100644
index 0000000..506c523
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-dachshund-black-tan.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96C9C5A03D33730C61AD8
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-mixed-breed.png b/apps/web/public/demo-pets/dog-mixed-breed.png
index 8b280b7..5933865 100644
Binary files a/apps/web/public/demo-pets/dog-mixed-breed.png and b/apps/web/public/demo-pets/dog-mixed-breed.png differ
diff --git a/apps/web/public/demo-pets/dog-pomeranian-white-studio.png b/apps/web/public/demo-pets/dog-pomeranian-white-studio.png
new file mode 100644
index 0000000..0d13b96
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-pomeranian-white-studio.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96BEB91911B30317E3BE8
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-schnauzer-black-groomed.png b/apps/web/public/demo-pets/dog-schnauzer-black-groomed.png
new file mode 100644
index 0000000..2cdf304
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-schnauzer-black-groomed.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96BFB7B92D33535D6D90D
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-setter-red-sunlit.png b/apps/web/public/demo-pets/dog-setter-red-sunlit.png
new file mode 100644
index 0000000..4e1850d
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-setter-red-sunlit.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96B8BDF4B473630A2E120
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/apps/web/public/demo-pets/dog-sheepdog-merle-running.png b/apps/web/public/demo-pets/dog-sheepdog-merle-running.png
new file mode 100644
index 0000000..d164736
--- /dev/null
+++ b/apps/web/public/demo-pets/dog-sheepdog-merle-running.png
@@ -0,0 +1,9 @@
+
+
+ AccessDenied
+ You have no right to access this object because of bucket acl.
+ 69D96D78BFFCAD343037C27C
+ hailuo-image-algeng-data-us.oss-us-east-1.aliyuncs.com
+ 0003-00000001
+ https://api.alibabacloud.com/troubleshoot?q=0003-00000001
+
diff --git a/infra b/infra
index d43a016..aaedafc 160000
--- a/infra
+++ b/infra
@@ -1 +1 @@
-Subproject commit d43a016f3f304d34bdbda82a48a8865399d420fd
+Subproject commit aaedafcb9a932ab9b3ede99f3e37645df3fae71d
diff --git a/packages/db/src/seed.ts b/packages/db/src/seed.ts
index bd659d4..2bbbce9 100644
--- a/packages/db/src/seed.ts
+++ b/packages/db/src/seed.ts
@@ -184,7 +184,7 @@ const dogBreeds = [
"Brittany", "English Springer Spaniel", "Maltese", "Bichon Frise",
"West Highland White Terrier", "Vizsla", "Chihuahua", "Collie",
"Basset Hound", "Newfoundland", "Samoyed", "Australian Shepherd",
- "Pembroke Welsh Corgi", "French Bulldog", "Weimaraner",
+ "Pembroke Welsh Corgi", "French Bulldog", "Weimaraner", "Puggle",
"Mixed Breed", "Mixed Breed", "Mixed Breed",
];
@@ -281,6 +281,45 @@ const productsUsed = [
"Coconut oil shampoo, leave-in conditioner, cologne",
];
+const demoPetImages = [
+ "/demo-pets/dog-golden-after.png",
+ "/demo-pets/dog-poodle-groomed.png",
+ "/demo-pets/dog-black-lab.png",
+ "/demo-pets/dog-shih-tzu.png",
+ "/demo-pets/dog-cocker-spaniel.png",
+ "/demo-pets/dog-schnauzer.png",
+ "/demo-pets/dog-maltese.png",
+ "/demo-pets/dog-dachshund.png",
+ "/demo-pets/dog-pomeranian.png",
+ "/demo-pets/dog-bichon-frise.png",
+ "/demo-pets/dog-golden-retriever.png",
+ "/demo-pets/dog-labrador.png",
+ "/demo-pets/dog-mixed-breed.png",
+ "/demo-pets/dog-poodle.png",
+ "/demo-pets/dog-terrier.png",
+ "/demo-pets/dog-afghan-hound.png",
+ "/demo-pets/dog-basset-brown-white.png",
+ "/demo-pets/dog-bichon-white-groomed.png",
+ "/demo-pets/dog-boxer-fawn-athletic.png",
+ "/demo-pets/dog-cavalier-cream-gentle.png",
+ "/demo-pets/dog-cocker-buff-friendly.png",
+ "/demo-pets/dog-corgi.png",
+ "/demo-pets/dog-dachshund-black-tan.png",
+ "/demo-pets/dog-golden-before.png",
+ "/demo-pets/dog-pomeranian-white-studio.png",
+ "/demo-pets/dog-schnauzer-black-groomed.png",
+ "/demo-pets/dog-setter-red-sunlit.png",
+ "/demo-pets/dog-sheepdog-merle-running.png",
+];
+
+const puggleImages = [
+ "/demo-pets/dog-puggle-fawn-playful.png",
+ "/demo-pets/dog-puggle-black-sitting.png",
+ "/demo-pets/dog-puggle-cream-groomed.png",
+ "/demo-pets/dog-puggle-tricolor-outdoor.png",
+ "/demo-pets/dog-puggle-fawn-grooming.png",
+];
+
// ── Service definitions ──────────────────────────────────────────────────────
// Deterministic service IDs + UNIQUE(name) constraint make seed fully idempotent:
// first run inserts, subsequent runs update existing rows via ON CONFLICT (name).
@@ -368,6 +407,58 @@ async function seedKnownUsers() {
}
}
+ // ── Staff: UAT Super User (oidcSub from SEED_UAT_SUPER_OIDC_SUB env var) ──
+ const uatSuperOidcSub = process.env.SEED_UAT_SUPER_OIDC_SUB;
+ if (uatSuperOidcSub) {
+ const UAT_SUPER_STAFF_ID = "00000000-0000-0000-0000-000000000003";
+ const [existingUatSuper] = await db
+ .select()
+ .from(schema.staff)
+ .where(eq(schema.staff.email, "uat-super@groombook.dev"))
+ .limit(1);
+
+ if (existingUatSuper) {
+ console.log(`✓ Staff 'UAT Super User' already exists — skipping`);
+ } else {
+ await db.insert(schema.staff).values({
+ id: UAT_SUPER_STAFF_ID,
+ name: "UAT Super User",
+ email: "uat-super@groombook.dev",
+ oidcSub: uatSuperOidcSub,
+ role: "manager",
+ isSuperUser: true,
+ active: true,
+ });
+ console.log(`✓ Created staff 'UAT Super User' (oidcSub: ${uatSuperOidcSub})`);
+ }
+ }
+
+ // ── Staff: UAT Staff Groomer (oidcSub from SEED_UAT_STAFF_OIDC_SUB env var) ──
+ const uatStaffOidcSub = process.env.SEED_UAT_STAFF_OIDC_SUB;
+ if (uatStaffOidcSub) {
+ const UAT_STAFF_STAFF_ID = "00000000-0000-0000-0000-000000000004";
+ const [existingUatStaff] = await db
+ .select()
+ .from(schema.staff)
+ .where(eq(schema.staff.email, "uat-groomer@groombook.dev"))
+ .limit(1);
+
+ if (existingUatStaff) {
+ console.log(`✓ Staff 'UAT Staff Groomer' already exists — skipping`);
+ } else {
+ await db.insert(schema.staff).values({
+ id: UAT_STAFF_STAFF_ID,
+ name: "UAT Staff Groomer",
+ email: "uat-groomer@groombook.dev",
+ oidcSub: uatStaffOidcSub,
+ role: "groomer",
+ isSuperUser: false,
+ active: true,
+ });
+ console.log(`✓ Created staff 'UAT Staff Groomer' (oidcSub: ${uatStaffOidcSub})`);
+ }
+ }
+
// ── Services: idempotent upsert using name as unique key ─────────────────────
// UNIQUE constraint on services.name (migration 0020) must exist first.
// Uses b0000001-... IDs to match main seed servicesDef for same-named services.
@@ -569,6 +660,7 @@ async function seed() {
const clientRecords: ClientRecord[] = [];
const petRecords: PetRecord[] = [];
+ let petIndex = 0; // Track pet count to assign Puggle images to first 250 pets
const clientBatchSize = 50;
for (let batch = 0; batch < Math.ceil(cfg.clientCount / clientBatchSize); batch++) {
const clientBatch: (typeof schema.clients.$inferInsert)[] = [];
@@ -600,7 +692,7 @@ async function seed() {
const petCount = rand() < 0.5 ? 1 : rand() < 0.7 ? 2 : 3;
for (let p = 0; p < petCount; p++) {
const petId = uuid();
- const breed = pick(dogBreeds);
+ const breed = petIndex < 250 ? "Puggle" : pick(dogBreeds);
const dob = new Date(now);
dob.setFullYear(dob.getFullYear() - randInt(1, 14));
dob.setMonth(randInt(0, 11));
@@ -619,9 +711,11 @@ async function seed() {
shampooPreference: pick(shampoos),
specialCareNotes: rand() < 0.1 ? "Vet clearance required before grooming" : null,
customFields: {},
+ image: petIndex < 250 ? pick(puggleImages) : pick(demoPetImages),
});
petRecords.push({ id: petId, clientId });
+ petIndex++;
}
}
@@ -652,6 +746,7 @@ async function seed() {
shampooPreference: pet.shampooPreference,
specialCareNotes: pet.specialCareNotes,
customFields: pet.customFields,
+ image: pet.image,
},
});
}
@@ -686,8 +781,8 @@ async function seed() {
.values({ id: uc.id, name: uc.name, email: uc.email, phone: uc.phone, address: uc.address })
.onConflictDoUpdate({ target: schema.clients.id, set: { name: uc.name, email: uc.email, phone: uc.phone, address: uc.address } });
await db.insert(schema.pets)
- .values({ id: uc.petId, clientId: uc.id, name: uc.petName, species: "Dog", breed: uc.petBreed, weightKg: "25.00", dateOfBirth: new Date("2021-03-15T00:00:00Z") })
- .onConflictDoUpdate({ target: schema.pets.id, set: { clientId: uc.id, name: uc.petName, species: "Dog", breed: uc.petBreed, weightKg: "25.00", dateOfBirth: new Date("2021-03-15T00:00:00Z") } });
+ .values({ id: uc.petId, clientId: uc.id, name: uc.petName, species: "Dog", breed: uc.petBreed, weightKg: "25.00", dateOfBirth: new Date("2021-03-15T00:00:00Z"), image: pick(demoPetImages) })
+ .onConflictDoUpdate({ target: schema.pets.id, set: { clientId: uc.id, name: uc.petName, species: "Dog", breed: uc.petBreed, weightKg: "25.00", dateOfBirth: new Date("2021-03-15T00:00:00Z"), image: pick(demoPetImages) } });
// Create one completed appointment for this client
const apptId = uuid();
const svcIdx = 0;