AP: add paging to outbox

finishes / fixes #383
as2-actor-ids
Ryan Barrett 2023-11-23 22:35:38 -08:00
rodzic db2668ffab
commit 579f55d965
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
2 zmienionych plików z 65 dodań i 12 usunięć

Wyświetl plik

@ -883,6 +883,8 @@ def follower_collection(id, collection):
* https://www.w3.org/TR/activitypub/#followers
* https://www.w3.org/TR/activitypub/#collections
* https://www.w3.org/TR/activitystreams-core/#paging
TODO: unify page generation with outbox()
"""
protocol = Protocol.for_request(fed='web')
assert protocol
@ -932,6 +934,10 @@ def follower_collection(id, collection):
@app.get(f'/<regex("{DOMAIN_RE}"):id>/outbox')
@flask_util.cached(cache, CACHE_TIME)
def outbox(id):
"""Serves a user's AP outbox.
TODO: unify page generation with follower_collection()
"""
protocol = Protocol.for_request(fed='web')
if not protocol:
error(f"Couldn't determine protocol", status=404)
@ -941,18 +947,34 @@ def outbox(id):
error(f'User {id} not found', status=404)
query = Object.query(Object.users == g.user.key)
objects, before, after = fetch_objects(query, by=Object.updated, user=g.user)
objects, new_before, new_after = fetch_objects(query, by=Object.updated,
user=g.user)
# page
page = {
'type': 'CollectionPage',
'partOf': request.base_url,
'items': util.trim_nulls([ActivityPub.convert(obj) for obj in objects]),
}
if new_before:
page['next'] = f'{request.base_url}?before={new_before}'
if new_after:
page['prev'] = f'{request.base_url}?after={new_after}'
if 'before' in request.args or 'after' in request.args:
page.update({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': request.url,
})
logger.info(f'Returning {json_dumps(page, indent=2)}')
return page, {'Content-Type': as2.CONTENT_TYPE}
# collection
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': request.url,
'summary': f"{id}'s outbox",
'type': 'OrderedCollection',
# TODO. needs to handle deleted
# 'totalItems': query.count(),
'first': {
'type': 'CollectionPage',
'partOf': request.base_url,
'items': [ActivityPub.convert(obj) for obj in objects],
},
'summary': f"{id}'s outbox",
'totalItems': query.count(),
'first': page,
}, {'Content-Type': as2.CONTENT_TYPE}

Wyświetl plik

@ -1568,6 +1568,7 @@ class ActivityPubTest(TestCase):
'id': 'https://fa.brid.gy/ap/fake:foo/outbox',
'summary': "fake:foo's outbox",
'type': 'OrderedCollection',
'totalItems': 0,
'first': {
'type': 'CollectionPage',
'partOf': 'https://fa.brid.gy/ap/fake:foo/outbox',
@ -1575,26 +1576,55 @@ class ActivityPubTest(TestCase):
},
}, resp.json)
def store_outbox_objects(self, user):
for i, obj in enumerate([REPLY, MENTION, LIKE, DELETE]):
self.store_object(id=obj['id'], users=[user.key], as2=obj)
@patch('models.PAGE_SIZE', 2)
def test_outbox_fake_objects(self, *_):
user = self.make_user('fake:foo', cls=Fake)
for i, obj in enumerate([REPLY, MENTION, LIKE, DELETE]):
self.store_object(id=str(i), users=[user.key], as2=obj)
self.store_outbox_objects(user)
resp = self.client.get(f'/ap/fake:foo/outbox',
base_url='https://fa.brid.gy')
self.assertEqual(200, resp.status_code)
after = Object.get_by_id(LIKE['id']).updated.isoformat()
self.assertEqual({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://fa.brid.gy/ap/fake:foo/outbox',
'summary': "fake:foo's outbox",
'type': 'OrderedCollection',
'totalItems': 4,
'first': {
'type': 'CollectionPage',
'partOf': 'https://fa.brid.gy/ap/fake:foo/outbox',
'items': [DELETE, LIKE, MENTION, REPLY],
'items': [DELETE, LIKE],
'next': f'https://fa.brid.gy/ap/fake:foo/outbox?before={after}',
},
}, resp.json)
@patch('models.PAGE_SIZE', 2)
def test_outbox_fake_objects_page(self, *_):
user = self.make_user('fake:foo', cls=Fake)
self.store_outbox_objects(user)
after = datetime(1900, 1, 1).isoformat()
resp = self.client.get(f'/ap/fake:foo/outbox?after={after}',
base_url='https://fa.brid.gy')
self.assertEqual(200, resp.status_code)
prev = Object.get_by_id(MENTION['id']).updated.isoformat()
self.assertEqual({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': f'https://fa.brid.gy/ap/fake:foo/outbox?after={after}',
'type': 'CollectionPage',
'partOf': 'https://fa.brid.gy/ap/fake:foo/outbox',
'prev': f'https://fa.brid.gy/ap/fake:foo/outbox?after={prev}',
'next': f'https://fa.brid.gy/ap/fake:foo/outbox?before={after}',
'items': [MENTION, REPLY],
}, resp.json)
def test_outbox_web_empty(self, *_):
resp = self.client.get(f'/user.com/outbox')
self.assertEqual(200, resp.status_code)
@ -1603,6 +1633,7 @@ class ActivityPubTest(TestCase):
'id': 'http://localhost/user.com/outbox',
'summary': "user.com's outbox",
'type': 'OrderedCollection',
'totalItems': 0,
'first': {
'type': 'CollectionPage',
'partOf': 'http://localhost/user.com/outbox',