Yeet expo page

Fix score uploading
This commit is contained in:
Michał Gdula 2023-06-21 00:50:36 +03:00
parent 85a329ad6d
commit a93a8cf04d
40 changed files with 109 additions and 1247 deletions

View file

@ -1,9 +1,4 @@
{$THE_FRONT_ROOMS_DOMAIN} {
reverse_proxy tfr:8000
encode gzip
}
{$THE_FRONT_ROOMS_DOMAIN}
reverse_proxy tfr:8000
encode gzip
{$GAME_EXPO_DOMAIN} {
reverse_proxy expo:5000
encode gzip
}

168
GameExpo/.gitignore vendored
View file

@ -1,168 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# remove development files
/instance
/migrations
/storage
/logs
/website/static/.webpack-cache
/website/static/gen

View file

@ -1,16 +0,0 @@
# syntax=docker/dockerfile:1
FROM alpine:latest
EXPOSE 5000
RUN apk update && apk add python3 py3-pip
WORKDIR /data
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN mkdir /storage
COPY ./website ./website
COPY ./run.sh ./run.sh
RUN chmod +x ./run.sh
CMD ["./run.sh"]

View file

@ -1,24 +0,0 @@
# Game Expo 2023
The all new and upgraded website that runs on the Flask macro-framework
## Developing
### Prerequisites
Clone the latest branch, and create a venv environment. Then, install the requirements.txt file.
To create the required database run:
```bash
$ flask --app website init-db
```
### Running
To run the website, run the following command:
```bash
$ flask --app website run
```
## Running
While the docker-compose file runs the program for you, its possible to just run the file locally.
```bash
# gunicorn --bind="0.0.0.0:5000" --workers=4 website:app
```

View file

@ -1,12 +0,0 @@
Gunicorn
Flask
Flask-SQLAlchemy
Flask-Migrate
Flask-Login
WTForms
Flask-WTF
Flask-Assets
Flask-Caching
libsass-bin
jsmin
cssmin

View file

@ -1,20 +0,0 @@
#!/bin/sh
# Check if migrastions folder exists
if [ ! -d "/data/storage/migrations" ];
then
echo "Creating tables..."
flask --app website db init
fi
# Check if there are any changes to the database
if ! $(flask --app website db check) | grep -q "No changes in schema detected.";
then
echo "Database changes detected! Migrating..."
flask --app website db migrate
flask --app website db upgrade
fi
# Start website!!!!
echo "Starting expo website..."
gunicorn --bind expo:5000 website:app

View file

@ -1,35 +0,0 @@
from flask import Flask
from flask_assets import Bundle
from website.models import Users
from website.extensions import db, migrate, login_manager, assets
from website.config import INSTANCE_DIR, MIGRATION_DIR
from website import routes
app = Flask(__name__, instance_path=INSTANCE_DIR)
app.config.from_pyfile("config.py")
db.init_app(app)
migrate.init_app(app, db, directory=MIGRATION_DIR)
with app.app_context():
db.create_all()
login_manager.init_app(app)
assets.init_app(app)
styles = Bundle(
"sass/styles.sass",
filters="libsass, cssmin",
output="gen/packed.css",
depends="sass/*.sass",
)
assets.register("styles", styles)
scripts = Bundle("js/*.js", filters="jsmin", output="gen/packed.js")
assets.register("scripts", scripts)
app.register_blueprint(routes.blueprint)
@login_manager.user_loader
def load_user(user_id):
return Users.query.filter_by(id=user_id).first()

View file

@ -1,15 +0,0 @@
import os
# Purely to make the code a bit more readable
def env(key):
return os.getenv(key)
# SECRET_KEY = env("FLASK_KEY")
SECRET_KEY = "dev"
SQLALCHEMY_DATABASE_URI = "sqlite:///site.db"
MIGRATION_DIR = "/data/storage/migrations"
INSTANCE_DIR = "/data/storage/instance"

View file

@ -1,11 +0,0 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_caching import Cache
from flask_assets import Environment
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
cache = Cache(config={"CACHE_TYPE": "simple"})
assets = Environment()

View file

@ -1,54 +0,0 @@
"""
Database models for the server
"""
from website.extensions import db
from flask_login import UserMixin
class Images(db.Model):
id = db.Column(db.Integer, primary_key=True)
image = db.Column(db.String, nullable=False)
game_id = db.Column(db.Integer, db.ForeignKey("games.id"))
class Tags(db.Model):
id = db.Column(db.Integer, primary_key=True)
tag = db.Column(db.String, nullable=False)
game_id = db.Column(db.Integer, db.ForeignKey("games.id"))
class Authors(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
role = db.Column(db.String, nullable=False, default="Developer")
game_id = db.Column(db.Integer, db.ForeignKey("games.id"))
class Users(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
uuid = db.Column(db.String, nullable=False)
game_id = db.Column(db.Integer, db.ForeignKey("games.id"))
def get_id(self):
return int(self.id)
class Games(db.Model):
id = db.Column(db.Integer, primary_key=True)
approved = db.Column(db.Boolean, nullable=False, default=False)
visible = db.Column(db.Boolean, nullable=False, default=False)
name = db.Column(db.String, nullable=False)
studio = db.Column(db.String, nullable=False)
description = db.Column(db.String, nullable=False)
logo = db.Column(db.String)
background = db.Column(db.String)
downloadLink = db.Column(db.String)
ageRating = db.Column(db.String, nullable=False)
tags = db.relationship("Tags", backref="game", lazy=True)
authors = db.relationship("Authors", backref="game", lazy=True)
images = db.relationship("Images", backref="game", lazy=True)
owner_id = db.relationship("Users", backref="game", lazy=True)

View file

@ -1,66 +0,0 @@
from flask import Blueprint, render_template, redirect, flash, abort
from flask_login import login_user, logout_user, login_required, current_user
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
from website.models import Users, Games
blueprint = Blueprint("website", __name__)
class LoginForm(FlaskForm):
uuid = StringField(
"uuid",
validators=[DataRequired()],
render_kw={"placeholder": "12345678-ABCD-ABCD-ABCD-123456789EFG"},
)
@blueprint.route("/")
def index():
games = Games.query.filter_by(approved=True).filter_by(visible=True).all()
return render_template("index.html", games=games)
@blueprint.route("/g/<int:game_id>")
def g(game_id):
game = (
Games.query.filter_by(id=game_id)
.filter_by(approved=True)
.filter_by(visible=True)
.first()
)
if not game:
abort(404)
return render_template("game.html", game=game)
@blueprint.route("/editor")
@login_required
def editor():
game = Users.query.filter_by(id=current_user.id).first().game
return render_template("editor.html", game=game)
@blueprint.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
if user := Users.query.filter_by(uuid=str(form.uuid.data)).first():
login_user(user, remember=True)
return redirect("/")
else:
flash("Incorrect login")
return render_template("login.html", form=form)
@blueprint.route("/logout")
@login_required
def logout():
logout_user()
return redirect("/")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

View file

@ -1,6 +0,0 @@
function keepRatio() {
let games = document.querySelectorAll(".game-box");
games.forEach((game) => {
game.style.height = (game.offsetWidth * 1.5) + "px";
});
}

View file

@ -1,15 +0,0 @@
window.onscroll = () => {
scrollFunction();
checkSection();
};
window.onload = () => {
keepRatio()
resizeNav();
scrollFunction();
checkSection();
};
window.onresize = () => {
keepRatio()
resizeNav();
checkSection();
};

View file

@ -1,82 +0,0 @@
const defaultTitle = "DV8 Game Expo <span>2023</span>";
let navSpacing = (5 * 16); // The amount of pixels to offset the section by
let prevElement = null;
function resizeNav() {
if (window.innerWidth > 600) {
navSpacing = (5 * 16);
} else {
navSpacing = (8 * 16);
}
}
function scrollFunction() {
let nav = document.querySelector("nav");
// let scrollHeight = window.innerHeight - nav.offsetHeight;
let scrollHeight = 0;
if (document.body.scrollTop > scrollHeight ||
document.documentElement.scrollTop > scrollHeight) {
nav.classList.add("scrolled");
} else {
nav.classList.remove("scrolled");
}
}
function checkSection() {
// Get the nav and sections
let navTitle = document.querySelector(".title > p");
let sections = document.querySelectorAll("section");
// If we're at the top of the page, set the title to the default
if ((window.pageYOffset + navSpacing) < sections[0].offsetTop || window.pageYOffset === 0) {
// If we're already on the default title, don't do anything as it'll break the animation
if (prevElement === null) return;
navTitle.innerHTML = defaultTitle;
navTitle.style.animation = "title-change 0.2s ease-in-out";
prevElement = null;
// Remove the animation after it's done, so we can animate again
setTimeout(() => { navTitle.style.animation = ""; }, 200);
return;
}
// While at this point we may not need to check for the sections
// There aren't many sections, so it's not a big deal
sections.forEach((section) => {
// Get the position of the section
let top = section.offsetTop;
let bottom = section.offsetTop + section.offsetHeight;
// If the section is on the screen is:
// 1. The top of the section is above the top of the screen
// 2. The bottom of the section is below the bottom of the screen
if ((window.pageYOffset + navSpacing) >= top && window.pageYOffset < (bottom - navSpacing)) {
// If we're already on the section, don't do anything as it'll break the animation
if (prevElement === section) return;
navTitle.innerHTML = section.id.split("_").join(" ");
navTitle.style.animation = "title-change 0.2s ease-in-out";
prevElement = section;
// Remove the animation after it's done, so we can animate again
setTimeout(() => { navTitle.style.animation = ""; }, 200);
}
});
}
document.querySelectorAll("nav > ul > li > a").forEach((element) => {
element.onclick = () => {
let anchor = location.hash.split("#")[1].toString();
let element = document.getElementById(anchor);
if (element === null) {
window.scrollTo({ top: 0, behavior: "smooth" });
} else {
window.scrollTo({top: (element.offsetTop + navSpacing), behavior: "smooth"});
}
}
});

View file

@ -1,38 +0,0 @@
@keyframes glow
0%
opacity: 0
50%
opacity: 1
100%
opacity: 0
@keyframes gradient
0%
background-position: 0% 0%
25%
background-position: 100% 50%
50%
background-position: 0% 100%
75%
background-position: 50% 25%
100%
background-position: 0% 50%
@keyframes title-change
0%
transform: translateX(-3rem)
opacity: 0
filter: blur(0.2rem)
100%
transform: translateX(0)
opacity: 1
filter: blur(0)
@media (max-width: 600px)
@keyframes title-change
0%
transform: translateY(-0.5rem)
opacity: 0
100%
transform: translateY(0)
opacity: 1

View file

@ -1,36 +0,0 @@
header
padding: 0 1rem
height: 100vh
max-height: 30rem
display: flex
flex-direction: column
justify-content: center
align-items: center
text-align: center
font-family: $main-font
color: RGB($primary)
> h1
margin: 0
font-size: 3rem
> span
font-family: $monospace-font
font-weight: normal
color: RGB($accent)
> p
margin: 0
font-size: 1.2rem
> img
margin-bottom: 1rem
width: 40rem
height: auto
max-width: 100%
max-height: 30rem
> i
margin: 1rem 0 0
font-size: 1.2rem
animation: glow 3s ease-in-out infinite

View file

@ -1,92 +0,0 @@
nav
padding-left: 1rem
padding-right: 0.5rem
width: 100%
height: 3rem
display: flex
flex-direction: row
align-items: center
position: fixed
top: 0
left: 0
font-weight: bold
font-family: $main-font
font-size: 1.1rem
white-space: nowrap
color: RGB($primary)
// border-bottom: 1px solid RGB($primary)
overflow: hidden
z-index: 100
transition: color 0.1s ease-in-out
&::before
content: ""
position: absolute
inset: 0
background: RGB(var(--nav))
transform: translateY(-3rem)
transition: transform 0.2s ease-in-out
z-index: -1
> span
width: 100%
> ul
margin: 0
padding: 0
height: 3rem
list-style: none
display: flex
flex-direction: row
align-items: center
> li > a
margin: 0 0.75rem
padding: 0.1rem 0.5rem
text-decoration: none
color: inherit
transition: color 0.1s ease-in-out
&:hover
color: RGB($accent)
> .title
height: 3rem
display: flex
flex-direction: row
align-items: center
justify-content: center
> p
margin: auto
font-size: inherit
color: inherit
transition: color 0.1s ease-in-out
> span
font-family: $monospace-font
color: RGB($accent)
&.scrolled
color: RGB($secondary)
&::before
transform: translateY(0)
@media (max-width: 600px)
nav
padding-right: 1rem
height: 6rem
top: -3rem
display: flex
flex-direction: column
justify-content: center
transition: top 0.2s ease-in-out
> .title
opacity: 1
> p
font-size: 1.3rem
&.scrolled
top: 0

View file

@ -1,198 +0,0 @@
section
margin: 3rem auto 0
max-width: 85rem
display: flex
flex-direction: column
gap: 1rem
scroll-margin-top: 4rem
> h2
margin: 0
font-size: 2rem
font-weight: bold
> p
margin: 0
font-size: 1rem
@media (max-width: 600px)
section
scroll-margin-top: 7rem
text-align: center
justify-content: center
.login
padding: 0.5rem
background-color: RGB($primary)
color: RGB($secondary)
border-radius: $radius
> p
margin: 0 0 0.5rem
padding: 0.5rem
background-color: RGB($accent)
color: RGB($primary)
border-radius: calc(calc(#{$radius} - 0.5rem) / 2)
&:first-child
border-top-left-radius: calc(#{$radius} - 0.5rem)
border-top-right-radius: calc(#{$radius} - 0.5rem)
> form
display: flex
flex-direction: row
> input
padding: 0.5rem 1rem
width: 100%
font-size: 1rem
font-family: $monospace-font
background-color: RGB($secondary)
color: RGB($primary)
border-radius: calc(calc(#{$radius} - 0.5rem) / 2) 0 0 calc(#{$radius} - 0.5rem)
border: none
transition: transform 0.1s ease-in-out, border-radius 0.1s ease-in-out
&:hover, &:focus-visible
outline: none
> button
padding: 0.5rem 1rem
font-size: 1rem
background-color: RGB($primary-button)
color: RGB($primary)
border-radius: 0 calc(calc(#{$radius} - 0.5rem) / 2) calc(#{$radius} - 0.5rem) 0
border: none
transition: transform 0.1s ease-in-out, border-radius 0.1s ease-in-out
&:hover, &:focus-visible
outline: none
background-color: RGB($secondary-button)
.games
margin-top: 1rem
display: flex
flex-direction: row
flex-wrap: wrap
gap: 1rem
.game-box
margin: 0 auto
padding: 1rem
width: 16rem
height: 0
position: relative
display: flex
flex-direction: column
font-family: $main-font
font-size: 1rem
text-decoration: none
text-align: center
background-color: RGB($primary)
color: RGB($secondary)
border-radius: $radius
box-shadow: 0 0.2rem 1rem 0 RGB($primary)
transition: box-shadow 0.1s ease-in-out, transform 0.25s ease-in-out
overflow: hidden
.background
position: absolute
inset: 0
width: 100%
height: 100%
object-fit: cover
opacity: 0.3
filter: blur(0.25rem)
z-index: +1
&::after
content: ''
position: absolute
inset: 0
background-image: linear-gradient(to top, transparent, RGB($primary))
z-index: +2
> div
position: relative
height: 100%
display: flex
flex-direction: column
gap: 0.5rem
font-weight: bold
z-index: +3
.logo
margin: 0 auto 1rem
width: auto
height: auto
max-width: 100%
max-height: 40%
display: block
border-radius: calc(#{$radius} / 2)
> h2
margin: 0
font-size: 2rem
color: RGB($accent)
> p
margin: 0
> span
height: 100%
> ul
margin: 0
padding: 0
list-style: none
display: flex
flex-direction: row
flex-wrap: wrap
gap: 0.5rem
> li
margin: 0
padding: 0.25rem 0.5rem
font-size: 0.9rem
font-weight: normal
background-color: RGBA($accent, 0.5)
color: RGB($secondary)
border-radius: $radius
&:hover
box-shadow: 0 0.25rem 1.25rem 0 RGB($primary)
transform: scale(1.03) translateY(-0.25rem)
@media (max-width: 600px)
.game-box
margin-bottom: 2rem
padding: 0.75rem
width: 14rem
> div
.logo
margin-bottom: 0.5rem
max-height: 25%
> h2
font-size: 1.5rem

View file

@ -1,124 +0,0 @@
$primary: var(--primary)
$secondary: var(--secondary)
$primary-button: var(--primary-button)
$secondary-button: var(--secondary-button)
$accent: var(--accent)
$radius: var(--radius)
$main-font: var(--main-font)
$monospace-font: var(--monospace-font)
// Accessibility setting
//*, *::before, *::after
// animation-duration: 0s !important
// transition: none !important
//--dv8-orange: #F36023
//--dv8-yellow: #FFE11C
//--dv8-cyan: #00AAB0
//--dv8-magenta: #D51E90
//--dv8-lime: #C0D939
\:root
--primary: 43, 43, 43
--secondary: 240, 240, 245
--primary-button: 242, 96, 34
--secondary-button: 191, 85, 40
--accent: 194, 165, 136
--radius: 0.3rem
--main-font: 'Rubik', sans-serif
--monospace-font: 'JetBrains Mono', monospace
--nav: 35, 35, 35
@import "animations"
@import "nav"
@import "header"
@import "sections"
*
box-sizing: border-box
html
font-family: $main-font
background-color: RGB($secondary)
color: RGB($primary)
body
margin: 0
padding: 0
min-height: 100vh
display: grid
grid-template-rows: 1fr auto
.background
background-color: RGB($secondary)
position: absolute
inset: 0
overflow: hidden
z-index: 1
> img
position: absolute
inset: -5%
width: 110%
height: 110%
object-fit: cover
filter: blur(0.25rem)
opacity: 0.6
&::after
content: ''
position: absolute
inset: 0
background-image: linear-gradient(to top, RGB($secondary) 3%, RGBA($primary, 0.1))
z-index: +1
main
padding: 3rem 2rem 2rem
position: relative
z-index: 2
@media (max-width: 600px)
main
padding: 3rem 1rem 1rem
footer
margin: auto 0 0
padding: 0.5rem
position: relative
display: flex
justify-content: center
align-items: center
background-color: RGB(var(--nav))
color: RGB($secondary)
z-index: 2
> p
margin: 0
font-size: 0.8rem
font-family: $monospace-font
text-align: center
color: RGB($secondary)
> a
margin: 0
font-size: inherit
font-family: inherit
color: RGB($accent)
text-decoration: none
cursor: pointer
&:hover
text-decoration: underline

View file

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game Event 23</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap">
<!-- Phosphor Icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<!-- Stylesheets -->
{% assets "scripts" %}<script src="{{ ASSET_URL }}" defer></script>{% endassets %}
<!-- Scripts -->
{% assets "styles" %}<link rel="stylesheet" href="{{ ASSET_URL }}" type="text/css" defer>{% endassets %}
</head>
<body>
<nav>
<div class="title"><p>DV8 Game Expo <span>2023</span></p></div>
<span><!-- This is a separator --></span>
<ul>
<li><a href="{{ url_for('website.index') }}#">Home</a></li>
<li><a href="{{ url_for('website.index') }}#What_is_this?">About</a></li>
<li><a href="{{ url_for('website.index') }}#Student_Games">Games</a></li>
{% if current_user.is_authenticated %}<li><a href="{{ url_for('website.logout') }}">Logout</a></li>{% endif %}
</ul>
</nav>
<span class="background">
<img src="{% block background %}{% endblock %}" alt="Background">
</span>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>Game Event 2023 | <a href="https://github.com/Fluffy-Bean/GameExpo23" target="_blank">Server Source</a></p>
</footer>
</body>
</html>

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block background %}{{ url_for('static', filename='images/default.jpg') }}{% endblock %}
{% block content %}
<header>
<h1>{{ game.name }}</h1>
<p>Editor</p>
</header>
{% endblock %}

View file

@ -1,43 +0,0 @@
{% extends "base.html" %}
{% block background %}
{% if game.background %}
{{ url_for('static', filename='images/backgrounds/' + game.background) }}
{% else %}
{{ url_for('static', filename='images/default.jpg') }}
{% endif %}
{% endblock %}
{% block content %}
<header>
{% if game.logo %}
<img src="{{ url_for('static', filename='images/logos/' + game.logo) }}" alt="{{ game.name }} Logo">
{% else %}
<h1>{{ game.name }}</h1>
{% endif %}
<p>By {{ game.studio }}</p>
<p>
{% for person in game.authors %}
{{ person.name }}
{% if not loop.last %}, {% endif %}
{% endfor %}
</p>
</header>
<section id="Description">
<h2>Description</h2>
<p>{{ game.description }}</p>
</section>
<section id="Tags">
<ul>
{% for tag in game.tags %}
<li>{{ tag }}</li>
{% endfor %}
</ul>
</section>
<section id="Images">
{% for image in game.images %}
<img src="{{ url_for('static', filename='images/user/' + image) }}" alt="{{ image.alt }}">
{% endfor %}
</section>
{% endblock %}

View file

@ -1,48 +0,0 @@
{% extends "base.html" %}
{% block background %}{{ url_for('static', filename='images/default.jpg') }}{% endblock %}
{% block content %}
<header>
<p>Welcome to the</p>
<h1>DV8 Game Expo <span data-text="2023">2023</span>!</h1>
<i class="ph-bold ph-caret-double-down"></i>
</header>
<section id="What_is_this?">
<h2>What is this?</h2>
<p>The DV8 Game Expo, is a showcase of the works and efforts of students from the past year. This includes Level 3 year 1 and year 2.</p>
</section>
<section id="Student_Games">
<h2>Student Games</h2>
<p>Here are some games AAAA</p>
<div class="games">
{% for game in games %}
<a class="game-box" href="{{ url_for('website.g', game_id=game.id) }}">
{% if game.background %}
<img src="{{ url_for('static', filename='images/backgrounds/' + game.background) }}" alt="{{ game.name }}" class="background">
{% else %}
<img src="{{ url_for('static', filename='images/default.jpg') }}" alt="{{ game.name }}" class="background">
{% endif %}
<div>
{% if game.logo %}
<img src="{{ url_for('static', filename='images/logos/' + game.logo) }}" alt="{{ game.name }}" class="logo">
{% else %}
<h2>{{ game.name }}</h2>
{% endif %}
<p>{{ game.studio }}</p>
<!-- <p>{{ game.description|truncate(100) }}</p> -->
<span><!-- Seperator --></span>
<ul>
{% for tag in game.tags %}
<li>{{ tag.tag }}</li>
{% endfor %}
</ul>
</div>
</a>
{% endfor %}
</div>
</section>
{% endblock %}

View file

@ -1,22 +0,0 @@
{% extends "base.html" %}
{% block background %}{{ url_for('static', filename='images/default.jpg') }}{% endblock %}
{% block content %}
<section class="center">
<div class="login">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
{% else %}
<p>Do not share your UUID</p>
{% endif %}
{% endwith %}
<form action="{{ url_for('website.login') }}" method="POST">
{{ form.csrf_token }}
{{ form.uuid(size=36) }}
<button type="submit">Login</button>
</form>
</div>
</section>
{% endblock %}

View file

@ -1,6 +1,12 @@
# GameExpo23
The all-new and improved DV8 Game Expo website
All new and improved DV8 Game Expo project
While the Expo page got scrapped due to multiple reasons, this repo still includes the following:
- TheFrontRooms Server
- Postgres Database for the Server
- Caddy Proxy for hosting
If, like me, you want to run the Postgres server online for whatever reason, I suggest giving your database a very stong password, change the default user and database names and use the following guide for `fail2ban` support
https://warlord0blog.wordpress.com/2022/09/14/fail2ban-postgresql/
# Nerd Info
Written on the Flask app-factory style with Python
Ran with Gunicorn on Docker, using Caddy for the reverse-proxy

2
TFR/.gitignore vendored
View file

@ -120,7 +120,7 @@ celerybeat.pid
*.sage.py
# Environments
.env
../.env
.venv
env/
venv/

View file

@ -27,3 +27,4 @@ fi
# Start server!!!!
echo "Starting server..."
gunicorn --bind tfr:8000 server:app
# flask --app server run --port 8000 --host tfr

View file

@ -7,7 +7,7 @@ from werkzeug.exceptions import HTTPException
from server.extensions import db, migrate, cache, assets, login_manager
from server.models import Users
from server.config import MIGRATION_DIR, INSTANCE_DIR
from server import views, auth, api
from server import views, auth, api, filters
app = Flask(__name__, instance_path=INSTANCE_DIR)
@ -39,6 +39,7 @@ cache.init_app(app)
app.register_blueprint(views.blueprint)
app.register_blueprint(auth.blueprint)
app.register_blueprint(api.blueprint)
app.register_blueprint(filters.blueprint)
@login_manager.user_loader

View file

@ -43,39 +43,34 @@ def tokens():
@blueprint.route("/post", methods=["POST"])
def post():
form = request.form
error = []
session_key = request.form.get("session", None)
version = request.form.get("version", "alpha")
difficulty = request.form.get("difficulty", 0)
score = request.form.get("score", 0)
if not form:
error.append("No form data provided!")
if not form["session"]:
error.append("No session key provided!")
if not form["version"]:
error.append("No version provided!")
if error:
return jsonify(error), 400
if not session_key:
return "No session key provided!"
if not score:
return "Score is not valid!"
try:
int(form["score"])
int(form["difficulty"])
float(score)
int(difficulty)
except TypeError:
error.append("Invalid score and difficulty must be valid numbers!")
return "Invalid score and difficulty must be valid numbers!"
if int(form["difficulty"]) not in GAME_DIFFICULTIES:
error.append("Invalid difficulty!")
if int(difficulty) not in GAME_DIFFICULTIES:
return "Invalid difficulty!"
session_data = Sessions.query.filter_by(auth_key=form["session"]).first()
session_data = Sessions.query.filter_by(auth_key=session_key).first()
if not session_data:
error.append("Authentication failed!")
if error:
return jsonify(error), 400
return "Authentication failed!"
score = Scores(
score=int(form["score"]),
difficulty=int(form["difficulty"]),
version=form["version"],
score=float(score),
difficulty=int(difficulty),
version=version,
user_id=session_data.user_id,
)

View file

@ -82,7 +82,7 @@ def login():
if error:
for err in error:
flash(err, "error")
return redirect(url_for("auth.account"))
return redirect(url_for("views.account"))
login_user(user, remember=True)
flash("Successfully logged in!", "success")

13
TFR/server/filters.py Normal file
View file

@ -0,0 +1,13 @@
import datetime
from flask import Blueprint
blueprint = Blueprint('filters', __name__, template_folder='templates')
@blueprint.app_template_filter()
def format_result(dttm):
dttm = str(dttm).split('.')
time = datetime.timedelta(seconds=int(dttm[0]))
microtime = dttm[1][:3]
return f'{time}:{microtime}'

View file

@ -16,9 +16,11 @@ class Scores(db.Model):
"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True))
score = db.Column(db.Float, nullable=False)
difficulty = db.Column(db.Integer, nullable=False)
scored_at = db.Column(
db.DateTime,
nullable=False,
@ -31,7 +33,6 @@ class Scores(db.Model):
)
version = db.Column(db.String, default=GAME_VERSION)
user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True))
class Sessions(db.Model):
@ -41,9 +42,11 @@ class Sessions(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True))
auth_key = db.Column(db.String, nullable=False, unique=True)
ip_address = db.Column(db.String)
device_type = db.Column(db.String)
created_at = db.Column(
db.DateTime,
nullable=False,
@ -56,14 +59,14 @@ class Sessions(db.Model):
)
class Reset(db.Model):
class PasswordReset(db.Model):
"""
Password reset table
"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True))
reset_key = db.Column(db.String, nullable=False, unique=True)
created_at = db.Column(
@ -73,6 +76,36 @@ class Reset(db.Model):
)
class Permissions(db.Model):
"""
Permissions table
"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True))
user_ban = db.Column(db.Boolean, default=False)
user_warn = db.Column(db.Boolean, default=False)
score_removal = db.Column(db.Boolean, default=False)
score_edit = db.Column(db.Boolean, default=False)
admin_panel = db.Column(db.Boolean, default=False)
admin_promote = db.Column(db.Boolean, default=False)
admin_demote = db.Column(db.Boolean, default=False)
class ProfileTags(db.Model):
"""
Profile Tags table
"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True))
tag = db.Column(db.String, nullable=False)
class Users(db.Model, UserMixin):
"""
User table
@ -93,6 +126,8 @@ class Users(db.Model, UserMixin):
scores = db.relationship("Scores", backref=db.backref("users", lazy=True))
tokens = db.relationship("Sessions", backref=db.backref("users", lazy=True))
reset = db.relationship("PasswordReset", backref=db.backref("users", lazy=True))
tags = db.relationship("ProfileTags", backref=db.backref("users", lazy=True))
def get_id(self):
return str(self.alt_id)

View file

@ -1,5 +1,8 @@
{% extends "base.html" %}
{% block content %}
<h2>What is The Front Rooms?</h2>
<h2>Project Redacted</h2>
<p>The Front Rooms is a game based on The Backrooms Genre of games.</p>
<h2>Is my data secured?</h2>
<p>Yes, all passwords and emails are hashed and salted, and at no point stored in plain text.</p>
{% endblock %}

View file

@ -6,12 +6,12 @@
<form action="{{ url_for('auth.login') }}" method="POST">
<span class="text-input">
<label for="login-username">Username</label>
<input type="text" name="username" placeholder="Jerry" id="login-username" required>
<input type="text" name="username" id="login-username" required>
</span>
<span class="text-input">
<label for="login-password">Password</label>
<input type="password" name="password" placeholder="password123" id="login-password" required>
<input type="password" name="password" id="login-password" required>
</span>
<button type="submit" class="button primary">Login</button>
@ -24,17 +24,17 @@
<form action="{{ url_for('auth.register') }}" method="POST">
<span class="text-input">
<label for="register-username">Username</label>
<input type="text" name="username" placeholder="Jerry" id="register-username" required>
<input type="text" name="username" id="register-username" required>
</span>
<span class="text-input">
<label for="register-email">Username</label>
<input type="text" name="email" placeholder="jerry@example.com" id="register-email" required>
<label for="register-email">Email</label>
<input type="text" name="email" id="register-email" required>
</span>
<span class="text-input">
<label for="register-password">Password</label>
<input type="password" name="password" placeholder="password123" id="register-password" required>
<input type="password" name="password" id="register-password" required>
</span>
<button type="submit" class="button primary">Register</button>

View file

@ -12,10 +12,12 @@
</select>
<select name="ver" class="button">
<option value="alpha" {% if ver=="alpha" %}selected{% endif %}>Alpha</option>
<option value="beta" {% if ver=="beta" %}selected{% endif %}>Beta</option>
<option value="1.0" {% if ver=="1.0" %}selected{% endif %}>1.0</option>
<option value="1.1" {% if ver=="1.1" %}selected{% endif %}>1.1</option>
{% for game_version in config["GAME_VERSIONS"] %}
<option
value="{{ game_version }}"
{% if ver==game_version %}selected{% endif %}
>{{ game_version }}</option>
{% endfor %}
</select>
<span class="text-input">
@ -56,7 +58,7 @@
<td>{{ score.users.username }}</td>
{% endif %}
<td>{{ score.score }}</td>
<td>{{ score.score | format_result }}</td>
<td>{{ score.scored_at.strftime('%Y-%m-%d') }}</td>
<!-- <td>{{ score.version }}</td> -->
</tr>

View file

@ -13,22 +13,22 @@ services:
- ./Caddy/config:/config
environment:
THE_FRONT_ROOMS_DOMAIN: ${THE_FRONT_ROOMS_DOMAIN}
GAME_EXPO_DOMAIN: ${GAME_EXPO_DOMAIN}
links:
- tfr
# - expo
db:
image: postgres:alpine
restart: unless-stopped
# ports:
# - "5432:5432"
ports:
- "5432:5432"
volumes:
- ./Postgres/data:/var/lib/postgresql/data
- /var/log/postgresql:/var/log/postgresql
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
command: postgres -c log_connections=on -c log_line_prefix='%m {%h} [%p] %q%u@%d ' -c log_directory='/var/log/postgresql' -c log_truncate_on_rotation=off -c log_rotation_age=1d
links:
- tfr
@ -44,12 +44,3 @@ services:
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_HOST: db
DB_NAME: ${POSTGRES_DB}
# expo:
# build: GameExpo
# restart: unless-stopped
# volumes:
# - ./GameExpo/storage:/data/storage
# - ./GameExpo/logs:/data/logs
# environment:
# FLASK_KEY: ${GAME_EXPO_SECRETE_KEY}