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

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

View File

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

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

View File

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