From c3edf3a68e5014403b2cf3a6d21d11c52eb396a5 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Wed, 18 Jan 2023 22:20:15 -0800 Subject: [PATCH] make Follower.to_as1() handle both inbound and outbound last_follow ...and use it in followers and following UI pages --- common.py | 3 ++- models.py | 6 +++--- pages.py | 24 ++++++++++-------------- tests/test_models.py | 18 +++++++++++++++++- tests/test_xrpc_graph.py | 12 +++++++----- 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/common.py b/common.py index c0db483..9750b32 100644 --- a/common.py +++ b/common.py @@ -74,6 +74,7 @@ DOMAIN_BLOCKLIST = frozenset(( _DEFAULT_SIGNATURE_USER = None CACHE_TIME = datetime.timedelta(seconds=10) +PAGE_SIZE = 20 def host_url(path_query=None): @@ -629,7 +630,7 @@ def fetch_page(query, model_class): query = query.order(-model_class.updated) query_iter = query.iter() - results = sorted(islice(query_iter, 0, PAGE_SIZE), + results = sorted(itertools.islice(query_iter, 0, PAGE_SIZE), key=lambda r: r.updated, reverse=True) # calculate new paging param(s) diff --git a/models.py b/models.py index fee9f08..c5b5be7 100644 --- a/models.py +++ b/models.py @@ -367,6 +367,6 @@ class Follower(StringIdModel): """Returns this follower as an AS1 actor dict, if possible.""" if self.last_follow: last_follow = json_loads(self.last_follow) - actor = last_follow.get('actor') - if actor: - return as2.to_as1(actor) + person = last_follow.get('actor' if util.is_web(self.src) else 'object') + if person: + return as2.to_as1(person) diff --git a/pages.py b/pages.py index e3df013..b402763 100644 --- a/pages.py +++ b/pages.py @@ -1,7 +1,6 @@ """UI pages.""" import calendar import datetime -from itertools import islice import logging import re import urllib.parse @@ -16,9 +15,9 @@ from oauth_dropins.webutil.util import json_dumps, json_loads from app import app, cache import common +from common import DOMAIN_RE, PAGE_SIZE from models import Follower, User, Activity -PAGE_SIZE = 20 ACTIVITIES_FETCH_LIMIT = 200 FOLLOWERS_UI_LIMIT = 999 @@ -65,12 +64,12 @@ def check_web_site(): return redirect(f'/user/{user.key.id()}') -@app.get(f'/responses/') # deprecated +@app.get(f'/responses/') # deprecated def user_deprecated(domain): return redirect(f'/user/{domain}', code=301) -@app.get(f'/user/') +@app.get(f'/user/') def user(domain): user = User.get_by_id(domain) if not user: @@ -102,7 +101,7 @@ def user(domain): ) -@app.get(f'/user//') +@app.get(f'/user//') def followers_or_following(domain, collection): if not (user := User.get_by_id(domain)): return render_template('user_not_found.html', domain=domain), 404 @@ -113,18 +112,15 @@ def followers_or_following(domain, collection): Follower.status == 'active', domain_prop == domain, ).order(-Follower.updated) - followers, before, after = common.fetch_page(query, Follower) + followers, before, after = fetch_page(query, Follower) for f in followers: f.url = f.src if collection == 'followers' else f.dest f.handle = re.sub(r'^https?://(.+)/(users/|@)(.+)$', r'@\3@\1', f.url) - if f.last_follow: - last_follow = json_loads(f.last_follow) - person = last_follow.get( - 'actor' if collection == 'followers' else 'object', {}) - if isinstance(person, dict): - f.name = person.get('name') or '' - f.picture = util.get_url(person, 'icon') or util.get_url(person, 'image') + person = f.to_as1() + if person and isinstance(person, dict): + f.name = person.get('name') or '' + f.picture = util.get_url(person, 'icon') or util.get_url(person, 'image') return render_template( f'{collection}.html', @@ -133,7 +129,7 @@ def followers_or_following(domain, collection): ) -@app.get(f'/user//feed') +@app.get(f'/user//feed') def feed(domain): format = request.args.get('format', 'html') if format not in ('html', 'atom', 'rss'): diff --git a/tests/test_models.py b/tests/test_models.py index f7350f7..5a92540 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -2,13 +2,15 @@ """Unit tests for models.py.""" from unittest import mock +from granary import as2 from oauth_dropins.webutil.testutil import requests_response from oauth_dropins.webutil.util import json_dumps, json_loads from app import app -from models import User, Activity +from models import Activity, Follower, User from . import testutil +from .test_activitypub import ACTOR class UserTest(testutil.TestCase): @@ -189,3 +191,17 @@ class ActivityTest(testutil.TestCase): activity.source_as2 = 'as2' self.assertEqual('http://localhost/render?source=abc&target=xyz', activity.proxy_url()) + + +class FollowerTest(testutil.TestCase): + def test_to_as1(self): + self.assertIsNone(Follower().to_as1()) + + as1_actor = as2.to_as1(ACTOR) + f = Follower(dest='foo.com', src='http://bar/@baz', + last_follow=json_dumps({'actor': ACTOR})) + self.assertEqual(as1_actor, f.to_as1()) + + f = Follower(dest='http://bar/@baz', src='foo.com', + last_follow=json_dumps({'object': ACTOR})) + self.assertEqual(as1_actor, f.to_as1()) diff --git a/tests/test_xrpc_graph.py b/tests/test_xrpc_graph.py index 51f5df9..b982093 100644 --- a/tests/test_xrpc_graph.py +++ b/tests/test_xrpc_graph.py @@ -7,7 +7,7 @@ from oauth_dropins.webutil.testutil import requests_response from oauth_dropins.webutil.util import json_dumps, json_loads import requests -from .test_activitypub import FOLLOW_WITH_ACTOR +from .test_activitypub import ACTOR, FOLLOW from . import testutil from models import Follower @@ -35,11 +35,13 @@ FOLLOWERS_BSKY = [{ 'declaration': ACTOR_DECLARATION, 'indexedAt': '2022-01-02T03:04:05+00:00', }] -OTHER_FOLLOW_AS2 = copy.deepcopy(FOLLOW_WITH_ACTOR) -OTHER_FOLLOW_AS2['actor'].update({ +FOLLOW_WITH_OBJECT_AS2 = copy.deepcopy(FOLLOW) +FOLLOW_WITH_OBJECT_AS2['object'] = ACTOR +OTHER_FOLLOW_AS2 = copy.deepcopy(FOLLOW) +OTHER_FOLLOW_AS2['object'] = { 'url': 'http://other', 'preferredUsername': 'yoozer', -}) +} @patch('requests.get') @@ -100,7 +102,7 @@ class XrpcGraphTest(testutil.TestCase): def test_getFollows(self, mock_get): Follower.get_or_create('https://no/stored/follow', 'foo.com') Follower.get_or_create('https://masto/user', 'foo.com', - last_follow=json_dumps(FOLLOW_WITH_ACTOR)) + last_follow=json_dumps(FOLLOW_WITH_OBJECT_AS2)) Follower.get_or_create( 'http://other', 'foo.com', last_follow=json_dumps(OTHER_FOLLOW_AS2)) Follower.get_or_create('http://nope', 'nope.com',