import express from 'express'; import cors from 'cors'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const port = process.env.PORT || 3001; // Middlewares app.use(cors()); app.use(express.json({ limit: '50mb' })); // Data Directory for JSON files const dataDir = path.resolve(__dirname, 'data'); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } // ======================= // API Routes // ======================= // Mock PPT APIs (Windows GUI features are not supported on Linux/Docker) app.get('/api/ppt/check', (req, res) => { res.json({ success: true, hasPpt: false, ppts: [] }); }); app.post('/api/ppt/control', (req, res) => { res.json({ success: false, error: 'Not supported on server environment' }); }); // Local File Storage APIs app.post('/api/local-file/save', (req, res) => { try { const { key, data } = req.body; if (!key || !data) throw new Error('Missing key or data'); // Sanitize key const safeKey = String(key).replace(/[^a-zA-Z0-9_-]/g, '_'); const filePath = path.join(dataDir, `${safeKey}.json`); fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); res.json({ success: true }); } catch (e) { res.status(500).json({ success: false, error: e.message }); } }); app.get('/api/local-file/load', (req, res) => { try { const key = req.query.key; if (!key) { return res.status(400).json({ success: false, error: 'Missing key' }); } const safeKey = String(key).replace(/[^a-zA-Z0-9_-]/g, '_'); const filePath = path.join(dataDir, `${safeKey}.json`); if (fs.existsSync(filePath)) { const data = fs.readFileSync(filePath, 'utf-8'); res.json({ success: true, data: JSON.parse(data) }); } else { res.status(404).json({ success: false, error: 'Not found' }); } } catch (e) { res.status(500).json({ success: false, error: e.message }); } }); app.get('/api/local-file/keys', (req, res) => { try { const files = fs.readdirSync(dataDir); const keys = files .filter(f => f.endsWith('.json')) .map(f => f.replace('.json', '')); res.json({ success: true, keys }); } catch (e) { res.status(500).json({ success: false, error: e.message }); } }); // ======================= // Static Files & SPA // ======================= const staticDir = path.resolve(__dirname, 'dist'); if (fs.existsSync(staticDir)) { // Serve static files app.use(express.static(staticDir, { setHeaders: (res, path, stat) => { // Prevent caching for sw.js and index.html if (path.endsWith('sw.js') || path.endsWith('index.html')) { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); } else { // Cache other static assets res.setHeader('Cache-Control', 'public, max-age=3600'); } } })); // Catch-all route for SPA fallback app.get('*', (req, res) => { res.sendFile(path.join(staticDir, 'index.html')); }); } else { // Fallback if dist doesn't exist yet app.get('*', (req, res) => { res.send('

Frontend not found!

Please build the Vue frontend (npm run build) and copy the dist folder into the docker-server directory before running.

'); }); } // Start Server app.listen(port, () => { console.log(`ClassHomeworkBoard Server running on port ${port}`); console.log(`Data directory: ${dataDir}`); console.log(`Static files directory: ${staticDir}`); });