Skip to content

Commit

Permalink
Merge pull request #139 from codeforjapan/feature/138
Browse files Browse the repository at this point in the history
Search Mock
  • Loading branch information
yu23ki14 authored Dec 23, 2024
2 parents cb10fbd + 1cfd73b commit eb47816
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 5 deletions.
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

0 comments on commit eb47816

Please sign in to comment.