From 8c49bf32eb101300787a4acb2812baaff300ad92 Mon Sep 17 00:00:00 2001 From: Jordan Dalby Date: Tue, 5 Nov 2024 11:24:45 +0000 Subject: [PATCH 1/2] Simplified server --- server/src/app.js | 62 ++++++++++++++--------------------------------- 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/server/src/app.js b/server/src/app.js index a7d4fae..a369d7f 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,56 +1,30 @@ const express = require('express'); -const bodyParser = require('body-parser'); -const path = require('path'); -const fs = require('fs'); const { initializeDatabase } = require('./config/database'); const snippetRoutes = require('./routes/snippetRoutes'); const authRoutes = require('./routes/authRoutes'); const { authenticateToken } = require('./middleware/auth'); +const { join } = require('path'); -const expressApp = express(); -const port = 5000; +const app = express(); +const PORT = 5000; -function app(server) { - server.use(bodyParser.json()); - server.set('trust proxy', true); +app.use(express.json()); +app.use(express.static(join(__dirname, '../../client/build'))); - const clientBuildPath = '/client/build'; +app.get(/^\/(?!api)/, (req, res) => { + res.sendFile(join(__dirname, '../../client/build/index.html')); +}); - server.use(express.static(clientBuildPath)); - server.use('/api/auth', authRoutes); - server.use('/api/snippets', authenticateToken, snippetRoutes); - - server.get('*', (req, res) => { - if (req.path.includes('/assets/') || req.path.endsWith('.json')) { - console.log('404 for static file:', req.path); - res.status(404).send('File not found'); - } else { - res.sendFile(path.join(clientBuildPath, 'index.html')); - } - }); -} +app.use('/api/auth', authRoutes); +app.use('/api/snippets', authenticateToken, snippetRoutes); -async function startServer() { - try { - await initializeDatabase(); - return new Promise((resolve) => { - expressApp.listen(port, () => { - console.log(`Server running on port ${port}`); - resolve(); - }); - }); - } catch (error) { - console.error('Failed to initialize database:', error); - throw error; - } -} +(async () => { + await initializeDatabase(); -if (require.main === module) { - app(expressApp); - startServer().catch(err => { - console.error('Failed to start server:', err); - process.exit(1); + return new Promise((resolve) => { + app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + resolve(); }); -} - -module.exports = { app, startServer }; \ No newline at end of file + }); +})(); \ No newline at end of file From 639cbfdaabf90c16cffbab04e4d881e97b09633c Mon Sep 17 00:00:00 2001 From: Jordan Dalby Date: Tue, 5 Nov 2024 14:49:56 +0000 Subject: [PATCH 2/2] Fixed base paths by rewriting the files sent to the client to use the base path --- client/src/api/auth.ts | 3 +- client/src/api/snippets.ts | 3 +- docker-compose.yaml | 1 + server/src/app.js | 62 ++++++++++++++++++++++++++++++++++---- 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/client/src/api/auth.ts b/client/src/api/auth.ts index f78f7ce..deb5e7f 100644 --- a/client/src/api/auth.ts +++ b/client/src/api/auth.ts @@ -6,7 +6,8 @@ interface LoginResponse { token: string; } -export const AUTH_API_URL = '/api/auth'; +export const basePath = (window as any).__BASE_PATH__ || ''; +export const AUTH_API_URL = `${basePath}/api/auth`; interface ApiError extends Error { status?: number; diff --git a/client/src/api/snippets.ts b/client/src/api/snippets.ts index 4413bdf..c16a37f 100644 --- a/client/src/api/snippets.ts +++ b/client/src/api/snippets.ts @@ -1,6 +1,7 @@ import { Snippet } from '../types/types'; -export const API_URL = '/api/snippets'; +export const basePath = (window as any).__BASE_PATH__ || ''; +export const API_URL = `${basePath}/api/snippets`; interface ApiError extends Error { status?: number; diff --git a/docker-compose.yaml b/docker-compose.yaml index f373a0c..561f049 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,6 +6,7 @@ services: ports: - "5001:5000" environment: + - BASE_PATH= # if auth username or password are left blank then authorisation is disabled # the username used for logging in - AUTH_USERNAME=bytestash diff --git a/server/src/app.js b/server/src/app.js index a369d7f..472e201 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -4,19 +4,69 @@ const snippetRoutes = require('./routes/snippetRoutes'); const authRoutes = require('./routes/authRoutes'); const { authenticateToken } = require('./middleware/auth'); const { join } = require('path'); +const fs = require('fs'); const app = express(); const PORT = 5000; app.use(express.json()); -app.use(express.static(join(__dirname, '../../client/build'))); -app.get(/^\/(?!api)/, (req, res) => { - res.sendFile(join(__dirname, '../../client/build/index.html')); -}); +const basePath = process.env.BASE_PATH || ''; +const buildPath = join(__dirname, '../../client/build'); +const assetsPath = join(buildPath, 'assets'); + +app.use(`${basePath}/api/auth`, authRoutes); +app.use(`${basePath}/api/snippets`, authenticateToken, snippetRoutes); + +app.use(`${basePath}/assets`, express.static(assetsPath)); +app.use(`${basePath}/monacoeditorwork`, express.static(join(buildPath, 'monacoeditorwork'))); + +app.use(basePath, express.static(buildPath, { index: false })); -app.use('/api/auth', authRoutes); -app.use('/api/snippets', authenticateToken, snippetRoutes); +/* + * A bit of a hack, we need to manually rewrite the HTML to support base paths with ingress + * If given a base path of /bytestash, the index.html file will still be using /assets/xyz.css + * But of course the files are not there on ingress, so we need to change them to /bytestash/assets/xyz.css + * It's a bit of a hacky mess but this is the only solution I figured out without directly modifying vite.config.ts + * on the client, but this will affect everyone, so not a viable solution + * + * We're also injecting the base path into the HTML so that the client can know the value without relying on an + * environment variable, which would be baked into the client code at build time, which is not acceptable in this case + */ +app.get(`${basePath}/*`, (req, res, next) => { + if (req.url.startsWith(`${basePath}/api`)) { + return next(); + } + + // Don't cache, if the base path changes the previous index.html file is still used which will have incorrect paths + res.set({ + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'Pragma': 'no-cache', + 'Expires': '0', + }); + + fs.readFile(join(buildPath, 'index.html'), 'utf8', (err, data) => { + if (err) { + return res.status(500).send('Error loading index.html'); + } + + const modifiedHtml = data.replace( + /(src|href)="\/assets\//g, + `$1="${basePath}/assets/` + ).replace( + /\/monacoeditorwork\//g, + `${basePath}/monacoeditorwork/` + ); + + const scriptInjection = ``; + const injectedHtml = modifiedHtml.replace( + '', + `${scriptInjection}` + ); + + res.send(injectedHtml); + }); +}); (async () => { await initializeDatabase();