-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from osuAkatsuki/add-user-stats-controller
Add public user stats API
- Loading branch information
Showing
6 changed files
with
202 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
from fastapi import APIRouter | ||
from fastapi import Response | ||
from fastapi import Query | ||
|
||
from app.api.responses import JSONResponse | ||
from app.errors import Error | ||
from app.errors import ErrorCode | ||
from app.common_types import GameMode | ||
from app.common_types import RelaxMode | ||
from app.common_types import AkatsukiMode | ||
from app.usecases import user_stats | ||
|
||
router = APIRouter(tags=["(Public) User Stats API"]) | ||
|
||
def map_error_code_to_http_status_code(error_code: ErrorCode) -> int: | ||
return _error_code_to_http_status_code_map[error_code] | ||
|
||
|
||
_error_code_to_http_status_code_map: dict[ErrorCode, int] = { | ||
ErrorCode.INCORRECT_CREDENTIALS: 401, | ||
ErrorCode.INSUFFICIENT_PRIVILEGES: 401, | ||
ErrorCode.PENDING_VERIFICATION: 401, | ||
ErrorCode.NOT_FOUND: 404, | ||
ErrorCode.INTERNAL_SERVER_ERROR: 500, | ||
} | ||
|
||
@router.get("/public/api/v1/users/{user_id}/stats") | ||
async def get_user_stats( | ||
user_id: int, | ||
game_mode: GameMode = Query(...), | ||
relax_mode: RelaxMode = Query(...), | ||
) -> Response: | ||
akatsuki_mode = AkatsukiMode.from_game_mode_and_relax_mode(game_mode, relax_mode) | ||
|
||
response = await user_stats.fetch_one_by_user_id_and_akatsuki_mode(user_id, akatsuki_mode) | ||
if isinstance(response, Error): | ||
return JSONResponse( | ||
content=response.model_dump(), | ||
status_code=map_error_code_to_http_status_code(response.error_code), | ||
) | ||
|
||
return JSONResponse( | ||
content=response.model_dump(), | ||
status_code=200, | ||
) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from pydantic import BaseModel | ||
|
||
class UserStats(BaseModel): | ||
ranked_score: int | ||
total_score: int | ||
play_count: int | ||
replays_watched: int | ||
total_hits: int | ||
accuracy: float | ||
pp: int | ||
play_time: int | ||
ssh_count: int | ||
ss_count: int | ||
sh_count: int | ||
s_count: int | ||
a_count: int | ||
b_count: int | ||
c_count: int | ||
d_count: int | ||
max_combo: int |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from pydantic import BaseModel | ||
|
||
from app.common_types import AkatsukiMode | ||
|
||
import app.state | ||
|
||
|
||
class UserStats(BaseModel): | ||
ranked_score: int | ||
total_score: int | ||
playcount: int | ||
replays_watched: int | ||
total_hits: int | ||
avg_accuracy: float | ||
pp: int | ||
playtime: int | ||
xh_count: int | ||
x_count: int | ||
sh_count: int | ||
s_count: int | ||
a_count: int | ||
b_count: int | ||
c_count: int | ||
d_count: int | ||
max_combo: int | ||
|
||
|
||
READ_PARAMS = """\ | ||
ranked_score, total_score, playcount, replays_watched, total_hits, avg_accuracy, pp, playtime, | ||
xh_count, x_count, sh_count, s_count, a_count, b_count, c_count, d_count, max_combo | ||
""" | ||
|
||
async def fetch_one_by_user_id_and_akatsuki_mode( | ||
user_id: int, | ||
akatsuki_mode: AkatsukiMode, | ||
) -> UserStats | None: | ||
query = f""" | ||
SELECT {READ_PARAMS} | ||
FROM user_stats | ||
WHERE user_id = :user_id | ||
AND mode = :akatsuki_mode | ||
""" | ||
params = {"user_id": user_id, "akatsuki_mode": akatsuki_mode.value} | ||
|
||
user_stats = await app.state.database.fetch_one(query, params) | ||
if user_stats is None: | ||
return None | ||
|
||
return UserStats( | ||
ranked_score=user_stats["ranked_score"], | ||
total_score=user_stats["total_score"], | ||
playcount=user_stats["playcount"], | ||
replays_watched=user_stats["replays_watched"], | ||
total_hits=user_stats["total_hits"], | ||
avg_accuracy=user_stats["avg_accuracy"], | ||
pp=user_stats["pp"], | ||
playtime=user_stats["playtime"], | ||
xh_count=user_stats["xh_count"], | ||
x_count=user_stats["x_count"], | ||
sh_count=user_stats["sh_count"], | ||
s_count=user_stats["s_count"], | ||
a_count=user_stats["a_count"], | ||
b_count=user_stats["b_count"], | ||
c_count=user_stats["c_count"], | ||
d_count=user_stats["d_count"], | ||
max_combo=user_stats["max_combo"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from app.common_types import AkatsukiMode | ||
from app.models.user_stats import UserStats | ||
from app.errors import Error | ||
from app.errors import ErrorCode | ||
from app.repositories import user_stats | ||
|
||
async def fetch_one_by_user_id_and_akatsuki_mode(user_id: int, mode: AkatsukiMode) -> UserStats | Error: | ||
stats = await user_stats.fetch_one_by_user_id_and_akatsuki_mode(user_id, mode) | ||
if stats is None: | ||
return Error( | ||
error_code=ErrorCode.NOT_FOUND, | ||
user_feedback="User statistics not found.", | ||
) | ||
|
||
return UserStats( | ||
ranked_score=stats.ranked_score, | ||
total_score=stats.total_score, | ||
play_count=stats.playcount, | ||
replays_watched=stats.replays_watched, | ||
total_hits=stats.total_hits, | ||
accuracy=stats.avg_accuracy, | ||
pp=stats.pp, | ||
play_time=stats.playtime, | ||
ssh_count=stats.xh_count, | ||
ss_count=stats.x_count, | ||
sh_count=stats.sh_count, | ||
s_count=stats.s_count, | ||
a_count=stats.a_count, | ||
b_count=stats.b_count, | ||
c_count=stats.c_count, | ||
d_count=stats.d_count, | ||
max_combo=stats.max_combo, | ||
) |