Initial version
This commit is contained in:
commit
73ef9bcfaa
16
README.md
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Sample application for verifying attendance through a unique QR code
|
||||
|
||||
To run in a Python virtual environment, using **Flask**:
|
||||
|
||||
```bash
|
||||
mkvenv flask
|
||||
venv flask
|
||||
pip install Flask Flask-QRcode Flask-UUID
|
||||
```
|
||||
|
||||
To create an empty `database.db` sqlite file with the table described in
|
||||
`schema.sql`:
|
||||
|
||||
```bash
|
||||
./init_db.py
|
||||
```
|
||||
BIN
__pycache__/main.cpython-310.pyc
Normal file
BIN
__pycache__/main.cpython-310.pyc
Normal file
Binary file not shown.
BIN
database.db
Normal file
BIN
database.db
Normal file
Binary file not shown.
10
html/index.html
Normal file
10
html/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi!</h1>
|
||||
<p>OK.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
12
init_db.py
Executable file
12
init_db.py
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sqlite3
|
||||
|
||||
connection = sqlite3.connect('database.db')
|
||||
|
||||
with open('schema.sql') as f:
|
||||
connection.executescript(f.read())
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
60
main.py
Executable file
60
main.py
Executable file
@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import uuid
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from flask import Flask, request, render_template
|
||||
from flask_uuid import FlaskUUID
|
||||
from flask_qrcode import QRcode
|
||||
# TODO: use sockets to broadcast when a hit to the expected uuid has been
|
||||
# received, so that connected clients (the Chromium public browser) can
|
||||
# refresh the qr code and update the list of users
|
||||
# https://flask-socketio.readthedocs.io
|
||||
|
||||
app = Flask(__name__, static_folder='html')
|
||||
FlaskUUID(app)
|
||||
QRcode(app)
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('database.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
@app.route('/')
|
||||
def show_qr_and_list():
|
||||
# TODO: reject direct connections to server; allow access only via proxy
|
||||
conn = get_db_connection()
|
||||
data = conn.execute('SELECT * FROM hits ORDER BY id DESC LIMIT 10').fetchall()
|
||||
conn.close()
|
||||
|
||||
# TODO: next_ uuid should be in global state, to verify it when it's received
|
||||
return render_template("template.html",
|
||||
next_uuid=str(uuid.uuid1()),
|
||||
hits=data)
|
||||
|
||||
@app.route('/<uuid:id>')
|
||||
def catch_uuids(id):
|
||||
ua = request.headers.get('User-Agent')
|
||||
# TODO: use the Remote-User header that Authelia should set after authentication
|
||||
# (check if "Authorization" in request.headers, or request.authorization)
|
||||
time = datetime.now().strftime("%A %Y-%m-%d %H:%M:%S")
|
||||
error = None
|
||||
conn = get_db_connection()
|
||||
existing = conn.execute(
|
||||
'SELECT * FROM hits WHERE uuid = ?', (str(id),)).fetchone()
|
||||
# TODO: verify that the uuid was generated by us (otherwise any uuid,
|
||||
# like one generated by the user, would be accepted)
|
||||
if existing:
|
||||
error = 'ALREADY_USED'
|
||||
else:
|
||||
data = conn.execute(
|
||||
'SELECT * FROM hits WHERE user = ? ORDER BY id DESC LIMIT 10', (ua,)
|
||||
).fetchall()
|
||||
conn.execute("INSERT INTO hits (uuid, user) VALUES (?, ?)", (str(id), ua))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return render_template('thanks.html', user=ua, time=time, error=error, hits=data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@ -0,0 +1,10 @@
|
||||
click==8.1.3
|
||||
Flask==2.1.3
|
||||
Flask-QRcode==3.1.0
|
||||
Flask-UUID==0.2
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.2
|
||||
MarkupSafe==2.1.1
|
||||
Pillow==9.2.0
|
||||
qrcode==7.3.1
|
||||
Werkzeug==2.2.1
|
||||
9
schema.sql
Normal file
9
schema.sql
Normal file
@ -0,0 +1,9 @@
|
||||
DROP TABLE IF EXISTS hits;
|
||||
|
||||
CREATE TABLE hits (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
uuid TEXT NOT NULL,
|
||||
user TEXT NOT NULL
|
||||
);
|
||||
|
||||
18
templates/template.html
Normal file
18
templates/template.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>QR Code:</h1>
|
||||
<p>For URL <a href="{{ request.url_root + next_uuid }}">{{ request.url_root + next_uuid }}</a></p>
|
||||
<img src="{{ qrcode(request.url_root + next_uuid, box_size=12, border=5) }}">
|
||||
<h1>Last 10 users:</h1>
|
||||
<li>
|
||||
{% for hit in hits %}
|
||||
<ul>{{ hit['created'] }}, {{ hit['user'] }}</ul>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
23
templates/thanks.html
Normal file
23
templates/thanks.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Example</title>
|
||||
</head>
|
||||
<body>
|
||||
{% if error %}
|
||||
<h1>Error</h1>
|
||||
<p>Code has been used already.</p>
|
||||
{% else %}
|
||||
<h1>Thanks</h1>
|
||||
<p><strong>At {{ time }}</strong>,</p>
|
||||
<p>Registered (entrance? exit?) for user {{ user }} .</p>
|
||||
<h2>Last 10 registers for {{ user }}:</h2>
|
||||
<li>
|
||||
{% for hit in hits %}
|
||||
<ul>{{ hit['created'] }}</ul>
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user