env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m36s
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m36s
This commit is contained in:
@@ -50,6 +50,7 @@ export default function AnalyticsProvider() {
|
|||||||
src="/stats/script.js"
|
src="/stats/script.js"
|
||||||
data-website-id={websiteId}
|
data-website-id={websiteId}
|
||||||
strategy="afterInteractive"
|
strategy="afterInteractive"
|
||||||
|
data-domains="klz-cables.com"
|
||||||
defer
|
defer
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
102
lib/config.ts
Normal file
102
lib/config.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Centralized configuration management for the application.
|
||||||
|
* This file defines the schema and provides a type-safe way to access environment variables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
env: process.env.NODE_ENV || 'development',
|
||||||
|
isProduction: process.env.NODE_ENV === 'production',
|
||||||
|
isDevelopment: process.env.NODE_ENV === 'development',
|
||||||
|
isTest: process.env.NODE_ENV === 'test',
|
||||||
|
|
||||||
|
baseUrl: process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000',
|
||||||
|
|
||||||
|
analytics: {
|
||||||
|
umami: {
|
||||||
|
websiteId: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID,
|
||||||
|
scriptUrl: process.env.UMAMI_SCRIPT_URL || 'https://analytics.infra.mintel.me/script.js',
|
||||||
|
// The proxied path used in the frontend
|
||||||
|
proxyPath: '/stats/script.js',
|
||||||
|
enabled: Boolean(process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
glitchtip: {
|
||||||
|
// Server-side DSN (direct to GlitchTip)
|
||||||
|
dsn: process.env.SENTRY_DSN,
|
||||||
|
// Client-side DSN (proxied through Next.js)
|
||||||
|
publicDsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
|
// The proxied origin used in the frontend
|
||||||
|
proxyPath: '/errors',
|
||||||
|
enabled: Boolean(process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
cache: {
|
||||||
|
redis: {
|
||||||
|
url: process.env.REDIS_URL,
|
||||||
|
keyPrefix: process.env.REDIS_KEY_PREFIX || 'klz:',
|
||||||
|
enabled: Boolean(process.env.REDIS_URL),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
logging: {
|
||||||
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
|
},
|
||||||
|
|
||||||
|
mail: {
|
||||||
|
host: process.env.MAIL_HOST,
|
||||||
|
port: parseInt(process.env.MAIL_PORT || '587', 10),
|
||||||
|
user: process.env.MAIL_USERNAME,
|
||||||
|
pass: process.env.MAIL_PASSWORD,
|
||||||
|
from: process.env.MAIL_FROM,
|
||||||
|
recipients: process.env.MAIL_RECIPIENTS?.split(',') || [],
|
||||||
|
},
|
||||||
|
|
||||||
|
woocommerce: {
|
||||||
|
url: process.env.WOOCOMMERCE_URL,
|
||||||
|
consumerKey: process.env.WOOCOMMERCE_CONSUMER_KEY,
|
||||||
|
consumerSecret: process.env.WOOCOMMERCE_CONSUMER_SECRET,
|
||||||
|
},
|
||||||
|
|
||||||
|
wordpress: {
|
||||||
|
appPassword: process.env.WORDPRESS_APP_PASSWORD,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get a masked version of the config for logging.
|
||||||
|
*/
|
||||||
|
export function getMaskedConfig() {
|
||||||
|
const mask = (val: string | undefined) => (val ? `***${val.slice(-4)}` : 'not set');
|
||||||
|
|
||||||
|
return {
|
||||||
|
env: config.env,
|
||||||
|
baseUrl: config.baseUrl,
|
||||||
|
analytics: {
|
||||||
|
umami: {
|
||||||
|
websiteId: mask(config.analytics.umami.websiteId),
|
||||||
|
scriptUrl: config.analytics.umami.scriptUrl,
|
||||||
|
enabled: config.analytics.umami.enabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
glitchtip: {
|
||||||
|
dsn: mask(config.errors.glitchtip.dsn),
|
||||||
|
publicDsn: mask(config.errors.glitchtip.publicDsn),
|
||||||
|
enabled: config.errors.glitchtip.enabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
redis: {
|
||||||
|
url: mask(config.cache.redis.url),
|
||||||
|
keyPrefix: config.cache.redis.keyPrefix,
|
||||||
|
enabled: config.cache.redis.enabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logging: {
|
||||||
|
level: config.logging.level,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -75,8 +75,9 @@ export class UmamiAnalyticsService implements AnalyticsService {
|
|||||||
// Server-side tracking via proxy
|
// Server-side tracking via proxy
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
const { getServerAppServices } = require('../create-services.server');
|
const { getServerAppServices } = require('../create-services.server');
|
||||||
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
const { config } = require('../../config');
|
||||||
const umamiUrl = process.env.UMAMI_SCRIPT_URL?.replace('/script.js', '') || 'https://analytics.infra.mintel.me';
|
const websiteId = config.analytics.umami.websiteId;
|
||||||
|
const umamiUrl = config.analytics.umami.scriptUrl.replace('/script.js', '');
|
||||||
|
|
||||||
if (!websiteId) return;
|
if (!websiteId) return;
|
||||||
|
|
||||||
@@ -123,8 +124,9 @@ export class UmamiAnalyticsService implements AnalyticsService {
|
|||||||
// Server-side tracking via proxy
|
// Server-side tracking via proxy
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
const { getServerAppServices } = require('../create-services.server');
|
const { getServerAppServices } = require('../create-services.server');
|
||||||
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
const { config } = require('../../config');
|
||||||
const umamiUrl = process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL?.replace('/script.js', '') || 'https://analytics.infra.mintel.me';
|
const websiteId = config.analytics.umami.websiteId;
|
||||||
|
const umamiUrl = config.analytics.umami.scriptUrl.replace('/script.js', '');
|
||||||
|
|
||||||
if (!websiteId || !url) return;
|
if (!websiteId || !url) return;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { RedisCacheService } from './cache/redis-cache-service';
|
|||||||
import { GlitchtipErrorReportingService } from './errors/glitchtip-error-reporting-service';
|
import { GlitchtipErrorReportingService } from './errors/glitchtip-error-reporting-service';
|
||||||
import { NoopErrorReportingService } from './errors/noop-error-reporting-service';
|
import { NoopErrorReportingService } from './errors/noop-error-reporting-service';
|
||||||
import { PinoLoggerService } from './logging/pino-logger-service';
|
import { PinoLoggerService } from './logging/pino-logger-service';
|
||||||
|
import { config, getMaskedConfig } from '../config';
|
||||||
|
|
||||||
let singleton: AppServices | undefined;
|
let singleton: AppServices | undefined;
|
||||||
|
|
||||||
@@ -15,71 +16,47 @@ export function getServerAppServices(): AppServices {
|
|||||||
// Create logger first to log initialization
|
// Create logger first to log initialization
|
||||||
const logger = new PinoLoggerService('server');
|
const logger = new PinoLoggerService('server');
|
||||||
|
|
||||||
// Log environment variables (safely masked)
|
|
||||||
const envLog = {
|
|
||||||
// Mask sensitive values - show only last 4 characters or '***' for empty
|
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
|
||||||
? `***${process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID.slice(-4)}`
|
|
||||||
: 'not set',
|
|
||||||
UMAMI_SCRIPT_URL: process.env.UMAMI_SCRIPT_URL ?? 'not set',
|
|
||||||
SENTRY_DSN: process.env.SENTRY_DSN
|
|
||||||
? `***${process.env.SENTRY_DSN.slice(-4)}`
|
|
||||||
: 'not set',
|
|
||||||
REDIS_URL: process.env.REDIS_URL
|
|
||||||
? `***${process.env.REDIS_URL.slice(-4)}`
|
|
||||||
: 'not set',
|
|
||||||
REDIS_KEY_PREFIX: process.env.REDIS_KEY_PREFIX ?? 'klz:',
|
|
||||||
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
|
|
||||||
NODE_ENV: process.env.NODE_ENV ?? 'not set',
|
|
||||||
// Safe to show - no sensitive data
|
|
||||||
NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL ?? 'not set',
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.info('Initializing server application services', {
|
logger.info('Initializing server application services', {
|
||||||
environment: envLog,
|
environment: getMaskedConfig(),
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const umamiEnabled = Boolean(process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID);
|
|
||||||
const sentryEnabled = Boolean(process.env.SENTRY_DSN);
|
|
||||||
|
|
||||||
logger.info('Service configuration', {
|
logger.info('Service configuration', {
|
||||||
umamiEnabled,
|
umamiEnabled: config.analytics.umami.enabled,
|
||||||
sentryEnabled,
|
sentryEnabled: config.errors.glitchtip.enabled,
|
||||||
redisEnabled: Boolean(process.env.REDIS_URL),
|
redisEnabled: config.cache.redis.enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
const analytics = umamiEnabled
|
const analytics = config.analytics.umami.enabled
|
||||||
? new UmamiAnalyticsService({ enabled: true })
|
? new UmamiAnalyticsService({ enabled: true })
|
||||||
: new NoopAnalyticsService();
|
: new NoopAnalyticsService();
|
||||||
|
|
||||||
if (umamiEnabled) {
|
if (config.analytics.umami.enabled) {
|
||||||
logger.info('Umami analytics service initialized');
|
logger.info('Umami analytics service initialized');
|
||||||
} else {
|
} else {
|
||||||
logger.info('Noop analytics service initialized (analytics disabled)');
|
logger.info('Noop analytics service initialized (analytics disabled)');
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = sentryEnabled
|
const errors = config.errors.glitchtip.enabled
|
||||||
? new GlitchtipErrorReportingService({ enabled: true })
|
? new GlitchtipErrorReportingService({ enabled: true })
|
||||||
: new NoopErrorReportingService();
|
: new NoopErrorReportingService();
|
||||||
|
|
||||||
if (sentryEnabled) {
|
if (config.errors.glitchtip.enabled) {
|
||||||
logger.info('GlitchTip error reporting service initialized');
|
logger.info('GlitchTip error reporting service initialized');
|
||||||
} else {
|
} else {
|
||||||
logger.info('Noop error reporting service initialized (error reporting disabled)');
|
logger.info('Noop error reporting service initialized (error reporting disabled)');
|
||||||
}
|
}
|
||||||
|
|
||||||
const redisUrl = process.env.REDIS_URL;
|
const cache = config.cache.redis.enabled && config.cache.redis.url
|
||||||
const cache = redisUrl
|
|
||||||
? new RedisCacheService({
|
? new RedisCacheService({
|
||||||
url: redisUrl,
|
url: config.cache.redis.url,
|
||||||
keyPrefix: process.env.REDIS_KEY_PREFIX ?? 'klz:',
|
keyPrefix: config.cache.redis.keyPrefix,
|
||||||
})
|
})
|
||||||
: new MemoryCacheService();
|
: new MemoryCacheService();
|
||||||
|
|
||||||
if (redisUrl) {
|
if (config.cache.redis.enabled) {
|
||||||
logger.info('Redis cache service initialized', {
|
logger.info('Redis cache service initialized', {
|
||||||
keyPrefix: process.env.REDIS_KEY_PREFIX ?? 'klz:'
|
keyPrefix: config.cache.redis.keyPrefix
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.info('Memory cache service initialized (Redis not configured)');
|
logger.info('Memory cache service initialized (Redis not configured)');
|
||||||
@@ -87,7 +64,7 @@ export function getServerAppServices(): AppServices {
|
|||||||
|
|
||||||
logger.info('Pino logger service initialized', {
|
logger.info('Pino logger service initialized', {
|
||||||
name: 'server',
|
name: 'server',
|
||||||
level: process.env.LOG_LEVEL ?? 'info',
|
level: config.logging.level,
|
||||||
});
|
});
|
||||||
|
|
||||||
singleton = new AppServices(analytics, errors, cache, logger);
|
singleton = new AppServices(analytics, errors, cache, logger);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import pino, { Logger as PinoLogger } from 'pino';
|
import pino, { Logger as PinoLogger } from 'pino';
|
||||||
import type { LoggerService } from './logger-service';
|
import type { LoggerService } from './logger-service';
|
||||||
|
import { config } from '../../config';
|
||||||
|
|
||||||
export class PinoLoggerService implements LoggerService {
|
export class PinoLoggerService implements LoggerService {
|
||||||
private readonly logger: PinoLogger;
|
private readonly logger: PinoLogger;
|
||||||
@@ -10,9 +11,9 @@ export class PinoLoggerService implements LoggerService {
|
|||||||
} else {
|
} else {
|
||||||
this.logger = pino({
|
this.logger = pino({
|
||||||
name: name || 'app',
|
name: name || 'app',
|
||||||
level: process.env.LOG_LEVEL || 'info',
|
level: config.logging.level,
|
||||||
transport:
|
transport:
|
||||||
process.env.NODE_ENV !== 'production'
|
!config.isProduction
|
||||||
? {
|
? {
|
||||||
target: 'pino-pretty',
|
target: 'pino-pretty',
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@@ -344,7 +344,9 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
const umamiUrl = (process.env.UMAMI_SCRIPT_URL || 'https://analytics.infra.mintel.me').replace('/script.js', '');
|
const umamiUrl = (process.env.UMAMI_SCRIPT_URL || 'https://analytics.infra.mintel.me').replace('/script.js', '');
|
||||||
const glitchtipUrl = process.env.SENTRY_DSN ? new URL(process.env.SENTRY_DSN).origin : 'https://errors.infra.mintel.me';
|
const glitchtipUrl = process.env.SENTRY_DSN
|
||||||
|
? new URL(process.env.SENTRY_DSN).origin
|
||||||
|
: 'https://errors.infra.mintel.me';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user