222 lines
7.2 KiB
TypeScript
222 lines
7.2 KiB
TypeScript
import { createDirectus, rest, authentication, staticToken, readItems, readCollections } from '@directus/sdk';
|
|
import { config } from './config';
|
|
import { getServerAppServices } from './services/create-services.server';
|
|
|
|
const { url, adminEmail, password, token, proxyPath, internalUrl } = config.directus;
|
|
|
|
// Use internal URL if on server to bypass Gatekeeper/Auth
|
|
// Use proxy path in browser to stay on the same origin
|
|
const effectiveUrl =
|
|
typeof window === 'undefined'
|
|
? internalUrl || url
|
|
: typeof window !== 'undefined'
|
|
? `${window.location.origin}${proxyPath}`
|
|
: proxyPath;
|
|
|
|
// Initialize client with authentication plugin
|
|
const client = createDirectus(effectiveUrl).with(rest()).with(authentication());
|
|
|
|
/**
|
|
* Helper to determine if we should show detailed errors
|
|
*/
|
|
const shouldShowDevErrors = config.isTesting || config.isDevelopment;
|
|
|
|
/**
|
|
* Genericizes error messages for production/staging
|
|
*/
|
|
function formatError(error: any) {
|
|
if (shouldShowDevErrors) {
|
|
return error.errors?.[0]?.message || error.message || 'An unexpected error occurred.';
|
|
}
|
|
return 'A system error occurred. Our team has been notified.';
|
|
}
|
|
|
|
let authPromise: Promise<void> | null = null;
|
|
|
|
export async function ensureAuthenticated() {
|
|
if (token) {
|
|
(client as any).setToken(token);
|
|
return;
|
|
}
|
|
|
|
// Check if we already have a valid session token in memory (for login flow)
|
|
const existingToken = await (client as any).getToken();
|
|
if (existingToken) {
|
|
return;
|
|
}
|
|
|
|
if (adminEmail && password) {
|
|
if (authPromise) {
|
|
return authPromise;
|
|
}
|
|
|
|
authPromise = (async () => {
|
|
try {
|
|
client.setToken(null as any);
|
|
await client.login(adminEmail, password);
|
|
console.log(`✅ Directus: Authenticated successfully as ${adminEmail}`);
|
|
} catch (e: any) {
|
|
if (typeof window === 'undefined') {
|
|
getServerAppServices().errors.captureException(e, { part: 'directus_auth' });
|
|
}
|
|
console.error(`Failed to authenticate with Directus (${adminEmail}):`, e.message);
|
|
if (shouldShowDevErrors && e.errors) {
|
|
console.error('Directus Auth Details:', JSON.stringify(e.errors, null, 2));
|
|
}
|
|
// Clear the promise on failure (especially on invalid credentials)
|
|
// so we can retry on next request if credentials were updated
|
|
authPromise = null;
|
|
throw e;
|
|
}
|
|
})();
|
|
|
|
return authPromise;
|
|
} else if (shouldShowDevErrors && !adminEmail && !password && !token) {
|
|
console.warn('Directus: No token or admin credentials provided.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps the new translation-based schema back to the application's Product interface
|
|
*/
|
|
function mapDirectusProduct(item: any, locale: string): any {
|
|
const langCode = locale === 'en' ? 'en-US' : 'de-DE';
|
|
const translation =
|
|
item.translations?.find((t: any) => t.languages_code === langCode) ||
|
|
item.translations?.[0] ||
|
|
{};
|
|
|
|
return {
|
|
id: item.id,
|
|
sku: item.sku,
|
|
title: translation.name || '',
|
|
description: translation.description || '',
|
|
content: translation.content || '',
|
|
technicalData: {
|
|
technicalItems: translation.technical_items || [],
|
|
voltageTables: translation.voltage_tables || [],
|
|
},
|
|
locale: locale,
|
|
// 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,
|
|
categories: (item.categories_link || [])
|
|
.map((c: any) => c.categories_id?.translations?.[0]?.name)
|
|
.filter(Boolean),
|
|
};
|
|
}
|
|
|
|
export async function getProducts(locale: string = 'de') {
|
|
await ensureAuthenticated();
|
|
try {
|
|
const items = await client.request(
|
|
readItems('products', {
|
|
fields: ['*', 'translations.*', 'categories_link.categories_id.translations.name'],
|
|
}),
|
|
);
|
|
return items.map((item) => mapDirectusProduct(item, locale));
|
|
} catch (error) {
|
|
if (typeof window === 'undefined') {
|
|
getServerAppServices().errors.captureException(error, { part: 'directus_get_products' });
|
|
}
|
|
console.error('Error fetching products:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function getProductBySlug(slug: string, locale: string = 'de') {
|
|
await ensureAuthenticated();
|
|
const langCode = locale === 'en' ? 'en-US' : 'de-DE';
|
|
try {
|
|
const items = await client.request(
|
|
readItems('products', {
|
|
filter: {
|
|
translations: {
|
|
slug: { _eq: slug },
|
|
languages_code: { _eq: langCode },
|
|
},
|
|
},
|
|
fields: ['*', 'translations.*', 'categories_link.categories_id.translations.name'],
|
|
limit: 1,
|
|
}),
|
|
);
|
|
|
|
if (!items || items.length === 0) return null;
|
|
return mapDirectusProduct(items[0], locale);
|
|
} catch (error) {
|
|
if (typeof window === 'undefined') {
|
|
getServerAppServices().errors.captureException(error, {
|
|
part: 'directus_get_product_by_slug',
|
|
slug,
|
|
});
|
|
}
|
|
console.error(`Error fetching product ${slug}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function checkHealth() {
|
|
try {
|
|
// 1. Connectivity & Auth Check
|
|
try {
|
|
await ensureAuthenticated();
|
|
await client.request(readCollections());
|
|
} catch (e: any) {
|
|
if (typeof window === 'undefined') {
|
|
getServerAppServices().errors.captureException(e, { part: 'directus_health_auth' });
|
|
}
|
|
console.error('Directus authentication failed during health check:', e);
|
|
return {
|
|
status: 'error',
|
|
message: shouldShowDevErrors
|
|
? 'Authentication failed. Check your DIRECTUS_ADMIN_EMAIL and DIRECTUS_ADMIN_PASSWORD.'
|
|
: 'CMS is currently unavailable due to an internal authentication error.',
|
|
code: 'AUTH_FAILED',
|
|
details: shouldShowDevErrors ? e.message : undefined,
|
|
};
|
|
}
|
|
|
|
// 2. Schema check (does the contact_submissions table exist?)
|
|
try {
|
|
await client.request(readItems('contact_submissions', { limit: 1 }));
|
|
} catch (e: any) {
|
|
if (typeof window === 'undefined') {
|
|
getServerAppServices().errors.captureException(e, { part: 'directus_health_schema' });
|
|
}
|
|
if (
|
|
e.message?.includes('does not exist') ||
|
|
e.code === 'INVALID_PAYLOAD' ||
|
|
e.status === 404
|
|
) {
|
|
return {
|
|
status: 'error',
|
|
message: shouldShowDevErrors
|
|
? `The "contact_submissions" collection is missing or inaccessible. Error: ${e.message || 'Unknown'}`
|
|
: 'Required data structures are currently unavailable.',
|
|
code: 'SCHEMA_MISSING',
|
|
};
|
|
}
|
|
return {
|
|
status: 'error',
|
|
message: shouldShowDevErrors
|
|
? `Schema error: ${e.errors?.[0]?.message || e.message || 'Unknown error'}`
|
|
: 'The data schema is currently misconfigured.',
|
|
code: 'SCHEMA_ERROR',
|
|
};
|
|
}
|
|
|
|
return { status: 'ok', message: 'Directus is reachable and responding.' };
|
|
} catch (error: any) {
|
|
if (typeof window === 'undefined') {
|
|
getServerAppServices().errors.captureException(error, { part: 'directus_health_critical' });
|
|
}
|
|
console.error('Directus health check failed with unexpected error:', error);
|
|
return {
|
|
status: 'error',
|
|
message: formatError(error),
|
|
code: error.code || 'UNKNOWN',
|
|
};
|
|
}
|
|
}
|
|
|
|
export default client;
|