kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
74ebbc428b
commit
af769de99e
|
@ -250,20 +250,46 @@ def follower_collection(domain, collection):
|
||||||
if not User.get_by_id(domain):
|
if not User.get_by_id(domain):
|
||||||
return f'User {domain} not found', 404
|
return f'User {domain} not found', 404
|
||||||
|
|
||||||
followers, before, after = common.fetch_followers(domain, collection)
|
# page
|
||||||
|
followers, new_before, new_after = common.fetch_followers(domain, collection)
|
||||||
|
items = []
|
||||||
|
for f in followers:
|
||||||
|
f_as2 = f.to_as2()
|
||||||
|
if f_as2:
|
||||||
|
items.append(f_as2)
|
||||||
|
|
||||||
|
page = {
|
||||||
|
'type': 'CollectionPage',
|
||||||
|
'partOf': request.base_url,
|
||||||
|
'items': items,
|
||||||
|
}
|
||||||
|
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
|
||||||
domain_prop = Follower.dest if collection == 'followers' else Follower.src
|
domain_prop = Follower.dest if collection == 'followers' else Follower.src
|
||||||
query = Follower.query(
|
count = Follower.query(
|
||||||
Follower.status == 'active',
|
Follower.status == 'active',
|
||||||
domain_prop == domain,
|
domain_prop == domain,
|
||||||
)
|
).count()
|
||||||
count = query.count()
|
|
||||||
ret = {
|
collection = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'summary': f"{domain}'s {collection}",
|
'id': request.base_url,
|
||||||
'type': 'Collection',
|
'type': 'Collection',
|
||||||
|
'summary': f"{domain}'s {collection}",
|
||||||
'totalItems': count,
|
'totalItems': count,
|
||||||
'items': [f.to_as2() for f in followers],
|
'first': page,
|
||||||
}
|
}
|
||||||
logger.info(f'Returning {json_dumps(ret, indent=2)}')
|
logger.info(f'Returning {json_dumps(collection, indent=2)}')
|
||||||
return ret, {'Content-Type': as2.CONTENT_TYPE}
|
return collection, {'Content-Type': as2.CONTENT_TYPE}
|
||||||
|
|
18
common.py
18
common.py
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
from datetime import timedelta, timezone
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
@ -73,7 +73,7 @@ DOMAIN_BLOCKLIST = frozenset((
|
||||||
|
|
||||||
_DEFAULT_SIGNATURE_USER = None
|
_DEFAULT_SIGNATURE_USER = None
|
||||||
|
|
||||||
CACHE_TIME = datetime.timedelta(seconds=10)
|
CACHE_TIME = timedelta(seconds=10)
|
||||||
PAGE_SIZE = 20
|
PAGE_SIZE = 20
|
||||||
|
|
||||||
|
|
||||||
|
@ -639,17 +639,21 @@ def fetch_page(query, model_class):
|
||||||
# TODO: unify this with Bridgy's user page
|
# TODO: unify this with Bridgy's user page
|
||||||
def get_paging_param(param):
|
def get_paging_param(param):
|
||||||
val = request.values.get(param)
|
val = request.values.get(param)
|
||||||
try:
|
if val:
|
||||||
return util.parse_iso8601(val.replace(' ', '+')) if val else None
|
try:
|
||||||
except BaseException:
|
dt = util.parse_iso8601(val.replace(' ', '+'))
|
||||||
error(f"Couldn't parse {param}, {val!r} as ISO8601")
|
except BaseException as e:
|
||||||
|
error(f"Couldn't parse {param}, {val!r} as ISO8601: {e}")
|
||||||
|
if dt.tzinfo:
|
||||||
|
dt = dt.astimezone(timezone.utc).replace(tzinfo=None)
|
||||||
|
return dt
|
||||||
|
|
||||||
before = get_paging_param('before')
|
before = get_paging_param('before')
|
||||||
after = get_paging_param('after')
|
after = get_paging_param('after')
|
||||||
if before and after:
|
if before and after:
|
||||||
error("can't handle both before and after")
|
error("can't handle both before and after")
|
||||||
elif after:
|
elif after:
|
||||||
query = query.filter(model_class.updated > after).order(model_class.updated)
|
query = query.filter(model_class.updated >= after).order(model_class.updated)
|
||||||
elif before:
|
elif before:
|
||||||
query = query.filter(model_class.updated < before).order(-model_class.updated)
|
query = query.filter(model_class.updated < before).order(-model_class.updated)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
TODO: test error handling
|
TODO: test error handling
|
||||||
"""
|
"""
|
||||||
import copy
|
import copy
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from unittest.mock import ANY, call, patch
|
from unittest.mock import ANY, call, patch
|
||||||
|
|
||||||
from granary import as2
|
from granary import as2
|
||||||
|
@ -657,15 +658,18 @@ class ActivityPubTest(testutil.TestCase):
|
||||||
self.assertEqual(200, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'summary': "foo.com's followers",
|
'id': 'http://localhost/foo.com/followers',
|
||||||
'type': 'Collection',
|
'type': 'Collection',
|
||||||
|
'summary': "foo.com's followers",
|
||||||
'totalItems': 0,
|
'totalItems': 0,
|
||||||
'items': [],
|
'first': {
|
||||||
|
'type': 'CollectionPage',
|
||||||
|
'partOf': 'http://localhost/foo.com/followers',
|
||||||
|
'items': [],
|
||||||
|
},
|
||||||
}, resp.json)
|
}, resp.json)
|
||||||
|
|
||||||
def test_followers_collection(self, *args):
|
def store_followers(self):
|
||||||
User.get_or_create('foo.com')
|
|
||||||
|
|
||||||
Follower.get_or_create('foo.com', 'https://bar.com',
|
Follower.get_or_create('foo.com', 'https://bar.com',
|
||||||
last_follow=json_dumps(FOLLOW_WITH_ACTOR))
|
last_follow=json_dumps(FOLLOW_WITH_ACTOR))
|
||||||
Follower.get_or_create('http://other/actor', 'foo.com')
|
Follower.get_or_create('http://other/actor', 'foo.com')
|
||||||
|
@ -673,14 +677,42 @@ class ActivityPubTest(testutil.TestCase):
|
||||||
last_follow=json_dumps(FOLLOW_WITH_ACTOR))
|
last_follow=json_dumps(FOLLOW_WITH_ACTOR))
|
||||||
Follower.get_or_create('foo.com', 'baj.com', status='inactive')
|
Follower.get_or_create('foo.com', 'baj.com', status='inactive')
|
||||||
|
|
||||||
|
def test_followers_collection(self, *args):
|
||||||
|
User.get_or_create('foo.com')
|
||||||
|
self.store_followers()
|
||||||
|
|
||||||
resp = self.client.get('/foo.com/followers')
|
resp = self.client.get('/foo.com/followers')
|
||||||
self.assertEqual(200, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'summary': "foo.com's followers",
|
'id': 'http://localhost/foo.com/followers',
|
||||||
'type': 'Collection',
|
'type': 'Collection',
|
||||||
|
'summary': "foo.com's followers",
|
||||||
'totalItems': 2,
|
'totalItems': 2,
|
||||||
'items': [ACTOR, ACTOR],
|
'first': {
|
||||||
|
'type': 'CollectionPage',
|
||||||
|
'partOf': 'http://localhost/foo.com/followers',
|
||||||
|
'items': [ACTOR, ACTOR],
|
||||||
|
},
|
||||||
|
}, resp.json)
|
||||||
|
|
||||||
|
@patch('common.PAGE_SIZE', 1)
|
||||||
|
def test_followers_collection_page(self, *args):
|
||||||
|
User.get_or_create('foo.com')
|
||||||
|
self.store_followers()
|
||||||
|
before = (datetime.utcnow() + timedelta(seconds=1)).isoformat()
|
||||||
|
next = Follower.get_by_id('foo.com https://baz.com').updated.isoformat()
|
||||||
|
|
||||||
|
resp = self.client.get(f'/foo.com/followers?before={before}')
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual({
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': f'http://localhost/foo.com/followers?before={before}',
|
||||||
|
'type': 'CollectionPage',
|
||||||
|
'partOf': 'http://localhost/foo.com/followers',
|
||||||
|
'next': f'http://localhost/foo.com/followers?before={next}',
|
||||||
|
'prev': f'http://localhost/foo.com/followers?after={before}',
|
||||||
|
'items': [ACTOR],
|
||||||
}, resp.json)
|
}, resp.json)
|
||||||
|
|
||||||
def test_following_collection_unknown_user(self, *args):
|
def test_following_collection_unknown_user(self, *args):
|
||||||
|
@ -694,15 +726,18 @@ class ActivityPubTest(testutil.TestCase):
|
||||||
self.assertEqual(200, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': 'http://localhost/foo.com/following',
|
||||||
'summary': "foo.com's following",
|
'summary': "foo.com's following",
|
||||||
'type': 'Collection',
|
'type': 'Collection',
|
||||||
'totalItems': 0,
|
'totalItems': 0,
|
||||||
'items': [],
|
'first': {
|
||||||
|
'type': 'CollectionPage',
|
||||||
|
'partOf': 'http://localhost/foo.com/following',
|
||||||
|
'items': [],
|
||||||
|
},
|
||||||
}, resp.json)
|
}, resp.json)
|
||||||
|
|
||||||
def test_following_collection(self, *args):
|
def store_following(self):
|
||||||
User.get_or_create('foo.com')
|
|
||||||
|
|
||||||
Follower.get_or_create('https://bar.com', 'foo.com',
|
Follower.get_or_create('https://bar.com', 'foo.com',
|
||||||
last_follow=json_dumps(FOLLOW_WITH_OBJECT))
|
last_follow=json_dumps(FOLLOW_WITH_OBJECT))
|
||||||
Follower.get_or_create('foo.com', 'http://other/actor')
|
Follower.get_or_create('foo.com', 'http://other/actor')
|
||||||
|
@ -710,12 +745,40 @@ class ActivityPubTest(testutil.TestCase):
|
||||||
last_follow=json_dumps(FOLLOW_WITH_OBJECT))
|
last_follow=json_dumps(FOLLOW_WITH_OBJECT))
|
||||||
Follower.get_or_create('baj.com', 'foo.com', status='inactive')
|
Follower.get_or_create('baj.com', 'foo.com', status='inactive')
|
||||||
|
|
||||||
|
def test_following_collection(self, *args):
|
||||||
|
User.get_or_create('foo.com')
|
||||||
|
self.store_following()
|
||||||
|
|
||||||
resp = self.client.get('/foo.com/following')
|
resp = self.client.get('/foo.com/following')
|
||||||
self.assertEqual(200, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': 'http://localhost/foo.com/following',
|
||||||
'summary': "foo.com's following",
|
'summary': "foo.com's following",
|
||||||
'type': 'Collection',
|
'type': 'Collection',
|
||||||
'totalItems': 2,
|
'totalItems': 2,
|
||||||
'items': [ACTOR, ACTOR],
|
'first': {
|
||||||
|
'type': 'CollectionPage',
|
||||||
|
'partOf': 'http://localhost/foo.com/following',
|
||||||
|
'items': [ACTOR, ACTOR],
|
||||||
|
},
|
||||||
|
}, resp.json)
|
||||||
|
|
||||||
|
@patch('common.PAGE_SIZE', 1)
|
||||||
|
def test_following_collection_page(self, *args):
|
||||||
|
User.get_or_create('foo.com')
|
||||||
|
self.store_following()
|
||||||
|
after = datetime(1900, 1, 1).isoformat()
|
||||||
|
prev = Follower.get_by_id('https://baz.com foo.com').updated.isoformat()
|
||||||
|
|
||||||
|
resp = self.client.get(f'/foo.com/following?after={after}')
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual({
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': f'http://localhost/foo.com/following?after={after}',
|
||||||
|
'type': 'CollectionPage',
|
||||||
|
'partOf': 'http://localhost/foo.com/following',
|
||||||
|
'prev': f'http://localhost/foo.com/following?after={prev}',
|
||||||
|
'next': f'http://localhost/foo.com/following?before={after}',
|
||||||
|
'items': [ACTOR],
|
||||||
}, resp.json)
|
}, resp.json)
|
||||||
|
|
Ładowanie…
Reference in New Issue