From 0327ef81a664dd6cd179c0801c45dfbe26f872e3 Mon Sep 17 00:00:00 2001 From: giteaadmin Date: Sat, 23 Aug 2025 11:27:40 -0400 Subject: [PATCH] feat: add proxy middleware for API requests and create production Docker Compose setup --- .eleventy.js | 16 +++++++ docker-compose.prod.yml | 20 ++++++++ package-lock.json | 103 +++++++++++++++++++++++++++++++++++++++- package.json | 3 +- src/js/view-counter.js | 5 +- 5 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 docker-compose.prod.yml diff --git a/.eleventy.js b/.eleventy.js index 9f252d3..008c837 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,4 +1,5 @@ module.exports = function(eleventyConfig) { + const { createProxyMiddleware } = require('http-proxy-middleware'); // Pass through static assets from the "src" directory eleventyConfig.addPassthroughCopy("src/css"); @@ -42,6 +43,21 @@ module.exports = function(eleventyConfig) { // Add a shortcode for the current year for the footer eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`); + // Only add the proxy middleware when in serve mode + if (process.env.ELEVENTY_RUN_MODE === "serve") { + // Dev server options + eleventyConfig.setServerOptions({ + // When the site is served by Eleventy's dev server, proxy API requests + // to the API server which is running in a separate container. + middleware: [ + createProxyMiddleware('/api', { + target: 'http://api:3000', // The 'api' service container + changeOrigin: true, + }), + ], + }); + } + return { // Set the source and output directories dir: { diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..6cbc7c9 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,20 @@ +# docker-compose.prod.yml for production +# This setup builds and runs the optimized production image. +# +# To start, run: docker-compose -f docker-compose.prod.yml up --build +# +services: + app: + build: + context: . + target: production # Use the 'production' stage from the Dockerfile + container_name: eleventy_prod + ports: + - "3000:3000" + volumes: + # Persist the view count data in a named volume. + - views_data:/app/_data + restart: unless-stopped + +volumes: + views_data: diff --git a/package-lock.json b/package-lock.json index e151a58..9cc50f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@11ty/eleventy": "^2.0.1", "cors": "^2.8.5", "express": "^4.21.2", - "fs-extra": "^11.3.1" + "fs-extra": "^11.3.1", + "nodemon": "^3.1.0" } }, "node_modules/@11ty/dependency-tree": { @@ -1190,6 +1191,14 @@ "uglify-js": "^3.1.4" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1278,6 +1287,11 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1846,6 +1860,53 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2076,6 +2137,11 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, "node_modules/pug": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", @@ -2545,6 +2611,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -2601,6 +2678,17 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2636,6 +2724,14 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2665,6 +2761,11 @@ "node": ">=0.8.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index 4e8b1ba..99db7fb 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "cors": "^2.8.5", "express": "^4.21.2", "fs-extra": "^11.3.1", - "nodemon": "^3.1.0" + "nodemon": "^3.1.0", + "http-proxy-middleware": "^3.0.0" } } diff --git a/src/js/view-counter.js b/src/js/view-counter.js index db779f0..c990cc8 100644 --- a/src/js/view-counter.js +++ b/src/js/view-counter.js @@ -3,12 +3,11 @@ document.addEventListener('DOMContentLoaded', () => { if (viewCountElement) { const slug = viewCountElement.dataset.slug; - const serverUrl = 'http://localhost:3000'; // IMPORTANT: Replace with your server's IP/domain // Function to fetch and display the view count const getViews = async () => { try { - const response = await fetch(`${serverUrl}/api/views/${slug}`); + const response = await fetch(`/api/views/${slug}`); if (!response.ok) throw new Error('Failed to fetch views'); const data = await response.json(); viewCountElement.textContent = `${data.count} views`; @@ -24,7 +23,7 @@ document.addEventListener('DOMContentLoaded', () => { // Use a flag in localStorage to prevent incrementing on every refresh const viewed = localStorage.getItem(`viewed-${slug}`); if (!viewed) { - await fetch(`${serverUrl}/api/views/${slug}`, { method: 'POST' }); + await fetch(`/api/views/${slug}`, { method: 'POST' }); localStorage.setItem(`viewed-${slug}`, 'true'); } } catch (error) {