Add frontend service with auth, MFA, and content management
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
- Node.js/Express backend with TypeScript - SQLite database for users, sessions, and content metadata - Authentication with TOTP and WebAuthn MFA support - Admin user auto-created on first startup - User content gallery with view/delete functionality - RunPod API proxy (keeps API keys server-side) - Docker setup with CI/CD for Gitea registry 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
90
frontend/src/config.ts
Normal file
90
frontend/src/config.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
function requireEnv(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`Missing required environment variable: ${name}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function optionalEnv(name: string, defaultValue: string): string {
|
||||
return process.env[name] || defaultValue;
|
||||
}
|
||||
|
||||
function optionalEnvInt(name: string, defaultValue: number): number {
|
||||
const value = process.env[name];
|
||||
if (!value) return defaultValue;
|
||||
const parsed = parseInt(value, 10);
|
||||
if (isNaN(parsed)) return defaultValue;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function optionalEnvBool(name: string, defaultValue: boolean): boolean {
|
||||
const value = process.env[name];
|
||||
if (!value) return defaultValue;
|
||||
return value.toLowerCase() === 'true' || value === '1';
|
||||
}
|
||||
|
||||
const dataDir = optionalEnv('DATA_DIR', './data');
|
||||
|
||||
// Ensure data directories exist
|
||||
if (!existsSync(dataDir)) {
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
const contentDir = join(dataDir, 'content');
|
||||
if (!existsSync(contentDir)) {
|
||||
mkdirSync(contentDir, { recursive: true });
|
||||
}
|
||||
|
||||
export const config = {
|
||||
// Server
|
||||
nodeEnv: optionalEnv('NODE_ENV', 'development'),
|
||||
port: optionalEnvInt('PORT', 3000),
|
||||
isProduction: optionalEnv('NODE_ENV', 'development') === 'production',
|
||||
|
||||
// Paths
|
||||
dataDir,
|
||||
contentDir,
|
||||
dbPath: join(dataDir, 'app.db'),
|
||||
|
||||
// Session
|
||||
sessionSecret: requireEnv('SESSION_SECRET'),
|
||||
sessionMaxAge: optionalEnvInt('SESSION_MAX_AGE_HOURS', 24) * 60 * 60 * 1000,
|
||||
|
||||
// Initial Admin
|
||||
adminUsername: optionalEnv('ADMIN_USERNAME', 'admin'),
|
||||
adminPassword: optionalEnv('ADMIN_PASSWORD', ''),
|
||||
adminEmail: optionalEnv('ADMIN_EMAIL', ''),
|
||||
|
||||
// RunPod
|
||||
runpod: {
|
||||
apiKey: requireEnv('RUNPOD_API_KEY'),
|
||||
endpointId: requireEnv('RUNPOD_ENDPOINT_ID'),
|
||||
baseUrl: 'https://api.runpod.ai/v2',
|
||||
pollIntervalMs: optionalEnvInt('RUNPOD_POLL_INTERVAL_MS', 5000),
|
||||
maxTimeoutMs: optionalEnvInt('RUNPOD_MAX_TIMEOUT_MS', 600000),
|
||||
},
|
||||
|
||||
// WebAuthn
|
||||
webauthn: {
|
||||
rpId: optionalEnv('WEBAUTHN_RP_ID', 'localhost'),
|
||||
rpName: optionalEnv('WEBAUTHN_RP_NAME', 'ComfyUI Video Generator'),
|
||||
origin: optionalEnv('WEBAUTHN_ORIGIN', 'http://localhost:3000'),
|
||||
},
|
||||
|
||||
// Security
|
||||
encryptionKey: requireEnv('ENCRYPTION_KEY'),
|
||||
trustProxy: optionalEnvBool('TRUST_PROXY', true),
|
||||
|
||||
// Rate Limiting
|
||||
rateLimit: {
|
||||
windowMs: optionalEnvInt('RATE_LIMIT_WINDOW_MS', 60000),
|
||||
maxRequests: optionalEnvInt('RATE_LIMIT_MAX_REQUESTS', 100),
|
||||
},
|
||||
loginRateLimit: {
|
||||
windowMs: optionalEnvInt('LOGIN_RATE_LIMIT_WINDOW_MS', 900000),
|
||||
maxRequests: optionalEnvInt('LOGIN_RATE_LIMIT_MAX', 5),
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user