Files
OFApp/server/index.js
T
Trey T 236f36aae6 Add app auth, dashboard, scheduler, video management, and new scrapers
- JWT-based app authentication with user roles, folder/route access control
- Dashboard with storage stats, health checks, and recent activity
- Auto-download/scrape scheduler (12h interval) with per-user and per-job configs
- Video upload, tagging, HLS transcoding, and detail pages
- New scrapers: LeakGallery, Mega (megajs), yt-dlp
- FlareSolverr integration for Cloudflare-protected sites
- Gallery: advanced filtering (date, size, search), sort modes, equal-mix shuffle
- Forum sites management with stored cookies/auth
- GridWall/GridCell components for responsive media grid
- Media API with folder-access permissions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:48:10 -05:00

136 lines
4.6 KiB
JavaScript

import express from 'express';
import https from 'https';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { initRules } from './signing.js';
import authRouter, { requireAuth, checkRoutePermission } from './auth.js';
import proxyRouter from './proxy.js';
import downloadRouter from './download.js';
import galleryRouter from './gallery.js';
import hlsRouter from './hls.js';
import settingsRouter from './settings.js';
import scrapeRouter from './scrape.js';
import flareSolverrRouter from './flaresolverr.js';
import drmStreamRouter from './drm-stream.js';
import healthRouter from './health.js';
import dashboardRouter from './dashboard.js';
import videosRouter from './videos.js';
import videoHlsRouter from './video-hls.js';
import mediaApiRouter from './media-api.js';
import { scanMediaFiles } from './gallery.js';
import { startScheduler } from './scheduler.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const PORT = process.env.PORT || 3001;
const HTTPS_PORT = process.env.HTTPS_PORT || 3443;
app.use(cors());
app.use(cookieParser());
// Parse DRM license request bodies as raw binary BEFORE global JSON parser
// (express.json can interfere with reading the raw body stream)
app.use('/api/drm-license', express.raw({ type: '*/*', limit: '1mb' }));
app.use(express.json());
// Auth routes (public endpoints like login/setup must be before requireAuth)
app.use(authRouter);
// Apply auth middleware globally (after auth routes)
app.use('/api', (req, res, next) => {
// Skip auth for app-auth public endpoints
if (req.path.startsWith('/app-auth/')) return next();
// Skip auth for internal DRM license requests from pywidevine subprocess
if (req.path.startsWith('/drm-license') && ['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.ip)) return next();
requireAuth(req, res, next);
});
app.use('/api', (req, res, next) => {
if (req.path.startsWith('/app-auth/')) return next();
checkRoutePermission(req, res, next);
});
// API routes
app.use(proxyRouter);
app.use(downloadRouter);
app.use(galleryRouter);
app.use(hlsRouter);
app.use(settingsRouter);
app.use(scrapeRouter);
app.use(flareSolverrRouter);
app.use(drmStreamRouter);
app.use(healthRouter);
app.use(dashboardRouter);
app.use(videosRouter);
app.use(videoHlsRouter);
app.use(mediaApiRouter);
// Serve static client build in production
const clientDist = join(__dirname, '..', 'client', 'dist');
if (existsSync(clientDist)) {
app.use(express.static(clientDist));
app.get('*', (req, res) => {
if (req.path.startsWith('/api/')) return res.status(404).json({ error: 'Not found' });
res.sendFile(join(clientDist, 'index.html'));
});
}
// Error handler
app.use((err, req, res, _next) => {
console.error('[server] Error:', err.message);
res.status(500).json({ error: err.message || 'Internal server error' });
});
async function start() {
try {
await initRules();
} catch (err) {
console.error('[server] Failed to load signing rules:', err.message);
console.error('[server] Signing will not work until rules are available');
}
app.listen(PORT, () => {
console.log(`[server] Listening on http://localhost:${PORT}`);
// Scan filesystem and populate media index in background
setImmediate(() => {
try { scanMediaFiles(); } catch (err) {
console.error('[server] Media scan failed:', err.message);
}
});
// Start auto-download/scrape scheduler
startScheduler();
});
// Start HTTPS server for DRM/EME support (requires secure context)
try {
const certDir = '/data/certs';
const certPath = `${certDir}/server.crt`;
const keyPath = `${certDir}/server.key`;
if (!existsSync(certPath) || !existsSync(keyPath)) {
mkdirSync(certDir, { recursive: true });
execSync(`openssl req -x509 -newkey rsa:2048 -keyout ${keyPath} -out ${certPath} -days 3650 -nodes -subj '/CN=ofapp'`);
console.log('[server] Generated self-signed HTTPS certificate');
}
const httpsServer = https.createServer({
key: readFileSync(keyPath),
cert: readFileSync(certPath),
}, app);
httpsServer.listen(HTTPS_PORT, () => {
console.log(`[server] HTTPS listening on https://localhost:${HTTPS_PORT}`);
});
} catch (err) {
console.error('[server] HTTPS setup failed:', err.message);
console.error('[server] DRM video playback will not work without HTTPS');
}
}
start();