Compare commits

..

No commits in common. "5ec2a86d086253d89b2b2bf29ff0faac9ff6ba21" and "d9837246dedc5ea1817f34b63860a45a70219fda" have entirely different histories.

22 changed files with 99 additions and 85 deletions

View File

@ -12,7 +12,6 @@ DATABASE_PORT= # Required if MySQL. Defaults to 3306
## MySQL Database Configuration (Required if configured to MySQL Database.) ## MySQL Database Configuration (Required if configured to MySQL Database.)
# Note that if using the Docker service, these configuration values will also be used when creating the database in the mysql container. # Note that if using the Docker service, these configuration values will also be used when creating the database in the mysql container.
MYSQL_RANDOM_ROOT_PASSWORD=True
MYSQL_DATABASE= # Required if MySQL. MYSQL_DATABASE= # Required if MySQL.
MYSQL_USER= # Required if MySQL MYSQL_USER= # Required if MySQL
MYSQL_PASSWORD= # Required if MySQL. Create secure password string. Note '@' character cannot be used. MYSQL_PASSWORD= # Required if MySQL. Create secure password string. Note '@' character cannot be used.

9
.gitignore vendored
View File

@ -155,11 +155,4 @@ database/data/
**/data/* **/data/*
# Ignore Logs Dir # Ignore Logs Dir
logs/* logs/*
# Ignore Certbot Dir
certbot/*
# Ignore src dir (exception for robots.txt)
src/html/*
src/html/robots.txt

2
certbot/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -10,7 +10,6 @@ services:
volumes: volumes:
- ./certbot:/etc/letsencrypt:ro - ./certbot:/etc/letsencrypt:ro
- ./nginx:/etc/nginx - ./nginx:/etc/nginx
- ./src/html/certbot:/usr/share/nginx/html/certbot:ro
- ./src/html/robots.txt:/usr/share/nginx/html/robots.txt:ro - ./src/html/robots.txt:/usr/share/nginx/html/robots.txt:ro
- ./ref-test/app/root:/usr/share/nginx/html/root:ro - ./ref-test/app/root:/usr/share/nginx/html/root:ro
- ./ref-test/app/admin/static:/usr/share/nginx/html/admin/static:ro - ./ref-test/app/admin/static:/usr/share/nginx/html/admin/static:ro
@ -61,7 +60,7 @@ services:
image: certbot/certbot image: certbot/certbot
volumes: volumes:
- ./certbot:/etc/letsencrypt - ./certbot:/etc/letsencrypt
- ./src/html/certbot:/var/www/html - ./src/html:/var/www/html
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
networks: networks:

View File

@ -1,6 +1,6 @@
# Certbot Renewal # Certbot Renewal
location ^~ /.well-known/acme-challenge/ { location ^~ /.well-known/acme-challenge/ {
root /usr/share/nginx/html/certbot; root /usr/share/nginx/html;
allow all; allow all;
default_type "text/plain"; default_type "text/plain";
} }

View File

@ -1,3 +1,2 @@
env/ env/
__pycache__/ __pycache__/
data/

View File

@ -7,6 +7,7 @@ from flask import flash, Flask, render_template, request
from flask.helpers import abort, url_for from flask.helpers import abort, url_for
from flask.json import jsonify from flask.json import jsonify
from flask_wtf.csrf import CSRFError from flask_wtf.csrf import CSRFError
from sqlalchemy.exc import SQLAlchemyError
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
from datetime import datetime from datetime import datetime
@ -26,7 +27,7 @@ def create_app():
@login_manager.user_loader @login_manager.user_loader
def _load_user(id): def _load_user(id):
try: return User.query.filter_by(id=id).first() try: return User.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when loading user fo login manager: {exception}') write('system.log', f'Database error when loading user fo login manager: {exception}')
return abort(500) return abort(500)

View File

@ -9,6 +9,7 @@ from ..tools.test import answer_options, get_correct_answers
from flask import abort, Blueprint, jsonify, render_template, request, send_file, session from flask import abort, Blueprint, jsonify, render_template, request, send_file, session
from flask.helpers import abort, flash, redirect, url_for from flask.helpers import abort, flash, redirect, url_for
from flask_login import current_user, login_required from flask_login import current_user, login_required
from sqlalchemy.exc import SQLAlchemyError
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from json import loads from json import loads
@ -30,7 +31,7 @@ def _home():
try: try:
tests = Test.query.all() tests = Test.query.all()
results = Entry.query.all() results = Entry.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
current_tests = [ test for test in tests if test.end_date >= datetime.now() and test.start_date.date() <= date.today() ] current_tests = [ test for test in tests if test.end_date >= datetime.now() and test.start_date.date() <= date.today() ]
@ -47,7 +48,7 @@ def _settings():
try: try:
users = User.query.all() users = User.query.all()
datasets = Dataset.query.all() datasets = Dataset.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
return render_template('/admin/settings/index.html', users=users, datasets=datasets) return render_template('/admin/settings/index.html', users=users, datasets=datasets)
@ -60,7 +61,7 @@ def _login():
if request.method == 'POST': if request.method == 'POST':
if form.validate_on_submit(): if form.validate_on_submit():
try: users = User.query.all() try: users = User.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
user = None user = None
@ -112,7 +113,7 @@ def _reset():
if form.validate_on_submit(): if form.validate_on_submit():
user = None user = None
try: users = User.query.all() try: users = User.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
for _user in users: for _user in users:
@ -127,7 +128,7 @@ def _reset():
token = request.args.get('token') token = request.args.get('token')
if token: if token:
try: user = User.query.filter_by(reset_token=token).first() try: user = User.query.filter_by(reset_token=token).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not user: return redirect(url_for('admin._reset')) if not user: return redirect(url_for('admin._reset'))
@ -147,7 +148,7 @@ def _update_password():
if form.validate_on_submit(): if form.validate_on_submit():
user = session.pop('user') user = session.pop('user')
try: user = User.query.filter_by(id=user).first() try: user = User.query.filter_by(id=user).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
user.update(password=request.form.get('password')) user.update(password=request.form.get('password'))
@ -161,7 +162,7 @@ def _update_password():
def _users(): def _users():
form = CreateUser() form = CreateUser()
try: users = User.query.all() try: users = User.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if request.method == 'POST': if request.method == 'POST':
@ -181,7 +182,7 @@ def _users():
@login_required @login_required
def _delete_user(id:str): def _delete_user(id:str):
try: user = User.query.filter_by(id=id).first() try: user = User.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
form = DeleteUser() form = DeleteUser()
@ -208,7 +209,7 @@ def _delete_user(id:str):
@login_required @login_required
def _update_user(id:str): def _update_user(id:str):
try: user = User.query.filter_by(id=id).first() try: user = User.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
form = UpdateUser() form = UpdateUser()
@ -253,7 +254,7 @@ def _questions():
return send_errors_to_client(form=form) return send_errors_to_client(form=form)
try: data = Dataset.query.all() try: data = Dataset.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
return render_template('/admin/settings/questions.html', form=form, data=data) return render_template('/admin/settings/questions.html', form=form, data=data)
@ -265,7 +266,7 @@ def _edit_questions():
action = request.get_json()['action'] action = request.get_json()['action']
if not action == 'delete': return jsonify({'error': 'Invalid action.'}), 400 if not action == 'delete': return jsonify({'error': 'Invalid action.'}), 400
try: dataset = Dataset.query.filter_by(id=id).first() try: dataset = Dataset.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if action == 'delete': success, message = dataset.delete() if action == 'delete': success, message = dataset.delete()
@ -276,7 +277,7 @@ def _edit_questions():
@login_required @login_required
def _download(id:str): def _download(id:str):
try: dataset = Dataset.query.filter_by(id=id).first() try: dataset = Dataset.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not dataset: return abort(404) if not dataset: return abort(404)
@ -290,7 +291,7 @@ def _download(id:str):
def _tests(filter:str=None): def _tests(filter:str=None):
tests = None tests = None
try: _tests = Test.query.all() try: _tests = Test.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
form = None form = None
@ -339,7 +340,7 @@ def _create_test():
new_test.time_limit = None if request.form.get('time_limit') == 'none' else int(request.form.get('time_limit')) new_test.time_limit = None if request.form.get('time_limit') == 'none' else int(request.form.get('time_limit'))
dataset = request.form.get('dataset') dataset = request.form.get('dataset')
try: new_test.dataset = Dataset.query.filter_by(id=dataset).first() try: new_test.dataset = Dataset.query.filter_by(id=dataset).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
success, message = new_test.create() success, message = new_test.create()
@ -356,7 +357,7 @@ def _edit_test():
action = request.get_json()['action'] action = request.get_json()['action']
if action not in ['start', 'delete', 'end']: return jsonify({'error': 'Invalid action.'}), 400 if action not in ['start', 'delete', 'end']: return jsonify({'error': 'Invalid action.'}), 400
try: test = Test.query.filter_by(id=id).first() try: test = Test.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not test: return jsonify({'error': 'Could not find the corresponding test to delete.'}), 404 if not test: return jsonify({'error': 'Could not find the corresponding test to delete.'}), 404
@ -373,7 +374,7 @@ def _edit_test():
def _view_test(id:str=None): def _view_test(id:str=None):
form = AddTimeAdjustment() form = AddTimeAdjustment()
try: test = Test.query.filter_by(id=id).first() try: test = Test.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if request.method == 'POST': if request.method == 'POST':
@ -393,7 +394,7 @@ def _view_test(id:str=None):
@login_required @login_required
def _delete_adjustment(id:str=None): def _delete_adjustment(id:str=None):
try: test = Test.query.filter_by(id=id).first() try: test = Test.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not test: return jsonify({'error': 'Invalid test ID.'}), 404 if not test: return jsonify({'error': 'Invalid test ID.'}), 404
@ -406,7 +407,7 @@ def _delete_adjustment(id:str=None):
@login_required @login_required
def _view_entries(): def _view_entries():
try: entries = Entry.query.all() try: entries = Entry.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
return render_template('/admin/results.html', entries = entries) return render_template('/admin/results.html', entries = entries)
@ -415,7 +416,7 @@ def _view_entries():
@login_required @login_required
def _view_entry(id:str=None): def _view_entry(id:str=None):
try: entry = Entry.query.filter_by(id=id).first() try: entry = Entry.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if request.method == 'POST': if request.method == 'POST':
@ -449,7 +450,7 @@ def _generate_certificate():
from ..extensions import db from ..extensions import db
id = request.get_json()['id'] id = request.get_json()['id']
try: entry = Entry.query.filter_by(id=id).first() try: entry = Entry.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not entry: return jsonify({'error': 'Invalid entry ID.'}), 404 if not entry: return jsonify({'error': 'Invalid entry ID.'}), 404

View File

@ -6,6 +6,7 @@ from ..tools.test import evaluate_answers, generate_questions
from flask import Blueprint, jsonify, request from flask import Blueprint, jsonify, request
from flask.helpers import abort, flash, url_for from flask.helpers import abort, flash, url_for
from flask_login import login_required from flask_login import login_required
from sqlalchemy.exc import SQLAlchemyError
from datetime import datetime, timedelta from datetime import datetime, timedelta
from json import loads from json import loads
@ -19,7 +20,7 @@ api = Blueprint(
def _fetch_questions(): def _fetch_questions():
id = request.get_json()['id'] id = request.get_json()['id']
try: entry = Entry.query.filter_by(id=id).first() try: entry = Entry.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not entry: return jsonify({'error': 'Invalid entry ID.'}), 400 if not entry: return jsonify({'error': 'Invalid entry ID.'}), 400
@ -56,7 +57,7 @@ def _submit_quiz():
id = request.get_json()['id'] id = request.get_json()['id']
answers = request.get_json()['answers'] answers = request.get_json()['answers']
try: entry = Entry.query.filter_by(id=id).first() try: entry = Entry.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not entry: return jsonify({'error': 'Unrecognised Entry.'}), 400 if not entry: return jsonify({'error': 'Unrecognised Entry.'}), 400
@ -80,7 +81,7 @@ def _editor(id:str=None):
request_data = request.get_json() request_data = request.get_json()
id = request_data['id'] id = request_data['id']
try: dataset = Dataset.query.filter_by(id=id).first() try: dataset = Dataset.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not dataset: return jsonify({'error': 'Invalid request. Dataset not found.'}), 404 if not dataset: return jsonify({'error': 'Invalid request. Dataset not found.'}), 404
@ -92,7 +93,7 @@ def _editor(id:str=None):
default = request_data['default'] default = request_data['default']
creator = request_data['creator'] creator = request_data['creator']
try: user = User.query.filter_by(id=creator).first() try: user = User.query.filter_by(id=creator).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
name = request_data['name'] name = request_data['name']

View File

@ -7,6 +7,7 @@ from ..tools.logs import write
from flask import Blueprint, jsonify, render_template from flask import Blueprint, jsonify, render_template
from flask.helpers import abort, flash, redirect, request, url_for from flask.helpers import abort, flash, redirect, request, url_for
from flask_login import login_required from flask_login import login_required
from sqlalchemy.exc import SQLAlchemyError
editor = Blueprint( editor = Blueprint(
name='editor', name='editor',
@ -36,7 +37,7 @@ def _editor_console(id:str=None):
dataset = Dataset.query.filter_by(id=id).first() dataset = Dataset.query.filter_by(id=id).first()
datasets = Dataset.query.count() datasets = Dataset.query.count()
users = User.query.all() users = User.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not dataset: if not dataset:

View File

@ -5,6 +5,7 @@ from ..tools.logs import write
from flask import current_app as app from flask import current_app as app
from flask.helpers import flash from flask.helpers import flash
from flask_login import current_user from flask_login import current_user
from sqlalchemy.exc import SQLAlchemyError
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from datetime import datetime from datetime import datetime
@ -44,12 +45,12 @@ class Dataset(db.Model):
def make_default(self): def make_default(self):
try: try:
for dataset in Dataset.query.all(): dataset.default = False for dataset in Dataset.query.all(): dataset.default = False
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when setting default dataset {self.id}: {exception}') write('system.log', f'Database error when setting default dataset {self.id}: {exception}')
return False, f'Database error {exception}.' return False, f'Database error {exception}.'
self.default = True self.default = True
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when setting default dataset {self.id}: {exception}') write('system.log', f'Database error when setting default dataset {self.id}: {exception}')
return False, f'Database error {exception}.' return False, f'Database error {exception}.'
@ -67,7 +68,7 @@ class Dataset(db.Model):
message = 'Cannot delete the only dataset.' message = 'Cannot delete the only dataset.'
flash(message, 'error') flash(message, 'error')
return False, message return False, message
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when setting default dataset {self.id}: {exception}') write('system.log', f'Database error when setting default dataset {self.id}: {exception}')
return False, f'Database error {exception}.' return False, f'Database error {exception}.'
write('system.log', f'Dataset {self.id} deleted by {current_user.get_username()}.') write('system.log', f'Dataset {self.id} deleted by {current_user.get_username()}.')
@ -77,7 +78,7 @@ class Dataset(db.Model):
try: try:
db.session.delete(self) db.session.delete(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when trying to delete dataset {self.id}: {exception}') write('system.log', f'Database error when trying to delete dataset {self.id}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -97,7 +98,7 @@ class Dataset(db.Model):
try: try:
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when trying to crreate dataset {self.id}: {exception}') write('system.log', f'Database error when trying to crreate dataset {self.id}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -127,7 +128,7 @@ class Dataset(db.Model):
try: try:
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when trying to update dataset {self.id}: {exception}') write('system.log', f'Database error when trying to update dataset {self.id}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'

View File

@ -5,6 +5,8 @@ from .test import Test
from flask_login import current_user from flask_login import current_user
from flask_mail import Message from flask_mail import Message
from smtplib import SMTPException
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy_json import MutableJson from sqlalchemy_json import MutableJson
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -72,7 +74,7 @@ class Entry(db.Model):
try: try:
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when preparing new entry for {self.get_surname()}, {self.get_first_name()}: {exception}') write('system.log', f'Database error when preparing new entry for {self.get_surname()}, {self.get_first_name()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -83,7 +85,7 @@ class Entry(db.Model):
self.start_time = datetime.now() self.start_time = datetime.now()
self.status = 'started' self.status = 'started'
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when starting test for {self.get_surname()}, {self.get_first_name()}: {exception}') write('system.log', f'Database error when starting test for {self.get_surname()}, {self.get_first_name()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -102,7 +104,7 @@ class Entry(db.Model):
self.status = 'late' self.status = 'late'
self.valid = False self.valid = False
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when submitting entry for {self.get_surname()}, {self.get_first_name()}: {exception}') write('system.log', f'Database error when submitting entry for {self.get_surname()}, {self.get_first_name()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -115,7 +117,7 @@ class Entry(db.Model):
self.valid = True self.valid = True
self.status = 'completed' self.status = 'completed'
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when validating entry {self.id}: {exception}') write('system.log', f'Database error when validating entry {self.id}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -128,7 +130,7 @@ class Entry(db.Model):
try: try:
db.session.delete(self) db.session.delete(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when deleting entry {id}: {exception}') write('system.log', f'Database error when deleting entry {id}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -197,4 +199,4 @@ class Entry(db.Model):
""" """
) )
try: mail.send(email) try: mail.send(email)
except Exception as exception: write('system.log', f'SMTP Error when trying to notify results to {self.get_surname()}, {self.get_first_name()} with error: {exception}') except (SMTPException, ConnectionError) as exception: write('system.log', f'SMTP Error when trying to notify results to {self.get_surname()}, {self.get_first_name()} with error: {exception}')

View File

@ -2,6 +2,7 @@ from ..extensions import db
from ..tools.logs import write from ..tools.logs import write
from flask_login import current_user from flask_login import current_user
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy_json import MutableJson from sqlalchemy_json import MutableJson
from datetime import date, datetime from datetime import date, datetime
@ -55,7 +56,7 @@ class Test(db.Model):
try: try:
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when creating test {self.get_code()}: {exception}') write('system.log', f'Database error when creating test {self.get_code()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -66,7 +67,7 @@ class Test(db.Model):
if self.entries: return False, f'Cannot delete a test with submitted entries.' if self.entries: return False, f'Cannot delete a test with submitted entries.'
db.session.delete(self) db.session.delete(self)
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when deleting test {self.get_code()}: {exception}') write('system.log', f'Database error when deleting test {self.get_code()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -78,7 +79,7 @@ class Test(db.Model):
if self.start_date.date() > now.date(): if self.start_date.date() > now.date():
self.start_date = now self.start_date = now
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when launching test {self.get_code()}: {exception}') write('system.log', f'Database error when launching test {self.get_code()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -91,7 +92,7 @@ class Test(db.Model):
if self.end_date >= now: if self.end_date >= now:
self.end_date = now self.end_date = now
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when closing test {self.get_code()}: {exception}') write('system.log', f'Database error when closing test {self.get_code()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -105,7 +106,7 @@ class Test(db.Model):
adjustments[code] = time adjustments[code] = time
self.adjustments = adjustments self.adjustments = adjustments
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when adding adjustment to test {self.get_code()}: {exception}') write('system.log', f'Database error when adding adjustment to test {self.get_code()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -117,7 +118,7 @@ class Test(db.Model):
self.adjustments.pop(code) self.adjustments.pop(code)
if not self.adjustments: self.adjustments = None if not self.adjustments: self.adjustments = None
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when deleting adjustment from test {self.get_code()}: {exception}') write('system.log', f'Database error when deleting adjustment from test {self.get_code()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -130,7 +131,7 @@ class Test(db.Model):
if end_date: self.end_date = end_date if end_date: self.end_date = end_date
if time_limit is not None: self.time_limit = time_limit if time_limit is not None: self.time_limit = time_limit
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when updating test {self.get_code()}: {exception}') write('system.log', f'Database error when updating test {self.get_code()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'

View File

@ -6,6 +6,8 @@ from flask import jsonify, session
from flask.helpers import flash, url_for from flask.helpers import flash, url_for
from flask_login import current_user, login_user, logout_user, UserMixin from flask_login import current_user, login_user, logout_user, UserMixin
from flask_mail import Message from flask_mail import Message
from smtplib import SMTPException
from sqlalchemy.exc import SQLAlchemyError
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
import secrets import secrets
@ -56,7 +58,7 @@ class User(UserMixin, db.Model):
def register(self, notify:bool=False, password:str=None): def register(self, notify:bool=False, password:str=None):
self.generate_id() self.generate_id()
try: users = User.query.all() try: users = User.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when setting default dataset {self.id}: {exception}') write('system.log', f'Database error when setting default dataset {self.id}: {exception}')
return False, f'Database error {exception}.' return False, f'Database error {exception}.'
for user in users: for user in users:
@ -66,7 +68,7 @@ class User(UserMixin, db.Model):
try: try:
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when registering user {self.get_username()}: {exception}') write('system.log', f'Database error when registering user {self.get_username()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -99,7 +101,7 @@ class User(UserMixin, db.Model):
""" """
) )
try: mail.send(email) try: mail.send(email)
except Exception as exception: write('system.log', f'SMTP Error while trying to notify new user account creation to {self.get_username()} with error: {exception}') except (SMTPException, ConnectionError) as exception: write('system.log', f'SMTP Error while trying to notify new user account creation to {self.get_username()} with error: {exception}')
return True, f'User {self.get_username()} was created successfully.' return True, f'User {self.get_username()} was created successfully.'
def login(self, remember:bool=False): def login(self, remember:bool=False):
@ -151,12 +153,12 @@ class User(UserMixin, db.Model):
""" """
) )
try: mail.send(email) try: mail.send(email)
except Exception as exception: except (SMTPException, ConnectionError) as exception:
write('system.log', f'SMTP Error while trying to reset password for {self.get_username()} with error: {exception}') write('system.log', f'SMTP Error while trying to reset password for {self.get_username()} with error: {exception}')
db.session.rollback() db.session.rollback()
return jsonify({'error': f'SMTP Error: {exception}'}), 500 return jsonify({'error': f'SMTP Error: {exception}'}), 500
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when resetting password for user {self.get_username()}: {exception}') write('system.log', f'Database error when resetting password for user {self.get_username()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -165,7 +167,7 @@ class User(UserMixin, db.Model):
def clear_reset_tokens(self): def clear_reset_tokens(self):
self.reset_token = self.verification_token = None self.reset_token = self.verification_token = None
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when resetting clearing reset tokens for user {self.get_username()}: {exception}') write('system.log', f'Database error when resetting clearing reset tokens for user {self.get_username()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -176,7 +178,7 @@ class User(UserMixin, db.Model):
try: try:
db.session.delete(self) db.session.delete(self)
db.session.commit() db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when deleting user {self.get_username()}: {exception}') write('system.log', f'Database error when deleting user {self.get_username()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -205,7 +207,7 @@ class User(UserMixin, db.Model):
""" """
) )
try: mail.send(email) try: mail.send(email)
except Exception as exception: write('system.log', f'SMTP Error when trying to delete account {username} with error: {exception}') except (SMTPException, ConnectionError) as exception: write('system.log', f'SMTP Error when trying to delete account {username} with error: {exception}')
return True, message return True, message
def update(self, password:str=None, email:str=None, notify:bool=False): def update(self, password:str=None, email:str=None, notify:bool=False):
@ -216,12 +218,12 @@ class User(UserMixin, db.Model):
try: try:
for entry in User.query.all(): for entry in User.query.all():
if entry.get_email() == email and not entry == self: return False, f'The email address {email} is already in use.' if entry.get_email() == email and not entry == self: return False, f'The email address {email} is already in use.'
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when setting default dataset {self.id}: {exception}') write('system.log', f'Database error when setting default dataset {self.id}: {exception}')
return False, f'Database error {exception}.' return False, f'Database error {exception}.'
self.set_email(email) self.set_email(email)
try: db.session.commit() try: db.session.commit()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
db.session.rollback() db.session.rollback()
write('system.log', f'Database error when updating user {self.get_username()}: {exception}') write('system.log', f'Database error when updating user {self.get_username()}: {exception}')
return False, f'Database error: {exception}' return False, f'Database error: {exception}'
@ -255,5 +257,5 @@ class User(UserMixin, db.Model):
""" """
) )
try: mail.send(message) try: mail.send(message)
except Exception as exception: write('system.log', f'SMTP Error when trying to update account {self.get_username()} with error: {exception}') except (SMTPException, ConnectionError) as exception: write('system.log', f'SMTP Error when trying to update account {self.get_username()} with error: {exception}')
return True, f'Account {self.get_username()} has been updated.' return True, f'Account {self.get_username()} has been updated.'

View File

@ -6,6 +6,7 @@ from ..tools.test import redirect_if_started
from flask import Blueprint, jsonify, render_template, request, session from flask import Blueprint, jsonify, render_template, request, session
from flask.helpers import abort, flash, redirect, url_for from flask.helpers import abort, flash, redirect, url_for
from sqlalchemy.exc import SQLAlchemyError
from datetime import datetime from datetime import datetime
@ -39,7 +40,7 @@ def _start():
entry.set_email(request.form.get('email')) entry.set_email(request.form.get('email'))
code = request.form.get('test_code').replace('', '').lower() code = request.form.get('test_code').replace('', '').lower()
try: test = Test.query.filter_by(code=code).first() try: test = Test.query.filter_by(code=code).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
entry.test = test entry.test = test
@ -68,7 +69,7 @@ def _quiz():
flash('Your session was not recognised. Please sign in to the quiz again.', 'error') flash('Your session was not recognised. Please sign in to the quiz again.', 'error')
session.pop('id', None) session.pop('id', None)
return redirect(url_for('quiz._start')) return redirect(url_for('quiz._start'))
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
return render_template('/quiz/client.html') return render_template('/quiz/client.html')
@ -77,7 +78,7 @@ def _quiz():
def _result(): def _result():
id = session.get('id') id = session.get('id')
try: entry = Entry.query.filter_by(id=id).first() try: entry = Entry.query.filter_by(id=id).first()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not entry: return abort(404) if not entry: return abort(404)

View File

@ -4,6 +4,7 @@ from ..tools.logs import write
from flask.helpers import abort, flash, redirect, url_for from flask.helpers import abort, flash, redirect, url_for
from flask_login import current_user from flask_login import current_user
from sqlalchemy.exc import SQLAlchemyError
from functools import wraps from functools import wraps
@ -14,7 +15,7 @@ def require_account_creation(function):
if User.query.count() == 0: if User.query.count() == 0:
flash('Please register a user account.', 'alert') flash('Please register a user account.', 'alert')
return redirect(url_for('admin._register')) return redirect(url_for('admin._register'))
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when checking for existing accounts: {exception}') write('system.log', f'Database error when checking for existing accounts: {exception}')
return abort(500) return abort(500)
return function(*args, **kwargs) return function(*args, **kwargs)

View File

@ -3,6 +3,7 @@ from ..tools.logs import write
from flask import current_app as app from flask import current_app as app
from flask.helpers import abort, flash, redirect, url_for from flask.helpers import abort, flash, redirect, url_for
from sqlalchemy.exc import SQLAlchemyError
import json import json
from pathlib import Path from pathlib import Path
@ -77,7 +78,7 @@ def check_dataset_exists(function):
@wraps(function) @wraps(function)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
try: datasets = Dataset.query.all() try: datasets = Dataset.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when checking existing datasets: {exception}') write('system.log', f'Database error when checking existing datasets: {exception}')
return abort(500) return abort(500)
if not datasets: if not datasets:

View File

@ -3,8 +3,12 @@ from ..extensions import db
from ..tools.logs import write from ..tools.logs import write
from flask import jsonify from flask import jsonify
from sqlalchemy.exc import SQLAlchemyError
from wtforms.validators import ValidationError from wtforms.validators import ValidationError
import json
from sqlalchemy.ext import mutable
def value(min:int=0, max:int=None): def value(min:int=0, max:int=None):
if not max: if not max:
message = f'Value must be greater than {min}.' message = f'Value must be greater than {min}.'
@ -28,7 +32,7 @@ def get_time_options():
def get_dataset_choices(): def get_dataset_choices():
from ..models import Dataset from ..models import Dataset
try: datasets = Dataset.query.all() try: datasets = Dataset.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when fetching dataset lists: {exception}') write('system.log', f'Database error when fetching dataset lists: {exception}')
return [] return []
dataset_choices = [] dataset_choices = []

View File

@ -4,6 +4,7 @@ from ..tools.logs import write
from flask import request, session from flask import request, session
from flask.helpers import abort, redirect, url_for from flask.helpers import abort, redirect, url_for
from sqlalchemy.exc import SQLAlchemyError
from functools import wraps from functools import wraps
@ -132,7 +133,7 @@ def redirect_if_started(function):
id = session.get('id') id = session.get('id')
try: try:
if request.method == 'GET' and id and Entry.query.filter_by(id=id).first(): return redirect(url_for('quiz._quiz')) if request.method == 'GET' and id and Entry.query.filter_by(id=id).first(): return redirect(url_for('quiz._quiz'))
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when checking if test has been started: {exception}') write('system.log', f'Database error when checking if test has been started: {exception}')
return abort(500) return abort(500)
return function(*args, **kwargs) return function(*args, **kwargs)

View File

@ -7,6 +7,7 @@ from ..tools.logs import write
from flask import Blueprint, jsonify, render_template, request from flask import Blueprint, jsonify, render_template, request
from flask.helpers import abort, flash, redirect, url_for from flask.helpers import abort, flash, redirect, url_for
from flask_login import login_required from flask_login import login_required
from sqlalchemy.exc import SQLAlchemyError
view = Blueprint( view = Blueprint(
name='view', name='view',
@ -37,7 +38,7 @@ def _view_console(id:str=None):
dataset = Dataset.query.filter_by(id=id).first() dataset = Dataset.query.filter_by(id=id).first()
datasets = Dataset.query.count() datasets = Dataset.query.count()
users = User.query.all() users = User.query.all()
except Exception as exception: except (SQLAlchemyError, ConnectionError) as exception:
write('system.log', f'Database error when processing request \'{request.url}\': {exception}') write('system.log', f'Database error when processing request \'{request.url}\': {exception}')
return abort(500) return abort(500)
if not dataset: if not dataset:

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
from main import app from main import app
from app.extensions import db from app.extensions import db
from app.models import *
from app.tools.data import save from app.tools.data import save
from app.tools.logs import write from app.tools.logs import write
from sqlalchemy_utils import create_database, database_exists from sqlalchemy_utils import create_database, database_exists
@ -22,12 +21,14 @@ with app.app_context():
if not path.isfile(f'./{data}/logs/users.log'): write('users.log', 'Log file created.') if not path.isfile(f'./{data}/logs/users.log'): write('users.log', 'Log file created.')
if not path.isfile(f'./{data}/logs/system.log'): write('system.log', 'Log file created.') if not path.isfile(f'./{data}/logs/system.log'): write('system.log', 'Log file created.')
if not path.isfile(f'./{data}/logs/tests.log'): write('tests.log', 'Log file created.') if not path.isfile(f'./{data}/logs/tests.log'): write('tests.log', 'Log file created.')
if not database_exists(database_uri):
create_database(database_uri)
write('system.log', 'No database found. Creating a new database.')
from app.models import *
db.create_all()
write('system.log', 'Creating database schema.')
if not path.isfile(f'./{data}/.encryption.key'): if not path.isfile(f'./{data}/.encryption.key'):
write('system.log', 'No encryption key found. Generating new encryption key.') write('system.log', 'No encryption key found. Generating new encryption key.')
with open(f'./{data}/.encryption.key', 'wb') as key_file: with open(f'./{data}/.encryption.key', 'wb') as key_file:
key = Fernet.generate_key() key = Fernet.generate_key()
key_file.write(key) key_file.write(key)
if not database_exists(database_uri):
create_database(database_uri)
write('system.log', 'No database found. Creating a new database.')
db.create_all()

View File

@ -2,12 +2,14 @@
from main import app from main import app
from app.models import User from app.models import User
from sqlalchemy.exc import SQLAlchemyError
import sys import sys
from getpass import getpass from getpass import getpass
with app.app_context(): with app.app_context():
try: users = User.query.all() try: users = User.query.all()
except Exception as exception: sys.exit('Database error:', exception) except (SQLAlchemyError, ConnectionError) as exception: sys.exit('Database error:', exception)
print('') print('')
print('This interface will allow you to override the password for an administrator account.') print('This interface will allow you to override the password for an administrator account.')
print('To exit this interface, press Ctrl + C.') print('To exit this interface, press Ctrl + C.')