diff --git a/docker-compose.yaml b/docker-compose.yaml index 818db44..3b3b155 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -62,8 +62,10 @@ services: image: directus/directus:11 restart: always networks: - - infra - - backend + infra: + aliases: + - ${PROJECT_NAME:-mb-grid-solutions}-directus + backend: env_file: - ${ENV_FILE:-.env} environment: diff --git a/lib/directus.ts b/lib/directus.ts index 7fbd5e9..1792efe 100644 --- a/lib/directus.ts +++ b/lib/directus.ts @@ -1,43 +1,28 @@ -import { createDirectus, rest, authentication } from "@directus/sdk"; -import { config } from "./config"; +import { + createMintelDirectusClient, + ensureDirectusAuthenticated, +} from "@mintel/next-utils"; import { getServerAppServices } from "./services/create-services.server"; -const { url, adminEmail, password, token, internalUrl } = config.directus; - -// Use internal URL if on server to bypass Gatekeeper/Auth/Proxy issues -const effectiveUrl = - typeof window === "undefined" && internalUrl ? internalUrl : url; - -const client = createDirectus(effectiveUrl).with(rest()).with(authentication()); +// Initialize client using Mintel standards (environment-aware) +const client = createMintelDirectusClient(); /** * Ensures the client is authenticated. - * Falls back to login with admin credentials if no static token is provided. + * Standardized using @mintel/next-utils ensureDirectusAuthenticated. */ export async function ensureAuthenticated() { - if (token) { - client.setToken(token); - return; - } - - if (adminEmail && password) { - try { - await client.login({ email: adminEmail, password: password }); - return; - } catch (e) { - if (typeof window === "undefined") { - getServerAppServices().errors.captureException(e, { - phase: "directus_auth_fallback", - }); - } - console.error("Failed to authenticate with Directus login fallback:", e); - throw e; + try { + await ensureDirectusAuthenticated(client); + } catch (e) { + if (typeof window === "undefined") { + getServerAppServices().errors.captureException(e, { + phase: "directus_auth_standardized", + }); } + console.error("Failed to authenticate with Directus:", e); + throw e; } - - throw new Error( - "Missing Directus authentication credentials (token or admin email/password)", - ); } export default client; diff --git a/lib/env.ts b/lib/env.ts index 4eb01e1..ea3e43c 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -1,144 +1,33 @@ import { z } from "zod"; - -/** - * Helper to treat empty strings as undefined. - */ -const preprocessEmptyString = (val: unknown) => (val === "" ? undefined : val); +import { validateMintelEnv, mintelEnvSchema } from "@mintel/next-utils"; /** * Environment variable schema. + * Extends the default Mintel environment schema which already includes: + * - Directus (URL, TOKEN, INTERNAL_URL, etc.) + * - Mail (HOST, PORT, etc.) + * - Gotify + * - Logging + * - Analytics */ -export const envSchema = z - .object({ - NODE_ENV: z - .enum(["development", "production", "test"]) - .default("development"), - NEXT_PUBLIC_BASE_URL: z.preprocess( - preprocessEmptyString, - z.string().url().optional(), - ), - NEXT_PUBLIC_TARGET: z - .enum(["development", "testing", "staging", "production"]) - .optional(), +export const envSchema = z.object({ + ...mintelEnvSchema, - // Analytics - UMAMI_WEBSITE_ID: z.preprocess( - preprocessEmptyString, - z.string().optional(), - ), - NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.preprocess( - preprocessEmptyString, - z.string().optional(), - ), - UMAMI_API_ENDPOINT: z.preprocess( - preprocessEmptyString, - z.string().url().default("https://analytics.infra.mintel.me"), - ), + // Project specific overrides or additions + AUTH_COOKIE_NAME: z.string().default("mb_gatekeeper_session"), - // Error Tracking - SENTRY_DSN: z.preprocess(preprocessEmptyString, z.string().optional()), - - // Logging - LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), - - // Mail - MAIL_HOST: z.preprocess(preprocessEmptyString, z.string().optional()), - MAIL_PORT: z.preprocess( - preprocessEmptyString, - z.coerce.number().default(587), - ), - MAIL_USERNAME: z.preprocess(preprocessEmptyString, z.string().optional()), - MAIL_PASSWORD: z.preprocess(preprocessEmptyString, z.string().optional()), - MAIL_FROM: z.preprocess(preprocessEmptyString, z.string().optional()), - MAIL_RECIPIENTS: z.preprocess( - (val) => (typeof val === "string" ? val.split(",").filter(Boolean) : val), - z.array(z.string()).default([]), - ), - - // Directus - DIRECTUS_URL: z.preprocess( - preprocessEmptyString, - z.string().url().default("http://localhost:8055"), - ), - DIRECTUS_ADMIN_EMAIL: z.preprocess( - preprocessEmptyString, - z.string().optional(), - ), - DIRECTUS_ADMIN_PASSWORD: z.preprocess( - preprocessEmptyString, - z.string().optional(), - ), - DIRECTUS_API_TOKEN: z.preprocess( - preprocessEmptyString, - z.string().optional(), - ), - INTERNAL_DIRECTUS_URL: z.preprocess( - preprocessEmptyString, - z.string().url().optional(), - ), - - // Deploy Target - TARGET: z - .enum(["development", "testing", "staging", "production"]) - .optional(), - // Gotify - GOTIFY_URL: z.preprocess( - preprocessEmptyString, - z.string().url().optional(), - ), - GOTIFY_TOKEN: z.preprocess(preprocessEmptyString, z.string().optional()), - }) - .superRefine((data, ctx) => { - const target = data.NEXT_PUBLIC_TARGET || data.TARGET; - const isDev = target === "development" || !target; - const isBuildTimeValidation = - process.env.SKIP_RUNTIME_ENV_VALIDATION === "true"; - const isServer = typeof window === "undefined"; - - // Only enforce server-only variables when running on the server. - // In the browser, non-NEXT_PUBLIC_ variables are undefined and should not trigger validation errors. - if (isServer && !isDev && !isBuildTimeValidation && !data.MAIL_HOST) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "MAIL_HOST is required in non-development environments", - path: ["MAIL_HOST"], - }); - } - }); - -export type Env = z.infer; + INFRA_DIRECTUS_URL: z.string().url().optional(), + INFRA_DIRECTUS_TOKEN: z.string().optional(), +}); /** - * Collects all environment variables from the process. - * Explicitly references NEXT_PUBLIC_ variables for Next.js inlining. + * Validated environment object. + */ +export const env = validateMintelEnv(envSchema.shape); + +/** + * For legacy compatibility with existing code. */ export function getRawEnv() { - return { - NODE_ENV: process.env.NODE_ENV, - NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL, - NEXT_PUBLIC_TARGET: process.env.NEXT_PUBLIC_TARGET, - UMAMI_WEBSITE_ID: - process.env.UMAMI_WEBSITE_ID || process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID, - NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID, - UMAMI_API_ENDPOINT: - process.env.UMAMI_API_ENDPOINT || - process.env.UMAMI_SCRIPT_URL || - process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL, - SENTRY_DSN: process.env.SENTRY_DSN, - LOG_LEVEL: process.env.LOG_LEVEL, - MAIL_HOST: process.env.MAIL_HOST, - MAIL_PORT: process.env.MAIL_PORT, - MAIL_USERNAME: process.env.MAIL_USERNAME, - MAIL_PASSWORD: process.env.MAIL_PASSWORD, - MAIL_FROM: process.env.MAIL_FROM, - MAIL_RECIPIENTS: process.env.MAIL_RECIPIENTS, - DIRECTUS_URL: process.env.DIRECTUS_URL, - DIRECTUS_ADMIN_EMAIL: process.env.DIRECTUS_ADMIN_EMAIL, - DIRECTUS_ADMIN_PASSWORD: process.env.DIRECTUS_ADMIN_PASSWORD, - DIRECTUS_API_TOKEN: process.env.DIRECTUS_API_TOKEN, - INTERNAL_DIRECTUS_URL: process.env.INTERNAL_DIRECTUS_URL, - TARGET: process.env.TARGET, - GOTIFY_URL: process.env.GOTIFY_URL, - GOTIFY_TOKEN: process.env.GOTIFY_TOKEN, - }; + return env; }