diff --git a/app/main.py b/app/main.py index 1467d56..dedc6fd 100644 --- a/app/main.py +++ b/app/main.py @@ -551,15 +551,35 @@ async def tag_by_name( db_session: AsyncSession = Depends(get_db_session), _: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker), ) -> ActivityPubResponse | templates.TemplateResponse: + where = [ + models.TaggedOutboxObject.tag == tag, + models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC, + models.OutboxObject.is_deleted.is_(False), + ] + tagged_count = await db_session.scalar( + select(func.count(models.OutboxObject.id)) + .join(models.TaggedOutboxObject) + .where(*where) + ) + if not tagged_count: + raise HTTPException(status_code=404) + + outbox_objects = await db_session.execute( + select(models.OutboxObject.ap_id) + .join(models.TaggedOutboxObject) + .where(*where) + .order_by(models.OutboxObject.ap_published_at.desc()) + .limit(20) + ) # TODO(ts): implement HTML version # if is_activitypub_requested(request): return ActivityPubResponse( { - "@context": ap.AS_CTX, # XXX: extended ctx? + "@context": ap.AS_CTX, "id": BASE_URL + f"/t/{tag}", "type": "OrderedCollection", - "totalItems": 0, - "orderedItems": [], + "totalItems": tagged_count, + "orderedItems": [outbox_object.ap_id for outbox_object in outbox_objects], } ) @@ -876,7 +896,8 @@ async def robots_file(): return """User-agent: * Disallow: /followers Disallow: /following -Disallow: /admin""" +Disallow: /admin +Disallow: /remote_follow""" async def _get_outbox_for_feed(db_session: AsyncSession) -> list[models.OutboxObject]: diff --git a/app/scss/main.scss b/app/scss/main.scss index e1d3125..dd60191 100644 --- a/app/scss/main.scss +++ b/app/scss/main.scss @@ -1,20 +1,92 @@ +$font-stack: Helvetica, sans-serif; +$background: #002B36; // solarized background color +// font-family: Inconsolata, monospace; +$primary-color: #e14eea; +$secondary-color: #32cd32; +$muted-color: #586e75; // solarized comment text +// #93a1a1; solarized body text + + body { - margin: 0; - padding: 0; - display: flex; - min-height: 100vh; - flex-direction: column; + font-family: $font-stack; + font-size: 20px; + line-height: 32px; + background: $background; + color: #ccc; + margin: 0; + padding: 0; + display: flex; + min-height: 100vh; + flex-direction: column; +} +a { + text-decoration: none; +} + +.activity-main { + a { + color: #ddd; + } +} +.activity-wrap { +} +code, pre { + color: #859900; // #cb4b16; // #268bd2; // #2aa198; + font-family: 'Inconsolata, monospace'; +} +header { + .title { + font-size: 1.3em; + text-decoration: none; + .handle { + font-size: 0.85em; + color: #93a1a1; + } + } + .counter { + color: #93a1a1; + } +} +.purple { + color: #e14eea; +} +a { + color: #e14eea; +} + +.green, a:hover { + color: #32cd32; } #main { flex: 1; } main { - max-width: 800px; - margin: 20px auto; + width: 100%; + max-width: 960px; + margin: 30px auto; } footer { - max-width: 800px; + width: 100%; + max-width: 960px; margin: 20px auto; + color: #93a1a1; +} +.actor-box { + display: flex; + column-gap: 20px; + margin:20px 0 10px 0; + .icon-box { + flex: 0 0 50px; + } + .actor-handle { + font-size: 0.85em; + line-height: 1em; + color: #93a1a1; + } + .actor-icon { + margin-top: 5px; + max-width: 50px; + } } #notifications, #followers, #following { ul { @@ -26,47 +98,47 @@ footer { display: inline-block; } } -.actor-box { - a { - text-decoration: none; + +#admin { +} + +.activity-bar { +input[type=submit], button { + font-size: 20px; + line-height: 32px; + font-family: "Inconsolata, monospace"; + background: none!important; + border: none; + padding: 0!important; + cursor: pointer; + color: #e14eea; + } + input[type=submit]:hover, button:hover { + color: #32cd32; + } } -#admin { -.navbar { - display: grid; - grid-template-rows: auto; - grid-template-columns: 1fr; - grid-auto-flow: dense; - justify-items: stretch; - align-items: stretch; - column-gap: 20px; -} - -.logo { - grid-column:-3; - padding: 5px; -} - -.menus { - display: flex; - flex-direction: row; - justify-content: start; - grid-column: 1; -} - -.menus * { - padding: 5px; -} -} - nav.flexbox { - display: flex; - justify-content: space-between; - align-items: center; + + input[type=submit] { + font-size: 20px; + line-height: 32px; + font-family: "Inconsolata, monospace"; + background: none!important; + border: none; + padding: 0!important; + cursor: pointer; + color: #e14eea; + } + input[type=submit]:hover { + color: #32cd32; + + } ul { display: flex; + flex-wrap: wrap; align-items: center; list-style-type: none; margin: 0; @@ -81,12 +153,13 @@ nav.flexbox { margin-right: 0px; } } -} -#admin { - a.active { - font-weight: bold; + a { text-decoration: none; } + a.active { + color: #32cd32; + font-weight: bold; + } } @@ -94,12 +167,11 @@ nav.flexbox { margin: 0 auto; padding: 30px 0; .actor-icon { - width:48px; + width: 50px; margin-right: 15px; - img { - max-width: 48px; - } + margin-top: 5px; } + .activity-content { display: flex; align-items:flex-start; @@ -112,6 +184,9 @@ nav.flexbox { font-weight:normal; margin-left: 5px; } + .actor-handle { + color: #93a1a1; + } .activity-date { float:right; } } } diff --git a/app/templates/admin_new.html b/app/templates/admin_new.html index 0c3b49b..b1221bd 100644 --- a/app/templates/admin_new.html +++ b/app/templates/admin_new.html @@ -18,7 +18,7 @@

{% for emoji in emojis %} - {{ emoji | emojify | safe }} + {{ emoji | emojify(True) | safe }} {% endfor %} {% for emoji in custom_emojis %} {{ emoji.name }} diff --git a/app/templates/header.html b/app/templates/header.html index b5be09f..a7e4988 100644 --- a/app/templates/header.html +++ b/app/templates/header.html @@ -3,8 +3,8 @@
- {{ local_actor.name }} - {{ local_actor.handle }} + {{ local_actor.name }} + {{ local_actor.handle }}
@@ -22,8 +22,8 @@ diff --git a/app/templates/object.html b/app/templates/object.html index 1bcb5e1..9b0e59d 100644 --- a/app/templates/object.html +++ b/app/templates/object.html @@ -2,6 +2,7 @@ {% extends "layout.html" %} {% block head %} +{% if outbox_object %} @@ -11,6 +12,7 @@ +{% endif %} {% endblock %} {% block content %} diff --git a/app/templates/utils.html b/app/templates/utils.html index 1f115d9..393fc0c 100644 --- a/app/templates/utils.html +++ b/app/templates/utils.html @@ -136,13 +136,13 @@ {% macro display_actor(actor, actors_metadata) %} {% set metadata = actors_metadata.get(actor.ap_id) %} -
-
- + {% if is_admin and metadata %} @@ -171,14 +171,13 @@ {% macro display_og_meta(object) %} {% if object.og_meta %} {% for og_meta in object.og_meta %} -
+
{% if og_meta.image %}
{{ og_meta.title }} - {% if og_meta.description %}

{{ og_meta.description }}

{% endif %} {{ og_meta.site_name }}
{% endif %} @@ -279,7 +278,7 @@
{{ object.actor.display_name }} - {{ object.actor.handle }} + {{ object.actor.handle }} {{ object.visibility.value }} diff --git a/tests/test_outbox.py b/tests/test_outbox.py index 81ec561..6a198d3 100644 --- a/tests/test_outbox.py +++ b/tests/test_outbox.py @@ -16,6 +16,19 @@ from tests import factories from tests.utils import generate_admin_session_cookies +def test_outbox__no_activities( + db: Session, + client: TestClient, +) -> None: + response = client.get("/outbox", headers={"Accept": ap.AP_CONTENT_TYPE}) + + assert response.status_code == 200 + + json_response = response.json() + assert json_response["totalItems"] == 0 + assert json_response["orderedItems"] == [] + + def test_send_follow_request( db: Session, client: TestClient, diff --git a/tests/test_tags.py b/tests/test_tags.py new file mode 100644 index 0000000..70d2977 --- /dev/null +++ b/tests/test_tags.py @@ -0,0 +1,13 @@ +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session + +from app import activitypub as ap + + +def test_tags__no_tags( + db: Session, + client: TestClient, +) -> None: + response = client.get("/t/nope", headers={"Accept": ap.AP_CONTENT_TYPE}) + + assert response.status_code == 404