Merge pull request #42 from Casvt:Casvt/issue38

Fixes issue #38
This commit is contained in:
Casvt
2023-03-12 21:47:10 +01:00
committed by GitHub
15 changed files with 89 additions and 73 deletions

22
MIND.py
View File

@@ -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...')

View File

@@ -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
#===================

View File

@@ -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;
};
});
};

View File

@@ -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 {

View File

@@ -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());

View File

@@ -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()));

View File

@@ -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';

View File

@@ -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);
};

View File

@@ -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;
});
};

View File

@@ -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);
};

View File

@@ -4,10 +4,11 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta id="url_prefix" data-value="{{url_prefix}}">
<link rel="stylesheet" href="/static/css/general.css">
<link rel="stylesheet" href="/static/css/login.css">
<script src="/static/js/login.js" defer></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/general.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
<title>Login - MIND</title>
</head>

View File

@@ -17,7 +17,7 @@
<main>
<h2>MIND</h1>
<p>404 - Page not found :(</p>
<a href="/">Go to home page</a>
<a href="{{url_prefix}}">Go to home page</a>
</main>
</body>
</html>

View File

@@ -4,19 +4,20 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta id="url_prefix" data-value="{{url_prefix}}">
<link rel="stylesheet" href="/static/css/general.css">
<link rel="stylesheet" href="/static/css/reminders_templates.css">
<link rel="stylesheet" href="/static/css/add_edit.css">
<link rel="stylesheet" href="/static/css/notification.css">
<link rel="stylesheet" href="/static/css/settings.css">
<script src="/static/js/general.js" defer></script>
<script src="/static/js/reminders.js" defer></script>
<script src="/static/js/add.js" defer></script>
<script src="/static/js/edit.js" defer></script>
<script src="/static/js/notification.js" defer></script>
<script src="/static/js/settings.js" defer></script>
<script src="/static/js/templates.js" defer></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/general.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/reminders_templates.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/add_edit.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/notification.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
<script src="{{ url_for('static', filename='js/general.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/reminders.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/add.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/edit.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/notification.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/settings.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/templates.js') }}" defer></script>
<title>Reminders - MIND</title>
</head>

View File

@@ -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)

View File

@@ -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)