From 60f96f43f71a71335598a297993dfa0c0d320cdd Mon Sep 17 00:00:00 2001 From: HumanoidSandvichDispenser Date: Sun, 10 Nov 2024 17:21:25 -0800 Subject: [PATCH] Refactor models into their own files --- backend-flask/app.py | 8 +- backend-flask/app_db.py | 26 +++ backend-flask/login.py | 6 +- backend-flask/middleware.py | 8 +- backend-flask/models.py | 184 ------------------ backend-flask/models/__init__.py | 0 backend-flask/models/auth_session.py | 27 +++ backend-flask/models/player.py | 27 +++ backend-flask/models/player_team.py | 41 ++++ .../models/player_team_availability.py | 30 +++ backend-flask/models/player_team_role.py | 48 +++++ backend-flask/models/team.py | 33 ++++ backend-flask/models/team_invite.py | 28 +++ backend-flask/schedule.py | 6 +- backend-flask/team.py | 12 +- 15 files changed, 285 insertions(+), 199 deletions(-) create mode 100644 backend-flask/app_db.py delete mode 100644 backend-flask/models.py create mode 100644 backend-flask/models/__init__.py create mode 100644 backend-flask/models/auth_session.py create mode 100644 backend-flask/models/player.py create mode 100644 backend-flask/models/player_team.py create mode 100644 backend-flask/models/player_team_availability.py create mode 100644 backend-flask/models/player_team_role.py create mode 100644 backend-flask/models/team.py create mode 100644 backend-flask/models/team_invite.py diff --git a/backend-flask/app.py b/backend-flask/app.py index 6b89dc6..0f67939 100644 --- a/backend-flask/app.py +++ b/backend-flask/app.py @@ -1,14 +1,12 @@ -from flask import Blueprint, Flask, make_response, request +from flask import Blueprint, make_response, request +from app_db import app, connect_db_with_app import login import schedule import team -from models import init_db from spec import spec -app = Flask(__name__) - -init_db(app) +connect_db_with_app() api = Blueprint("api", __name__, url_prefix="/api") api.register_blueprint(login.api_login) diff --git a/backend-flask/app_db.py b/backend-flask/app_db.py new file mode 100644 index 0000000..0fc8657 --- /dev/null +++ b/backend-flask/app_db.py @@ -0,0 +1,26 @@ +from flask import Flask +from flask_migrate import Migrate +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import MetaData +from sqlalchemy.orm import DeclarativeBase + +class BaseModel(DeclarativeBase): + pass + +convention = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" +} + +def connect_db_with_app(): + app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite3" + db.init_app(app) + migrate.init_app(app, db) + +metadata = MetaData(naming_convention=convention) +app = Flask(__name__) +db = SQLAlchemy(model_class=BaseModel, metadata=metadata) +migrate = Migrate(render_as_batch=True) diff --git a/backend-flask/login.py b/backend-flask/login.py index 60ee1d0..b692ab5 100644 --- a/backend-flask/login.py +++ b/backend-flask/login.py @@ -7,7 +7,9 @@ import requests from spectree import Response from spec import spec import models -from models import AuthSession, Player, PlayerSchema, db +from app_db import db +from models.auth_session import AuthSession +from models.player import Player, PlayerSchema from middleware import requires_authentication api_login = Blueprint("login", __name__, url_prefix="/login") @@ -107,7 +109,7 @@ def generate_base36(length): alphabet = string.digits + string.ascii_uppercase return "".join(random.choice(alphabet) for _ in range(length)) -def create_auth_session_for_player(player: models.Player): +def create_auth_session_for_player(player: Player): session = AuthSession() session.player = player diff --git a/backend-flask/middleware.py b/backend-flask/middleware.py index 46e0175..1ecf001 100644 --- a/backend-flask/middleware.py +++ b/backend-flask/middleware.py @@ -1,7 +1,7 @@ from functools import wraps from flask import abort, make_response, request -from models import db -import models +from app_db import db +from models.auth_session import AuthSession def requires_authentication(f): @@ -12,8 +12,8 @@ def requires_authentication(f): if not auth: abort(401) - statement = db.select(models.AuthSession).filter_by(key=auth) - auth_session: models.AuthSession | None = \ + statement = db.select(AuthSession).filter_by(key=auth) + auth_session: AuthSession | None = \ db.session.execute(statement).scalar_one_or_none() if not auth_session: diff --git a/backend-flask/models.py b/backend-flask/models.py deleted file mode 100644 index 99da6f4..0000000 --- a/backend-flask/models.py +++ /dev/null @@ -1,184 +0,0 @@ -from datetime import date, datetime, timedelta -import enum -from typing import List -from flask import Flask -from flask_sqlalchemy import SQLAlchemy -from flask_migrate import Migrate -from sqlalchemy import TIMESTAMP, BigInteger, Boolean, Enum, ForeignKey, ForeignKeyConstraint, Integer, Interval, MetaData, SmallInteger, String, func -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship -from sqlalchemy_utc import UtcDateTime - -import spec - -class Base(DeclarativeBase): - pass - -convention = { - "ix": "ix_%(column_0_label)s", - "uq": "uq_%(table_name)s_%(column_0_name)s", - "ck": "ck_%(table_name)s_%(constraint_name)s", - "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", - "pk": "pk_%(table_name)s" -} - -metadata = MetaData(naming_convention=convention) -db = SQLAlchemy(model_class=Base, metadata=metadata) -migrate = Migrate(render_as_batch=True) - -class Player(db.Model): - __tablename__ = "players" - - steam_id: Mapped[int] = mapped_column(BigInteger, primary_key=True) - username: Mapped[str] = mapped_column(String(63)) - - teams: Mapped[List["PlayerTeam"]] = relationship(back_populates="player") - auth_sessions: Mapped[List["AuthSession"]] = relationship(back_populates="player") - - created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) - -class PlayerSchema(spec.BaseModel): - steam_id: str - username: str - -class Team(db.Model): - __tablename__ = "teams" - - id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True) - team_name: Mapped[str] = mapped_column(String(63), unique=True) - discord_webhook_url: Mapped[str] = mapped_column(String(255), nullable=True) - tz_timezone: Mapped[str] = mapped_column(String(31), default="Etc/UTC") - minute_offset: Mapped[int] = mapped_column(SmallInteger, default=0) - - players: Mapped[List["PlayerTeam"]] = relationship(back_populates="team") - invites: Mapped[List["TeamInvite"]] = relationship(back_populates="team") - - created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) - -class TeamSchema(spec.BaseModel): - id: int - team_name: str - discord_webhook_url: str | None - tz_timezone: str - minute_offset: int - #players: list[PlayerTeamSpec] | None - -class PlayerTeam(db.Model): - __tablename__ = "players_teams" - - class TeamRole(enum.Enum): - Player = 0 - CoachMentor = 1 - - player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id"), primary_key=True) - team_id: Mapped[int] = mapped_column(ForeignKey("teams.id"), primary_key=True) - - player: Mapped["Player"] = relationship(back_populates="teams") - team: Mapped["Team"] = relationship(back_populates="players") - - player_roles: Mapped[List["PlayerTeamRole"]] = relationship("PlayerTeamRole", back_populates="player_team") - availability: Mapped[List["PlayerTeamAvailability"]] = relationship(back_populates="player_team") - - team_role: Mapped[TeamRole] = mapped_column(Enum(TeamRole), default=TeamRole.Player) - playtime: Mapped[timedelta] = mapped_column(Interval, default=timedelta(0)) - is_team_leader: Mapped[bool] = mapped_column(Boolean, default=False) - created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) - -class PlayerTeamSchema(spec.BaseModel): - player: PlayerSchema - team: TeamSchema - -class PlayerTeamRole(db.Model): - __tablename__ = "players_teams_roles" - - class Role(enum.Enum): - Unknown = 0 - - Scout = 1 - PocketScout = 2 - FlankScout = 3 - - Soldier = 4 - PocketSoldier = 5 - Roamer = 6 - - Pyro = 7 - Demoman = 8 - HeavyWeapons = 9 - Engineer = 10 - Medic = 11 - Sniper = 12 - Spy = 13 - - player_id: Mapped[int] = mapped_column(primary_key=True) - team_id: Mapped[int] = mapped_column(primary_key=True) - - player_team: Mapped["PlayerTeam"] = relationship("PlayerTeam", back_populates="player_roles") - - #player: Mapped["Player"] = relationship(back_populates="teams") - - role: Mapped[Role] = mapped_column(Enum(Role), primary_key=True) - is_main: Mapped[bool] = mapped_column(Boolean) - - __table_args__ = ( - ForeignKeyConstraint( - [player_id, team_id], - [PlayerTeam.player_id, PlayerTeam.team_id] - ), - ) - -class PlayerTeamAvailability(db.Model): - __tablename__ = "players_teams_availability" - - player_id: Mapped[int] = mapped_column(primary_key=True) - team_id: Mapped[int] = mapped_column(primary_key=True) - start_time: Mapped[datetime] = mapped_column(UtcDateTime, primary_key=True) - - player_team: Mapped["PlayerTeam"] = relationship( - "PlayerTeam", back_populates="availability") - - availability: Mapped[int] = mapped_column(Integer, default=2) - end_time: Mapped[datetime] = mapped_column(UtcDateTime) - - __table_args__ = ( - ForeignKeyConstraint( - [player_id, team_id], - [PlayerTeam.player_id, PlayerTeam.team_id] - ), - ) - -class TeamInvite(db.Model): - __tablename__ = "team_invites" - - key: Mapped[str] = mapped_column(String(31), primary_key=True) - team_id: Mapped[int] = mapped_column(ForeignKey("teams.id")) - delete_on_use: Mapped[bool] = mapped_column(Boolean, default=True) - created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) - - team: Mapped["Team"] = relationship(back_populates="invites") - -class TeamInviteSchema(spec.BaseModel): - key: str - team_id: int - created_at: datetime - -class AuthSession(db.Model): - __tablename__ = "auth_sessions" - - @staticmethod - def gen_cookie_expiration(): - valid_until = date.today() + timedelta(days=7) - AuthSession.gen_cookie_expiration() - return valid_until - - key: Mapped[str] = mapped_column(String(31), primary_key=True) - player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id")) - created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) - - player: Mapped["Player"] = relationship(back_populates="auth_sessions") - -def init_db(app: Flask): - app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite3" - #app.config["SQLALCHEMY_ECHO"] = True - db.init_app(app) - migrate.init_app(app, db) - return app diff --git a/backend-flask/models/__init__.py b/backend-flask/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend-flask/models/auth_session.py b/backend-flask/models/auth_session.py new file mode 100644 index 0000000..401b60a --- /dev/null +++ b/backend-flask/models/auth_session.py @@ -0,0 +1,27 @@ +from datetime import date, datetime, timedelta + +from sqlalchemy.orm import mapped_column, relationship +from sqlalchemy.orm.attributes import Mapped +from sqlalchemy.schema import ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.types import TIMESTAMP, String +import app_db + + +class AuthSession(app_db.BaseModel): + __tablename__ = "auth_sessions" + + @staticmethod + def gen_cookie_expiration(): + valid_until = date.today() + timedelta(days=7) + AuthSession.gen_cookie_expiration() + return valid_until + + key: Mapped[str] = mapped_column(String(31), primary_key=True) + player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id")) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) + + player: Mapped["Player"] = relationship(back_populates="auth_sessions") + + +from models.player import Player diff --git a/backend-flask/models/player.py b/backend-flask/models/player.py new file mode 100644 index 0000000..5f9e8a3 --- /dev/null +++ b/backend-flask/models/player.py @@ -0,0 +1,27 @@ +from datetime import datetime +from sqlalchemy.orm import mapped_column, relationship +from sqlalchemy.orm.attributes import Mapped +from sqlalchemy.sql import func +from sqlalchemy.types import TIMESTAMP, BigInteger, String +import app_db +import spec + + +class Player(app_db.BaseModel): + __tablename__ = "players" + + steam_id: Mapped[int] = mapped_column(BigInteger, primary_key=True) + username: Mapped[str] = mapped_column(String(63)) + + teams: Mapped[list["PlayerTeam"]] = relationship(back_populates="player") + auth_sessions: Mapped[list["AuthSession"]] = relationship(back_populates="player") + + created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) + +class PlayerSchema(spec.BaseModel): + steam_id: str + username: str + + +from models.auth_session import AuthSession +from models.player_team import PlayerTeam diff --git a/backend-flask/models/player_team.py b/backend-flask/models/player_team.py new file mode 100644 index 0000000..f00bffc --- /dev/null +++ b/backend-flask/models/player_team.py @@ -0,0 +1,41 @@ +from datetime import datetime, timedelta +import enum +from sqlalchemy.orm import mapped_column, relationship +from sqlalchemy.orm.attributes import Mapped +from sqlalchemy.orm.properties import ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.types import TIMESTAMP, Boolean, Enum, Interval +import app_db +import spec + + +class PlayerTeam(app_db.BaseModel): + __tablename__ = "players_teams" + + class TeamRole(enum.Enum): + Player = 0 + CoachMentor = 1 + + player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id"), primary_key=True) + team_id: Mapped[int] = mapped_column(ForeignKey("teams.id"), primary_key=True) + + player: Mapped["Player"] = relationship(back_populates="teams") + team: Mapped["Team"] = relationship(back_populates="players") + + player_roles: Mapped[list["PlayerTeamRole"]] = relationship("PlayerTeamRole", back_populates="player_team") + availability: Mapped[list["PlayerTeamAvailability"]] = relationship(back_populates="player_team") + + team_role: Mapped[TeamRole] = mapped_column(Enum(TeamRole), default=TeamRole.Player) + playtime: Mapped[timedelta] = mapped_column(Interval, default=timedelta(0)) + is_team_leader: Mapped[bool] = mapped_column(Boolean, default=False) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) + +class PlayerTeamSchema(spec.BaseModel): + player: "PlayerSchema" + team: "TeamSchema" + + +from models.player import Player, PlayerSchema +from models.player_team_availability import PlayerTeamAvailability +from models.player_team_role import PlayerTeamRole +from models.team import Team, TeamSchema diff --git a/backend-flask/models/player_team_availability.py b/backend-flask/models/player_team_availability.py new file mode 100644 index 0000000..60a36e8 --- /dev/null +++ b/backend-flask/models/player_team_availability.py @@ -0,0 +1,30 @@ +from datetime import datetime +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.schema import ForeignKeyConstraint +from sqlalchemy.types import Integer +from sqlalchemy_utc import UtcDateTime +import app_db + + +class PlayerTeamAvailability(app_db.BaseModel): + __tablename__ = "players_teams_availability" + + player_id: Mapped[int] = mapped_column(primary_key=True) + team_id: Mapped[int] = mapped_column(primary_key=True) + start_time: Mapped[datetime] = mapped_column(UtcDateTime, primary_key=True) + + player_team: Mapped["PlayerTeam"] = relationship( + "PlayerTeam", back_populates="availability") + + availability: Mapped[int] = mapped_column(Integer, default=2) + end_time: Mapped[datetime] = mapped_column(UtcDateTime) + + + from models.player_team import PlayerTeam + + __table_args__ = ( + ForeignKeyConstraint( + [player_id, team_id], + [PlayerTeam.player_id, PlayerTeam.team_id] + ), + ) diff --git a/backend-flask/models/player_team_role.py b/backend-flask/models/player_team_role.py new file mode 100644 index 0000000..bd5829e --- /dev/null +++ b/backend-flask/models/player_team_role.py @@ -0,0 +1,48 @@ +import enum + +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.schema import ForeignKeyConstraint +from sqlalchemy.types import Boolean, Enum +import app_db + + +class PlayerTeamRole(app_db.BaseModel): + __tablename__ = "players_teams_roles" + + class Role(enum.Enum): + Unknown = 0 + + Scout = 1 + PocketScout = 2 + FlankScout = 3 + + Soldier = 4 + PocketSoldier = 5 + Roamer = 6 + + Pyro = 7 + Demoman = 8 + HeavyWeapons = 9 + Engineer = 10 + Medic = 11 + Sniper = 12 + Spy = 13 + + player_id: Mapped[int] = mapped_column(primary_key=True) + team_id: Mapped[int] = mapped_column(primary_key=True) + + player_team: Mapped["PlayerTeam"] = relationship("PlayerTeam", back_populates="player_roles") + + #player: Mapped["Player"] = relationship(back_populates="teams") + + role: Mapped[Role] = mapped_column(Enum(Role), primary_key=True) + is_main: Mapped[bool] = mapped_column(Boolean) + + from models.player_team import PlayerTeam + + __table_args__ = ( + ForeignKeyConstraint( + [player_id, team_id], + [PlayerTeam.player_id, PlayerTeam.team_id] + ), + ) diff --git a/backend-flask/models/team.py b/backend-flask/models/team.py new file mode 100644 index 0000000..537a9b9 --- /dev/null +++ b/backend-flask/models/team.py @@ -0,0 +1,33 @@ +from datetime import datetime +from sqlalchemy.orm import mapped_column, relationship +from sqlalchemy.orm.attributes import Mapped +from sqlalchemy.sql import func +from sqlalchemy.types import TIMESTAMP, Integer, SmallInteger, String +import app_db +import spec + +class Team(app_db.BaseModel): + __tablename__ = "teams" + + id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True) + team_name: Mapped[str] = mapped_column(String(63), unique=True) + discord_webhook_url: Mapped[str] = mapped_column(String(255), nullable=True) + tz_timezone: Mapped[str] = mapped_column(String(31), default="Etc/UTC") + minute_offset: Mapped[int] = mapped_column(SmallInteger, default=0) + + players: Mapped[list["PlayerTeam"]] = relationship(back_populates="team") + invites: Mapped[list["TeamInvite"]] = relationship(back_populates="team") + + created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) + +class TeamSchema(spec.BaseModel): + id: int + team_name: str + discord_webhook_url: str | None + tz_timezone: str + minute_offset: int + #players: list[PlayerTeamSpec] | None + + +from models.player_team import PlayerTeam +from models.team_invite import TeamInvite diff --git a/backend-flask/models/team_invite.py b/backend-flask/models/team_invite.py new file mode 100644 index 0000000..84ec108 --- /dev/null +++ b/backend-flask/models/team_invite.py @@ -0,0 +1,28 @@ +from datetime import datetime + +from sqlalchemy.orm import mapped_column, relationship +from sqlalchemy.orm.attributes import Mapped +from sqlalchemy.orm.properties import ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.types import TIMESTAMP, Boolean, String +import app_db +import spec + + +class TeamInvite(app_db.BaseModel): + __tablename__ = "team_invites" + + key: Mapped[str] = mapped_column(String(31), primary_key=True) + team_id: Mapped[int] = mapped_column(ForeignKey("teams.id")) + delete_on_use: Mapped[bool] = mapped_column(Boolean, default=True) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now()) + + team: Mapped["Team"] = relationship(back_populates="invites") + +class TeamInviteSchema(spec.BaseModel): + key: str + team_id: int + created_at: datetime + + +from models.team import Team diff --git a/backend-flask/schedule.py b/backend-flask/schedule.py index a66de56..9ff6073 100644 --- a/backend-flask/schedule.py +++ b/backend-flask/schedule.py @@ -2,7 +2,11 @@ import datetime from typing import cast from flask import Blueprint, abort, jsonify, make_response, request from spectree import Response -from models import Player, PlayerTeam, PlayerTeamAvailability, PlayerTeamRole, db +from app_db import db +from models.player import Player +from models.player_team import PlayerTeam +from models.player_team_availability import PlayerTeamAvailability +from models.player_team_role import PlayerTeamRole from middleware import requires_authentication from spec import spec, BaseModel diff --git a/backend-flask/team.py b/backend-flask/team.py index bcf665d..5583756 100644 --- a/backend-flask/team.py +++ b/backend-flask/team.py @@ -7,7 +7,13 @@ from flask import Blueprint, abort, jsonify, make_response, request from pydantic.v1 import validator from spectree import Response from sqlalchemy.orm import joinedload, subqueryload -from models import Player, PlayerSchema, PlayerTeam, PlayerTeamAvailability, PlayerTeamRole, PlayerTeamSchema, Team, TeamInvite, TeamInviteSchema, TeamSchema, db +from app_db import db +from models.player import Player, PlayerSchema +from models.player_team import PlayerTeam +from models.player_team_availability import PlayerTeamAvailability +from models.player_team_role import PlayerTeamRole +from models.team import Team, TeamSchema +from models.team_invite import TeamInvite, TeamInviteSchema from middleware import requires_authentication import models from spec import spec, BaseModel @@ -51,10 +57,10 @@ class CreateTeamJson(BaseModel): return v class ViewTeamResponse(BaseModel): - team: models.TeamSchema + team: TeamSchema class ViewTeamsResponse(BaseModel): - teams: list[models.TeamSchema] + teams: list[TeamSchema] @api_team.post("/") @spec.validate(