diff --git a/admin/admin.py b/admin/admin.py index 301e5ba..ec27447 100644 --- a/admin/admin.py +++ b/admin/admin.py @@ -1,5 +1,6 @@ import json -from flask import render_template, Blueprint, request, jsonify +from flask import render_template, Blueprint, request, jsonify, redirect, url_for +from flask_security import roles_required, auth_required import database from database import get_all_questions, get_question @@ -8,22 +9,26 @@ Admin = Blueprint("admin", __name__, template_folder="templates") @Admin.route("/admin/") +@roles_required("admin") def index(): return render_template("admin/index.html", title="admin") @Admin.route("/admin/questions/") +@roles_required("admin") def questions(): return render_template("admin/question_list.html") @Admin.route("/admin/questions/edit/") +@roles_required("admin") def edit_question(question_id): question: database.AllQuestions = get_question(database.AllQuestions, question_id) return render_template("admin/edit_question.html", question=question) @Admin.route("/admin/question_query") +@roles_required("admin") def query_questions(): offset = request.args.get("offset", type=int) limit = request.args.get("limit", type=int) diff --git a/admin/templates/admin/base.html b/admin/templates/admin/base.html index 5bf2938..9c5d268 100644 --- a/admin/templates/admin/base.html +++ b/admin/templates/admin/base.html @@ -3,6 +3,7 @@ Admin + {% block head %}{% endblock %} diff --git a/config.py b/config.py index e05ca65..3c62f4b 100644 --- a/config.py +++ b/config.py @@ -13,6 +13,7 @@ class Config: DB_HOST = environ.get("DB_HOST") DB_PORT = environ.get("DB_PORT") DB_NAME = environ.get("DB_NAME") + SECURITY_REGISTERABLE = True class Production(Config): diff --git a/database.py b/database.py index afdfac9..f4bebaa 100644 --- a/database.py +++ b/database.py @@ -1,9 +1,10 @@ from flask import current_app, g +from flask_security import UserMixin, RoleMixin import sqlalchemy from typing import Union, Optional, Literal, Type, List, Tuple import random import os -from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, ARRAY +from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, ARRAY, Boolean, UnicodeText, DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import sessionmaker, relationship, scoped_session @@ -16,6 +17,38 @@ engine = create_engine(config.DB_URL) session_factory = sessionmaker(bind=engine) Session = scoped_session(session_factory) Base = declarative_base() +Base.query = Session.query_property() + + +class User(Base, UserMixin): + __tablename__ = "users" + user_id = Column(Integer, primary_key=True) + email = Column(String, unique=True, nullable=False) + username = Column(String, unique=True) + password = Column(String, nullable=False) + active = Column(Boolean, nullable=False) + last_login_at = Column(DateTime()) + current_login_at = Column(DateTime()) + last_login_ip = Column(String(100)) + current_login_ip = Column(String(100)) + login_count = Column(Integer) + fs_uniquifier = Column(String, unique=True, nullable=False) + roles = relationship("Role", secondary="users_roles") + + +class Role(Base, RoleMixin): + __tablename__ = "roles" + role_id = Column(Integer, primary_key=True) + name = Column(String, unique=True) + description = Column(String) + permissions = Column(UnicodeText) + + +class UsersRoles(Base): + __tablename__ = "users_roles" + user_role_id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey("users.user_id")) + role_id = Column(Integer, ForeignKey("roles.role_id")) class AllQuestions(Base): @@ -91,6 +124,9 @@ class MultipleChoice(Base): return f"" +Base.metadata.create_all(engine) + + def add_multiple_choice_question(question, answer, addresses, difficulty, hint, wrong_answers): session: sqlalchemy.orm.session.Session = Session() question_id = session.query(AllQuestions).count() @@ -140,7 +176,6 @@ def get_random_hidden_answer(difficulty: Optional[Literal[1, 2, 3]] = None) -> H def get_random_multiple_choice(difficulty: Optional[Literal[1, 2, 3]] = None) -> MultipleChoice: session: sqlalchemy.orm.session.Session = Session() - print(type(session)) if difficulty is not None: return session.query(MultipleChoice).filter(MultipleChoice.difficulty == difficulty).order_by(func.random()).first() return session.query(MultipleChoice).order_by(func.random()).first() diff --git a/main.py b/main.py index 8fa6ec5..191f54c 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,15 @@ import os -from flask import Flask, render_template, request, jsonify -from flask_security import Security +from flask import request, render_template, jsonify, Flask +from flask_security import Security, SQLAlchemySessionUserDatastore, RegisterForm import database from admin import admin app = Flask(__name__) -app.register_blueprint(admin.Admin) environment_configuration = os.environ['CONFIGURATION_SETUP'] app.config.from_object(environment_configuration) +user_datastore = SQLAlchemySessionUserDatastore(database.Session, database.User, database.Role) +security = Security(app, user_datastore) +app.register_blueprint(admin.Admin) @app.route("/") @@ -31,7 +33,7 @@ def hidden_answer_category(): @app.route("/category/multiple_choice") def multiple_choice_category(): - easy = database.get_random_multiple_choice(3) + easy = database.get_random_multiple_choice(1) easy.randomize_answer_list() medium = database.get_random_multiple_choice(2) medium.randomize_answer_list() @@ -54,5 +56,20 @@ def check_answer(): return jsonify(database.check_answer(question_id, answer)) +@app.errorhandler(404) +def error_404(e): + print(e) + return render_template("error.html", error_msg="The requested page can not be found.", error_code=404), 404 + + +@app.errorhandler(500) +def error_404(e): + print(e) + msg = "There was an error with the server." + if app.config["DEBUG"]: + msg = e + return render_template("error.html", error_msg=msg, error_code=500), 500 + + if __name__ == "__main__": app.run() diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..45e4858 Binary files /dev/null and b/static/favicon.ico differ diff --git a/templates/base.html b/templates/base.html index 7cc991e..7fa98c3 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,6 +3,7 @@ Quiz The Word - {{ title }} + diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..000ea20 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block body %} +
+

{{ error_code }}

+

{{ error_msg }}

+
+{% endblock %} \ No newline at end of file diff --git a/templates/security/login_user.html b/templates/security/login_user.html new file mode 100644 index 0000000..cb50f86 --- /dev/null +++ b/templates/security/login_user.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block body %} +
+
+ {{ login_user_form.hidden_tag() }} +
+ + +
+
+ + +
+
+ + +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/security/register_user.html b/templates/security/register_user.html new file mode 100644 index 0000000..35c7e3c --- /dev/null +++ b/templates/security/register_user.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block body %} +
+
+ {{ register_user_form.hidden_tag() }} +
+ + +
+
+ + +
+
+ + +
+ +
+
+{% endblock %} \ No newline at end of file