Compare commits
4 Commits
dfa616fc52
...
e74871bf94
| Author | SHA1 | Date | |
|---|---|---|---|
| e74871bf94 | |||
| a9dfb8d54d | |||
| 502938b8cf | |||
| 5413323a3c |
@@ -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
1453
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
14
server.js
14
server.js
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user