Merge branch 'main' into gamelimit
This commit is contained in:
commit
c9541ae89e
@ -10,3 +10,4 @@ DB_PORT=3306
|
||||
DB_USER=""
|
||||
DB_PASSWORD=""
|
||||
DB_DBNAME=""
|
||||
ADMIN_PASSWORD="s0meV3ryL0ngP@sswOrd"
|
@ -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__()
|
@ -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]
|
||||
|
@ -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/<npc_id>")
|
||||
@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)
|
||||
|
@ -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():
|
||||
|
@ -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}
|
@ -34,7 +34,6 @@ def lobbyRedirect():
|
||||
@routes_ui.route("/lobby/<game_id>")
|
||||
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)
|
||||
|
@ -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"})
|
||||
}
|
||||
|
||||
|
9
truthinquiry/templates/admin/auth.html
Normal file
9
truthinquiry/templates/admin/auth.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% if failed %}
|
||||
<p style="color: red"> Invalid password !</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="/api/v1/admin/auth" method="POST">
|
||||
<p>Password :</p>
|
||||
<input name="password">
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
@ -3,27 +3,33 @@
|
||||
<head>
|
||||
<title>NPC</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="admin_ui.css">
|
||||
<link rel="stylesheet" href="/static/css/admin_ui.css">
|
||||
<script src="/static/js/api.js"></script>
|
||||
<script src="/static/js/admin.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/admin"> go Back </a> <br>
|
||||
|
||||
<section>
|
||||
<span>Npc name: </span>
|
||||
<input value="{{ npc.get('name') or ''}}">
|
||||
<img href="{{npc.get('img')}}">
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>Answers:</p>
|
||||
{%for answer_type in npc.get("answers") or []%}
|
||||
<section>
|
||||
{%for answer in answer_type%}
|
||||
<input value="{{answer}}">
|
||||
<section id="npc">
|
||||
<section>
|
||||
<span>Npc name: </span>
|
||||
<input id="npc_id" value="{{ npc.get('id') or ''}}" hidden>
|
||||
<input id="npc_name" value="{{ npc.get('name') or ''}}">
|
||||
<img href="{{npc.get('img')}}">
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>Answers:</p>
|
||||
{%for answer_type in npc.get("answers") or []%}
|
||||
<section class="answerType">
|
||||
{%for answer in answer_type%}
|
||||
<input value="{{answer}}">
|
||||
{%endfor%}
|
||||
</section>
|
||||
{%endfor%}
|
||||
</section>
|
||||
{%endfor%}
|
||||
</section>
|
||||
|
||||
<button onclick="saveFormNpc()">Save changes</button>
|
||||
</body>
|
||||
</html>
|
||||
|
20
truthinquiry/utils.py
Normal file
20
truthinquiry/utils.py
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user