diff --git a/apps/api/src/lib/auth.ts b/apps/api/src/lib/auth.ts index 020e540..50daaab 100644 --- a/apps/api/src/lib/auth.ts +++ b/apps/api/src/lib/auth.ts @@ -171,17 +171,21 @@ export async function initAuth(): Promise { const hasGoogle = !!(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET); const hasGitHub = !!(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET); + const callbackBase = `${BETTER_AUTH_URL}/api/auth/callback`; + const socialPlugins = []; if (hasGoogle) { socialPlugins.push(google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + redirectURI: `${callbackBase}/google`, })); } if (hasGitHub) { socialPlugins.push(github({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, + redirectURI: `${callbackBase}/github`, })); } 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..f18f5f7 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,44 @@ 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-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 +406,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 +659,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 +691,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 +710,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 +745,7 @@ async function seed() { shampooPreference: pet.shampooPreference, specialCareNotes: pet.specialCareNotes, customFields: pet.customFields, + image: pet.image, }, }); } @@ -686,8 +780,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;