diff --git a/.env.dist b/.env.dist index 864daf0..541254c 100644 --- a/.env.dist +++ b/.env.dist @@ -10,3 +10,4 @@ DB_PORT=3306 DB_USER="" DB_PASSWORD="" DB_DBNAME="" +ADMIN_PASSWORD="s0meV3ryL0ngP@sswOrd" \ No newline at end of file diff --git a/truthinquiry/ext/database/models.py b/truthinquiry/ext/database/models.py index f47cb99..957a8be 100644 --- a/truthinquiry/ext/database/models.py +++ b/truthinquiry/ext/database/models.py @@ -1,3 +1,5 @@ +import uuid + from sqlalchemy import Column, Integer, VARCHAR, Text, LargeBinary, ForeignKey from sqlalchemy.orm import relationship, declarative_base @@ -50,6 +52,27 @@ class Locale(Base): def __repr__(self) -> str: return self.__str__() + def get_texts(self, lang): + texts = [] + for text in self.TEXTS: + if text.LANG == lang: + texts.append(text) + return texts + + def get_text(self, lang, auto_create=False): + for text in self.TEXTS: + if text.LANG == lang: + return text + + if auto_create: + text = Text(None, None, lang, None) + self.TEXTS.append(text) + return text + else: + return None + + + class Place(Base): """ @@ -59,7 +82,7 @@ class Place(Base): __tablename__ = 'T_PLACE' PLACE_ID = Column(Integer, primary_key=True, autoincrement=True, comment="ID of this place") NAME_LID = Column(Integer, ForeignKey("T_LOCALE.LID"), comment="Place name") - LOCALE = relationship("Locale") + NAME_LOCALE = relationship("Locale") def __init__(self, PLACE_ID, NAME_LID): self.PLACE_ID = PLACE_ID @@ -80,7 +103,7 @@ class QuestionType(Base): __tablename__ = "T_QUESTION_TYPE" QUESTION_TYPE_ID = Column(Integer, default=0, primary_key=True, comment="ID of this question type.") TEXT_LID = Column(Integer, ForeignKey("T_LOCALE.LID"), comment="Question text") - LOCALE = relationship("Locale") + TEXT_LOCALE = relationship("Locale") def __init__(self, QUESTION_TYPE_ID, TEXT_LID): self.QUESTION_TYPE_ID = QUESTION_TYPE_ID @@ -103,7 +126,7 @@ class Answer(Base): QUESTION_TYPE_ID = Column(Integer, ForeignKey("T_QUESTION_TYPE.QUESTION_TYPE_ID"), primary_key=True, comment="Question type ID") NPC_ID = Column(Integer, ForeignKey("T_NPC.NPC_ID"), primary_key=True, comment="ID of the NPC that will say this answer") TEXT_LID = Column(Integer, ForeignKey("T_LOCALE.LID"), comment="Text of the answer") - LOCALE = relationship("Locale") + TEXT_LOCALE = relationship("Locale") NPC = relationship("Npc", backref="ANSWERS") def __init__(self, QUESTION_TYPE_ID, NPC_ID, TEXT_LID): @@ -128,7 +151,7 @@ class Npc(Base): NPC_ID = Column(Integer, autoincrement=True, primary_key=True, comment="ID of this Npc") NAME_LID = Column(Integer, ForeignKey("T_LOCALE.LID"), comment="Name of this Npc") DEFAULT_IMG = Column(LargeBinary(length=2**24), comment="Binary data of the default image of this Npc") - LOCALE = relationship("Locale") + NAME_LOCALE = relationship("Locale") def __init__(self, NPC_ID, NAME_LID): self.NPC_ID = NPC_ID @@ -150,8 +173,8 @@ class Trait(Base): NAME_LID = Column(Integer, ForeignKey("T_LOCALE.LID"), comment="Name of this trait") DESC_LID = Column(Integer, ForeignKey("T_LOCALE.LID"), comment="Description of this trait") - Name = relationship("Locale",foreign_keys=[NAME_LID]) - Desc = relationship("Locale",foreign_keys=[DESC_LID]) + NAME_LOCALE = relationship("Locale",foreign_keys=[NAME_LID]) + DESC_LOCALE = relationship("Locale",foreign_keys=[DESC_LID]) def __init__(self, TRAIT_ID, NAME_LID, DESC_LID): @@ -177,14 +200,16 @@ class Reaction(Base): IMG = Column(LargeBinary(length=2**24), comment="Binary data of the image associated to this npc and trait") NPC = relationship("Npc") TRAIT = relationship("Trait") + REACTION_UUID = Column(VARCHAR(255), unique=True, comment="ID of this reaction") def __init__(self, REACTION_ID, NPC_ID, TRAIT_ID): self.REACTION_ID = REACTION_ID self.NPC_ID = NPC_ID self.TRAIT_ID = TRAIT_ID + self.REACTION_UUID = uuid.uuid4() def __str__(self) -> str: - return f"Reaction(REACTION_ID={self.REACTION_ID}, NPC_ID={self.NPC_ID}, TRAIT_ID={self.TRAIT_ID})" + return f"Reaction(REACTION_ID={self.REACTION_ID}, NPC_ID={self.NPC_ID}, TRAIT_ID={self.TRAIT_ID}, REACTION_UUID={self.REACTION_UUID})" def __repr__(self) -> str: return self.__str__() \ No newline at end of file diff --git a/truthinquiry/logic/game_logic.py b/truthinquiry/logic/game_logic.py index 1e2b126..2e7bcb8 100644 --- a/truthinquiry/logic/game_logic.py +++ b/truthinquiry/logic/game_logic.py @@ -132,7 +132,6 @@ class Game: :param npc_id: the id of the npc, to get the reactions from, must be in the current game :return: the reaction image as bytes """ - print(self.reaction_table) if npc_id not in self.reaction_table: return None trait_id = self.reaction_table[npc_id] diff --git a/truthinquiry/routes/routes_admin.py b/truthinquiry/routes/routes_admin.py index a48ec4b..a4d23b7 100644 --- a/truthinquiry/routes/routes_admin.py +++ b/truthinquiry/routes/routes_admin.py @@ -3,16 +3,27 @@ from sqlalchemy import select, or_ from truthinquiry.ext.database.models import * from truthinquiry.ext.database.fsa import db +from truthinquiry.utils import require_admin + routes_admin = flask.Blueprint("admin", __name__) +DEFAULT_LANG = "FR" + @routes_admin.route("/") +@require_admin(ui=True) def index(): npcs_objs = db.session.query(Npc).all() - npcs_dicts = [{"id": npc_obj.NPC_ID, "name": npc_obj.LOCALE.TEXTS[0].TEXT} for npc_obj in npcs_objs] + npcs_dicts = [{"id": npc_obj.NPC_ID, "name": npc_obj.NAME_LOCALE.get_text(DEFAULT_LANG).TEXT} for npc_obj in npcs_objs] return flask.render_template("admin/index.html", npcs=npcs_dicts) +@routes_admin.route("/auth") +def auth(): + input_failed = bool(flask.request.values.get("failed")) + return flask.render_template("admin/auth.html", failed=input_failed) + @routes_admin.route("/npc/") +@require_admin(ui=True) def npc(npc_id): if npc_id == "new": return flask.render_template("admin/npc.html", npc={}) @@ -21,11 +32,12 @@ def npc(npc_id): npc_answers = [] for answer_type in npc_obj.ANSWERS: - answer_list = [answer.TEXT for answer in answer_type.LOCALE.TEXTS] + answer_list = [answer.TEXT for answer in answer_type.TEXT_LOCALE.TEXTS] npc_answers.append(answer_list) npc_dict = { - "name": npc_obj.LOCALE.TEXTS[0].TEXT, + "id": npc_obj.NPC_ID, + "name": npc_obj.NAME_LOCALE.get_text(DEFAULT_LANG).TEXT, "img": npc_obj.NPC_ID, "answers": npc_answers, } @@ -33,8 +45,9 @@ def npc(npc_id): return flask.render_template("admin/npc.html", npc=npc_dict) @routes_admin.route("/questions") +@require_admin(ui=True) def questions(): - lang = "FR" + lang = DEFAULT_LANG results = db.session.execute( select(QuestionType, Text) @@ -59,13 +72,19 @@ def questions(): return flask.render_template("admin/questions.html", questions=data, langs=["FR", "EN"]) @routes_admin.route("/places") +@require_admin(ui=True) def places(): + lang = DEFAULT_LANG + places_objs = db.session.query(Place).all() - places_dicts = [{"id": place_obj.PLACE_ID, "name": place_obj.LOCALE.TEXTS[0].TEXT} for place_obj in places_objs] + places_dicts = [{"id": place_obj.PLACE_ID, "name": place_obj.NAME_LOCALE.get_text(lang).TEXT} for place_obj in places_objs] return flask.render_template("admin/places.html", places=places_dicts) @routes_admin.route("/traits") +@require_admin(ui=True) def traits(): + lang = DEFAULT_LANG + traits_objs = db.session.query(Trait).all() - traits_dicts = [{"id": trait_obj.TRAIT_ID, "name": trait_obj.Name.TEXTS[0].TEXT, "desc": trait_obj.Desc.TEXTS[0].TEXT} for trait_obj in traits_objs] + traits_dicts = [{"id": trait_obj.TRAIT_ID, "name": trait_obj.NAME_LOCALE.get_text(lang).TEXT, "desc": trait_obj.DESC_LOCALE.get_text(lang).TEXT} for trait_obj in traits_objs] return flask.render_template("admin/traits.html", traits=traits_dicts) diff --git a/truthinquiry/routes/routes_api.py b/truthinquiry/routes/routes_api.py index 6e3e878..5e17f05 100644 --- a/truthinquiry/routes/routes_api.py +++ b/truthinquiry/routes/routes_api.py @@ -1,8 +1,12 @@ import json -import json +import io + import flask import os +from sqlalchemy import select +from truthinquiry.ext.database.models import * +from truthinquiry.ext.database.fsa import db from truthinquiry.ext.discord_bot import discord_bot from truthinquiry.ext.socketio import socket_io from truthinquiry.logic import game_logic @@ -157,6 +161,20 @@ def get_npc_reaction(): 'Content-Disposition', 'attachment', filename='reaction.png') return response +@routes_api.route("/getReaction", methods=["GET", "POST"]) +def get_reaction(): + input_uuid = flask.request.values.get("uuid") + results = db.session.execute(select(Reaction).where(Reaction.REACTION_UUID==input_uuid)) + + row = results.first() + if row == None: + return {"error": 1, "msg": "No such reaction"} + reaction_obj = row[0] + + return flask.send_file(io.BytesIO(reaction_obj.IMG), mimetype='image/png') + + + @routes_api.route("/gameProgress", methods=["GET", "POST"]) def game_progress(): diff --git a/truthinquiry/routes/routes_api_admin.py b/truthinquiry/routes/routes_api_admin.py index 42a29db..0e6e8ff 100644 --- a/truthinquiry/routes/routes_api_admin.py +++ b/truthinquiry/routes/routes_api_admin.py @@ -1,13 +1,26 @@ +import os + import flask from sqlalchemy import select, delete, or_ from truthinquiry.ext.database.models import * from truthinquiry.ext.database.fsa import db +from truthinquiry.utils import require_admin routes_api_admin = flask.Blueprint("api_admin", __name__) +@routes_api_admin.route("/auth", methods=["GET", "POST"]) +def auth(): + password = flask.request.values.get("password") + if password == os.getenv("ADMIN_PASSWORD"): + flask.session["admin"] = True + return flask.redirect("/admin") + else: + return flask.redirect("/admin/auth?failed=1") + @routes_api_admin.route("/setQuestions", methods=["GET", "POST"]) +@require_admin(api=True) def set_questions(): if not flask.request.json: return {"error": 1, "msg": "no json set"} @@ -44,6 +57,7 @@ def set_questions(): return {"error": 0} @routes_api_admin.route("/setTraits", methods=["GET", "POST"]) +@require_admin(api=True) def set_traits(): input_lang = flask.request.json["lang"] input_traits = flask.request.json["traits"] @@ -57,10 +71,10 @@ def set_traits(): # modify db_trait = list(filter(lambda db_trait: db_trait.TRAIT_ID == int(input_trait["id"]), db_traits))[0] - db.session.delete(db_trait.Name.TEXTS[0]) - db.session.delete(db_trait.Desc.TEXTS[0]) - db_trait.Name.TEXTS = [Text(None, None, input_lang, input_trait["name"])] - db_trait.Desc.TEXTS = [Text(None, None, input_lang, input_trait["desc"])] + db.session.delete(db_trait.NAME_LOCALE.get_text(input_lang)) + db.session.delete(db_trait.DESC_LOCALE.get_text(input_lang)) + db_trait.NAME_LOCALE.TEXTS = [Text(None, None, input_lang, input_trait["name"])] + db_trait.DESC_LOCALE.TEXTS = [Text(None, None, input_lang, input_trait["desc"])] db.session.add(db_trait) modified_db_traits.append(db_trait) @@ -68,11 +82,11 @@ def set_traits(): # add new_trait = Trait(None, None, None) - new_trait.Name = Locale(None) - new_trait.Desc = Locale(None) + new_trait.NAME_LOCALE = Locale(None) + new_trait.DESC_LOCALE = Locale(None) - new_trait.Name.TEXTS.append(Text(None, None, input_lang, input_trait["name"])) - new_trait.Desc.TEXTS.append(Text(None, None, input_lang, input_trait["desc"])) + new_trait.NAME_LOCALE.TEXTS.append(Text(None, None, input_lang, input_trait["name"])) + new_trait.DESC_LOCALE.TEXTS.append(Text(None, None, input_lang, input_trait["desc"])) db.session.add(new_trait) @@ -86,6 +100,7 @@ def set_traits(): return {"error": 0} @routes_api_admin.route("/setPlaces", methods=["GET", "POST"]) +@require_admin(api=True) def set_places(): input_lang = flask.request.json["lang"] input_places = flask.request.json["places"] @@ -99,9 +114,9 @@ def set_places(): # modify db_place = list(filter(lambda db_place: db_place.PLACE_ID == int(input_place["id"]), db_places))[0] - db.session.delete(db_place.LOCALE.TEXTS[0]) + db.session.delete(db_place.NAME_LOCALE.get_text(input_lang)) - db_place.LOCALE.TEXTS = [Text(None, None, input_lang, input_place["name"])] + db_place.NAME_LOCALE.TEXTS = [Text(None, None, input_lang, input_place["name"])] db.session.add(db_place) modified_db_places.append(db_place) @@ -109,8 +124,8 @@ def set_places(): # add new_place = Place(None, None) - new_place.LOCALE = Locale(None) - new_place.LOCALE.TEXTS = [Text(None, None, input_lang, input_place["name"])] + new_place.NAME_LOCALE = Locale(None) + new_place.NAME_LOCALE.TEXTS = [Text(None, None, input_lang, input_place["name"])] db.session.add(new_place) @@ -121,4 +136,29 @@ def set_places(): db.session.commit() + return {"error": 0} + +@routes_api_admin.route("/setNpc", methods=["GET", "POST"]) +@require_admin(api=True) +def set_npc(): + input_lang = flask.request.json["lang"] + input_npc = flask.request.json["npc"] + + if input_npc["id"] == None: + npc_obj = Npc(None, None) + db.session.add(npc_obj) + else: + npc_obj = db.session.get(Npc, input_npc["id"]) + + npc_obj.NAME_LOCALE.get_text(input_lang, True).TEXT = input_npc["name"] + + for answer_type, input_answer_type in zip(npc_obj.ANSWERS, input_npc["allAnswers"]): + for text in answer_type.TEXT_LOCALE.get_texts(input_lang): + db.session.delete(text) + for input_answer in input_answer_type["answers"]: + answer_type.TEXT_LOCALE.TEXTS.append(Text(None, None, input_lang, input_answer["text"])) + + + db.session.commit() + return {"error": 0} \ No newline at end of file diff --git a/truthinquiry/routes/routes_ui.py b/truthinquiry/routes/routes_ui.py index a1410fe..e2ffd18 100644 --- a/truthinquiry/routes/routes_ui.py +++ b/truthinquiry/routes/routes_ui.py @@ -34,7 +34,6 @@ def lobbyRedirect(): @routes_ui.route("/lobby/") def lobby(game_id): # rendered by the javascript client-side - print(game_id) if game_id is None: return flask.redirect("") return flask.render_template("lobby.html", gameid=game_id) diff --git a/truthinquiry/static/js/admin.js b/truthinquiry/static/js/admin.js index 1923bd7..d8e30cb 100644 --- a/truthinquiry/static/js/admin.js +++ b/truthinquiry/static/js/admin.js @@ -92,3 +92,27 @@ function changeLang(){ } +//functions for npc.html + +function saveFormNpc(){ + let data = {} + + data["id"] = npc.querySelector("#npc_id").value; + data["name"] = npc.querySelector("#npc_name").value; + + let allAnswersJson = []; + data["allAnswers"] = allAnswersJson; + + for(let answerTypeNode of npc.querySelectorAll(".answerType")){ + let answersJson = []; + let answerTypeJson = {"answers": answersJson}; + allAnswersJson.push(answerTypeJson); + + for(let answerNode of answerTypeNode.querySelectorAll("input")){ + answersJson.push({"text": answerNode.value}) + } + } + + makeAPIRequest("admin/setNpc", {"npc": data, "lang": "FR"}, {"content": "json"}) +} + diff --git a/truthinquiry/templates/admin/auth.html b/truthinquiry/templates/admin/auth.html new file mode 100644 index 0000000..66dd733 --- /dev/null +++ b/truthinquiry/templates/admin/auth.html @@ -0,0 +1,9 @@ +{% if failed %} +

Invalid password !

+{% endif %} + +
+

Password :

+ + +
\ No newline at end of file diff --git a/truthinquiry/templates/admin/npc.html b/truthinquiry/templates/admin/npc.html index f4a03b7..3f04ba7 100644 --- a/truthinquiry/templates/admin/npc.html +++ b/truthinquiry/templates/admin/npc.html @@ -3,27 +3,33 @@ NPC - + + go Back
-
- Npc name: - - -
- -
-

Answers:

- {%for answer_type in npc.get("answers") or []%} -
- {%for answer in answer_type%} - +
+
+ Npc name: + + + +
+ +
+

Answers:

+ {%for answer_type in npc.get("answers") or []%} +
+ {%for answer in answer_type%} + + {%endfor%} +
{%endfor%}
- {%endfor%}
+ + diff --git a/truthinquiry/utils.py b/truthinquiry/utils.py new file mode 100644 index 0000000..9359137 --- /dev/null +++ b/truthinquiry/utils.py @@ -0,0 +1,20 @@ +from functools import wraps + +import flask + +def require_admin(*args, **kwargs): + def decorator(route): + @wraps(route) + def decorated_function(*route_args, **route_kwargs): + + if flask.session.get("admin"): + return route(*route_args, **route_kwargs) + elif kwargs.get("api"): + return {"error": 1, "msg": "Invalid authentication"} + elif kwargs.get("ui"): + return flask.redirect("/admin/auth") + else: + raise ValueError("Can't determine request type") + + return decorated_function + return decorator