Compare commits

4 Commits

8 changed files with 1581 additions and 37 deletions

View File

@@ -13,6 +13,8 @@ services:
- DEBUG=app
- AUTH_USERNAME=fredrik
- AUTH_PASSWORD=apa
- SESSION_SECRET=superheimlich # Add your session secret key
- NODE_ENV=production # Ensure the environment is set to production
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3044"]
interval: 5m

1453
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
"dependencies": {
"basic-auth": "^2.0.1",
"body-parser": "^1.20.3",
"connect-sqlite3": "^0.9.15",
"cookie-parser": "^1.4.7",
"debug": "^4.4.0",
"dotenv": "^16.4.7",

View File

@@ -3,6 +3,24 @@ if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
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 => {
console.log('ServiceWorker registration failed: ', error);
@@ -40,15 +58,32 @@ document.addEventListener('DOMContentLoaded', function() {
firstDay: 1
});
// Initialize timepicker
M.Timepicker.init(document.querySelectorAll('.timepicker'), {
twelveHour: false // Use 24-hour format
});
// Check if user is already logged in
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 => {
if (data.loggedIn) {
loginContainer.style.display = 'none';
appContainer.style.display = 'block';
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) {
@@ -103,14 +138,18 @@ document.addEventListener('DOMContentLoaded', function() {
const subject = document.getElementById('subject').value;
const description = document.getElementById('description').value;
const scheduled = document.getElementById('scheduled').value;
const time = document.getElementById('time').value;
const tagsInput = document.getElementById('tags').value;
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
const taskData = {
subject: tags ? `${subject} :${tags}:` : subject,
description,
scheduled
scheduled: scheduledDateTime
};
// Save tags to server

View File

@@ -55,6 +55,10 @@
<input type="text" id="scheduled" class="datepicker" required>
<label for="scheduled">Planerat datum</label>
</div>
<div class="input-field">
<input type="text" id="time" class="timepicker">
<label for="time">Tid (valfritt)</label>
</div>
<div class="input-field">
<input type="text" id="tags" placeholder="Taggar">
<label for="tags">Taggar</label>

View File

@@ -1,28 +1,51 @@
self.addEventListener('install', (event) => {
const CACHE_NAME = 'org-todo-pwa-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/style.css',
'/app.js',
'/manifest.json'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open('task-manager-cache').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/style.css',
'/app.js',
'/manifest.json',
'/icons/icon-192x192.png',
'/icons/icon-512x512.png'
]);
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) => {
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);
})
);
self.addEventListener('message', event => {
if (event.data === 'skipWaiting') {
self.skipWaiting();
}
});

View File

@@ -23,32 +23,44 @@ 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}>
// Format the scheduled date and time for Org mode
const scheduledDate = new Date(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:
- State "TODO" from "TODO" [${currentDateTime}]
:END:
`;
`;
if (description) {
orgFormattedData = `
* TODO ${subject}
orgFormattedData = `* TODO ${subject}
${description}
SCHEDULED: <${scheduled}>
SCHEDULED: ${formattedScheduled}
: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.');
res.json({ message: 'Task added successfully' });
} catch (error) {
logger.error('Error writing to tasks.org file:', error);
res.status(500).json({ message: 'Error adding task' });
}
});

View File

@@ -3,6 +3,7 @@ const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const SQLiteStore = require('connect-sqlite3')(session);
const debug = require('debug')('app');
const tasksRouter = require('./routes/tasks');
const authRouter = require('./routes/auth');
@@ -15,12 +16,21 @@ app.use(bodyParser.json());
app.use(cookieParser());
app.use(express.static('public'));
// Configure session middleware
// Configure session middleware with SQLite store
app.use(session({
secret: process.env.SESSION_SECRET || 'default_secret', // Use a strong secret in production
resave: 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);