Player Addition

This commit is contained in:
Jack Jackson 2024-01-22 23:20:29 -08:00
parent ab3899e5c5
commit 731bce91a7
10 changed files with 242 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
instance/

6
DEVELOPMENT.md Normal file
View File

@ -0,0 +1,6 @@
# Run locally
In increasing complexity:
* `./basic-run.sh` - just raw-dog it
* `docker compose up --build`

51
Dockerfile Normal file
View File

@ -0,0 +1,51 @@
# syntax=docker/dockerfile:1
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
ARG PYTHON_VERSION=3.9.6
FROM python:${PYTHON_VERSION}-slim as base
# Prevents Python from writing pyc files.
ENV PYTHONDONTWRITEBYTECODE=1
# Keeps Python from buffering stdout and stderr to avoid situations where
# the application crashes without emitting any logs due to buffering.
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
# Leverage a bind mount to requirements.txt to avoid having to copy them into
# into this layer.
RUN --mount=type=cache,target=/root/.cache/pip \
--mount=type=bind,source=requirements.txt,target=requirements.txt \
python -m pip install -r requirements.txt
# Switch to the non-privileged user to run the application.
USER appuser
# Copy the source code into the container.
COPY . .
# Expose the port that the application listens on.
EXPOSE 5000
# Run the application. Note that this sets a hard-coded secret key, which is not secure - use proper secret generation
# and management in production!
CMD FLASK_APP=app SECRET_KEY=super-secret flask run --host=0.0.0.0

40
NOTES.md Normal file
View File

@ -0,0 +1,40 @@
# Development plan
- [X] Basic Game Definition
- [X] Basic Player Definition
- [ ] Basic Deck Definition
- [ ] Basic testing
- [ ] Swagger API
- [ ] Local development tool to clear/seed database
...
- [ ] Authentication (will need to link `user` table to `player`)
...
- [ ] Helm chart including an initContainer to create the database if it doesn't exist already
# Tables
Tables:
* Decks
* Name
* Description
* Owner
* DecklistId (optional)
* Players (not the same as Users! Can model a Player who is not a User)
* Users
* Standard auth stuff
* Games
* Date
* Location
* DeckIds (array)
* WinningDeckId
* FinalTurnNum
* Notes
# Database Migrations
https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/quickstart/#create-the-tables
# Authentication
https://www.digitalocean.com/community/tutorials/how-to-add-authentication-to-your-app-with-flask-login

32
app/__init__.py Normal file
View File

@ -0,0 +1,32 @@
import os
import sys
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
secret_key = os.environ.get('SECRET_KEY')
if not secret_key:
sys.stderr.write('YOU NEED TO SET AN ENV VARIABLE NAMED SECRET_KEY\n')
sys.exit(1)
app.config['SECRET_KEY'] = secret_key
# TODO - support other database types 🙃
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
db.init_app(app)
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
# TODO - understand how this works, since `db.create_all()` requires that the model classes have already been
# imported in order to know what to create. Perhaps `__init__.py` just magically has the context of everything in
# its module? Good opportunity to learn more about the Python import system!
with app.app_context():
db.create_all()
return app

22
app/main.py Normal file
View File

@ -0,0 +1,22 @@
from flask import Blueprint, request
from . import db
from .models import Deck, Game, Player
main = Blueprint("main", __name__)
@main.route("/")
def index():
return 'Hello, World - but new!'
@main.route("/player", methods=["POST"])
def create_player():
data = request.json
player = Player(
name=data['name']
)
db.session.add(player)
db.session.commit()
return {'id': player.id}
# TODO - implement a GET method - can it be a separate method, or must it be the same annotation with an `if method==`?
# Time for testing, methinks!

32
app/models.py Normal file
View File

@ -0,0 +1,32 @@
from . import db
# Note that a `Player` is "someone who plays in the Pod", whereas `User` (which will be implemented later) is "a user of
# this system". While all Users will _probably_ be Players, they do not have to be - and, it is likely that several
# Players will not be Users (if they don't register to use the system).
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
class Deck(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), nullable=False)
description = db.Column(db.String)
owner = db.Column(db.String, db.ForeignKey(Player.__table__.c.id), nullable=False, )
class Game(db.Model):
id = db.Column(db.Integer, primary_key=True)
# TODO - columns like `location`, `writeups`, etc.
# Not wild about this structure ("fill in non-null columns to indicate decks that were present"), but what can you
# do with a database that doesn't support arrays? (Postgres _does_, but I don't wanna ramp up on a whole other
# database system just for that...)
deck_1 = db.Column(db.Integer)
deck_2 = db.Column(db.Integer)
deck_3 = db.Column(db.Integer)
deck_4 = db.Column(db.Integer)
deck_5 = db.Column(db.Integer)
deck_6 = db.Column(db.Integer)
deck_7 = db.Column(db.Integer)
deck_8 = db.Column(db.Integer)
deck_9 = db.Column(db.Integer)
deck_10 = db.Column(db.Integer)

5
basic-run.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Idempotent
source .venv/bin/activate
FLASK_APP=app SECRET_KEY=super-secret flask run

49
compose.yaml Normal file
View File

@ -0,0 +1,49 @@
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/
# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
server:
build:
context: .
ports:
- 5000:5000
# The commented out section below is an example of how to define a PostgreSQL
# database that your application can use. `depends_on` tells Docker Compose to
# start the database before your application. The `db-data` volume persists the
# database data between container restarts. The `db-password` secret is used
# to set the database password. You must create `db/password.txt` and add
# a password of your choosing to it before running `docker compose up`.
# depends_on:
# db:
# condition: service_healthy
# db:
# image: postgres
# restart: always
# user: postgres
# secrets:
# - db-password
# volumes:
# - db-data:/var/lib/postgresql/data
# environment:
# - POSTGRES_DB=example
# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
# expose:
# - 5432
# healthcheck:
# test: [ "CMD", "pg_isready" ]
# interval: 10s
# timeout: 5s
# retries: 5
# volumes:
# db-data:
# secrets:
# db-password:
# file: db/password.txt

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
Flask
Flask-SQLAlchemy
flasgger
flask-login