diff --git a/Dockerfile b/Dockerfile index 89ff123..1325fbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,9 @@ ENV CI=true ENV NEXT_TELEMETRY_DISABLED=1 ENV DATABASE_URL=postgres://build:build@localhost:5432/build ENV AUTH_SECRET=build-time-placeholder +# Build-only placeholder. Runtime public URL is AUTH_URL from stack.env — never prefer this. ENV NEXT_PUBLIC_APP_URL=http://localhost:3000 +ENV AUTH_URL=http://localhost:3000 COPY . . RUN pnpm install --offline --frozen-lockfile RUN --mount=type=cache,id=famapp-nextjs,target=/app/.next/cache \ diff --git a/src/lib/app-url.ts b/src/lib/app-url.ts new file mode 100644 index 0000000..783629a --- /dev/null +++ b/src/lib/app-url.ts @@ -0,0 +1,8 @@ +/** Public origin for absolute links. AUTH_URL wins — Docker build sets NEXT_PUBLIC to localhost. */ +export function getAppPublicUrl(): string { + const raw = + process.env.AUTH_URL?.trim() || + process.env.NEXT_PUBLIC_APP_URL?.trim() || + "http://localhost:3000"; + return raw.replace(/\/$/, ""); +} diff --git a/src/modules/_core/share-api.ts b/src/modules/_core/share-api.ts index 754a241..c9025df 100644 --- a/src/modules/_core/share-api.ts +++ b/src/modules/_core/share-api.ts @@ -1,6 +1,7 @@ import { createHash, randomBytes } from "crypto"; import { and, eq, isNull } from "drizzle-orm"; import type { ApiAuthContext } from "@/lib/api-auth"; +import { getAppPublicUrl } from "@/lib/app-url"; import { db } from "@/lib/db"; import { getEntityType, getRegistry } from "./registry"; import { shareLinks } from "./schema"; @@ -27,9 +28,7 @@ function hashToken(raw: string): string { } function buildUrl(token: string): string { - const base = - process.env["NEXT_PUBLIC_APP_URL"] ?? process.env["AUTH_URL"] ?? "http://localhost:3000"; - return `${base}/s/${token}`; + return `${getAppPublicUrl()}/s/${token}`; } export function listShareableEntityTypes() { diff --git a/src/modules/_core/share.ts b/src/modules/_core/share.ts index e3d6e30..cc6b0f8 100644 --- a/src/modules/_core/share.ts +++ b/src/modules/_core/share.ts @@ -2,6 +2,7 @@ import { createHash, randomBytes } from "crypto"; import { and, eq, isNull } from "drizzle-orm"; +import { getAppPublicUrl } from "@/lib/app-url"; import { db } from "@/lib/db"; import { getCurrentSession } from "@/lib/session"; import { getEntityType } from "./registry"; @@ -21,9 +22,7 @@ function hashToken(raw: string): string { } function buildUrl(token: string): string { - const base = - process.env["NEXT_PUBLIC_APP_URL"] ?? process.env["AUTH_URL"] ?? "http://localhost:3000"; - return `${base}/s/${token}`; + return `${getAppPublicUrl()}/s/${token}`; } export async function createShareLink(