11 Commits

19 changed files with 151 additions and 130 deletions

View File

@ -2,7 +2,12 @@
# Source https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71 # Source https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71
if ! [ -x "$(command -v docker compose)" ]; then if ! [ -x "$(command -v docker)" ]; then
echo 'Error: docker is not installed.' >&2
exit 1
fi
if ! [ -x "$(command -v compose)" ]; then
echo 'Error: docker compose is not installed.' >&2 echo 'Error: docker compose is not installed.' >&2
exit 1 exit 1
fi fi

View File

@ -0,0 +1,57 @@
from .config import Production as Config
from .install import install_app
from .models import User
from .extensions import bootstrap, csrf, db, login_manager, mail
from flask import flash, Flask, render_template, request
from flask.helpers import url_for
from flask.json import jsonify
from flask_wtf.csrf import CSRFError
from werkzeug.middleware.proxy_fix import ProxyFix
from datetime import datetime
def create_app():
app = Flask(__name__)
app.config.from_object(Config())
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto= 1, x_host= 1)
bootstrap.init_app(app)
csrf.init_app(app)
db.init_app(app)
login_manager.init_app(app)
mail.init_app(app)
login_manager.login_view = 'admin._login'
@login_manager.user_loader
def _load_user(id):
return User.query.filter_by(id=id).first()
@app.before_request
def _check_cookie_consent():
if request.cookies.get('cookie_consent'):
return
if any([ request.path.startswith(x) for x in [ '/admin/static/', '/root/', '/quiz/static', '/cookies/' ] ]):
return
flash(f'<strong>Cookie Consent</strong>: This web site only stores minimal, functional cookies. It does not store any tracking information. By using this site, you consent to this use of cookies. For more information, see our <a href="{url_for("views._privacy")}">privacy policy</a>.', 'cookie_alert')
@app.errorhandler(404)
def _404_handler(error): return render_template('404.html')
@app.errorhandler(CSRFError)
def _csrf_handler(): return jsonify({'error':'Could not validate a secure connection.'}), 403
@app.context_processor
def _now(): return {'now': datetime.now()}
from .admin.views import admin
from .api.views import api
from .quiz.views import quiz
from .views import views
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(api, url_prefix='/api')
app.register_blueprint(views)
app.register_blueprint(quiz)
install_app(app)
return app

View File

@ -1,7 +1,7 @@
from ..forms.admin import AddTimeAdjustment, CreateTest, CreateUser, DeleteUser, Login, Register, ResetPassword, UpdatePassword, UpdateUser, UploadData from ..forms.admin import AddTimeAdjustment, CreateTest, CreateUser, DeleteUser, Login, Register, ResetPassword, UpdatePassword, UpdateUser, UploadData
from ..models import Dataset, Entry, Test, User from ..models import Dataset, Entry, Test, User
from ..tools.auth import disable_if_logged_in, require_account_creation from ..tools.auth import disable_if_logged_in, require_account_creation
from ..tools.forms import get_dataset_choices, get_time_options from ..tools.forms import get_dataset_choices, get_time_options, send_errors_to_client
from ..tools.data import check_is_json, validate_json from ..tools.data import check_is_json, validate_json
from ..tools.test import answer_options, get_correct_answers from ..tools.test import answer_options, get_correct_answers
@ -61,8 +61,7 @@ def _login():
return jsonify({'success': f'Successfully logged in.'}), 200 return jsonify({'success': f'Successfully logged in.'}), 200
return jsonify({'error': f'The password you entered is incorrect.'}), 401 return jsonify({'error': f'The password you entered is incorrect.'}), 401
return jsonify({'error': f'The username you entered does not exist.'}), 401 return jsonify({'error': f'The username you entered does not exist.'}), 401
errors = [*form.username.errors, *form.password.errors] return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 400
if 'remembered_username' in session: form.username.data = session.pop('remembered_username') if 'remembered_username' in session: form.username.data = session.pop('remembered_username')
next = request.args.get('next') next = request.args.get('next')
return render_template('/admin/auth/login.html', form=form, next=next) return render_template('/admin/auth/login.html', form=form, next=next)
@ -90,8 +89,7 @@ def _register():
return jsonify({'success': message}), 200 return jsonify({'success': message}), 200
flash(message=message, category='error') flash(message=message, category='error')
return jsonify({'error': message}), 401 return jsonify({'error': message}), 401
errors = [*form.username.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors] return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 400
return render_template('admin/auth/register.html', form=form) return render_template('admin/auth/register.html', form=form)
@admin.route('/reset/', methods=['GET','POST']) @admin.route('/reset/', methods=['GET','POST'])
@ -108,8 +106,7 @@ def _reset():
if not user: return jsonify({'error': 'The user account does not exist.'}), 400 if not user: return jsonify({'error': 'The user account does not exist.'}), 400
if not user.get_email() == request.form.get('email'): return jsonify({'error': 'The email address does not match the user account.'}), 400 if not user.get_email() == request.form.get('email'): return jsonify({'error': 'The email address does not match the user account.'}), 400
return user.reset_password() return user.reset_password()
errors = [*form.username.errors, *form.email.errors] return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 400
token = request.args.get('token') token = request.args.get('token')
if token: if token:
@ -134,8 +131,7 @@ def _update_password():
session['remembered_username'] = user.get_username() session['remembered_username'] = user.get_username()
flash('Your password has been reset.', 'success') flash('Your password has been reset.', 'success')
return jsonify({'success':'Your password has been reset'}), 200 return jsonify({'success':'Your password has been reset'}), 200
errors = [*form.password.errors, *form.password_reenter.errors] return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 401
@admin.route('/settings/users/', methods=['GET', 'POST']) @admin.route('/settings/users/', methods=['GET', 'POST'])
@login_required @login_required
@ -152,8 +148,7 @@ def _users():
success, message = new_user.register(notify=request.form.get('notify'), password=password) success, message = new_user.register(notify=request.form.get('notify'), password=password)
if success: return jsonify({'success': message}), 200 if success: return jsonify({'success': message}), 200
return jsonify({'error': message}), 401 return jsonify({'error': message}), 401
errors = [*form.username.errors, *form.email.errors, *form.password.errors] return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 401
return render_template('/admin/settings/users.html', form = form, users = users) return render_template('/admin/settings/users.html', form = form, users = users)
@admin.route('/settings/users/delete/<string:id>', methods=['GET', 'POST']) @admin.route('/settings/users/delete/<string:id>', methods=['GET', 'POST'])
@ -170,8 +165,7 @@ def _delete_user(id:str):
success, message = user.delete(notify=request.form.get('notify')) success, message = user.delete(notify=request.form.get('notify'))
if success: return jsonify({'success': message}), 200 if success: return jsonify({'success': message}), 200
return jsonify({'error': message}), 400 return jsonify({'error': message}), 400
errors = form.password.errors return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 400
if id == current_user.id: if id == current_user.id:
flash('Cannot delete your own user account.', 'error') flash('Cannot delete your own user account.', 'error')
@ -199,8 +193,7 @@ def _update_user(id:str):
flash(message, 'success') flash(message, 'success')
return jsonify({'success': message}), 200 return jsonify({'success': message}), 200
return jsonify({'error': message}), 400 return jsonify({'error': message}), 400
errors = [*form.confirm_password.errors, *form.email.errors, *form.password.errors, *form.password_reenter.errors] return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 400
if not user: if not user:
flash('User not found.', 'error') flash('User not found.', 'error')
return redirect(url_for('admin._users')) return redirect(url_for('admin._users'))
@ -222,8 +215,7 @@ def _questions():
) )
if success: return jsonify({'success': message}), 200 if success: return jsonify({'success': message}), 200
return jsonify({'error': message}), 400 return jsonify({'error': message}), 400
errors = form.data_file.errors return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 400
data = Dataset.query.all() data = Dataset.query.all()
return render_template('/admin/settings/questions.html', form=form, data=data) return render_template('/admin/settings/questions.html', form=form, data=data)
@ -299,9 +291,7 @@ def _create_test():
flash(message=message, category='success') flash(message=message, category='success')
return jsonify({'success': message}), 200 return jsonify({'success': message}), 200
return jsonify({'error': message}), 400 return jsonify({'error': message}), 400
else: return send_errors_to_client(form=form)
errors = [*form.start_date.errors, *form.expiry_date.errors, *form.time_limit.errors]
return jsonify({ 'error': errors}), 400
@admin.route('/tests/edit/', methods=['POST']) @admin.route('/tests/edit/', methods=['POST'])
@login_required @login_required
@ -385,7 +375,7 @@ def _view_entry(id:str=None):
@admin.route('/certificate/',methods=['POST']) @admin.route('/certificate/',methods=['POST'])
@login_required @login_required
def _generate_certificate(): def _generate_certificate():
from main import db from ..extensions import db
id = request.get_json()['id'] id = request.get_json()['id']
entry = Entry.query.filter_by(id=id).first() entry = Entry.query.filter_by(id=id).first()
if not entry: return jsonify({'error': 'Invalid entry ID.'}), 404 if not entry: return jsonify({'error': 'Invalid entry ID.'}), 404

View File

@ -1,9 +1,7 @@
import os import os
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv
if not os.getenv('DATA'): load_dotenv('../.env')
from dotenv import load_dotenv
load_dotenv('../.env')
class Config(object): class Config(object):
APP_HOST = '0.0.0.0' APP_HOST = '0.0.0.0'
@ -13,7 +11,7 @@ class Config(object):
SECRET_KEY = os.getenv('SECRET_KEY') SECRET_KEY = os.getenv('SECRET_KEY')
SERVER_NAME = os.getenv('SERVER_NAME') SERVER_NAME = os.getenv('SERVER_NAME')
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
SQLALCHEMY_DATABASE_URI = f'sqlite:///{Path(DATA)}/database.db' SQLALCHEMY_DATABASE_URI = f'sqlite:///{Path(os.path.abspath(f"{DATA}/database.db"))}'
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
MAIL_SERVER = os.getenv('MAIL_SERVER') MAIL_SERVER = os.getenv('MAIL_SERVER')
@ -34,6 +32,7 @@ class Production(Config):
class Development(Config): class Development(Config):
APP_HOST = '127.0.0.1' APP_HOST = '127.0.0.1'
DEBUG = True DEBUG = True
SERVER_NAME = '127.0.0.1:5000'
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
MAIL_SERVER = 'localhost' MAIL_SERVER = 'localhost'
MAIL_DEBUG = True MAIL_DEBUG = True

View File

@ -1,5 +0,0 @@
from config import Config
from os import path
from pathlib import Path
data = Path(Config.DATA)

34
ref-test/app/install.py Normal file
View File

@ -0,0 +1,34 @@
from .extensions import db
from .tools.data import save
from .tools.logs import write
from sqlalchemy_utils import create_database, database_exists
from cryptography.fernet import Fernet
from os import mkdir, path
from pathlib import Path
def install_app(app):
with app.app_context():
data = Path(app.config.get('DATA'))
database_uri = app.config.get('SQLALCHEMY_DATABASE_URI')
if not path.isdir(f'./{data}'): mkdir(f'./{data}')
if not path.isdir(f'./{data}/questions'): mkdir(f'./{data}/questions')
if not path.isfile(f'./{data}/.gitignore'):
with open(f'./{data}/.gitignore', 'a+') as file: file.write(f'*')
if not path.isfile(f'./{data}/config.json'): save({}, 'config.json')
if not path.isdir(f'./{data}/logs'): mkdir(f'./{data}/logs')
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/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 .models import Entry, Dataset, Test, User
db.create_all()
write('system.log', 'Creating database schema.')
if not path.isfile(f'./{data}/.encryption.key'):
write('system.log', 'No encryption key found. Generating new encryption key.')
with open(f'./{data}/.encryption.key', 'wb') as key_file:
key = Fernet.generate_key()
key_file.write(key)

View File

@ -1,14 +1,15 @@
from ..data import data from ..extensions import db
from ..modules import db
from ..tools.logs import write from ..tools.logs import write
from flask import flash from flask import flash
from flask import current_app as app
from flask_login import current_user from flask_login import current_user
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from datetime import datetime from datetime import datetime
from json import dump, loads from json import dump, loads
from os import path, remove from os import path, remove
from pathlib import Path
from uuid import uuid4 from uuid import uuid4
class Dataset(db.Model): class Dataset(db.Model):
@ -48,6 +49,7 @@ class Dataset(db.Model):
return False, message return False, message
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()}.')
filename = secure_filename('.'.join([self.id,'json'])) filename = secure_filename('.'.join([self.id,'json']))
data = Path(app.config.get('DATA'))
file_path = path.join(data, 'questions', filename) file_path = path.join(data, 'questions', filename)
remove(file_path) remove(file_path)
db.session.delete(self) db.session.delete(self)
@ -58,6 +60,7 @@ class Dataset(db.Model):
self.generate_id() self.generate_id()
timestamp = datetime.now() timestamp = datetime.now()
filename = secure_filename('.'.join([self.id,'json'])) filename = secure_filename('.'.join([self.id,'json']))
data = Path(app.config.get('DATA'))
file_path = path.join(data, 'questions', filename) file_path = path.join(data, 'questions', filename)
upload.stream.seek(0) upload.stream.seek(0)
questions = loads(upload.read()) questions = loads(upload.read())
@ -73,11 +76,13 @@ class Dataset(db.Model):
def check_file(self): def check_file(self):
filename = secure_filename('.'.join([self.id,'json'])) filename = secure_filename('.'.join([self.id,'json']))
data = Path(app.config.get('DATA'))
file_path = path.join(data, 'questions', filename) file_path = path.join(data, 'questions', filename)
if not path.isfile(file_path): return False, 'Data file is missing.' if not path.isfile(file_path): return False, 'Data file is missing.'
return True, 'Data file found.' return True, 'Data file found.'
def get_file(self): def get_file(self):
filename = secure_filename('.'.join([self.id,'json'])) filename = secure_filename('.'.join([self.id,'json']))
data = Path(app.config.get('DATA'))
file_path = path.join(data, 'questions', filename) file_path = path.join(data, 'questions', filename)
return file_path return file_path

View File

@ -1,4 +1,4 @@
from ..modules import db, mail from ..extensions import db, mail
from ..tools.forms import JsonEncodedDict from ..tools.forms import JsonEncodedDict
from ..tools.encryption import decrypt, encrypt from ..tools.encryption import decrypt, encrypt
from ..tools.logs import write from ..tools.logs import write

View File

@ -1,4 +1,4 @@
from ..modules import db from ..extensions import db
from ..tools.encryption import decrypt, encrypt from ..tools.encryption import decrypt, encrypt
from ..tools.forms import JsonEncodedDict from ..tools.forms import JsonEncodedDict
from ..tools.logs import write from ..tools.logs import write

View File

@ -1,4 +1,4 @@
from ..modules import db, mail from ..extensions import db, mail
from ..tools.encryption import decrypt, encrypt from ..tools.encryption import decrypt, encrypt
from ..tools.logs import write from ..tools.logs import write
@ -189,7 +189,10 @@ class User(UserMixin, db.Model):
if not password and not email: return False, 'There were no changes requested.' if not password and not email: return False, 'There were no changes requested.'
if password: self.set_password(password) if password: self.set_password(password)
old_email = self.get_email() old_email = self.get_email()
if email: self.set_email(email) if email:
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.'
self.set_email(email)
db.session.commit() db.session.commit()
write('system.log', f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.') write('system.log', f'Information for user {self.get_username()} has been updated by {current_user.get_username()}.')
if notify: if notify:

View File

@ -1,5 +1,6 @@
from ..forms.quiz import StartQuiz from ..forms.quiz import StartQuiz
from ..models import Entry, Test from ..models import Entry, Test
from ..tools.forms import send_errors_to_client
from ..tools.test import redirect_if_started from ..tools.test import redirect_if_started
from flask import abort, Blueprint, jsonify, redirect, render_template, request, session from flask import abort, Blueprint, jsonify, redirect, render_template, request, session
@ -52,8 +53,7 @@ def _start():
'id': entry.id 'id': entry.id
}), 200 }), 200
return jsonify({'error': 'There was an error processing the user test and/or user codes.'}), 400 return jsonify({'error': 'There was an error processing the user test and/or user codes.'}), 400
errors = [*form.test_code.errors, *form.user_code.errors, *form.first_name.errors, *form.surname.errors, *form.email.errors, *form.club.errors] return send_errors_to_client(form=form)
return jsonify({ 'error': errors}), 400
return render_template('/quiz/start_quiz.html', form = form) return render_template('/quiz/start_quiz.html', form = form)
@quiz.route('/quiz/') @quiz.route('/quiz/')

View File

@ -1,13 +1,16 @@
from ..data import data as data_dir from flask import current_app as app
import json import json
from pathlib import Path
from random import shuffle from random import shuffle
def load(filename:str): def load(filename:str):
data_dir = Path(app.config.get('DATA'))
with open(f'./{data_dir}/{filename}') as file: with open(f'./{data_dir}/{filename}') as file:
return json.load(file) return json.load(file)
def save(data:dict, filename:str): def save(data:dict, filename:str):
data_dir = Path(app.config.get('DATA'))
with open(f'./{data_dir}/{filename}', 'w') as file: with open(f'./{data_dir}/{filename}', 'w') as file:
json.dump(data, file, indent=4) json.dump(data, file, indent=4)

View File

@ -1,7 +1,10 @@
from ..data import data from flask import current_app as app
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from pathlib import Path
def load_key(): def load_key():
data = Path(app.config.get('DATA'))
with open(f'./{data}/.encryption.key', 'rb') as keyfile: return keyfile.read() with open(f'./{data}/.encryption.key', 'rb') as keyfile: return keyfile.read()
def decrypt(input:str): def decrypt(input:str):

View File

@ -1,6 +1,7 @@
from ..modules import db from ..extensions import db
from flask import jsonify
from wtforms.validators import ValidationError from wtforms.validators import ValidationError
import json import json
@ -54,3 +55,7 @@ def get_dataset_choices():
choice = (dataset.id, label) choice = (dataset.id, label)
dataset_choices.append(choice) dataset_choices.append(choice)
return dataset_choices return dataset_choices
def send_errors_to_client(form):
errors = [*form.errors]
return jsonify({ 'error': errors}), 400

View File

@ -1,10 +1,14 @@
from ..data import data from flask import current_app as app
from datetime import datetime from datetime import datetime
from pathlib import Path
def read(filename:str): def read(filename:str):
data = Path(app.config.get('DATA'))
with open(f'./{data}/logs/{filename}') as file: with open(f'./{data}/logs/{filename}') as file:
return file.readlines() return file.readlines()
def write(filename:str, message:str): def write(filename:str, message:str):
data = Path(app.config.get('DATA'))
with open(f'./{data}/logs/{filename}', 'a+') as file: with open(f'./{data}/logs/{filename}', 'a+') as file:
file.write(f'{datetime.now().strftime("%Y-%m-%d-%X")}: {message}\n') file.write(f'{datetime.now().strftime("%Y-%m-%d-%X")}: {message}\n')

View File

@ -1,6 +1,5 @@
from .config import Config
from flask import Blueprint, redirect, request, render_template from flask import Blueprint, redirect, request, render_template
from flask import current_app as app
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -24,7 +23,7 @@ def _cookie_consent():
max_age = timedelta(days=14) if request.cookies.get('remember') == 'True' else None, max_age = timedelta(days=14) if request.cookies.get('remember') == 'True' else None,
path = '/', path = '/',
expires = datetime.utcnow() + timedelta(days=14) if request.cookies.get('remember') else None, expires = datetime.utcnow() + timedelta(days=14) if request.cookies.get('remember') else None,
domain = f'{Config.SERVER_NAME}', domain = f'{app.config.get("SERVER_NAME")}',
secure = True secure = True
) )
return resp return resp

View File

@ -1 +0,0 @@
from app.config import Production as Config

View File

@ -1,83 +1,3 @@
from app.data import data from app import create_app
from app.models import Entry, Dataset, Test, User
from app.modules import bootstrap, csrf, db, login_manager, mail
from app.tools.data import save
from app.tools.logs import write
from config import Config
from flask import flash, Flask, render_template, request
from flask.helpers import url_for
from flask.json import jsonify
from flask_wtf.csrf import CSRFError
from sqlalchemy_utils import database_exists, create_database
from werkzeug.middleware.proxy_fix import ProxyFix
from cryptography.fernet import Fernet
from datetime import datetime
from os import mkdir, path
def create_app():
app = Flask(__name__)
app.config.from_object(Config())
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto= 1, x_host= 1)
bootstrap.init_app(app)
csrf.init_app(app)
db.init_app(app)
login_manager.init_app(app)
mail.init_app(app)
login_manager.login_view = 'admin._login'
@login_manager.user_loader
def _load_user(id):
return User.query.filter_by(id=id).first()
@app.before_request
def _check_cookie_consent():
if request.cookies.get('cookie_consent'):
return
if any([ request.path.startswith(x) for x in [ '/admin/static/', '/root/', '/quiz/static', '/cookies/' ] ]):
return
flash(f'<strong>Cookie Consent</strong>: This web site only stores minimal, functional cookies. It does not store any tracking information. By using this site, you consent to this use of cookies. For more information, see our <a href="{url_for("views._privacy")}">privacy policy</a>.', 'cookie_alert')
@app.errorhandler(404)
def _404_handler(error): return render_template('404.html')
@app.errorhandler(CSRFError)
def _csrf_handler(): return jsonify({'error':'Could not validate a secure connection.'}), 403
@app.context_processor
def _now(): return {'now': datetime.now()}
from app.admin.views import admin
from app.api.views import api
from app.quiz.views import quiz
from app.views import views
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(api, url_prefix='/api')
app.register_blueprint(views)
app.register_blueprint(quiz)
if not path.isdir(f'./{data}'): mkdir(f'./{data}')
if not path.isdir(f'./{data}/questions'): mkdir(f'./{data}/questions')
if not path.isfile(f'./{data}/.gitignore'):
with open(f'./{data}/.gitignore', 'a+') as file: file.write(f'*')
if not path.isfile(f'./{data}/config.json'): save({}, 'config.json')
if not path.isdir(f'./{data}/logs'): mkdir(f'./{data}/logs')
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/tests.log'): write('tests.log', 'Log file created.')
if not database_exists(Config.SQLALCHEMY_DATABASE_URI):
create_database(Config.SQLALCHEMY_DATABASE_URI)
write('system.log', 'No database found. Creating a new database.')
with app.app_context(): db.create_all()
write('system.log', 'Creating database schema.')
if not path.isfile(f'./{data}/.encryption.key'):
write('system.log', 'No encryption key found. Generating new encryption key.')
with open(f'./{data}/.encryption.key', 'wb') as key_file:
key = Fernet.generate_key()
key_file.write(key)
return app
app = create_app() app = create_app()
if __name__ == '__main__': app.run() if __name__ == '__main__': app.run()