diff --git a/Dockerfile b/Dockerfile index 7818282..a94b67d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,33 @@ -FROM node:18-alpine -ENV NODE_ENV=production +# STAGE 1: Build the Eleventy site +FROM node:18-alpine AS builder WORKDIR /app + +# Copy project files and install dependencies COPY package*.json ./ RUN npm install COPY . . -EXPOSE 8080 9229 -CMD [ "npm", "start" ] \ No newline at end of file + +# Build the Eleventy site +# The output will be in the default `_site` directory +RUN npx @11ty/eleventy + +# STAGE 2: Setup the production server +FROM node:18-alpine +WORKDIR /app + +# Copy server dependencies from the builder stage +COPY --from=builder /app/package*.json ./ + +# Install only production dependencies for the server +RUN npm install --omit=dev + +# Copy the server file and the built Eleventy site +COPY --from=builder /app/server.js . +COPY --from=builder /app/_site ./_site +COPY --from=builder /app/data ./data + +# Expose the port the server runs on +EXPOSE 8080 + +# The command to start the server +CMD [ "npm", "start" ] diff --git a/package.json b/package.json index a86d672..a28060a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "author": "", "license": "ISC", "dependencies": { - "@11ty/eleventy": "^2.0.1" + "@11ty/eleventy": "^2.0.1", + "cors": "^2.8.5", + "express": "^4.21.2", + "fs-extra": "^11.3.1" } } diff --git a/src/_data/views.json b/src/_data/views.json new file mode 100644 index 0000000..6c13182 --- /dev/null +++ b/src/_data/views.json @@ -0,0 +1 @@ +{"8.14.25":1,"8.5.25":1} diff --git a/src/_includes/layout.njk b/src/_includes/layout.njk index 61fcea6..25474a7 100644 --- a/src/_includes/layout.njk +++ b/src/_includes/layout.njk @@ -94,4 +94,4 @@ - \ No newline at end of file + diff --git a/src/_includes/post.njk b/src/_includes/post.njk index ea7ba5b..5cbfa15 100644 --- a/src/_includes/post.njk +++ b/src/_includes/post.njk @@ -9,8 +9,12 @@ layout: "layout.njk"

{{ title }}

-

Published on {{ date | readableDate }}

+
+

Published on {{ date | readableDate }}

+ Loading views... +

{{ content | safe }} -
\ No newline at end of file + +
diff --git a/src/js/view-counter.js b/src/js/view-counter.js new file mode 100644 index 0000000..db779f0 --- /dev/null +++ b/src/js/view-counter.js @@ -0,0 +1,38 @@ +document.addEventListener('DOMContentLoaded', () => { + const viewCountElement = document.querySelector('[data-view-count]'); + + 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}`); + if (!response.ok) throw new Error('Failed to fetch views'); + const data = await response.json(); + viewCountElement.textContent = `${data.count} views`; + } catch (error) { + console.error('Error getting views:', error); + viewCountElement.textContent = 'Could not load views'; + } + }; + + // Function to increment the view count + const incrementView = async () => { + try { + // 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' }); + localStorage.setItem(`viewed-${slug}`, 'true'); + } + } catch (error) { + console.error('Error incrementing view:', error); + } + }; + + // Run the functions + incrementView().then(getViews); + } +}); diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..118b481 --- /dev/null +++ b/src/server.js @@ -0,0 +1,53 @@ +const express = require('express'); +const cors = require('cors'); +const fs = require('fs-extra'); +const path = require('path'); + +const app = express(); +const port = 3000; // You can change this port if needed + +// Path to the file where view counts will be stored +const dbPath = path.join(__dirname, '_data', 'views.json'); + +// Ensure the data directory and the views.json file exist +fs.ensureFileSync(dbPath); +const initialData = fs.readFileSync(dbPath, 'utf8'); +if (initialData.length === 0) { + fs.writeJsonSync(dbPath, {}); +} + +app.use(cors()); // Enable Cross-Origin Resource Sharing +app.use(express.json()); + +// --- API Endpoints --- + +// GET: Fetch the view count for a specific post slug +app.get('/api/views/:slug', async (req, res) => { + try { + const { slug } = req.params; + const views = await fs.readJson(dbPath); + const count = views[slug] || 0; + res.json({ count }); + } catch (error) { + console.error('Error reading view count:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// POST: Increment the view count for a specific post slug +app.post('/api/views/:slug', async (req, res) => { + try { + const { slug } = req.params; + const views = await fs.readJson(dbPath); + views[slug] = (views[slug] || 0) + 1; + await fs.writeJson(dbPath, views); + res.json({ count: views[slug] }); + } catch (error) { + console.error('Error updating view count:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +app.listen(port, () => { + console.log(`View counter server listening at http://localhost:${port}`); +});