From 68cc98cfd49bdc8da08958ab0eba75b979fc103a Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Sun, 23 Nov 2025 11:45:42 +0100 Subject: [PATCH] refac --- index.js => src/index.js | 8 +- src/server/routes/categories.js | 18 +++ src/server/routes/images.js | 17 +++ src/server/routes/index.js | 20 ++++ src/server/routes/products.js | 32 ++++++ server.js => src/server/server.js | 107 +++--------------- src/server/socket/connection.js | 8 ++ .../syncers/category-products-syncer.js | 2 +- .../syncers/category-syncer.js | 2 +- .../syncers/picture-syncer.js | 2 +- database.js => src/utils/database.js | 0 11 files changed, 116 insertions(+), 100 deletions(-) rename index.js => src/index.js (87%) create mode 100644 src/server/routes/categories.js create mode 100644 src/server/routes/images.js create mode 100644 src/server/routes/index.js create mode 100644 src/server/routes/products.js rename server.js => src/server/server.js (50%) create mode 100644 src/server/socket/connection.js rename category-products-syncer.js => src/syncers/category-products-syncer.js (99%) rename category-syncer.js => src/syncers/category-syncer.js (99%) rename picture-syncer.js => src/syncers/picture-syncer.js (98%) rename database.js => src/utils/database.js (100%) diff --git a/index.js b/src/index.js similarity index 87% rename from index.js rename to src/index.js index 7bedcc5..dd85f42 100644 --- a/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ -import categorySyncer from './category-syncer.js'; -import pictureSyncer from './picture-syncer.js'; -import categoryProductsSyncer from './category-products-syncer.js'; -import { startServer } from './server.js'; +import categorySyncer from './syncers/category-syncer.js'; +import pictureSyncer from './syncers/picture-syncer.js'; +import categoryProductsSyncer from './syncers/category-products-syncer.js'; +import { startServer } from './server/server.js'; categorySyncer.on('synced', async ({ tree, unprunedTree, changed }) => { if (changed) { diff --git a/src/server/routes/categories.js b/src/server/routes/categories.js new file mode 100644 index 0000000..7496b52 --- /dev/null +++ b/src/server/routes/categories.js @@ -0,0 +1,18 @@ +export function registerCategories(app, cache) { + app.get('/api/categories', async (req, res) => { + try { + // Check if client has cached version + if (req.headers['if-none-match'] === cache.categories.etag) { + return res.status(304).end(); // Not Modified + } + + // Set cache headers with ETag + res.set('Cache-Control', 'public, max-age=60, must-revalidate'); + res.set('ETag', cache.categories.etag); + + res.json(JSON.parse(cache.categories.data)); + } catch (err) { + res.status(500).json({ error: 'Failed to load category tree' }); + } + }); +} diff --git a/src/server/routes/images.js b/src/server/routes/images.js new file mode 100644 index 0000000..873a69a --- /dev/null +++ b/src/server/routes/images.js @@ -0,0 +1,17 @@ +import path from 'path'; + +export function registerImages(app, cacheDir) { + app.get('/img/cat/:id.avif', (req, res) => { + const { id } = req.params; + const imagePath = path.join(cacheDir, 'img', 'categories', `${id}.avif`); + + // Cache images for 1 year (immutable content) + res.set('Cache-Control', 'public, max-age=31536000, immutable'); + + res.sendFile(path.resolve(imagePath), (err) => { + if (err) { + res.status(404).send('Image not found'); + } + }); + }); +} diff --git a/src/server/routes/index.js b/src/server/routes/index.js new file mode 100644 index 0000000..f63c5c8 --- /dev/null +++ b/src/server/routes/index.js @@ -0,0 +1,20 @@ +export function registerIndex(app, cache) { + app.get('/', async (req, res) => { + try { + // Check if client has cached version + if (req.headers['if-none-match'] === cache.html.etag) { + return res.status(304).end(); // Not Modified + } + + // Set cache headers with ETag + res.set('Cache-Control', 'public, max-age=300, must-revalidate'); + res.set('ETag', cache.html.etag); + res.set('Content-Type', 'text/html'); + + res.send(cache.html.data); + } catch (err) { + console.error('Error serving index.html:', err); + res.status(500).send('Error loading page'); + } + }); +} diff --git a/src/server/routes/products.js b/src/server/routes/products.js new file mode 100644 index 0000000..feb1d40 --- /dev/null +++ b/src/server/routes/products.js @@ -0,0 +1,32 @@ +export function registerProducts(app, cache, updateProductCache) { + app.get('/api/categories/:id/products', async (req, res) => { + try { + const id = parseInt(req.params.id); + + // Lazy load if not in cache + if (!cache.products.has(id)) { + await updateProductCache(id); + } + + const cached = cache.products.get(id); + + if (!cached) { + return res.status(404).json({ error: 'Category products not found' }); + } + + // Check if client has cached version + if (req.headers['if-none-match'] === cached.etag) { + return res.status(304).end(); // Not Modified + } + + // Set cache headers with ETag + res.set('Cache-Control', 'public, max-age=60, must-revalidate'); + res.set('ETag', cached.etag); + + res.json(JSON.parse(cached.data)); + } catch (err) { + console.error(`Error serving products for category ${req.params.id}:`, err); + res.status(500).json({ error: 'Failed to load products' }); + } + }); +} diff --git a/server.js b/src/server/server.js similarity index 50% rename from server.js rename to src/server/server.js index 5b08463..14db110 100644 --- a/server.js +++ b/src/server/server.js @@ -4,6 +4,11 @@ import { Server } from 'socket.io'; import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs/promises'; +import { registerCategories } from './routes/categories.js'; +import { registerProducts } from './routes/products.js'; +import { registerImages } from './routes/images.js'; +import { registerIndex } from './routes/index.js'; +import { registerConnection } from './socket/connection.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -61,7 +66,7 @@ export function startServer(categorySyncer, categoryProductsSyncer) { // Function to calculate ETag for HTML async function updateHtmlCache() { try { - const htmlPath = path.join(__dirname, 'index.html'); + const htmlPath = path.join(__dirname, '../../index.html'); const data = await fs.readFile(htmlPath, 'utf-8'); const crypto = await import('crypto'); cache.html.etag = crypto.createHash('md5').update(data).digest('hex'); @@ -93,100 +98,16 @@ export function startServer(categorySyncer, categoryProductsSyncer) { }); } - // Socket.io connection - io.on('connection', (socket) => { - console.log('🔌 Client connected'); - socket.on('disconnect', () => { - console.log('🔌 Client disconnected'); - }); - }); + // Register socket connection handler + registerConnection(io); - // Serve category tree JSON (with ETag for conditional caching) - app.get('/api/categories', async (req, res) => { - try { - // Check if client has cached version - if (req.headers['if-none-match'] === cache.categories.etag) { - return res.status(304).end(); // Not Modified - } - - // Set cache headers with ETag - res.set('Cache-Control', 'public, max-age=60, must-revalidate'); - res.set('ETag', cache.categories.etag); - - res.json(JSON.parse(cache.categories.data)); - } catch (err) { - res.status(500).json({ error: 'Failed to load category tree' }); - } - }); - - // Serve category products JSON (with ETag) - app.get('/api/categories/:id/products', async (req, res) => { - try { - const id = parseInt(req.params.id); - - // Lazy load if not in cache - if (!cache.products.has(id)) { - await updateProductCache(id); - } - - const cached = cache.products.get(id); - - if (!cached) { - return res.status(404).json({ error: 'Category products not found' }); - } - - // Check if client has cached version - if (req.headers['if-none-match'] === cached.etag) { - return res.status(304).end(); // Not Modified - } - - // Set cache headers with ETag - res.set('Cache-Control', 'public, max-age=60, must-revalidate'); - res.set('ETag', cached.etag); - - res.json(JSON.parse(cached.data)); - } catch (err) { - console.error(`Error serving products for category ${req.params.id}:`, err); - res.status(500).json({ error: 'Failed to load products' }); - } - }); - - // Serve category images (long cache - images rarely change) - app.get('/img/cat/:id.avif', (req, res) => { - const { id } = req.params; - const imagePath = path.join(CACHE_DIR, 'img', 'categories', `${id}.avif`); - - // Cache images for 1 year (immutable content) - res.set('Cache-Control', 'public, max-age=31536000, immutable'); - - res.sendFile(path.resolve(imagePath), (err) => { - if (err) { - res.status(404).send('Image not found'); - } - }); - }); - - // Serve index.html (with ETag for conditional caching) - app.get('/', async (req, res) => { - try { - // Check if client has cached version - if (req.headers['if-none-match'] === cache.html.etag) { - return res.status(304).end(); // Not Modified - } - - // Set cache headers with ETag - res.set('Cache-Control', 'public, max-age=300, must-revalidate'); - res.set('ETag', cache.html.etag); - res.set('Content-Type', 'text/html'); - - res.send(cache.html.data); - } catch (err) { - console.error('Error serving index.html:', err); - res.status(500).send('Error loading page'); - } - }); + // Register routes + registerCategories(app, cache); + registerProducts(app, cache, updateProductCache); + registerImages(app, CACHE_DIR); + registerIndex(app, cache); httpServer.listen(PORT, HOST, () => { console.log(`🌐 Server running on http://${HOST}:${PORT}`); }); -} +} \ No newline at end of file diff --git a/src/server/socket/connection.js b/src/server/socket/connection.js new file mode 100644 index 0000000..6032ca8 --- /dev/null +++ b/src/server/socket/connection.js @@ -0,0 +1,8 @@ +export function registerConnection(io) { + io.on('connection', (socket) => { + console.log('🔌 Client connected'); + socket.on('disconnect', () => { + console.log('🔌 Client disconnected'); + }); + }); +} diff --git a/category-products-syncer.js b/src/syncers/category-products-syncer.js similarity index 99% rename from category-products-syncer.js rename to src/syncers/category-products-syncer.js index 89db5be..1c65bb2 100644 --- a/category-products-syncer.js +++ b/src/syncers/category-products-syncer.js @@ -1,7 +1,7 @@ import fs from 'fs/promises'; import path from 'path'; import { EventEmitter } from 'events'; -import { createConnection } from './database.js'; +import { createConnection } from '../utils/database.js'; class CategoryProductsSyncer extends EventEmitter { constructor() { diff --git a/category-syncer.js b/src/syncers/category-syncer.js similarity index 99% rename from category-syncer.js rename to src/syncers/category-syncer.js index 55bc0cb..979cd84 100644 --- a/category-syncer.js +++ b/src/syncers/category-syncer.js @@ -1,7 +1,7 @@ import { EventEmitter } from 'events'; import fs from 'fs/promises'; import path from 'path'; -import { createConnection } from './database.js'; +import { createConnection } from '../utils/database.js'; class CategorySyncer extends EventEmitter { constructor() { diff --git a/picture-syncer.js b/src/syncers/picture-syncer.js similarity index 98% rename from picture-syncer.js rename to src/syncers/picture-syncer.js index 3c8ebd5..3e501dd 100644 --- a/picture-syncer.js +++ b/src/syncers/picture-syncer.js @@ -1,7 +1,7 @@ import fs from 'fs/promises'; import path from 'path'; import sharp from 'sharp'; -import { createConnection } from './database.js'; +import { createConnection } from '../utils/database.js'; class PictureSyncer { constructor() { diff --git a/database.js b/src/utils/database.js similarity index 100% rename from database.js rename to src/utils/database.js