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:
137
frontend/src/index.ts
Normal file
137
frontend/src/index.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import express from 'express';
|
||||
import session from 'express-session';
|
||||
import helmet from 'helmet';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { config } from './config.js';
|
||||
import { initDatabase, closeDatabase } from './db/index.js';
|
||||
import { createInitialAdmin } from './services/initService.js';
|
||||
import { SQLiteSessionStore } from './services/sessionService.js';
|
||||
import { apiRateLimiter } from './middleware/rateLimit.js';
|
||||
import { errorHandler } from './middleware/errorHandler.js';
|
||||
import { logger } from './utils/logger.js';
|
||||
|
||||
import authRoutes from './routes/auth.js';
|
||||
import userRoutes from './routes/users.js';
|
||||
import contentRoutes from './routes/content.js';
|
||||
import generateRoutes from './routes/generate.js';
|
||||
import healthRoutes from './routes/health.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const app = express();
|
||||
|
||||
// Trust proxy (for Cloudflare)
|
||||
if (config.trustProxy) {
|
||||
app.set('trust proxy', 1);
|
||||
}
|
||||
|
||||
// Security headers
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", 'data:', 'blob:'],
|
||||
mediaSrc: ["'self'", 'data:', 'blob:'],
|
||||
connectSrc: ["'self'"],
|
||||
},
|
||||
},
|
||||
crossOriginEmbedderPolicy: false,
|
||||
}));
|
||||
|
||||
// Body parsing
|
||||
app.use(express.json({ limit: '15mb' }));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Session middleware
|
||||
const sessionStore = new SQLiteSessionStore();
|
||||
|
||||
app.use(session({
|
||||
secret: config.sessionSecret,
|
||||
name: 'sid',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: sessionStore,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: config.isProduction,
|
||||
sameSite: 'strict',
|
||||
maxAge: config.sessionMaxAge,
|
||||
},
|
||||
}));
|
||||
|
||||
// Rate limiting for API routes
|
||||
app.use('/api/', apiRateLimiter);
|
||||
|
||||
// API routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/users', userRoutes);
|
||||
app.use('/api/content', contentRoutes);
|
||||
app.use('/api/generate', generateRoutes);
|
||||
|
||||
// Health check routes (no rate limiting)
|
||||
app.use('/health', healthRoutes);
|
||||
|
||||
// Static files
|
||||
app.use(express.static(join(__dirname, '..', 'public')));
|
||||
|
||||
// SPA fallback - serve index.html for all non-API routes
|
||||
app.get('*', (req, res, next) => {
|
||||
if (req.path.startsWith('/api/') || req.path.startsWith('/health')) {
|
||||
return next();
|
||||
}
|
||||
res.sendFile(join(__dirname, '..', 'public', 'index.html'));
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.use(errorHandler);
|
||||
|
||||
// Startup
|
||||
async function start() {
|
||||
try {
|
||||
logger.info('Starting ComfyUI Frontend Service...');
|
||||
|
||||
// Initialize database
|
||||
initDatabase();
|
||||
logger.info({ dbPath: config.dbPath }, 'Database initialized');
|
||||
|
||||
// Create initial admin user if needed
|
||||
await createInitialAdmin();
|
||||
|
||||
// Start server
|
||||
const server = app.listen(config.port, () => {
|
||||
logger.info({ port: config.port, env: config.nodeEnv }, 'Server started');
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
const shutdown = async (signal: string) => {
|
||||
logger.info({ signal }, 'Shutdown signal received');
|
||||
|
||||
server.close(() => {
|
||||
logger.info('HTTP server closed');
|
||||
sessionStore.close();
|
||||
closeDatabase();
|
||||
logger.info('Database closed');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Force exit after 10 seconds
|
||||
setTimeout(() => {
|
||||
logger.error('Forced shutdown after timeout');
|
||||
process.exit(1);
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
|
||||
} catch (error) {
|
||||
logger.error({ error }, 'Failed to start server');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
start();
|
||||
Reference in New Issue
Block a user