promote: dev → uat (UAT Tester seed fix + TypeScript CI compliance)

promote: dev → uat (UAT Tester seed fix + TypeScript CI compliance)
This commit was merged in pull request #8.
This commit is contained in:
the-dogfather-cto[bot]
2026-05-14 08:07:54 +00:00
committed by GitHub
30 changed files with 58 additions and 37 deletions
+27 -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;
@@ -459,6 +454,32 @@ async function seedKnownUsers() {
} }
} }
// ── Staff: UAT Tester (oidcSub from SEED_UAT_TESTER_OIDC_SUB env var) ──
const uatTesterOidcSub = process.env.SEED_UAT_TESTER_OIDC_SUB;
if (uatTesterOidcSub) {
const UAT_TESTER_STAFF_ID = "00000000-0000-0000-0000-000000000007";
const [existingUatTester] = await db
.select()
.from(schema.staff)
.where(eq(schema.staff.email, "uat-tester@groombook.dev"))
.limit(1);
if (existingUatTester) {
console.log(`✓ Staff 'UAT Tester' already exists — skipping`);
} else {
await db.insert(schema.staff).values({
id: UAT_TESTER_STAFF_ID,
name: "UAT Tester",
email: "uat-tester@groombook.dev",
oidcSub: uatTesterOidcSub,
role: "groomer",
isSuperUser: false,
active: true,
});
console.log(`✓ Created staff 'UAT Tester' (oidcSub: ${uatTesterOidcSub})`);
}
}
// ── Staff: UAT Groomer Personas (SEED_UAT_GROOMER_EMAILS + SEED_UAT_GROOMER_NAMES) ── // ── Staff: UAT Groomer Personas (SEED_UAT_GROOMER_EMAILS + SEED_UAT_GROOMER_NAMES) ──
const groomerEmails = process.env.SEED_UAT_GROOMER_EMAILS?.split(",").map((e) => e.trim()).filter(Boolean) ?? []; const groomerEmails = process.env.SEED_UAT_GROOMER_EMAILS?.split(",").map((e) => e.trim()).filter(Boolean) ?? [];
const groomerNames = process.env.SEED_UAT_GROOMER_NAMES?.split(",").map((n) => n.trim()).filter(Boolean) ?? []; const groomerNames = process.env.SEED_UAT_GROOMER_NAMES?.split(",").map((n) => n.trim()).filter(Boolean) ?? [];
@@ -1079,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";
+2 -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;
+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>();
+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, 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,
+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(