216 lines
5.9 KiB
JavaScript
216 lines
5.9 KiB
JavaScript
/**
|
|
* Application Configuration
|
|
* Centralized configuration management
|
|
*/
|
|
|
|
import { readFileSync, existsSync } from 'fs'
|
|
import { dirname, join } from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
import { log } from '../middleware/logger.mjs'
|
|
|
|
const _configDir = dirname(fileURLToPath(import.meta.url))
|
|
const _repoRoot = join(_configDir, '..', '..')
|
|
|
|
/** Default dev API port from repo root `mailflow.dev.port.json` (avoids clashes with other apps on 3000). */
|
|
function readDevPortFromFile() {
|
|
try {
|
|
const f = join(_repoRoot, 'mailflow.dev.port.json')
|
|
if (!existsSync(f)) return 3030
|
|
const j = JSON.parse(readFileSync(f, 'utf8'))
|
|
const p = parseInt(String(j.port), 10)
|
|
return Number.isFinite(p) && p > 0 ? p : 3030
|
|
} catch {
|
|
return 3030
|
|
}
|
|
}
|
|
|
|
const nodeEnv = process.env.NODE_ENV || 'development'
|
|
const listenPort = process.env.PORT
|
|
? parseInt(process.env.PORT, 10)
|
|
: nodeEnv === 'production'
|
|
? 3000
|
|
: readDevPortFromFile()
|
|
const defaultLocalBase = `http://localhost:${listenPort}`
|
|
|
|
/**
|
|
* Environment configuration
|
|
*/
|
|
export const config = {
|
|
// Server
|
|
port: listenPort,
|
|
nodeEnv,
|
|
isDev: nodeEnv !== 'production',
|
|
isProd: nodeEnv === 'production',
|
|
|
|
// URLs
|
|
baseUrl: process.env.BASE_URL || defaultLocalBase,
|
|
frontendUrl: process.env.FRONTEND_URL || 'http://localhost:5173',
|
|
|
|
// Appwrite
|
|
appwrite: {
|
|
endpoint: process.env.APPWRITE_ENDPOINT,
|
|
projectId: process.env.APPWRITE_PROJECT_ID,
|
|
apiKey: process.env.APPWRITE_API_KEY,
|
|
databaseId: process.env.APPWRITE_DATABASE_ID,
|
|
},
|
|
|
|
// Stripe
|
|
stripe: {
|
|
secretKey: process.env.STRIPE_SECRET_KEY,
|
|
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
prices: {
|
|
basic: process.env.STRIPE_PRICE_BASIC || 'price_basic_monthly',
|
|
pro: process.env.STRIPE_PRICE_PRO || 'price_pro_monthly',
|
|
business: process.env.STRIPE_PRICE_BUSINESS || 'price_business_monthly',
|
|
},
|
|
},
|
|
|
|
// Google OAuth
|
|
google: {
|
|
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
redirectUri: process.env.GOOGLE_REDIRECT_URI || `${defaultLocalBase}/api/oauth/gmail/callback`,
|
|
},
|
|
|
|
// Microsoft OAuth
|
|
microsoft: {
|
|
clientId: process.env.MICROSOFT_CLIENT_ID,
|
|
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
|
|
redirectUri: process.env.MICROSOFT_REDIRECT_URI || `${defaultLocalBase}/api/oauth/outlook/callback`,
|
|
},
|
|
|
|
// Mistral AI
|
|
mistral: {
|
|
apiKey: process.env.MISTRAL_API_KEY,
|
|
},
|
|
|
|
// Rate Limiting
|
|
rateLimit: {
|
|
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '60000', 10),
|
|
max: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
|
|
},
|
|
|
|
// CORS (Dev: localhost + 127.0.0.1 für Vite, Browser ruft API oft direkt auf 127.0.0.1:PORT)
|
|
cors: {
|
|
origin:
|
|
process.env.NODE_ENV === 'production'
|
|
? process.env.CORS_ORIGIN || process.env.FRONTEND_URL || 'http://localhost:5173'
|
|
: [
|
|
process.env.CORS_ORIGIN,
|
|
process.env.FRONTEND_URL,
|
|
'http://localhost:5173',
|
|
'http://127.0.0.1:5173',
|
|
]
|
|
.filter(Boolean)
|
|
.filter((o, i, a) => a.indexOf(o) === i),
|
|
credentials: true,
|
|
},
|
|
|
|
// Free Tier Limits
|
|
freeTier: {
|
|
emailsPerMonth: parseInt(process.env.FREE_TIER_EMAILS_PER_MONTH || '500', 10),
|
|
emailAccounts: 1,
|
|
autoSchedule: false, // manual only
|
|
},
|
|
|
|
/** Highest product tier (admin comped plan, PLANS key in stripe.mjs). Optional env: TOP_SUBSCRIPTION_PLAN */
|
|
topSubscriptionPlan: (process.env.TOP_SUBSCRIPTION_PLAN || 'business').trim().toLowerCase(),
|
|
|
|
// Admin: comma-separated list of emails with admin rights (e.g. support).
|
|
// support@webklar.com is always included; env adds more.
|
|
adminEmails: (() => {
|
|
const fromEnv = (process.env.ADMIN_EMAILS || '')
|
|
.split(',')
|
|
.map((e) => e.trim().toLowerCase())
|
|
.filter(Boolean)
|
|
return [...new Set(['support@webklar.com', ...fromEnv])]
|
|
})(),
|
|
|
|
// Gitea Webhook (Deployment) — trim: trailing newlines in .env break HMAC/Bearer match
|
|
gitea: (() => {
|
|
const secret = (process.env.GITEA_WEBHOOK_SECRET || '').trim()
|
|
const auth = (process.env.GITEA_WEBHOOK_AUTH_TOKEN || '').trim()
|
|
return {
|
|
webhookSecret: secret,
|
|
webhookAuthToken: auth || secret,
|
|
}
|
|
})(),
|
|
|
|
/** HMAC secret for Gmail/Outlook OAuth state (recommended in production) */
|
|
oauthStateSecret: process.env.OAUTH_STATE_SECRET || '',
|
|
}
|
|
|
|
/**
|
|
* Required environment variables
|
|
*/
|
|
const requiredVars = [
|
|
'APPWRITE_ENDPOINT',
|
|
'APPWRITE_PROJECT_ID',
|
|
'APPWRITE_API_KEY',
|
|
'APPWRITE_DATABASE_ID',
|
|
'STRIPE_SECRET_KEY',
|
|
'STRIPE_WEBHOOK_SECRET',
|
|
]
|
|
|
|
/**
|
|
* Optional but recommended variables
|
|
*/
|
|
const recommendedVars = [
|
|
'MISTRAL_API_KEY',
|
|
'GOOGLE_CLIENT_ID',
|
|
'MICROSOFT_CLIENT_ID',
|
|
]
|
|
|
|
/**
|
|
* Validate configuration
|
|
*/
|
|
export function validateConfig() {
|
|
const missing = []
|
|
const warnings = []
|
|
|
|
// Check required variables
|
|
for (const varName of requiredVars) {
|
|
if (!process.env[varName]) {
|
|
missing.push(varName)
|
|
}
|
|
}
|
|
|
|
if (missing.length > 0) {
|
|
log.error(`Fehlende Umgebungsvariablen: ${missing.join(', ')}`)
|
|
process.exit(1)
|
|
}
|
|
|
|
// Check recommended variables
|
|
for (const varName of recommendedVars) {
|
|
if (!process.env[varName]) {
|
|
warnings.push(varName)
|
|
}
|
|
}
|
|
|
|
if (warnings.length > 0) {
|
|
log.warn(`Optionale Variablen fehlen: ${warnings.join(', ')}`)
|
|
}
|
|
|
|
log.success('Konfiguration validiert')
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Feature flags based on available config
|
|
*/
|
|
export const features = {
|
|
gmail: () => Boolean(config.google.clientId && config.google.clientSecret),
|
|
outlook: () => Boolean(config.microsoft.clientId && config.microsoft.clientSecret),
|
|
ai: () => Boolean(config.mistral.apiKey),
|
|
}
|
|
|
|
/**
|
|
* Check if an email has admin rights (support, etc.)
|
|
*/
|
|
export function isAdmin(email) {
|
|
if (!email || typeof email !== 'string') return false
|
|
return config.adminEmails.includes(email.trim().toLowerCase())
|
|
}
|
|
|
|
export default config
|