Skip to content

Commit

Permalink
Add release workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
andy-takker committed Mar 2, 2024
1 parent 4207c62 commit 9b19c6c
Show file tree
Hide file tree
Showing 19 changed files with 258 additions and 76 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
from aiohttp.web_exceptions import HTTPNotFound
from aiohttp.web_response import Response

from industry_game.utils.http.auth.base import require_authorization
from industry_game.utils.http.auth.base import AuthMixin, require_authorization
from industry_game.utils.http.deps import DependenciesMixin
from industry_game.utils.http.params import parse_path_param
from industry_game.utils.http.response import msgspec_json_response


class ReadByIdGameHandler(View, DependenciesMixin):
class GameDetailsHandler(View, DependenciesMixin, AuthMixin):
@require_authorization
async def get(self) -> Response:
game_id = parse_path_param(self.request, "game_id", int)
Expand Down
File renamed without changes.
File renamed without changes.
7 changes: 5 additions & 2 deletions industry_game/handlers/games/lobby/list_lobby.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from aiohttp.web import HTTPNotFound, Response, View

from industry_game.utils.http.auth.base import AuthMixin, require_authorization
from industry_game.utils.http.auth.base import (
AuthMixin,
require_admin_authorization,
)
from industry_game.utils.http.deps import DependenciesMixin
from industry_game.utils.http.params import (
PaginationParamsModel,
Expand All @@ -11,7 +14,7 @@


class ListGameLobbyHandler(View, DependenciesMixin, AuthMixin):
@require_authorization
@require_admin_authorization
async def get(self) -> Response:
game_id = parse_path_param(self.request, "game_id", int)
game = await self.game_storage.read_by_id(game_id=game_id)
Expand Down
2 changes: 1 addition & 1 deletion industry_game/handlers/players/login_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ async def parse_player_model(self) -> AuthUserModel:
try:
return AuthUserModel.model_validate_json(body)
except ValidationError:
raise HTTPBadRequest
raise HTTPBadRequest(reason="Incorrect user auth data")
8 changes: 4 additions & 4 deletions industry_game/services/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from yarl import URL

from industry_game.handlers.games.create_game import CreateGameHandler
from industry_game.handlers.games.list_game import ListGameHandler
from industry_game.handlers.games.game_details import GameDetailsHandler
from industry_game.handlers.games.game_list import ListGameHandler
from industry_game.handlers.games.game_update import UpdateGameHandler
from industry_game.handlers.games.lobby.add_user_to_lobby import (
AddUserToGameLobbyHandler,
)
Expand All @@ -22,8 +24,6 @@
from industry_game.handlers.games.lobby.read_lobby import (
ReadGameUserLobbyHandler,
)
from industry_game.handlers.games.read_by_id_game import ReadByIdGameHandler
from industry_game.handlers.games.update_game import UpdateGameHandler
from industry_game.handlers.ping import PingHandler
from industry_game.handlers.players.list_player import ListPlayerHandler
from industry_game.handlers.players.login_player import LoginPlayerHandler
Expand Down Expand Up @@ -83,7 +83,7 @@ class REST(AIOHTTPService):
# game handlers
(hdrs.METH_GET, "/api/v1/games/", ListGameHandler),
(hdrs.METH_POST, "/api/v1/games/", CreateGameHandler),
(hdrs.METH_GET, "/api/v1/games/{game_id}/", ReadByIdGameHandler),
(hdrs.METH_GET, "/api/v1/games/{game_id}/", GameDetailsHandler),
(hdrs.METH_POST, "/api/v1/games/{game_id}/", UpdateGameHandler),
# lobby handlers
(hdrs.METH_GET, "/api/v1/games/{game_id}/lobby/", ListGameLobbyHandler),
Expand Down
3 changes: 2 additions & 1 deletion industry_game/utils/lobby/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from industry_game.db.models import UserGameLobby as UserGameLobbyDb
from industry_game.utils.pagination import MetaPagination
from industry_game.utils.users.models import ShortUser


class Lobby(msgspec.Struct, frozen=True):
Expand All @@ -18,4 +19,4 @@ def from_model(cls, obj: UserGameLobbyDb) -> "Lobby":

class LobbyPagination(msgspec.Struct, frozen=True):
meta: MetaPagination
items: list[Lobby]
items: list[ShortUser]
11 changes: 7 additions & 4 deletions industry_game/utils/lobby/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from sqlalchemy import delete, func, insert, select
from sqlalchemy.ext.asyncio import AsyncSession

from industry_game.db.models import User as UserDb
from industry_game.db.models import UserGameLobby as UserGameLobbyDb
from industry_game.utils.db import AbstractStorage, inject_session
from industry_game.utils.lobby.models import Lobby, LobbyPagination
from industry_game.utils.pagination import MetaPagination
from industry_game.utils.users.models import ShortUser


class LobbyStorage(AbstractStorage):
Expand Down Expand Up @@ -80,16 +82,17 @@ async def count(self, session: AsyncSession) -> int:
@inject_session
async def get_items(
self, session: AsyncSession, game_id: int, page: int, page_size: int
) -> list[Lobby]:
) -> list[ShortUser]:
query = (
select(UserGameLobbyDb)
select(UserDb)
.join(UserGameLobbyDb, UserDb.id == UserGameLobbyDb.user_id)
.where(UserGameLobbyDb.game_id == game_id)
.limit(page_size)
.offset((page - 1) * page_size)
)

games = await session.scalars(query)
items: list[Lobby] = []
items: list[ShortUser] = []
for game in games:
items.append(Lobby.from_model(game))
items.append(ShortUser.from_model(game))
return items
4 changes: 3 additions & 1 deletion industry_game/utils/users/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import StrEnum

from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict

from industry_game.utils.msgspec import CustomStruct

Expand All @@ -16,6 +16,8 @@ class RegisterPlayerModel(BaseModel):


class AuthUserModel(BaseModel):
model_config = ConfigDict(str_min_length=8)

username: str
password: str

Expand Down
1 change: 0 additions & 1 deletion industry_game/utils/users/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ async def get_by_username_and_password_hash(
password_hash: str,
) -> FullUser | None:
stmt = select(UserDb).where(
UserDb.type == UserType.PLAYER,
UserDb.username == username,
UserDb.password_hash == password_hash,
)
Expand Down
56 changes: 25 additions & 31 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pre-commit = "^3.6.0"
bandit = "^1.7.7"
ruff = "^0.2.0"
aiomisc-pytest = "^1.1.1"
polyfactory = "^2.14.1"
factory-boy = "^3.3.0"

[build-system]
requires = ["poetry-core"]
Expand Down
29 changes: 14 additions & 15 deletions tests/plugins/factories/games.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
from collections.abc import Callable
from datetime import UTC, datetime

import factory
import pytest
from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
from sqlalchemy.ext.asyncio import AsyncSession

from industry_game.db.models import Game, User
from industry_game.db.models import Game
from industry_game.utils.users.base import UserType
from tests.plugins.factories.users import UserFactory


def utc_now() -> datetime:
return datetime.now(tz=UTC)
class GameFactory(factory.Factory):
class Meta:
model = Game


class GameFactory(SQLAlchemyFactory[Game]):
__set_primary_key__ = False
__set_foreign_keys__ = False
__use_defaults__ = True

created_at = utc_now
updated_at = utc_now
id = factory.Sequence(lambda n: n + 1)
name = "New game"
description = "New game description"
finished_at = None
started_at = None

created_by = factory.SubFactory(UserFactory, type=UserType.ADMIN)


@pytest.fixture
def create_game(session: AsyncSession) -> Callable:
async def factory(created_by: User, **kwargs) -> Game:
game = GameFactory.build(created_by=created_by, **kwargs)
async def factory(**kwargs) -> Game:
game = GameFactory(**kwargs)
session.add(game)
await session.commit()
await session.flush(game)
Expand Down
30 changes: 19 additions & 11 deletions tests/plugins/factories/users.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
from collections.abc import Callable
from typing import Any

import factory
import pytest
from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
from sqlalchemy.ext.asyncio import AsyncSession

from industry_game.db.models import User
from industry_game.utils.security import Passgen
from industry_game.utils.users.base import UserType


def empty_properties() -> dict[str, Any]:
return dict()
class UserPropertiesFactory(factory.Factory):
class Meta:
model = dict

name = "First Last Name"
telegram = "@tg_username"

class UserFactory(SQLAlchemyFactory[User]):
__set_primary_key__ = False

properties = empty_properties
class UserFactory(factory.Factory):
class Meta:
model = User

id = factory.Sequence(lambda n: n + 1)
username = "username"
type = UserType.PLAYER
password_hash = ""
properties = factory.SubFactory(UserPropertiesFactory)


@pytest.fixture
def create_user(session: AsyncSession, passgen: Passgen) -> Callable:
async def factory(**kwargs) -> User:
password = "secret"
password = kwargs.get("password", "secret00")
if "password" in kwargs:
password = kwargs["password"]
del kwargs["password"]
password_hash = passgen.hashpw(password)
user = UserFactory.build(**kwargs, password_hash=password_hash)
kwargs["password_hash"] = passgen.hashpw(password)
user = UserFactory(**kwargs)
session.add(user)
await session.commit()
await session.flush(user)
Expand Down
17 changes: 17 additions & 0 deletions tests/plugins/jwt_auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from collections.abc import Callable

import pytest
from cryptography.hazmat.primitives.asymmetric import rsa

from industry_game.db.models import User
from industry_game.utils.http.auth.jwt import (
JwtAuthrorizationProvider,
JwtProcessor,
Expand Down Expand Up @@ -52,3 +55,17 @@ def admin_token(admin: AuthUser, jwt_processor: JwtProcessor) -> str:
@pytest.fixture
def player_token(player: AuthUser, jwt_processor: JwtProcessor) -> str:
return jwt_processor.encode(player.to_dict())


@pytest.fixture
def token_from_user(jwt_processor: JwtProcessor) -> Callable:
def _factory(user: User) -> str:
return jwt_processor.encode(
{
"id": user.id,
"username": user.username,
"type": user.type,
}
)

return _factory
Loading

0 comments on commit 9b19c6c

Please sign in to comment.