124 lines
3.6 KiB
JavaScript
124 lines
3.6 KiB
JavaScript
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('<h3>Frontend not found!</h3><p>Please build the Vue frontend (<code>npm run build</code>) and copy the <code>dist</code> folder into the <code>docker-server</code> directory before running.</p>');
|
|
});
|
|
}
|
|
|
|
// Start Server
|
|
app.listen(port, () => {
|
|
console.log(`ClassHomeworkBoard Server running on port ${port}`);
|
|
console.log(`Data directory: ${dataDir}`);
|
|
console.log(`Static files directory: ${staticDir}`);
|
|
});
|