Skip to content

Commit 58ef912

Browse files
committed
Add API endpoint to GET single public collection
1 parent 026234b commit 58ef912

File tree

2 files changed

+122
-9
lines changed

2 files changed

+122
-9
lines changed

backend/btrixcloud/colls.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Collections API
33
"""
44

5+
# pylint: disable=too-many-lines
6+
57
from collections import Counter
68
from uuid import UUID, uuid4
79
from typing import Optional, List, TYPE_CHECKING, cast, Dict, Tuple, Any, Union
@@ -210,11 +212,11 @@ async def remove_crawls_from_collection(
210212
return await self.get_collection_out(coll_id, org)
211213

212214
async def get_collection_raw(
213-
self, coll_id: UUID, public_only: bool = False
215+
self, coll_id: UUID, public_or_unlisted_only: bool = False
214216
) -> Dict[str, Any]:
215217
"""Get collection by id as dict from database"""
216218
query: dict[str, object] = {"_id": coll_id}
217-
if public_only:
219+
if public_or_unlisted_only:
218220
query["access"] = {"$in": ["public", "unlisted"]}
219221

220222
result = await self.collections.find_one(query)
@@ -224,17 +226,21 @@ async def get_collection_raw(
224226
return result
225227

226228
async def get_collection(
227-
self, coll_id: UUID, public_only: bool = False
229+
self, coll_id: UUID, public_or_unlisted_only: bool = False
228230
) -> Collection:
229231
"""Get collection by id"""
230-
result = await self.get_collection_raw(coll_id, public_only)
232+
result = await self.get_collection_raw(coll_id, public_or_unlisted_only)
231233
return Collection.from_dict(result)
232234

233235
async def get_collection_out(
234-
self, coll_id: UUID, org: Organization, resources=False, public_only=False
236+
self,
237+
coll_id: UUID,
238+
org: Organization,
239+
resources=False,
240+
public_or_unlisted_only=False,
235241
) -> CollOut:
236242
"""Get CollOut by id"""
237-
result = await self.get_collection_raw(coll_id, public_only)
243+
result = await self.get_collection_raw(coll_id, public_or_unlisted_only)
238244

239245
if resources:
240246
result["resources"] = await self.get_collection_crawl_resources(coll_id)
@@ -248,6 +254,26 @@ async def get_collection_out(
248254

249255
return CollOut.from_dict(result)
250256

257+
async def get_public_collection_out(
258+
self, coll_id: UUID, org: Organization
259+
) -> PublicCollOut:
260+
"""Get PublicCollOut by id"""
261+
result = await self.get_collection_raw(coll_id)
262+
263+
if result.get("access") != "public":
264+
raise HTTPException(status_code=404, detail="collection_not_found")
265+
266+
result["resources"] = await self.get_collection_crawl_resources(coll_id)
267+
268+
thumbnail = result.get("thumbnail")
269+
if thumbnail:
270+
image_file = ImageFile(**thumbnail)
271+
result["thumbnail"] = await image_file.get_public_image_file_out(
272+
org, self.storage_ops
273+
)
274+
275+
return PublicCollOut.from_dict(result)
276+
251277
async def list_collections(
252278
self,
253279
org: Organization,
@@ -825,7 +851,7 @@ async def get_collection_public_replay(
825851
org: Organization = Depends(org_public),
826852
):
827853
coll = await colls.get_collection_out(
828-
coll_id, org, resources=True, public_only=True
854+
coll_id, org, resources=True, public_or_unlisted_only=True
829855
)
830856
response.headers["Access-Control-Allow-Origin"] = "*"
831857
response.headers["Access-Control-Allow-Headers"] = "*"
@@ -920,6 +946,24 @@ async def get_org_public_collections(
920946
sort_direction=sortDirection,
921947
)
922948

949+
@app.get(
950+
"/public-collections/{org_slug}/collections/{coll_id}",
951+
tags=["collections"],
952+
response_model=PublicCollOut,
953+
)
954+
async def get_public_collection(
955+
org_slug: str,
956+
coll_id: UUID,
957+
):
958+
try:
959+
org = await colls.orgs.get_org_by_slug(org_slug)
960+
# pylint: disable=broad-exception-caught
961+
except Exception:
962+
# pylint: disable=raise-missing-from
963+
raise HTTPException(status_code=404, detail="collection_not_found")
964+
965+
return await colls.get_public_collection_out(coll_id, org)
966+
923967
@app.get(
924968
"/orgs/{oid}/collections/{coll_id}/urls",
925969
tags=["collections"],

backend/test/test_collections.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@
1515
CAPTION = "Short caption"
1616
UPDATED_CAPTION = "Updated caption"
1717

18+
NON_PUBLIC_COLL_FIELDS = (
19+
"oid",
20+
"modified",
21+
"crawlCount",
22+
"pageCount",
23+
"totalSize",
24+
"tags",
25+
"access",
26+
"homeUrlPageId",
27+
)
28+
NON_PUBLIC_IMAGE_FIELDS = ("originalFilename", "userid", "userName", "created")
29+
30+
1831
_coll_id = None
1932
_second_coll_id = None
2033
_public_coll_id = None
@@ -1024,7 +1037,7 @@ def test_list_public_colls_home_url_thumbnail():
10241037
assert coll["dateEarliest"]
10251038
assert coll["dateLatest"]
10261039

1027-
for field in non_public_fields:
1040+
for field in NON_PUBLIC_COLL_FIELDS:
10281041
assert field not in coll
10291042

10301043
if coll["id"] == _public_coll_id:
@@ -1042,13 +1055,69 @@ def test_list_public_colls_home_url_thumbnail():
10421055
assert thumbnail["size"]
10431056
assert thumbnail["mime"]
10441057

1045-
for field in non_public_image_fields:
1058+
for field in NON_PUBLIC_IMAGE_FIELDS:
10461059
assert field not in thumbnail
10471060

10481061
if coll["id"] == _second_public_coll_id:
10491062
assert coll["description"]
10501063

10511064

1065+
def test_get_public_collection():
1066+
r = requests.get(
1067+
f"{API_PREFIX}/public-collections/{default_org_slug}/collections/{_public_coll_id}"
1068+
)
1069+
assert r.status_code == 200
1070+
coll = r.json()
1071+
1072+
assert coll["id"] == _public_coll_id
1073+
assert coll["name"]
1074+
assert coll["resources"]
1075+
assert coll["dateEarliest"]
1076+
assert coll["dateLatest"]
1077+
1078+
for field in NON_PUBLIC_COLL_FIELDS:
1079+
assert field not in coll
1080+
1081+
assert coll["caption"] == CAPTION
1082+
1083+
assert coll["homeUrl"]
1084+
assert coll["homeUrlTs"]
1085+
1086+
thumbnail = coll["thumbnail"]
1087+
assert thumbnail
1088+
1089+
assert thumbnail["name"]
1090+
assert thumbnail["path"]
1091+
assert thumbnail["hash"]
1092+
assert thumbnail["size"]
1093+
assert thumbnail["mime"]
1094+
1095+
for field in NON_PUBLIC_IMAGE_FIELDS:
1096+
assert field not in thumbnail
1097+
1098+
# Invalid org slug - don't reveal whether org exists or not, use
1099+
# same exception as if collection doesn't exist
1100+
r = requests.get(
1101+
f"{API_PREFIX}/public-collections/doesntexist/collections/{_public_coll_id}"
1102+
)
1103+
assert r.status_code == 404
1104+
assert r.json()["detail"] == "collection_not_found"
1105+
1106+
# Invalid collection id
1107+
r = requests.get(
1108+
f"{API_PREFIX}/public-collections/{default_org_slug}/collections/doesntexist"
1109+
)
1110+
assert r.status_code == 404
1111+
assert r.json()["detail"] == "collection_not_found"
1112+
1113+
# Collection isn't public
1114+
r = requests.get(
1115+
f"{API_PREFIX}/public-collections/{default_org_slug}/collections/{ _coll_id}"
1116+
)
1117+
assert r.status_code == 404
1118+
assert r.json()["detail"] == "collection_not_found"
1119+
1120+
10521121
def test_delete_thumbnail(crawler_auth_headers, default_org_id):
10531122
r = requests.delete(
10541123
f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}/thumbnail",

0 commit comments

Comments
 (0)