Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

串接大平台1 #93

Merged
merged 6 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

### Practice Stack

- 三層式架構 (MVC)
- 三層式架構 (MVC)=> clean architecture(CA)
- 用 event storming,找出遊戲的功能與流程
- 用 example mapping,確定需求的具體內容
- Test-Driven Development: ATDD
Expand Down
24 changes: 17 additions & 7 deletions love_letter/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import os

REPOSITORY_IMPL = os.environ.get("repository_impl")
DB_HOST = os.environ.get("DB_HOST", "127.0.0.1")
DB_PORT = os.environ.get("DB_PORT", 27017)
DB_USER = os.environ.get("DB_USER")
DB_PASSWORD = os.environ.get("DB_PASSWORD")
DB_NAME = os.environ.get("DB_NAME", "love_letter")
DB_COLLECTION = os.environ.get("DB_COLLECTION", "love_letter")

class Configuration:
REPOSITORY_IMPL = os.environ.get("repository_impl")
DB_HOST = os.environ.get("DB_HOST", "127.0.0.1")
DB_PORT = os.environ.get("DB_PORT", 27017)
DB_USER = os.environ.get("DB_USER")
DB_PASSWORD = os.environ.get("DB_PASSWORD")
DB_NAME = os.environ.get("DB_NAME", "love_letter")
DB_COLLECTION = os.environ.get("DB_COLLECTION", "love_letter")
FRONTEND_HOST = os.environ.get("FRONTEND_HOST", "http://127.0.0.1")
LOBBY_ISSUER = os.environ.get(
"LOBBY_ISSUER", "https://dev-1l0ixjw8yohsluoi.us.auth0.com/"
)
LOBBY_AUDIENCE = os.environ.get("LOBBY_ISSUER", "https://api.gaas.waterballsa.tw")


config = Configuration()
3 changes: 2 additions & 1 deletion love_letter/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ class Seen:


class Player:
def __init__(self, name: str):
def __init__(self, name: str, user_id: Union[str] = None):
self.id = user_id
self.name = name
self.cards: List[Card] = []
self.am_i_out: bool = False
Expand Down
5 changes: 5 additions & 0 deletions love_letter/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ class CardPlayedEvent(DomainEvent):
@dataclass
class GetStatusEvent(DomainEvent):
game: "Game"


@dataclass
class GameEvent(DomainEvent):
url: str
2 changes: 1 addition & 1 deletion love_letter/repository/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pymongo import MongoClient
from pymongo.collection import Collection

from love_letter import config
from love_letter.config import config
from love_letter.models import Game
from love_letter.repository.data import GameData

Expand Down
3 changes: 2 additions & 1 deletion love_letter/repository/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ def to_dict(player: "Player", last_round: "Round") -> Dict:
for x in player.cards
],
score=player.tokens_of_affection,
id=player.id,
)

@staticmethod
def to_domain(player_dict: Dict) -> "Player":
player = Player(player_dict["name"])
player = Player(player_dict["name"], player_dict["id"])
player.am_i_out = player_dict["out"]
player.cards = [CardData.to_domain(c) for c in player_dict["cards"]]
player.seen_cards = [SeenData.to_domain(s) for s in player_dict["seen_cards"]]
Expand Down
14 changes: 14 additions & 0 deletions love_letter/usecase/lobby_start_game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from love_letter.config import config
from love_letter.models import Game, Player
from love_letter.models.event import GameEvent
from love_letter.usecase.common import Presenter, game_repository


class LobbyStartGame:
def execute(self, input: "LobbyPlayers", presenter: Presenter):
game = Game()
for player in input.players:
game.join(Player(player.nickname, player.id))
game.start()
game_repository.save_or_update(game)
presenter.present([GameEvent(url=f"{config.FRONTEND_HOST}/games/{game.id}")])
19 changes: 17 additions & 2 deletions love_letter/web/app.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from typing import Union

import uvicorn
from fastapi import FastAPI
from fastapi import Depends, FastAPI
from fastapi.middleware.cors import CORSMiddleware

from love_letter.models import GuessCard, ToSomeoneCard
from love_letter.usecase.create_game import CreateGame
from love_letter.usecase.get_status import GetStatus
from love_letter.usecase.join_game import JoinGame
from love_letter.usecase.lobby_start_game import LobbyStartGame
from love_letter.usecase.play_card import PlayCard
from love_letter.usecase.start_game import StartGame
from love_letter.web.dto import GameStatus
from love_letter.web.auth import JWTBearer
from love_letter.web.dto import GameStatus, LobbyPlayers

# isort: off
from love_letter.web.presenter import (
Expand All @@ -20,6 +22,7 @@
build_player_view,
PlayCardPresenter,
GetStatusPresenter,
LobbyStartGamePresenter,
)

# isort: on
Expand Down Expand Up @@ -89,6 +92,18 @@ async def get_status(game_id: str, player_id: str):
return build_player_view(game, player_id)


@app.post("/games", dependencies=[Depends(JWTBearer())])
async def lobby_start_game(players: LobbyPlayers):
presenter = LobbyStartGamePresenter.presenter()
LobbyStartGame().execute(players, presenter)
return presenter.as_view_model()


@app.get("/heath")
async def heath():
return {"success": True}


def run():
uvicorn.run("love_letter.web.app:app", host="0.0.0.0", port=8080, reload=True)

Expand Down
65 changes: 65 additions & 0 deletions love_letter/web/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import datetime

import jwt
from fastapi import HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from starlette.requests import Request

from love_letter.config import config


class JWTBearer(HTTPBearer):
def __init__(self, auto_error: bool = True):
super().__init__(auto_error=auto_error)

async def __call__(self, request: Request):
credentials: HTTPAuthorizationCredentials = await super().__call__(request)
if credentials:
if not credentials.scheme == "Bearer":
raise HTTPException(
status_code=403, detail="Invalid authentication scheme."
)
if not self.verify_jwt(credentials.credentials):
raise HTTPException(
status_code=403, detail="Invalid token or expired token."
)
return credentials.credentials
else:
raise HTTPException(status_code=403, detail="Invalid authorization code.")

@staticmethod
def verify_jwt(token: str) -> bool:
"""
Verify jwt token iss and aud are as expected, and not expired
:param token:
:return:
"""
try:
jwt.decode(
token,
options={
"verify_signature": False,
"verify_iss": True,
"verify_aud": True,
"verify_exp": True,
},
audience=config.LOBBY_AUDIENCE,
issuer=config.LOBBY_ISSUER,
)
return True
except Exception as e:
print(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

接近要上線的時刻,也許值得在追另新的 task,接上 sentry
https://sentry.io/welcome/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

酷~ 好喔,沒玩過之後來試試看

return False

@staticmethod
def create_jwt():
now = datetime.datetime.now()
token = jwt.encode(
payload={
"aud": config.LOBBY_AUDIENCE,
"iss": config.LOBBY_ISSUER,
"exp": now + datetime.timedelta(hours=1),
},
key="",
)
return f"Bearer {token}"
10 changes: 10 additions & 0 deletions love_letter/web/dto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class RoundModel(BaseModel):
class NamedPlayer(BaseModel):
name: str | None
score: int
id: str | None


class GameStatus(BaseModel):
Expand All @@ -40,3 +41,12 @@ class GameStatus(BaseModel):
events: List[Dict]
rounds: List[RoundModel]
final_winner: str | None


class LobbyPlayer(BaseModel):
id: str
nickname: str


class LobbyPlayers(BaseModel):
players: List[LobbyPlayer]
19 changes: 18 additions & 1 deletion love_letter/web/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
from love_letter.models import Game, PlayerJoinedEvent

# isort: off
from love_letter.models.event import CardPlayedEvent, GetStatusEvent, StartGameEvent
from love_letter.models.event import (
CardPlayedEvent,
GetStatusEvent,
StartGameEvent,
GameEvent,
)

# isort: on
from love_letter.repository.data import GameData
Expand Down Expand Up @@ -141,3 +146,15 @@ def as_view_model(self) -> Game:
@classmethod
def presenter(cls) -> "GetStatusPresenter":
return GetStatusPresenter()


class LobbyStartGamePresenter(Presenter):
def as_view_model(self):
for event in self.events:
if isinstance(event, GameEvent):
return event
raise BaseException("Game is unavailable.")

@classmethod
def presenter(cls) -> "LobbyStartGamePresenter":
return LobbyStartGamePresenter()
Loading