9 Commits

17 changed files with 331 additions and 190 deletions

View File

@@ -1,23 +1,24 @@
# Use the official Node.js image as the base image # Use the official Node.js image as the base image
FROM node:14 FROM node:14
# Set the working directory # Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Copy package.json and package-lock.json # Install app dependencies
COPY package*.json ./ COPY package*.json ./
# Install dependencies
RUN npm install RUN npm install
# Copy the rest of the application code # Copy app source code
COPY . . COPY . .
# Run the build script to update the version number
RUN node build.js
# Expose the port the app runs on # Expose the port the app runs on
EXPOSE 3044 EXPOSE 3044
# Set the DEBUG environment variable # Set the DEBUG environment variable
ENV DEBUG=app ENV DEBUG=app
# Command to run the application # Command to run the app
CMD ["node", "server.js"] CMD ["node", "server.js"]

17
build.js Normal file
View File

@@ -0,0 +1,17 @@
const fs = require('fs');
const path = require('path');
// Generate version number with timestamp
const version = new Date().toISOString().replace(/[-:.]/g, '').slice(0, 15);
// Read the HTML file
const indexPath = path.join(__dirname, 'public', 'index.html');
let indexHtml = fs.readFileSync(indexPath, 'utf8');
// Replace the version placeholder with the generated version number
indexHtml = indexHtml.replace(/<!-- VERSION_PLACEHOLDER -->/g, `Version: ${version}`);
// Write the updated HTML back to the file
fs.writeFileSync(indexPath, indexHtml);
console.log(`Version number updated to: ${version}`);

View File

@@ -2,7 +2,7 @@ version: '3.8'
services: services:
org-todo-pwa: org-todo-pwa:
image: org-todo-pwa container_name: org-todo-pwa
build: . build: .
ports: ports:
- "3044:3044" - "3044:3044"
@@ -27,4 +27,3 @@ services:
- "traefik.http.routers.plan.tls.certresolver=myhttpchallenge" - "traefik.http.routers.plan.tls.certresolver=myhttpchallenge"
- "traefik.http.routers.plan.rule=Host(`todo.casablanca.wahlberg.se`)" - "traefik.http.routers.plan.rule=Host(`todo.casablanca.wahlberg.se`)"
- "traefik.http.routers.plan.entrypoints=websecure" - "traefik.http.routers.plan.entrypoints=websecure"

View File

@@ -4,11 +4,11 @@ const logger = createLogger({
level: 'info', level: 'info',
format: format.combine( format: format.combine(
format.timestamp(), format.timestamp(),
format.json() format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
), ),
transports: [ transports: [
new transports.Console(), new transports.Console(),
new transports.File({ filename: 'app.log' }) new transports.File({ filename: '/data/app.log' })
] ]
}); });

View File

@@ -1,10 +1,12 @@
const basicAuth = require('basic-auth'); const basicAuth = require('basic-auth');
const logger = require('../logger');
const auth = (req, res, next) => { const auth = (req, res, next) => {
if (req.session && req.session.user) { if (req.session && req.session.user) {
return next(); return next();
} else { } else {
res.status(401).send('Authentication required.'); res.status(401).send('Authentication required.');
logger.error(`Unauthorized access attempted from IP: ${req.ip}`);
} }
}; };

View File

@@ -1,24 +1,25 @@
{ {
"name": "pwa", "name": "org-todo-pwa",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "app.js", "main": "main.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
"build": "node build.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"basic-auth": "^2.0.1", "basic-auth": "^2.0.1",
"body-parser": "^1.20.3", "body-parser": "^1.19.0",
"connect-sqlite3": "^0.9.15", "connect-sqlite3": "^0.9.11",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.5",
"debug": "^4.4.0", "debug": "^4.3.1",
"dotenv": "^16.4.7", "dotenv": "^8.2.0",
"express": "^4.21.2", "express": "^4.17.1",
"express-session": "^1.18.1", "express-session": "^1.17.1",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"winston": "^3.17.0" "winston": "^3.17.0"
} }
} }

View File

@@ -16,7 +16,17 @@ h1 {
#responseMessage { #responseMessage {
text-align: center; text-align: center;
color: green;} color: green;
}
/* Add styles for the version number */
#version {
color: #888; /* Subtle gray color */
font-size: 0.8em; /* Smaller font size */
text-align: right; /* Align text to the right */
margin: 0; /* Remove any default margin */
padding: 0; /* Remove any default padding */
}
.menu { .menu {
position: relative; position: relative;

View File

@@ -1,16 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="sv-SE"> <html lang="sv">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fredriks todos</title> <title>Fredriks todos</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="css/style.css">
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json">
<!-- Flatpickr for dates -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
</head> </head>
<body> <body>
<div id="loginContainer" class="container"> <div id="loginContainer" class="container">
@@ -60,15 +56,20 @@
<label for="time">Tid (valfritt)</label> <label for="time">Tid (valfritt)</label>
</div> </div>
<div class="input-field"> <div class="input-field">
<input type="text" id="tags" placeholder="Taggar"> <input type="text" id="tags" placeholder="Taggar" autocomplete="off">
<label for="tags">Taggar</label> <label for="tags">Taggar</label>
</div> </div>
<button class="btn waves-effect waves-light" type="submit">Spara uppgift</button> <button class="btn waves-effect waves-light" type="submit">Spara uppgift</button>
<p id="responseMessage"></p> <p id="responseMessage"></p>
<p id="version"><!-- VERSION_PLACEHOLDER --></p>
</form> </form>
</div> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="app.js"></script> <script src="js/auth.js" type="module"></script>
<script src="js/tasks.js" type="module"></script>
<script src="js/tags.js" type="module"></script>
<script src="js/utils.js" type="module"></script>
<script src="js/main.js" type="module"></script>
</body> </body>
</html> </html>

25
public/js/auth.js Normal file
View File

@@ -0,0 +1,25 @@
export function checkSession() {
return fetch('/check-session')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
});
}
export function login(username, password) {
return fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa(username + ':' + password)
}
});
}
export function logout() {
return fetch('/logout', {
method: 'POST'
});
}

View File

@@ -1,3 +1,8 @@
import { checkSession, login, logout } from './auth.js';
import { saveTask } from './tasks.js';
import { saveTags, loadTags } from './tags.js';
import { idb } from './utils.js';
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
window.addEventListener('load', () => { window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js') navigator.serviceWorker.register('/service-worker.js')
@@ -28,7 +33,7 @@ if ('serviceWorker' in navigator) {
}); });
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', async function() {
const loginForm = document.getElementById('loginForm'); const loginForm = document.getElementById('loginForm');
const loginContainer = document.getElementById('loginContainer'); const loginContainer = document.getElementById('loginContainer');
const appContainer = document.getElementById('appContainer'); const appContainer = document.getElementById('appContainer');
@@ -50,12 +55,13 @@ document.addEventListener('DOMContentLoaded', function() {
const tomorrow = new Date(today); const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1); tomorrow.setDate(today.getDate() + 1);
// Initialize datepicker with tomorrow as the default date // Initialize datepicker with tomorrow as the default date and disable past dates
M.Datepicker.init(document.querySelectorAll('.datepicker'), { M.Datepicker.init(document.querySelectorAll('.datepicker'), {
format: 'yyyy-mm-dd', format: 'yyyy-mm-dd',
defaultDate: tomorrow, defaultDate: tomorrow,
setDefaultDate: true, setDefaultDate: true,
firstDay: 1 firstDay: 1,
minDate: today // Disable past dates
}); });
// Initialize timepicker // Initialize timepicker
@@ -64,18 +70,30 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// Check if user is already logged in // Check if user is already logged in
fetch('/check-session') checkSession()
.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().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(', ');
}
}
});
});
} else { } else {
loginContainer.style.display = 'block'; loginContainer.style.display = 'block';
appContainer.style.display = 'none'; appContainer.style.display = 'none';
@@ -92,43 +110,52 @@ document.addEventListener('DOMContentLoaded', function() {
const username = document.getElementById('username').value; const username = document.getElementById('username').value;
const password = document.getElementById('password').value; const password = document.getElementById('password').value;
// Send credentials to the server for validation login(username, password)
fetch('/login', { .then(response => {
method: 'POST', if (response.ok) {
headers: { sessionStorage.setItem('loggedIn', 'true');
'Content-Type': 'application/json', loginContainer.style.display = 'none';
'Authorization': 'Basic ' + btoa(username + ':' + password) appContainer.style.display = 'block';
} loadTags().then(tags => {
}) localStorage.setItem('tags', JSON.stringify(tags));
.then(response => { const autocompleteData = {};
if (response.ok) { tags.forEach(tag => {
sessionStorage.setItem('loggedIn', 'true'); autocompleteData[tag] = null; // Materialize autocomplete requires a key-value pair
loginContainer.style.display = 'none'; });
appContainer.style.display = 'block';
loadTags(); const tagsInput = document.getElementById('tags');
} else { M.Autocomplete.init(tagsInput, {
loginMessage.textContent = 'Invalid username or password'; data: autocompleteData,
} onAutocomplete: function(selectedTag) {
}) const currentTags = tagsInput.value.split(',').map(tag => tag.trim()).filter(tag => tag);
.catch(error => { if (!currentTags.includes(selectedTag)) {
loginMessage.textContent = 'Error logging in'; currentTags.push(selectedTag);
}); tagsInput.value = currentTags.join(', ');
}
}
});
});
} else {
loginMessage.textContent = 'Invalid username or password';
}
})
.catch(error => {
loginMessage.textContent = 'Error logging in';
});
}); });
logoutButton.addEventListener('click', function() { logoutButton.addEventListener('click', function() {
fetch('/logout', { logout()
method: 'POST' .then(response => {
}) if (response.ok) {
.then(response => { sessionStorage.removeItem('loggedIn');
if (response.ok) { loginContainer.style.display = 'block';
sessionStorage.removeItem('loggedIn'); appContainer.style.display = 'none';
loginContainer.style.display = 'block'; }
appContainer.style.display = 'none'; })
} .catch(error => {
}) console.error('Error logging out:', error);
.catch(error => { });
console.error('Error logging out:', error);
});
}); });
taskForm.addEventListener('submit', async function(e) { taskForm.addEventListener('submit', async function(e) {
@@ -153,85 +180,39 @@ document.addEventListener('DOMContentLoaded', function() {
}; };
// Save tags to server // Save tags to server
const savedTags = JSON.parse(localStorage.getItem('tags')) || []; saveTags(tags.split(',').map(tag => tag.trim()).filter(tag => tag))
const newTags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag && !savedTags.includes(tag)); .then(() => {
const allTags = [...savedTags, ...newTags]; loadTags(); // Force refresh tags after saving
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
});
// Save task to server or IndexedDB if offline // Save task to server or IndexedDB if offline
if (navigator.onLine) { try {
try { const data = await saveTask(taskData);
const response = await fetch('/add-task', { document.getElementById('responseMessage').textContent = data.message;
method: 'POST', taskForm.reset(); // Reset the form after saving the task
headers: { } catch (error) {
'Content-Type': 'application/json', if (error.status === 401) {
}, sessionStorage.removeItem('loggedIn');
body: JSON.stringify(taskData) loginContainer.style.display = 'block';
}); appContainer.style.display = 'none';
const data = await response.json(); } else {
document.getElementById('responseMessage').textContent = data.message; document.getElementById('responseMessage').textContent = "Error saving task!";
taskForm.reset(); // Reset the form after saving the task
} catch (error) {
if (error.status === 401) {
sessionStorage.removeItem('loggedIn');
loginContainer.style.display = 'block';
appContainer.style.display = 'none';
} else {
document.getElementById('responseMessage').textContent = "Error saving task!";
}
}
} else {
try {
// Save task to IndexedDB
const db = await idb.openDB('org-todo-pwa', 1, {
upgrade(db) {
db.createObjectStore('tasks', { keyPath: 'id', autoIncrement: true });
}
});
await db.add('tasks', taskData);
document.getElementById('responseMessage').textContent = "Task saved offline!";
taskForm.reset(); // Reset the form after saving the task
} catch (error) {
document.getElementById('responseMessage').textContent = "Error saving task offline!";
console.error('Error saving task offline:', error);
} }
} }
}); });
// Load tags from server and initialize autocomplete // Synchronize tasks when back online
function loadTags() { window.addEventListener('online', async () => {
fetch('/get-tags') const db = await idb.openDB('org-todo-pwa', 1);
.then(response => response.json()) const tasks = await db.getAll('tasks');
.then(tags => { for (const task of tasks) {
localStorage.setItem('tags', JSON.stringify(tags)); try {
const autocompleteData = {}; await saveTask(task);
tags.forEach(tag => { await db.delete('tasks', task.id);
autocompleteData[tag] = null; // Materialize autocomplete requires a key-value pair console.log(`Task synchronized: ${task.subject}`);
}); } catch (error) {
console.error('Error synchronizing task:', error);
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);
});
}
}); });

17
public/js/tags.js Normal file
View File

@@ -0,0 +1,17 @@
export async function saveTags(newTags) {
const existingTags = await loadTags();
const allTags = Array.from(new Set([...existingTags, ...newTags]));
return fetch('/save-tags', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tags: allTags })
});
}
export function loadTags() {
return fetch('/get-tags')
.then(response => response.json());
}

30
public/js/tasks.js Normal file
View File

@@ -0,0 +1,30 @@
import { idb } from './utils.js';
export async function saveTask(taskData) {
if (navigator.onLine) {
try {
const response = await fetch('/add-task', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(taskData)
});
return await response.json();
} catch (error) {
throw error;
}
} else {
try {
const db = await idb.openDB('org-todo-pwa', 1, {
upgrade(db) {
db.createObjectStore('tasks', { keyPath: 'id', autoIncrement: true });
}
});
await db.add('tasks', taskData);
return { message: "Task saved offline!" };
} catch (error) {
throw error;
}
}
}

16
public/js/utils.js Normal file
View File

@@ -0,0 +1,16 @@
export const idb = {
openDB(name, version, { upgrade }) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onupgradeneeded = (event) => {
upgrade(request.result, event.oldVersion, event.newVersion, request.transaction);
};
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
});
}
};

View File

@@ -2,9 +2,14 @@ const CACHE_NAME = 'org-todo-pwa-cache-v1';
const urlsToCache = [ const urlsToCache = [
'/', '/',
'/index.html', '/index.html',
'/style.css', '/css/style.css',
'/app.js', '/js/auth.js',
'/manifest.json' '/js/tasks.js',
'/js/tags.js',
'/js/utils.js',
'/js/main.js',
'https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js'
]; ];
self.addEventListener('install', event => { self.addEventListener('install', event => {
@@ -14,22 +19,21 @@ self.addEventListener('install', event => {
return cache.addAll(urlsToCache); return cache.addAll(urlsToCache);
}) })
); );
self.skipWaiting(); // Force the waiting service worker to become the active service worker
}); });
self.addEventListener('activate', event => { self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil( event.waitUntil(
caches.keys().then(cacheNames => { caches.keys().then(cacheNames => {
return Promise.all( return Promise.all(
cacheNames.map(cacheName => { cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) { if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName); return caches.delete(cacheName);
} }
}) })
); );
}) })
); );
self.clients.claim(); // Take control of all clients immediately
}); });
self.addEventListener('fetch', event => { self.addEventListener('fetch', event => {
@@ -39,13 +43,19 @@ self.addEventListener('fetch', event => {
if (response) { if (response) {
return response; return response;
} }
return fetch(event.request); return fetch(event.request).then(
response => {
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}
);
}) })
); );
}); });
self.addEventListener('message', event => {
if (event.data === 'skipWaiting') {
self.skipWaiting();
}
});

View File

@@ -1,5 +1,6 @@
const express = require('express'); const express = require('express');
const basicAuth = require('basic-auth'); const basicAuth = require('basic-auth');
const logger = require('../logger');
const router = express.Router(); const router = express.Router();
router.post('/login', (req, res) => { router.post('/login', (req, res) => {
@@ -10,6 +11,7 @@ router.post('/login', (req, res) => {
if (user && user.name === username && user.pass === password) { if (user && user.name === username && user.pass === password) {
req.session.user = user.name; req.session.user = user.name;
res.status(200).send('Login successful'); res.status(200).send('Login successful');
logger.info(`User ${user.name} logged in`);
} else { } else {
res.status(401).send('Authentication required'); res.status(401).send('Authentication required');
} }

View File

@@ -3,10 +3,17 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const auth = require('../middleware/auth'); const auth = require('../middleware/auth');
const logger = require('../logger'); const logger = require('../logger');
const sqlite3 = require('sqlite3').verbose();
const router = express.Router(); const router = express.Router();
const dataDir = '/data'; const dataDir = '/data';
const db = new sqlite3.Database('/data/sessions.sqlite', (err) => {
if (err) {
console.error('Error opening database:', err);
}
});
// Ensure the /data directory exists // Ensure the /data directory exists
if (!fs.existsSync(dataDir)) { if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true }); fs.mkdirSync(dataDir, { recursive: true });
@@ -58,6 +65,7 @@ router.post('/add-task', auth, async (req, res) => {
try { try {
await fs.promises.appendFile(filePath, orgFormattedData); await fs.promises.appendFile(filePath, orgFormattedData);
res.json({ message: 'Task added successfully' }); res.json({ message: 'Task added successfully' });
logger.info(`Task added: ${orgFormattedData}`);
} catch (error) { } catch (error) {
logger.error('Error writing to tasks.org file:', error); logger.error('Error writing to tasks.org file:', error);
res.status(500).json({ message: 'Error adding task' }); res.status(500).json({ message: 'Error adding task' });
@@ -67,27 +75,31 @@ router.post('/add-task', auth, async (req, res) => {
// Endpoint to save tags // Endpoint to save tags
router.post('/save-tags', auth, async (req, res) => { router.post('/save-tags', auth, async (req, res) => {
const { tags } = req.body; const { tags } = req.body;
const filePath = path.join(dataDir, 'tags.json'); const placeholders = tags.map(() => '(?)').join(',');
try { const sql = `INSERT OR IGNORE INTO tags (tag) VALUES ${placeholders}`;
await fs.promises.writeFile(filePath, JSON.stringify(tags));
res.send({ message: 'Tags saved successfully!' }); db.run(sql, tags, function(err) {
} catch (err) { if (err) {
logger.error('Error saving tags:', err); logger.error('Error saving tags:', err);
res.status(500).send('Error saving tags.'); res.status(500).send('Error saving tags.');
} } else {
res.send({ message: 'Tags saved successfully!' });
logger.info(`New tags saved: ${tags}`);
}
});
}); });
// Endpoint to retrieve tags // Endpoint to retrieve tags
router.get('/get-tags', auth, async (req, res) => { router.get('/get-tags', auth, async (req, res) => {
const filePath = path.join(dataDir, 'tags.json'); db.all('SELECT tag FROM tags', [], (err, rows) => {
try { if (err) {
const data = await fs.promises.readFile(filePath, 'utf-8'); logger.error('Error retrieving tags:', err);
const tags = JSON.parse(data); res.status(500).json({ error: 'Error retrieving tags' });
res.json(tags); } else {
} catch (err) { const tags = rows.map(row => row.tag);
logger.error('Error retrieving tags:', err); res.json(tags);
res.status(500).json({ error: 'Error retrieving tags' }); }
} });
}); });
module.exports = router; module.exports = router;

View File

@@ -4,14 +4,30 @@ 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 SQLiteStore = require('connect-sqlite3')(session);
const debug = require('debug')('app'); const sqlite3 = require('sqlite3').verbose();
const tasksRouter = require('./routes/tasks'); const tasksRouter = require('./routes/tasks');
const authRouter = require('./routes/auth'); const authRouter = require('./routes/auth');
const authMiddleware = require('./middleware/auth'); const authMiddleware = require('./middleware/auth');
const logger = require('./logger');
const app = express(); const app = express();
const port = 3044; const port = 3044;
const db = new sqlite3.Database('/data/sessions.sqlite', (err) => {
if (err) {
console.error('Error opening database:', err);
} else {
db.run(`CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tag TEXT UNIQUE
)`, (err) => {
if (err) {
console.error('Error creating tags table:', err);
}
});
}
});
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(cookieParser()); app.use(cookieParser());
app.use(express.static('public')); app.use(express.static('public'));
@@ -27,8 +43,7 @@ app.use(session({
ttl: 30 * 24 * 60 * 60 // 1 month ttl: 30 * 24 * 60 * 60 // 1 month
}), }),
cookie: { cookie: {
//secure: process.env.NODE_ENV === 'production', // Ensure cookies are only sent over HTTPS in production 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 maxAge: 30 * 24 * 60 * 60 * 1000 // 1 month
} }
})); }));
@@ -36,6 +51,8 @@ app.use(session({
app.use('/', authRouter); app.use('/', authRouter);
app.use('/', authMiddleware, tasksRouter); app.use('/', authMiddleware, tasksRouter);
app.listen(port, () => { app.listen(port, () => {
debug(`Server running at http://localhost:${port}`); logger.info(`Server running at http://localhost:${port}`);
}); });