Database
Updated to just use DATABASE_* in everything other than local docker.
This commit is contained in:
19 files changed
+120
-56
No files matched your search
+5
-1
@@ -1,6 +1,10 @@
|
|||||||
# Database (MySQL 8.0 — migrated from PostgreSQL in Phase 7, see
|
# Database (MySQL 8.0 — migrated from PostgreSQL in Phase 7, see
|
||||||
# docs/phase-7-summary.md; the production host only supports MySQL).
|
# docs/phase-7-summary.md; the production host only supports MySQL).
|
||||||
DATABASE_URL="mysql://user:password@localhost:3306/nextchair"
|
DATABASE_HOST="localhost"
|
||||||
|
DATABASE_PORT="3306"
|
||||||
|
DATABASE_NAME="nextchair"
|
||||||
|
DATABASE_USER="user"
|
||||||
|
DATABASE_PASSWORD="password"
|
||||||
# Optional: cap the connection pool (see src/lib/db/prisma.ts). Lower this
|
# Optional: cap the connection pool (see src/lib/db/prisma.ts). Lower this
|
||||||
# for a hosting provider with a small max-connections limit.
|
# for a hosting provider with a small max-connections limit.
|
||||||
DATABASE_CONNECTION_LIMIT="5"
|
DATABASE_CONNECTION_LIMIT="5"
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ concurrency:
|
|||||||
env:
|
env:
|
||||||
# A throwaway, non-secret DB the mysql service creates for this run only
|
# A throwaway, non-secret DB the mysql service creates for this run only
|
||||||
# — never a real deployment's database.
|
# — never a real deployment's database.
|
||||||
DATABASE_URL: mysql://root:root@127.0.0.1:3306/nextchair
|
DATABASE_HOST: 127.0.0.1
|
||||||
|
DATABASE_PORT: "3306"
|
||||||
|
DATABASE_NAME: nextchair
|
||||||
|
DATABASE_USER: root
|
||||||
|
DATABASE_PASSWORD: root
|
||||||
# Any fixed value works (see src/lib/booking-draft/sign.ts) — only needs
|
# Any fixed value works (see src/lib/booking-draft/sign.ts) — only needs
|
||||||
# to be consistent for the lifetime of this one CI run.
|
# to be consistent for the lifetime of this one CI run.
|
||||||
AUTH_SECRET: ci-run-secret-do-not-use-in-production
|
AUTH_SECRET: ci-run-secret-do-not-use-in-production
|
||||||
|
|||||||
@@ -51,3 +51,7 @@ yarn-error.log*
|
|||||||
.env.local.example
|
.env.local.example
|
||||||
.env.example
|
.env.example
|
||||||
.env.example
|
.env.example
|
||||||
|
|
||||||
|
.agents/
|
||||||
|
.claude/
|
||||||
|
.windsurf/
|
||||||
+14
-9
@@ -26,18 +26,23 @@ FROM node:22-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
# DATABASE_URL isn't needed to generate the client (no DB connection at
|
# These database env vars aren't needed to generate the client (no DB
|
||||||
# generate time) — only real values needed at runtime, never baked in here.
|
# connection at generate time) — only real values needed at runtime, never
|
||||||
|
# baked in here.
|
||||||
RUN npx prisma generate
|
RUN npx prisma generate
|
||||||
# `next build`'s page-data collection step imports every route module
|
# `next build`'s page-data collection step imports every route module
|
||||||
# (including /api/health) to inspect it — that pulls in
|
# (including /api/health) to inspect it — that pulls in
|
||||||
# src/lib/db/prisma.ts, which requires DATABASE_URL just to construct its
|
# src/lib/db/prisma.ts, which requires DATABASE_HOST/NAME/USER/PASSWORD
|
||||||
# connection-pool config object, even though nothing actually connects at
|
# just to construct its connection-pool config object, even though nothing
|
||||||
# build time. This placeholder only exists to satisfy that check; the
|
# actually connects at build time. These placeholders only exist to
|
||||||
# real value is supplied at container runtime via env_file (see
|
# satisfy that check; the real values are supplied at container runtime
|
||||||
# docker-compose.local.yaml) and this one is never used to connect to
|
# via env_file (see docker-compose.local.yaml) and these are never used to
|
||||||
# anything or baked into a layer that matters.
|
# connect to anything or baked into a layer that matters.
|
||||||
ENV DATABASE_URL="mysql://build:build@localhost:3306/build"
|
ENV DATABASE_HOST="localhost"
|
||||||
|
ENV DATABASE_PORT="3306"
|
||||||
|
ENV DATABASE_NAME="build"
|
||||||
|
ENV DATABASE_USER="build"
|
||||||
|
ENV DATABASE_PASSWORD="build"
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:22-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
|
|||||||
+8
-7
@@ -2,16 +2,17 @@ import { execFileSync } from "node:child_process";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies migrations and loads the dev/demo seed data against whatever
|
* Applies migrations and loads the dev/demo seed data against whatever
|
||||||
* DATABASE_URL points at before the suite runs. Deliberately does not
|
* DATABASE_HOST/PORT/NAME/USER/PASSWORD point at before the suite runs.
|
||||||
* create or tear down the database itself — same division of
|
* Deliberately does not create or tear down the database itself — same
|
||||||
* responsibility as `npm run test:integration`, where the caller is
|
* division of responsibility as `npm run test:integration`, where the
|
||||||
* responsible for pointing DATABASE_URL at a disposable Postgres.
|
* caller is responsible for pointing these at a disposable database.
|
||||||
*/
|
*/
|
||||||
export default function globalSetup() {
|
export default function globalSetup() {
|
||||||
if (!process.env.DATABASE_URL) {
|
if (!process.env.DATABASE_HOST || !process.env.DATABASE_NAME || !process.env.DATABASE_USER) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"DATABASE_URL is not set. Point it at a disposable Postgres before running the e2e suite, e.g.\n" +
|
"Database connection is not configured. Point DATABASE_HOST, DATABASE_NAME, DATABASE_USER, and " +
|
||||||
' DATABASE_URL="postgresql://user:pass@localhost:5432/nextchair_e2e" npm run test:e2e',
|
"DATABASE_PASSWORD at a disposable MySQL database before running the e2e suite, e.g.\n" +
|
||||||
|
' DATABASE_HOST=localhost DATABASE_NAME=nextchair_e2e DATABASE_USER=user DATABASE_PASSWORD=pass npm run test:e2e',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { SEEDED, completeBookingFlow, signIn, uniqueEmail } from "./helpers";
|
|||||||
|
|
||||||
test.describe.serial("owner administration journeys", () => {
|
test.describe.serial("owner administration journeys", () => {
|
||||||
test.afterAll(async () => {
|
test.afterAll(async () => {
|
||||||
const pool = createPool(buildMysqlPoolConfig(process.env.DATABASE_URL));
|
const pool = createPool(buildMysqlPoolConfig());
|
||||||
// Journey 7 creates a category/service with no deterministic id to
|
// Journey 7 creates a category/service with no deterministic id to
|
||||||
// upsert against on a re-run — clean up by name instead.
|
// upsert against on a re-run — clean up by name instead.
|
||||||
await pool.query("DELETE FROM `Service` WHERE name LIKE 'E2E Test Service%'");
|
await pool.query("DELETE FROM `Service` WHERE name LIKE 'E2E Test Service%'");
|
||||||
@@ -108,7 +108,7 @@ test.describe.serial("owner administration journeys", () => {
|
|||||||
// them from an earlier seed run their date is whatever it was back
|
// them from an earlier seed run their date is whatever it was back
|
||||||
// then, not necessarily still "N days from today" — read the real date
|
// then, not necessarily still "N days from today" — read the real date
|
||||||
// straight from the database instead of predicting it from wall-clock time.
|
// straight from the database instead of predicting it from wall-clock time.
|
||||||
const pool = createPool(buildMysqlPoolConfig(process.env.DATABASE_URL));
|
const pool = createPool(buildMysqlPoolConfig());
|
||||||
const rows = await pool.query<{ id: string; start_at: Date; first_name: string; last_name: string }[]>(
|
const rows = await pool.query<{ id: string; start_at: Date; first_name: string; last_name: string }[]>(
|
||||||
"SELECT a.id, a.startAt as start_at, c.firstName as first_name, c.lastName as last_name" +
|
"SELECT a.id, a.startAt as start_at, c.firstName as first_name, c.lastName as last_name" +
|
||||||
" FROM Appointment a JOIN CustomerProfile c ON c.id = a.customerId" +
|
" FROM Appointment a JOIN CustomerProfile c ON c.id = a.customerId" +
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ test.describe.serial("barber operations journeys", () => {
|
|||||||
// safety net removes them directly regardless of how the tests above
|
// safety net removes them directly regardless of how the tests above
|
||||||
// exited, on top of (not instead of) each test's own UI-driven cleanup.
|
// exited, on top of (not instead of) each test's own UI-driven cleanup.
|
||||||
test.afterAll(async () => {
|
test.afterAll(async () => {
|
||||||
const pool = createPool(buildMysqlPoolConfig(process.env.DATABASE_URL));
|
const pool = createPool(buildMysqlPoolConfig());
|
||||||
await pool.query("DELETE FROM `TimeOff` WHERE reason = 'E2E conflict test'");
|
await pool.query("DELETE FROM `TimeOff` WHERE reason = 'E2E conflict test'");
|
||||||
await pool.query("DELETE FROM `Break` WHERE label = 'Afternoon tea'");
|
await pool.query("DELETE FROM `Break` WHERE label = 'Afternoon tea'");
|
||||||
await pool.end();
|
await pool.end();
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { defineConfig, devices } from "@playwright/test";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* End-to-end customer journeys — real browser, real dev server, real
|
* End-to-end customer journeys — real browser, real dev server, real
|
||||||
* Postgres. Needs DATABASE_URL pointed at a throwaway database before
|
* MySQL. Needs DATABASE_HOST/DATABASE_NAME/DATABASE_USER/DATABASE_PASSWORD
|
||||||
* running (same "migrate, seed, test, tear down" pattern as
|
* pointed at a throwaway database before running (same "migrate, seed,
|
||||||
* `npm run test:integration` — see docs/phase-3-summary.md and
|
* test, tear down" pattern as `npm run test:integration` — see
|
||||||
* docs/phase-4-summary.md). `globalSetup` applies migrations and seed
|
* docs/phase-3-summary.md and docs/phase-4-summary.md). `globalSetup`
|
||||||
* data automatically so a single `npm run test:e2e` is enough once
|
* applies migrations and seed data automatically so a single
|
||||||
* DATABASE_URL is set.
|
* `npm run test:e2e` is enough once those are set.
|
||||||
*/
|
*/
|
||||||
const PORT = 3100;
|
const PORT = 3100;
|
||||||
const baseURL = `http://localhost:${PORT}`;
|
const baseURL = `http://localhost:${PORT}`;
|
||||||
@@ -34,7 +34,6 @@ export default defineConfig({
|
|||||||
timeout: 60_000,
|
timeout: 60_000,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
DATABASE_URL: process.env.DATABASE_URL ?? "",
|
|
||||||
// Any fixed value is fine — see src/lib/booking-draft/sign.ts. Only
|
// Any fixed value is fine — see src/lib/booking-draft/sign.ts. Only
|
||||||
// needs to be consistent for the lifetime of this one server process.
|
// needs to be consistent for the lifetime of this one server process.
|
||||||
AUTH_SECRET: process.env.AUTH_SECRET ?? "playwright-e2e-test-secret-do-not-use-in-production",
|
AUTH_SECRET: process.env.AUTH_SECRET ?? "playwright-e2e-test-secret-do-not-use-in-production",
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,12 @@
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import { defineConfig } from "prisma/config";
|
import { defineConfig } from "prisma/config";
|
||||||
|
import { buildMysqlConnectionUrl } from "./src/lib/db/mysql-pool-config";
|
||||||
|
|
||||||
|
// The Prisma CLI (migrate/introspect) needs a connection string, not
|
||||||
|
// discrete fields — this app's own runtime code never uses this URL (see
|
||||||
|
// src/lib/db/mysql-pool-config.ts's buildMysqlPoolConfig, which reads
|
||||||
|
// DATABASE_HOST/PORT/NAME/USER/PASSWORD directly), so it's built here only
|
||||||
|
// to satisfy the CLI.
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
schema: "prisma/schema.prisma",
|
schema: "prisma/schema.prisma",
|
||||||
migrations: {
|
migrations: {
|
||||||
@@ -8,6 +14,6 @@ export default defineConfig({
|
|||||||
seed: "tsx prisma/seed.ts",
|
seed: "tsx prisma/seed.ts",
|
||||||
},
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
url: process.env["DATABASE_URL"],
|
url: buildMysqlConnectionUrl(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Dev/demo fixtures. Run with `npm run prisma:seed` (requires a reachable
|
* Dev/demo fixtures. Run with `npm run prisma:seed` (requires a reachable
|
||||||
* DATABASE_URL — see .env.example). Intentionally small: enough to exercise
|
* DATABASE_HOST/NAME/USER/PASSWORD — see .env.example). Intentionally small: enough to exercise
|
||||||
* every relation in the schema once, not a full demo dataset.
|
* every relation in the schema once, not a full demo dataset.
|
||||||
*/
|
*/
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
@@ -28,7 +28,7 @@ function minutesFromNow(minutes: number): Date {
|
|||||||
return new Date(Date.now() + minutes * 60_000);
|
return new Date(Date.now() + minutes * 60_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const adapter = new PrismaMariaDb(buildMysqlPoolConfig(process.env.DATABASE_URL));
|
const adapter = new PrismaMariaDb(buildMysqlPoolConfig());
|
||||||
const prisma = new PrismaClient({ adapter });
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
const WEEKDAYS: Weekday[] = [
|
const WEEKDAYS: Weekday[] = [
|
||||||
|
|||||||
+10
-2
@@ -19,11 +19,19 @@
|
|||||||
// non-GET request (Server Actions, API mutations) is never intercepted at
|
// non-GET request (Server Actions, API mutations) is never intercepted at
|
||||||
// all — this worker must never sit between the app and a write.
|
// all — this worker must never sit between the app and a write.
|
||||||
|
|
||||||
const CACHE_VERSION = "v1";
|
// v2: /manifest.webmanifest was previously cache-first here alongside the
|
||||||
|
// truly static icon fallbacks — but it's dynamic, per-business content
|
||||||
|
// (name/icons/colors, editable from "App Icon & Home Screen" settings),
|
||||||
|
// not a static build asset. Cache-first meant a phone that had ever
|
||||||
|
// loaded it once would keep serving that stale response indefinitely,
|
||||||
|
// regardless of any later branding change or server-side fix — bumping
|
||||||
|
// CACHE_VERSION forces every existing install to drop its old cache
|
||||||
|
// (including any previously-cached manifest response) on next activate.
|
||||||
|
const CACHE_VERSION = "v2";
|
||||||
const CACHE_NAME = `nextchair-shell-${CACHE_VERSION}`;
|
const CACHE_NAME = `nextchair-shell-${CACHE_VERSION}`;
|
||||||
const OFFLINE_URL = "/offline";
|
const OFFLINE_URL = "/offline";
|
||||||
|
|
||||||
const APP_SHELL = [OFFLINE_URL, "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
const APP_SHELL = [OFFLINE_URL, "/icons/icon-192.png", "/icons/icon-512.png"];
|
||||||
|
|
||||||
self.addEventListener("install", (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ async function main() {
|
|||||||
const lastName = process.env.SUPER_ADMIN_LAST_NAME?.trim() ?? "";
|
const lastName = process.env.SUPER_ADMIN_LAST_NAME?.trim() ?? "";
|
||||||
const name = [firstName, lastName].filter(Boolean).join(" ") || null;
|
const name = [firstName, lastName].filter(Boolean).join(" ") || null;
|
||||||
|
|
||||||
const adapter = new PrismaMariaDb(buildMysqlPoolConfig(process.env.DATABASE_URL));
|
const adapter = new PrismaMariaDb(buildMysqlPoolConfig());
|
||||||
const prisma = new PrismaClient({ adapter });
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const getBranding = cache(async (): Promise<BrandingInfo> => {
|
|||||||
// Reached from the root layout's generateMetadata, which Next may
|
// Reached from the root layout's generateMetadata, which Next may
|
||||||
// invoke while statically prerendering a route at *build* time (e.g.
|
// invoke while statically prerendering a route at *build* time (e.g.
|
||||||
// /_not-found) — no real database is reachable then (see the
|
// /_not-found) — no real database is reachable then (see the
|
||||||
// Dockerfile's placeholder build-time DATABASE_URL). A transient/
|
// Dockerfile's placeholder build-time database env vars). A transient/
|
||||||
// unreachable database is treated exactly like "not installed yet":
|
// unreachable database is treated exactly like "not installed yet":
|
||||||
// neutral fallback wording, never a crashed build or a 500 at runtime.
|
// neutral fallback wording, never a crashed build or a 500 at runtime.
|
||||||
let business;
|
let business;
|
||||||
|
|||||||
@@ -1,31 +1,64 @@
|
|||||||
/**
|
/**
|
||||||
* Builds the `mariadb` pool config from `DATABASE_URL`, shared by every
|
* Builds the `mariadb` pool config from the discrete `DATABASE_HOST` /
|
||||||
* Prisma Client instance in the app (the main app client, the seed
|
* `DATABASE_PORT` / `DATABASE_NAME` / `DATABASE_USER` / `DATABASE_PASSWORD`
|
||||||
* script, and the integration-test fixtures) so the UTC-timezone and
|
* env vars, shared by every Prisma Client instance in the app (the main
|
||||||
* connection-limit handling can't drift between them. See
|
* app client, the seed script, and the integration-test fixtures) so the
|
||||||
|
* UTC-timezone and connection-limit handling can't drift between them. See
|
||||||
* src/lib/db/prisma.ts for why `timezone`/`sessionVariables` matter — this
|
* src/lib/db/prisma.ts for why `timezone`/`sessionVariables` matter — this
|
||||||
* file exists only because that reasoning needs to apply to all three
|
* file exists only because that reasoning needs to apply to all three
|
||||||
* call sites, not because the logic itself is complex.
|
* call sites, not because the logic itself is complex.
|
||||||
*/
|
*/
|
||||||
export function buildMysqlPoolConfig(databaseUrl: string | undefined) {
|
export function buildMysqlPoolConfig() {
|
||||||
if (!databaseUrl) throw new Error("DATABASE_URL is not set.");
|
const host = process.env.DATABASE_HOST;
|
||||||
|
const database = process.env.DATABASE_NAME;
|
||||||
|
const user = process.env.DATABASE_USER;
|
||||||
|
const password = process.env.DATABASE_PASSWORD;
|
||||||
|
|
||||||
// Accept both `mysql://` and `mariadb://` — the `mariadb` driver's own
|
if (!host || !database || !user || password === undefined) {
|
||||||
// string parser only recognizes the latter, but the `URL` global parses
|
throw new Error(
|
||||||
// either scheme fine since we build the pool config object ourselves.
|
"Database connection is not configured. Set DATABASE_HOST, DATABASE_NAME, DATABASE_USER, and DATABASE_PASSWORD (DATABASE_PORT is optional, defaults to 3306).",
|
||||||
const url = new URL(databaseUrl);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
host: url.hostname,
|
host,
|
||||||
port: url.port ? Number(url.port) : 3306,
|
port: process.env.DATABASE_PORT ? Number(process.env.DATABASE_PORT) : 3306,
|
||||||
user: decodeURIComponent(url.username),
|
user,
|
||||||
password: decodeURIComponent(url.password),
|
password,
|
||||||
database: url.pathname.replace(/^\//, ""),
|
database,
|
||||||
connectionLimit: Number(process.env.DATABASE_CONNECTION_LIMIT ?? 5),
|
connectionLimit: Number(process.env.DATABASE_CONNECTION_LIMIT ?? 5),
|
||||||
connectTimeout: Number(process.env.DATABASE_CONNECT_TIMEOUT_MS ?? 10_000),
|
connectTimeout: Number(process.env.DATABASE_CONNECT_TIMEOUT_MS ?? 10_000),
|
||||||
// Load-bearing, not cosmetic — see src/lib/db/prisma.ts's file header.
|
// Load-bearing, not cosmetic — see src/lib/db/prisma.ts's file header.
|
||||||
timezone: "Etc/UTC",
|
timezone: "Etc/UTC",
|
||||||
sessionVariables: { time_zone: "+00:00" },
|
sessionVariables: { time_zone: "+00:00" },
|
||||||
ssl: process.env.DATABASE_SSL === "true" ? { rejectUnauthorized: true } : undefined,
|
ssl: process.env.DATABASE_SSL === "true" ? { rejectUnauthorized: true } : undefined,
|
||||||
|
// MySQL 8's default auth plugin (caching_sha2_password) needs to send
|
||||||
|
// the client its RSA public key to encrypt the password, and the
|
||||||
|
// `mariadb` driver refuses to fetch it over a plain connection unless
|
||||||
|
// told to — surfacing as "Public Key Retrieval is not allowed" on
|
||||||
|
// every connection attempt (not intermittent, not env-specific: it
|
||||||
|
// fires against any caching_sha2_password server whenever DATABASE_SSL
|
||||||
|
// isn't "true"). Opting in doesn't weaken authentication itself, only
|
||||||
|
// lets the client ask for that public key; a connection that's already
|
||||||
|
// on `ssl` above doesn't need this exchange in the first place.
|
||||||
|
allowPublicKeyRetrieval: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a `mysql://` connection string from the same discrete env vars,
|
||||||
|
* for the one place that genuinely needs a URL rather than a pool config
|
||||||
|
* object: prisma.config.ts, whose `datasource.url` is a plain string
|
||||||
|
* because the Prisma CLI (migrate/introspect) doesn't accept discrete
|
||||||
|
* connection fields.
|
||||||
|
*/
|
||||||
|
export function buildMysqlConnectionUrl(): string | undefined {
|
||||||
|
const host = process.env.DATABASE_HOST;
|
||||||
|
const database = process.env.DATABASE_NAME;
|
||||||
|
const user = process.env.DATABASE_USER;
|
||||||
|
const password = process.env.DATABASE_PASSWORD;
|
||||||
|
if (!host || !database || !user || password === undefined) return undefined;
|
||||||
|
|
||||||
|
const port = process.env.DATABASE_PORT || "3306";
|
||||||
|
return `mysql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ const globalForPrisma = globalThis as unknown as {
|
|||||||
prisma: PrismaClient | undefined;
|
prisma: PrismaClient | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const adapter = new PrismaMariaDb(buildMysqlPoolConfig(process.env.DATABASE_URL));
|
const adapter = new PrismaMariaDb(buildMysqlPoolConfig());
|
||||||
|
|
||||||
export const prisma =
|
export const prisma =
|
||||||
globalForPrisma.prisma ??
|
globalForPrisma.prisma ??
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import path from "node:path";
|
|||||||
* Real filesystem I/O against a throwaway temp directory (via
|
* Real filesystem I/O against a throwaway temp directory (via
|
||||||
* MEDIA_STORAGE_ROOT), not mocks — this module's whole job is getting
|
* MEDIA_STORAGE_ROOT), not mocks — this module's whole job is getting
|
||||||
* real file paths right, so a mocked `fs` would test nothing useful. No
|
* real file paths right, so a mocked `fs` would test nothing useful. No
|
||||||
* DATABASE_URL/Prisma dependency here (media-storage.ts is DB-free), so
|
* database/Prisma dependency here (media-storage.ts is DB-free), so
|
||||||
* this stays safe to run in the plain unit suite. The module is
|
* this stays safe to run in the plain unit suite. The module is
|
||||||
* dynamically imported after MEDIA_STORAGE_ROOT is set, since it reads
|
* dynamically imported after MEDIA_STORAGE_ROOT is set, since it reads
|
||||||
* that env var once at module-load time.
|
* that env var once at module-load time.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { BusinessReviewSummary, ReviewsProvider } from "../reviews.service"
|
|||||||
describe("reviews.service", () => {
|
describe("reviews.service", () => {
|
||||||
// The module has no eager default provider (see reviews.service.ts's
|
// The module has no eager default provider (see reviews.service.ts's
|
||||||
// header — it lazily resolves a real, DB-backed provider on first use
|
// header — it lazily resolves a real, DB-backed provider on first use
|
||||||
// otherwise, which would need a live DATABASE_URL). Every test here
|
// otherwise, which would need live database env vars). Every test here
|
||||||
// explicitly opts into the demo provider so none of them ever touch it.
|
// explicitly opts into the demo provider so none of them ever touch it.
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setReviewsProvider(createDemoReviewsProvider());
|
setReviewsProvider(createDemoReviewsProvider());
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ export class DbReviewsProvider implements ReviewsProvider {
|
|||||||
* `null` = no explicit override; resolved lazily to a real DbReviewsProvider
|
* `null` = no explicit override; resolved lazily to a real DbReviewsProvider
|
||||||
* on first use. Deliberately NOT constructed eagerly at module load — this
|
* on first use. Deliberately NOT constructed eagerly at module load — this
|
||||||
* file must stay importable from a pure unit-test context with no
|
* file must stay importable from a pure unit-test context with no
|
||||||
* DATABASE_URL (see reviews.service.test.ts), and src/lib/db/prisma.ts
|
* database env vars set (see reviews.service.test.ts), and src/lib/db/prisma.ts
|
||||||
* throws at import time if that's unset.
|
* throws at import time if they're unset.
|
||||||
*/
|
*/
|
||||||
let provider: ReviewsProvider | null = null;
|
let provider: ReviewsProvider | null = null;
|
||||||
let cachedDefaultProvider: ReviewsProvider | null = null;
|
let cachedDefaultProvider: ReviewsProvider | null = null;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Weekday } from "@/generated/prisma/enums";
|
|||||||
* (migrated from PostgreSQL to MySQL in Phase 7).
|
* (migrated from PostgreSQL to MySQL in Phase 7).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const adapter = new PrismaMariaDb(buildMysqlPoolConfig(process.env.DATABASE_URL));
|
const adapter = new PrismaMariaDb(buildMysqlPoolConfig());
|
||||||
export const prisma = new PrismaClient({ adapter });
|
export const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
|||||||
Reference in new issue
Block a user