feat(beatmapset): support search beatmapset
This commit is contained in:
@@ -28,10 +28,10 @@ class BeatmapBase(SQLModel):
|
|||||||
url: str
|
url: str
|
||||||
mode: GameMode
|
mode: GameMode
|
||||||
beatmapset_id: int = Field(foreign_key="beatmapsets.id", index=True)
|
beatmapset_id: int = Field(foreign_key="beatmapsets.id", index=True)
|
||||||
difficulty_rating: float = Field(default=0.0)
|
difficulty_rating: float = Field(default=0.0, index=True)
|
||||||
total_length: int
|
total_length: int
|
||||||
user_id: int
|
user_id: int = Field(index=True)
|
||||||
version: str
|
version: str = Field(index=True)
|
||||||
|
|
||||||
# optional
|
# optional
|
||||||
checksum: str = Field(sa_column=Column(VARCHAR(32), index=True))
|
checksum: str = Field(sa_column=Column(VARCHAR(32), index=True))
|
||||||
@@ -50,14 +50,14 @@ class BeatmapBase(SQLModel):
|
|||||||
count_spinners: int = Field(default=0)
|
count_spinners: int = Field(default=0)
|
||||||
deleted_at: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
deleted_at: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
||||||
hit_length: int = Field(default=0)
|
hit_length: int = Field(default=0)
|
||||||
last_updated: datetime = Field(sa_column=Column(DateTime))
|
last_updated: datetime = Field(sa_column=Column(DateTime, index=True))
|
||||||
|
|
||||||
|
|
||||||
class Beatmap(BeatmapBase, table=True):
|
class Beatmap(BeatmapBase, table=True):
|
||||||
__tablename__ = "beatmaps" # pyright: ignore[reportAssignmentType]
|
__tablename__ = "beatmaps" # pyright: ignore[reportAssignmentType]
|
||||||
id: int = Field(primary_key=True, index=True)
|
id: int = Field(primary_key=True, index=True)
|
||||||
beatmapset_id: int = Field(foreign_key="beatmapsets.id", index=True)
|
beatmapset_id: int = Field(foreign_key="beatmapsets.id", index=True)
|
||||||
beatmap_status: BeatmapRankStatus
|
beatmap_status: BeatmapRankStatus = Field(index=True)
|
||||||
# optional
|
# optional
|
||||||
beatmapset: Beatmapset = Relationship(
|
beatmapset: Beatmapset = Relationship(
|
||||||
back_populates="beatmaps", sa_relationship_kwargs={"lazy": "joined"}
|
back_populates="beatmaps", sa_relationship_kwargs={"lazy": "joined"}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, NotRequired, TypedDict
|
from typing import TYPE_CHECKING, NotRequired, Self, TypedDict
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.models.beatmap import BeatmapRankStatus, Genre, Language
|
from app.models.beatmap import BeatmapRankStatus, Genre, Language
|
||||||
@@ -7,7 +7,7 @@ from app.models.score import GameMode
|
|||||||
|
|
||||||
from .lazer_user import BASE_INCLUDES, User, UserResp
|
from .lazer_user import BASE_INCLUDES, User, UserResp
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, model_validator
|
||||||
from sqlalchemy import JSON, Column, DateTime, Text
|
from sqlalchemy import JSON, Column, DateTime, Text
|
||||||
from sqlalchemy.ext.asyncio import AsyncAttrs
|
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||||||
from sqlmodel import Field, Relationship, SQLModel, col, exists, func, select
|
from sqlmodel import Field, Relationship, SQLModel, col, exists, func, select
|
||||||
@@ -72,17 +72,17 @@ class BeatmapsetBase(SQLModel):
|
|||||||
artist: str = Field(index=True)
|
artist: str = Field(index=True)
|
||||||
artist_unicode: str = Field(index=True)
|
artist_unicode: str = Field(index=True)
|
||||||
covers: BeatmapCovers | None = Field(sa_column=Column(JSON))
|
covers: BeatmapCovers | None = Field(sa_column=Column(JSON))
|
||||||
creator: str
|
creator: str = Field(index=True)
|
||||||
nsfw: bool = Field(default=False)
|
nsfw: bool = Field(default=False)
|
||||||
play_count: int
|
play_count: int = Field(index=True)
|
||||||
preview_url: str
|
preview_url: str
|
||||||
source: str = Field(default="")
|
source: str = Field(default="")
|
||||||
|
|
||||||
spotlight: bool = Field(default=False)
|
spotlight: bool = Field(default=False)
|
||||||
title: str
|
title: str = Field(index=True)
|
||||||
title_unicode: str
|
title_unicode: str = Field(index=True)
|
||||||
user_id: int
|
user_id: int = Field(index=True)
|
||||||
video: bool
|
video: bool = Field(index=True)
|
||||||
|
|
||||||
# optional
|
# optional
|
||||||
# converts: list[Beatmap] = Relationship(back_populates="beatmapset")
|
# converts: list[Beatmap] = Relationship(back_populates="beatmapset")
|
||||||
@@ -95,19 +95,21 @@ class BeatmapsetBase(SQLModel):
|
|||||||
# TODO: events: Optional[list[BeatmapsetEvent]] = None
|
# TODO: events: Optional[list[BeatmapsetEvent]] = None
|
||||||
|
|
||||||
pack_tags: list[str] = Field(default=[], sa_column=Column(JSON))
|
pack_tags: list[str] = Field(default=[], sa_column=Column(JSON))
|
||||||
ratings: list[int] = Field(default=None, sa_column=Column(JSON))
|
ratings: list[int] | None = Field(default=None, sa_column=Column(JSON))
|
||||||
# TODO: related_users: Optional[list[User]] = None
|
# TODO: related_users: Optional[list[User]] = None
|
||||||
# TODO: user: Optional[User] = Field(default=None)
|
# TODO: user: Optional[User] = Field(default=None)
|
||||||
track_id: int | None = Field(default=None) # feature artist?
|
track_id: int | None = Field(default=None, index=True) # feature artist?
|
||||||
|
|
||||||
# BeatmapsetExtended
|
# BeatmapsetExtended
|
||||||
bpm: float = Field(default=0.0)
|
bpm: float = Field(default=0.0)
|
||||||
can_be_hyped: bool = Field(default=False)
|
can_be_hyped: bool = Field(default=False)
|
||||||
discussion_locked: bool = Field(default=False)
|
discussion_locked: bool = Field(default=False)
|
||||||
last_updated: datetime = Field(sa_column=Column(DateTime))
|
last_updated: datetime = Field(sa_column=Column(DateTime, index=True))
|
||||||
ranked_date: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
ranked_date: datetime | None = Field(
|
||||||
storyboard: bool = Field(default=False)
|
default=None, sa_column=Column(DateTime, index=True)
|
||||||
submitted_date: datetime = Field(sa_column=Column(DateTime))
|
)
|
||||||
|
storyboard: bool = Field(default=False, index=True)
|
||||||
|
submitted_date: datetime = Field(sa_column=Column(DateTime, index=True))
|
||||||
tags: str = Field(default="", sa_column=Column(Text))
|
tags: str = Field(default="", sa_column=Column(Text))
|
||||||
|
|
||||||
|
|
||||||
@@ -117,13 +119,13 @@ class Beatmapset(AsyncAttrs, BeatmapsetBase, table=True):
|
|||||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||||
# Beatmapset
|
# Beatmapset
|
||||||
beatmap_status: BeatmapRankStatus = Field(
|
beatmap_status: BeatmapRankStatus = Field(
|
||||||
default=BeatmapRankStatus.GRAVEYARD,
|
default=BeatmapRankStatus.GRAVEYARD, index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# optional
|
# optional
|
||||||
beatmaps: list["Beatmap"] = Relationship(back_populates="beatmapset")
|
beatmaps: list["Beatmap"] = Relationship(back_populates="beatmapset")
|
||||||
beatmap_genre: Genre = Field(default=Genre.UNSPECIFIED)
|
beatmap_genre: Genre = Field(default=Genre.UNSPECIFIED, index=True)
|
||||||
beatmap_language: Language = Field(default=Language.UNSPECIFIED)
|
beatmap_language: Language = Field(default=Language.UNSPECIFIED, index=True)
|
||||||
nominations_required: int = Field(default=0)
|
nominations_required: int = Field(default=0)
|
||||||
nominations_current: int = Field(default=0)
|
nominations_current: int = Field(default=0)
|
||||||
|
|
||||||
@@ -148,9 +150,13 @@ class Beatmapset(AsyncAttrs, BeatmapsetBase, table=True):
|
|||||||
if resp.hype:
|
if resp.hype:
|
||||||
update["hype_current"] = resp.hype.current
|
update["hype_current"] = resp.hype.current
|
||||||
update["hype_required"] = resp.hype.required
|
update["hype_required"] = resp.hype.required
|
||||||
if resp.genre:
|
if resp.genre_id:
|
||||||
|
update["beatmap_genre"] = Genre(resp.genre_id)
|
||||||
|
elif resp.genre:
|
||||||
update["beatmap_genre"] = Genre(resp.genre.id)
|
update["beatmap_genre"] = Genre(resp.genre.id)
|
||||||
if resp.language:
|
if resp.language_id:
|
||||||
|
update["beatmap_language"] = Language(resp.language_id)
|
||||||
|
elif resp.language:
|
||||||
update["beatmap_language"] = Language(resp.language.id)
|
update["beatmap_language"] = Language(resp.language.id)
|
||||||
beatmapset = Beatmapset.model_validate(
|
beatmapset = Beatmapset.model_validate(
|
||||||
{
|
{
|
||||||
@@ -191,12 +197,26 @@ class BeatmapsetResp(BeatmapsetBase):
|
|||||||
hype: BeatmapHype | None = None
|
hype: BeatmapHype | None = None
|
||||||
availability: BeatmapAvailability
|
availability: BeatmapAvailability
|
||||||
genre: BeatmapTranslationText | None = None
|
genre: BeatmapTranslationText | None = None
|
||||||
|
genre_id: int
|
||||||
language: BeatmapTranslationText | None = None
|
language: BeatmapTranslationText | None = None
|
||||||
|
language_id: int
|
||||||
nominations: BeatmapNominations | None = None
|
nominations: BeatmapNominations | None = None
|
||||||
has_favourited: bool = False
|
has_favourited: bool = False
|
||||||
favourite_count: int = 0
|
favourite_count: int = 0
|
||||||
recent_favourites: list[UserResp] = Field(default_factory=list)
|
recent_favourites: list[UserResp] = Field(default_factory=list)
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def fix_genre_language(self) -> Self:
|
||||||
|
if self.genre is None:
|
||||||
|
self.genre = BeatmapTranslationText(
|
||||||
|
name=Genre(self.genre_id).name, id=self.genre_id
|
||||||
|
)
|
||||||
|
if self.language is None:
|
||||||
|
self.language = BeatmapTranslationText(
|
||||||
|
name=Language(self.language_id).name, id=self.language_id
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_db(
|
async def from_db(
|
||||||
cls,
|
cls,
|
||||||
@@ -228,6 +248,8 @@ class BeatmapsetResp(BeatmapsetBase):
|
|||||||
name=beatmapset.beatmap_language.name,
|
name=beatmapset.beatmap_language.name,
|
||||||
id=beatmapset.beatmap_language.value,
|
id=beatmapset.beatmap_language.value,
|
||||||
),
|
),
|
||||||
|
"genre_id": beatmapset.beatmap_genre.value,
|
||||||
|
"language_id": beatmapset.beatmap_language.value,
|
||||||
"nominations": BeatmapNominations(
|
"nominations": BeatmapNominations(
|
||||||
required=beatmapset.nominations_required,
|
required=beatmapset.nominations_required,
|
||||||
current=beatmapset.nominations_current,
|
current=beatmapset.nominations_current,
|
||||||
@@ -288,3 +310,8 @@ class BeatmapsetResp(BeatmapsetBase):
|
|||||||
return cls.model_validate(
|
return cls.model_validate(
|
||||||
update,
|
update,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchBeatmapsetsResp(SQLModel):
|
||||||
|
beatmapsets: list[BeatmapsetResp]
|
||||||
|
total: int
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from app.database.beatmapset import BeatmapsetResp
|
from app.database.beatmapset import BeatmapsetResp, SearchBeatmapsetsResp
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
|
from app.models.beatmap import SearchQueryModel
|
||||||
|
from app.models.model import Cursor
|
||||||
|
|
||||||
from ._base import BaseFetcher
|
from ._base import BaseFetcher
|
||||||
|
|
||||||
@@ -17,3 +19,23 @@ class BeatmapsetFetcher(BaseFetcher):
|
|||||||
f"https://osu.ppy.sh/api/v2/beatmapsets/{beatmap_set_id}"
|
f"https://osu.ppy.sh/api/v2/beatmapsets/{beatmap_set_id}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def search_beatmapset(
|
||||||
|
self, query: SearchQueryModel, cursor: Cursor
|
||||||
|
) -> SearchBeatmapsetsResp:
|
||||||
|
logger.opt(colors=True).debug(
|
||||||
|
f"<blue>[BeatmapsetFetcher]</blue> search_beatmapset: <y>{query}</y>"
|
||||||
|
)
|
||||||
|
|
||||||
|
params = query.model_dump(
|
||||||
|
exclude_none=True, exclude_unset=True, exclude_defaults=True
|
||||||
|
)
|
||||||
|
for k, v in cursor.items():
|
||||||
|
params[f"cursor[{k}]"] = v
|
||||||
|
resp = SearchBeatmapsetsResp.model_validate(
|
||||||
|
await self.request_api(
|
||||||
|
"https://osu.ppy.sh/api/v2/beatmapsets/search",
|
||||||
|
params=params,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return resp
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
from typing import Annotated, Any, Literal
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from .score import Rank
|
||||||
|
|
||||||
|
from pydantic import BaseModel, BeforeValidator, Field, PlainSerializer
|
||||||
|
|
||||||
|
|
||||||
class BeatmapRankStatus(IntEnum):
|
class BeatmapRankStatus(IntEnum):
|
||||||
@@ -79,3 +82,123 @@ class BeatmapAttributes(BaseModel):
|
|||||||
|
|
||||||
# taiko
|
# taiko
|
||||||
mono_stamina_factor: float | None = None
|
mono_stamina_factor: float | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_list(v: Any):
|
||||||
|
if isinstance(v, str):
|
||||||
|
return v.split(".")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class SearchQueryModel(BaseModel):
|
||||||
|
# model_config = ConfigDict(serialize_by_alias=True)
|
||||||
|
|
||||||
|
q: str = Field("", description="搜索关键词")
|
||||||
|
c: Annotated[
|
||||||
|
list[
|
||||||
|
Literal[
|
||||||
|
"recommended", "converts", "follows", "spotlights", "featured_artists"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
BeforeValidator(_parse_list),
|
||||||
|
PlainSerializer(lambda x: ".".join(x)),
|
||||||
|
] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
description=(
|
||||||
|
"常规:recommended 推荐难度 / converts 包括转谱 / follows 已关注谱师 / "
|
||||||
|
"spotlights 聚光灯谱面 / featured_artists 精选艺术家"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
m: int | None = Field(None, description="模式", alias="m")
|
||||||
|
s: Literal[
|
||||||
|
"any",
|
||||||
|
"leaderboard",
|
||||||
|
"ranked",
|
||||||
|
"qualified",
|
||||||
|
"loved",
|
||||||
|
"favourites",
|
||||||
|
"pending",
|
||||||
|
"wip",
|
||||||
|
"graveyard",
|
||||||
|
"mine",
|
||||||
|
] = Field(
|
||||||
|
default="leaderboard",
|
||||||
|
description=(
|
||||||
|
"分类:any 全部 / leaderboard 拥有排行榜 / ranked 上架 / "
|
||||||
|
"qualified 过审 / loved 社区喜爱 / favourites 收藏 / "
|
||||||
|
"pending 待定 / wip 制作中 / graveyard 坟场 / mine 我做的谱面"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
l: Literal[ # noqa: E741
|
||||||
|
"any",
|
||||||
|
"unspecified",
|
||||||
|
"english",
|
||||||
|
"japanese",
|
||||||
|
"chinese",
|
||||||
|
"instrumental",
|
||||||
|
"korean",
|
||||||
|
"french",
|
||||||
|
"german",
|
||||||
|
"swedish",
|
||||||
|
"spanish",
|
||||||
|
"italian",
|
||||||
|
"russian",
|
||||||
|
"polish",
|
||||||
|
"other",
|
||||||
|
] = Field(
|
||||||
|
default="any",
|
||||||
|
description=(
|
||||||
|
"语言:any 全部 / unspecified 未指定 / english 英语 / japanese 日语 / "
|
||||||
|
"chinese 中文 / instrumental 器乐 / korean 韩语 / "
|
||||||
|
"french 法语 / german 德语 / swedish 瑞典语 / "
|
||||||
|
"spanish 西班牙语 / italian 意大利语 / russian 俄语 / "
|
||||||
|
"polish 波兰语 / other 其他"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
sort: Literal[
|
||||||
|
"title_asc",
|
||||||
|
"artist_asc",
|
||||||
|
"difficulty_asc",
|
||||||
|
"updated_asc",
|
||||||
|
"ranked_asc",
|
||||||
|
"rating_asc",
|
||||||
|
"plays_asc",
|
||||||
|
"favourites_asc",
|
||||||
|
"relevance_asc",
|
||||||
|
"nominations_asc",
|
||||||
|
"title_desc",
|
||||||
|
"artist_desc",
|
||||||
|
"difficulty_desc",
|
||||||
|
"updated_desc",
|
||||||
|
"ranked_desc",
|
||||||
|
"rating_desc",
|
||||||
|
"plays_desc",
|
||||||
|
"favourites_desc",
|
||||||
|
"relevance_desc",
|
||||||
|
"nominations_desc",
|
||||||
|
] = Field(
|
||||||
|
...,
|
||||||
|
description=(
|
||||||
|
"排序方式: title 标题 / artist 艺术家 / difficulty 难度 / updated 更新时间"
|
||||||
|
" / ranked 上架时间 / rating 评分 / plays 游玩次数 / favourites 收藏量"
|
||||||
|
" / relevance 相关性 / nominations 提名"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
e: Annotated[
|
||||||
|
list[Literal["video", "storyboard"]],
|
||||||
|
BeforeValidator(_parse_list),
|
||||||
|
PlainSerializer(lambda x: ".".join(x)),
|
||||||
|
] = Field(
|
||||||
|
default_factory=list, description=("其他:video 有视频 / storyboard 有故事板")
|
||||||
|
)
|
||||||
|
r: Annotated[
|
||||||
|
list[Rank], BeforeValidator(_parse_list), PlainSerializer(lambda x: ".".join(x))
|
||||||
|
] = Field(default_factory=list, description="成绩")
|
||||||
|
played: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="玩过",
|
||||||
|
)
|
||||||
|
nsfw: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="不良内容",
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,22 +1,82 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Literal
|
import re
|
||||||
|
from typing import Annotated, Literal
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
from app.database import Beatmap, Beatmapset, BeatmapsetResp, FavouriteBeatmapset, User
|
from app.database import Beatmap, Beatmapset, BeatmapsetResp, FavouriteBeatmapset, User
|
||||||
from app.dependencies.database import get_db
|
from app.database.beatmapset import SearchBeatmapsetsResp
|
||||||
|
from app.dependencies.database import engine, get_db
|
||||||
from app.dependencies.fetcher import get_fetcher
|
from app.dependencies.fetcher import get_fetcher
|
||||||
from app.dependencies.user import get_client_user, get_current_user
|
from app.dependencies.user import get_client_user, get_current_user
|
||||||
from app.fetcher import Fetcher
|
from app.fetcher import Fetcher
|
||||||
|
from app.models.beatmap import SearchQueryModel
|
||||||
|
|
||||||
from .router import router
|
from .router import router
|
||||||
|
|
||||||
from fastapi import Depends, Form, HTTPException, Path, Query, Security
|
from fastapi import (
|
||||||
|
BackgroundTasks,
|
||||||
|
Depends,
|
||||||
|
Form,
|
||||||
|
HTTPException,
|
||||||
|
Path,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
Security,
|
||||||
|
)
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
from httpx import HTTPError
|
from httpx import HTTPError
|
||||||
from sqlmodel import select
|
from sqlmodel import exists, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
|
||||||
|
async def _save_to_db(sets: SearchBeatmapsetsResp):
|
||||||
|
async with AsyncSession(engine) as session:
|
||||||
|
for s in sets.beatmapsets:
|
||||||
|
if not (
|
||||||
|
await session.exec(select(exists()).where(Beatmapset.id == s.id))
|
||||||
|
).first():
|
||||||
|
await Beatmapset.from_resp(session, s)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/beatmapsets/search",
|
||||||
|
name="搜索谱面集",
|
||||||
|
tags=["谱面集"],
|
||||||
|
response_model=SearchBeatmapsetsResp,
|
||||||
|
)
|
||||||
|
async def search_beatmapset(
|
||||||
|
query: Annotated[SearchQueryModel, Query(...)],
|
||||||
|
request: Request,
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
|
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
fetcher: Fetcher = Depends(get_fetcher),
|
||||||
|
):
|
||||||
|
params = parse_qs(qs=request.url.query, keep_blank_values=True)
|
||||||
|
cursor = {}
|
||||||
|
for k, v in params.items():
|
||||||
|
match = re.match(r"cursor\[(\w+)\]", k)
|
||||||
|
if match:
|
||||||
|
cursor[match.group(1)] = v[0] if v else None
|
||||||
|
if (
|
||||||
|
"recommended" in query.c
|
||||||
|
or len(query.r) > 0
|
||||||
|
or query.played
|
||||||
|
or "follows" in query.c
|
||||||
|
or "mine" in query.s
|
||||||
|
or "favourites" in query.s
|
||||||
|
):
|
||||||
|
# TODO: search locally
|
||||||
|
return SearchBeatmapsetsResp(total=0, beatmapsets=[])
|
||||||
|
try:
|
||||||
|
sets = await fetcher.search_beatmapset(query, cursor)
|
||||||
|
background_tasks.add_task(_save_to_db, sets)
|
||||||
|
except HTTPError as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
return sets
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/beatmapsets/lookup",
|
"/beatmapsets/lookup",
|
||||||
tags=["谱面集"],
|
tags=["谱面集"],
|
||||||
|
|||||||
124
migrations/versions/59c9a0827de0_beatmap_add_indexes.py
Normal file
124
migrations/versions/59c9a0827de0_beatmap_add_indexes.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
"""beatmap: add indexes
|
||||||
|
|
||||||
|
Revision ID: 59c9a0827de0
|
||||||
|
Revises: 881ac7ca01d5
|
||||||
|
Create Date: 2025-08-13 07:07:52.506510
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "59c9a0827de0"
|
||||||
|
down_revision: str | Sequence[str] | None = "f785165a5c0b"
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmaps_beatmap_status"), "beatmaps", ["beatmap_status"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmaps_difficulty_rating"),
|
||||||
|
"beatmaps",
|
||||||
|
["difficulty_rating"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmaps_last_updated"), "beatmaps", ["last_updated"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_beatmaps_user_id"), "beatmaps", ["user_id"], unique=False)
|
||||||
|
op.create_index(op.f("ix_beatmaps_version"), "beatmaps", ["version"], unique=False)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_beatmap_genre"),
|
||||||
|
"beatmapsets",
|
||||||
|
["beatmap_genre"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_beatmap_language"),
|
||||||
|
"beatmapsets",
|
||||||
|
["beatmap_language"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_beatmap_status"),
|
||||||
|
"beatmapsets",
|
||||||
|
["beatmap_status"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_creator"), "beatmapsets", ["creator"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_last_updated"),
|
||||||
|
"beatmapsets",
|
||||||
|
["last_updated"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_play_count"), "beatmapsets", ["play_count"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_ranked_date"), "beatmapsets", ["ranked_date"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_storyboard"), "beatmapsets", ["storyboard"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_submitted_date"),
|
||||||
|
"beatmapsets",
|
||||||
|
["submitted_date"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_title"), "beatmapsets", ["title"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_title_unicode"),
|
||||||
|
"beatmapsets",
|
||||||
|
["title_unicode"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_track_id"), "beatmapsets", ["track_id"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_user_id"), "beatmapsets", ["user_id"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_beatmapsets_video"), "beatmapsets", ["video"], unique=False
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_video"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_user_id"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_track_id"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_title_unicode"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_title"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_submitted_date"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_storyboard"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_ranked_date"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_play_count"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_last_updated"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_creator"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_beatmap_status"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_beatmap_language"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmapsets_beatmap_genre"), table_name="beatmapsets")
|
||||||
|
op.drop_index(op.f("ix_beatmaps_version"), table_name="beatmaps")
|
||||||
|
op.drop_index(op.f("ix_beatmaps_user_id"), table_name="beatmaps")
|
||||||
|
op.drop_index(op.f("ix_beatmaps_last_updated"), table_name="beatmaps")
|
||||||
|
op.drop_index(op.f("ix_beatmaps_difficulty_rating"), table_name="beatmaps")
|
||||||
|
op.drop_index(op.f("ix_beatmaps_beatmap_status"), table_name="beatmaps")
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user