Refactor models into their own files

master
John Montagu, the 4th Earl of Sandvich 2024-11-10 17:21:25 -08:00
parent ae6f5e8884
commit 60f96f43f7
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
15 changed files with 285 additions and 199 deletions

View File

@ -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 login
import schedule import schedule
import team import team
from models import init_db
from spec import spec from spec import spec
app = Flask(__name__) connect_db_with_app()
init_db(app)
api = Blueprint("api", __name__, url_prefix="/api") api = Blueprint("api", __name__, url_prefix="/api")
api.register_blueprint(login.api_login) api.register_blueprint(login.api_login)

View File

@ -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)

View File

@ -7,7 +7,9 @@ import requests
from spectree import Response from spectree import Response
from spec import spec from spec import spec
import models 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 from middleware import requires_authentication
api_login = Blueprint("login", __name__, url_prefix="/login") api_login = Blueprint("login", __name__, url_prefix="/login")
@ -107,7 +109,7 @@ def generate_base36(length):
alphabet = string.digits + string.ascii_uppercase alphabet = string.digits + string.ascii_uppercase
return "".join(random.choice(alphabet) for _ in range(length)) 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 = AuthSession()
session.player = player session.player = player

View File

@ -1,7 +1,7 @@
from functools import wraps from functools import wraps
from flask import abort, make_response, request from flask import abort, make_response, request
from models import db from app_db import db
import models from models.auth_session import AuthSession
def requires_authentication(f): def requires_authentication(f):
@ -12,8 +12,8 @@ def requires_authentication(f):
if not auth: if not auth:
abort(401) abort(401)
statement = db.select(models.AuthSession).filter_by(key=auth) statement = db.select(AuthSession).filter_by(key=auth)
auth_session: models.AuthSession | None = \ auth_session: AuthSession | None = \
db.session.execute(statement).scalar_one_or_none() db.session.execute(statement).scalar_one_or_none()
if not auth_session: if not auth_session:

View File

@ -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

View File

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]
),
)

View File

@ -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]
),
)

View File

@ -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

View File

@ -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

View File

@ -2,7 +2,11 @@ import datetime
from typing import cast from typing import cast
from flask import Blueprint, abort, jsonify, make_response, request from flask import Blueprint, abort, jsonify, make_response, request
from spectree import Response 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 middleware import requires_authentication
from spec import spec, BaseModel from spec import spec, BaseModel

View File

@ -7,7 +7,13 @@ from flask import Blueprint, abort, jsonify, make_response, request
from pydantic.v1 import validator from pydantic.v1 import validator
from spectree import Response from spectree import Response
from sqlalchemy.orm import joinedload, subqueryload 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 from middleware import requires_authentication
import models import models
from spec import spec, BaseModel from spec import spec, BaseModel
@ -51,10 +57,10 @@ class CreateTeamJson(BaseModel):
return v return v
class ViewTeamResponse(BaseModel): class ViewTeamResponse(BaseModel):
team: models.TeamSchema team: TeamSchema
class ViewTeamsResponse(BaseModel): class ViewTeamsResponse(BaseModel):
teams: list[models.TeamSchema] teams: list[TeamSchema]
@api_team.post("/") @api_team.post("/")
@spec.validate( @spec.validate(