Skip to content
This repository has been archived by the owner on Jul 7, 2024. It is now read-only.

Commit

Permalink
feat(auth): add groundwork for authentication based off fastapi docs
Browse files Browse the repository at this point in the history
(includes minor adaptations but has not been shaped for the project yet)

Signed-off-by: CyberFlame <[email protected]>
  • Loading branch information
CyberFlameGO committed Oct 17, 2023
1 parent d4160bc commit efa737f
Show file tree
Hide file tree
Showing 9 changed files with 490 additions and 84 deletions.
11 changes: 8 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from datetime import UTC, datetime
from enum import Enum
from typing import Annotated, Tuple, Type

import sentry_sdk
from fastapi import Depends, FastAPI, Request, status
from fastapi.responses import HTMLResponse, RedirectResponse, Response
from fastapi.staticfiles import StaticFiles
from fastapi.security import OAuth2PasswordBearer
from fastapi.templating import Jinja2Templates
from sqlalchemy.exc import IntegrityError, NoResultFound
from sqlalchemy.ext.asyncio import AsyncSession
Expand All @@ -28,9 +30,10 @@
app.mount("/static", StaticFiles(directory="static"), name="static")

db = utils.Database("data.db") # TODO: setup database tables and re-jig the spreadsheet layout
Session = Annotated[AsyncSession, Depends(db.get_session)]
oauth2_scheme = OAuth2PasswordBearer("token")

# TODO: adapt the following:
Session = Annotated[AsyncSession, Depends(db.get_session)]
OAuth2Scheme = Annotated[str, oauth2_scheme]


class Endpoint(Enum):
Expand Down Expand Up @@ -68,12 +71,14 @@ async def shutdown():


@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
async def home(request: Request, token: OAuth2Scheme):
"""
Home page
:param token:
:param request:
:return:
"""
print(token)
return templates.TemplateResponse(
"index.html",
{
Expand Down
Binary file modified data.db
Binary file not shown.
77 changes: 7 additions & 70 deletions models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,11 @@
"""
Base class
"""
from datetime import datetime
from typing import Set

from sqlalchemy import ForeignKey
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


class Base(AsyncAttrs, DeclarativeBase):
"""
Base class for all models
"""

__abstract__: bool = True
id: Mapped[int] = mapped_column(autoincrement=True, nullable=False, unique=True, primary_key=True)


class Game(Base):
__tablename__: str = "games"

match: Mapped[Set["Match"]] = relationship()
name: Mapped[str] = mapped_column(nullable=False, unique=True)
description: Mapped[str] = mapped_column(nullable=False)


class User(Base):
__tablename__: str = "users"

email: Mapped[str] = mapped_column(unique=True, nullable=False)
username: Mapped[str] = mapped_column(unique=True, nullable=False)
password: Mapped[str] = mapped_column(unique=True, nullable=False)
role: Mapped[str] = mapped_column(nullable=False)
first_name: Mapped[str] = mapped_column()
last_name: Mapped[str] = mapped_column()
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
Imports all contents from the package folder into this __init__.py file.
creator: Mapped[Set["Match"]] = relationship()
player: Mapped[Set["MatchPlayers"]] = relationship(back_populates="player")
noqa (no quality assurance) is a flake8 directive. flake8 is a linter, which checks code for style and syntax errors.
noqa tells flake8 to ignore the line in question, and for the rule code specified succeeding the directive.
F401: module imported but unused
"""

class Match(Base):
__tablename__: str = "matches"

game_id: Mapped[int] = mapped_column(ForeignKey("games.id"), nullable=False)
played_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)

creator_id: Mapped[str] = mapped_column(ForeignKey("users.id"))
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
players: Mapped[Set["MatchPlayers"]] = relationship(back_populates="match")
results: Mapped["MatchResult"] = relationship(back_populates="match")


class MatchPlayers(Base):
__tablename__: str = "matchplayers"

match: Mapped["Match"] = relationship(back_populates="players")
match_id: Mapped[int] = mapped_column(ForeignKey("matches.id"), nullable=False)
player: Mapped["User"] = relationship(back_populates="player")
player_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)


class MatchResult(Base):
__tablename__: str = "matchresults"

match: Mapped["Match"] = relationship(back_populates="results")
match_id: Mapped[int] = mapped_column(ForeignKey("matches.id"), unique=True, nullable=False)

won_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
lost_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False)
won: Mapped["User"] = relationship("User", foreign_keys=[won_id])
lost: Mapped["User"] = relationship("User", foreign_keys=[lost_id])
from .sqlalchemy import * # noqa F401
from .pydantic import * # noqa F401
30 changes: 30 additions & 0 deletions models/pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from datetime import UTC, datetime
from pydantic import BaseModel


# SQLModel would reduce the duplicated code greatly here and follow DRY, but it isn't compatible with my versions of
# SQLAlchemy and Pydantic - there isn't much I can do to reduce it easily, but it is worth noting.
class PydanticUser(BaseModel):
email: str
username: str
password: str
role: str
first_name: str
last_name: str
year_level: int
house: str

created_at: datetime = datetime.now(tz = UTC)


class Token(BaseModel):
access_token: str
token_type: str


class TokenData(BaseModel):
username: str | None = None


class UserInDB(PydanticUser):
hashed_password: str
77 changes: 77 additions & 0 deletions models/sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Base class
"""
from datetime import UTC, datetime
from typing import Set

from sqlalchemy import ForeignKey
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


class Base(AsyncAttrs, DeclarativeBase):
"""
Base class for all models
"""

__abstract__: bool = True
id: Mapped[int] = mapped_column(autoincrement=True, nullable=False, unique=True, primary_key=True)


class Game(Base):
__tablename__: str = "games"

match: Mapped[Set["Match"]] = relationship()
name: Mapped[str] = mapped_column(nullable=False, unique=True)
description: Mapped[str] = mapped_column(nullable=False)


class User(Base):
__tablename__: str = "users"

email: Mapped[str] = mapped_column(unique=True, nullable=False)
username: Mapped[str] = mapped_column(unique=True, nullable=False)
password: Mapped[str] = mapped_column(unique=True, nullable=False)
role: Mapped[str] = mapped_column(nullable=False)
first_name: Mapped[str] = mapped_column(nullable = False)
last_name: Mapped[str] = mapped_column(nullable = False)
year_level: Mapped[int] = mapped_column(nullable = True)
house: Mapped[str] = mapped_column(nullable = False)

created_at: Mapped[datetime] = mapped_column(default=datetime.now(tz = UTC))

creator: Mapped[Set["Match"]] = relationship()
player: Mapped[Set["MatchPlayers"]] = relationship(back_populates="player")


class Match(Base):
__tablename__: str = "matches"

game_id: Mapped[int] = mapped_column(ForeignKey("games.id"), nullable=False)
played_at: Mapped[datetime] = mapped_column(default=datetime.now(tz = UTC))

creator_id: Mapped[str] = mapped_column(ForeignKey("users.id"))
created_at: Mapped[datetime] = mapped_column(default=datetime.now(tz = UTC))
players: Mapped[Set["MatchPlayers"]] = relationship(back_populates="match")
results: Mapped["MatchResult"] = relationship(back_populates="match")


class MatchPlayers(Base):
__tablename__: str = "matchplayers"

match: Mapped["Match"] = relationship(back_populates="players")
match_id: Mapped[int] = mapped_column(ForeignKey("matches.id"), nullable=False)
player: Mapped["User"] = relationship(back_populates="player")
player_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)


class MatchResult(Base):
__tablename__: str = "matchresults"

match: Mapped["Match"] = relationship(back_populates="results")
match_id: Mapped[int] = mapped_column(ForeignKey("matches.id"), unique=True, nullable=False)

won_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
lost_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False)
won: Mapped["User"] = relationship("User", foreign_keys=[won_id])
lost: Mapped["User"] = relationship("User", foreign_keys=[lost_id])
Loading

0 comments on commit efa737f

Please sign in to comment.