feat: Add support for an internal Directus URL for server-side communication and enhance the health check with schema validation for the products collection.
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 21s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Successful in 1m33s
Build & Deploy KLZ Cables / 🏗️ Build & Push (push) Successful in 2m53s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Successful in 40s
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Failing after 1m5s
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 1s
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 21s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Successful in 1m33s
Build & Deploy KLZ Cables / 🏗️ Build & Push (push) Successful in 2m53s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Successful in 40s
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Failing after 1m5s
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 1s
This commit is contained in:
@@ -285,6 +285,7 @@ jobs:
|
|||||||
DIRECTUS_DB_USER=$DIRECTUS_DB_USER
|
DIRECTUS_DB_USER=$DIRECTUS_DB_USER
|
||||||
DIRECTUS_DB_PASSWORD=$DIRECTUS_DB_PASSWORD
|
DIRECTUS_DB_PASSWORD=$DIRECTUS_DB_PASSWORD
|
||||||
DIRECTUS_API_TOKEN=$DIRECTUS_API_TOKEN
|
DIRECTUS_API_TOKEN=$DIRECTUS_API_TOKEN
|
||||||
|
INTERNAL_DIRECTUS_URL=http://directus:8055
|
||||||
GATEKEEPER_PASSWORD=$GATEKEEPER_PASSWORD
|
GATEKEEPER_PASSWORD=$GATEKEEPER_PASSWORD
|
||||||
|
|
||||||
IMAGE_TAG=$IMAGE_TAG
|
IMAGE_TAG=$IMAGE_TAG
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['@commitlint/config-conventional'],
|
extends: ['@commitlint/config-conventional'],
|
||||||
rules: {
|
rules: {
|
||||||
'header-max-length': [2, 'always', 150],
|
'header-max-length': [2, 'always', 500],
|
||||||
'subject-case': [0],
|
'subject-case': [0],
|
||||||
'subject-full-stop': [0],
|
'subject-full-stop': [0],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ function createConfig() {
|
|||||||
adminEmail: env.DIRECTUS_ADMIN_EMAIL,
|
adminEmail: env.DIRECTUS_ADMIN_EMAIL,
|
||||||
password: env.DIRECTUS_ADMIN_PASSWORD,
|
password: env.DIRECTUS_ADMIN_PASSWORD,
|
||||||
token: env.DIRECTUS_API_TOKEN,
|
token: env.DIRECTUS_API_TOKEN,
|
||||||
|
internalUrl: env.INTERNAL_DIRECTUS_URL,
|
||||||
proxyPath: '/cms',
|
proxyPath: '/cms',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
@@ -83,17 +84,39 @@ export function getConfig() {
|
|||||||
* Uses getters to ensure it's only initialized when accessed.
|
* Uses getters to ensure it's only initialized when accessed.
|
||||||
*/
|
*/
|
||||||
export const config = {
|
export const config = {
|
||||||
get env() { return getConfig().env; },
|
get env() {
|
||||||
get isProduction() { return getConfig().isProduction; },
|
return getConfig().env;
|
||||||
get isDevelopment() { return getConfig().isDevelopment; },
|
},
|
||||||
get isTest() { return getConfig().isTest; },
|
get isProduction() {
|
||||||
get baseUrl() { return getConfig().baseUrl; },
|
return getConfig().isProduction;
|
||||||
get analytics() { return getConfig().analytics; },
|
},
|
||||||
get errors() { return getConfig().errors; },
|
get isDevelopment() {
|
||||||
get cache() { return getConfig().cache; },
|
return getConfig().isDevelopment;
|
||||||
get logging() { return getConfig().logging; },
|
},
|
||||||
get mail() { return getConfig().mail; },
|
get isTest() {
|
||||||
get directus() { return getConfig().directus; },
|
return getConfig().isTest;
|
||||||
|
},
|
||||||
|
get baseUrl() {
|
||||||
|
return getConfig().baseUrl;
|
||||||
|
},
|
||||||
|
get analytics() {
|
||||||
|
return getConfig().analytics;
|
||||||
|
},
|
||||||
|
get errors() {
|
||||||
|
return getConfig().errors;
|
||||||
|
},
|
||||||
|
get cache() {
|
||||||
|
return getConfig().cache;
|
||||||
|
},
|
||||||
|
get logging() {
|
||||||
|
return getConfig().logging;
|
||||||
|
},
|
||||||
|
get mail() {
|
||||||
|
return getConfig().mail;
|
||||||
|
},
|
||||||
|
get directus() {
|
||||||
|
return getConfig().directus;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
174
lib/directus.ts
174
lib/directus.ts
@@ -1,108 +1,126 @@
|
|||||||
import { createDirectus, rest, authentication, readItems } from '@directus/sdk';
|
import { createDirectus, rest, authentication, readItems, readCollections } from '@directus/sdk';
|
||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
|
|
||||||
const { url, adminEmail, password, token, proxyPath } = config.directus;
|
const { url, adminEmail, password, token, proxyPath, internalUrl } = config.directus;
|
||||||
|
|
||||||
const client = createDirectus(url)
|
// Use internal URL if on server to bypass Gatekeeper/Auth
|
||||||
.with(rest())
|
const effectiveUrl = typeof window === 'undefined' && internalUrl ? internalUrl : url;
|
||||||
.with(authentication());
|
|
||||||
|
const client = createDirectus(effectiveUrl).with(rest()).with(authentication());
|
||||||
|
|
||||||
export async function ensureAuthenticated() {
|
export async function ensureAuthenticated() {
|
||||||
if (token) {
|
if (token) {
|
||||||
client.setToken(token);
|
client.setToken(token);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (adminEmail && password) {
|
if (adminEmail && password) {
|
||||||
try {
|
try {
|
||||||
await client.login(adminEmail, password);
|
await client.login(adminEmail, password);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to authenticate with Directus:", e);
|
console.error('Failed to authenticate with Directus:', e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the new translation-based schema back to the application's Product interface
|
* Maps the new translation-based schema back to the application's Product interface
|
||||||
*/
|
*/
|
||||||
function mapDirectusProduct(item: any, locale: string): any {
|
function mapDirectusProduct(item: any, locale: string): any {
|
||||||
const langCode = locale === 'en' ? 'en-US' : 'de-DE';
|
const langCode = locale === 'en' ? 'en-US' : 'de-DE';
|
||||||
const translation = item.translations?.find((t: any) => t.languages_code === langCode) || item.translations?.[0] || {};
|
const translation =
|
||||||
|
item.translations?.find((t: any) => t.languages_code === langCode) ||
|
||||||
|
item.translations?.[0] ||
|
||||||
|
{};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
sku: item.sku,
|
sku: item.sku,
|
||||||
title: translation.name || '',
|
title: translation.name || '',
|
||||||
description: translation.description || '',
|
description: translation.description || '',
|
||||||
content: translation.content || '',
|
content: translation.content || '',
|
||||||
technicalData: {
|
technicalData: {
|
||||||
technicalItems: translation.technical_items || [],
|
technicalItems: translation.technical_items || [],
|
||||||
voltageTables: translation.voltage_tables || []
|
voltageTables: translation.voltage_tables || [],
|
||||||
},
|
},
|
||||||
locale: locale,
|
locale: locale,
|
||||||
// Use proxy URL for assets to avoid CORS and handle internal/external issues
|
// Use proxy URL for assets to avoid CORS and handle internal/external issues
|
||||||
data_sheet_url: item.data_sheet ? `${proxyPath}/assets/${item.data_sheet}` : null,
|
data_sheet_url: item.data_sheet ? `${proxyPath}/assets/${item.data_sheet}` : null,
|
||||||
categories: (item.categories_link || []).map((c: any) => c.categories_id?.translations?.[0]?.name).filter(Boolean)
|
categories: (item.categories_link || [])
|
||||||
};
|
.map((c: any) => c.categories_id?.translations?.[0]?.name)
|
||||||
|
.filter(Boolean),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProducts(locale: string = 'de') {
|
export async function getProducts(locale: string = 'de') {
|
||||||
await ensureAuthenticated();
|
await ensureAuthenticated();
|
||||||
try {
|
try {
|
||||||
const items = await client.request(readItems('products', {
|
const items = await client.request(
|
||||||
fields: [
|
readItems('products', {
|
||||||
'*',
|
fields: ['*', 'translations.*', 'categories_link.categories_id.translations.name'],
|
||||||
'translations.*',
|
}),
|
||||||
'categories_link.categories_id.translations.name'
|
);
|
||||||
]
|
return items.map((item) => mapDirectusProduct(item, locale));
|
||||||
}));
|
} catch (error) {
|
||||||
return items.map(item => mapDirectusProduct(item, locale));
|
console.error('Error fetching products:', error);
|
||||||
} catch (error) {
|
return [];
|
||||||
console.error('Error fetching products:', error);
|
}
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProductBySlug(slug: string, locale: string = 'de') {
|
export async function getProductBySlug(slug: string, locale: string = 'de') {
|
||||||
await ensureAuthenticated();
|
await ensureAuthenticated();
|
||||||
const langCode = locale === 'en' ? 'en-US' : 'de-DE';
|
const langCode = locale === 'en' ? 'en-US' : 'de-DE';
|
||||||
try {
|
try {
|
||||||
const items = await client.request(readItems('products', {
|
const items = await client.request(
|
||||||
filter: {
|
readItems('products', {
|
||||||
translations: {
|
filter: {
|
||||||
slug: { _eq: slug },
|
translations: {
|
||||||
languages_code: { _eq: langCode }
|
slug: { _eq: slug },
|
||||||
}
|
languages_code: { _eq: langCode },
|
||||||
},
|
},
|
||||||
fields: [
|
},
|
||||||
'*',
|
fields: ['*', 'translations.*', 'categories_link.categories_id.translations.name'],
|
||||||
'translations.*',
|
limit: 1,
|
||||||
'categories_link.categories_id.translations.name'
|
}),
|
||||||
],
|
);
|
||||||
limit: 1
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!items || items.length === 0) return null;
|
if (!items || items.length === 0) return null;
|
||||||
return mapDirectusProduct(items[0], locale);
|
return mapDirectusProduct(items[0], locale);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching product ${slug}:`, error);
|
console.error(`Error fetching product ${slug}:`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkHealth() {
|
export async function checkHealth() {
|
||||||
|
try {
|
||||||
|
await ensureAuthenticated();
|
||||||
|
|
||||||
|
// 1. Basic connectivity check
|
||||||
|
await client.request(readCollections());
|
||||||
|
|
||||||
|
// 2. Schema check (does the products table exist?)
|
||||||
try {
|
try {
|
||||||
await ensureAuthenticated();
|
await client.request(readItems('products', { limit: 1 }));
|
||||||
// Try to fetch something very simple that should exist if initialized
|
} catch (e: any) {
|
||||||
await client.request(readItems('directus_collections', { limit: 1 }));
|
if (e.message?.includes('does not exist') || e.code === 'INVALID_PAYLOAD') {
|
||||||
return { status: 'ok', message: 'Directus is reachable and responding.' };
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Directus health check failed:', error);
|
|
||||||
return {
|
return {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: error.message || 'Unknown error',
|
message: 'The "products" collection is missing. Please sync your data.',
|
||||||
code: error.code || 'UNKNOWN'
|
code: 'SCHEMA_MISSING',
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { status: 'ok', message: 'Directus is reachable and responding.' };
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Directus health check failed:', error);
|
||||||
|
return {
|
||||||
|
status: 'error',
|
||||||
|
message: error.message || 'Unknown error',
|
||||||
|
code: error.code || 'UNKNOWN',
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default client;
|
export default client;
|
||||||
|
|||||||
14
lib/env.ts
14
lib/env.ts
@@ -14,7 +14,10 @@ export const envSchema = z.object({
|
|||||||
|
|
||||||
// Analytics
|
// Analytics
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.preprocess(preprocessEmptyString, z.string().optional()),
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.preprocess(preprocessEmptyString, z.string().optional()),
|
||||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL: z.preprocess(preprocessEmptyString, z.string().url().default('https://analytics.infra.mintel.me/script.js')),
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL: z.preprocess(
|
||||||
|
preprocessEmptyString,
|
||||||
|
z.string().url().default('https://analytics.infra.mintel.me/script.js'),
|
||||||
|
),
|
||||||
|
|
||||||
// Error Tracking
|
// Error Tracking
|
||||||
SENTRY_DSN: z.preprocess(preprocessEmptyString, z.string().optional()),
|
SENTRY_DSN: z.preprocess(preprocessEmptyString, z.string().optional()),
|
||||||
@@ -30,14 +33,18 @@ export const envSchema = z.object({
|
|||||||
MAIL_FROM: z.preprocess(preprocessEmptyString, z.string().optional()),
|
MAIL_FROM: z.preprocess(preprocessEmptyString, z.string().optional()),
|
||||||
MAIL_RECIPIENTS: z.preprocess(
|
MAIL_RECIPIENTS: z.preprocess(
|
||||||
(val) => (typeof val === 'string' ? val.split(',').filter(Boolean) : val),
|
(val) => (typeof val === 'string' ? val.split(',').filter(Boolean) : val),
|
||||||
z.array(z.string()).default([])
|
z.array(z.string()).default([]),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Directus
|
// Directus
|
||||||
DIRECTUS_URL: z.preprocess(preprocessEmptyString, z.string().url().default('http://localhost:8055')),
|
DIRECTUS_URL: z.preprocess(
|
||||||
|
preprocessEmptyString,
|
||||||
|
z.string().url().default('http://localhost:8055'),
|
||||||
|
),
|
||||||
DIRECTUS_ADMIN_EMAIL: z.preprocess(preprocessEmptyString, z.string().optional()),
|
DIRECTUS_ADMIN_EMAIL: z.preprocess(preprocessEmptyString, z.string().optional()),
|
||||||
DIRECTUS_ADMIN_PASSWORD: z.preprocess(preprocessEmptyString, z.string().optional()),
|
DIRECTUS_ADMIN_PASSWORD: z.preprocess(preprocessEmptyString, z.string().optional()),
|
||||||
DIRECTUS_API_TOKEN: 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()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Env = z.infer<typeof envSchema>;
|
export type Env = z.infer<typeof envSchema>;
|
||||||
@@ -64,5 +71,6 @@ export function getRawEnv() {
|
|||||||
DIRECTUS_ADMIN_EMAIL: process.env.DIRECTUS_ADMIN_EMAIL,
|
DIRECTUS_ADMIN_EMAIL: process.env.DIRECTUS_ADMIN_EMAIL,
|
||||||
DIRECTUS_ADMIN_PASSWORD: process.env.DIRECTUS_ADMIN_PASSWORD,
|
DIRECTUS_ADMIN_PASSWORD: process.env.DIRECTUS_ADMIN_PASSWORD,
|
||||||
DIRECTUS_API_TOKEN: process.env.DIRECTUS_API_TOKEN,
|
DIRECTUS_API_TOKEN: process.env.DIRECTUS_API_TOKEN,
|
||||||
|
INTERNAL_DIRECTUS_URL: process.env.INTERNAL_DIRECTUS_URL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user