mirror of
https://github.com/Casvt/MIND.git
synced 2026-04-03 03:00:22 -04:00
Added logging
This commit is contained in:
16
MIND.py
16
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user