diff --git a/MIND.py b/MIND.py index 4b5ee14..c2db3e5 100644 --- a/MIND.py +++ b/MIND.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 #-*- coding: utf-8 -*- +import logging from os import makedirs, urandom -from os.path import abspath, dirname, join, isfile +from os.path import abspath, dirname, isfile, join from shutil import move from sys import version_info -from flask import Flask, render_template, request +from flask import Flask from waitress.server import create_server +from werkzeug.middleware.dispatcher import DispatcherMiddleware from backend.db import DBConnection, close_db, setup_db from frontend.api import api, reminder_handler @@ -15,6 +17,7 @@ from frontend.ui import ui HOST = '0.0.0.0' PORT = '8080' +URL_PREFIX = '' # Must start with '/' e.g. '/mind' THREADS = 10 DB_FILENAME = 'db', 'MIND.db' @@ -39,15 +42,10 @@ def _create_app() -> Flask: app.config['SECRET_KEY'] = urandom(32) app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True app.config['JSON_SORT_KEYS'] = False + app.config['APPLICATION_ROOT'] = URL_PREFIX + app.wsgi_app = DispatcherMiddleware(Flask(__name__), {URL_PREFIX: app.wsgi_app}) # Add error handlers - @app.errorhandler(404) - def not_found(e): - if request.path.startswith('/api'): - return {'error': 'Not Found', 'result': {}}, 404 - else: - return render_template('page_not_found.html') - @app.errorhandler(400) def bad_request(e): return {'error': 'Bad request', 'result': {}}, 400 @@ -78,6 +76,10 @@ def MIND() -> None: print('Error: the minimum python version required is python3.7 (currently ' + version_info.major + '.' + version_info.minor + '.' + version_info.micro + ')') # Register web server + # We need to get the value to ui.py but MIND.py imports from ui.py so we get an import loop. + # To go around this, we abuse the fact that the logging module is a singleton. + # We add an attribute to the logging module and in ui.py get the value this way. + logging.URL_PREFIX = URL_PREFIX app = _create_app() with app.app_context(): if isfile(_folder_path('db', 'Noted.db')): @@ -93,7 +95,7 @@ def MIND() -> None: # Create waitress server and run server = create_server(app, host=HOST, port=PORT, threads=THREADS) - print(f'MIND running on http://{HOST}:{PORT}/') + print(f'MIND running on http://{HOST}:{PORT}{URL_PREFIX}') server.run() print(f'\nShutting down MIND...') diff --git a/frontend/api.py b/frontend/api.py index ba977e3..813e340 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -120,6 +120,10 @@ def extract_key(values: dict, key: str, check_existence: bool=True) -> Any: return value +@api.errorhandler(404) +def not_found(e): + return return_api({}, 'Not Found', 404) + #=================== # Authentication endpoints #=================== diff --git a/frontend/static/js/add.js b/frontend/static/js/add.js index 6d1d668..58d9c4b 100644 --- a/frontend/static/js/add.js +++ b/frontend/static/js/add.js @@ -35,7 +35,7 @@ function addReminder() { data['repeat_interval'] = type_buttons['repeat-interval'].value }; - fetch(`/api/reminders?api_key=${api_key}`, { + fetch(`${url_prefix}/api/reminders?api_key=${api_key}`, { 'method': 'POST', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -52,7 +52,7 @@ function addReminder() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else if (e === 400) { inputs.time.classList.add('error-input'); inputs.time.title = 'Time is in the past'; @@ -144,7 +144,7 @@ function testReminder() { 'notification_service': inputs.notification_service.value, 'text': inputs.text.value }; - fetch(`/api/reminders/test?api_key=${api_key}`, { + fetch(`${url_prefix}/api/reminders/test?api_key=${api_key}`, { 'method': 'POST', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -158,7 +158,7 @@ function testReminder() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; }; }); }; diff --git a/frontend/static/js/edit.js b/frontend/static/js/edit.js index 9b0fb30..42ad2e3 100644 --- a/frontend/static/js/edit.js +++ b/frontend/static/js/edit.js @@ -34,7 +34,7 @@ function editReminder() { data['repeat_interval'] = edit_type_buttons['repeat-edit-interval'].value; }; - fetch(`/api/reminders/${id}?api_key=${api_key}`, { + fetch(`${url_prefix}/api/reminders/${id}?api_key=${api_key}`, { 'method': 'PUT', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -51,7 +51,7 @@ function editReminder() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -60,7 +60,7 @@ function editReminder() { function showEdit(id) { document.getElementById('edit-form').dataset.id = id; - fetch(`/api/reminders/${id}?api_key=${api_key}`) + fetch(`${url_prefix}/api/reminders/${id}?api_key=${api_key}`) .then(response => { // catch errors if (!response.ok) { @@ -99,7 +99,7 @@ function showEdit(id) { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else if (e === 404) { fillList(); } else { @@ -127,7 +127,7 @@ function toggleEditRepeated() { function deleteReminder() { const id = document.getElementById('edit-form').dataset.id; - fetch(`/api/reminders/${id}?api_key=${api_key}`, { + fetch(`${url_prefix}/api/reminders/${id}?api_key=${api_key}`, { 'method': 'DELETE' }) .then(response => { @@ -142,7 +142,7 @@ function deleteReminder() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else if (e === 404) { fillList(); } else { diff --git a/frontend/static/js/general.js b/frontend/static/js/general.js index b410fb9..244724e 100644 --- a/frontend/static/js/general.js +++ b/frontend/static/js/general.js @@ -1,10 +1,10 @@ function logout() { - fetch(`/api/auth/logout?api_key=${api_key}`, { + fetch(`${url_prefix}/api/auth/logout?api_key=${api_key}`, { 'method': 'POST' }) .then(response => { sessionStorage.removeItem('api_key'); - window.location.href = '/'; + window.location.href = url_prefix; }); }; @@ -49,9 +49,10 @@ function showTab(tab_id, button_id, load_function=null) { // code run on load +const url_prefix = document.getElementById('url_prefix').dataset.value; const api_key = sessionStorage.getItem('api_key'); if (api_key === null) { - window.location.href = '/'; + window.location.href = url_prefix; }; document.getElementById('toggle-nav').addEventListener('click', e => toggleNav()); diff --git a/frontend/static/js/login.js b/frontend/static/js/login.js index a635bdf..8992c33 100644 --- a/frontend/static/js/login.js +++ b/frontend/static/js/login.js @@ -10,7 +10,7 @@ function login(data=null) { 'password': document.getElementById('password-input').value }; }; - fetch(`/api/auth/login`, { + fetch(`${url_prefix}/api/auth/login`, { 'method': 'POST', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -25,7 +25,7 @@ function login(data=null) { }) .then(json => { sessionStorage.setItem('api_key', json.result.api_key); - window.location.href = '/reminders'; + window.location.href = `${url_prefix}/reminders`; }) .catch(e => { if (e === 401) { @@ -49,7 +49,7 @@ function create() { 'username': document.getElementById('new-username-input').value, 'password': document.getElementById('new-password-input').value }; - fetch(`/api/user/add`, { + fetch(`${url_prefix}/api/user/add`, { 'method': 'POST', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -81,6 +81,8 @@ function toggleWindow() { // code run on load +const url_prefix = document.getElementById('url_prefix').dataset.value; + document.getElementById('login-form').setAttribute('action', 'javascript:login();'); document.getElementById('create-form').setAttribute('action', 'javascript:create();'); document.querySelectorAll('.switch-button').forEach(e => e.addEventListener('click', e => toggleWindow())); diff --git a/frontend/static/js/notification.js b/frontend/static/js/notification.js index 9596eab..f6b6cc0 100644 --- a/frontend/static/js/notification.js +++ b/frontend/static/js/notification.js @@ -1,5 +1,5 @@ function fillNotificationSelection() { - fetch(`/api/notificationservices?api_key=${api_key}`) + fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`) .then(response => { // catch errors if (!response.ok) { @@ -87,7 +87,7 @@ function fillNotificationSelection() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -96,7 +96,7 @@ function fillNotificationSelection() { function deleteService(id) { const row = document.querySelector(`tr[data-id="${id}"]`); - fetch(`/api/notificationservices/${id}?api_key=${api_key}`, { + fetch(`${url_prefix}/api/notificationservices/${id}?api_key=${api_key}`, { 'method': 'DELETE' }) .then(response => response.json()) @@ -113,7 +113,7 @@ function deleteService(id) { }) .catch(e => { if (e.error === 'ApiKeyExpired' || e.error === 'ApiKeyInvalid') { - window.location.href = '/'; + window.location.href = url_prefix; } else if (e.error === 'NotificationServiceInUse') { const delete_button = row.querySelector('button[title="Delete"]'); delete_button.classList.add('error-icon'); @@ -136,7 +136,7 @@ function saveService(id) { 'title': row.querySelector(`td.title-column > input`).value, 'url': row.querySelector(`td.url-column > input`).value }; - fetch(`/api/notificationservices/${id}?api_key=${api_key}`, { + fetch(`${url_prefix}/api/notificationservices/${id}?api_key=${api_key}`, { 'method': 'PUT', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -151,7 +151,7 @@ function saveService(id) { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else if (e === 400) { save_button.classList.add('error-icon'); save_button.title = 'Invalid Apprise URL'; @@ -176,7 +176,7 @@ function addService() { 'title': inputs_buttons.title.value, 'url': inputs_buttons.url.value }; - fetch(`/api/notificationservices?api_key=${api_key}`, { + fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`, { 'method': 'POST', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -199,7 +199,7 @@ function addService() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + 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'; diff --git a/frontend/static/js/reminders.js b/frontend/static/js/reminders.js index 9399fc0..54af9c7 100644 --- a/frontend/static/js/reminders.js +++ b/frontend/static/js/reminders.js @@ -42,7 +42,7 @@ function fillTable(result) { }; function fillList() { - fetch(`/api/reminders?api_key=${api_key}`) + fetch(`${url_prefix}/api/reminders?api_key=${api_key}`) .then(response => { // catch errors if (!response.ok) { @@ -55,7 +55,7 @@ function fillList() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -64,7 +64,7 @@ function fillList() { function search() { const query = document.getElementById('search-input').value; - fetch(`/api/reminders/search?api_key=${api_key}&query=${query}`) + fetch(`${url_prefix}/api/reminders/search?api_key=${api_key}&query=${query}`) .then(response => { // catch errors if (!response.ok) { @@ -77,7 +77,7 @@ function search() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; diff --git a/frontend/static/js/settings.js b/frontend/static/js/settings.js index 0ba0e73..9065cc5 100644 --- a/frontend/static/js/settings.js +++ b/frontend/static/js/settings.js @@ -2,7 +2,7 @@ function changePassword() { const data = { 'new_password': document.getElementById('password-input').value }; - fetch(`/api/user?api_key=${api_key}`, { + fetch(`${url_prefix}/api/user?api_key=${api_key}`, { 'method': 'PUT', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -16,7 +16,7 @@ function changePassword() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -24,11 +24,11 @@ function changePassword() { }; function deleteAccount() { - fetch(`/api/user?api_key=${api_key}`, { + fetch(`${url_prefix}/api/user?api_key=${api_key}`, { 'method': 'DELETE' }) .then(response => { - window.location.href = '/'; + window.location.href = url_prefix; }); }; diff --git a/frontend/static/js/templates.js b/frontend/static/js/templates.js index c5993d0..b7ff021 100644 --- a/frontend/static/js/templates.js +++ b/frontend/static/js/templates.js @@ -18,7 +18,7 @@ function loadTemplates(force=true) { return }; - fetch(`/api/templates?api_key=${api_key}`) + fetch(`${url_prefix}/api/templates?api_key=${api_key}`) .then(response => { // catch errors if (!response.ok) { @@ -57,7 +57,7 @@ function loadTemplates(force=true) { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -74,7 +74,7 @@ function loadTemplate() { toggleColor(inputs.color); }; } else { - fetch(`/api/templates/${id}?api_key=${api_key}`) + fetch(`${url_prefix}/api/templates/${id}?api_key=${api_key}`) .then(response => { // catch errors if (!response.ok) { @@ -99,7 +99,7 @@ function loadTemplate() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -117,7 +117,7 @@ function addTemplate() { if (!template_inputs.color.classList.contains('hidden')) { data['color'] = template_inputs.color.querySelector('button[data-selected="true"]').dataset.color; }; - fetch(`/api/templates?api_key=${api_key}`, { + fetch(`${url_prefix}/api/templates?api_key=${api_key}`, { 'method': 'POST', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -134,7 +134,7 @@ function addTemplate() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -154,7 +154,7 @@ function closeAddTemplate() { }; function showEditTemplate(id) { - fetch(`/api/templates/${id}?api_key=${api_key}`) + fetch(`${url_prefix}/api/templates/${id}?api_key=${api_key}`) .then(response => { // catch errors if (!response.ok) { @@ -177,7 +177,7 @@ function showEditTemplate(id) { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -195,7 +195,7 @@ function saveTemplate() { if (!edit_template_inputs.color.classList.contains('hidden')) { data['color'] = edit_template_inputs.color.querySelector('button[data-selected="true"]').dataset.color; }; - fetch(`/api/templates/${id}?api_key=${api_key}`, { + fetch(`${url_prefix}/api/templates/${id}?api_key=${api_key}`, { 'method': 'PUT', 'headers': {'Content-Type': 'application/json'}, 'body': JSON.stringify(data) @@ -210,7 +210,7 @@ function saveTemplate() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; @@ -219,7 +219,7 @@ function saveTemplate() { function deleteTemplate() { const id = document.getElementById('template-edit-form').dataset.id; - fetch(`/api/templates/${id}?api_key=${api_key}`, { + fetch(`${url_prefix}/api/templates/${id}?api_key=${api_key}`, { 'method': 'DELETE' }) .then(response => { @@ -233,7 +233,7 @@ function deleteTemplate() { }) .catch(e => { if (e === 401) { - window.location.href = '/'; + window.location.href = url_prefix; } else { console.log(e); }; diff --git a/frontend/templates/login.html b/frontend/templates/login.html index 5b8da9b..ca6385a 100644 --- a/frontend/templates/login.html +++ b/frontend/templates/login.html @@ -4,10 +4,11 @@ + - - - + + + Login - MIND diff --git a/frontend/templates/page_not_found.html b/frontend/templates/page_not_found.html index aa1c51f..202755a 100644 --- a/frontend/templates/page_not_found.html +++ b/frontend/templates/page_not_found.html @@ -17,7 +17,7 @@

MIND

404 - Page not found :(

- Go to home page + Go to home page
\ No newline at end of file diff --git a/frontend/templates/reminders.html b/frontend/templates/reminders.html index 0d63ba1..ea5f499 100644 --- a/frontend/templates/reminders.html +++ b/frontend/templates/reminders.html @@ -4,19 +4,20 @@ + - - - - - - - - - - - - + + + + + + + + + + + + Reminders - MIND diff --git a/frontend/ui.py b/frontend/ui.py index 562da9c..113ae20 100644 --- a/frontend/ui.py +++ b/frontend/ui.py @@ -1,15 +1,20 @@ #-*- coding: utf-8 -*- +import logging from flask import Blueprint, render_template ui = Blueprint('ui', __name__) methods = ['GET'] +@ui.errorhandler(404) +def not_found(e): + return render_template('page_not_found.html', url_prefix=logging.URL_PREFIX) + @ui.route('/', methods=methods) def ui_login(): - return render_template('login.html') + return render_template('login.html', url_prefix=logging.URL_PREFIX) @ui.route('/reminders', methods=methods) def ui_reminders(): - return render_template('reminders.html') + return render_template('reminders.html', url_prefix=logging.URL_PREFIX) diff --git a/tests/Noted_test.py b/tests/MIND_test.py similarity index 92% rename from tests/Noted_test.py rename to tests/MIND_test.py index 11442af..4e0e084 100644 --- a/tests/Noted_test.py +++ b/tests/MIND_test.py @@ -15,6 +15,6 @@ class Test_MIND(unittest.TestCase): self.assertEqual(result.blueprints.get('api'), api) handlers = result.error_handler_spec[None].keys() - required_handlers = 404, 400, 405, 500 + required_handlers = 400, 405, 500 for handler in required_handlers: self.assertIn(handler, handlers)