diff --git a/app/__init__.py b/app/__init__.py index 0aaa8f5..0c61044 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,11 +3,14 @@ from flask_bootstrap import Bootstrap from config import Config from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate +from flask_login import LoginManager app = Flask(__name__) app.config.from_object(Config) bootstrap = Bootstrap(app) db = SQLAlchemy(app) migrate = Migrate(app, db) +login = LoginManager(app) +login.login_view = 'login' from app import routes, models diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..c3b3d0d --- /dev/null +++ b/app/forms.py @@ -0,0 +1,22 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField +from wtforms.validators import DataRequired, EqualTo, ValidationError, Length +from app.models import Player + +class LoginForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + password = PasswordField('Password', validators=[DataRequired()]) + remember_me = BooleanField('Remember Me', default=True) + submit = SubmitField('Sign In') + +class RegistrationForm(FlaskForm): + username = StringField('Username', validators=[DataRequired(), Length(min=0, max=64)]) + password = PasswordField('Password', validators=[DataRequired(), Length(min=0, max=128)]) + password2 = PasswordField( + 'Repeat Password', validators=[DataRequired(), EqualTo('password')]) + submit = SubmitField('Register') + + def validate_username(self, username): + player = Player.query.filter_by(name=username.data).first() + if player is not None: + raise ValidationError('Please use a different username.') \ No newline at end of file diff --git a/app/models.py b/app/models.py index 671d30b..6589e69 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,8 @@ -from app import app, db +from werkzeug.security import generate_password_hash, check_password_hash +from app import app, db, login from sqlalchemy.sql import func +from secrets import token_hex +from flask_login import UserMixin game_player = db.Table('game_player', db.Column('game_id', db.Integer, db.ForeignKey('game.id'), nullable=False), @@ -9,9 +12,9 @@ game_player = db.Table('game_player', ) player_found_objective = db.Table('player_found_objective', - db.Column('game_id', db.Integer, db.ForeignKey('game.id'), nullable=False), - db.Column('player_id', db.Integer, db.ForeignKey('player.id'), nullable=False), - db.PrimaryKeyConstraint('game_id', 'player_id'), + db.Column('objective_id', db.Integer, db.ForeignKey('objective.id'), nullable=False, server_default='-1'), + db.Column('player_id', db.Integer, db.ForeignKey('player.id'), nullable=False, server_default='-1'), + db.PrimaryKeyConstraint('objective_id', 'player_id'), db.Column('timestamp', db.DateTime, server_default=func.now(), nullable=False) ) @@ -49,11 +52,11 @@ class Game(db.Model): lazy='select', backref=db.backref('game', lazy='joined')) -class Player(db.Model): +class Player(UserMixin, db.Model): __tablename__ = 'player' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True, nullable=False) - auth_hash = db.Column(db.String(128), unique=True, nullable=True) + auth_hash = db.Column(db.String(32), unique=True, nullable=True) password_hash = db.Column(db.String(128)) games = db.relationship( 'Game', @@ -78,6 +81,19 @@ class Player(db.Model): secondary=notification_player, back_populates='recipients') + def set_password(self, password): + self.password_hash = generate_password_hash(password) + + def set_auth_hash(self): + self.auth_hash = token_hex(16) + + def check_password(self, password): + return check_password_hash(self.password_hash, password) + +@login.user_loader +def load_user(id): + return Player.query.get(int(id)) + class Objective(db.Model): __tablename__ = 'objective' id = db.Column(db.Integer, primary_key=True) diff --git a/app/routes.py b/app/routes.py index 7be62c6..c065776 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,11 +1,45 @@ from flask import render_template, flash, redirect, url_for, request +from flask_login import login_user, logout_user, current_user, login_required from app import app, db -#from app.forms import - +from app.models import Player, Game +from app.forms import LoginForm, RegistrationForm @app.route('/', methods=['GET']) @app.route('/index', methods=['GET']) +@login_required def index(): message="Hello, World" return render_template("index.html", title='Home', message=message) -#@app.route('/game//leader') \ No newline at end of file +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = LoginForm() + if form.validate_on_submit(): + player = Player.query.filter_by(name=form.username.data).first() + if player is None or not player.check_password(form.password.data): + flash('Invalid username or password') + return redirect(url_for('login')) + login_user(player, remember=form.remember_me.data) + return redirect(url_for('index')) + return render_template('login.html', title='Sign In', form=form) + +@app.route('/logout') +def logout(): + logout_user() + return redirect(url_for('index')) + +@app.route('/register', methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = RegistrationForm() + if form.validate_on_submit(): + player = Player(name=form.username.data) + player.set_password(form.password.data) + db.session.add(player) + db.session.commit() + flash('Congratulations, you are now a registered user!') + return redirect(url_for('login')) + return render_template('register.html', title='Register', form=form) \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 2ddea63..828c8f6 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,7 +1,7 @@ {% extends 'bootstrap/base.html' %} {% block title %} -{% if title %}{{ title }} - Microblog{% else %}Welcome to Microblog{% endif %} +{% if title %}{{ title }} - The Hunt{% else %}Welcome to The Hunt{% endif %} {% endblock %} {% block navbar %} @@ -14,14 +14,18 @@ - Microblog + The Hunt @@ -30,6 +34,15 @@ {% block content %}
+ {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + {# application content needs to be provided in the app_content block #} {% block app_content %}{% endblock %}
{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html index 01be431..69e0cb7 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,8 +1,40 @@ {% extends 'base.html' %} {% block app_content %} - -

Welcome!

+

Hi, {{ current_user.name }}!

{{ message }}

-{% endblock %} +

My games:

+
+ + + + + + + + + + + + + + + + + + + {% for game in current_user.games %} + + + + + + + + {% endfor %} + +
NameStateStart TimeEnd TimeMy Role
GamenameRunningyesterdaytomorrowbitch
{{ game.name }}{{ game.state }}{{ game.start_time }}{{ game.end_time }}bitch
+
+ +{% endblock %} \ No newline at end of file diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..cc2df0e --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,14 @@ + +{% extends 'base.html' %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +

Sign In

+
+
+ {{ wtf.quick_form(form) }} +
+
+
+

New User? Click to Register!

+{% endblock %} \ No newline at end of file diff --git a/app/templates/register.html b/app/templates/register.html new file mode 100644 index 0000000..b1f2fa4 --- /dev/null +++ b/app/templates/register.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +

Register

+
+
+ {{ wtf.quick_form(form) }} +
+
+{% endblock %} \ No newline at end of file diff --git a/migrations/versions/b98d6f905471_update_up_to_1_3.py b/migrations/versions/01c009738a1f_update_to_1_3.py similarity index 91% rename from migrations/versions/b98d6f905471_update_up_to_1_3.py rename to migrations/versions/01c009738a1f_update_to_1_3.py index 973ce66..9870626 100644 --- a/migrations/versions/b98d6f905471_update_up_to_1_3.py +++ b/migrations/versions/01c009738a1f_update_to_1_3.py @@ -1,8 +1,8 @@ -"""update up to 1.3 +"""update to 1.3 -Revision ID: b98d6f905471 +Revision ID: 01c009738a1f Revises: -Create Date: 2020-07-03 11:23:06.765509 +Create Date: 2020-07-03 13:12:00.463246 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = 'b98d6f905471' +revision = '01c009738a1f' down_revision = None branch_labels = None depends_on = None @@ -30,7 +30,7 @@ def upgrade(): op.create_table('player', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=64), nullable=False), - sa.Column('auth_hash', sa.String(length=128), nullable=True), + sa.Column('auth_hash', sa.String(length=32), nullable=True), sa.Column('password_hash', sa.String(length=128), nullable=True), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('auth_hash'), @@ -82,14 +82,6 @@ def upgrade(): sa.ForeignKeyConstraint(['caught_player_id'], ['player.id'], ), sa.UniqueConstraint('photo_reference') ) - op.create_table('player_found_objective', - sa.Column('game_id', sa.Integer(), nullable=False), - sa.Column('player_id', sa.Integer(), nullable=False), - sa.Column('timestamp', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), - sa.ForeignKeyConstraint(['game_id'], ['game.id'], ), - sa.ForeignKeyConstraint(['player_id'], ['player.id'], ), - sa.PrimaryKeyConstraint('game_id', 'player_id') - ) op.create_table('notification_player', sa.Column('notification_id', sa.Integer(), nullable=False), sa.Column('player_id', sa.Integer(), nullable=False), @@ -98,13 +90,21 @@ def upgrade(): sa.ForeignKeyConstraint(['player_id'], ['player.id'], ), sa.PrimaryKeyConstraint('notification_id', 'player_id') ) + op.create_table('player_found_objective', + sa.Column('objective_id', sa.Integer(), server_default='-1', nullable=False), + sa.Column('player_id', sa.Integer(), server_default='-1', nullable=False), + sa.Column('timestamp', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), + sa.ForeignKeyConstraint(['objective_id'], ['objective.id'], ), + sa.ForeignKeyConstraint(['player_id'], ['player.id'], ), + sa.PrimaryKeyConstraint('objective_id', 'player_id') + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('notification_player') op.drop_table('player_found_objective') + op.drop_table('notification_player') op.drop_table('player_caught_player') op.drop_table('objective') op.drop_table('notification')