Skip to content

Commit 41ca2ef

Browse files
authored
Merge pull request #256 from Police-Data-Accessibility-Project/mc_254_search_url
feat(app): Add `/search/url` endpoint
2 parents 18be3c9 + 1fcc949 commit 41ca2ef

7 files changed

Lines changed: 82 additions & 2 deletions

File tree

api/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from api.routes.collector import collector_router
1212
from api.routes.review import review_router
1313
from api.routes.root import root_router
14+
from api.routes.search import search_router
1415
from api.routes.task import task_router
1516
from api.routes.url import url_router
1617
from collector_db.AsyncDatabaseClient import AsyncDatabaseClient
@@ -128,7 +129,8 @@ async def redirect_docs():
128129
annotate_router,
129130
url_router,
130131
task_router,
131-
review_router
132+
review_router,
133+
search_router
132134
]
133135

134136
for router in routers:

api/routes/search.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from fastapi import APIRouter, Query, Depends
2+
3+
from api.dependencies import get_async_core
4+
from core.AsyncCore import AsyncCore
5+
from core.DTOs.SearchURLResponse import SearchURLResponse
6+
from security_manager.SecurityManager import get_access_info, AccessInfo
7+
8+
search_router = APIRouter(prefix="/search", tags=["search"])
9+
10+
11+
@search_router.get("/url")
12+
async def search_url(
13+
url: str = Query(description="The URL to search for"),
14+
access_info: AccessInfo = Depends(get_access_info),
15+
async_core: AsyncCore = Depends(get_async_core),
16+
) -> SearchURLResponse:
17+
"""
18+
Search for a URL in the database
19+
"""
20+
return await async_core.search_for_url(url)

collector_db/AsyncDatabaseClient.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
GetURLsResponseInnerInfo
4444
from core.DTOs.ManualBatchInputDTO import ManualBatchInputDTO
4545
from core.DTOs.ManualBatchResponseDTO import ManualBatchResponseDTO
46+
from core.DTOs.SearchURLResponse import SearchURLResponse
4647
from core.DTOs.URLAgencySuggestionInfo import URLAgencySuggestionInfo
4748
from core.DTOs.task_data_objects.AgencyIdentificationTDO import AgencyIdentificationTDO
4849
from core.DTOs.task_data_objects.SubmitApprovedURLTDO import SubmitApprovedURLTDO, SubmittedURLInfo
@@ -1778,3 +1779,18 @@ async def upload_manual_batch(
17781779
duplicate_urls=duplicate_urls
17791780
)
17801781

1782+
@session_manager
1783+
async def search_for_url(self, session: AsyncSession, url: str) -> SearchURLResponse:
1784+
query = select(URL).where(URL.url == url)
1785+
raw_results = await session.execute(query)
1786+
url = raw_results.scalars().one_or_none()
1787+
if url is None:
1788+
return SearchURLResponse(
1789+
found=False,
1790+
url_id=None
1791+
)
1792+
return SearchURLResponse(
1793+
found=True,
1794+
url_id=url.id
1795+
)
1796+

core/AsyncCore.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from core.DTOs.ManualBatchInputDTO import ManualBatchInputDTO
2626
from core.DTOs.ManualBatchResponseDTO import ManualBatchResponseDTO
2727
from core.DTOs.MessageResponse import MessageResponse
28+
from core.DTOs.SearchURLResponse import SearchURLResponse
2829
from core.TaskManager import TaskManager
2930
from core.enums import BatchStatus, RecordType
3031

@@ -282,3 +283,5 @@ async def upload_manual_batch(
282283
dto=dto
283284
)
284285

286+
async def search_for_url(self, url: str) -> SearchURLResponse:
287+
return await self.adb_client.search_for_url(url)

core/DTOs/SearchURLResponse.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from typing import Optional
2+
3+
from pydantic import BaseModel
4+
5+
6+
class SearchURLResponse(BaseModel):
7+
found: bool
8+
url_id: Optional[int] = None

tests/test_automated/integration/api/helpers/RequestValidator.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from core.DTOs.MessageResponse import MessageResponse
3131
from core.DTOs.RecordTypeAnnotationPostInfo import RecordTypeAnnotationPostInfo
3232
from core.DTOs.RelevanceAnnotationPostInfo import RelevanceAnnotationPostInfo
33+
from core.DTOs.SearchURLResponse import SearchURLResponse
3334
from core.enums import BatchStatus
3435
from util.helper_functions import update_if_not_none
3536

@@ -385,4 +386,11 @@ async def submit_manual_batch(
385386
url="/collector/manual",
386387
json=dto.model_dump(mode='json'),
387388
)
388-
return ManualBatchResponseDTO(**data)
389+
return ManualBatchResponseDTO(**data)
390+
391+
async def search_url(self, url: str) -> SearchURLResponse:
392+
data = self.get(
393+
url=f"/search/url",
394+
params={"url": url}
395+
)
396+
return SearchURLResponse(**data)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
3+
from core.DTOs.SearchURLResponse import SearchURLResponse
4+
5+
6+
@pytest.mark.asyncio
7+
async def test_search_url(api_test_helper):
8+
ath = api_test_helper
9+
10+
# Create a batch with 1 URL
11+
creation_info = await ath.db_data_creator.batch_and_urls(url_count=1, with_html_content=False)
12+
13+
# Search for that URL and locate it
14+
response: SearchURLResponse = await ath.request_validator.search_url(url=creation_info.urls[0])
15+
16+
assert response.found
17+
assert response.url_id == creation_info.url_ids[0]
18+
19+
# Search for a non-existent URL
20+
response: SearchURLResponse = await ath.request_validator.search_url(url="http://doesnotexist.com")
21+
22+
assert not response.found
23+
assert response.url_id is None

0 commit comments

Comments
 (0)