Compare commits
4 Commits
dfa616fc52
...
e74871bf94
| Author | SHA1 | Date | |
|---|---|---|---|
| e74871bf94 | |||
| a9dfb8d54d | |||
| 502938b8cf | |||
| 5413323a3c |
@@ -13,6 +13,8 @@ services:
|
|||||||
- DEBUG=app
|
- DEBUG=app
|
||||||
- AUTH_USERNAME=fredrik
|
- AUTH_USERNAME=fredrik
|
||||||
- AUTH_PASSWORD=apa
|
- AUTH_PASSWORD=apa
|
||||||
|
- SESSION_SECRET=superheimlich # Add your session secret key
|
||||||
|
- NODE_ENV=production # Ensure the environment is set to production
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:3044"]
|
test: ["CMD", "curl", "-f", "http://localhost:3044"]
|
||||||
interval: 5m
|
interval: 5m
|
||||||
|
|||||||
1453
package-lock.json
generated
1453
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"basic-auth": "^2.0.1",
|
"basic-auth": "^2.0.1",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
|
"connect-sqlite3": "^0.9.15",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
|||||||
@@ -3,6 +3,24 @@ if ('serviceWorker' in navigator) {
|
|||||||
navigator.serviceWorker.register('/service-worker.js')
|
navigator.serviceWorker.register('/service-worker.js')
|
||||||
.then(registration => {
|
.then(registration => {
|
||||||
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||||
|
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// New update available
|
||||||
|
console.log('New content is available; please refresh.');
|
||||||
|
if (confirm('New version available. Do you want to update?')) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Content is cached for offline use
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('ServiceWorker registration failed: ', error);
|
console.log('ServiceWorker registration failed: ', error);
|
||||||
@@ -40,15 +58,32 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
firstDay: 1
|
firstDay: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize timepicker
|
||||||
|
M.Timepicker.init(document.querySelectorAll('.timepicker'), {
|
||||||
|
twelveHour: false // Use 24-hour format
|
||||||
|
});
|
||||||
|
|
||||||
// Check if user is already logged in
|
// Check if user is already logged in
|
||||||
fetch('/check-session')
|
fetch('/check-session')
|
||||||
.then(response => response.json())
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.loggedIn) {
|
if (data.loggedIn) {
|
||||||
loginContainer.style.display = 'none';
|
loginContainer.style.display = 'none';
|
||||||
appContainer.style.display = 'block';
|
appContainer.style.display = 'block';
|
||||||
loadTags();
|
loadTags();
|
||||||
|
} else {
|
||||||
|
loginContainer.style.display = 'block';
|
||||||
|
appContainer.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error checking session:', error);
|
||||||
|
loginMessage.textContent = 'Error checking session. Please try again later.';
|
||||||
});
|
});
|
||||||
|
|
||||||
loginForm.addEventListener('submit', function(e) {
|
loginForm.addEventListener('submit', function(e) {
|
||||||
@@ -103,14 +138,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const subject = document.getElementById('subject').value;
|
const subject = document.getElementById('subject').value;
|
||||||
const description = document.getElementById('description').value;
|
const description = document.getElementById('description').value;
|
||||||
const scheduled = document.getElementById('scheduled').value;
|
const scheduled = document.getElementById('scheduled').value;
|
||||||
|
const time = document.getElementById('time').value;
|
||||||
const tagsInput = document.getElementById('tags').value;
|
const tagsInput = document.getElementById('tags').value;
|
||||||
const tags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag).join(':');
|
const tags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag).join(':');
|
||||||
|
|
||||||
|
// Combine scheduled date and time if time is provided
|
||||||
|
const scheduledDateTime = time ? `${scheduled}T${time}:00` : scheduled;
|
||||||
|
|
||||||
// Structure data for Org mode
|
// Structure data for Org mode
|
||||||
const taskData = {
|
const taskData = {
|
||||||
subject: tags ? `${subject} :${tags}:` : subject,
|
subject: tags ? `${subject} :${tags}:` : subject,
|
||||||
description,
|
description,
|
||||||
scheduled
|
scheduled: scheduledDateTime
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save tags to server
|
// Save tags to server
|
||||||
|
|||||||
@@ -55,6 +55,10 @@
|
|||||||
<input type="text" id="scheduled" class="datepicker" required>
|
<input type="text" id="scheduled" class="datepicker" required>
|
||||||
<label for="scheduled">Planerat datum</label>
|
<label for="scheduled">Planerat datum</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="text" id="time" class="timepicker">
|
||||||
|
<label for="time">Tid (valfritt)</label>
|
||||||
|
</div>
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<input type="text" id="tags" placeholder="Taggar">
|
<input type="text" id="tags" placeholder="Taggar">
|
||||||
<label for="tags">Taggar</label>
|
<label for="tags">Taggar</label>
|
||||||
|
|||||||
@@ -1,28 +1,51 @@
|
|||||||
self.addEventListener('install', (event) => {
|
const CACHE_NAME = 'org-todo-pwa-cache-v1';
|
||||||
event.waitUntil(
|
const urlsToCache = [
|
||||||
caches.open('task-manager-cache').then((cache) => {
|
|
||||||
return cache.addAll([
|
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
'/style.css',
|
'/style.css',
|
||||||
'/app.js',
|
'/app.js',
|
||||||
'/manifest.json',
|
'/manifest.json'
|
||||||
'/icons/icon-192x192.png',
|
];
|
||||||
'/icons/icon-512x512.png'
|
|
||||||
]);
|
self.addEventListener('install', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(cache => {
|
||||||
|
return cache.addAll(urlsToCache);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
self.skipWaiting(); // Force the waiting service worker to become the active service worker
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then(cacheNames => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map(cacheName => {
|
||||||
|
if (cacheName !== CACHE_NAME) {
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
self.clients.claim(); // Take control of all clients immediately
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request)
|
||||||
|
.then(response => {
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
return fetch(event.request);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
self.addEventListener('message', event => {
|
||||||
if (event.request.url.includes('/get-tags') || event.request.url.includes('/save-tags')) {
|
if (event.data === 'skipWaiting') {
|
||||||
// Bypass cache for tags endpoints
|
self.skipWaiting();
|
||||||
event.respondWith(fetch(event.request));
|
|
||||||
} else {
|
|
||||||
event.respondWith(
|
|
||||||
caches.match(event.request).then((cachedResponse) => {
|
|
||||||
return cachedResponse || fetch(event.request);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,19 +23,31 @@ router.post('/add-task', auth, async (req, res) => {
|
|||||||
const { subject, description, scheduled } = req.body;
|
const { subject, description, scheduled } = req.body;
|
||||||
const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
||||||
|
|
||||||
let orgFormattedData = `
|
// Format the scheduled date and time for Org mode
|
||||||
* TODO ${subject}
|
const scheduledDate = new Date(scheduled);
|
||||||
SCHEDULED: <${scheduled}>
|
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
|
const dayName = dayNames[scheduledDate.getUTCDay()];
|
||||||
|
|
||||||
|
let formattedScheduled;
|
||||||
|
if (scheduledDate.getUTCHours() === 0 && scheduledDate.getUTCMinutes() === 0) {
|
||||||
|
// No time provided, format without time
|
||||||
|
formattedScheduled = `<${scheduledDate.getUTCFullYear()}-${String(scheduledDate.getUTCMonth() + 1).padStart(2, '0')}-${String(scheduledDate.getUTCDate()).padStart(2, '0')} ${dayName}>`;
|
||||||
|
} else {
|
||||||
|
// Time provided, format with time
|
||||||
|
formattedScheduled = `<${scheduledDate.getUTCFullYear()}-${String(scheduledDate.getUTCMonth() + 1).padStart(2, '0')}-${String(scheduledDate.getUTCDate()).padStart(2, '0')} ${dayName} ${String(scheduledDate.getUTCHours()).padStart(2, '0')}:${String(scheduledDate.getUTCMinutes()).padStart(2, '0')}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let orgFormattedData = `* TODO ${subject}
|
||||||
|
SCHEDULED: ${formattedScheduled}
|
||||||
:LOGBOOK:
|
:LOGBOOK:
|
||||||
- State "TODO" from "TODO" [${currentDateTime}]
|
- State "TODO" from "TODO" [${currentDateTime}]
|
||||||
:END:
|
:END:
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (description) {
|
if (description) {
|
||||||
orgFormattedData = `
|
orgFormattedData = `* TODO ${subject}
|
||||||
* TODO ${subject}
|
|
||||||
${description}
|
${description}
|
||||||
SCHEDULED: <${scheduled}>
|
SCHEDULED: ${formattedScheduled}
|
||||||
:LOGBOOK:
|
:LOGBOOK:
|
||||||
- State "TODO" from "TODO" [${currentDateTime}]
|
- State "TODO" from "TODO" [${currentDateTime}]
|
||||||
:END:
|
:END:
|
||||||
@@ -45,10 +57,10 @@ router.post('/add-task', auth, async (req, res) => {
|
|||||||
const filePath = path.join(dataDir, 'tasks.org');
|
const filePath = path.join(dataDir, 'tasks.org');
|
||||||
try {
|
try {
|
||||||
await fs.promises.appendFile(filePath, orgFormattedData);
|
await fs.promises.appendFile(filePath, orgFormattedData);
|
||||||
res.send({ message: 'Task added successfully!' });
|
res.json({ message: 'Task added successfully' });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error('Error writing to file:', err);
|
logger.error('Error writing to tasks.org file:', error);
|
||||||
res.status(500).send('Error writing to file.');
|
res.status(500).json({ message: 'Error adding task' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
14
server.js
14
server.js
@@ -3,6 +3,7 @@ const express = require('express');
|
|||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
|
const SQLiteStore = require('connect-sqlite3')(session);
|
||||||
const debug = require('debug')('app');
|
const debug = require('debug')('app');
|
||||||
const tasksRouter = require('./routes/tasks');
|
const tasksRouter = require('./routes/tasks');
|
||||||
const authRouter = require('./routes/auth');
|
const authRouter = require('./routes/auth');
|
||||||
@@ -15,12 +16,21 @@ app.use(bodyParser.json());
|
|||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
|
|
||||||
// Configure session middleware
|
// Configure session middleware with SQLite store
|
||||||
app.use(session({
|
app.use(session({
|
||||||
secret: process.env.SESSION_SECRET || 'default_secret', // Use a strong secret in production
|
secret: process.env.SESSION_SECRET || 'default_secret', // Use a strong secret in production
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 } // 1 day
|
store: new SQLiteStore({
|
||||||
|
db: 'sessions.sqlite',
|
||||||
|
dir: '/data',
|
||||||
|
ttl: 30 * 24 * 60 * 60 // 1 month
|
||||||
|
}),
|
||||||
|
cookie: {
|
||||||
|
//secure: process.env.NODE_ENV === 'production', // Ensure cookies are only sent over HTTPS in production
|
||||||
|
secure: false,
|
||||||
|
maxAge: 30 * 24 * 60 * 60 * 1000 // 1 month
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.use('/', authRouter);
|
app.use('/', authRouter);
|
||||||
|
|||||||
Reference in New Issue
Block a user