From 11ecbe1b51d98e07640fc225b350ea46cdd44900 Mon Sep 17 00:00:00 2001 From: meewan Date: Sun, 3 Apr 2022 14:19:54 +0200 Subject: [PATCH] first commit --- __init__.py | 0 app.py | 292 ++++++++++++++++++++++++++++++++++++++++++ etc/ludo/ludo.service | 13 ++ etc/ludo/uwsgi.ini | 5 + requirement.txt | 2 + static/style.css | 25 ++++ templates/index.html | 82 ++++++++++++ templates/master.html | 28 ++++ 8 files changed, 447 insertions(+) create mode 100644 __init__.py create mode 100644 app.py create mode 100644 etc/ludo/ludo.service create mode 100644 etc/ludo/uwsgi.ini create mode 100644 requirement.txt create mode 100644 static/style.css create mode 100644 templates/index.html create mode 100644 templates/master.html diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app.py b/app.py new file mode 100644 index 0000000..be1b47d --- /dev/null +++ b/app.py @@ -0,0 +1,292 @@ +import os +from time import sleep +from hashlib import pbkdf2_hmac +from configparser import ConfigParser +from getpass import getpass +from binascii import hexlify +from datetime import datetime +import random +from flask import ( + Flask, + flash, + request, + render_template, + redirect, + send_from_directory, + session, +) +from flask_sqlalchemy import SQLAlchemy + +CONFIG_PATH = "./config.ini" + +application = Flask(__name__) +if os.path.exists(CONFIG_PATH): + config = ConfigParser() + config.read(CONFIG_PATH) + application.secret_key = config.get('ludo', 'secret_key').encode('utf-8') +application.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///ludo.db' +db = SQLAlchemy(application) + + +class User(db.Model): + __tablename__ = 'user' + id = db.Column(db.Integer, primary_key=True) + is_admin = db.Column(db.Boolean, nullable=False, default=False) + name = db.Column(db.String(120), unique=True, nullable=False) + password = db.Column(db.String(200), nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) + + +class Sub(db.Model): + __tablename__ = 'sub' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(80), unique=False, nullable=False) + roll_id = db.Column( + db.Integer, db.ForeignKey('roll.id'), nullable=False + ) + roll = db.relationship('Roll', back_populates="subs", foreign_keys=[roll_id]) + + +class Roll(db.Model): + __tablename__ = 'roll' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(80), unique=False, nullable=False) + roll_datetime = db.Column(db.DateTime, unique=False, nullable=False) + subs = db.relationship('Sub', back_populates="roll", foreign_keys=[Sub.roll_id]) + winner_id = db.Column(db.Integer, db.ForeignKey('sub.id'), nullable=True) + winner = db.relationship('Sub', foreign_keys=[winner_id]) + + @property + def roll_date(self): + return self.roll_datetime.strftime('%Y-%m-%d') + + @property + def roll_time(self): + return self.roll_datetime.strftime('%H:%M') + + @property + def print_datetime(self): + return self.roll_datetime.strftime('%d/%m/%Y à %Hh%M') + + +def create_user(name, password, is_admin, email): + salt = hexlify(os.urandom(8)) + password_hash = hexlify(pbkdf2_hmac( + 'sha256', + password.encode('utf-8'), + salt, + 100_000 + )) + passwd = f'sha256:100000:{salt.decode("utf-8")}:{password_hash.decode("utf-8")}' + user = User(name=name, password=passwd, email=email, is_admin=is_admin) + db.session.add(user) + return user + + +def check_password(password, db_hash): + algo, iter_count, salt, hashed = db_hash.split(":") + return hashed == hexlify( + pbkdf2_hmac( + algo, + password.encode('utf-8'), + salt.encode('utf-8'), + int(iter_count), + ) + ).decode('utf-8') + + +@application.route("/") +def index(): + to_roll = Roll.query.filter( + Roll.roll_datetime < datetime.now(), + Roll.winner == None + ) + for roll in to_roll: + if not roll.subs: + Roll.filter_by(id=roll.id).delete() + else: + winner = random.choice(roll.subs) + roll.winner = winner + db.session.commit() + + rolls = Roll.query.order_by(Roll.roll_datetime.desc()).all() + return render_template("index.html", rolls=rolls) + + +@application.route("/rolls", methods=['POST']) +def rolls(): + if session.get("logged", False): + clean = True + if "roll_date" not in request.form or not request.form['roll_date']: + flash('La date de tirage est obligatoire', category='create_roll') + clean = False + if "roll_time" not in request.form or not request.form['roll_time']: + flash('L\'heure de tirage est obligatoire', category='create_roll') + clean = False + if "name" not in request.form or not request.form['name']: + flash('Le nom du tirage est obligatoire', category='create_roll') + clean = False + if not clean: + return redirect('/') + name = request.form['name'] + try: + roll_datetime = datetime.strptime( + f"{request.form['roll_date']} {request.form['roll_time']}", + "%Y-%m-%d %H:%M" + ) + except ValueError: + flash( + 'La date ou l\'heure n\'est pas au bon format', + category='create_roll' + ) + return redirect('/') + if roll_datetime < datetime.now(): + flash( + 'Le date de tirage ne peut pas être dans le passé', + category='create_roll' + ) + return redirect('/') + roll = Roll(name=name, roll_datetime=roll_datetime) + db.session.add(roll) + db.session.commit() + return redirect('/') + + +@application.route("/rolls/", methods=['POST']) +def rolls_update(roll_id): + if session.get("logged", False): + roll = Roll.query.filter_by(id=roll_id).one_or_none() + clean = True + if "roll_date" not in request.form or not request.form['roll_date']: + flash('La date de tirage est obligatoire', category=roll_id) + clean = False + if "roll_time" not in request.form or not request.form['roll_time']: + flash('L\'heure de tirage est obligatoire', category=roll_id) + clean = False + if "name" not in request.form or not request.form['name']: + flash('Le nom du tirage est obligatoire', category=roll_id) + clean = False + if not roll: + clean = False + if roll and roll.roll_datetime > datetime.now(): + flash('Les tirages passé ne sont pas editables', category=roll_id) + clean = False + if not clean: + return redirect('/') + name = request.form['name'] + try: + roll_datetime = datetime.strptime( + f"{request.form['roll_date']} {request.form['roll_time']}", + "%Y-%m-%d %H:%M" + ) + except ValueError: + flash( + 'La date ou l\'heure n\'est pas au bon format', + category=roll_id + ) + return redirect('/') + if roll_datetime < datetime.now(): + flash( + 'Le date de tirage ne peut pas être dans le passé', + category=roll_id + ) + return redirect('/') + roll.roll_datetime = roll_datetime + roll.name = name + db.session.add(roll) + db.session.commit() + return redirect('/') + + +@application.route("/rolls//subscribe", methods=['POST']) +def subscribe(roll_id): + if session.get("logged", False): + if "name" not in request.form or not request.form['name']: + flash('Le nom du participant est obligatoire', category=f'subscribe_{roll_id}') + return redirect('/') + name = request.form['name'] + roll = Roll.query.filter(Roll.id == roll_id).one_or_none() + if not roll: + return redirect('/') + if Sub.query.filter_by(name=name, roll_id=roll_id).one_or_none(): + flash('Ce participant est deja inscrit', category=f'subscribe_{roll_id}') + elif roll.roll_datetime < datetime.now(): + flash('Il est trop tard pour inscrire un participant à ce tirage', category=f'subscribe_{roll_id}') + else: + sub = Sub(name=name, roll=roll) + db.session.add(sub) + db.session.commit() + + return redirect('/') + + +@application.route("/unsubscribe") +def unsubscribe(): + if session.get("logged", False): + if "sub" not in request.args or not request.args['sub']: + return redirect('/') + sub_id = request.args['sub'] + sub = Sub.query.filter_by(id=sub_id).one_or_none() + if sub: + if not sub.roll.roll_datetime < datetime.now(): + Sub.query.filter_by(id=sub_id).delete() + db.session.commit() + return redirect('/') + + +@application.route("/login", methods=['POST']) +def login(): + form = request.form + user = User.query.filter_by(name=form['login']).one_or_none() + if user: + if check_password(form['password'], user.password): + session['logged'] = True + return redirect(form['callback']) + sleep(random.random()) + return redirect(form['callback']) + + +@application.route("/logout") +def logout(): + del session['logged'] + return redirect(request.args['callback']) + + +@application.route("/style.css") +def send_style(): + return send_from_directory("static", "style.css") + + +@application.cli.command("setup") +def setup(): + config = ConfigParser() + if os.path.exists(CONFIG_PATH): + print('The app is already installed.') + config['ludo'] = { + 'secret_key': hexlify(os.urandom(32)).decode('utf-8') + } + db.create_all() + name = input("Admin login: ") + email = input("Admin email: ") + while True: + password1 = getpass(prompt='Password: ') + password2 = getpass(prompt='Retype your password: ') + if password1 == password2: + break + print("The passwords dont match") + create_user( + name=name, + password=password1, + email=email, + is_admin=True + ) + + with open(CONFIG_PATH, 'w') as configfile: + config.write(configfile) + + db.session.commit() + print('application setup done') + + +if __name__ == "__main__": + application.run(debug=True) diff --git a/etc/ludo/ludo.service b/etc/ludo/ludo.service new file mode 100644 index 0000000..8addbe6 --- /dev/null +++ b/etc/ludo/ludo.service @@ -0,0 +1,13 @@ +[Unit] +Description=Nextcloudregister a service to register in nextcloud +After=local-fs.target +Wants=network-online.target + +[Service] +Environment=PYTHONPATH=/usr/lib/python3/dist-packages/:/sites/ +ExecStart=/usr/bin/uwsgi-core --ini /etc/ludo/uwsgi.ini +User=ludo +Group=ludo + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/etc/ludo/uwsgi.ini b/etc/ludo/uwsgi.ini new file mode 100644 index 0000000..84364d4 --- /dev/null +++ b/etc/ludo/uwsgi.ini @@ -0,0 +1,5 @@ +[uwsgi] +socket = :9092 +protocol = http +wsgi-file = /sites/ludo/app.py +plugin = python3 \ No newline at end of file diff --git a/requirement.txt b/requirement.txt new file mode 100644 index 0000000..c76484a --- /dev/null +++ b/requirement.txt @@ -0,0 +1,2 @@ +flask +flask-sqlalchemy \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..817b964 --- /dev/null +++ b/static/style.css @@ -0,0 +1,25 @@ +body { + margin: 0; +} +nav { + background-color: #d3d3d3; + padding: 0.5em; +} + +.new_roll{ + margin-bottom: 30px; +} + +#main{ + padding: 0.5em; +} + +.winner_div { + padding: 1em; + background-color: lightsteelblue; +} + +.winner_name { + font-size: 1.2em; + font-weight: 600; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c6192d2 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,82 @@ +{% extends "master.html" %} + +{% block main %} + {% if session.logged %} +
+

Nouveau tirage :

+ {% with messages = get_flashed_messages(with_categories=true, category_filter=("create_roll",)) %} + {% if messages %} +
    + {% for category, message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} +
+ +
+ +
+ +
+ +
+
+
+ {% endif%} +

Liste des tirages :

+ {% for roll in rolls %} +
+ {% if session.logged and roll.winner == None %} + {% with messages = get_flashed_messages(with_categories=true, category_filter=(roll.id,)) %} + {% if messages %} +
    + {% for category, message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} +
+ +
+ +
+ +
+ +
+ {% else %} +

+ {{ roll.name }} le {{roll.print_datetime}} +

+ {% endif %} + + {% if roll.winner != None %} +
+ Lea gagnant⋅e de ce tirage est : {{ roll.winner.name }} +
+ {% endif %} +
+
    + {% for sub in roll.subs %} +
  1. + {{ sub.name }}{% if not roll.winner %} désinscrire{% endif %} +
  2. + {% endfor %} +
+
+ {% if session.logged and roll.winner == None %} +
+
+ + + +
+
+
+ {% endif %} +
+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/templates/master.html b/templates/master.html new file mode 100644 index 0000000..e587b9e --- /dev/null +++ b/templates/master.html @@ -0,0 +1,28 @@ + + + + + Luto de La Lune d'Argent + + + + +
+ {% block main %} + {% endblock %} +
+ + \ No newline at end of file