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

Search Mock #139

Merged
merged 2 commits into from
Dec 23, 2024
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
95 changes: 95 additions & 0 deletions api/birdxplorer_api/openapi_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,98 @@ class FastAPIEndpointDocs(Generic[_KEY]):
"participant_id": v1_data_user_enrollments_participant_id,
},
)

v1_data_x_user_name = FastAPIEndpointParamDocs(
description="Xのユーザー名",
openapi_examples={
"single": {
"summary": "@以降のユーザ名",
"value": "elonmusk",
},
},
)

v1_data_x_user_follower_count = FastAPIEndpointParamDocs(
description="Xのユーザーのフォロワー数。",
openapi_examples={
"single": {
"summary": "フォロワー数",
"value": 100,
},
},
)

v1_data_x_user_follow_count = FastAPIEndpointParamDocs(
description="Xのユーザーのフォロー数。",
openapi_examples={
"single": {
"summary": "フォロー数",
"value": 100,
},
},
)

v1_data_post_favorite_count = FastAPIEndpointParamDocs(
description="Postのお気に入り数。",
openapi_examples={
"single": {
"summary": "お気に入り数",
"value": 100,
},
},
)

v1_data_post_repost_count = FastAPIEndpointParamDocs(
description="Postのリポスト数。",
openapi_examples={
"single": {
"summary": "リポスト数",
"value": 100,
},
},
)

v1_data_post_impression_count = FastAPIEndpointParamDocs(
description="Postのインプレッション数。",
openapi_examples={
"single": {
"summary": "インプレッション数",
"value": 100,
},
},
)

v1_data_post_includes_media = FastAPIEndpointParamDocs(
description="メディア情報を含んでいるか。",
openapi_examples={
"single": {
"summary": "メディア情報を含める",
"value": True,
},
},
)

# Get /api/v1/data/search の OpenAPI ドキュメント
V1DataSearchDocs = FastAPIEndpointDocs(
"アドバンスドサーチでデータを取得するエンドポイント",
{
"note_includes_text": v1_data_notes_search_text,
"note_excludes_text": v1_data_notes_search_text,
"post_includes_text": v1_data_posts_search_text,
"post_excludes_text": v1_data_posts_search_text,
"language": v1_data_notes_language,
"topic_ids": v1_date_notes_topic_ids,
"note_status": v1_data_notes_current_status,
"note_created_at_from": v1_data_notes_created_at_from,
"note_created_at_to": v1_data_notes_created_at_to,
"x_user_name": v1_data_x_user_name,
"x_user_followers_count_from": v1_data_x_user_follower_count,
"x_user_follow_count_from": v1_data_x_user_follow_count,
"post_favorite_count_from": v1_data_post_favorite_count,
"post_repost_count_from": v1_data_post_repost_count,
"post_impression_count_from": v1_data_post_impression_count,
"post_includes_media": v1_data_post_includes_media,
"offset": v1_data_posts_offset,
"limit": v1_data_posts_limit,
},
)
195 changes: 195 additions & 0 deletions api/birdxplorer_api/routers/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from birdxplorer_api.openapi_doc import (
V1DataNotesDocs,
V1DataPostsDocs,
V1DataSearchDocs,
V1DataTopicsDocs,
V1DataUserEnrollmentsDocs,
)
Expand All @@ -22,6 +23,7 @@
ParticipantId,
Post,
PostId,
SummaryString,
Topic,
TopicId,
TwitterTimestamp,
Expand Down Expand Up @@ -54,6 +56,18 @@
),
]

SearchPaginationMetaWithExamples: TypeAlias = Annotated[
PaginationMeta,
PydanticField(
description="ページネーション用情報。 リクエスト時に指定した offset / limit の値に応じて、次のページや前のページのリクエスト用 URL が設定される。",
json_schema_extra={
"examples": [
{"next": "http://birdxplorer.onrender.com/api/v1/data/search?offset=100&limit=100", "prev": "null"}
]
},
),
]

TopicListWithExamples: TypeAlias = Annotated[
List[Topic],
PydanticField(
Expand Down Expand Up @@ -141,6 +155,92 @@
]


class SearchedNote(BaseModel):
noteId: NoteId
summary: Annotated[SummaryString, PydanticField(description="コミュニティノートの本文")]
language: Annotated[LanguageIdentifier, PydanticField(description="コミュニティノートの言語")]
topics: Annotated[List[Topic], PydanticField(description="コミュニティノートに関連付けられたトピックのリスト")]
postId: PostId
current_status: Annotated[
Annotated[
str,
PydanticField(
json_schema_extra={
"enum": ["NEEDS_MORE_RATINGS", "CURRENTLY_RATED_HELPFUL", "CURRENTLY_RATED_NOT_HELPFUL"]
},
),
]
| None,
PydanticField(
description="コミュニティノートの現在の評価状態",
),
]
created_at: Annotated[
TwitterTimestamp, PydanticField(description="コミュニティノートの作成日時 (ミリ秒単位の UNIX EPOCH TIMESTAMP)")
]
post: Annotated[Post, PydanticField(description="コミュニティノートに関連付けられた Post の情報")]


SearchWithExamples: TypeAlias = Annotated[
List[SearchedNote],
PydanticField(
description="検索結果のノートのリスト",
json_schema_extra={
"examples": [
{
"noteId": "1845672983001710655",
"language": "ja",
"topics": [
{
"topicId": 26,
"label": {"ja": "セキュリティ上の脅威", "en": "security threat"},
"referenceCount": 0,
},
{"topicId": 47, "label": {"ja": "検閲", "en": "Censorship"}, "referenceCount": 0},
{"topicId": 51, "label": {"ja": "テクノロジー", "en": "technology"}, "referenceCount": 0},
],
"summary": "Content Security Policyは情報の持ち出しを防止する仕組みではありません。コンテンツインジェクションの脆弱性のリスクを軽減する仕組みです。適切なContent Security Policyがレスポンスヘッダーに設定されている場合でも、外部への通信をブロックできない点に注意が必要です。 Content Security Policy Level 3 https://w3c.github.io/webappsec-csp/", # noqa: E501
"currentStatus": "NEEDS_MORE_RATINGS",
"createdAt": 1728877704750,
"post": {
"postId": "1846718284369912064",
"xUserId": "90954365",
"xUser": {
"userId": "90954365",
"name": "earthquakejapan",
"profileImage": "https://pbs.twimg.com/profile_images/1638600342/japan_rel96_normal.jpg",
"followersCount": 162934,
"followingCount": 6,
},
"text": "今後48時間以内に日本ではマグニチュード6.0の地震が発生する可能性があります。地図をご覧ください。(10月17日~10月18日) - https://t.co/nuyiVdM4FW https://t.co/Xd6U9XkpbL", # noqa: E501
"mediaDetails": [
{
"mediaKey": "3_1846718279236177920-1846718284369912064",
"type": "photo",
"url": "https://pbs.twimg.com/media/GaDcfZoX0AAko2-.jpg",
"width": 900,
"height": 738,
}
],
"createdAt": 1729094524000,
"likeCount": 451,
"repostCount": 104,
"impressionCount": 82378,
"links": [
{
"linkId": "9c139b99-8111-e4f0-ad41-fc9e40d08722",
"url": "https://www.quakeprediction.com/Earthquake%20Forecast%20Japan.html",
}
],
"link": "https://x.com/earthquakejapan/status/1846718284369912064",
},
},
]
},
),
]


class TopicListResponse(BaseModel):
data: TopicListWithExamples

Expand All @@ -155,6 +255,11 @@ class PostListResponse(BaseModel):
meta: PostsPaginationMetaWithExamples


class SearchResponse(BaseModel):
data: SearchWithExamples
meta: SearchPaginationMetaWithExamples


def str_to_twitter_timestamp(s: str) -> TwitterTimestamp:
try:
return TwitterTimestamp.from_int(int(s))
Expand Down Expand Up @@ -310,4 +415,94 @@ def get_posts(

return PostListResponse(data=posts, meta=PaginationMeta(next=next_url, prev=prev_url))

@router.get("/search", description=V1DataSearchDocs.description, response_model=SearchResponse)
def search(
note_includes_text: Union[None, str] = Query(default=None, **V1DataSearchDocs.params["note_includes_text"]),
note_excludes_text: Union[None, str] = Query(default=None, **V1DataSearchDocs.params["note_excludes_text"]),
post_includes_text: Union[None, str] = Query(default=None, **V1DataSearchDocs.params["post_includes_text"]),
post_excludes_text: Union[None, str] = Query(default=None, **V1DataSearchDocs.params["post_excludes_text"]),
language: Union[LanguageIdentifier, None] = Query(default=None, **V1DataSearchDocs.params["language"]),
topic_ids: Union[List[TopicId], None] = Query(default=None, **V1DataSearchDocs.params["topic_ids"]),
note_status: Union[None, List[str]] = Query(default=None, **V1DataSearchDocs.params["note_status"]),
note_created_at_from: Union[None, TwitterTimestamp, str] = Query(
default=None, **V1DataSearchDocs.params["note_created_at_from"]
),
note_created_at_to: Union[None, TwitterTimestamp, str] = Query(
default=None, **V1DataSearchDocs.params["note_created_at_to"]
),
x_user_names: Union[List[str], None] = Query(default=None, **V1DataSearchDocs.params["x_user_name"]),
x_user_followers_count_from: Union[None, int] = Query(
default=None, **V1DataSearchDocs.params["x_user_followers_count_from"]
),
x_user_follow_count_from: Union[None, int] = Query(
default=None, **V1DataSearchDocs.params["x_user_follow_count_from"]
),
post_favorite_count_from: Union[None, int] = Query(
default=None, **V1DataSearchDocs.params["post_favorite_count_from"]
),
post_repost_count_from: Union[None, int] = Query(
default=None, **V1DataSearchDocs.params["post_repost_count_from"]
),
post_impression_count_from: Union[None, int] = Query(
default=None, **V1DataSearchDocs.params["post_impression_count_from"]
),
post_includes_media: bool = Query(default=True, **V1DataSearchDocs.params["post_includes_media"]),
offset: int = Query(default=0, ge=0, **V1DataSearchDocs.params["offset"]),
limit: int = Query(default=100, gt=0, le=1000, **V1DataSearchDocs.params["limit"]),
) -> SearchResponse:
return SearchResponse(
data=[
SearchedNote(
noteId="1845672983001710655",
language="ja",
topics=[
{
"topicId": 26,
"label": {"ja": "セキュリティ上の脅威", "en": "security threat"},
"referenceCount": 0,
},
{"topicId": 47, "label": {"ja": "検閲", "en": "Censorship"}, "referenceCount": 0},
{"topicId": 51, "label": {"ja": "テクノロジー", "en": "technology"}, "referenceCount": 0},
],
postId="1846718284369912064",
summary="Content Security Policyは情報の持ち出しを防止する仕組みではありません。コンテンツインジェクションの脆弱性のリスクを軽減する仕組みです。適切なContent Security Policyがレスポンスヘッダーに設定されている場合でも、外部への通信をブロックできない点に注意が必要です。 Content Security Policy Level 3 https://w3c.github.io/webappsec-csp/", # noqa: E501
current_status="NEEDS_MORE_RATINGS",
created_at=1728877704750,
post={
"postId": "1846718284369912064",
"xUserId": "90954365",
"xUser": {
"userId": "90954365",
"name": "earthquakejapan",
"profileImage": "https://pbs.twimg.com/profile_images/1638600342/japan_rel96_normal.jpg",
"followersCount": 162934,
"followingCount": 6,
},
"text": "今後48時間以内に日本ではマグニチュード6.0の地震が発生する可能性があります。地図をご覧ください。",
"mediaDetails": [
{
"mediaKey": "3_1846718279236177920-1846718284369912064",
"type": "photo",
"url": "https://pbs.twimg.com/media/GaDcfZoX0AAko2-.jpg",
"width": 900,
"height": 738,
}
],
"createdAt": 1729094524000,
"likeCount": 451,
"repostCount": 104,
"impressionCount": 82378,
"links": [
{
"linkId": "9c139b99-8111-e4f0-ad41-fc9e40d08722",
"url": "https://www.quakeprediction.com/Earthquake%20Forecast%20Japan.html",
}
],
"link": "https://x.com/earthquakejapan/status/1846718284369912064",
},
)
],
meta=PaginationMeta(next=None, prev=None),
)

return router
10 changes: 5 additions & 5 deletions common/birdxplorer_common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from pydantic.alias_generators import to_camel
from pydantic.json_schema import JsonSchemaValue
from pydantic.main import IncEx
from pydantic_core import Url, core_schema
from pydantic_core import core_schema

StrT = TypeVar("StrT", bound="BaseString")
IntT = TypeVar("IntT", bound="BaseInt")
Expand Down Expand Up @@ -799,11 +799,11 @@ class Link(BaseModel):
t.co に短縮される前の URL ごとに一意な ID を持つ。

>>> Link.model_validate_json('{"linkId": "d5d15194-6574-0c01-8f6f-15abd72b2cf6", "url": "https://example.com"}')
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=Url('https://example.com/'))
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=HttpUrl('https://example.com/'))
>>> Link(url="https://example.com/")
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=Url('https://example.com/'))
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=HttpUrl('https://example.com/'))
>>> Link(link_id=UUID("d5d15194-6574-0c01-8f6f-15abd72b2cf6"), url="https://example.com/")
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=Url('https://example.com/'))
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=HttpUrl('https://example.com/'))
""" # noqa: E501

link_id: Annotated[LinkId, PydanticField(description="リンクを識別できる UUID")]
Expand Down Expand Up @@ -840,7 +840,7 @@ def link(self) -> HttpUrl:
"""
PostのX上でのURLを返す。
"""
return Url(f"https://x.com/{self.x_user.name}/status/{self.post_id}")
return HttpUrl(f"https://x.com/{self.x_user.name}/status/{self.post_id}")


class PaginationMeta(BaseModel):
Expand Down
Loading