kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
afa16d3864
commit
c1e0a08f72
17
models.py
17
models.py
|
@ -957,7 +957,7 @@ class Follower(ndb.Model):
|
|||
filter_prop == g.user.key,
|
||||
).order(-Follower.updated)
|
||||
|
||||
followers, before, after = fetch_page(query, Follower)
|
||||
followers, before, after = fetch_page(query, Follower, by=Follower.updated)
|
||||
users = ndb.get_multi(f.from_ if collection == 'followers' else f.to
|
||||
for f in followers)
|
||||
User.load_multi(u for u in users if u)
|
||||
|
@ -968,12 +968,11 @@ class Follower(ndb.Model):
|
|||
return followers, before, after
|
||||
|
||||
|
||||
def fetch_page(query, model_class):
|
||||
def fetch_page(query, model_class, by=None):
|
||||
"""Fetches a page of results from a datastore query.
|
||||
|
||||
Uses the ``before`` and ``after`` query params (if provided; should be
|
||||
ISO8601 timestamps) and the queried model class's ``updated`` property to
|
||||
identify the page to fetch.
|
||||
ISO8601 timestamps) and the ``by`` property to identify the page to fetch.
|
||||
|
||||
Populates a ``log_url_path`` property on each result entity that points to a
|
||||
its most recent logged request.
|
||||
|
@ -981,6 +980,8 @@ def fetch_page(query, model_class):
|
|||
Args:
|
||||
query (google.cloud.ndb.query.Query)
|
||||
model_class (class)
|
||||
by (ndb.model.Property): paging property, eg :attr:`models.Object.updated`
|
||||
or :attr:`models.Object.created`
|
||||
|
||||
Returns:
|
||||
(list of Object or Follower, str, str) tuple: (results, new_before,
|
||||
|
@ -988,6 +989,8 @@ def fetch_page(query, model_class):
|
|||
``before`` and ``after`` to fetch the previous and next pages,
|
||||
respectively
|
||||
"""
|
||||
assert by
|
||||
|
||||
# if there's a paging param ('before' or 'after'), update query with it
|
||||
# TODO: unify this with Bridgy's user page
|
||||
def get_paging_param(param):
|
||||
|
@ -1006,11 +1009,11 @@ def fetch_page(query, model_class):
|
|||
if before and after:
|
||||
error("can't handle both before and after")
|
||||
elif after:
|
||||
query = query.filter(model_class.updated >= after).order(model_class.updated)
|
||||
query = query.filter(by >= after).order(by)
|
||||
elif before:
|
||||
query = query.filter(model_class.updated < before).order(-model_class.updated)
|
||||
query = query.filter(by < before).order(-by)
|
||||
else:
|
||||
query = query.order(-model_class.updated)
|
||||
query = query.order(-by)
|
||||
|
||||
query_iter = query.iter()
|
||||
results = sorted(itertools.islice(query_iter, 0, PAGE_SIZE),
|
||||
|
|
70
pages.py
70
pages.py
|
@ -9,7 +9,7 @@ from flask import g, render_template, request
|
|||
from google.cloud.ndb import tasklets
|
||||
from google.cloud.ndb.query import AND, OR
|
||||
from google.cloud.ndb.stats import KindStat
|
||||
from granary import as1, as2, atom, microformats2, rss
|
||||
from granary import as1, atom, microformats2, rss
|
||||
import humanize
|
||||
from oauth_dropins.webutil import flask_util, logs, util
|
||||
from oauth_dropins.webutil.flask_util import error, flash, redirect
|
||||
|
@ -42,6 +42,9 @@ def load_user(protocol, id):
|
|||
:class:`werkzeug.exceptions.HTTPException` on error or redirect
|
||||
"""
|
||||
assert id
|
||||
if protocol == 'ap' and not id.startswith('@'):
|
||||
id = '@' + id
|
||||
|
||||
cls = PROTOCOLS[protocol]
|
||||
g.user = cls.get_by_id(id)
|
||||
|
||||
|
@ -95,15 +98,12 @@ def web_user_redirects(**kwargs):
|
|||
# WARNING: this overrides the /ap/... actor URL route in activitypub.py, *only*
|
||||
# for handles with leading @ character. be careful when changing this route!
|
||||
@app.get(f'/ap/@<id>', defaults={'protocol': 'ap'})
|
||||
def user(protocol, id):
|
||||
if protocol == 'ap' and not id.startswith('@'):
|
||||
id = '@' + id
|
||||
|
||||
def profile(protocol, id):
|
||||
load_user(protocol, id)
|
||||
|
||||
query = Object.query(OR(Object.users == g.user.key,
|
||||
Object.notify == g.user.key))
|
||||
objects, before, after = fetch_objects(query)
|
||||
objects, before, after = fetch_objects(query, by=Object.updated)
|
||||
|
||||
followers = Follower.query(Follower.to == g.user.key,
|
||||
Follower.status == 'active')\
|
||||
|
@ -115,15 +115,27 @@ def user(protocol, id):
|
|||
.count(limit=FOLLOWERS_UI_LIMIT)
|
||||
following = f'{following}{"+" if following == FOLLOWERS_UI_LIMIT else ""}'
|
||||
|
||||
return render_template(
|
||||
'profile.html',
|
||||
follow_url=request.values.get('url'),
|
||||
logs=logs,
|
||||
util=util,
|
||||
address=request.args.get('address'),
|
||||
g=g,
|
||||
**locals(),
|
||||
)
|
||||
return render_template('profile.html', logs=logs, util=util, g=g, **locals())
|
||||
|
||||
|
||||
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>/home')
|
||||
def home(protocol, id):
|
||||
load_user(protocol, id)
|
||||
|
||||
query = Object.query(Object.feed == g.user.key)
|
||||
objects, before, after = fetch_objects(query, by=Object.created)
|
||||
|
||||
return render_template('home.html', logs=logs, util=util, g=g, **locals())
|
||||
|
||||
|
||||
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>/notifications')
|
||||
def notifications(protocol, id):
|
||||
load_user(protocol, id)
|
||||
|
||||
query = Object.query(Object.notify == g.user.key)
|
||||
objects, before, after = fetch_objects(query, by=Object.updated)
|
||||
|
||||
return render_template('notifications.html', logs=logs, util=util, g=g, **locals())
|
||||
|
||||
|
||||
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>/<any(followers,following):collection>')
|
||||
|
@ -134,10 +146,10 @@ def followers_or_following(protocol, id, collection):
|
|||
return render_template(
|
||||
f'{collection}.html',
|
||||
address=request.args.get('address'),
|
||||
as2=as2,
|
||||
follow_url=request.values.get('url'),
|
||||
g=g,
|
||||
util=util,
|
||||
**locals()
|
||||
**locals(),
|
||||
)
|
||||
|
||||
|
||||
|
@ -149,13 +161,13 @@ def feed(protocol, id):
|
|||
|
||||
load_user(protocol, id)
|
||||
|
||||
objects = Object.query(OR(Object.feed == g.user.key,
|
||||
# backward compatibility
|
||||
AND(Object.users == g.user.key,
|
||||
Object.labels == 'feed'))) \
|
||||
.order(-Object.created) \
|
||||
.fetch(PAGE_SIZE)
|
||||
activities = [obj.as1 for obj in objects if not obj.deleted]
|
||||
query = Object.query(Object.feed == g.user.key)
|
||||
# .order(-Object.created) \
|
||||
# .fetch(PAGE_SIZE)
|
||||
# activities = [obj.as1 for obj in objects if not obj.deleted]
|
||||
|
||||
objects, _, _ = fetch_objects(query, by=Object.created)
|
||||
activities = [obj.as1 for obj in objects]
|
||||
|
||||
# hydrate authors, actors, objects from stored Objects
|
||||
fields = 'author', 'actor', 'object'
|
||||
|
@ -230,7 +242,7 @@ def bridge_user():
|
|||
return render_template('bridge_user.html')
|
||||
|
||||
|
||||
def fetch_objects(query):
|
||||
def fetch_objects(query, by=None):
|
||||
"""Fetches a page of :class:`models.Object` entities from a datastore query.
|
||||
|
||||
Wraps :func:`models.fetch_page` and adds attributes to the returned
|
||||
|
@ -238,16 +250,22 @@ def fetch_objects(query):
|
|||
|
||||
Args:
|
||||
query (ndb.Query)
|
||||
by (ndb.model.Property): either :attr:`models.Object.updated` or
|
||||
:attr:`models.Object.created`
|
||||
|
||||
Returns:
|
||||
(list of models.Object, str, str) tuple:
|
||||
(results, new ``before`` query param, new ``after`` query param)
|
||||
to fetch the previous and next pages, respectively
|
||||
"""
|
||||
objects, new_before, new_after = fetch_page(query, Object)
|
||||
assert by is Object.updated or by is Object.created
|
||||
objects, new_before, new_after = fetch_page(query, Object, by=by)
|
||||
|
||||
# synthesize human-friendly content for objects
|
||||
for i, obj in enumerate(objects):
|
||||
if obj.deleted:
|
||||
continue
|
||||
|
||||
obj_as1 = obj.as1
|
||||
inner_obj = as1.get_object(obj_as1)
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "user_base.html" %}
|
||||
{% set tab = "home" %}
|
||||
|
||||
{% block subtabs %}
|
||||
<div class="row">
|
||||
Subscribe:
|
||||
<a href="{{ g.user.user_page_path('feed') }}">HTML</a>
|
||||
· <a href="{{ g.user.user_page_path('feed?format=atom') }}">Atom</a>
|
||||
· <a href="{{ g.user.user_page_path('feed?format=rss') }}">RSS</a>
|
||||
</div>
|
||||
{% endblock subtabs %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "user_base.html" %}
|
||||
{% set tab = "notifications" %}
|
||||
|
||||
{% block subtabs %}
|
||||
<div class="row">
|
||||
Subscribe:
|
||||
<a href="{{ g.user.user_page_path('feed') }}">HTML</a>
|
||||
· <a href="{{ g.user.user_page_path('feed?format=atom') }}">Atom</a>
|
||||
· <a href="{{ g.user.user_page_path('feed?format=rss') }}">RSS</a>
|
||||
</div>
|
||||
{% endblock subtabs %}
|
|
@ -9,7 +9,7 @@
|
|||
·
|
||||
|
||||
{% if g.user.LABEL != 'activitypub' %}
|
||||
Bridged to
|
||||
bridged to
|
||||
<nobr title="Fediverse address">
|
||||
<img class="logo" src="/static/fediverse_logo.svg">
|
||||
{{ g.user.ap_address() }}
|
||||
|
@ -24,8 +24,3 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
{% endblock subtabs %}
|
||||
|
||||
{#
|
||||
{% block feed %}
|
||||
{% endblock feed %}
|
||||
#}
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
<!-- tabs -->
|
||||
<div class="row tabs">
|
||||
<a>
|
||||
<a></a>
|
||||
<a href="{{ g.user.user_page_path() }}"
|
||||
{% if tab == 'profile' %}class="active-tab"{% endif %}
|
||||
>👤 Profile</a><a
|
||||
|
@ -51,17 +51,10 @@
|
|||
href="{{ g.user.user_page_path('notifications') }}"
|
||||
{% if tab == 'notifications' %}class="active-tab"{% endif %}
|
||||
>🔔 Notifications</a>
|
||||
<a>
|
||||
<a></a>
|
||||
</div>
|
||||
|
||||
{% block subtabs %}
|
||||
<div class="row">
|
||||
· <a href="{{ g.user.user_page_path('feed') }}">HTML</a>
|
||||
· <a href="{{ g.user.user_page_path('feed?format=atom') }}">Atom</a>
|
||||
· <a href="{{ g.user.user_page_path('feed?format=rss') }}">RSS</a>
|
||||
· <a href="/.well-known/webfinger?resource=acct:{{ g.user.ap_address() }}">Webfinger</a>
|
||||
· <a href="{{ g.user.ap_actor() }}">ActivityPub</a>
|
||||
</div>
|
||||
{% endblock subtabs %}
|
||||
|
||||
{% block feed %}
|
||||
|
|
|
@ -44,8 +44,8 @@ class PagesTest(TestCase):
|
|||
self.assert_equals(200, got.status_code)
|
||||
|
||||
def test_user_fake(self):
|
||||
self.make_user('foo.com', cls=Fake)
|
||||
got = self.client.get('/fa/foo.com')
|
||||
self.make_user('fake:foo', cls=Fake)
|
||||
got = self.client.get('/fa/fake:foo')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
||||
def test_user_page_handle(self):
|
||||
|
@ -171,9 +171,19 @@ class PagesTest(TestCase):
|
|||
self.assertIn('@follow@stored', body)
|
||||
self.assertIn('@me@plus.google.com', body)
|
||||
|
||||
def test_home_fake(self):
|
||||
self.make_user('fake:foo', cls=Fake)
|
||||
got = self.client.get('/fa/fake:foo/home')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
||||
def test_home_objects(self):
|
||||
self.add_objects()
|
||||
got = self.client.get('/web/user.com/home')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
||||
def test_followers_fake(self):
|
||||
self.make_user('foo.com', cls=Fake)
|
||||
got = self.client.get('/fa/foo.com/followers')
|
||||
self.make_user('fake:foo', cls=Fake)
|
||||
got = self.client.get('/fa/fake:foo/followers')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
||||
def test_followers_empty(self):
|
||||
|
@ -216,8 +226,8 @@ class PagesTest(TestCase):
|
|||
self.assertNotIn('class="follower', got.get_data(as_text=True))
|
||||
|
||||
def test_following_fake(self):
|
||||
self.make_user('foo.com', cls=Fake)
|
||||
got = self.client.get('/fa/foo.com/following')
|
||||
self.make_user('fake:foo', cls=Fake)
|
||||
got = self.client.get('/fa/fake:foo/following')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
||||
def test_following_user_not_found(self):
|
||||
|
@ -247,8 +257,8 @@ class PagesTest(TestCase):
|
|||
self.assert_equals('/web/user.com/feed', got.headers['Location'])
|
||||
|
||||
def test_feed_fake(self):
|
||||
self.make_user('foo.com', cls=Fake)
|
||||
got = self.client.get('/fa/foo.com/feed')
|
||||
self.make_user('fake:foo', cls=Fake)
|
||||
got = self.client.get('/fa/fake:foo/feed')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
||||
def test_feed_html_empty(self):
|
||||
|
|
Ładowanie…
Reference in New Issue