- Add ScrollToTop component matching main site's green Top button (appears after 130px scroll, same styling and font) - Fix main-site server.js: JS/CSS now use max-age=3600 + must-revalidate instead of 30d immutable — changes reach users within 1 hour instead of being stuck in browser cache for a month - Images/fonts keep 30d immutable (safe, as they are content-addressed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
3.5 KiB
JavaScript
89 lines
3.5 KiB
JavaScript
// Load environment variables from .env file for development
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
require('dotenv').config();
|
|
}
|
|
|
|
const express = require('express');
|
|
const bodyParser = require('body-parser');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const cors = require('cors');
|
|
|
|
const app = express();
|
|
const port = 3050;
|
|
// Admin password (shared secret). Defaults to "balloons" for development if not provided.
|
|
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'balloons';
|
|
|
|
// --- Production Security Check ---
|
|
if (process.env.NODE_ENV === 'production' && (!ADMIN_PASSWORD || ADMIN_PASSWORD === "balloons")) {
|
|
console.warn(`
|
|
****************************************************************
|
|
** WARNING: ADMIN_PASSWORD is not set or is insecure in production. **
|
|
** If the admin UI is already behind auth, this may be acceptable. **
|
|
** Otherwise, set a strong ADMIN_PASSWORD environment variable. **
|
|
****************************************************************
|
|
`);
|
|
}
|
|
|
|
// --- Middleware Setup ---
|
|
// More explicit CORS configuration to allow all origins
|
|
app.use(cors({
|
|
origin: '*'
|
|
}));
|
|
app.use(bodyParser.json());
|
|
|
|
// --- API Routes ---
|
|
const apiRouter = express.Router();
|
|
|
|
apiRouter.post('/update-status', (req, res) => {
|
|
console.log(`[${new Date().toISOString()}] Received request for /api/update-status`);
|
|
const { data } = req.body;
|
|
|
|
if (!data) {
|
|
return res.status(400).json({ success: false, message: 'Bad Request: No data provided.' });
|
|
}
|
|
|
|
const jsonString = JSON.stringify(data, null, 4);
|
|
const filePath = path.join(__dirname, 'update.json');
|
|
|
|
fs.writeFile(filePath, jsonString, (err) => {
|
|
if (err) {
|
|
console.error(`[${new Date().toISOString()}] Error writing to update.json:`, err);
|
|
return res.status(500).json({ success: false, message: 'Internal Server Error: Could not write to file.' });
|
|
}
|
|
console.log(`[${new Date().toISOString()}] update.json was successfully updated.`);
|
|
res.json({ success: true, message: 'Status updated successfully.' });
|
|
});
|
|
});
|
|
|
|
// Mount the API router under the /api path
|
|
app.use('/api', apiRouter);
|
|
|
|
// --- Static Files ---
|
|
const staticCacheOptions = {
|
|
maxAge: process.env.NODE_ENV === 'production' ? '30d' : 0,
|
|
setHeaders: (res, filePath) => {
|
|
if (filePath.endsWith('.html') || filePath.endsWith('update.json')) {
|
|
// Never cache HTML or live data
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
} else if (/\.(js|css)$/i.test(filePath)) {
|
|
// JS/CSS: 1 hour, must revalidate — allows updates to reach users quickly
|
|
res.setHeader('Cache-Control', 'public, max-age=3600, must-revalidate');
|
|
} else if (/\.(png|jpg|jpeg|webp|avif|svg|ico|woff2?)$/i.test(filePath)) {
|
|
// Images/fonts: 30 days immutable (these are named by content, rarely change)
|
|
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable');
|
|
}
|
|
}
|
|
};
|
|
// Serve bundled assets under /build with long cache
|
|
app.use('/build', express.static(path.join(__dirname, 'public/build'), staticCacheOptions));
|
|
// Serve static files from the root directory (handles all other GET requests)
|
|
app.use(express.static(path.join(__dirname), staticCacheOptions));
|
|
|
|
app.listen(port, () => {
|
|
console.log(`Server listening at http://localhost:${port}`);
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
console.log(`Admin panel available at http://localhost:${port}/admin.html`);
|
|
}
|
|
});
|