diff --git a/Noted.py b/Noted.py index a227985..2ef2806 100644 --- a/Noted.py +++ b/Noted.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 #-*- coding: utf-8 -*- -from os import urandom -from os.path import dirname, join, abspath +from os import makedirs, urandom +from os.path import abspath, dirname, join from sys import version_info from flask import Flask, render_template, request from waitress.server import create_server -from backend.db import close_db, setup_db, DBConnection +from backend.db import DBConnection, close_db, setup_db from frontend.api import api, reminder_handler from frontend.ui import ui HOST = '0.0.0.0' PORT = '8080' THREADS = 10 -DB_FILENAME = 'Noted.db' +DB_FILENAME = 'db', 'Noted.db' def _folder_path(*folders) -> str: """Turn filepaths relative to the project folder into absolute paths @@ -79,7 +79,9 @@ def Noted() -> None: # Register web server app = _create_app() with app.app_context(): - DBConnection.file = _folder_path(DB_FILENAME) + db_location = _folder_path(*DB_FILENAME) + makedirs(dirname(db_location), exist_ok=True) + DBConnection.file = db_location setup_db() reminder_handler._find_next_reminder() diff --git a/README.md b/README.md index a6055a1..7cc545e 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,9 @@ You can see our planned features in our [Project board](https://github.com/users ## Installation Replace the timezone value (`TZ=`) to the [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) of your timezone! ```bash -docker volume create noted-db docker run -d \ --name noted \ - -v noted-db:/app \ + -v noted-db:/app/db \ -e TZ=Europe/Amsterdam \ -p 8080:8080 \ mrcas/noted:latest diff --git a/backend/db.py b/backend/db.py index d63c297..54a1667 100644 --- a/backend/db.py +++ b/backend/db.py @@ -1,12 +1,14 @@ #-*- coding: utf-8 -*- +from datetime import datetime from sqlite3 import Connection, Row from threading import current_thread +from time import time from typing import Union from flask import g -__DATABASE_VERSION__ = 1 +__DATABASE_VERSION__ = 2 class Singleton(type): _instances = {} @@ -67,7 +69,18 @@ def migrate_db(current_db_version: int) -> None: """ print('Migrating database to newer version...') cursor = get_db() - + if current_db_version == 1: + # V1 -> V2 + t = time() + utc_offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t) + reminders = cursor.execute("SELECT time, id FROM reminders;").fetchall() + new_reminders = [] + new_reminders_append = new_reminders.append + for reminder in reminders: + new_reminders_append([round((datetime.fromtimestamp(reminder[0]) - utc_offset).timestamp()), reminder[1]]) + cursor.executemany("UPDATE reminders SET time = ? WHERE id = ?;", new_reminders) + __DATABASE_VERSION__ = 2 + return def setup_db() -> None: diff --git a/backend/reminders.py b/backend/reminders.py index cd7a111..70c0692 100644 --- a/backend/reminders.py +++ b/backend/reminders.py @@ -29,7 +29,7 @@ def _find_next_time( ) -> int: td = relativedelta(**{repeat_quantity: repeat_interval}) new_time = datetime.fromtimestamp(original_time) - current_time = datetime.fromtimestamp(epoch_time()) + current_time = datetime.fromtimestamp(datetime.utcnow().timestamp()) while new_time <= current_time: new_time += td return int(new_time.timestamp()) @@ -76,7 +76,7 @@ class ReminderHandler(): def _handle(self) -> None: while not self.stop: - if self.next_reminder and self.next_reminder <= epoch_time(): + if self.next_reminder and self.next_reminder <= datetime.utcnow().timestamp(): with self.context(): cursor = get_db(dict) # Get all reminders for the timestamp @@ -182,7 +182,7 @@ class Reminder: Args: title (str): The new title of the entry. Defaults to None. - time (int): The new epoch timestamp the the reminder should be send. Defaults to None. + time (int): The new UTC epoch timestamp the the reminder should be send. Defaults to None. notification_service (int): The new id of the notification service to use to send the reminder. Defaults to None. text (str, optional): The new body of the reminder. Defaults to None. @@ -198,10 +198,11 @@ class Reminder: raise InvalidKeyValue('repeat_interval', repeat_interval) repeated_reminder = repeat_quantity is not None and repeat_interval is not None - if not repeated_reminder: - if time < epoch_time(): - raise InvalidTime - time = round(time) + if time is not None: + if not repeated_reminder: + if time < datetime.utcnow().timestamp(): + raise InvalidTime + time = round(time) # Get current data and update it with new values data = self.get() @@ -355,7 +356,7 @@ class Reminders: Args: title (str): The title of the entry - time (int): The epoch timestamp the the reminder should be send. + time (int): The UTC epoch timestamp the the reminder should be send. notification_service (int): The id of the notification service to use to send the reminder. text (str, optional): The body of the reminder. Defaults to ''. repeat_quantity (Literal["year", "month", "week", "day", "hours", "minutes"], optional): The quantity of the repeat specified for the reminder. Defaults to None. @@ -364,7 +365,7 @@ class Reminders: Returns: dict: The info about the reminder """ - if time < epoch_time(): + if time < datetime.utcnow().timestamp(): raise InvalidTime time = round(time) diff --git a/docker-compose.yml b/docker-compose.yml index 4fdf73c..afbf0cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: noted: container_name: noted volumes: - - 'noted-db:/app' + - 'noted-db:/app/db' environment: - TZ=Europe/Amsterdam ports: diff --git a/frontend/api.py b/frontend/api.py index 89b646f..89bc6fc 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -401,7 +401,7 @@ def api_reminders_list(): Description: Add a reminder Parameters (body (content-type: application/json)): title (required): the title of the reminder - time (required): the epoch timestamp that the reminder should be sent at + time (required): the UTC epoch timestamp that the reminder should be sent at notification_service (required): the id of the notification service to use to send the notification text: the body of the reminder repeat_quantity ('year', 'month', 'week', 'day', 'hours', 'minutes'): The quantity of the repeat_interval @@ -481,7 +481,7 @@ def api_get_reminder(r_id: int): Description: Edit the reminder Parameters (body (content-type: application/json)): title: The new title of the entry. - time: The new epoch timestamp the the reminder should be send. + time: The new UTC epoch timestamp the the reminder should be send. notification_service: The new id of the notification service to use to send the reminder. text: The new body of the reminder. repeat_quantity ('year', 'month', 'week', 'day', 'hours', 'minutes'): The quantity of the repeat_interval diff --git a/frontend/static/js/add.js b/frontend/static/js/add.js index 09b5299..7fb9e14 100644 --- a/frontend/static/js/add.js +++ b/frontend/static/js/add.js @@ -20,7 +20,7 @@ function addReminder() { const data = { 'title': inputs.title.value, - 'time': new Date(inputs.time.value).getTime() / 1000, + 'time': (new Date(inputs.time.value) / 1000) + (new Date(inputs.time.value).getTimezoneOffset() * 60), 'notification_service': inputs.notification_service.value, 'text': inputs.text.value }; diff --git a/frontend/static/js/edit.js b/frontend/static/js/edit.js index b296c40..a93f52f 100644 --- a/frontend/static/js/edit.js +++ b/frontend/static/js/edit.js @@ -18,7 +18,7 @@ function editReminder() { const id = document.getElementById('edit-form').dataset.id; const data = { 'title': edit_inputs.title.value, - 'time': new Date(edit_inputs.time.value).getTime() / 1000, + 'time': (new Date(edit_inputs.time.value) / 1000) + (new Date(edit_inputs.time.value).getTimezoneOffset() * 60), 'notification_service': edit_inputs.notification_service.value, 'text': edit_inputs.text.value, 'repeat_quantity': null, @@ -67,9 +67,10 @@ function showEdit(id) { .then(json => { edit_inputs.title.value = json.result.title; - edit_inputs.time.value = new Date( + var trigger_date = new Date( (json.result.time + new Date(json.result.time * 1000).getTimezoneOffset() * -60) * 1000 - ).toISOString().slice(0, 16); + ); + edit_inputs.time.value = trigger_date.toLocaleString('en-CA').slice(0,10) + 'T' + trigger_date.toTimeString().slice(0,5); edit_inputs.notification_service.value = json.result.notification_service; if (json.result['repeat_interval'] === null) { diff --git a/frontend/static/js/reminders.js b/frontend/static/js/reminders.js index 17c842f..5f05675 100644 --- a/frontend/static/js/reminders.js +++ b/frontend/static/js/reminders.js @@ -13,7 +13,8 @@ function fillTable(result) { entry.appendChild(title); const time = document.createElement('p'); - var d = new Date(reminder.time * 1000); + var offset = new Date(reminder.time * 1000).getTimezoneOffset() * -60; + var d = new Date((reminder.time + offset) * 1000); var formatted_date = d.toLocaleString('en-CA').slice(0,10) + ' ' + d.toTimeString().slice(0,5); if (reminder.repeat_interval !== null) { if (reminder.repeat_interval === 1) { diff --git a/tests/db_test.py b/tests/db_test.py index 3540b3e..06fe2e9 100644 --- a/tests/db_test.py +++ b/tests/db_test.py @@ -1,11 +1,11 @@ import unittest from backend.db import DBConnection -from Noted import DB_FILENAME +from Noted import DB_FILENAME, _folder_path class Test_DB(unittest.TestCase): def test_foreign_key(self): - DBConnection.file = DB_FILENAME + DBConnection.file = _folder_path(*DB_FILENAME) instance = DBConnection(timeout=20.0) self.assertEqual(instance.cursor().execute("PRAGMA foreign_keys;").fetchone()[0], 1)