Add frontend service with auth, MFA, and content management
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:
Debian
2026-01-07 04:57:08 +00:00
parent 8a5610a1e4
commit 890543fb77
33 changed files with 6851 additions and 0 deletions

90
frontend/src/config.ts Normal file
View 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),
},
};