Browse Source

resolve issue #20

testing
Burathar 4 years ago
parent
commit
ea4d5189cd
  1. 21
      app/auth/routes.py
  2. 18
      app/main/routes.py
  3. 3
      app/models/user.py
  4. 30
      app/templates/game_player.html
  5. 1
      documentation/database/database_schema1.4.2.drawio
  6. BIN
      documentation/database/database_schema1.4.2.png
  7. 1
      documentation/database/entity_relationship_diagram_v1.4.2.drawio
  8. BIN
      documentation/database/entity_relationship_diagram_v1.4.2.png
  9. 7
      migrations/versions/fb12ee70fe66_reset_migrations.py

21
app/auth/routes.py

@ -1,5 +1,7 @@
from flask import render_template, flash, redirect, url_for from datetime import datetime
from flask import render_template, flash, redirect, url_for, abort
from flask_login import login_user, logout_user, current_user, login_required from flask_login import login_user, logout_user, current_user, login_required
from app import db from app import db
from app.auth import bp from app.auth import bp
from app.models import User from app.models import User
@ -16,6 +18,7 @@ def login():
flash('Invalid username or password') flash('Invalid username or password')
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data) login_user(user, remember=form.remember_me.data)
user.last_login = datetime.utcnow()
return redirect(url_for('main.index')) return redirect(url_for('main.index'))
return render_template('auth/login.html', title='Sign In', form=form) return render_template('auth/login.html', title='Sign In', form=form)
@ -34,8 +37,24 @@ def register():
user = User(name=form.username.data) user = User(name=form.username.data)
user.set_password(form.password.data) user.set_password(form.password.data)
user.set_auth_hash() user.set_auth_hash()
user.last_login = datetime.utcnow()
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
flash('Congratulations, you are now a registered user!') flash('Congratulations, you are now a registered user!')
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
return render_template('auth/register.html', title='Register', form=form) return render_template('auth/register.html', title='Register', form=form)
@bp.route('/generate_auth_hash/<username>')
def generate_auth_hash(username):
user = User.query.filter(User.name == username).first_or_404()
if not current_user.owns_game_played_by(user):
abort(403)
if user.auth_hash:
flash('Auth hash is already generated')
abort(403)
if user.last_login:
flash('After a player has logged in, it is no longer possible to generate a QR code.')
abort(403)
user.set_auth_hash()
db.session.commit()
return 'nothing'

18
app/main/routes.py

@ -1,14 +1,22 @@
import json import json
from datetime import datetime
from io import BytesIO
import qrcode import qrcode
from flask import render_template, flash, redirect, url_for, request, abort, send_file from flask import render_template, flash, redirect, url_for, request, abort, send_file
from flask_login import current_user, login_required from flask_login import current_user, login_required
from sqlalchemy import and_ from sqlalchemy import and_
from io import BytesIO
from app import db from app import db
from app.main import bp from app.main import bp
from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder
from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, PlayerUpdateForm from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, PlayerUpdateForm
@bp.before_app_request
def before_request():
if current_user.is_authenticated:
current_user.last_login = datetime.utcnow()
db.session.commit()
@bp.route('/') @bp.route('/')
@bp.route('/index') @bp.route('/index')
@login_required @login_required
@ -108,13 +116,19 @@ def user_qrcode(auth_hash):
user = User.query.filter_by(auth_hash=auth_hash).first_or_404() user = User.query.filter_by(auth_hash=auth_hash).first_or_404()
if not current_user.owns_game_played_by(user): if not current_user.owns_game_played_by(user):
abort(403) abort(403)
img = generate_qr_code(url_for('main.player', auth_hash=auth_hash, _external=True)) if user.last_login:
flash('After a player has logged in, it is no longer possible to request their QR code.')
abort(403)
img = generate_qr_code(url_for('main.user', auth_hash=auth_hash, _external=True))
return serve_pil_image(img) return serve_pil_image(img)
@bp.route('/user/<auth_hash>') @bp.route('/user/<auth_hash>')
@login_required @login_required
def user(auth_hash): def user(auth_hash):
user = User.query.filter_by(auth_hash=auth_hash).first_or_404() user = User.query.filter_by(auth_hash=auth_hash).first_or_404()
if user.password_hash:
flash('Please login with your username and password!')
abort(404)
return render_template('auth/user.html', title=f'User: {user.name}', user=user) return render_template('auth/user.html', title=f'User: {user.name}', user=user)
@bp.route('/game/<game_name>/add_objective', methods=['GET', 'POST']) @bp.route('/game/<game_name>/add_objective', methods=['GET', 'POST'])

3
app/models/user.py

@ -15,6 +15,7 @@ class User(UserMixin, db.Model):
name = db.Column(db.String(64), unique=True, nullable=False) name = db.Column(db.String(64), unique=True, nullable=False)
auth_hash = db.Column(db.String(32), unique=True, nullable=True) auth_hash = db.Column(db.String(32), unique=True, nullable=True)
password_hash = db.Column(db.String(128)) password_hash = db.Column(db.String(128))
last_login = db.Column(db.DateTime)
user_games = db.relationship( user_games = db.relationship(
'GamePlayer', 'GamePlayer',
@ -68,7 +69,7 @@ class User(UserMixin, db.Model):
def owns_game_played_by(self, player): def owns_game_played_by(self, player):
'''self is an owner of a game the player participates in''' '''self is an owner of a game the player participates in'''
return self in [gameplayer.player for gameplayers in return self in [gameplayer.user for gameplayers in
[game.game_players for game in player.games] [game.game_players for game in player.games]
for gameplayer in gameplayers if gameplayer.role == Role.owner] for gameplayer in gameplayers if gameplayer.role == Role.owner]

30
app/templates/game_player.html

@ -21,10 +21,17 @@
{{ wtf.form_field(form.submit, class='btn btn-primary', value='Update') }} {{ wtf.form_field(form.submit, class='btn btn-primary', value='Update') }}
</form> </form>
</div> </div>
{% if user.auth_hash %}
{% if user.auth_hash and not user.last_login %}
<div class="row"> <div class="row">
<img src="{{ url_for('main.user_qrcode', auth_hash=user.auth_hash) }}" alt="qr_code_failed", width="100%"> <img src="{{ url_for('main.user_qrcode', auth_hash=user.auth_hash) }}" alt="qr_code_failed", width="100%">
</div> </div>
{% elif not user.last_login %}
<br>
<div class="row">
<a href="#", id="generate_auth_hash">
<button class="btn btn-success">Generate Login Code</button></a>
</div>
{% endif %} {% endif %}
</div> </div>
<div id="map" style=" height: 600px; border-radius: 10px; " class="col-md-6 col-xs-12"></div> <div id="map" style=" height: 600px; border-radius: 10px; " class="col-md-6 col-xs-12"></div>
@ -60,7 +67,8 @@
shadowSize: [41, 41] shadowSize: [41, 41]
}); });
var locations = JSON.parse('{{ json.dumps(user.locations_during_game(game)), cls=location_encoder)|safe }}') var locations = JSON.parse('{{ json.dumps(user.locations_during_game(game), cls=location_encoder)|safe }}')
if (locations == null) {locations=[]}
for (var i = 0; i < locations.length; i++){ for (var i = 0; i < locations.length; i++){
var playerMarker = L.marker([ var playerMarker = L.marker([
locations[i]['latitude'], locations[i]['latitude'],
@ -80,9 +88,21 @@
[41.83, -87.62], [41.83, -87.62],
[32.76, -96.72]] [32.76, -96.72]]
]; ];
var polyline = L.polyline(locations.map(l => [l.latitude, l.longitude]), {color: 'red'}).addTo(map); if (locations.length > 0){
// zoom the map to the polyline var polyline = L.polyline(locations.map(l => [l.latitude, l.longitude]), {color: 'red'}).addTo(map);
map.fitBounds(polyline.getBounds()); // zoom the map to the polyline
map.fitBounds(polyline.getBounds());
}
//Ajax for Generate Login Code button
$(function() {
$('a#generate_auth_hash').bind('click', function() {
$.ajax({url: "{{ url_for('auth.generate_auth_hash', username=user.name) }}",
success: function(result) {
location.reload();
}});
});
});
</script> </script>
{% endblock %} {% endblock %}

1
documentation/database/database_schema1.4.2.drawio

@ -0,0 +1 @@
<mxfile host="office.sciuro.org" modified="2020-07-18T22:23:35.254Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" etag="b1UJ2q3xYko4xSObHrmu" version="12.8.6" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">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</diagram></mxfile>

BIN
documentation/database/database_schema1.4.2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

1
documentation/database/entity_relationship_diagram_v1.4.2.drawio

@ -0,0 +1 @@
<mxfile host="office.sciuro.org" modified="2020-07-18T22:25:24.207Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" etag="wvjCfHbN-iwSZYwNJAo7" version="12.8.6" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7V3dd6K6Fv9rXOveh3ZB+PSxddrpnOn09Ezb2/a+oURlBokDONX5609QopBEBeQjKl19kBgDZO/92zv7I+kovcn8s29Nx9+QDd0OkOx5R/nUAUBWAehE/5K9WLUYqrFqGPmOHXfaNDw5f2DcKMWtM8eGQapjiJAbOtN04wB5HhyEqTbL99FHutsQuem7Tq0RZBqeBpbLtr46djhetZrA2LTfQWc0JneW9e7qm4lFOsdvEowtG30kmpSbjtLzEQpXnybzHnSjySPz8vpl8ere/9Q///VP8Mt6uf76/PC/i9Vgt3l+sn4FH3ph4aHDh7v537/AnfJn/vZg3t45QbC4UFZD/7bcWTxf8buGCzKB0MbzGV8iPxyjEfIs92bTeu2jmWfD6DYSvtr0uUdoihtl3PgDhuEiZg5rFiLcNA4nbvwt9OyriNT40kMeXLXcOq4bDzlEXnhrTRw34sEemvkO9PFDPsCP+Mt4ZFmNfjp3wrfoh5dafPVOboM/f5rHYy4vFvFFxgmOCRHgJxjAHbNK+NzyRzDc0S+e7GiGE8wak+8zRBMY+gvcwYeuFTq/0xxtxYIxWvfbEB9/iOmfgxdklhm+HT03BJgIITXcsi0x4I/ZZEread3FRz9hD7nIX763Ii3/ls+C5zrBX9HlmsGiiw2HLa8WCX57S3x+T/KhSEwpm2VzZfzTR+TgFwFSrFpUKf5JrFiAqqeHWD1p/KsNb2NiWotEt2nUIchxH1OiRGU14kZw1u94gCwxovTZmsBH11pEvErJVFpiPsZOCJ+m1pKYH1gpp6VjiPk2wZe2Bc3hgMex+sCE/WFeecnHdL+hH8L5Ti6Jv9UAnwrSx0Ypy1LcNk4oZFWqCu50hkb3aIA5GnlnSSFZSxFIkbtNEwhwhegsiaNoafGRCag1Rh1VZajzgEJn6JyxCJnCEUli6JDXgOMYR0VsugJG+TZLa2Nb8S2tLCbfPhtzu/1XIaPtN+HUjDac0hVqZaGySP7AMCaWsTDNNJbrjDz8eYBnD0+ych1JIsYX9yr+YuLY9oplYeD8sfrLoSIixTYhHle77mifcItr9aF7bQ1+jpbsnaDscPnHId5OSrOU3SmADIasHR7xU3eSPgUetlxIl0CndHQ8rUUNcdIFDYcBDBkql2AGa5pg+CMn0GeDRfvwJ4U+GzBq8Wcr3VWh8Mc0BWND6dIoogdbPszJh6YhFB/KrM38d/8HVgLRnc/RYNa7olnMrKXCQofrOtMA7ieKFUxXEY2hM48ISVMJmx1gwKWSrfd1TReFShq1+FR01jWg1kmkbgYiNe+nXuP22jWcDbebRNaswFp65ILvu9VVivO0bnqIknzEhkzdx+xSTFqFj1hi2PhFzFXRAdy4U34PXhRJl5JsghTpjMPWRMTflh5UpkIT1a2YWAuhA3Q3YgHb+Y0/jqKP1zPPW5B2fJvEV60ZESkepWnftZFBtEVSUTmXFk2qKK1kFbXxr2iappeCHppUj9pSlRxqq06/D8kREmbB3fp96hA5EsEXZLmtsuuEc3E7E/1Tits57XQmXCOw15kh+91sScpztI3oyHGtthFUv856V/a1qvce8esbbwBcrVGnXkuoRkWwD+RLRmeSsJWEZ+7ES0KhM3nshJgu86Ns3Ha9YGV1jCb9WZBBTvNIHS3UGjRtlSfUJugrul6lnOqUK5QkEyTk1OSIqV5Z+k0W7cmyelFK8DMEdmdIFs3FrEEAd2Y07ZXABM01Ds1J26HLFMrrtc6UJEOs3pxZprAD6fR6hxpoy3qnLIUPOJ60APoeL1sss/f+EByBMkYSg4cjXd1QrCpxRFaoPL4uR9/LtYZU2GTYRysIPpBvnz11uNZYvdQBDHWuZvhRgHRnBeOWQEbjBMpSo7NLDZfkUygaztoodpA95yFt7xfX19Lt8/PjV+k9fJXe++HrP+/9L1+JHbzfg5jVYK5HXcvkedasSbFcVnUtE3dkPJCmVuOeBKaefmC5hqga4AWHmQhKDzO35XhB1iDKiS8+NCrO2m147aGwBh279gjG1jT66DreTxrxhPHE1ul3KA6TOyVp/7Ima7ZfPThJJw2AossaJjup5mWNwhrOHN/ZMaj+HMzfGBOLtTYH3ZKYWFEbZmI2o+FwB3AbHDxAmkuWMJIqk5SwXWa3IO5n8tjJKBG+CZD6EHrRDcOzswJlLb3AUCR27VuvGcgufVszsG4zkOS+7S86E0uDylQ1JkmOyb9c7u4ZqGoNqgumL9sizoP0ZdZsGlmsbBqFTavoIeTbjmeF4oZZVt+QnZ9AldozrTyB1rTjWBGv6q360O4J44ZxpLjB5kI/OxOIiTKZCgsbNYGE0nh0SRVtMd4aFweBRFZ3l1gF2mqWredOM1N3G4QUydSVdN1I4csF2TBC3GTdrmhb1LTewFoAiCyiBUEg8tyMO3AYsRrDoqfuCjToCqamXYGqaIuZFiYOqjPP6lQUy07R2LyEc7FTSLZ+CTXbAAhvlajs5qA9axbB3LlpAo2qMVVBw5pAUwTTBIXcWqlMivN1amkgqx4o3alVKKVRJ3YikYZuKqWRrYygdyZI968mBZJMajLhfoxC9B/427GhN4D/ZSXoxP1umqmm6KDxdjeu1fGmsXkxRxBOmULfwe8P/egJHG+UApc6oiwmtY84d8ewegkp2sqk9aAepJEyh1kEW5qwYZazLODR6aIDzjkDteKDLpqDMw8+CCTnCagCubCqbIAoPaO45INUZDpVuKRyHsVIS1Yd56horFv24RyrehW69qtxVGMjdqJFkvN4aAXCuZLhSq8JhRQqeVimh6g4S1LnnEciGEPmKYMpWnIjECNTidkV8fV+z5FYdjp57uQe4IPBzI9ijQ7n2JwTdy8DSrE1XXmqi5ZsXUacERwRapQNE5mzrStRk7ltbGq3NiBRh7ru69+twSjX2Z27ziQKStChjCio3KVytWLFIG5QVGddTEVMLMouEM7iajPO82KnYCaWecwet9YjX5gNSTK9IGyoswVTT3AJ1ZhPzt3Olw3Wg1WroW+AUwAJuYWIJTHlxjRVITOfrk3WarDayRzt3hjrGwwCC89Ntn2xTt3tLoO0jd64291QTwG12jRoQk7luHBLT2+OSXz81eIWG2l6XuAFJLh9wZPmDditwk8elQSLBRpiZ0BlzMhNgdL5LvqNrPlPgkKSWgcktSXtW9FI1huHIzZn5GmMPjw8WLy313do2WdHJyBRh8c3vvW4KbYx21Zy5NEbZlZTtvy0uMOYkDUvn0KRU/CrwgeV3ii0cSA3RYvFt3blAfhwpIcImqwXn+M2S5h/reMswgQqL6H5eiuTE7w+x0xipoCqeZhn7fXzXU2Z9A5hdQrOh+W580E4u3u6+fUyf1Rfbobdi8PLrRtOmd2b/VbdeY3cCc16/ExF9SO5D5mmT0GQy/Ww7Jqj3Ur3xrMjoGhV7haV2zBwgEaAY5M+Y+TKnykXBI5GuFVa3STJvrd7OtuU7Q70Hd07W46nX/Mz99H2bd9dFgKx0fKnaB22Apwzhxa5RmjZdZJxgjjfkdvSBdRpLHLP+DjcYyP4lmzV2YpZjyrk9mssAZf7NGx9AFsbKkR9QI56gF3sXkY5AFMNINIeabt4kzp0mSF0Wrz3ATGVsGpBczjgYas+MGF/LxkP82nmiK3RB2DzgFiqE4i14wNialuHfa7yvW7wsspPdwFu/WVlB3FFlgKekwBm4rQvZVdtagdEIDows5XwX5bVxTaeM2foYAlfPgUD1CdegqBK9IKyvhIELp1YvzcL2gKf7nnIGd37z97OCpx1HceZ9mQoNFNkPUwMaNT58mQ71bLP3qYeWNXK3XiSy9Ayz217RBxdJKldjJPqs3oT65EWRaKkxTAvu8m/grJD5XqqSjWyo8hphV/2pq22PH98+T775aLuAP61+PQ27P0/W8Tj3gqi57xHo2irkCMMe3AMsmzCsj39VEkzRa2ZBlxK8uqsjggEeYBG4UoxPb+d7RvDKSoXojStrkkVIRP1wJpyEDLhSx+hMNkdw8L4G7Jh1ONf</diagram></mxfile>

BIN
documentation/database/entity_relationship_diagram_v1.4.2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

7
migrations/versions/1d844798c277_reset_migrations.py → migrations/versions/fb12ee70fe66_reset_migrations.py

@ -1,8 +1,8 @@
"""reset migrations """reset migrations
Revision ID: 1d844798c277 Revision ID: fb12ee70fe66
Revises: Revises:
Create Date: 2020-07-18 23:47:44.369860 Create Date: 2020-07-19 00:47:38.970210
""" """
from alembic import op from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '1d844798c277' revision = 'fb12ee70fe66'
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@ -32,6 +32,7 @@ def upgrade():
sa.Column('name', sa.String(length=64), nullable=False), sa.Column('name', sa.String(length=64), nullable=False),
sa.Column('auth_hash', sa.String(length=32), nullable=True), sa.Column('auth_hash', sa.String(length=32), nullable=True),
sa.Column('password_hash', sa.String(length=128), nullable=True), sa.Column('password_hash', sa.String(length=128), nullable=True),
sa.Column('last_login', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('auth_hash'), sa.UniqueConstraint('auth_hash'),
sa.UniqueConstraint('name') sa.UniqueConstraint('name')
Loading…
Cancel
Save