From cf69427e87b4acff6185563d4efc3e58d63788fd Mon Sep 17 00:00:00 2001 From: CasVT Date: Thu, 8 Jun 2023 14:34:02 +0200 Subject: [PATCH 01/46] Added search to static reminders and templates --- backend/reminders.py | 1 - backend/static_reminders.py | 22 ++++++- backend/templates.py | 20 ++++++ frontend/api.py | 46 ++++++++++++++ frontend/static/css/library.css | 6 +- frontend/static/js/general.js | 6 +- frontend/static/js/library.js | 31 +++++++-- frontend/templates/reminders.html | 102 ++++++++++++++---------------- 8 files changed, 167 insertions(+), 67 deletions(-) diff --git a/backend/reminders.py b/backend/reminders.py index 7724f17..9827289 100644 --- a/backend/reminders.py +++ b/backend/reminders.py @@ -17,7 +17,6 @@ from backend.db import close_db, get_db filter_function = lambda query, p: ( query in p["title"].lower() or query in p["text"].lower() - or query in p["notification_service_title"].lower() ) def _find_next_time( diff --git a/backend/static_reminders.py b/backend/static_reminders.py index 8801882..a3a0ba1 100644 --- a/backend/static_reminders.py +++ b/backend/static_reminders.py @@ -9,6 +9,10 @@ from backend.custom_exceptions import (NotificationServiceNotFound, ReminderNotFound) from backend.db import get_db +filter_function = lambda query, p: ( + query in p["title"].lower() + or query in p["text"].lower() +) class StaticReminder: """Represents a static reminder @@ -147,7 +151,23 @@ class StaticReminders: )) return reminders - + + def search(self, query: str) -> List[dict]: + """Search for static reminders + + Args: + query (str): The term to search for + + Returns: + List[dict]: All static reminders that match. Similar output to self.fetchall + """ + query = query.lower() + reminders = list(filter( + lambda p: filter_function(query, p), + self.fetchall() + )) + return reminders + def fetchone(self, id: int) -> StaticReminder: """Get one static reminder diff --git a/backend/templates.py b/backend/templates.py index 9343c8d..bc5f65f 100644 --- a/backend/templates.py +++ b/backend/templates.py @@ -7,6 +7,10 @@ from backend.custom_exceptions import (NotificationServiceNotFound, TemplateNotFound) from backend.db import get_db +filter_function = lambda query, p: ( + query in p["title"].lower() + or query in p["text"].lower() +) class Template: """Represents a template @@ -136,6 +140,22 @@ class Templates: return templates + def search(self, query: str) -> List[dict]: + """Search for templates + + Args: + query (str): The term to search for + + Returns: + List[dict]: All templates that match. Similar output to self.fetchall + """ + query = query.lower() + reminders = list(filter( + lambda p: filter_function(query, p), + self.fetchall() + )) + return reminders + def fetchone(self, id: int) -> Template: """Get one template diff --git a/frontend/api.py b/frontend/api.py index 48bc0ea..ae19fcf 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -632,6 +632,29 @@ def api_get_templates(): color=color) return return_api(result.get(), code=201) +@api.route('/templates/search', methods=['GET']) +@error_handler +@auth +def api_templates_query(): + """ + Endpoint: /templates/search + Description: Search through the list of templates + Requires being logged in: Yes + Methods: + GET: + Parameters (url): + query (required): The search term + Returns: + 200: + The search results, listed like GET /templates + 400: + KeyNotFound: One of the required parameters was not given + """ + query = extract_key(request.values, 'query') + + result = g.user_data.templates.search(query) + return return_api(result) + @api.route('/templates/', methods=['GET', 'PUT', 'DELETE']) @error_handler @auth @@ -746,6 +769,29 @@ def api_static_reminders_list(): color=color) return return_api(result.get(), code=201) +@api.route('/staticreminders/search', methods=['GET']) +@error_handler +@auth +def api_static_reminders_query(): + """ + Endpoint: /staticreminders/search + Description: Search through the list of staticreminders + Requires being logged in: Yes + Methods: + GET: + Parameters (url): + query (required): The search term + Returns: + 200: + The search results, listed like GET /staticreminders + 400: + KeyNotFound: One of the required parameters was not given + """ + query = extract_key(request.values, 'query') + + result = g.user_data.static_reminders.search(query) + return return_api(result) + @api.route('/staticreminders/', methods=['GET', 'POST', 'PUT', 'DELETE']) @error_handler @auth diff --git a/frontend/static/css/library.css b/frontend/static/css/library.css index 80562e9..2b2e6ba 100644 --- a/frontend/static/css/library.css +++ b/frontend/static/css/library.css @@ -46,9 +46,9 @@ } /* REMINDER LIST */ -#reminder-list, -#static-reminder-list, -#template-list { +#reminder-tab, +#static-reminder-tab, +#template-tab { --gap: 1rem; --entry-width: 13rem; max-width: 43rem; diff --git a/frontend/static/js/general.js b/frontend/static/js/general.js index 32ffd2e..85b8069 100644 --- a/frontend/static/js/general.js +++ b/frontend/static/js/general.js @@ -1,7 +1,7 @@ const types = { - 'reminder': document.getElementById('reminder-list'), - 'static_reminder': document.getElementById('static-reminder-list'), - 'template': document.getElementById('template-list') + 'reminder': document.getElementById('reminder-tab'), + 'static_reminder': document.getElementById('static-reminder-tab'), + 'template': document.getElementById('template-tab') }; const icons = { diff --git a/frontend/static/js/library.js b/frontend/static/js/library.js index 3f9d990..47ada58 100644 --- a/frontend/static/js/library.js +++ b/frontend/static/js/library.js @@ -5,7 +5,7 @@ function showTab(button) { ); // Show desired tab and hide all others - document.querySelectorAll('#home > div:not(.tab-selector)').forEach( + document.querySelectorAll('#home > div:not(.tab-selector):not(.search-container)').forEach( e => e.classList.add('hidden') ); document.getElementById(button.dataset.target).classList.remove('hidden'); @@ -87,13 +87,25 @@ function fillTemplates() { // Library search // function searchLibrary() { - const query = document.querySelector('#search-input').value; - fetch(`${url_prefix}/api/reminders/search?api_key=${api_key}&query=${query}`) + const query = document.querySelector('#search-input').value, + tab = document.getElementById( + document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target + ) + let url; + if (tab === types.reminder) + url = `${url_prefix}/api/reminders/search?api_key=${api_key}&query=${query}`; + else if (tab === types.static_reminder) + url = `${url_prefix}/api/staticreminders/search?api_key=${api_key}&query=${query}`; + else if (tab === types.template) + url = `${url_prefix}/api/templates/search?api_key=${api_key}&query=${query}`; + else return; + + fetch(url) .then(response => { if (!response.ok) return Promise.reject(response.status); return response.json(); }) - .then(json => fillTable(types.reminder, json.result)) + .then(json => fillTable(tab, json.result)) .catch(e => { if (e === 401) window.location.href = `${url_prefix}/`; @@ -104,7 +116,16 @@ function searchLibrary() { function clearSearchLibrary() { document.querySelector('#search-input').value = ''; - fillReminders(); + const tab = document.getElementById( + document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target + ) + if (tab === types.reminder) + fillReminders(); + else if (tab === types.static_reminder) + fillStaticReminders(); + else if (tab === types.template) + fillTemplates(); + else return; }; // code run on load diff --git a/frontend/templates/reminders.html b/frontend/templates/reminders.html index 2050993..57e26f2 100644 --- a/frontend/templates/reminders.html +++ b/frontend/templates/reminders.html @@ -81,71 +81,65 @@ -
-
- -
- -
-
-
- + + +
+ +
+
+ -
+ + +

Add a notification service first!

+ From 97bde5dc074ed668888d25427e9c71599fb6ebd6 Mon Sep 17 00:00:00 2001 From: CasVT Date: Fri, 9 Jun 2023 21:46:24 +0200 Subject: [PATCH 02/46] Added sorting of library --- backend/reminders.py | 21 ++-- backend/static_reminders.py | 30 ++++- backend/templates.py | 27 ++++- frontend/api.py | 34 ++++-- frontend/static/css/general.css | 4 - frontend/static/css/info.css | 1 + frontend/static/css/library.css | 6 + frontend/static/js/library.js | 127 +++++++++++++++++---- frontend/static/js/notification.js | 176 +++++++++++++++++++++++++---- frontend/static/js/window.js | 15 ++- frontend/templates/reminders.html | 1 + 11 files changed, 362 insertions(+), 80 deletions(-) diff --git a/backend/reminders.py b/backend/reminders.py index 9827289..96bc8f5 100644 --- a/backend/reminders.py +++ b/backend/reminders.py @@ -310,24 +310,26 @@ class Reminders: """Represents the reminder library of the user account """ sort_functions = { - 'title': (lambda r: (r['title'], r['time']), False), - 'title_reversed': (lambda r: (r['title'], r['time']), True), - 'time': (lambda r: r['time'], False), - 'time_reversed': (lambda r: r['time'], True) + 'time': (lambda r: (r['time'], r['title'], r['text'], r['color']), False), + 'time_reversed': (lambda r: (r['time'], r['title'], r['text'], r['color']), True), + 'title': (lambda r: (r['title'], r['time'], r['text'], r['color']), False), + 'title_reversed': (lambda r: (r['title'], r['time'], r['text'], r['color']), True), + 'date_added': (lambda r: r['id'], False), + 'date_added_reversed': (lambda r: r['id'], True) } def __init__(self, user_id: int): self.user_id = user_id - def fetchall(self, sort_by: Literal["time", "time_reversed", "title", "title_reversed"] = "time") -> List[dict]: + def fetchall(self, sort_by: Literal["time", "time_reversed", "title", "title_reversed", "date_added", "date_added_reversed"] = "time") -> List[dict]: """Get all reminders Args: - sort_by (Literal["time", "time_reversed", "title", "title_reversed"], optional): How to sort the result. Defaults to "time". + sort_by (Literal["time", "time_reversed", "title", "title_reversed", "date_added", "date_added_reversed"], optional): How to sort the result. Defaults to "time". Returns: List[dict]: The id, title, text, time and color of each reminder - """ + """ sort_function = self.sort_functions.get( sort_by, self.sort_functions['time'] @@ -353,11 +355,12 @@ class Reminders: return reminders - def search(self, query: str) -> List[dict]: + def search(self, query: str, sort_by: Literal["time", "time_reversed", "title", "title_reversed", "date_added", "date_added_reversed"] = "time") -> List[dict]: """Search for reminders Args: query (str): The term to search for + sort_by (Literal["time", "time_reversed", "title", "title_reversed", "date_added", "date_added_reversed"], optional): How to sort the result. Defaults to "time". Returns: List[dict]: All reminders that match. Similar output to self.fetchall @@ -365,7 +368,7 @@ class Reminders: query = query.lower() reminders = list(filter( lambda p: filter_function(query, p), - self.fetchall() + self.fetchall(sort_by) )) return reminders diff --git a/backend/static_reminders.py b/backend/static_reminders.py index a3a0ba1..d3a684b 100644 --- a/backend/static_reminders.py +++ b/backend/static_reminders.py @@ -1,7 +1,7 @@ #-*- coding: utf-8 -*- from sqlite3 import IntegrityError -from typing import List +from typing import List, Literal from apprise import Apprise @@ -125,16 +125,30 @@ class StaticReminder: class StaticReminders: """Represents the static reminder library of the user account """ - + sort_functions = { + 'title': (lambda r: (r['title'], r['text'], r['color']), False), + 'title_reversed': (lambda r: (r['title'], r['text'], r['color']), True), + 'date_added': (lambda r: r['id'], False), + 'date_added_reversed': (lambda r: r['id'], True) + } + def __init__(self, user_id: int) -> None: self.user_id = user_id - def fetchall(self) -> List[dict]: + def fetchall(self, sort_by: Literal["title", "title_reversed", "date_added", "date_added_reversed"] = "title") -> List[dict]: """Get all static reminders + Args: + sort_by (Literal["title", "title_reversed", "date_added", "date_added_reversed"], optional): How to sort the result. Defaults to "title". + Returns: List[dict]: The id, title, text and color of each static reminder - """ + """ + sort_function = self.sort_functions.get( + sort_by, + self.sort_functions['title'] + ) + reminders: list = list(map( dict, get_db(dict).execute(""" @@ -150,13 +164,17 @@ class StaticReminders: ) )) + # Sort result + reminders.sort(key=sort_function[0], reverse=sort_function[1]) + return reminders - def search(self, query: str) -> List[dict]: + def search(self, query: str, sort_by: Literal["title", "title_reversed", "date_added", "date_added_reversed"] = "title") -> List[dict]: """Search for static reminders Args: query (str): The term to search for + sort_by (Literal["title", "title_reversed", "date_added", "date_added_reversed"], optional): How to sort the result. Defaults to "title". Returns: List[dict]: All static reminders that match. Similar output to self.fetchall @@ -164,7 +182,7 @@ class StaticReminders: query = query.lower() reminders = list(filter( lambda p: filter_function(query, p), - self.fetchall() + self.fetchall(sort_by) )) return reminders diff --git a/backend/templates.py b/backend/templates.py index bc5f65f..7824701 100644 --- a/backend/templates.py +++ b/backend/templates.py @@ -1,7 +1,7 @@ #-*- coding: utf-8 -*- from sqlite3 import IntegrityError -from typing import List +from typing import List, Literal from backend.custom_exceptions import (NotificationServiceNotFound, TemplateNotFound) @@ -117,15 +117,30 @@ class Template: class Templates: """Represents the template library of the user account """ + sort_functions = { + 'title': (lambda r: (r['title'], r['text'], r['color']), False), + 'title_reversed': (lambda r: (r['title'], r['text'], r['color']), True), + 'date_added': (lambda r: r['id'], False), + 'date_added_reversed': (lambda r: r['id'], True) + } + def __init__(self, user_id: int): self.user_id = user_id - def fetchall(self) -> List[dict]: + def fetchall(self, sort_by: Literal["title", "title_reversed", "date_added", "date_added_reversed"] = "title") -> List[dict]: """Get all templates + Args: + sort_by (Literal["title", "title_reversed", "date_added", "date_added_reversed"], optional): How to sort the result. Defaults to "title". + Returns: List[dict]: The id, title, text and color """ + sort_function = self.sort_functions.get( + sort_by, + self.sort_functions['title'] + ) + templates: list = list(map(dict, get_db(dict).execute(""" SELECT id, @@ -138,13 +153,17 @@ class Templates: (self.user_id,) ))) + # Sort result + templates.sort(key=sort_function[0], reverse=sort_function[1]) + return templates - def search(self, query: str) -> List[dict]: + def search(self, query: str, sort_by: Literal["title", "title_reversed", "date_added", "date_added_reversed"] = "title") -> List[dict]: """Search for templates Args: query (str): The term to search for + sort_by (Literal["title", "title_reversed", "date_added", "date_added_reversed"], optional): How to sort the result. Defaults to "title". Returns: List[dict]: All templates that match. Similar output to self.fetchall @@ -152,7 +171,7 @@ class Templates: query = query.lower() reminders = list(filter( lambda p: filter_function(query, p), - self.fetchall() + self.fetchall(sort_by) )) return reminders diff --git a/frontend/api.py b/frontend/api.py index ae19fcf..b49e2a7 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -74,7 +74,7 @@ def error_handler(method): wrapper.__name__ = method.__name__ return wrapper -def extract_key(values: dict, key: str, check_existence: bool=True) -> Any: +def extract_key(values: dict, key: str, check_existence: bool=True, sort_options: dict=None) -> Any: value: str = values.get(key) if check_existence and value is None: raise KeyNotFound(key) @@ -96,7 +96,7 @@ def extract_key(values: dict, key: str, check_existence: bool=True) -> Any: raise InvalidKeyValue(key, value) elif key == 'sort_by': - if not value in Reminders.sort_functions: + if not value in sort_options: raise InvalidKeyValue(key, value) elif key == 'repeat_quantity': @@ -123,7 +123,7 @@ def extract_key(values: dict, key: str, check_existence: bool=True) -> Any: else: if key == 'sort_by': - value = 'time' + value = next(iter(sort_options)) elif key == 'text': value = '' @@ -409,7 +409,7 @@ def api_reminders_list(): GET: Description: Get a list of all reminders Parameters (url): - sort_by: how to sort the result. Allowed values are 'title', 'title_reversed', 'time' and 'time_reversed' + sort_by: How to sort the result. Allowed values are 'title', 'title_reversed', 'time', 'time_reversed', 'date_added' and 'date_added_reversed' Returns: 200: The id, title, text, time, repeat_quantity, repeat_interval and color of each reminder @@ -434,8 +434,8 @@ def api_reminders_list(): reminders: Reminders = g.user_data.reminders if request.method == 'GET': - sort_by = extract_key(request.values, 'sort_by', check_existence=False) - result = reminders.fetchall(sort_by=sort_by) + sort_by = extract_key(request.values, 'sort_by', check_existence=False, sort_options=Reminders.sort_functions) + result = reminders.fetchall(sort_by) return return_api(result) elif request.method == 'POST': @@ -469,6 +469,7 @@ def api_reminders_query(): GET: Parameters (url): query (required): The search term + sort_by: How to sort the result. Allowed values are 'title', 'title_reversed', 'time', 'time_reversed', 'date_added' and 'date_added_reversed' Returns: 200: The search results, listed like GET /reminders @@ -476,8 +477,9 @@ def api_reminders_query(): KeyNotFound: One of the required parameters was not given """ query = extract_key(request.values, 'query') + sort_by = extract_key(request.values, 'sort_by', check_existence=False, sort_options=Reminders.sort_functions) - result = g.user_data.reminders.search(query) + result = g.user_data.reminders.search(query, sort_by) return return_api(result) @api.route('/reminders/test', methods=['POST']) @@ -595,6 +597,8 @@ def api_get_templates(): Methods: GET: Description: Get a list of all templates + Parameters (url): + sort_by: How to sort the result. Allowed values are 'title', 'title_reversed', 'date_added' and 'date_added_reversed' Returns: 200: The id, title, text and color of every template @@ -616,7 +620,8 @@ def api_get_templates(): templates: Templates = g.user_data.templates if request.method == 'GET': - result = templates.fetchall() + sort_by = extract_key(request.values, 'sort_by', check_existence=False, sort_options=Templates.sort_functions) + result = templates.fetchall(sort_by) return return_api(result) elif request.method == 'POST': @@ -644,6 +649,7 @@ def api_templates_query(): GET: Parameters (url): query (required): The search term + sort_by: How to sort the result. Allowed values are 'title', 'title_reversed', 'date_added' and 'date_added_reversed' Returns: 200: The search results, listed like GET /templates @@ -651,8 +657,9 @@ def api_templates_query(): KeyNotFound: One of the required parameters was not given """ query = extract_key(request.values, 'query') + sort_by = extract_key(request.values, 'sort_by', check_existence=False, sort_options=Templates.sort_functions) - result = g.user_data.templates.search(query) + result = g.user_data.templates.search(query, sort_by) return return_api(result) @api.route('/templates/', methods=['GET', 'PUT', 'DELETE']) @@ -732,6 +739,8 @@ def api_static_reminders_list(): Methods: GET: Description: Get a list of all static reminders + Parameters (url): + sort_by: How to sort the result. Allowed values are 'title', 'title_reversed', 'date_added' and 'date_added_reversed' Returns: 200: The id, title, text and color of each static reminder @@ -753,7 +762,8 @@ def api_static_reminders_list(): reminders: StaticReminders = g.user_data.static_reminders if request.method == 'GET': - result = reminders.fetchall() + sort_by = extract_key(request.values, 'sort_by', check_existence=False, sort_options=StaticReminders.sort_functions) + result = reminders.fetchall(sort_by) return return_api(result) elif request.method == 'POST': @@ -781,6 +791,7 @@ def api_static_reminders_query(): GET: Parameters (url): query (required): The search term + sort_by: How to sort the result. Allowed values are 'title', 'title_reversed', 'date_added' and 'date_added_reversed' Returns: 200: The search results, listed like GET /staticreminders @@ -788,8 +799,9 @@ def api_static_reminders_query(): KeyNotFound: One of the required parameters was not given """ query = extract_key(request.values, 'query') + sort_by = extract_key(request.values, 'sort_by', check_existence=False, sort_options=StaticReminders.sort_functions) - result = g.user_data.static_reminders.search(query) + result = g.user_data.static_reminders.search(query, sort_by) return return_api(result) @api.route('/staticreminders/', methods=['GET', 'POST', 'PUT', 'DELETE']) diff --git a/frontend/static/css/general.css b/frontend/static/css/general.css index 2b72338..90f08dd 100644 --- a/frontend/static/css/general.css +++ b/frontend/static/css/general.css @@ -275,10 +275,6 @@ nav > div > button svg { text-align: center; } -.window-container > div:not(#home) > p > a { - color: var(--color-light); -} - .tab-selector { width: 100%; display: flex; diff --git a/frontend/static/css/info.css b/frontend/static/css/info.css index 8d121c3..aaa4517 100644 --- a/frontend/static/css/info.css +++ b/frontend/static/css/info.css @@ -107,6 +107,7 @@ .notification-service-list > div > input { width: fit-content; + box-shadow: none; } .repeat-bar, diff --git a/frontend/static/css/library.css b/frontend/static/css/library.css index 2b2e6ba..bdf57cb 100644 --- a/frontend/static/css/library.css +++ b/frontend/static/css/library.css @@ -45,6 +45,12 @@ opacity: 1; } +#sort-input { + width: min-content; + border: 0; + box-shadow: none; +} + /* REMINDER LIST */ #reminder-tab, #static-reminder-tab, diff --git a/frontend/static/js/library.js b/frontend/static/js/library.js index 47ada58..54a3890 100644 --- a/frontend/static/js/library.js +++ b/frontend/static/js/library.js @@ -1,3 +1,25 @@ +const sorting_options = {}; +sorting_options[types.reminder] = [ + ['time', 'Time'], + ['time_reversed', 'Time Reversed'], + ['title', 'Title'], + ['title_reversed', 'Title Reversed'], + ['date_added', 'Date Added'], + ['date_added_reversed', 'Date Added Reversed'] +]; +sorting_options[types.static_reminder] = [ + ['title', 'Title'], + ['title_reversed', 'Title Reversed'], + ['date_added', 'Date Added'], + ['date_added_reversed', 'Date Added Reversed'] +]; +sorting_options[types.template] = [ + ['title', 'Title'], + ['title_reversed', 'Title Reversed'], + ['date_added', 'Date Added'], + ['date_added_reversed', 'Date Added Reversed'] +]; + function showTab(button) { // Apply styling to selected button document.querySelectorAll('.tab-selector > button').forEach( @@ -9,6 +31,9 @@ function showTab(button) { e => e.classList.add('hidden') ); document.getElementById(button.dataset.target).classList.remove('hidden'); + + fillSortOptions(); + document.querySelector('#search-input').value = ''; }; // @@ -72,15 +97,26 @@ function fillLibrary(url, type) { }; function fillReminders() { - fillLibrary(`/api/reminders?api_key=${api_key}`, types.reminder); + const sorting = document.querySelector('#sort-input').value; + fillLibrary(`/api/reminders?api_key=${api_key}&sort_by=${sorting}`, types.reminder); }; -function fillStaticReminders() { - fillLibrary(`/api/staticreminders?api_key=${api_key}`, types.static_reminder); +function fillStaticReminders(assume_sorting=false) { + let sorting; + if (assume_sorting) + sorting = sorting_options[types.static_reminder][0][0]; + else + sorting = document.querySelector('#sort-input').value; + fillLibrary(`/api/staticreminders?api_key=${api_key}&sort_by=${sorting}`, types.static_reminder); } -function fillTemplates() { - fillLibrary(`/api/templates?api_key=${api_key}`, types.template); +function fillTemplates(assume_sorting=false) { + let sorting; + if (assume_sorting) + sorting = sorting_options[types.template][0][0]; + else + sorting = document.querySelector('#sort-input').value; + fillLibrary(`/api/templates?api_key=${api_key}&sort_by=${sorting}`, types.template); }; // @@ -91,27 +127,17 @@ function searchLibrary() { tab = document.getElementById( document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target ) + const sorting = document.querySelector('#sort-input').value; let url; if (tab === types.reminder) - url = `${url_prefix}/api/reminders/search?api_key=${api_key}&query=${query}`; + url = `${url_prefix}/api/reminders/search?api_key=${api_key}&query=${query}&sort_by=${sorting}`; else if (tab === types.static_reminder) - url = `${url_prefix}/api/staticreminders/search?api_key=${api_key}&query=${query}`; + url = `${url_prefix}/api/staticreminders/search?api_key=${api_key}&query=${query}&sort_by=${sorting}`; else if (tab === types.template) - url = `${url_prefix}/api/templates/search?api_key=${api_key}&query=${query}`; + url = `${url_prefix}/api/templates/search?api_key=${api_key}&query=${query}&sort_by=${sorting}`; else return; - fetch(url) - .then(response => { - if (!response.ok) return Promise.reject(response.status); - return response.json(); - }) - .then(json => fillTable(tab, json.result)) - .catch(e => { - if (e === 401) - window.location.href = `${url_prefix}/`; - else - console.log(e); - }); + fillLibrary(url, tab); }; function clearSearchLibrary() { @@ -128,16 +154,73 @@ function clearSearchLibrary() { else return; }; +// +// Library sort +// +function fillSortOptions() { + const tab = document.getElementById( + document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target + ), + sort_options = sorting_options[tab]; + + const select = document.getElementById('sort-input'); + select.innerHTML = ''; + sort_options.forEach(o => { + const entry = document.createElement('option'); + entry.value = o[0] + entry.innerText = o[1] + select.appendChild(entry); + }); + select.querySelector(':first-child').setAttribute('selected', ''); +}; + +function applySorting() { + const query = document.querySelector('#search-input').value; + if (query !== '') { + searchLibrary(); + return; + }; + + const sorting = document.getElementById('sort-input').value, + tab = document.getElementById( + document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target + ) + + let url; + if (tab === types.reminder) + url = `${url_prefix}/api/reminders?api_key=${api_key}&sort_by=${sorting}`; + else if (tab === types.static_reminder) + url = `${url_prefix}/api/staticreminders?api_key=${api_key}&sort_by=${sorting}`; + else if (tab === types.template) + url = `${url_prefix}/api/templates?api_key=${api_key}&sort_by=${sorting}`; + else return; + + fetch(url) + .then(response => { + if (!response.ok) return Promise.reject(response.status); + return response.json(); + }) + .then(json => fillTable(tab, json.result)) + .catch(e => { + if (e === 401) + window.location.href = `${url_prefix}/`; + else + console.log(e); + }); +}; + // code run on load document.querySelectorAll('.tab-selector > button').forEach(b => { b.addEventListener('click', e => showTab(b)); }); +fillSortOptions(); fillReminders(); -fillStaticReminders(); -fillTemplates(); +fillStaticReminders(assume_sorting=true); +fillTemplates(assume_sorting=true); setInterval(fillReminders, 60000); document.querySelector('#search-form').setAttribute('action', 'javascript:searchLibrary();'); document.querySelector('#clear-button').addEventListener('click', e => clearSearchLibrary()); +document.querySelector('#sort-input').addEventListener('change', e => applySorting()); diff --git a/frontend/static/js/notification.js b/frontend/static/js/notification.js index 37679bd..1c5af6d 100644 --- a/frontend/static/js/notification.js +++ b/frontend/static/js/notification.js @@ -1,9 +1,3 @@ -const inputs_buttons = { - 'save_button': document.querySelector('#add-row button[data-type="save"]'), - 'title': document.querySelector('#add-row td.title-column input'), - 'url': document.querySelector('#add-row td.url-column input') -}; - function fillNotificationSelection() { fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`) .then(response => { @@ -32,7 +26,7 @@ function fillNotificationSelection() { inputs.notification_service.querySelector(':first-child input').checked = true; const table = document.getElementById('services-list'); - table.querySelectorAll('tr:not(#add-row)').forEach(e => e.remove()); + table.innerHTML = ''; json.result.forEach(service => { const entry = document.createElement('tr'); entry.dataset.id = service.id; @@ -157,13 +151,157 @@ function deleteService(id) { }; function toggleAddService() { - document.getElementById('add-row').classList.toggle('hidden'); + const cont = document.querySelector('.overflow-container'); + if (cont.classList.contains('show-add')) { + // Hide add + cont.classList.remove('show-add'); + hideAddServiceWindow(); + } else { + // Show add + if (notification_services === null) { + fetch(`${url_prefix}/api/notificationservices/available?api_key=${api_key}`) + .then(response => response.json()) + .then(json => { + notification_services = json.result; + const table = document.querySelector('#service-list'); + json.result.forEach((result, index) => { + const entry = document.createElement('button'); + entry.innerText = result.name; + entry.addEventListener('click', e => showAddServiceWindow(index)); + table.appendChild(entry); + }); + }); + }; + cont.classList.add('show-add'); + }; +}; + +function showAddServiceWindow(index) { + const window = document.getElementById('add-service-window'); + window.innerHTML = ''; + + if (index === -1) { + // Custom url + const title = document.createElement('h3'); + title.innerText = 'Custom URL'; + window.appendChild(title); + + const desc = document.createElement('p'); + desc.innerHTML = 'Enter a custom Apprise URL. See the Apprise URL documentation.'; + window.appendChild(desc); + + const service_title = document.createElement('input'); + service_title.id = 'service-title'; + service_title.type = 'text'; + service_title.placeholder = 'Service Title'; + service_title.required = true; + window.appendChild(service_title); + + const url_input = document.createElement('input'); + url_input.type = 'text'; + url_input.placeholder = 'Apprise URL'; + window.appendChild(url_input); + } else { + const data = notification_services[index]; + console.log(data); + + const title = document.createElement('h3'); + title.innerText = data.name; + window.appendChild(title); + + const docs = document.createElement('a'); + docs.href = data.doc_url; + docs.target = '_blank'; + docs.innerText = 'Documentation'; + window.appendChild(docs); + + const service_title = document.createElement('input'); + service_title.id = 'service-title'; + service_title.type = 'text'; + service_title.placeholder = 'Service Title'; + service_title.required = true; + window.appendChild(service_title); + + data.details.tokens.forEach(token => { + if (token.type === 'choice') { + const choice = document.createElement('select'); + choice.dataset.map = token.map_to; + choice.dataset.prefix = ''; + choice.placeholder = token.name; + choice.required = token.required; + token.options.forEach(option => { + const entry = document.createElement('option'); + entry.value = option; + entry.innerText = option; + choice.appendChild(entry); + }); + window.appendChild(choice); + + } else if (token.type === 'list') { + if (token.content.length === 0) { + + } else { + token.content.forEach(content => { + + }); + }; + + } else if (token.type === 'string') { + const str_input = document.createElement('input'); + str_input.dataset.map = token.map_to; + str_input.dataset.prefix = token.prefix; + str_input.dataset.regex = token.regex; + str_input.type = 'text'; + str_input.placeholder = `${token.name}${!token.required ? ' (Optional)' : ''}`; + str_input.required = token.required; + window.appendChild(str_input); + + } else if (token.type === 'int') { + const int_input = document.createElement('input'); + int_input.dataset.map = token.map_to; + int_input.dataset.prefix = token.prefix; + int_input.type = 'number'; + int_input.placeholder = `${token.name}${!token.required ? ' (Optional)' : ''}`; + int_input.required = token.required; + if (token.min !== null) + int_input.min = token.min; + if (token.max !== null) + int_input.max = token.max; + window.appendChild(int_input); + }; + }); + }; + + // Bottom options + const options = document.createElement('div'); + options.classList.add('options'); + const cancel = document.createElement('button'); + cancel.type = 'button'; + cancel.innerText = 'Cancel'; + cancel.addEventListener('click', e => toggleAddService()); + options.appendChild(cancel); + const add = document.createElement('button'); + add.type = 'submit'; + add.innerText = 'Add'; + options.appendChild(add); + window.appendChild(options); + + document.getElementById('add-service-container').classList.add('show-add-window'); +}; + +function hideAddServiceWindow() { + document.getElementById('add-service-container').classList.remove('show-add-window'); +}; + +function buildAppriseURL() { + return null; }; function addService() { + const add_button = document.querySelector('#add-service-window > .options > button[type="submit"]'); const data = { - 'title': inputs_buttons.title.value, - 'url': inputs_buttons.url.value + 'title': document.querySelector('#service-title').value, + 'url': buildAppriseURL() }; fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`, { 'method': 'POST', @@ -173,13 +311,9 @@ function addService() { .then(response => { if (!response.ok) return Promise.reject(response.status); - inputs_buttons.title.value = ''; - inputs_buttons.url.value = ''; + add_button.classList.remove('error-input'); + add_button.title = ''; - inputs_buttons.save_button.classList.remove('error-icon'); - inputs_buttons.save_button.title = 'Add'; - inputs_buttons.save_button.setAttribute('aria-label', 'Add'); - toggleAddService(); fillNotificationSelection(); }) @@ -187,9 +321,8 @@ function addService() { if (e === 401) window.location.href = `${url_prefix}/`; else if (e === 400) { - inputs_buttons.save_button.classList.add('error-icon'); - inputs_buttons.save_button.title = 'Invalid Apprise URL'; - inputs_buttons.save_button.setAttribute('aria-label', 'Invalid Apprise URL'); + add_button.classList.add('error-input'); + add_button.title = 'Invalid Apprise URL'; } else console.log(e); }); @@ -199,5 +332,8 @@ function addService() { fillNotificationSelection(); +let notification_services = null; + document.getElementById('add-service-button').addEventListener('click', e => toggleAddService()); -document.querySelector('#add-row button[data-type="save"]').addEventListener('click', e => addService()); +document.querySelector('#service-list button').addEventListener('click', e => showAddServiceWindow(-1)); +document.getElementById('add-service-window').setAttribute('action', 'javascript:addService();'); diff --git a/frontend/static/js/window.js b/frontend/static/js/window.js index 13d4c61..09522d0 100644 --- a/frontend/static/js/window.js +++ b/frontend/static/js/window.js @@ -143,10 +143,17 @@ function deleteInfo() { .then(response => { if (!response.ok) return Promise.reject(response.status); - fillNotificationSelection(); - fillReminders(); - fillStaticReminders(); - fillTemplates(); + if (cl.contains('show-edit-reminder')) { + // Delete reminder + fillReminders(); + } else if (cl.contains('show-edit-template')) { + // Delete template + fillTemplates(); + loadTemplateSelection(); + } else if (cl.contains('show-edit-static-reminder')) { + // Delete static reminder + fillStaticReminders(); + }; hideWindow(); }) .catch(e => { diff --git a/frontend/templates/reminders.html b/frontend/templates/reminders.html index 57e26f2..964c89a 100644 --- a/frontend/templates/reminders.html +++ b/frontend/templates/reminders.html @@ -104,6 +104,7 @@ + From 47e5854f42ebe7ea49240be1b8398a16c3dd00f3 Mon Sep 17 00:00:00 2001 From: CasVT Date: Wed, 21 Jun 2023 00:19:49 +0200 Subject: [PATCH 03/46] Add documentation page --- .dockerignore | 6 + .github/workflows/deploy.yml | 20 +++ .gitignore | 3 + README.md | 52 ++++--- docs-requirements.txt | 6 + docs/api.md | 3 + docs/index.md | 34 +++++ docs/installation.md | 3 + docs/setup_after_installation.md | 3 + docs/stylesheets/extra.css | 226 +++++++++++++++++++++++++++++++ mkdocs.yml | 82 +++++++++++ 11 files changed, 409 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 docs-requirements.txt create mode 100644 docs/api.md create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/setup_after_installation.md create mode 100644 docs/stylesheets/extra.css create mode 100644 mkdocs.yml diff --git a/.dockerignore b/.dockerignore index ea273f0..fd03066 100644 --- a/.dockerignore +++ b/.dockerignore @@ -151,3 +151,9 @@ LICENSE # Tests tests/ + +# Project management files +release.sh +docs/ +docs-requirements.txt +mkdocs.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..29d8472 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,20 @@ +name: Build and deploy docs + +on: + push: + branches: + - Development + +jobs: + deploy: + name: Deploy docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install -r docs-requirements.txt + - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index 585383e..53bf3f3 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,6 @@ dmypy.json # VS code *.code-workspace + +# Project management files +release.sh diff --git a/README.md b/README.md index 9e2d708..fc59d77 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,28 @@ -# MIND Reminders -A simple self hosted reminder platform that uses push to send notifications to your device. Set the reminder and forget about it! 📢 +# MIND + +[![Docker Pulls](https://img.shields.io/docker/pulls/mrcas/mind.svg)](https://hub.docker.com/r/mrcas/mind) +[![GitHub Downloads](https://img.shields.io/github/downloads/Casvt/MIND/total.svg)](https://github.com/Casvt/MIND/releases) + +__A simple self hosted reminder application that can send push notifications to your device. Set the reminder and forget about it!__ Mind is a simple self hosted application for creating reminders that get pushed to your device using the [Apprise](https://github.com/caronc/apprise) API. You can send messages to just about every platform, including scheduled emails! -## Screenshots -![mind-reminders-home](https://user-images.githubusercontent.com/57927413/213593220-495aeb86-2bf8-4c43-895d-c7cba38c3cee.png) +## Workings +MIND can be used for sending notifications at the desired time. This can be a set time, like a yearly reminder for a birthday, or at a button click, to easily send a predefined notification when you want to. -![mind-reminders-add-notification-services](https://user-images.githubusercontent.com/57927413/212755314-1104531e-7feb-4e59-af1d-927576e47152.png) +## Features +- Works cross-timezone +- Notifications are sent with second-precision +- Uses the [apprise library](https://github.com/caronc/apprise), giving you 80+ platforms to send notifications to +- Easily manage the reminders with sorting options, search ability and color coding +- Docker image available +- Mobile friendly web-interface +- API available -![mind-reminders-edit](https://user-images.githubusercontent.com/57927413/213594471-ecc99a72-cf0f-4570-8e78-92ffbf37e59d.png) +### Planned Features +You can see the planned features in the [Project board](https://github.com/users/Casvt/projects/3). -![mind-reminders-settings](https://user-images.githubusercontent.com/57927413/212755327-b45da53c-72f7-480c-9a77-eaad28803fbb.png) - -## Core Features -* Basic auth -* Utilizes Apprise -* Create, edit and delete reminders -* Schedule reminders -* Recurring reminders -* Docker image -* Mobile friendly - -## Planned Features -You can see our planned features in our [Project board](https://github.com/users/Casvt/projects/3). - -## Installation +## Getting started 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 run -d \ @@ -34,13 +32,9 @@ docker run -d \ -p 8080:8080 \ mrcas/mind:latest ``` -## Getting Started -- Create a new account -- Click the bell icon on the left side to add an Apprise push option and save it (Here is an example using Pushover) -![mind-reminders-notification-service](https://user-images.githubusercontent.com/57927413/213593832-6c62307c-cf7c-4d11-b6ce-dea33676d477.png) +More information about installing can be found [in the wiki](https://casvt.github.io/MIND/). - -- Click the home icon and create a reminder! - -You can see the [wiki](https://github.com/Casvt/MIND/wiki) for instructions on how to install MIND on other OS'es. +## Contact +- For support, a [discord server](https://discord.gg/nMNdgG7vsE) is available +- Alternatively, [make an issue](https://github.com/Casvt/MIND/issues) diff --git a/docs-requirements.txt b/docs-requirements.txt new file mode 100644 index 0000000..37c7ed3 --- /dev/null +++ b/docs-requirements.txt @@ -0,0 +1,6 @@ +wheel>=0.38.4 +mkdocs-material>=8.5.11 +mkdocs-redirects>=1.2.0 +mkdocs-git-revision-date-localized-plugin>=1.1.0 +Pygments>=2.13.0 +pymdown-extensions>=9.9 diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..9f31430 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,3 @@ +# API + +Coming soon \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..530e376 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,34 @@ +# MIND Docs + +[![Docker Pulls](https://img.shields.io/docker/pulls/mrcas/mind.svg)](https://hub.docker.com/r/mrcas/mind) +[![GitHub Downloads](https://img.shields.io/github/downloads/Casvt/MIND/total.svg)](https://github.com/Casvt/MIND/releases) + +__A simple self hosted reminder application that can send push notifications to your device. Set the reminder and forget about it!__ + +Getting started: + +- [Installation](./installation.md) +- [Setup After Installation](./setup_after_installation.md) + +General Information: + +- [API Docs](./api.md) + +## Workings +MIND can be used for sending notifications at the desired time. This can be a set time, like a yearly reminder for a birthday, or at a button click, to easily send a predefined notification when you want to. + +## Features +- Works cross-timezone +- Notifications are sent with second-precision +- Uses the [apprise library](https://github.com/caronc/apprise), giving you 80+ platforms to send notifications to +- Easily manage the reminders with sorting options, search ability and color coding +- Docker image available +- Mobile friendly web-interface +- API available + +### Planned Features +You can see the planned features in the [Project board](https://github.com/users/Casvt/projects/3). + +## Contact +- For support, a [discord server](https://discord.gg/nMNdgG7vsE) is available +- Alternatively, [make an issue](https://github.com/Casvt/MIND/issues) diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..782af53 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,3 @@ +# Installation + +Coming soon \ No newline at end of file diff --git a/docs/setup_after_installation.md b/docs/setup_after_installation.md new file mode 100644 index 0000000..0d3eced --- /dev/null +++ b/docs/setup_after_installation.md @@ -0,0 +1,3 @@ +# Setup After Installation + +Coming Soon \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..16ba9be --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,226 @@ +:root { + --color-light: #ffffff; + --color-dim-light: #b5b5b5; + --color-gray: #3c3c3c; + --color-dark: #1b1b1b; +} + +.md-typeset code { + border-radius: 6px; +} + +.md-clipboard { + color: var(--color-gray); +} + +:hover > .md-clipboard { + color: var(--color-dark); +} + +.md-clipboard:hover { + color: var(--color-light); +} + +/* */ +/* Light mode */ +/* */ +[data-md-color-scheme="MIND"] { + /* Background color of header */ + --md-primary-fg-color: var(--color-gray); + /* Header and code background color and clipboard color */ + --md-default-fg-color--light: var(--color-dim-light); + --md-primary-fg-color--lighter: #00000052; + --md-default-fg-color--lightest: var(--color-dim-light); + /* Color in header */ + --md-primary-bg-color: var(--color-light); + /* Color in placeholder of search and non-hover for nav */ + --md-primary-bg-color--light: var(--color-dim-light); + --md-primary-bg-color--lighter: #ffffff4d; + --md-primary-bg-color--lightest: #ffffff1f; + /* Background color */ + --md-default-bg-color: var(--color-light); + + /* Color in search */ + --md-default-fg-color: var(--color-dark); + + /* Hover color for mark and ToC */ + --md-accent-fg-color: var(--color-gray); + + /* Code color */ + --md-code-fg-color: var(--color-light); + /* Code background color */ + --md-code-bg-color: var(--color-gray); + --md-code-hl-color: #ffff0080; + --md-code-hl-number-color: #d52a2a; + --md-code-hl-special-color: #db1457; + --md-code-hl-function-color: #a846b9; + --md-code-hl-constant-color: #6e59d9; + --md-code-hl-keyword-color: #3f6ec6; + --md-code-hl-string-color: #1c7d4d; + + /* Slashes in code */ + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--color-dim-light); + --md-code-hl-punctuation-color: var(--color-dim-light); + --md-code-hl-comment-color: var(--color-dim-light); + --md-code-hl-generic-color: var(--color-dim-light); + --md-code-hl-variable-color: var(--color-dim-light); + + /* Text color */ + --md-typeset-color: var(--color-dark); + /* Link color */ + --md-typeset-a-color: var(--color-dim-light); + --md-typeset-mark-color: #ffff0080; + --md-typeset-del-color: #f5503d26; + --md-typeset-ins-color: #0bd57026; + --md-typeset-kbd-color: #fafafa; + --md-typeset-kbd-accent-color: #fff; + --md-typeset-kbd-border-color: #b8b8b8; + --md-typeset-table-color: #0000001f; + --md-typeset-table-color--light: rgba(0,0,0,.035); + + --md-admonition-fg-color: var(--color-dark); + --md-admonition-bg-color: var(--color-light); + + --md-warning-fg-color: #000000de; + --md-warning-bg-color: #ff9; + + /* Footer color */ + --md-footer-fg-color: var(--color-light); + /* Footer background color */ + --md-footer-bg-color: var(--color-gray); + + --md-shadow-z1: 0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a; + --md-shadow-z2: 0 0.2rem 0.5rem #0000001a,0 0 0.05rem #00000040; + --md-shadow-z3: 0 0.2rem 0.5rem #0003,0 0 0.05rem #00000059; +} + +[data-md-color-scheme="MIND"] button[data-md-component="top"], +[data-md-color-scheme="MIND"] h1, +[data-md-color-scheme="MIND"] [data-md-component="search"] { + --md-default-fg-color--light: var(--color-dark); +} + +[data-md-color-scheme="MIND"] [data-md-component="toc"] { + color: var(--color-dark); +} + +[data-md-color-scheme="MIND"] [data-md-component="toc"] nav { + color: var(--color-gray); +} + +[data-md-color-scheme="MIND"] .tabbed-block { + --md-default-fg-color--light: var(--color-dark); +} + +[data-md-color-scheme="MIND"] details { + border-color: var(--color-gray); +} + +[data-md-color-scheme="MIND"] details > summary { + background-color: transparent; +} + +[data-md-color-scheme="MIND"] details > summary::before { + background-color: var(--color-dark); +} + +/* */ +/* Dark mode */ +/* */ +[data-md-color-scheme="MIND-dark"] { + /* Background color of header */ + --md-primary-fg-color: var(--color-gray); + /* Header and code background color and clipboard color */ + --md-default-fg-color--light: var(--color-gray); + --md-primary-fg-color--lighter: #00000052; + --md-default-fg-color--lightest: var(--color-gray); + /* Color in header */ + --md-primary-bg-color: var(--color-light); + /* Color in placeholder of search and non-hover for nav */ + --md-primary-bg-color--light: var(--color-dim-light); + --md-primary-bg-color--lighter: #ffffff4d; + --md-primary-bg-color--lightest: #ffffff1f; + /* Background color */ + --md-default-bg-color: var(--color-dark); + + /* Color in search */ + --md-default-fg-color: var(--color-light); + + /* Hover color for mark and ToC */ + --md-accent-fg-color: var(--color-dim-light); + + /* Code color */ + --md-code-fg-color: var(--color-light); + /* Code background color */ + --md-code-bg-color: var(--color-gray); + --md-code-hl-color: #ffff0080; + --md-code-hl-number-color: #d52a2a; + --md-code-hl-special-color: #db1457; + --md-code-hl-function-color: #a846b9; + --md-code-hl-constant-color: #6e59d9; + --md-code-hl-keyword-color: #3f6ec6; + --md-code-hl-string-color: #1c7d4d; + + /* Slashes in code */ + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--color-dim-light); + --md-code-hl-punctuation-color: var(--color-dim-light); + --md-code-hl-comment-color: var(--color-dim-light); + --md-code-hl-generic-color: var(--color-dim-light); + --md-code-hl-variable-color: var(--color-dim-light); + + /* Text color */ + --md-typeset-color: var(--color-light); + /* Link color */ + --md-typeset-a-color: var(--color-dim-light); + --md-typeset-mark-color: #ffff0080; + --md-typeset-del-color: #f5503d26; + --md-typeset-ins-color: #0bd57026; + --md-typeset-kbd-color: #fafafa; + --md-typeset-kbd-accent-color: #fff; + --md-typeset-kbd-border-color: #b8b8b8; + --md-typeset-table-color: #0000001f; + --md-typeset-table-color--light: rgba(0,0,0,.035); + + --md-admonition-fg-color: var(--color-light); + --md-admonition-bg-color: var(--color-dark); + + --md-warning-fg-color: #000000de; + --md-warning-bg-color: #ff9; + + /* Footer color */ + --md-footer-fg-color: var(--color-light); + /* Footer background color */ + --md-footer-bg-color: var(--color-gray); + + --md-shadow-z1: 0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a; + --md-shadow-z2: 0 0.2rem 0.5rem #0000001a,0 0 0.05rem #00000040; + --md-shadow-z3: 0 0.2rem 0.5rem #0003,0 0 0.05rem #00000059; +} + +[data-md-color-scheme="MIND-dark"] button[data-md-component="top"], +[data-md-color-scheme="MIND-dark"] h1, +[data-md-color-scheme="MIND-dark"] [data-md-component="search"] { + --md-default-fg-color--light: var(--color-light); +} + +[data-md-color-scheme="MIND-dark"] [data-md-component="toc"] nav { + color: var(--color-dim-light); +} + +[data-md-color-scheme="MIND-dark"] .tabbed-block { + --md-default-fg-color--light: var(--color-dark); +} + +[data-md-color-scheme="MIND-dark"] details { + border-color: var(--color-gray); +} + +[data-md-color-scheme="MIND-dark"] details > summary { + background-color: transparent; +} + +[data-md-color-scheme="MIND-dark"] details > summary::before { + background-color: var(--color-dim-light); +} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..a0fe6d8 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,82 @@ +# Site Details +site_name: MIND Docs +site_description: The official guide for MIND +site_author: MrCas +#edit_uri: https://github.com/?? +repo_url: https://github.com/Casvt/MIND +repo_name: Casvt/MIND + +extra_css: + - stylesheets/extra.css + +# Site appearance (logos, colours, icons) +theme: + name: material + language: en + code_wrap: true + features: + - content.tabs.link + - content.code.copy + - header.autohide + - navigation.expand + - navigation.indexes + - navigation.instant + - navigation.sections + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - navigation.tracking + #favicon: assets/logo/favicon.ico + #logo: assets/logo/kapowarr-logo.png? + palette: + # Light mode + - media: "(prefers-color-scheme: light)" + scheme: MIND + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + # Dark mode + - media: "(prefers-color-scheme: dark)" + scheme: MIND-dark + toggle: + icon: material/toggle-switch + name: Switch to light mode + +# Markdown extensions +markdown_extensions: + - abbr + - attr_list + - meta + - pymdownx.details + - pymdownx.highlight: + guess_lang: true + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.saneheaders + - pymdownx.smartsymbols + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - sane_lists + - toc: + permalink: true + toc_depth: 5 + +# mkdocs function extensions +plugins: + - search + - git-revision-date-localized: + type: timeago + locale: en + fallback_to_build_date: false + +# Navigation Layout +nav: + - Home: index.md + - Getting Started: + - Installation: installation.md + - Setup After Installation: setup_after_installation.md + - Other Docs: + - API: api.md From ee893fbe4da8655db7b9d2bbeeb82c058ae21f4e Mon Sep 17 00:00:00 2001 From: CasVT Date: Wed, 21 Jun 2023 01:12:00 +0200 Subject: [PATCH 04/46] Added installation and post-installation docs --- docs/index.md | 4 ++ docs/installation.md | 82 +++++++++++++++++++++++++++++++- docs/setup_after_installation.md | 23 ++++++++- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 530e376..edcc1dc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,9 +15,11 @@ General Information: - [API Docs](./api.md) ## Workings + MIND can be used for sending notifications at the desired time. This can be a set time, like a yearly reminder for a birthday, or at a button click, to easily send a predefined notification when you want to. ## Features + - Works cross-timezone - Notifications are sent with second-precision - Uses the [apprise library](https://github.com/caronc/apprise), giving you 80+ platforms to send notifications to @@ -27,8 +29,10 @@ MIND can be used for sending notifications at the desired time. This can be a se - API available ### Planned Features + You can see the planned features in the [Project board](https://github.com/users/Casvt/projects/3). ## Contact + - For support, a [discord server](https://discord.gg/nMNdgG7vsE) is available - Alternatively, [make an issue](https://github.com/Casvt/MIND/issues) diff --git a/docs/installation.md b/docs/installation.md index 782af53..734b9bb 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,3 +1,83 @@ # Installation -Coming soon \ No newline at end of file +The recommended way to install MIND is using Docker. After installing MIND, it is advised to read the [Setup After Installation page](setup_after_installation.md). + +NOTE: Make sure to set all time related settings (time, date, timezone, etc.) correct on your computer, as MIND depends on it to work correctly. + +## Docker + +=== "Docker CLI" + The command to get the docker container running can be found below. 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 run -d \ + --name mind \ + -v mind-db:/app/db \ + -e TZ=Europe/Amsterdam \ + -p 8080:8080 \ + mrcas/mind:latest + ``` +=== "Docker Compose" + The contents of the `docker-compose.yml` file would look like below. Replace the timezone value (`TZ=`) to the [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) of your timezone. + ```yml + version: '3.3' + services: + mind: + container_name: mind + volumes: + - 'mind-db:/app/db' + environment: + - TZ=Europe/Amsterdam + ports: + - '8080:8080' + image: 'mrcas/mind:latest' + ``` + +Information on how to change the port can be found on the [Setup After Installation page](setup_after_installation.md#port). + +Using a named volume in docker requires you to create the volume before you can use it (see [Named Volumes](#named-volumes)). + +### Named Volumes + +=== "Docker CLI" + ```bash + docker volume create mind-db + ``` + +=== "Portainer" + - Open `Volumes` + - Click `Add Volume` + - Enter name matching the one you'll use in compose (`mind-db`, in the above provided command) + - Click `Create the volume` + - Open `Stacks` + - Create the stack with the named volume in it. + +Both of these options will create a named volume that you can then use in the command above. +If you'd prefer to use a local folder on the host machine for storing config, Linux standards would suggest putting that in `/opt/application_name`, as the `/opt` directory is where program options should be stored. +In this case, you'd create the desired folder with something like `mkdir /opt/MIND/db`, and replace 'mind-db:/app/db' with '/opt/MIND/db:/app/db'. + +## Manual Install + +See below for installation instructions for your OS if you want to install it manually. + +=== "Linux / MacOS" + ```bash + sudo apt-get install git python3-pip + sudo git clone https://github.com/Casvt/MIND.git /opt/MIND + cd /opt/MIND + python3 -m pip install -r requirements.txt + python3 MIND.py + ``` +=== "Windows" + 1. Install python [in the Microsoft Store](https://www.microsoft.com/store/productId/9PJPW5LDXLZ5) + 2. Install pip [using these instructions](https://www.liquidweb.com/kb/install-pip-windows/) + 3. Download [the latest release](https://github.com/Casvt/MIND/zipball/master) + 4. Extract the ZIP file + 5. With the folder open, right click and select `Open in Terminal` + 6. Type the following command: + ```bash + python3 -m pip install -r requirements.txt + ``` + 7. Type the following command: + ```bash + python3 MIND.py + ``` diff --git a/docs/setup_after_installation.md b/docs/setup_after_installation.md index 0d3eced..6a5323e 100644 --- a/docs/setup_after_installation.md +++ b/docs/setup_after_installation.md @@ -1,3 +1,24 @@ # Setup After Installation -Coming Soon \ No newline at end of file +After installing MIND, you should have access to the web-ui. MIND needs some configuration in order for it to work properly. + +## Port + +The first thing to do is decide if you want to leave MIND running on the default port of `8080`. If you _do_, you can go to the next step. If you want to _change_ the port, continue reading. + +=== "Docker CLI" + Alter the command to run the container and replace `-p 8080:8080` with `-p {PORT}:8080`, where `{PORT}` is the desired port (e.g. `-p 8009:8080`). Then run the container with the new version of the command. + +=== "Docker Compose" + Alter the file to run the container and replace `- 8080:8080` with `- {PORT}:8080`, where `{PORT}` is the desired port (e.g. `- 8009:8080`). Then run the container with the new version of the file. + +=== "Manual Install" + Inside the `MIND.py` file at the top, you can set the port via the `PORT` variable. Change it from `PORT = '8080'` to `PORT = '{PORT}'`, where `{PORT}` is the desired port (e.g. `PORT = '8009'`). Then restart the application. + +## Creating an account + +When accessing the web-ui, you'll be prompted to log in. Click on `Or create an account`, enter the desired username and password for the account and click `Create`. The account is created and can now be logged in with. The complete authentication process is local and no data is shared with any other service. + +## Add a notification service + +A notification service is a way of sending a notification. For example an e-mail to a group of people or a PushBullet notification to a specific device. What the actual content of the notification is, is decided by the title and text of the reminder. The notification service only specifies in which way the title and text is sent. You set it up once, and then you can select it when creating a reminder. A notification service consists of a title (name) and an Apprise URL. See the [Apprise URL documentation](https://github.com/caronc/apprise#supported-notifications) to learn how to make a valid Apprise URL. From ad72e334d41df2be1a7a510888fc60bd8df779cc Mon Sep 17 00:00:00 2001 From: CasVT Date: Wed, 21 Jun 2023 01:24:21 +0200 Subject: [PATCH 05/46] Added links in settings --- frontend/static/css/general.css | 4 ++++ frontend/static/css/settings.css | 26 ++++++++++++++++++++++++++ frontend/templates/reminders.html | 7 +++++++ 3 files changed, 37 insertions(+) diff --git a/frontend/static/css/general.css b/frontend/static/css/general.css index 90f08dd..2aaa5ef 100644 --- a/frontend/static/css/general.css +++ b/frontend/static/css/general.css @@ -271,6 +271,10 @@ nav > div > button svg { margin-bottom: 2rem; } +.window-container > div:not(#home) > h2:not(:first-of-type) { + margin-top: 1.5rem; +} + .window-container > div:not(#home) > p { text-align: center; } diff --git a/frontend/static/css/settings.css b/frontend/static/css/settings.css index a348eb1..de69400 100644 --- a/frontend/static/css/settings.css +++ b/frontend/static/css/settings.css @@ -40,3 +40,29 @@ #delete-account-button { background-color: var(--color-error); } + +.contact-list { + width: min(100%, 31rem); + margin: auto; + + display: flex; + justify-content: center; + gap: 1rem; + flex-wrap: wrap; +} + +.contact-list > a { + height: 5rem; + width: 15rem; + + display: flex; + justify-content: center; + align-items: center; + + border-radius: 6px; + background-color: var(--color-gray); + color: var(--color-light); + + text-decoration: none; + font-size: 1.2rem; +} \ No newline at end of file diff --git a/frontend/templates/reminders.html b/frontend/templates/reminders.html index 964c89a..75f614b 100644 --- a/frontend/templates/reminders.html +++ b/frontend/templates/reminders.html @@ -270,6 +270,13 @@

Delete Account

+

Contact and Donation

+ From e9369df81b0230e6aaae956f89356da412797e12 Mon Sep 17 00:00:00 2001 From: CasVT Date: Thu, 22 Jun 2023 14:01:11 +0200 Subject: [PATCH 06/46] Added logo to UI and docs --- docs/{stylesheets => assets/css}/extra.css | 0 docs/assets/img/favicon.svg | 30 ++++++++++++++++++++++ frontend/static/css/general.css | 5 ++-- frontend/static/img/favicon.svg | 30 ++++++++++++++++++++++ frontend/templates/login.html | 4 ++- frontend/templates/page_not_found.html | 4 ++- frontend/templates/reminders.html | 4 ++- mkdocs.yml | 10 ++++---- 8 files changed, 77 insertions(+), 10 deletions(-) rename docs/{stylesheets => assets/css}/extra.css (100%) create mode 100644 docs/assets/img/favicon.svg create mode 100644 frontend/static/img/favicon.svg diff --git a/docs/stylesheets/extra.css b/docs/assets/css/extra.css similarity index 100% rename from docs/stylesheets/extra.css rename to docs/assets/css/extra.css diff --git a/docs/assets/img/favicon.svg b/docs/assets/img/favicon.svg new file mode 100644 index 0000000..878a5d5 --- /dev/null +++ b/docs/assets/img/favicon.svg @@ -0,0 +1,30 @@ + + + + White + + + + Off-white + + + + Gray + + + + Dark gray + + + + + + + + + + + M​ + + + \ No newline at end of file diff --git a/frontend/static/css/general.css b/frontend/static/css/general.css index 2aaa5ef..d63c3c0 100644 --- a/frontend/static/css/general.css +++ b/frontend/static/css/general.css @@ -155,8 +155,9 @@ header > div { width: var(--height); } -h1 { - font-size: clamp(1.3rem, 7vw, 2rem); +header img { + height: 3rem; + width: fit-content; } /* */ diff --git a/frontend/static/img/favicon.svg b/frontend/static/img/favicon.svg new file mode 100644 index 0000000..878a5d5 --- /dev/null +++ b/frontend/static/img/favicon.svg @@ -0,0 +1,30 @@ + + + + White + + + + Off-white + + + + Gray + + + + Dark gray + + + + + + + + + + + M​ + + + \ No newline at end of file diff --git a/frontend/templates/login.html b/frontend/templates/login.html index 02e93f0..5639ab8 100644 --- a/frontend/templates/login.html +++ b/frontend/templates/login.html @@ -6,6 +6,8 @@ + + @@ -14,7 +16,7 @@
-

MIND Reminders

+
diff --git a/frontend/templates/page_not_found.html b/frontend/templates/page_not_found.html index 1d7b929..5f47268 100644 --- a/frontend/templates/page_not_found.html +++ b/frontend/templates/page_not_found.html @@ -5,6 +5,8 @@ + + @@ -12,7 +14,7 @@
-

MIND Reminders

+

MIND

diff --git a/frontend/templates/reminders.html b/frontend/templates/reminders.html index 75f614b..84f002a 100644 --- a/frontend/templates/reminders.html +++ b/frontend/templates/reminders.html @@ -6,6 +6,8 @@ + + @@ -33,7 +35,7 @@ -

MIND Reminders

+
From 84660ccad3c470801351542bb706c3b603acb20e Mon Sep 17 00:00:00 2001 From: CasVT Date: Sat, 1 Jul 2023 00:25:02 +0200 Subject: [PATCH 19/46] Fixed logging in on new devices after last commit --- frontend/static/css/general.css | 3 ++- frontend/static/js/login.js | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/static/css/general.css b/frontend/static/css/general.css index d63c3c0..a93a477 100644 --- a/frontend/static/css/general.css +++ b/frontend/static/css/general.css @@ -287,7 +287,8 @@ nav > div > button svg { align-items: center; flex-wrap: wrap; gap: 1rem; - + + padding-inline: .5rem; padding-top: 1rem; } diff --git a/frontend/static/js/login.js b/frontend/static/js/login.js index 25e119f..36cd44c 100644 --- a/frontend/static/js/login.js +++ b/frontend/static/js/login.js @@ -110,6 +110,9 @@ function checkLogin() { // code run on load +if (localStorage.getItem('MIND') === null) + localStorage.setItem('MIND', JSON.stringify({'api_key': null, 'locale': 'en-GB'})) + const url_prefix = document.getElementById('url_prefix').dataset.value; checkLogin(); From c64034ea0195bbc5fe753f694302b910ed837739 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:14:19 +0200 Subject: [PATCH 20/46] Moved project management files into own folder --- .github/workflows/build_docs.yml | 33 +++++++++++++++++++ .github/workflows/deploy.yml | 20 ----------- .../docs-requirements.txt | 0 .../generate_api_docs.py | 12 +++++++ mkdocs.yml => project_management/mkdocs.yml | 1 + 5 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/build_docs.yml delete mode 100644 .github/workflows/deploy.yml rename docs-requirements.txt => project_management/docs-requirements.txt (100%) rename generate_api_docs.py => project_management/generate_api_docs.py (93%) rename mkdocs.yml => project_management/mkdocs.yml (99%) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml new file mode 100644 index 0000000..0bd0944 --- /dev/null +++ b/.github/workflows/build_docs.yml @@ -0,0 +1,33 @@ +name: Update and build docs + +on: + push: + branches: + - Development + +jobs: + update: + name: Update API docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install -r requirements.txt + - run: python3 project_management/generate_api_docs.py + + deploy: + name: Build docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install -r project_management/docs-requirements.txt + - run: mkdocs gh-deploy --force -f project_management/mkdocs.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 29d8472..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Build and deploy docs - -on: - push: - branches: - - Development - -jobs: - deploy: - name: Deploy docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip install -r docs-requirements.txt - - run: mkdocs gh-deploy --force diff --git a/docs-requirements.txt b/project_management/docs-requirements.txt similarity index 100% rename from docs-requirements.txt rename to project_management/docs-requirements.txt diff --git a/generate_api_docs.py b/project_management/generate_api_docs.py similarity index 93% rename from generate_api_docs.py rename to project_management/generate_api_docs.py index 7daf5a9..7cf4c13 100644 --- a/generate_api_docs.py +++ b/project_management/generate_api_docs.py @@ -1,6 +1,12 @@ #!/usr/bin/env python3 #-*- coding: utf-8 -*- +from sys import path +from os.path import dirname + +path.insert(0, dirname(path[0])) + +from subprocess import run from typing import Union from frontend.api import (DataSource, NotificationServiceNotFound, ReminderNotFound, TemplateNotFound, api_docs) @@ -114,3 +120,9 @@ if current_content == result: else: with open(_folder_path('docs', 'api.md'), 'w+') as f: f.write(result) + + run(["git", "checkout", "Development"]) + run(["git", "add", _folder_path('docs', 'api.md')]) + run(["git", "commit", "-m", "Updated API docs"]) + run(["git", "push"]) + \ No newline at end of file diff --git a/mkdocs.yml b/project_management/mkdocs.yml similarity index 99% rename from mkdocs.yml rename to project_management/mkdocs.yml index 7f887a1..6c5b31f 100644 --- a/mkdocs.yml +++ b/project_management/mkdocs.yml @@ -5,6 +5,7 @@ site_author: Casvt repo_url: https://github.com/Casvt/MIND edit_uri: blob/Development/docs/ repo_name: Casvt/MIND +docs_dir: ../docs extra_css: - assets/css/extra.css From c3f99c5b149965b77ecc31634f69922c34e1d12f Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:19:13 +0200 Subject: [PATCH 21/46] Deploy runs after update instead of simultaneously --- .github/workflows/build_docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 0bd0944..8be91a6 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -20,6 +20,7 @@ jobs: - run: python3 project_management/generate_api_docs.py deploy: + needs: update name: Build docs runs-on: ubuntu-latest steps: From ce72af1dd314b7fbbb181d6d21e9848d3e0f27ef Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:36:35 +0200 Subject: [PATCH 22/46] Added test workflow --- .github/workflows/tests.yml | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..52a491f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,57 @@ +name: Tests + +on: + push: + branches: + - Development + +jobs: + p3.7: + name: Python 3.7 + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v2 + with: + python-version: 3.7 + - run: pip install -r requirements.txt + - run: python3 -m unittest discover -s ./tests -p '*.py' + + p3.8: + name: Python 3.8 + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + - run: pip install -r requirements.txt + - run: python3 -m unittest discover -s ./tests -p '*.py' + + p3.9: + name: Python 3.9 + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v2 + with: + python-version: 3.9 + - run: pip install -r requirements.txt + - run: python3 -m unittest discover -s ./tests -p '*.py' + + p3.10: + name: Python 3.10 + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v2 + with: + python-version: 3.10 + - run: pip install -r requirements.txt + - run: python3 -m unittest discover -s ./tests -p '*.py' + + p3.11: + name: Python 3.11 + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v2 + with: + python-version: 3.11 + - run: pip install -r requirements.txt + - run: python3 -m unittest discover -s ./tests -p '*.py' From 39c4b7227c4a0893582db6790492d069eea583c4 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:37:22 +0200 Subject: [PATCH 23/46] Fixed tests workflow --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 52a491f..9d1d5a1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ on: - Development jobs: - p3.7: + p3_7: name: Python 3.7 runs-on: ubuntu-latest steps: @@ -16,7 +16,7 @@ jobs: - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' - p3.8: + p3_8: name: Python 3.8 runs-on: ubuntu-latest steps: @@ -26,7 +26,7 @@ jobs: - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' - p3.9: + p3_9: name: Python 3.9 runs-on: ubuntu-latest steps: @@ -36,7 +36,7 @@ jobs: - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' - p3.10: + p3_10: name: Python 3.10 runs-on: ubuntu-latest steps: @@ -46,7 +46,7 @@ jobs: - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' - p3.11: + p3_11: name: Python 3.11 runs-on: ubuntu-latest steps: From bdf0a36869602cee0ef05e37139de86ea1a73ab0 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:43:49 +0200 Subject: [PATCH 24/46] Fixed tests not finding requirements file --- .github/workflows/tests.yml | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9d1d5a1..dc2a527 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,51 +7,66 @@ on: jobs: p3_7: - name: Python 3.7 + name: Python 3.7.1 runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.7.1 - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' p3_8: - name: Python 3.8 + name: Python 3.8.0 runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.8.0 - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' p3_9: - name: Python 3.9 + name: Python 3.9.0 runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.9.0 - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' p3_10: - name: Python 3.10 + name: Python 3.10.0 runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: 3.10.0 - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' p3_11: - name: Python 3.11 + name: Python 3.11.0 runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - uses: actions/setup-python@v2 with: - python-version: 3.11 + python-version: 3.11.0 - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' From d8c6e331b0ae14a4d182703be8c4e1938211af4e Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:50:08 +0200 Subject: [PATCH 25/46] Fixed fetching python versions in workflow --- .github/workflows/tests.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dc2a527..2964cd3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,9 +13,12 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.7.1 + architecture: x64 + cache: 'pip' + cache-dependency-path: requirements.txt - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' @@ -26,9 +29,12 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.8.0 + architecture: x64 + cache: 'pip' + cache-dependency-path: requirements.txt - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' @@ -39,9 +45,12 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.9.0 + architecture: x64 + cache: 'pip' + cache-dependency-path: requirements.txt - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' @@ -52,9 +61,12 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.10.0 + architecture: x64 + cache: 'pip' + cache-dependency-path: requirements.txt - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' @@ -65,8 +77,11 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.11.0 + architecture: x64 + cache: 'pip' + cache-dependency-path: requirements.txt - run: pip install -r requirements.txt - run: python3 -m unittest discover -s ./tests -p '*.py' From 34425ed4a822287afff017885643b5993d5817ce Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:52:32 +0200 Subject: [PATCH 26/46] Fixed ubuntu platform for workflow --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2964cd3..2163fe0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ on: jobs: p3_7: name: Python 3.7.1 - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: @@ -24,7 +24,7 @@ jobs: p3_8: name: Python 3.8.0 - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: @@ -40,7 +40,7 @@ jobs: p3_9: name: Python 3.9.0 - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: @@ -56,7 +56,7 @@ jobs: p3_10: name: Python 3.10.0 - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: @@ -72,7 +72,7 @@ jobs: p3_11: name: Python 3.11.0 - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: From 09c2dd6fe6b866fb5969700e433a48740bcb38e7 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 13:54:55 +0200 Subject: [PATCH 27/46] Removed python 3.7 test --- .github/workflows/tests.yml | 16 ---------------- CONTRIBUTING.md | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2163fe0..34a6ac9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,22 +6,6 @@ on: - Development jobs: - p3_7: - name: Python 3.7.1 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-python@v4 - with: - python-version: 3.7.1 - architecture: x64 - cache: 'pip' - cache-dependency-path: requirements.txt - - run: pip install -r requirements.txt - - run: python3 -m unittest discover -s ./tests -p '*.py' - p3_8: name: Python 3.8.0 runs-on: ubuntu-20.04 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8965f67..f06e3ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ python3 -m unittest discover -v -s './tests' -p '*_test.py' ## Styling guide The code of MIND is written in such way that it follows the following rules. Your code should too. -1. Compatible with python 3.7 . +1. Compatible with python 3.8 . 2. Tabs (4 space size) are used for indentation. 3. Use type hints as much as possible, though don't if it requires importing extra functions or classes (except for the `typing` library). 4. Each function in the backend needs a doc string describing the function, what the inputs are, what errors could be raised from within the function and what the output is. From 94becf35e6c38417abbe7933b41359bafcf3b22f Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 14:12:59 +0200 Subject: [PATCH 28/46] Fixed write problems in workflow --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 34a6ac9..b0a7bc9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,5 +1,7 @@ name: Tests +permissions: write-all + on: push: branches: From 7279abcf31a7c294b23e3b6543485dea99ee71d5 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 14:19:42 +0200 Subject: [PATCH 29/46] Converted workflow to matrix --- .github/workflows/tests.yml | 79 ++++++++----------------------------- 1 file changed, 17 insertions(+), 62 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0a7bc9..c91d6fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,73 +1,28 @@ name: Tests -permissions: write-all - on: push: branches: - Development jobs: - p3_8: - name: Python 3.8.0 + test: runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 with: - fetch-depth: 0 - - uses: actions/setup-python@v4 - with: - python-version: 3.8.0 - architecture: x64 - cache: 'pip' - cache-dependency-path: requirements.txt - - run: pip install -r requirements.txt - - run: python3 -m unittest discover -s ./tests -p '*.py' - - p3_9: - name: Python 3.9.0 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-python@v4 - with: - python-version: 3.9.0 - architecture: x64 - cache: 'pip' - cache-dependency-path: requirements.txt - - run: pip install -r requirements.txt - - run: python3 -m unittest discover -s ./tests -p '*.py' - - p3_10: - name: Python 3.10.0 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-python@v4 - with: - python-version: 3.10.0 - architecture: x64 - cache: 'pip' - cache-dependency-path: requirements.txt - - run: pip install -r requirements.txt - - run: python3 -m unittest discover -s ./tests -p '*.py' - - p3_11: - name: Python 3.11.0 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-python@v4 - with: - python-version: 3.11.0 - architecture: x64 - cache: 'pip' - cache-dependency-path: requirements.txt - - run: pip install -r requirements.txt - - run: python3 -m unittest discover -s ./tests -p '*.py' + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run Tests + run: python -m unittest discover -s ./tests -p '*.py' From 025c372671b5bccf0d0d3f96b2739b5b71e4cdc5 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 14:25:46 +0200 Subject: [PATCH 30/46] Attempt at fixing tests workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c91d6fd..3ab7695 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,4 +25,4 @@ jobs: pip install -r requirements.txt - name: Run Tests - run: python -m unittest discover -s ./tests -p '*.py' + run: sudo python -m unittest discover -s ./tests -p '*.py' From 489a47eb6720da0a7c67a31376b881bf6e1634e4 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 14:26:44 +0200 Subject: [PATCH 31/46] Attempt two at fixing tests workflow --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ab7695..300faa9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,8 +21,8 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r requirements.txt + sudo python -m pip install --upgrade pip + sudo pip install -r requirements.txt - name: Run Tests run: sudo python -m unittest discover -s ./tests -p '*.py' From 0ea20afb4f74b53a2b90cd1d914631c4b837daec Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 14:54:25 +0200 Subject: [PATCH 32/46] Most likely finally fixed tests workflow --- .github/workflows/tests.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 300faa9..f830c75 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,8 +21,10 @@ jobs: - name: Install dependencies run: | - sudo python -m pip install --upgrade pip - sudo pip install -r requirements.txt + python -m pip install --upgrade pip + pip install -r requirements.txt - name: Run Tests - run: sudo python -m unittest discover -s ./tests -p '*.py' + run: | + mkdir db + python -m unittest discover -s ./tests -p '*.py' From 1e3691ff3e81611f1eb4f2219a85765315c080a8 Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 15:10:27 +0200 Subject: [PATCH 33/46] Removed pip upgrade from tests workflow --- .github/workflows/tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f830c75..9f108bf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,9 +20,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt + run: pip install -r requirements.txt - name: Run Tests run: | From c7047452205e690bb716c6c6487debb06cb0d1ca Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 20:06:22 +0200 Subject: [PATCH 34/46] Fixed test for python3.11 --- tests/custom_exceptions_test.py | 2 +- tests/db_test.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/custom_exceptions_test.py b/tests/custom_exceptions_test.py index 07547aa..66a97cb 100644 --- a/tests/custom_exceptions_test.py +++ b/tests/custom_exceptions_test.py @@ -7,7 +7,7 @@ import backend.custom_exceptions class Test_Custom_Exceptions(unittest.TestCase): def test_type(self): - defined_exceptions: List[Exception] = map(lambda c: c[1], getmembers(modules['backend.custom_exceptions'], isclass)) + defined_exceptions: List[Exception] = filter(lambda c: c.__module__ == 'backend.custom_exceptions', map(lambda c: c[1], getmembers(modules['backend.custom_exceptions'], isclass))) for defined_exception in defined_exceptions: self.assertEqual(getmro(defined_exception)[1], Exception) result = defined_exception().api_response diff --git a/tests/db_test.py b/tests/db_test.py index 965114f..13b3802 100644 --- a/tests/db_test.py +++ b/tests/db_test.py @@ -8,5 +8,3 @@ class Test_DB(unittest.TestCase): DBConnection.file = _folder_path(*DB_FILENAME) instance = DBConnection(timeout=20.0) self.assertEqual(instance.cursor().execute("PRAGMA foreign_keys;").fetchone()[0], 1) - - \ No newline at end of file From 4b1a4601eb82915108d9fecb1e4c26d26586fcdb Mon Sep 17 00:00:00 2001 From: CasVT Date: Sun, 2 Jul 2023 20:22:29 +0200 Subject: [PATCH 35/46] Added pip caching in workflows --- .github/workflows/build_docs.yml | 22 ++++++++++------------ .github/workflows/tests.yml | 1 + 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 8be91a6..0163e14 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -8,27 +8,25 @@ on: jobs: update: name: Update API docs - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: - fetch-depth: 0 - - uses: actions/setup-python@v2 - with: - python-version: 3.x + python-version: 3.8 + cache: 'pip' - run: pip install -r requirements.txt - run: python3 project_management/generate_api_docs.py deploy: needs: update name: Build docs - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: - fetch-depth: 0 - - uses: actions/setup-python@v2 - with: - python-version: 3.x + python-version: 3.8 + cache: 'pip' - run: pip install -r project_management/docs-requirements.txt - run: mkdocs gh-deploy --force -f project_management/mkdocs.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f108bf..74e28cd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,6 +18,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: 'pip' - name: Install dependencies run: pip install -r requirements.txt From eda45a1ab189be83a62346b076d1282c204da12f Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 15:14:48 +0200 Subject: [PATCH 36/46] Updated CONTRIBUTING after adding tests workflow --- CONTRIBUTING.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f06e3ce..e4d4fb7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to MIND ## General steps -Contributing to MIND consists of 5 steps, listed hereunder. +Contributing to MIND consists of 5 steps: 1. Make a [contributing request](https://github.com/Casvt/MIND/issues/new?template=contribute-request.md), where you describe what you plan on doing. This request needs to get approved before you can start, or your pull request won't be accepted. This is to avoid multiple people from doing the same thing and to avoid you wasting your time if we do not wish the changes. This is also where discussions can be held about how something will be implemented. 2. When the request is accepted, start your local development (more info about this below). @@ -11,16 +11,15 @@ Contributing to MIND consists of 5 steps, listed hereunder. ## Local development steps Once your request is accepted, you can start your local development. -1. Clone the repository onto your computer and open it using your prefered IDE (Visual Studio Code is used by us). +1. Fork the repository and clone the fork onto your computer and open it using your preferred IDE (Visual Studio Code is used by us). 2. Make the changes needed and write accompanying tests. 3. Check if the code written follows the styling guide below. -4. Run the finished version, using python 3.7, to check if you've made any errors. -5. Run the tests (unittest is used). This can be done with a button click within VS Code, or with the following command where you need to be inside the root folder of the project: +4. If you want to run the tests manually before committing, use the command below in the root folder of the project: ```bash python3 -m unittest discover -v -s './tests' -p '*_test.py' ``` -6. Test your version thoroughly to catch as many bugs as possible (if any). -7. If anything is changed about the API, run the `generate_api_docs.py` script to generate the new API docs. Commit the changes that were made by the script. +5. Update the docs if needed. +6. Commit and push to your fork. When you push, GitHub Actions will do a lot of work for you: the tests are run again on python versions 3.8 - 3.11, the API documentation is updated if any changes have been made to the API and the docs are updated if any changes have been made to the docs. All GitHub Actions need to succeed before you're allowed to make a PR (you'll see a green checkmark next to the commit in GitHub). ## Styling guide The code of MIND is written in such way that it follows the following rules. Your code should too. @@ -33,4 +32,4 @@ The code of MIND is written in such way that it follows the following rules. You 6. The code needs to be compatible with Linux, MacOS, Windows and Docker. 7. The code should, though not strictly enforced, reasonably comply with the rule of 80 characters per line. -If you just code in the same style as the current code, you'll follow most of these rules automatically. \ No newline at end of file +If you just code in the same style as the current code, you'll follow most of these rules automatically. From da286bd7d868718ebd019edfd5bc01a7c75ce24a Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 15:58:22 +0200 Subject: [PATCH 37/46] Updated API docs --- docs/api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 5b56bb2..f734c5d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -5,7 +5,8 @@ All endpoints have the `/api` prefix. That means, for example, that `/auth/login ## Authentication -Authentication is done using an API key. To log in, make a POST request to the [`/api/auth/login`](#authlogin) endpoint. You'll receive an API key, which you can then use in your requests to authenticate. Supply it as a url parameter with the key `api_key`. This API key is valid for one hour, after which the key expires, any further requests return 401 'APIKeyExpired' and you are required to log in again. If no `api_key` is supplied or it is invalid, 401 `APIKeyInvalid` is returned. +Authentication is done using an API key. +To log in, make a POST request to the [`/api/auth/login`](#authlogin) endpoint. You'll receive an API key, which you can then use in your requests to authenticate. Supply it as a url parameter with the key `api_key`. This API key is valid for one hour, after which the key expires, any further requests return 401 'APIKeyExpired' and you are required to log in again. If no `api_key` is supplied or it is invalid, 401 `APIKeyInvalid` is returned. For example: ```bash From 155c51cc22e68a01e115ce5c66b587edb66d0e70 Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 16:03:50 +0200 Subject: [PATCH 38/46] Added base URL and locale docs, moved some things around --- docs/installation.md | 64 +++++++++++++++---------- docs/setup_after_installation.md | 12 ++++- project_management/generate_api_docs.py | 18 ++++--- 3 files changed, 62 insertions(+), 32 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 734b9bb..c19384b 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -6,8 +6,35 @@ NOTE: Make sure to set all time related settings (time, date, timezone, etc.) co ## Docker +### Database location + +We first need to create a named volume, or a folder, to store the database file of MIND in. + === "Docker CLI" - The command to get the docker container running can be found below. 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 mind-db + ``` + +=== "Portainer" + - Open `Volumes` + - Click `Add Volume` + - Enter name matching the one you'll use in compose (`mind-db`, in the above provided command) + - Click `Create the volume` + - Open `Stacks` + - Create the stack with the named volume in it. + +=== "Folder" + Linux standards suggest to put the database in `/opt/application_name`, as the `/opt` directory is where program options should be stored. In this case, you'd create the desired folder using the following command: + ```bash + mkdir /opt/MIND/db + ``` + +### Run the container + +Now that we can store the database somewhere, we can get the container running. + +=== "Docker CLI" + The command to get the docker container running can be found below. Replace the timezone value (`TZ=`) with the [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) of your timezone (value of `TZ identifier` on webpage). ```bash docker run -d \ --name mind \ @@ -16,8 +43,9 @@ NOTE: Make sure to set all time related settings (time, date, timezone, etc.) co -p 8080:8080 \ mrcas/mind:latest ``` + === "Docker Compose" - The contents of the `docker-compose.yml` file would look like below. Replace the timezone value (`TZ=`) to the [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) of your timezone. + The contents of the `docker-compose.yml` file would look like below. Replace the timezone value (`TZ=`) with the [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) of your timezone (value of `TZ identifier` on webpage). ```yml version: '3.3' services: @@ -31,30 +59,15 @@ NOTE: Make sure to set all time related settings (time, date, timezone, etc.) co - '8080:8080' image: 'mrcas/mind:latest' ``` + Now run the compose by running the following command in the root folder: + ```bash + docker compose up -d + ``` + +If you didn't name your docker volume `mind-db` (see [Database location](#database-location)), replace `mind-db` in the command with the name of your volume. If you created a folder, replace `mind-db` with `/opt/MIND/db` or the folder you want. Information on how to change the port can be found on the [Setup After Installation page](setup_after_installation.md#port). -Using a named volume in docker requires you to create the volume before you can use it (see [Named Volumes](#named-volumes)). - -### Named Volumes - -=== "Docker CLI" - ```bash - docker volume create mind-db - ``` - -=== "Portainer" - - Open `Volumes` - - Click `Add Volume` - - Enter name matching the one you'll use in compose (`mind-db`, in the above provided command) - - Click `Create the volume` - - Open `Stacks` - - Create the stack with the named volume in it. - -Both of these options will create a named volume that you can then use in the command above. -If you'd prefer to use a local folder on the host machine for storing config, Linux standards would suggest putting that in `/opt/application_name`, as the `/opt` directory is where program options should be stored. -In this case, you'd create the desired folder with something like `mkdir /opt/MIND/db`, and replace 'mind-db:/app/db' with '/opt/MIND/db:/app/db'. - ## Manual Install See below for installation instructions for your OS if you want to install it manually. @@ -67,6 +80,7 @@ See below for installation instructions for your OS if you want to install it ma python3 -m pip install -r requirements.txt python3 MIND.py ``` + === "Windows" 1. Install python [in the Microsoft Store](https://www.microsoft.com/store/productId/9PJPW5LDXLZ5) 2. Install pip [using these instructions](https://www.liquidweb.com/kb/install-pip-windows/) @@ -75,9 +89,9 @@ See below for installation instructions for your OS if you want to install it ma 5. With the folder open, right click and select `Open in Terminal` 6. Type the following command: ```bash - python3 -m pip install -r requirements.txt + python -m pip install -r requirements.txt ``` 7. Type the following command: ```bash - python3 MIND.py + python MIND.py ``` diff --git a/docs/setup_after_installation.md b/docs/setup_after_installation.md index 6a5323e..18d13de 100644 --- a/docs/setup_after_installation.md +++ b/docs/setup_after_installation.md @@ -15,10 +15,20 @@ The first thing to do is decide if you want to leave MIND running on the default === "Manual Install" Inside the `MIND.py` file at the top, you can set the port via the `PORT` variable. Change it from `PORT = '8080'` to `PORT = '{PORT}'`, where `{PORT}` is the desired port (e.g. `PORT = '8009'`). Then restart the application. +## Base URL + +If you want to set a base url (e.g. for a reverse proxy), go inside the `MIND.py` file and at the top, you can set the base URL via the `URL_PREFIX` variable. Change it from `URL_PREFIX = ''` to `URL_PREFIX = '/{PREFIX}'`, where `{PREFIX}` is the desired URL prefix (e.g. `URL_PREFIX = '/mind'`). Then restart the application. + ## Creating an account When accessing the web-ui, you'll be prompted to log in. Click on `Or create an account`, enter the desired username and password for the account and click `Create`. The account is created and can now be logged in with. The complete authentication process is local and no data is shared with any other service. +## Set your locale + +In the settings, you can change your locale, so that the dates and times are displayed in the format used by your country. + ## Add a notification service -A notification service is a way of sending a notification. For example an e-mail to a group of people or a PushBullet notification to a specific device. What the actual content of the notification is, is decided by the title and text of the reminder. The notification service only specifies in which way the title and text is sent. You set it up once, and then you can select it when creating a reminder. A notification service consists of a title (name) and an Apprise URL. See the [Apprise URL documentation](https://github.com/caronc/apprise#supported-notifications) to learn how to make a valid Apprise URL. +A notification service is a way of sending a notification. For example an e-mail to a group of people or a PushBullet notification to a specific device. What the actual content of the notification is, is decided by the title and text of the reminder. The notification service only specifies in which way the title and text is sent. You set it up once, and then you can select it when creating a reminder. + +Go to the "Notification Services" tab in the web-ui and click the `+` button. A notification service consists of a title (name) and an Apprise URL. See the [Apprise URL documentation](https://github.com/caronc/apprise#supported-notifications) to learn how to make a valid Apprise URL. diff --git a/project_management/generate_api_docs.py b/project_management/generate_api_docs.py index 7cf4c13..93bd353 100644 --- a/project_management/generate_api_docs.py +++ b/project_management/generate_api_docs.py @@ -26,27 +26,34 @@ All endpoints have the `{api_prefix}` prefix. That means, for example, that `/au ## Authentication -Authentication is done using an API key. To log in, make a POST request to the [`/api/auth/login`](#authlogin) endpoint. You'll receive an API key, which you can then use in your requests to authenticate. Supply it as a url parameter with the key `api_key`. This API key is valid for one hour, after which the key expires, any further requests return 401 'APIKeyExpired' and you are required to log in again. If no `api_key` is supplied or it is invalid, 401 `APIKeyInvalid` is returned. +Authentication is done using an API key. +To log in, make a POST request to the [`{api_prefix}/auth/login`](#authlogin) endpoint. +You'll receive an API key, which you can then use in your requests to authenticate. +Supply it via the url parameter `api_key`. +This API key is valid for one hour after which the key expires, any further requests return 401 'APIKeyExpired' and you are required to log in again. +If no `api_key` is supplied or it is invalid, 401 `APIKeyInvalid` is returned. For example: ```bash -curl -sSL 'http://192.168.2.15:8080/api/reminders?api_key=ABCDEFG' +curl -sSL 'http://192.168.2.15:8080{api_prefix}/reminders?api_key=ABCDEFG' ``` ## Supplying data -Often, data needs to be supplied with a request. If the parameters need to be supplied via `url`, add them to the url as url parameters. If the parameters need to be supplied via `body`, add them to the body as a json object and supply the `Content-Type: application/json` header. +Often, data needs to be supplied with a request. +If the parameters need to be supplied via `url`, add them to the url as url parameters. +If the parameters need to be supplied via `body`, add them to the body as a json object and supply the `Content-Type: application/json` header. For example: ```bash # URL parameter -curl -sSL 'http://192.168.2.15:8080/api/reminders/search?api_key=ABCDEFG&query=Fountain&sort_by=time_reversed' +curl -sSL 'http://192.168.2.15:8080{api_prefix}/reminders/search?api_key=ABCDEFG&query=Fountain&sort_by=time_reversed' # Body parameter curl -sSLX POST \\ -H 'Content-Type: application/json' \\ -d '{{"title": "Test service", "url": "test://fake/url"}}' \\ - 'http://192.168.2.15:8080/api/notificationservices?api_key=ABCDEFG' + 'http://192.168.2.15:8080{api_prefix}/notificationservices?api_key=ABCDEFG' ``` ## Endpoints @@ -125,4 +132,3 @@ else: run(["git", "add", _folder_path('docs', 'api.md')]) run(["git", "commit", "-m", "Updated API docs"]) run(["git", "push"]) - \ No newline at end of file From 3ab2df31f75d20206d3363ae7132dcbb6552381a Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 16:09:07 +0200 Subject: [PATCH 39/46] Fixed auto gen API docs script committing --- project_management/generate_api_docs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project_management/generate_api_docs.py b/project_management/generate_api_docs.py index 93bd353..98c8935 100644 --- a/project_management/generate_api_docs.py +++ b/project_management/generate_api_docs.py @@ -128,6 +128,8 @@ else: with open(_folder_path('docs', 'api.md'), 'w+') as f: f.write(result) + run(["git", "config", "--global", "user.email", '"casvantijn@gmail.com"']) + run(["git", "config", "--global", "user.name", '"CasVT"']) run(["git", "checkout", "Development"]) run(["git", "add", _folder_path('docs', 'api.md')]) run(["git", "commit", "-m", "Updated API docs"]) From c0dc02a52d3c5d49d8504de043afdd316d85c793 Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 14:09:31 +0000 Subject: [PATCH 40/46] Updated API docs --- docs/api.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/api.md b/docs/api.md index f734c5d..d8658d3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -6,7 +6,11 @@ All endpoints have the `/api` prefix. That means, for example, that `/auth/login ## Authentication Authentication is done using an API key. -To log in, make a POST request to the [`/api/auth/login`](#authlogin) endpoint. You'll receive an API key, which you can then use in your requests to authenticate. Supply it as a url parameter with the key `api_key`. This API key is valid for one hour, after which the key expires, any further requests return 401 'APIKeyExpired' and you are required to log in again. If no `api_key` is supplied or it is invalid, 401 `APIKeyInvalid` is returned. +To log in, make a POST request to the [`/api/auth/login`](#authlogin) endpoint. +You'll receive an API key, which you can then use in your requests to authenticate. +Supply it via the url parameter `api_key`. +This API key is valid for one hour after which the key expires, any further requests return 401 'APIKeyExpired' and you are required to log in again. +If no `api_key` is supplied or it is invalid, 401 `APIKeyInvalid` is returned. For example: ```bash @@ -15,7 +19,9 @@ curl -sSL 'http://192.168.2.15:8080/api/reminders?api_key=ABCDEFG' ## Supplying data -Often, data needs to be supplied with a request. If the parameters need to be supplied via `url`, add them to the url as url parameters. If the parameters need to be supplied via `body`, add them to the body as a json object and supply the `Content-Type: application/json` header. +Often, data needs to be supplied with a request. +If the parameters need to be supplied via `url`, add them to the url as url parameters. +If the parameters need to be supplied via `body`, add them to the body as a json object and supply the `Content-Type: application/json` header. For example: ```bash From 5f0e3a578c70fe9712500d27ea0ee1c62e608243 Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 16:39:27 +0200 Subject: [PATCH 41/46] Sped up docs workflow and added names --- .github/workflows/build_docs.yml | 23 ++++++++--------------- .github/workflows/tests.yml | 17 +++++++++-------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 0163e14..97c59b8 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -6,27 +6,20 @@ on: - Development jobs: - update: - name: Update API docs + update_build: + name: Update API docs and build docs runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + name: Setup checkout - uses: actions/setup-python@v4 + name: Setup python 3.8 with: python-version: 3.8 cache: 'pip' - - run: pip install -r requirements.txt + - run: pip install -r requirements.txt -r project_management/docs-requirements.txt + name: Install dependencies - run: python3 project_management/generate_api_docs.py - - deploy: - needs: update - name: Build docs - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.8 - cache: 'pip' - - run: pip install -r project_management/docs-requirements.txt + name: Generate API docs - run: mkdocs gh-deploy --force -f project_management/mkdocs.yml + name: Build docs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 74e28cd..a09f86a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,16 +14,17 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + name: Set up checkout + - uses: actions/setup-python@v4 + name: Set up Python ${{ matrix.python-version }} with: python-version: ${{ matrix.python-version }} cache: 'pip' - - - name: Install dependencies - run: pip install -r requirements.txt - - - name: Run Tests - run: | + + - run: pip install -r requirements.txt + name: Install dependencies + + - run: | mkdir db python -m unittest discover -s ./tests -p '*.py' + name: Run Tests From 4186931ad9c64535577a07bec0f344f6297ba650 Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 16:42:21 +0200 Subject: [PATCH 42/46] Fixed warning from mkdocs in workflow --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a09f86a..9b20e4d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,6 +15,8 @@ jobs: steps: - uses: actions/checkout@v3 name: Set up checkout + with: + fetch-depth: 0 - uses: actions/setup-python@v4 name: Set up Python ${{ matrix.python-version }} with: From 6105e9754a92fbf0cfde2fa7c6425d4e8a57bfa1 Mon Sep 17 00:00:00 2001 From: CasVT Date: Mon, 3 Jul 2023 17:01:15 +0200 Subject: [PATCH 43/46] Added backup and recovery docs --- docs/backup_your_data.md | 23 +++++++++++++++++++++++ project_management/mkdocs.yml | 1 + 2 files changed, 24 insertions(+) create mode 100644 docs/backup_your_data.md diff --git a/docs/backup_your_data.md b/docs/backup_your_data.md new file mode 100644 index 0000000..4d14dd9 --- /dev/null +++ b/docs/backup_your_data.md @@ -0,0 +1,23 @@ +# Backup your data + +It's possible, and very easy, to backup all MIND data. This way, you can recover data if something goes wrong, port your instance over to an other computer or run redundant instances. + +## Backing up the data + +It's as simple as making a copy of the database file and storing it somewhere safe. + +1. Stop MIND, if it's still running. +2. Go inside the docker volume of the container (most likely `mind-db`) or the mapped folder. +3. Inside the volume/folder, you'll find the `MIND.db` file. Make a copy of this. That's all you need. +4. You can now start the instance back up. + +The database file contains all data and is the only thing needed to keep a complete backup of your MIND instance. + +## Recovering the data + +It's as simple as putting the database file in the database folder and restarting the instance. + +1. Stop MIND, if it's still running. +2. Go inside the docker volume of the container (most likely `mind-db`) or the mapped folder. +3. Inside the volume/folder, place the database file that you backed up. +4. You can now start the instance back up. Everything should be recovered. diff --git a/project_management/mkdocs.yml b/project_management/mkdocs.yml index 6c5b31f..be312fd 100644 --- a/project_management/mkdocs.yml +++ b/project_management/mkdocs.yml @@ -82,3 +82,4 @@ nav: - Setup After Installation: setup_after_installation.md - Other Docs: - API: api.md + - Backup Your Data: backup_your_data.md From c9a60f1b483aeeabaace6d5ac2a1bac865341987 Mon Sep 17 00:00:00 2001 From: CasVT Date: Tue, 4 Jul 2023 13:41:35 +0200 Subject: [PATCH 44/46] Added logging --- MIND.py | 16 +++++++++++--- backend/custom_exceptions.py | 38 ++++++++++++++++++++++----------- backend/db.py | 11 ++++++++-- backend/notification_service.py | 9 ++++++-- backend/reminders.py | 29 ++++++++++++++++++++++--- backend/static_reminders.py | 14 +++++++++++- backend/templates.py | 13 ++++++++++- backend/users.py | 14 +++++++++--- 8 files changed, 116 insertions(+), 28 deletions(-) diff --git a/MIND.py b/MIND.py index 548cf5a..7e7879a 100644 --- a/MIND.py +++ b/MIND.py @@ -18,9 +18,16 @@ from frontend.ui import ui HOST = '0.0.0.0' PORT = '8080' URL_PREFIX = '' # Must either be empty or start with '/' e.g. '/mind' +LOGGING_LEVEL = logging.INFO THREADS = 10 DB_FILENAME = 'db', 'MIND.db' +logging.basicConfig( + level=LOGGING_LEVEL, + format='[%(asctime)s][%(threadName)s][%(levelname)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) + def _folder_path(*folders) -> str: """Turn filepaths relative to the project folder into absolute paths Returns: @@ -32,7 +39,7 @@ def _create_app() -> Flask: """Create a Flask app instance Returns: Flask: The created app instance - """ + """ app = Flask( __name__, template_folder=_folder_path('frontend','templates'), @@ -79,7 +86,7 @@ def MIND() -> None: """ # Check python version if (version_info.major < 3) or (version_info.major == 3 and version_info.minor < 7): - print('Error: the minimum python version required is python3.7 (currently ' + version_info.major + '.' + version_info.minor + '.' + version_info.micro + ')') + logging.error('Error: the minimum python version required is python3.7 (currently ' + version_info.major + '.' + version_info.minor + '.' + version_info.micro + ')') exit(1) # Register web server @@ -91,8 +98,11 @@ def MIND() -> None: with app.app_context(): if isfile(_folder_path('db', 'Noted.db')): move(_folder_path('db', 'Noted.db'), _folder_path(*DB_FILENAME)) + db_location = _folder_path(*DB_FILENAME) + logging.debug(f'Database location: {db_location}') makedirs(dirname(db_location), exist_ok=True) + DBConnection.file = db_location setup_db() reminder_handler.find_next_reminder() @@ -101,7 +111,7 @@ def MIND() -> None: dispatcher = ThreadedTaskDispatcher() dispatcher.set_thread_count(THREADS) server = create_server(app, _dispatcher=dispatcher, host=HOST, port=PORT, threads=THREADS) - print(f'MIND running on http://{HOST}:{PORT}{URL_PREFIX}') + logging.info(f'MIND running on http://{HOST}:{PORT}{URL_PREFIX}') server.run() # Stopping thread diff --git a/backend/custom_exceptions.py b/backend/custom_exceptions.py index 593c676..feee8f8 100644 --- a/backend/custom_exceptions.py +++ b/backend/custom_exceptions.py @@ -1,33 +1,39 @@ #-*- coding: utf-8 -*- +import logging from typing import Any, Dict +class CustomException(Exception): + def __init__(self, e=None) -> None: + logging.warning(self.__doc__) + super().__init__(e) + return -class UsernameTaken(Exception): +class UsernameTaken(CustomException): """The username is already taken""" api_response = {'error': 'UsernameTaken', 'result': {}, 'code': 400} -class UsernameInvalid(Exception): +class UsernameInvalid(CustomException): """The username contains invalid characters""" api_response = {'error': 'UsernameInvalid', 'result': {}, 'code': 400} -class UserNotFound(Exception): +class UserNotFound(CustomException): """The user requested can not be found""" api_response = {'error': 'UserNotFound', 'result': {}, 'code': 404} -class AccessUnauthorized(Exception): +class AccessUnauthorized(CustomException): """The password given is not correct""" api_response = {'error': 'AccessUnauthorized', 'result': {}, 'code': 401} -class ReminderNotFound(Exception): +class ReminderNotFound(CustomException): """The reminder with the id can not be found""" api_response = {'error': 'ReminderNotFound', 'result': {}, 'code': 404} -class NotificationServiceNotFound(Exception): +class NotificationServiceNotFound(CustomException): """The notification service was not found""" api_response = {'error': 'NotificationServiceNotFound', 'result': {}, 'code': 404} -class NotificationServiceInUse(Exception): +class NotificationServiceInUse(CustomException): """The notification service is wished to be deleted but a reminder is still using it""" def __init__(self, type: str=''): self.type = type @@ -37,11 +43,11 @@ class NotificationServiceInUse(Exception): def api_response(self) -> Dict[str, Any]: return {'error': 'NotificationServiceInUse', 'result': {'type': self.type}, 'code': 400} -class InvalidTime(Exception): +class InvalidTime(CustomException): """The time given is in the past""" api_response = {'error': 'InvalidTime', 'result': {}, 'code': 400} -class KeyNotFound(Exception): +class KeyNotFound(CustomException): """A key was not found in the input that is required to be given""" def __init__(self, key: str=''): self.key = key @@ -51,7 +57,7 @@ class KeyNotFound(Exception): def api_response(self) -> Dict[str, Any]: return {'error': 'KeyNotFound', 'result': {'key': self.key}, 'code': 400} -class InvalidKeyValue(Exception): +class InvalidKeyValue(CustomException): """The value of a key is invalid""" def __init__(self, key: str='', value: str=''): self.key = key @@ -62,14 +68,20 @@ class InvalidKeyValue(Exception): def api_response(self) -> Dict[str, Any]: return {'error': 'InvalidKeyValue', 'result': {'key': self.key, 'value': self.value}, 'code': 400} -class TemplateNotFound(Exception): +class TemplateNotFound(CustomException): """The template was not found""" api_response = {'error': 'TemplateNotFound', 'result': {}, 'code': 404} -class APIKeyInvalid(Exception): +class APIKeyInvalid(CustomException): """The API key is not correct""" api_response = {'error': 'APIKeyInvalid', 'result': {}, 'code': 401} + + def __init__(self, e=None) -> None: + return -class APIKeyExpired(Exception): +class APIKeyExpired(CustomException): """The API key has expired""" api_response = {'error': 'APIKeyExpired', 'result': {}, 'code': 401} + + def __init__(self, e=None) -> None: + return diff --git a/backend/db.py b/backend/db.py index 074c768..43bf7a6 100644 --- a/backend/db.py +++ b/backend/db.py @@ -1,6 +1,7 @@ #-*- coding: utf-8 -*- from datetime import datetime +import logging from sqlite3 import Connection, ProgrammingError, Row from threading import current_thread, main_thread from time import time @@ -19,6 +20,7 @@ class Singleton(type): i = f'{cls}{current_thread()}' if (i not in cls._instances or cls._instances[i].closed): + logging.debug(f'Creating singleton instance: {i}') cls._instances[i] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[i] @@ -28,10 +30,12 @@ class ThreadedTaskDispatcher(OldThreadedTaskDispatcher): super().handler_thread(thread_no) i = f'{DBConnection}{current_thread()}' if i in Singleton._instances and not Singleton._instances[i].closed: + logging.debug(f'Closing singleton instance: {i}') Singleton._instances[i].close() def shutdown(self, cancel_pending: bool = True, timeout: int = 5) -> bool: - print('Shutting down MIND...') + print() + logging.info('Shutting down MIND...') super().shutdown(cancel_pending, timeout) DBConnection(20.0).close() @@ -39,12 +43,14 @@ class DBConnection(Connection, metaclass=Singleton): file = '' def __init__(self, timeout: float) -> None: + logging.debug(f'Opening database connection for {current_thread()}') super().__init__(self.file, timeout=timeout) super().cursor().execute("PRAGMA foreign_keys = ON;") self.closed = False return def close(self) -> None: + logging.debug(f'Closing database connection for {current_thread()}') self.closed = True super().close() return @@ -91,7 +97,7 @@ def migrate_db(current_db_version: int) -> None: Migrate a MIND database from it's current version to the newest version supported by the MIND version installed. """ - print('Migrating database to newer version...') + logging.info('Migrating database to newer version...') cursor = get_db() if current_db_version == 1: # V1 -> V2 @@ -289,6 +295,7 @@ def setup_db() -> None: "SELECT value FROM config WHERE key = 'database_version' LIMIT 1;" ).fetchone()[0]) + logging.debug(f'Current database version {current_db_version} and desired database version {__DATABASE_VERSION__}') if current_db_version < __DATABASE_VERSION__: migrate_db(current_db_version) cursor.execute( diff --git a/backend/notification_service.py b/backend/notification_service.py index 9f23938..12f04d3 100644 --- a/backend/notification_service.py +++ b/backend/notification_service.py @@ -1,5 +1,6 @@ #-*- coding: utf-8 -*- +import logging from typing import List from backend.custom_exceptions import (NotificationServiceInUse, @@ -44,6 +45,7 @@ class NotificationService: Returns: dict: The new info about the service """ + logging.info(f'Updating notification service {self.id}: {title=}, {url=}') # Get current data and update it with new values data = self.get() @@ -75,7 +77,9 @@ class NotificationService: Raises: NotificationServiceInUse: The service is still used by a reminder - """ + """ + logging.info(f'Deleting notification service {self.id}') + # Check if no reminders exist with this service cursor = get_db() cursor.execute(""" @@ -164,7 +168,8 @@ class NotificationServices: Returns: dict: The info about the new service - """ + """ + logging.info(f'Adding notification service with {title=}, {url=}') new_id = get_db().execute(""" INSERT INTO notification_services(user_id, title, url) diff --git a/backend/reminders.py b/backend/reminders.py index 62e99e2..4c0fb69 100644 --- a/backend/reminders.py +++ b/backend/reminders.py @@ -1,6 +1,7 @@ #-*- coding: utf-8 -*- from datetime import datetime +import logging from sqlite3 import IntegrityError from threading import Timer from typing import List, Literal @@ -29,7 +30,11 @@ def _find_next_time( current_time = datetime.fromtimestamp(datetime.utcnow().timestamp()) while new_time <= current_time: new_time += td - return int(new_time.timestamp()) + result = int(new_time.timestamp()) + logging.debug( + f'{original_time=}, {current_time=} and interval of {repeat_interval} {repeat_quantity} leads to {result}' + ) + return result class ReminderHandler: """Handle set reminders @@ -82,6 +87,7 @@ class ReminderHandler: "DELETE FROM reminders WHERE id = ?;", (reminder['id'],) ) + logging.info(f'Deleted reminder {reminder["id"]}') else: # Set next time new_time = _find_next_time( @@ -123,8 +129,14 @@ class ReminderHandler: or time < self.next_trigger['time']): if self.next_trigger['thread'] is not None: self.next_trigger['thread'].cancel() + t = time - datetime.utcnow().timestamp() - self.next_trigger['thread'] = Timer(t, self.__trigger_reminders, (time,)) + self.next_trigger['thread'] = Timer( + t, + self.__trigger_reminders, + (time,) + ) + self.next_trigger['thread'].name = "ReminderHandler" self.next_trigger['thread'].start() self.next_trigger['time'] = time @@ -209,6 +221,10 @@ class Reminder: Returns: dict: The new reminder info """ + logging.info( + f'Updating notification service {self.id}: ' + + f'{title=}, {time=}, {notification_services=}, {text=}, {repeat_quantity=}, {repeat_interval=}, {color=}' + ) cursor = get_db() # Validate data @@ -301,7 +317,8 @@ class Reminder: def delete(self) -> None: """Delete the reminder - """ + """ + logging.info(f'Deleting reminder {self.id}') get_db().execute("DELETE FROM reminders WHERE id = ?", (self.id,)) reminder_handler.find_next_reminder() return @@ -410,6 +427,11 @@ class Reminders: Returns: dict: The info about the reminder """ + logging.info( + f'Adding reminder with {title=}, {time=}, {notification_services=}, ' + + f'{text=}, {repeat_quantity=}, {repeat_interval=}, {color=}' + ) + if time < datetime.utcnow().timestamp(): raise InvalidTime time = round(time) @@ -466,6 +488,7 @@ class Reminders: 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 ''. """ + logging.info(f'Testing reminder with {title=}, {notification_services=}, {text=}') a = Apprise() cursor = get_db(dict) for service in notification_services: diff --git a/backend/static_reminders.py b/backend/static_reminders.py index 1f1330a..078de45 100644 --- a/backend/static_reminders.py +++ b/backend/static_reminders.py @@ -1,5 +1,6 @@ #-*- coding: utf-8 -*- +import logging from sqlite3 import IntegrityError from typing import List, Literal @@ -74,7 +75,12 @@ class StaticReminder: Returns: dict: The new static reminder info - """ + """ + logging.info( + f'Updating static reminder {self.id}: ' + + f'{title=}, {notification_services=}, {text=}, {color=}' + ) + # Get current data and update it with new values data = self.get() new_values = { @@ -119,6 +125,7 @@ class StaticReminder: def delete(self) -> None: """Delete the static reminder """ + logging.info(f'Deleting static reminder {self.id}') get_db().execute("DELETE FROM static_reminders WHERE id = ?", (self.id,)) return @@ -218,6 +225,10 @@ class StaticReminders: Returns: StaticReminder: A StaticReminder instance representing the newly created static reminder """ + logging.info( + f'Adding static reminder with {title=}, {notification_services=}, {text=}, {color=}' + ) + cursor = get_db() id = cursor.execute(""" INSERT INTO static_reminders(user_id, title, text, color) @@ -245,6 +256,7 @@ class StaticReminders: Raises: ReminderNotFound: The static reminder with the given id was not found """ + logging.info(f'Triggering static reminder {self.id}') cursor = get_db(dict) reminder = cursor.execute(""" SELECT title, text diff --git a/backend/templates.py b/backend/templates.py index 84749ea..177a037 100644 --- a/backend/templates.py +++ b/backend/templates.py @@ -1,5 +1,6 @@ #-*- coding: utf-8 -*- +import logging from sqlite3 import IntegrityError from typing import List, Literal @@ -69,6 +70,11 @@ class Template: Returns: dict: The new template info """ + logging.info( + f'Updating template {self.id}: ' + + f'{title=}, {notification_services=}, {text=}, {color=}' + ) + cursor = get_db() data = self.get() @@ -111,6 +117,7 @@ class Template: def delete(self) -> None: """Delete the template """ + logging.info(f'Deleting template {self.id}') get_db().execute("DELETE FROM templates WHERE id = ?;", (self.id,)) return @@ -203,7 +210,11 @@ class Templates: Returns: Template: The info about the template - """ + """ + logging.info( + f'Adding template with {title=}, {notification_services=}, {text=}, {color=}' + ) + cursor = get_db() id = cursor.execute(""" INSERT INTO templates(user_id, title, text, color) diff --git a/backend/users.py b/backend/users.py index 6b45518..9355bb3 100644 --- a/backend/users.py +++ b/backend/users.py @@ -1,5 +1,6 @@ #-*- coding: utf-8 -*- +import logging from backend.custom_exceptions import (AccessUnauthorized, UsernameInvalid, UsernameTaken, UserNotFound) from backend.db import get_db @@ -27,7 +28,7 @@ class User: self.salt = result['salt'] self.user_id = result['id'] - # check password + # Check password hash_password = get_hash(result['salt'], password) if not hash_password == result['hash']: raise AccessUnauthorized @@ -90,11 +91,14 @@ class User: "UPDATE users SET hash = ? WHERE id = ?", (hash_password, self.user_id) ) + logging.info(f'The user {self.username} ({self.user_id}) changed their password') return def delete(self) -> None: """Delete the user account - """ + """ + logging.info(f'Deleting the user {self.username} ({self.user_id})') + cursor = get_db() cursor.execute("DELETE FROM reminders WHERE user_id = ?", (self.user_id,)) cursor.execute("DELETE FROM templates WHERE user_id = ?", (self.user_id,)) @@ -111,7 +115,8 @@ def _check_username(username: str) -> None: Raises: UsernameInvalid: The username is not valid - """ + """ + logging.debug(f'Checking the username {username}') if username in ONEPASS_INVALID_USERNAMES or username.isdigit(): raise UsernameInvalid if list(filter(lambda c: not c in ONEPASS_USERNAME_CHARACTERS, username)): @@ -132,6 +137,8 @@ def register_user(username: str, password: str) -> int: Returns: user_id (int): The id of the new user. User registered successful """ + logging.info(f'Registering user with username {username}') + # Check if username is valid _check_username(username) @@ -156,4 +163,5 @@ def register_user(username: str, password: str) -> int: (username, salt, hashed_password) ).lastrowid + logging.debug(f'Newly registered user has id {user_id}') return user_id From b73ac3bcde86dc980cf6d9caad552d5c27cfbd71 Mon Sep 17 00:00:00 2001 From: CasVT Date: Tue, 4 Jul 2023 13:52:16 +0200 Subject: [PATCH 45/46] Fixed exceptions test after adding logging --- tests/custom_exceptions_test.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/custom_exceptions_test.py b/tests/custom_exceptions_test.py index 66a97cb..88388da 100644 --- a/tests/custom_exceptions_test.py +++ b/tests/custom_exceptions_test.py @@ -7,9 +7,19 @@ import backend.custom_exceptions class Test_Custom_Exceptions(unittest.TestCase): def test_type(self): - defined_exceptions: List[Exception] = filter(lambda c: c.__module__ == 'backend.custom_exceptions', map(lambda c: c[1], getmembers(modules['backend.custom_exceptions'], isclass))) + defined_exceptions: List[Exception] = filter( + lambda c: c.__module__ == 'backend.custom_exceptions' and c is not backend.custom_exceptions.CustomException, + map( + lambda c: c[1], + getmembers(modules['backend.custom_exceptions'], isclass) + ) + ) + for defined_exception in defined_exceptions: - self.assertEqual(getmro(defined_exception)[1], Exception) + self.assertEqual( + getmro(defined_exception)[1], + backend.custom_exceptions.CustomException + ) result = defined_exception().api_response self.assertIsInstance(result, dict) result['error'] From 54791596e0ab903c1a35326192cc7f6543538f7a Mon Sep 17 00:00:00 2001 From: CasVT Date: Tue, 4 Jul 2023 15:24:57 +0200 Subject: [PATCH 46/46] Fixed add notification service button I accidentally committed half of the code for the new URL builder a long time ago, breaking the old method of adding a notification service. Making the URL builder is taking a lot longer than expected, so instead of leaving it broken until I complete the URL builder and then making a release, I've decided to fix it to the old way first, make a release and then include the URL builder in the next release. This means that currently there is a lot of JS code that is not being used or is commented out, because it's part of the URL builder that is coming. --- frontend/static/js/notification.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/static/js/notification.js b/frontend/static/js/notification.js index 1c5af6d..272937b 100644 --- a/frontend/static/js/notification.js +++ b/frontend/static/js/notification.js @@ -26,7 +26,8 @@ function fillNotificationSelection() { inputs.notification_service.querySelector(':first-child input').checked = true; const table = document.getElementById('services-list'); - table.innerHTML = ''; + table.querySelectorAll('tr:not(#add-row)').forEach(e => e.remove()); + // table.innerHTML = ''; json.result.forEach(service => { const entry = document.createElement('tr'); entry.dataset.id = service.id; @@ -151,6 +152,9 @@ function deleteService(id) { }; function toggleAddService() { + document.getElementById('add-row').classList.toggle('hidden'); + return; + const cont = document.querySelector('.overflow-container'); if (cont.classList.contains('show-add')) { // Hide add @@ -298,11 +302,16 @@ function buildAppriseURL() { }; function addService() { - const add_button = document.querySelector('#add-service-window > .options > button[type="submit"]'); + const add_button = document.querySelector('#add-row > .action-column > button'); const data = { - 'title': document.querySelector('#service-title').value, - 'url': buildAppriseURL() + 'title': document.querySelector('#add-row > .title-column > input').value, + 'url': document.querySelector('#add-row > .url-column > input').value }; + // const add_button = document.querySelector('#add-service-window > .options > button[type="submit"]'); + // const data = { + // 'title': document.querySelector('#service-title').value, + // 'url': buildAppriseURL() + // }; fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`, { 'method': 'POST', 'headers': {'Content-Type': 'application/json'}, @@ -321,7 +330,8 @@ function addService() { if (e === 401) window.location.href = `${url_prefix}/`; else if (e === 400) { - add_button.classList.add('error-input'); + // add_button.classList.add('error-input'); + add_button.classList.add('error-icon'); add_button.title = 'Invalid Apprise URL'; } else console.log(e); @@ -335,5 +345,6 @@ fillNotificationSelection(); let notification_services = null; document.getElementById('add-service-button').addEventListener('click', e => toggleAddService()); -document.querySelector('#service-list button').addEventListener('click', e => showAddServiceWindow(-1)); -document.getElementById('add-service-window').setAttribute('action', 'javascript:addService();'); +// document.querySelector('#service-list button').addEventListener('click', e => showAddServiceWindow(-1)); +// document.getElementById('add-service-window').setAttribute('action', 'javascript:addService();'); +document.querySelector('#add-row > .action-column > button').addEventListener('click', e => addService());