19 Commits

Author SHA1 Message Date
05d6cba8ef Fix #20 2025-01-25 09:04:25 +01:00
6864cfa14e Tar bort rest av basic auth 2025-01-25 08:38:54 +01:00
6159fc4ee8 .vscode 2025-01-25 08:23:05 +01:00
6520dcb78c Lägger till .vscode för att dölja ikoner etc. 2025-01-25 08:22:55 +01:00
c993848278 Merge pull request 'Längre intervall på healthcheck' (#17) from reset into main
Reviewed-on: #17
2025-01-24 23:17:55 +01:00
87feaa9880 Längre intervall på healthcheck 2025-01-24 23:17:02 +01:00
23a39247a1 Implement session-based authentication and add logout functionality
Fix #15
2025-01-24 22:02:54 +01:00
da2f568acf Implement server-side authentication and update login UI texts 2025-01-24 21:54:05 +01:00
1edcefbd64 Ta bort kolon vid tomma taggar.
Fixar #12
2025-01-24 21:40:16 +01:00
276587f1dc Merge pull request 'cleanup' (#11) from cleanup into main
Reviewed-on: #11
2025-01-24 21:34:53 +01:00
adbe5fcfb4 Lägg till hälsokontroll för tjänsten i docker-compose, fixes #8 2025-01-24 21:33:09 +01:00
a58da5398c Lägg till hälsokontroll för tjänsten i docker-compose 2025-01-24 21:30:19 +01:00
51656c0ee4 Merge pull request 'Lägg till autentisering och loggning, refaktorisera uppgifter till router' (#9) from cleanup into main
Reviewed-on: #9
2025-01-24 21:26:50 +01:00
e091da93e9 Lägg till autentisering och loggning, refaktorisera uppgifter till router 2025-01-24 21:26:06 +01:00
70365715fe Uppdaterar iconerna 2025-01-24 21:08:18 +01:00
6c72c02896 Tagghantering, fix #3 2025-01-24 20:54:12 +01:00
eb5adae406 Vi har morgondagen som förvalt datum istället för idag 2025-01-24 18:30:01 +01:00
892c20dfae Ny inloggning, formulär innnan. Fixes #7 2025-01-24 18:26:53 +01:00
d75a0882dd Fixes #5 2025-01-24 18:22:05 +01:00
19 changed files with 570 additions and 102 deletions

9
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"files.exclude": {
"**/*.png": true,
"**/*.jpg": true,
"**/*.gif": true,
"**/*.svg": true,
"**/*.ico": true
}
}

View File

@@ -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"]

View File

@@ -9,6 +9,15 @@ services:
volumes:
- /srv/swarm/org-todo-pwa/data:/data
user: "1000:1000"
environment:
- DEBUG=app
- AUTH_USERNAME=fredrik
- AUTH_PASSWORD=apa
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3044"]
interval: 5m
timeout: 10s
retries: 3
deploy:
labels:
- "traefik.enable=true"
@@ -16,3 +25,4 @@ services:
- "traefik.http.routers.plan.tls.certresolver=myhttpchallenge"
- "traefik.http.routers.plan.rule=Host(`todo.casablanca.wahlberg.se`)"
- "traefik.http.routers.plan.entrypoints=websecure"

98
logger.js Normal file
View File

@@ -0,0 +1,98 @@
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.json()
),
transports: [
new transports.Console(),
new transports.File({ filename: 'app.log' })
]
});
module.exports = logger;
// filepath: /home/fredrik/dev/org-todo-pwa/routes/tasks.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const logger = require('../logger');
const auth = require('../middleware/auth');
const router = express.Router();
const dataDir = '/data';
// Ensure the /data directory exists
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([]));
}
// Protect the /add-task endpoint with authentication
router.post('/add-task', auth, async (req, res) => {
const { subject, description, scheduled } = req.body;
const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
let orgFormattedData = `
* TODO ${subject}
SCHEDULED: <${scheduled}>
:LOGBOOK:
- State "TODO" from "TODO" [${currentDateTime}]
:END:
`;
if (description) {
orgFormattedData = `
* TODO ${subject}
${description}
SCHEDULED: <${scheduled}>
:LOGBOOK:
- State "TODO" from "TODO" [${currentDateTime}]
:END:
`;
}
const filePath = path.join(dataDir, 'tasks.org');
try {
await fs.promises.appendFile(filePath, orgFormattedData);
res.send({ message: 'Task added successfully!' });
} catch (err) {
logger.error('Error writing to file:', err);
res.status(500).send('Error writing to file.');
}
});
// Endpoint to save tags
router.post('/save-tags', auth, async (req, res) => {
const { tags } = req.body;
const filePath = path.join(dataDir, 'tags.json');
try {
await fs.promises.writeFile(filePath, JSON.stringify(tags));
res.send({ message: 'Tags saved successfully!' });
} catch (err) {
logger.error('Error saving tags:', err);
res.status(500).send('Error saving tags.');
}
});
// Endpoint to retrieve tags
router.get('/get-tags', auth, async (req, res) => {
const filePath = path.join(dataDir, 'tags.json');
try {
const data = await fs.promises.readFile(filePath);
const tags = JSON.parse(data);
res.send(tags);
} catch (err) {
logger.error('Error retrieving tags:', err);
res.status(500).send('Error retrieving tags.');
}
});
module.exports = router;

9
middleware/auth.js Normal file
View File

@@ -0,0 +1,9 @@
const auth = (req, res, next) => {
if (req.session && req.session.user) {
return next();
} else {
res.status(401).send('Authentication required.');
}
};
module.exports = auth;

164
package-lock.json generated
View File

@@ -11,7 +11,10 @@
"dependencies": {
"basic-auth": "^2.0.1",
"body-parser": "^1.20.3",
"debug": "^4.4.0",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-session": "^1.18.1",
"fs": "^0.0.1-security"
}
},
@@ -71,6 +74,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 +155,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": {
@@ -163,6 +187,17 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -274,6 +309,63 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express-session": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
"dependencies": {
"cookie": "0.7.2",
"cookie-signature": "1.0.7",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/express-session/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express-session/node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
},
"node_modules/express-session/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-session/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"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 +383,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 +595,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",
@@ -524,6 +629,14 @@
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -563,6 +676,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -632,6 +753,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 +774,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",
@@ -760,6 +889,17 @@
"node": ">= 0.6"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@@ -12,7 +12,10 @@
"dependencies": {
"basic-auth": "^2.0.1",
"body-parser": "^1.20.3",
"debug": "^4.4.0",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-session": "^1.18.1",
"fs": "^0.0.1-security"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -10,6 +10,52 @@ if ('serviceWorker' in navigator) {
});
}
document.addEventListener('DOMContentLoaded', function() {
const loginForm = document.getElementById('loginForm');
const loginContainer = document.getElementById('loginContainer');
const appContainer = document.getElementById('appContainer');
const loginMessage = document.getElementById('loginMessage');
// Check if user is already logged in
fetch('/check-session')
.then(response => response.json())
.then(data => {
if (data.loggedIn) {
loginContainer.style.display = 'none';
appContainer.style.display = 'block';
loadTags();
}
});
loginForm.addEventListener('submit', function(e) {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
// Send credentials to the server for validation
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa(username + ':' + password)
}
})
.then(response => {
if (response.ok) {
sessionStorage.setItem('loggedIn', 'true');
loginContainer.style.display = 'none';
appContainer.style.display = 'block';
loadTags();
} else {
loginMessage.textContent = 'Invalid username or password';
}
})
.catch(error => {
loginMessage.textContent = 'Error logging in';
});
});
document.getElementById('taskForm').addEventListener('submit', function(e) {
e.preventDefault();
@@ -17,14 +63,31 @@ document.getElementById('taskForm').addEventListener('submit', function(e) {
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: tags ? `${subject} :${tags}:` : subject,
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',
@@ -41,3 +104,45 @@ document.getElementById('taskForm').addEventListener('submit', function(e) {
document.getElementById('responseMessage').textContent = "Error saving task!";
});
});
// Set tomorrow's date as the default for the date input
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const tomorrowString = tomorrow.toISOString().split('T')[0];
document.getElementById('scheduled').value = tomorrowString;
// Initialize flatpickr with Swedish locale and Monday as the first day of the week
flatpickr("#scheduled", {
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);
});
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 467 B

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -13,12 +13,28 @@
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
</head>
<body>
<div class="container">
<div id="loginContainer" class="container">
<h1 class="center-align">Logga in</h1>
<form id="loginForm">
<div class="input-field">
<label for="username">Användarnamn:</label>
<input type="text" id="username" required>
</div>
<div class="input-field">
<label for="password">Lösenord:</label>
<input type="password" id="password" required>
</div>
<button class="btn waves-effect waves-light" type="submit">Login</button>
</form>
<p id="loginMessage" class="center-align"></p>
</div>
<div id="appContainer" class="container" style="display:none;">
<h1 class="center-align">TODO</h1>
<form id="taskForm">
<div class="input-field">
<label for="subject">Uppgift:</label>
<input type="text" id="subject" required>
<input type="text" id="subject" required autocomplete="off">
</div>
<div class="input-field">
@@ -31,6 +47,11 @@
<input type="date" id="scheduled" lang="sv-SE" required>
</div>
<div class="input-field">
<label for="tags">Taggar (separera med komma):</label>
<input type="text" id="tags" class="autocomplete" autocomplete="off">
</div>
<button class="btn waves-effect waves-light" type="submit">Spara</button>
</form>
<p id="responseMessage" class="center-align"></p>
@@ -43,7 +64,6 @@
document.getElementById('scheduled').value = today;
// Initialize flatpickr with Swedish locale and Monday as the first day of the week
flatpickr("#scheduled", {
//locale: "sv-SE", // Set Swedish locale
weekNumbers: true, // Show week numbers
firstDayOfWeek: 1 // Start weeks on Monday
});

View File

@@ -15,9 +15,14 @@ self.addEventListener('install', (event) => {
});
self.addEventListener('fetch', (event) => {
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);
})
);
}
});

27
routes/auth.js Normal file
View File

@@ -0,0 +1,27 @@
const express = require('express');
const basicAuth = require('basic-auth');
const router = express.Router();
router.post('/login', (req, res) => {
const user = basicAuth(req);
const username = process.env.AUTH_USERNAME;
const password = process.env.AUTH_PASSWORD;
if (user && user.name === username && user.pass === password) {
req.session.user = user.name;
res.status(200).send('Login successful');
} else {
res.status(401).send('Authentication required');
}
});
router.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).send('Error logging out');
}
res.status(200).send('Logout successful');
});
});
module.exports = router;

79
routes/tasks.js Normal file
View File

@@ -0,0 +1,79 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const debug = require('debug')('app');
const auth = require('../middleware/auth');
const router = express.Router();
const dataDir = '/data';
// Ensure the /data directory exists
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([]));
}
// Protect the /add-task endpoint with authentication
router.post('/add-task', auth, async (req, res) => {
const { subject, description, scheduled } = req.body;
const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
let orgFormattedData = `
* TODO ${subject}
SCHEDULED: <${scheduled}>
:LOGBOOK:
- State "TODO" from "TODO" [${currentDateTime}]
:END:`;
if (description) {
orgFormattedData = `
* TODO ${subject}
${description}
SCHEDULED: <${scheduled}>
:LOGBOOK:
- State "TODO" from "TODO" [${currentDateTime}]
:END:`;
}
const filePath = path.join(dataDir, 'tasks.org');
try {
await fs.promises.appendFile(filePath, orgFormattedData);
res.send({ message: 'Task added successfully!' });
} catch (err) {
debug('Error writing to file:', err);
res.status(500).send('Error writing to file.');
}
});
// Endpoint to save tags
router.post('/save-tags', auth, async (req, res) => {
const { tags } = req.body;
const filePath = path.join(dataDir, 'tags.json');
try {
await fs.promises.writeFile(filePath, JSON.stringify(tags));
res.send({ message: 'Tags saved successfully!' });
} catch (err) {
debug('Error saving tags:', err);
res.status(500).send('Error saving tags.');
}
});
// Endpoint to retrieve tags
router.get('/get-tags', auth, async (req, res) => {
const filePath = path.join(dataDir, 'tags.json');
try {
const data = await fs.promises.readFile(filePath);
const tags = JSON.parse(data);
res.send(tags);
} catch (err) {
debug('Error retrieving tags:', err);
res.status(500).send('Error retrieving tags.');
}
});
module.exports = router;

View File

@@ -1,8 +1,10 @@
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const basicAuth = require('basic-auth');
const session = require('express-session');
const debug = require('debug')('app');
const tasksRouter = require('./routes/tasks');
const authRouter = require('./routes/auth');
const app = express();
const port = 3044;
@@ -10,59 +12,17 @@ const port = 3044;
app.use(bodyParser.json());
app.use(express.static('public'));
// Ensure the /data directory exists
const dataDir = path.join('/data');
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
// Configure session middleware
app.use(session({
secret: process.env.SESSION_SECRET || 'default_secret', // Use a strong secret in production
resave: false,
saveUninitialized: true,
cookie: { secure: false } // Set to true if using HTTPS
}));
// Authentication middleware
const auth = (req, res, next) => {
const user = basicAuth(req);
const username = 'fredrik'; // Replace with your desired username
const password = 'apa'; // Replace with your desired password
if (user && user.name === username && user.pass === password) {
return next();
} else {
res.set('WWW-Authenticate', 'Basic realm="401"');
return res.status(401).send('Authentication required.');
}
};
// Protect the /add-task endpoint with authentication
app.post('/add-task', auth, (req, res) => {
const { subject, description, scheduled } = req.body;
const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
let orgFormattedData = `
* TODO ${subject}
SCHEDULED: <${scheduled}>
:LOGBOOK:
- State "TODO" from "TODO" [${currentDateTime}]
:END:
`;
if (description) {
orgFormattedData = `
* TODO ${subject}
${description}
SCHEDULED: <${scheduled}>
:LOGBOOK:
- State "TODO" from "TODO" [${currentDateTime}]
:END:
`;
}
const filePath = path.join(dataDir, 'tasks.org');
fs.appendFile(filePath, orgFormattedData, (err) => {
if (err) {
return res.status(500).send('Error writing to file.');
}
res.send({ message: 'Task added successfully!' });
});
});
app.use('/', tasksRouter);
app.use('/', authRouter);
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
debug(`Server running at http://localhost:${port}`);
});