feat(developer): support custom OAuth 2.0 client
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
import re
|
||||
from typing import Literal
|
||||
|
||||
from app.auth import (
|
||||
authenticate_user,
|
||||
@@ -9,12 +10,14 @@ from app.auth import (
|
||||
generate_refresh_token,
|
||||
get_password_hash,
|
||||
get_token_by_refresh_token,
|
||||
get_user_by_authorization_code,
|
||||
store_token,
|
||||
)
|
||||
from app.config import settings
|
||||
from app.database import DailyChallengeStats, User
|
||||
from app.database import DailyChallengeStats, OAuthClient, User
|
||||
from app.database.statistics import UserStatistics
|
||||
from app.dependencies import get_db
|
||||
from app.dependencies.database import get_redis
|
||||
from app.log import logger
|
||||
from app.models.oauth import (
|
||||
OAuthErrorResponse,
|
||||
@@ -26,6 +29,7 @@ from app.models.score import GameMode
|
||||
|
||||
from fastapi import APIRouter, Depends, Form
|
||||
from fastapi.responses import JSONResponse
|
||||
from redis.asyncio import Redis
|
||||
from sqlalchemy import text
|
||||
from sqlmodel import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
@@ -195,21 +199,36 @@ async def register_user(
|
||||
|
||||
@router.post("/oauth/token", response_model=TokenResponse)
|
||||
async def oauth_token(
|
||||
grant_type: str = Form(...),
|
||||
client_id: str = Form(...),
|
||||
grant_type: Literal[
|
||||
"authorization_code", "refresh_token", "password", "client_credentials"
|
||||
] = Form(...),
|
||||
client_id: int = Form(...),
|
||||
client_secret: str = Form(...),
|
||||
code: str | None = Form(None),
|
||||
scope: str = Form("*"),
|
||||
username: str | None = Form(None),
|
||||
password: str | None = Form(None),
|
||||
refresh_token: str | None = Form(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
redis: Redis = Depends(get_redis),
|
||||
):
|
||||
"""OAuth 令牌端点"""
|
||||
# 验证客户端凭据
|
||||
if (
|
||||
client_id != settings.osu_client_id
|
||||
or client_secret != settings.osu_client_secret
|
||||
):
|
||||
scopes = scope.split(" ")
|
||||
|
||||
client = (
|
||||
await db.exec(
|
||||
select(OAuthClient).where(
|
||||
OAuthClient.client_id == client_id,
|
||||
OAuthClient.client_secret == client_secret,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
is_game_client = (client_id, client_secret) in [
|
||||
(settings.osu_client_id, settings.osu_client_secret),
|
||||
(settings.osu_web_client_id, settings.osu_web_client_secret),
|
||||
]
|
||||
|
||||
if client is None and not is_game_client:
|
||||
return create_oauth_error_response(
|
||||
error="invalid_client",
|
||||
description=(
|
||||
@@ -222,7 +241,6 @@ async def oauth_token(
|
||||
)
|
||||
|
||||
if grant_type == "password":
|
||||
# 密码授权流程
|
||||
if not username or not password:
|
||||
return create_oauth_error_response(
|
||||
error="invalid_request",
|
||||
@@ -233,6 +251,16 @@ async def oauth_token(
|
||||
),
|
||||
hint="Username and password required",
|
||||
)
|
||||
if scopes != ["*"]:
|
||||
return create_oauth_error_response(
|
||||
error="invalid_scope",
|
||||
description=(
|
||||
"The requested scope is invalid, unknown, "
|
||||
"or malformed. The client may not request "
|
||||
"more than one scope at a time."
|
||||
),
|
||||
hint="Only '*' scope is allowed for password grant type",
|
||||
)
|
||||
|
||||
# 验证用户
|
||||
user = await authenticate_user(db, username, password)
|
||||
@@ -261,6 +289,8 @@ async def oauth_token(
|
||||
await store_token(
|
||||
db,
|
||||
user.id,
|
||||
client_id,
|
||||
scopes,
|
||||
access_token,
|
||||
refresh_token_str,
|
||||
settings.access_token_expire_minutes * 60,
|
||||
@@ -313,6 +343,8 @@ async def oauth_token(
|
||||
await store_token(
|
||||
db,
|
||||
token_record.user_id,
|
||||
client_id,
|
||||
scopes,
|
||||
access_token,
|
||||
new_refresh_token,
|
||||
settings.access_token_expire_minutes * 60,
|
||||
@@ -325,7 +357,69 @@ async def oauth_token(
|
||||
refresh_token=new_refresh_token,
|
||||
scope=scope,
|
||||
)
|
||||
elif grant_type == "authorization_code":
|
||||
if client is None:
|
||||
return create_oauth_error_response(
|
||||
error="invalid_client",
|
||||
description=(
|
||||
"Client authentication failed (e.g., unknown client, "
|
||||
"no client authentication included, "
|
||||
"or unsupported authentication method)."
|
||||
),
|
||||
hint="Invalid client credentials",
|
||||
status_code=401,
|
||||
)
|
||||
|
||||
if not code:
|
||||
return create_oauth_error_response(
|
||||
error="invalid_request",
|
||||
description=(
|
||||
"The request is missing a required parameter, "
|
||||
"includes an invalid parameter value, "
|
||||
"includes a parameter more than once, or is otherwise malformed."
|
||||
),
|
||||
hint="Authorization code required",
|
||||
)
|
||||
|
||||
code_result = await get_user_by_authorization_code(db, redis, client_id, code)
|
||||
if not code_result:
|
||||
return create_oauth_error_response(
|
||||
error="invalid_grant",
|
||||
description=(
|
||||
"The provided authorization grant (e.g., authorization code, "
|
||||
"resource owner credentials) or refresh token is invalid, "
|
||||
"expired, revoked, does not match the redirection URI used in "
|
||||
"the authorization request, or was issued to another client."
|
||||
),
|
||||
hint="Invalid authorization code",
|
||||
)
|
||||
user, scopes = code_result
|
||||
# 生成令牌
|
||||
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
|
||||
access_token = create_access_token(
|
||||
data={"sub": str(user.id)}, expires_delta=access_token_expires
|
||||
)
|
||||
refresh_token_str = generate_refresh_token()
|
||||
|
||||
# 存储令牌
|
||||
assert user.id
|
||||
await store_token(
|
||||
db,
|
||||
user.id,
|
||||
client_id,
|
||||
scopes,
|
||||
access_token,
|
||||
refresh_token_str,
|
||||
settings.access_token_expire_minutes * 60,
|
||||
)
|
||||
|
||||
return TokenResponse(
|
||||
access_token=access_token,
|
||||
token_type="Bearer",
|
||||
expires_in=settings.access_token_expire_minutes * 60,
|
||||
refresh_token=refresh_token_str,
|
||||
scope=" ".join(scopes),
|
||||
)
|
||||
else:
|
||||
return create_oauth_error_response(
|
||||
error="unsupported_grant_type",
|
||||
|
||||
@@ -19,7 +19,7 @@ from app.models.score import (
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends, HTTPException, Query
|
||||
from fastapi import Depends, HTTPException, Query, Security
|
||||
from httpx import HTTPError, HTTPStatusError
|
||||
from pydantic import BaseModel
|
||||
from redis.asyncio import Redis
|
||||
@@ -33,7 +33,7 @@ async def lookup_beatmap(
|
||||
id: int | None = Query(default=None, alias="id"),
|
||||
md5: str | None = Query(default=None, alias="checksum"),
|
||||
filename: str | None = Query(default=None, alias="filename"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
fetcher: Fetcher = Depends(get_fetcher),
|
||||
):
|
||||
@@ -56,7 +56,7 @@ async def lookup_beatmap(
|
||||
@router.get("/beatmaps/{bid}", tags=["beatmap"], response_model=BeatmapResp)
|
||||
async def get_beatmap(
|
||||
bid: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
fetcher: Fetcher = Depends(get_fetcher),
|
||||
):
|
||||
@@ -75,7 +75,7 @@ class BatchGetResp(BaseModel):
|
||||
@router.get("/beatmaps/", tags=["beatmap"], response_model=BatchGetResp)
|
||||
async def batch_get_beatmaps(
|
||||
b_ids: list[int] = Query(alias="ids[]", default_factory=list),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
fetcher: Fetcher = Depends(get_fetcher),
|
||||
):
|
||||
@@ -126,7 +126,7 @@ async def batch_get_beatmaps(
|
||||
)
|
||||
async def get_beatmap_attributes(
|
||||
beatmap: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
mods: list[str] = Query(default_factory=list),
|
||||
ruleset: GameMode | None = Query(default=None),
|
||||
ruleset_id: int | None = Query(default=None),
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.fetcher import Fetcher
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends, Form, HTTPException, Query
|
||||
from fastapi import Depends, Form, HTTPException, Query, Security
|
||||
from fastapi.responses import RedirectResponse
|
||||
from httpx import HTTPError
|
||||
from sqlmodel import select
|
||||
@@ -20,7 +20,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
@router.get("/beatmapsets/lookup", tags=["beatmapset"], response_model=BeatmapsetResp)
|
||||
async def lookup_beatmapset(
|
||||
beatmap_id: int = Query(),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
fetcher: Fetcher = Depends(get_fetcher),
|
||||
):
|
||||
@@ -34,7 +34,7 @@ async def lookup_beatmapset(
|
||||
@router.get("/beatmapsets/{sid}", tags=["beatmapset"], response_model=BeatmapsetResp)
|
||||
async def get_beatmapset(
|
||||
sid: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
fetcher: Fetcher = Depends(get_fetcher),
|
||||
):
|
||||
@@ -51,7 +51,7 @@ async def get_beatmapset(
|
||||
async def download_beatmapset(
|
||||
beatmapset: int,
|
||||
no_video: bool = Query(True, alias="noVideo"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
):
|
||||
if current_user.country_code == "CN":
|
||||
return RedirectResponse(
|
||||
@@ -68,7 +68,7 @@ async def download_beatmapset(
|
||||
async def favourite_beatmapset(
|
||||
beatmapset: int,
|
||||
action: Literal["favourite", "unfavourite"] = Form(),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
existing_favourite = (
|
||||
|
||||
@@ -8,7 +8,7 @@ from app.models.score import GameMode
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, Security
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
@router.get("/me/", response_model=UserResp)
|
||||
async def get_user_info_default(
|
||||
ruleset: GameMode | None = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["identify"]),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
return await UserResp.from_db(
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.database import User as DBUser
|
||||
from app.database.relationship import Relationship, RelationshipResp, RelationshipType
|
||||
from app.database import Relationship, RelationshipResp, RelationshipType, User
|
||||
from app.dependencies.database import get_db
|
||||
from app.dependencies.user import get_current_user
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends, HTTPException, Query, Request
|
||||
from fastapi import Depends, HTTPException, Query, Request, Security
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
@@ -17,7 +16,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
@router.get("/blocks", tags=["relationship"], response_model=list[RelationshipResp])
|
||||
async def get_relationship(
|
||||
request: Request,
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["friends.read"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
relationship_type = (
|
||||
@@ -43,7 +42,7 @@ class AddFriendResp(BaseModel):
|
||||
async def add_relationship(
|
||||
request: Request,
|
||||
target: int = Query(),
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
relationship_type = (
|
||||
@@ -106,7 +105,7 @@ async def add_relationship(
|
||||
async def delete_relationship(
|
||||
request: Request,
|
||||
target: int,
|
||||
current_user: DBUser = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
relationship_type = (
|
||||
|
||||
@@ -20,7 +20,7 @@ from app.signalr.hub import MultiplayerHubs
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends, HTTPException, Query
|
||||
from fastapi import Depends, HTTPException, Query, Security
|
||||
from pydantic import BaseModel, Field
|
||||
from redis.asyncio import Redis
|
||||
from sqlalchemy.sql.elements import ColumnElement
|
||||
@@ -36,7 +36,7 @@ async def get_all_rooms(
|
||||
category: RoomCategory = Query(RoomCategory.NORMAL),
|
||||
status: RoomStatus | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
):
|
||||
resp_list: list[RoomResp] = []
|
||||
where_clauses: list[ColumnElement[bool]] = [col(Room.category) == category]
|
||||
@@ -124,7 +124,7 @@ async def _participate_room(
|
||||
async def create_room(
|
||||
room: APIUploadedRoom,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
):
|
||||
user_id = current_user.id
|
||||
db_room = await create_playlist_room_from_api(db, room, user_id)
|
||||
@@ -141,7 +141,7 @@ async def get_room(
|
||||
room: int,
|
||||
category: str = Query(default=""),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
redis: Redis = Depends(get_redis),
|
||||
):
|
||||
# 直接从db获取信息,毕竟都一样
|
||||
@@ -155,7 +155,11 @@ async def get_room(
|
||||
|
||||
|
||||
@router.delete("/rooms/{room}", tags=["room"])
|
||||
async def delete_room(room: int, db: AsyncSession = Depends(get_db)):
|
||||
async def delete_room(
|
||||
room: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
):
|
||||
db_room = (await db.exec(select(Room).where(Room.id == room))).first()
|
||||
if db_room is None:
|
||||
raise HTTPException(404, "Room not found")
|
||||
@@ -166,7 +170,12 @@ async def delete_room(room: int, db: AsyncSession = Depends(get_db)):
|
||||
|
||||
|
||||
@router.put("/rooms/{room}/users/{user}", tags=["room"])
|
||||
async def add_user_to_room(room: int, user: int, db: AsyncSession = Depends(get_db)):
|
||||
async def add_user_to_room(
|
||||
room: int,
|
||||
user: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
):
|
||||
db_room = (await db.exec(select(Room).where(Room.id == room))).first()
|
||||
if db_room is not None:
|
||||
await _participate_room(room, user, db_room, db)
|
||||
@@ -181,7 +190,10 @@ async def add_user_to_room(room: int, user: int, db: AsyncSession = Depends(get_
|
||||
|
||||
@router.delete("/rooms/{room}/users/{user}", tags=["room"])
|
||||
async def remove_user_from_room(
|
||||
room: int, user: int, db: AsyncSession = Depends(get_db)
|
||||
room: int,
|
||||
user: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
):
|
||||
db_room = (await db.exec(select(Room).where(Room.id == room))).first()
|
||||
if db_room is not None:
|
||||
@@ -211,7 +223,7 @@ class APILeaderboard(BaseModel):
|
||||
async def get_room_leaderboard(
|
||||
room: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
):
|
||||
db_room = (await db.exec(select(Room).where(Room.id == room))).first()
|
||||
if db_room is None:
|
||||
@@ -253,7 +265,7 @@ class RoomEvents(BaseModel):
|
||||
async def get_room_events(
|
||||
room_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
after: int | None = Query(None, ge=0),
|
||||
before: int | None = Query(None, ge=0),
|
||||
|
||||
@@ -46,7 +46,7 @@ from app.path import REPLAY_DIR
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Body, Depends, Form, HTTPException, Query
|
||||
from fastapi import Body, Depends, Form, HTTPException, Query, Security
|
||||
from fastapi.responses import FileResponse
|
||||
from httpx import HTTPError
|
||||
from pydantic import BaseModel
|
||||
@@ -135,7 +135,7 @@ async def get_beatmap_scores(
|
||||
legacy_only: bool = Query(None), # TODO:加入对这个参数的查询
|
||||
mods: list[str] = Query(default_factory=set, alias="mods[]"),
|
||||
type: LeaderboardType = Query(LeaderboardType.GLOBAL),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
):
|
||||
@@ -170,7 +170,7 @@ async def get_user_beatmap_score(
|
||||
legacy_only: bool = Query(None),
|
||||
mode: str = Query(None),
|
||||
mods: str = Query(None), # TODO:添加mods筛选
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if legacy_only:
|
||||
@@ -211,7 +211,7 @@ async def get_user_all_beatmap_scores(
|
||||
user: int,
|
||||
legacy_only: bool = Query(None),
|
||||
ruleset: str = Query(None),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if legacy_only:
|
||||
@@ -241,7 +241,7 @@ async def create_solo_score(
|
||||
version_hash: str = Form(""),
|
||||
beatmap_hash: str = Form(),
|
||||
ruleset_id: int = Form(..., ge=0, le=3),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
assert current_user.id
|
||||
@@ -266,7 +266,7 @@ async def submit_solo_score(
|
||||
beatmap: int,
|
||||
token: int,
|
||||
info: SoloScoreSubmissionInfo,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
redis: Redis = Depends(get_redis),
|
||||
fetcher=Depends(get_fetcher),
|
||||
@@ -284,7 +284,7 @@ async def create_playlist_score(
|
||||
beatmap_hash: str = Form(),
|
||||
ruleset_id: int = Form(..., ge=0, le=3),
|
||||
version_hash: str = Form(""),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
room = await session.get(Room, room_id)
|
||||
@@ -351,7 +351,7 @@ async def submit_playlist_score(
|
||||
playlist_id: int,
|
||||
token: int,
|
||||
info: SoloScoreSubmissionInfo,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
redis: Redis = Depends(get_redis),
|
||||
fetcher: Fetcher = Depends(get_fetcher),
|
||||
@@ -404,7 +404,7 @@ async def index_playlist_scores(
|
||||
playlist_id: int,
|
||||
limit: int = 50,
|
||||
cursor: int = Query(2000000, alias="cursor[total_score]"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
room = await session.get(Room, room_id)
|
||||
@@ -464,7 +464,7 @@ async def show_playlist_score(
|
||||
room_id: int,
|
||||
playlist_id: int,
|
||||
score_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
redis: Redis = Depends(get_redis),
|
||||
):
|
||||
@@ -528,7 +528,7 @@ async def get_user_playlist_score(
|
||||
room_id: int,
|
||||
playlist_id: int,
|
||||
user_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
score_record = None
|
||||
@@ -558,7 +558,7 @@ async def get_user_playlist_score(
|
||||
@router.put("/score-pins/{score}", status_code=204)
|
||||
async def pin_score(
|
||||
score: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
score_record = (
|
||||
@@ -594,7 +594,7 @@ async def pin_score(
|
||||
@router.delete("/score-pins/{score}", status_code=204)
|
||||
async def unpin_score(
|
||||
score: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
score_record = (
|
||||
@@ -626,7 +626,7 @@ async def reorder_score_pin(
|
||||
score: int,
|
||||
after_score_id: int | None = Body(default=None),
|
||||
before_score_id: int | None = Body(default=None),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["*"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
score_record = (
|
||||
@@ -713,7 +713,7 @@ async def reorder_score_pin(
|
||||
@router.get("/scores/{score_id}/download")
|
||||
async def download_score_replay(
|
||||
score_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
score = (await db.exec(select(Score).where(Score.id == score_id))).first()
|
||||
|
||||
@@ -20,7 +20,7 @@ from app.models.user import BeatmapsetType
|
||||
|
||||
from .api_router import router
|
||||
|
||||
from fastapi import Depends, HTTPException, Query
|
||||
from fastapi import Depends, HTTPException, Query, Security
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import exists, false, select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
@@ -36,6 +36,7 @@ class BatchUserResponse(BaseModel):
|
||||
@router.get("/users/lookup/", response_model=BatchUserResponse)
|
||||
async def get_users(
|
||||
user_ids: list[int] = Query(default_factory=list, alias="ids[]"),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
include_variant_statistics: bool = Query(default=False), # TODO: future use
|
||||
session: AsyncSession = Depends(get_db),
|
||||
):
|
||||
@@ -64,6 +65,7 @@ async def get_user_info(
|
||||
user: str,
|
||||
ruleset: GameMode | None = None,
|
||||
session: AsyncSession = Depends(get_db),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
):
|
||||
searched_user = (
|
||||
await session.exec(
|
||||
@@ -91,7 +93,7 @@ async def get_user_info(
|
||||
async def get_user_beatmapsets(
|
||||
user_id: int,
|
||||
type: BeatmapsetType,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
offset: int = Query(0, ge=0),
|
||||
@@ -147,6 +149,7 @@ async def get_user_scores(
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
offset: int = Query(0, ge=0),
|
||||
session: AsyncSession = Depends(get_db),
|
||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||
):
|
||||
db_user = await session.get(User, user)
|
||||
if not db_user:
|
||||
|
||||
Reference in New Issue
Block a user