first commit
This commit is contained in:
parent
2d16051195
commit
11ecbe1b51
0
__init__.py
Normal file
0
__init__.py
Normal file
292
app.py
Normal file
292
app.py
Normal file
@ -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/<int:roll_id>", 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/<int:roll_id>/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)
|
13
etc/ludo/ludo.service
Normal file
13
etc/ludo/ludo.service
Normal file
@ -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
|
5
etc/ludo/uwsgi.ini
Normal file
5
etc/ludo/uwsgi.ini
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[uwsgi]
|
||||||
|
socket = :9092
|
||||||
|
protocol = http
|
||||||
|
wsgi-file = /sites/ludo/app.py
|
||||||
|
plugin = python3
|
2
requirement.txt
Normal file
2
requirement.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
flask
|
||||||
|
flask-sqlalchemy
|
25
static/style.css
Normal file
25
static/style.css
Normal file
@ -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;
|
||||||
|
}
|
82
templates/index.html
Normal file
82
templates/index.html
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{% extends "master.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% if session.logged %}
|
||||||
|
<div class="new_roll">
|
||||||
|
<h3>Nouveau tirage :</h3>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true, category_filter=("create_roll",)) %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class=flashes>
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<li class="{{ category }}">{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<form method="POST" action="/rolls">
|
||||||
|
<label for="name">Nom :</label>
|
||||||
|
<input type="text" name="name"/><br/>
|
||||||
|
<label for="name">Date :</label>
|
||||||
|
<input type="date" name="roll_date"/><br/>
|
||||||
|
<label for="name">Heure :</label>
|
||||||
|
<input type="time" name="roll_time"/><br/>
|
||||||
|
<input type="submit" name="submit" value="create"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
{% endif%}
|
||||||
|
<h3>Liste des tirages :</h3>
|
||||||
|
{% for roll in rolls %}
|
||||||
|
<div class="roll">
|
||||||
|
{% if session.logged and roll.winner == None %}
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true, category_filter=(roll.id,)) %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class=flashes>
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<li class="{{ category }}">{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<form method="POST" action="/rolls/{{roll.id}}">
|
||||||
|
<label for="name">Nom :</label>
|
||||||
|
<input type="text" name="name" value="{{roll.name}}"/><br/>
|
||||||
|
<label for="name">Date :</label>
|
||||||
|
<input type="date" name="roll_date" value="{{roll.roll_date}}"/><br/>
|
||||||
|
<label for="name">Heure :</label>
|
||||||
|
<input type="time" name="roll_time" value="{{roll.roll_time}}"/><br/>
|
||||||
|
<input type="submit" name="submit" value="update"/>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<h4 class="roll_title">
|
||||||
|
{{ roll.name }} le {{roll.print_datetime}}
|
||||||
|
</h4>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if roll.winner != None %}
|
||||||
|
<div class="winner_div">
|
||||||
|
Lea gagnant⋅e de ce tirage est : <span class="winner_name">{{ roll.winner.name }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="subs">
|
||||||
|
<ol>
|
||||||
|
{% for sub in roll.subs %}
|
||||||
|
<li>
|
||||||
|
{{ sub.name }}{% if not roll.winner %} <a href="/unsubscribe?sub={{sub.id}}">désinscrire</a>{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
{% if session.logged and roll.winner == None %}
|
||||||
|
<div>
|
||||||
|
<form method="POST" action="/rolls/{{roll.id}}/subscribe">
|
||||||
|
<label for="name">Inscrire un participant :</label>
|
||||||
|
<input type="text" name="name" />
|
||||||
|
<input type="submit" name="submit" value="Inscrire"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
28
templates/master.html
Normal file
28
templates/master.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Luto de La Lune d'Argent</title>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div id="login-form">
|
||||||
|
{% if session.logged %}
|
||||||
|
<a href="/logout?callback={{request.path}}">logout</a>
|
||||||
|
{% else %}
|
||||||
|
<form method="POST" action="/login">
|
||||||
|
<input type="text" name="login" placeholder="Login" />
|
||||||
|
<input type="password" name="password" placeholder="Password"/>
|
||||||
|
<input type="hidden" name="callback" value="{{request.path}}" />
|
||||||
|
<input type="submit" name="submit" value="Se connecter"/>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div id="main">
|
||||||
|
{% block main %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user