From 1acefc679da00ef0c97b7df93200cd12a59805ad Mon Sep 17 00:00:00 2001 From: Thomas Sileo Date: Sun, 3 Jul 2022 19:17:19 +0200 Subject: [PATCH] HTML page to show tagged objects --- app/main.py | 64 +++++++++++++++++++++++++++++++--------------- tests/test_tags.py | 43 +++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/app/main.py b/app/main.py index dedc6fd..3de8d69 100644 --- a/app/main.py +++ b/app/main.py @@ -64,22 +64,13 @@ _RESIZED_CACHE: MutableMapping[tuple[str, int], tuple[bytes, str, Any]] = LFUCac # TODO(ts): # # Next: -# - inbox/outbox admin -# - no counters anymore? -# - allow to show tags in the menu # - support update post with history -# - inbox/outbox in the admin (as in show every objects) -# - show likes/announces counter for outbox activities # - update actor support # - hash config/profile to detect when to send Update actor # # - [ ] block support -# - [ ] make the media proxy authenticated # - [ ] prevent SSRF (urlutils from little-boxes) # - [ ] Dockerization -# - [ ] Webmentions -# - [ ] custom emoji -# - [ ] poll/questions support # - [ ] cleanup tasks app = FastAPI(docs_url=None, redoc_url=None) @@ -564,23 +555,54 @@ async def tag_by_name( if not tagged_count: raise HTTPException(status_code=404) - outbox_objects = await db_session.execute( - select(models.OutboxObject.ap_id) - .join(models.TaggedOutboxObject) + if is_activitypub_requested(request): + outbox_object_ids = await db_session.execute( + select(models.OutboxObject.ap_id) + .join( + models.TaggedOutboxObject, + models.TaggedOutboxObject.outbox_object_id == models.OutboxObject.id, + ) + .where(*where) + .order_by(models.OutboxObject.ap_published_at.desc()) + .limit(20) + ) + return ActivityPubResponse( + { + "@context": ap.AS_CTX, + "id": BASE_URL + f"/t/{tag}", + "type": "OrderedCollection", + "totalItems": tagged_count, + "orderedItems": [ + outbox_object.ap_id for outbox_object in outbox_object_ids + ], + } + ) + + outbox_objects_result = await db_session.scalars( + select(models.OutboxObject) .where(*where) + .join( + models.TaggedOutboxObject, + models.TaggedOutboxObject.outbox_object_id == models.OutboxObject.id, + ) + .options( + joinedload(models.OutboxObject.outbox_object_attachments).options( + joinedload(models.OutboxObjectAttachment.upload) + ) + ) .order_by(models.OutboxObject.ap_published_at.desc()) .limit(20) ) - # TODO(ts): implement HTML version - # if is_activitypub_requested(request): - return ActivityPubResponse( + outbox_objects = outbox_objects_result.unique().all() + + return await templates.render_template( + db_session, + request, + "index.html", { - "@context": ap.AS_CTX, - "id": BASE_URL + f"/t/{tag}", - "type": "OrderedCollection", - "totalItems": tagged_count, - "orderedItems": [outbox_object.ap_id for outbox_object in outbox_objects], - } + "request": request, + "objects": outbox_objects, + }, ) diff --git a/tests/test_tags.py b/tests/test_tags.py index 70d2977..6c3967b 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -2,6 +2,9 @@ from fastapi.testclient import TestClient from sqlalchemy.orm import Session from app import activitypub as ap +from app import models +from app.config import generate_csrf_token +from tests.utils import generate_admin_session_cookies def test_tags__no_tags( @@ -11,3 +14,43 @@ def test_tags__no_tags( response = client.get("/t/nope", headers={"Accept": ap.AP_CONTENT_TYPE}) assert response.status_code == 404 + + +def test_tags__note_with_tag(db: Session, client: TestClient) -> None: + # Call admin endpoint to create a note with + note_content = "Hello #testing" + + response = client.post( + "/admin/actions/new", + data={ + "redirect_url": "http://testserver/", + "content": note_content, + "visibility": ap.VisibilityEnum.PUBLIC.name, + "csrf_token": generate_csrf_token(), + }, + cookies=generate_admin_session_cookies(), + ) + + # Then the server returns a 302 + assert response.status_code == 302 + + # And the Follow activity was created in the outbox + outbox_object = db.query(models.OutboxObject).one() + assert outbox_object.ap_type == "Note" + assert len(outbox_object.tags) == 1 + emoji_tag = outbox_object.tags[0] + assert emoji_tag["type"] == "Hashtag" + assert emoji_tag["name"] == "#testing" + + # And the tag page returns this note + html_resp = client.get("/t/testing") + html_resp.raise_for_status() + assert html_resp.status_code == 200 + assert "Hello" in html_resp.text + + # And the AP version of the page turns the note too + ap_resp = client.get("/t/testing", headers={"Accept": ap.AP_CONTENT_TYPE}) + ap_resp.raise_for_status() + ap_json_resp = ap_resp.json() + assert ap_json_resp["totalItems"] == 1 + assert ap_json_resp["orderedItems"] == [outbox_object.ap_id]