From 6c72c02896da6a03db542aa5989c29dbebb85464 Mon Sep 17 00:00:00 2001 From: Fredrik Wahlberg Date: Fri, 24 Jan 2025 20:54:12 +0100 Subject: [PATCH] Tagghantering, fix #3 --- Dockerfile | 3 ++ package-lock.json | 80 ++++++++++++++++++++++++++++++++++------ package.json | 1 + public/app.js | 49 +++++++++++++++++++++++- public/index.html | 5 +++ public/service-worker.js | 15 +++++--- server.js | 45 ++++++++++++++++++++-- 7 files changed, 177 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 57ec642..4609219 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,5 +16,8 @@ COPY . . # Expose the port the app runs on EXPOSE 3044 +# Set the DEBUG environment variable +ENV DEBUG=app + # Command to run the application CMD ["node", "server.js"] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8c38c4a..3fcf444 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "basic-auth": "^2.0.1", "body-parser": "^1.20.3", + "debug": "^4.4.0", "express": "^4.21.2", "fs": "^0.0.1-security" } @@ -71,6 +72,19 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -139,11 +153,19 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/depd": { @@ -274,6 +296,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -291,6 +326,19 @@ "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -490,9 +538,9 @@ } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/negotiator": { "version": "0.6.3", @@ -632,6 +680,19 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -640,11 +701,6 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", diff --git a/package.json b/package.json index 04bb4fb..0d5998f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "basic-auth": "^2.0.1", "body-parser": "^1.20.3", + "debug": "^4.4.0", "express": "^4.21.2", "fs": "^0.0.1-security" } diff --git a/public/app.js b/public/app.js index e528212..cb116a4 100644 --- a/public/app.js +++ b/public/app.js @@ -20,6 +20,7 @@ document.addEventListener('DOMContentLoaded', function() { if (sessionStorage.getItem('loggedIn') === 'true') { loginContainer.style.display = 'none'; appContainer.style.display = 'block'; + loadTags(); } loginForm.addEventListener('submit', function(e) { @@ -33,6 +34,7 @@ document.addEventListener('DOMContentLoaded', function() { sessionStorage.setItem('loggedIn', 'true'); loginContainer.style.display = 'none'; appContainer.style.display = 'block'; + loadTags(); } else { loginMessage.textContent = 'Invalid username or password'; } @@ -45,14 +47,31 @@ document.addEventListener('DOMContentLoaded', function() { const subject = document.getElementById('subject').value; const description = document.getElementById('description').value; const scheduled = document.getElementById('scheduled').value; + const tagsInput = document.getElementById('tags').value; + const tags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag).join(':'); // Structure data for Org mode const taskData = { - subject, + subject: `${subject} :${tags}:`, description, scheduled }; + // Save tags to server + const savedTags = JSON.parse(localStorage.getItem('tags')) || []; + const newTags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag && !savedTags.includes(tag)); + const allTags = [...savedTags, ...newTags]; + localStorage.setItem('tags', JSON.stringify(allTags)); + fetch('/save-tags', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ tags: allTags }) + }).then(() => { + loadTags(); // Force refresh tags after saving + }); + // Send data to backend using fetch fetch('/add-task', { method: 'POST', @@ -82,4 +101,32 @@ document.addEventListener('DOMContentLoaded', function() { weekNumbers: true, // Show week numbers firstDayOfWeek: 1 // Start weeks on Monday }); + + // Load tags from server and initialize autocomplete + function loadTags() { + fetch('/get-tags') + .then(response => response.json()) + .then(tags => { + localStorage.setItem('tags', JSON.stringify(tags)); + const autocompleteData = {}; + tags.forEach(tag => { + autocompleteData[tag] = null; // Materialize autocomplete requires a key-value pair + }); + + const tagsInput = document.getElementById('tags'); + M.Autocomplete.init(tagsInput, { + data: autocompleteData, + onAutocomplete: function(selectedTag) { + const currentTags = tagsInput.value.split(',').map(tag => tag.trim()).filter(tag => tag); + if (!currentTags.includes(selectedTag)) { + currentTags.push(selectedTag); + tagsInput.value = currentTags.join(', '); + } + } + }); + }) + .catch(error => { + console.error('Error loading tags:', error); + }); + } }); diff --git a/public/index.html b/public/index.html index 23e4fe5..87e067d 100644 --- a/public/index.html +++ b/public/index.html @@ -47,6 +47,11 @@ +
+ + +
+

diff --git a/public/service-worker.js b/public/service-worker.js index bb93cb9..fb575a2 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -15,9 +15,14 @@ self.addEventListener('install', (event) => { }); self.addEventListener('fetch', (event) => { - event.respondWith( - caches.match(event.request).then((cachedResponse) => { - return cachedResponse || fetch(event.request); - }) - ); + if (event.request.url.includes('/get-tags') || event.request.url.includes('/save-tags')) { + // Bypass cache for tags endpoints + event.respondWith(fetch(event.request)); + } else { + event.respondWith( + caches.match(event.request).then((cachedResponse) => { + return cachedResponse || fetch(event.request); + }) + ); + } }); diff --git a/server.js b/server.js index ebd1b5e..074f584 100644 --- a/server.js +++ b/server.js @@ -3,6 +3,7 @@ const bodyParser = require('body-parser'); const fs = require('fs'); const path = require('path'); const basicAuth = require('basic-auth'); +const debug = require('debug')('app'); const app = express(); const port = 3044; @@ -11,11 +12,17 @@ app.use(bodyParser.json()); app.use(express.static('public')); // Ensure the /data directory exists -const dataDir = path.join('/data'); +const dataDir = '/data'; if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } +// Ensure the tags.json file exists +const tagsFilePath = path.join(dataDir, 'tags.json'); +if (!fs.existsSync(tagsFilePath)) { + fs.writeFileSync(tagsFilePath, JSON.stringify([])); +} + // Authentication middleware const auth = (req, res, next) => { const user = basicAuth(req); @@ -57,12 +64,44 @@ app.post('/add-task', auth, (req, res) => { const filePath = path.join(dataDir, 'tasks.org'); fs.appendFile(filePath, orgFormattedData, (err) => { if (err) { + debug('Error writing to file:', err); return res.status(500).send('Error writing to file.'); } res.send({ message: 'Task added successfully!' }); }); }); -app.listen(port, () => { - console.log(`Server running at http://localhost:${port}`); +// Endpoint to save tags +app.post('/save-tags', auth, (req, res) => { + const { tags } = req.body; + const filePath = path.join(dataDir, 'tags.json'); + fs.writeFile(filePath, JSON.stringify(tags), (err) => { + if (err) { + debug('Error saving tags:', err); + return res.status(500).send('Error saving tags.'); + } + res.send({ message: 'Tags saved successfully!' }); + }); +}); + +// Endpoint to retrieve tags +app.get('/get-tags', auth, (req, res) => { + const filePath = path.join(dataDir, 'tags.json'); + fs.readFile(filePath, (err, data) => { + if (err) { + debug('Error retrieving tags:', err); + return res.status(500).send('Error retrieving tags.'); + } + try { + const tags = JSON.parse(data); + res.send(tags); + } catch (e) { + debug('Error parsing tags JSON:', e); + res.send([]); + } + }); +}); + +app.listen(port, () => { + debug(`Server running at http://localhost:${port}`); });