diff --git a/.env b/.env index 9f4f825..fe4ec14 100644 --- a/.env +++ b/.env @@ -5,5 +5,5 @@ POSTGRES_USER = leggy POSTGRES_PASSWORD = leggy POSTGRES_DB = leggy -THE_FRONT_ROOMS_DOMAIN = tfr.leggy.dev +THE_FRONT_ROOMS_DOMAIN = thefrontroomsga.me THE_FRONT_ROOMS_SECRETE_KEY = leggy diff --git a/TFR/requirements.txt b/TFR/requirements.txt index da0c99e..58c8de8 100644 --- a/TFR/requirements.txt +++ b/TFR/requirements.txt @@ -1,5 +1,6 @@ Gunicorn psycopg2-binary +shortuuid Flask Flask-SQLAlchemy Flask-Migrate diff --git a/TFR/server/api.py b/TFR/server/api.py index a676ec6..22fbdff 100644 --- a/TFR/server/api.py +++ b/TFR/server/api.py @@ -1,10 +1,11 @@ -import uuid +import shortuuid from flask import Blueprint, request, jsonify from flask_login import login_required, current_user from server.models import Tokens, Scores from server.extensions import db +from server.config import GAME_VERSION, GAME_VERSIONS, GAME_DIFFICULTIES, USER_MAX_TOKENS blueprint = Blueprint("api", __name__, url_prefix="/api") @@ -21,7 +22,7 @@ def tokens(): token = Tokens.query.filter_by(id=token_id).first() if not token: return jsonify({"error": "Token not found!"}), 404 - if token.holder != current_user.id: + if token.user_id != current_user.id: return jsonify({"error": "You do not own this token!"}), 403 db.session.delete(token) @@ -29,58 +30,56 @@ def tokens(): return jsonify({"success": "Token deleted!"}), 200 elif request.method == "POST": - if len(Tokens.query.filter_by(holder=current_user.id).all()) >= 5: - return jsonify({"error": "You already have 5 tokens!"}), 403 + if len(Tokens.query.filter_by(user_id=current_user.id).all()) >= USER_MAX_TOKENS: + return jsonify({"error": f"You already have {USER_MAX_TOKENS} tokens!"}), 403 - token = Tokens(token=str(uuid.uuid4()), holder=current_user.id) + new_string = str(shortuuid.ShortUUID().random(length=20)) + token = Tokens(token=new_string, user_id=current_user.id) db.session.add(token) db.session.commit() return jsonify({"success": "Token added!"}), 200 -@blueprint.route("/post", methods=["GET", "POST"]) +@blueprint.route("/post", methods=["POST"]) def post(): - if request.method == "GET": - return """ -
- """ - form = request.form + errors = [] if not form: - return "Invalid form", 400 + errors += "No form data provided!" if not form["token"]: - return "Invalid authentication", 401 + errors += "No token provided!" + if not form["version"]: + errors += "No version provided!" - # if not isinstance(form["score"], int): - # return "Score must be an integer", 400 - if int(form["score"]) < 0: - return "Score must be greater than 0", 400 - if int(form["difficulty"]) not in [0, 1, 2, 3, 4]: - # 0 = Easy, Level 1 - # 1 = Easy, Level 2 - # 2 = Easy, Level 3 - # 3 = Normal - # 4 = Hard - return "Invalid difficulty", 400 + if errors: + return jsonify(errors), 400 - if token := Tokens.query.filter_by(token=form["token"]).first(): - # Yupeee, authenticated - score = Scores( - score=int(form["score"]), - difficulty=int(form["difficulty"]), - scorer=token.holder, - ) - db.session.add(score) - db.session.commit() + try: + int(form["score"]) + int(form["difficulty"]) + except TypeError: + errors += "Invalid score and difficulty must be valid numbers!" - return "Success!", 200 + if int(form["difficulty"]) not in GAME_DIFFICULTIES: + errors += "Invalid difficulty!" - # L no authentication :3 - return "Authentication failed", 401 + token_data = Tokens.query.filter_by(token=form["token"]).first() + if not token_data: + errors += "Authentication failed!" + + if errors: + return jsonify(errors), 400 + + score = Scores( + score=int(form["score"]), + difficulty=int(form["difficulty"]), + version=form["version"], + user_id=token_data.user_id, + ) + + db.session.add(score) + db.session.commit() + + return "Success!", 200 diff --git a/TFR/server/auth.py b/TFR/server/auth.py index 8f3eee5..8856f38 100644 --- a/TFR/server/auth.py +++ b/TFR/server/auth.py @@ -31,7 +31,7 @@ def account(): if action == "password": flash("Insert password change function", "error") - token_list = Tokens.query.filter_by(holder=current_user.id).all() + token_list = Tokens.query.filter_by(user_id=current_user.id).all() return render_template("account.html", token_list=token_list) diff --git a/TFR/server/config.py b/TFR/server/config.py index b6fed09..08a4f82 100644 --- a/TFR/server/config.py +++ b/TFR/server/config.py @@ -13,3 +13,9 @@ SQLALCHEMY_POOL_RECYCLE = 621 MIGRATION_DIR = "/data/storage/migrations" INSTANCE_DIR = "/data/storage/instance" + +GAME_VERSION = "alpha" +GAME_VERSIONS = ["alpha"] +GAME_DIFFICULTIES = [0, 1, 2, 3, 4] + +USER_MAX_TOKENS = 3 diff --git a/TFR/server/models.py b/TFR/server/models.py index 43666fe..614d612 100644 --- a/TFR/server/models.py +++ b/TFR/server/models.py @@ -4,17 +4,16 @@ Database models for the server import uuid from flask_login import UserMixin from server.extensions import db +from server.config import GAME_VERSION class Scores(db.Model): """ Post table Scores supports anonymous posting, and instead just wants to post a score, - then the username must be provided.Otherwise, it's grabbed from the user table + then the username must be provided. Otherwise, it's grabbed from the user + table """ - - __tablename__ = "scores" - id = db.Column(db.Integer, primary_key=True) score = db.Column(db.Float, nullable=False) @@ -24,17 +23,34 @@ class Scores(db.Model): nullable=False, server_default=db.func.now(), ) + posted_at = db.Column( + db.DateTime, + nullable=False, + server_default=db.func.now(), + ) - scorer = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + version = db.Column(db.String, default=GAME_VERSION) + user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True)) + + +class Tokens(db.Model): + """ + Token table + """ + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True)) + token = db.Column(db.String, nullable=False, unique=True) + created_at = db.Column( + db.DateTime, + nullable=False, + server_default=db.func.now(), + ) class Users(db.Model, UserMixin): """ User table """ - - __tablename__ = "users" - id = db.Column(db.Integer, primary_key=True) alt_id = db.Column(db.String, nullable=False, unique=True) @@ -46,25 +62,9 @@ class Users(db.Model, UserMixin): server_default=db.func.now(), ) - scores = db.relationship("Scores", backref="user", lazy=True) - tokens = db.relationship("Tokens", backref="user", lazy=True) + + scores = db.relationship("Scores", backref=db.backref('users', lazy=True)) + tokens = db.relationship("Tokens", backref=db.backref('users', lazy=True)) def get_id(self): return str(self.alt_id) - - -class Tokens(db.Model): - """ - Token table - """ - - __tablename__ = "tokens" - - id = db.Column(db.Integer, primary_key=True) - holder = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) - token = db.Column(db.String, nullable=False, unique=True) - created_at = db.Column( - db.DateTime, - nullable=False, - server_default=db.func.now(), - ) diff --git a/TFR/server/static/bg.png b/TFR/server/static/background.png similarity index 100% rename from TFR/server/static/bg.png rename to TFR/server/static/background.png diff --git a/TFR/server/static/background.webp b/TFR/server/static/background.webp new file mode 100644 index 0000000..2c5f4c7 Binary files /dev/null and b/TFR/server/static/background.webp differ diff --git a/TFR/server/static/sass/button.sass b/TFR/server/static/sass/button.sass new file mode 100644 index 0000000..c6588af --- /dev/null +++ b/TFR/server/static/sass/button.sass @@ -0,0 +1,82 @@ +.button + margin: auto 0.15rem + padding: 0.5rem 0.7rem + + display: flex + align-items: center + gap: 0.5rem + + text-decoration: none + white-space: nowrap + font-size: 0.9em + + background-color: RGBA($white, 0.02) + color: RGB($white) + + border-radius: 2px + border: 0 solid transparent + + transition: background-color 0.1s ease-in-out + + &:hover + background-color: RGBA($white, 0.3) + + &.primary + background-color: RGBA($primary, 0.02) + color: RGB($primary) + + &:hover + background-color: RGBA($primary, 0.3) + + &.secondary + background-color: RGBA($secondary, 0.02) + color: RGB($secondary) + + &:hover + background-color: RGBA($secondary, 0.3) + + > i + font-size: 1.25em + display: block + +.search + margin: auto 0.15rem + width: 100% + + position: relative + display: flex + flex-direction: row + + > label + padding: 0.5rem 0.7rem + + text-decoration: none + white-space: nowrap + font-size: 0.9em + + background-color: RGBA($white, 0.02) + color: RGB($white) + border-radius: 2px 0 0 2px + + > input + margin: 0 + padding: 0.5rem 0.7rem + + width: 100% + + text-decoration: none + white-space: nowrap + font-size: 0.9em + + background-color: RGBA($white, 0.02) + color: RGB($white) + + border: 0 solid transparent + border-left: 1px solid RGBA($white, 0.1) + border-radius: 0 2px 2px 0 + + transition: background-color 0.1s ease-in-out + + &:focus-visible, &:focus + background-color: RGBA($white, 0.1) + outline: 0 solid transparent diff --git a/TFR/server/static/sass/style.sass b/TFR/server/static/sass/style.sass index 719ae6d..b9ac4d8 100644 --- a/TFR/server/static/sass/style.sass +++ b/TFR/server/static/sass/style.sass @@ -5,23 +5,6 @@ $secondary: var(--secondary) $gold: var(--gold) $silver: var(--silver) $bronze: var(--bronze) -$darkBlue: var(--darkBlue) - -@mixin button($color) - text-decoration: none - text-align: center - white-space: nowrap - background-color: RGBA($color, 0.02) - color: RGB($color) - border-radius: 2px - border: 0 solid transparent - transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out - - &:hover - background-color: RGBA($color, 0.3) - transform: translateY(-0.1rem) - -@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700&display=swap') \:root --black: 21, 21, 21 @@ -31,7 +14,9 @@ $darkBlue: var(--darkBlue) --gold: 255, 222, 70 --silver: 229, 220, 206 --bronze: 193, 145, 69 - --darkBlue: 9, 9, 39 + +@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700&display=swap') +@import "button" * box-sizing: border-box @@ -46,17 +31,20 @@ body padding: 0 display: flex flex-direction: row - background-color: RGB($darkBlue) + background-color: RGB($black) color: RGB($white) .background - width: 100% - height: 100% - - object-fit: cover - position: absolute + position: fixed + inset: 0 + overflow: hidden z-index: 1 + > img + width: 100% + height: 100% + object-fit: cover + .app margin: 0 auto padding: 0 @@ -68,7 +56,7 @@ body display: flex flex-direction: column - background-color: rgba($darkBlue, 0.7) + background-color: rgba($black, 0.4) backdrop-filter: blur(5px) z-index: 2 @@ -77,14 +65,15 @@ body header padding: 1rem + background-image: linear-gradient(to bottom, RGB(var(--black)), transparent) - background-color: RGBA($darkBlue, 0.7) - +.title + margin-bottom: 1rem + height: auto > img - margin-bottom: 1rem width: 100% - height: auto - text-align: center + height: auto + max-width: 100% nav margin-top: 0.3rem @@ -94,31 +83,14 @@ nav flex-direction: row justify-content: center - > span + > form width: 100% + display: flex + flex-direction: row + justify-content: center - > a - margin: auto 0.15rem - padding: 0.5rem 0.7rem - - text-decoration: none - white-space: nowrap - font-size: 0.9em - - color: RGB($primary) - - &.button - @include button($white) - - &.primary - @include button($primary) - - &.secondary - @include button($secondary) - - > i - font-size: 1.25em - display: block + .spacer + width: 100% .flash display: flex @@ -133,7 +105,7 @@ nav width: 100% position: relative - background-color: RGB($darkBlue) + background-color: RGB($black) color: RGB($primary) transition: background-color 0.2s ease-in-out, padding 0.2s ease-in-out @@ -230,7 +202,7 @@ main display: flex flex-direction: column - background-color: rgba($darkBlue, 0.7) + background-color: rgba($black, 0.7) border-radius: 2px > h2 @@ -242,28 +214,6 @@ main margin: 0 0 1rem 0 font-size: 1em - .button - margin: 0 - padding: 0.5rem 0.7rem - - text-decoration: none - white-space: nowrap - font-size: 0.9em - - color: RGB($primary) - - > i - font-size: 1.25em - display: block - - @include button($white) - - &.primary - @include button($primary) - - &.secondary - @include button($secondary) - > table width: 100% border-collapse: collapse @@ -300,7 +250,7 @@ main border: 1px solid RGB($white) border-radius: 2px - background-color: RGB($darkBlue) + background-color: RGB($black) color: RGB($white) &:focus @@ -344,7 +294,7 @@ footer width: 100% display: flex flex-direction: row - background-color: RGBA($darkBlue, 0.7) + background-image: linear-gradient(to top, RGB(var(--black)), transparent) > p margin: 0 diff --git a/TFR/server/static/title.webp b/TFR/server/static/title.webp new file mode 100644 index 0000000..8a5f790 Binary files /dev/null and b/TFR/server/static/title.webp differ diff --git a/TFR/server/templates/account.html b/TFR/server/templates/account.html index 80a1ff3..514789e 100644 --- a/TFR/server/templates/account.html +++ b/TFR/server/templates/account.html @@ -2,8 +2,7 @@ {% block nav %} {% endblock %} diff --git a/TFR/server/templates/base.html b/TFR/server/templates/base.html index cbc02b1..fb4ce1d 100644 --- a/TFR/server/templates/base.html +++ b/TFR/server/templates/base.html @@ -7,42 +7,39 @@- - {{ message }} -
+{{ message }}
{% endfor %} {% endif %} {% endwith %}