Tweak the admin, finish the pagination

Fixes #14
This commit is contained in:
Thomas Sileo 2018-07-07 13:56:00 +02:00
parent f7e6d37dce
commit f6c26abccb
7 changed files with 115 additions and 44 deletions

View file

@ -50,8 +50,6 @@
microblog.pub implements an [ActivityPub](http://activitypub.rocks/) server, it implements both the client to server API and the federated server to server API. microblog.pub implements an [ActivityPub](http://activitypub.rocks/) server, it implements both the client to server API and the federated server to server API.
Compatible with [Mastodon](https://github.com/tootsuite/mastodon) (which is not following the spec closely), but will drop OStatus messages.
Activities are verified using HTTP Signatures or by fetching the content on the remote server directly. Activities are verified using HTTP Signatures or by fetching the content on the remote server directly.
## Running your instance ## Running your instance

View file

@ -14,19 +14,19 @@ from html2text import html2text
import tasks import tasks
from config import BASE_URL from config import BASE_URL
from config import DB from config import DB
from config import EXTRA_INBOXES
from config import ID from config import ID
from config import ME from config import ME
from config import MEDIA_CACHE
from config import USER_AGENT from config import USER_AGENT
from config import USERNAME from config import USERNAME
from config import MEDIA_CACHE
from config import EXTRA_INBOXES
from utils.media import Kind
from little_boxes import activitypub as ap from little_boxes import activitypub as ap
from little_boxes import strtobool from little_boxes import strtobool
from little_boxes.activitypub import _to_list from little_boxes.activitypub import _to_list
from little_boxes.backend import Backend from little_boxes.backend import Backend
from little_boxes.collection import parse_collection as ap_parse_collection from little_boxes.collection import parse_collection as ap_parse_collection
from little_boxes.errors import Error from little_boxes.errors import Error
from utils.media import Kind
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

134
app.py
View file

@ -132,12 +132,23 @@ def inject_config():
"type": ActivityType.LIKE.value, "type": ActivityType.LIKE.value,
} }
) )
followers_q = {
"box": Box.INBOX.value,
"type": ActivityType.FOLLOW.value,
"meta.undo": False,
}
following_q = {
"box": Box.OUTBOX.value,
"type": ActivityType.FOLLOW.value,
"meta.undo": False,
}
return dict( return dict(
microblogpub_version=VERSION, microblogpub_version=VERSION,
config=config, config=config,
logged_in=session.get("logged_in", False), logged_in=session.get("logged_in", False),
followers_count=DB.followers.count(), followers_count=DB.activities.count(followers_q),
following_count=DB.following.count(), following_count=DB.activities.count(following_q),
notes_count=notes_count, notes_count=notes_count,
liked_count=liked_count, liked_count=liked_count,
with_replies_count=with_replies_count, with_replies_count=with_replies_count,
@ -499,7 +510,14 @@ def authorize_follow():
actor = get_actor_url(request.form.get("profile")) actor = get_actor_url(request.form.get("profile"))
if not actor: if not actor:
abort(500) abort(500)
if DB.following.find({"remote_actor": actor}).count() > 0:
q = {
"box": Box.OUTBOX.value,
"type": ActivityType.FOLLOW.value,
"meta.undo": False,
"activity.object": actor,
}
if DB.activities.count(q) > 0:
return redirect("/following") return redirect("/following")
follow = ap.Follow(actor=MY_PERSON.id, object=actor) follow = ap.Follow(actor=MY_PERSON.id, object=actor)
@ -589,6 +607,38 @@ def tmp_migrate3():
return "Done" return "Done"
@app.route("/migration3")
@login_required
def tmp_migrate4():
for activity in DB.activities.find(
{"box": Box.OUTBOX.value, "type": ActivityType.UNDO.value}
):
try:
activity = ap.parse_activity(activity["activity"])
if activity.get_object().type == ActivityType.FOLLOW.value:
DB.activities.update_one(
{"remote_id": activity.get_object().id},
{"$set": {"meta.undo": True}},
)
print(activity.get_object().to_dict())
except Exception:
app.logger.exception("failed")
for activity in DB.activities.find(
{"box": Box.INBOX.value, "type": ActivityType.UNDO.value}
):
try:
activity = ap.parse_activity(activity["activity"])
if activity.get_object().type == ActivityType.FOLLOW.value:
DB.activities.update_one(
{"remote_id": activity.get_object().id},
{"$set": {"meta.undo": True}},
)
print(activity.get_object().to_dict())
except Exception:
app.logger.exception("failed")
return "Done"
def paginated_query(db, q, limit=25, sort_key="_id"): def paginated_query(db, q, limit=25, sort_key="_id"):
older_than = newer_than = None older_than = newer_than = None
query_sort = -1 query_sort = -1
@ -765,7 +815,7 @@ def nodeinfo():
q = { q = {
"box": Box.OUTBOX.value, "box": Box.OUTBOX.value,
"meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone "meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone
'type': {'$in': [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]}, "type": {"$in": [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]},
} }
return Response( return Response(
headers={ headers={
@ -781,10 +831,7 @@ def nodeinfo():
"protocols": ["activitypub"], "protocols": ["activitypub"],
"services": {"inbound": [], "outbound": []}, "services": {"inbound": [], "outbound": []},
"openRegistrations": False, "openRegistrations": False,
"usage": { "usage": {"users": {"total": 1}, "localPosts": DB.activities.count(q)},
"users": {"total": 1},
"localPosts": DB.activities.count(q),
},
"metadata": { "metadata": {
"sourceCode": "https://github.com/tsileo/microblog.pub", "sourceCode": "https://github.com/tsileo/microblog.pub",
"nodeName": f"@{USERNAME}@{DOMAIN}", "nodeName": f"@{USERNAME}@{DOMAIN}",
@ -895,7 +942,7 @@ def outbox():
q = { q = {
"box": Box.OUTBOX.value, "box": Box.OUTBOX.value,
"meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone "meta.deleted": False, # TODO(tsileo): retrieve deleted and expose tombstone
'type': {'$in': [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]}, "type": {"$in": [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]},
} }
return jsonify( return jsonify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
@ -1068,9 +1115,9 @@ def outbox_activity_shares(item_id):
) )
@app.route("/admin/stats", methods=["GET"]) @app.route("/admin", methods=["GET"])
@login_required @login_required
def admin_stats(): def admin():
q = { q = {
"meta.deleted": False, "meta.deleted": False,
"meta.undo": False, "meta.undo": False,
@ -1084,11 +1131,21 @@ def admin_stats():
instances=list(DB.instances.find()), instances=list(DB.instances.find()),
inbox_size=DB.activities.count({"box": Box.INBOX.value}), inbox_size=DB.activities.count({"box": Box.INBOX.value}),
outbox_size=DB.activities.count({"box": Box.OUTBOX.value}), outbox_size=DB.activities.count({"box": Box.OUTBOX.value}),
object_cache_size=0,
actor_cache_size=0,
col_liked=col_liked, col_liked=col_liked,
col_followers=DB.followers.count(), col_followers=DB.activities.count(
col_following=DB.following.count(), {
"box": Box.INBOX.value,
"type": ActivityType.FOLLOW.value,
"meta.undo": False,
}
),
col_following=DB.activities.count(
{
"box": Box.OUTBOX.value,
"type": ActivityType.FOLLOW.value,
"meta.undo": False,
}
),
) )
@ -1452,7 +1509,14 @@ def api_block():
def api_follow(): def api_follow():
actor = _user_api_arg("actor") actor = _user_api_arg("actor")
existing = DB.following.find_one({"remote_actor": actor}) q = {
"box": Box.OUTBOX.value,
"type": ActivityType.FOLLOW.value,
"meta.undo": False,
"activity.object": actor,
}
existing = DB.activities.find_one(q)
if existing: if existing:
return _user_api_response(activity=existing["activity"]["id"]) return _user_api_response(activity=existing["activity"]["id"])
@ -1464,36 +1528,50 @@ def api_follow():
@app.route("/followers") @app.route("/followers")
def followers(): def followers():
q = {"box": Box.INBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
if is_api_request(): if is_api_request():
return jsonify( return jsonify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.followers, DB.activities,
q=q,
cursor=request.args.get("cursor"), cursor=request.args.get("cursor"),
map_func=lambda doc: doc["remote_actor"], map_func=lambda doc: doc["activity"]["object"],
) )
) )
followers = [ followers, older_than, newer_than = paginated_query(DB.activities, q)
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.followers.find(limit=50) followers = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in followers]
] return render_template(
return render_template("followers.html", followers_data=followers) "followers.html",
followers_data=followers,
older_than=older_than,
newer_than=newer_than,
)
@app.route("/following") @app.route("/following")
def following(): def following():
q = {"box": Box.OUTBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
if is_api_request(): if is_api_request():
return jsonify( return jsonify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.following, DB.activities,
q=q,
cursor=request.args.get("cursor"), cursor=request.args.get("cursor"),
map_func=lambda doc: doc["remote_actor"], map_func=lambda doc: doc["activity"]["object"],
) )
) )
following = [ following, older_than, newer_than = paginated_query(DB.activities, q)
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.following.find(limit=50) following = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in following]
] return render_template(
return render_template("following.html", following_data=following) "following.html",
following_data=following,
older_than=older_than,
newer_than=newer_than,
)
@app.route("/tags/<tag>") @app.route("/tags/<tag>")

View file

@ -5,13 +5,11 @@
<div id="container"> <div id="container">
{% include "header.html" %} {% include "header.html" %}
<div id="admin"> <div id="admin">
<h3>Stats</h3> <h3>Admin</h3>
<h4>DB</h4> <h4>DB</h4>
<ul> <ul>
<li>Inbox size: <strong>{{ inbox_size }}</strong></li> <li>Inbox size: <strong>{{ inbox_size }}</strong></li>
<li>Outbox size: <strong>{{ outbox_size }}</strong></li> <li>Outbox size: <strong>{{ outbox_size }}</strong></li>
<li>Object cache size: <strong>{{ object_cache_size }}</strong></li>
<li>Actor cache size: <strong>{{ actor_cache_size }}</strong></li>
</ul> </ul>
<h4>Collections</h4> <h4>Collections</h4>
<ul> <ul>
@ -19,12 +17,6 @@
<li>following: <strong>{{ col_following }}</strong></li> <li>following: <strong>{{ col_following }}</strong></li>
<li>liked: <strong>{{col_liked }}</strong></li> <li>liked: <strong>{{col_liked }}</strong></li>
</ul> </ul>
<h4>Known Instances</h4>
<ul>
{% for instance in instances %}
<li><a href="{{ instance.instance }}">{{ instance.instance }}</a></li>
{% endfor %}
</ul>
</div> </div>
</div> </div>

View file

@ -13,7 +13,9 @@
{{ utils.display_actor_inline(follower, size=80) }} {{ utils.display_actor_inline(follower, size=80) }}
</div> </div>
{% endfor %} {% endfor %}
{{ utils.display_pagination(older_than, newer_than) }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block links %}{{ utils.display_pagination_links(older_than, newer_than) }}{% endblock %}

View file

@ -13,7 +13,9 @@
{{ utils.display_actor_inline(followed, size=80) }} {{ utils.display_actor_inline(followed, size=80) }}
</div> </div>
{% endfor %} {% endfor %}
{{ utils.display_pagination(older_than, newer_than) }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block links %}{{ utils.display_pagination_links(older_than, newer_than) }}{% endblock %}

View file

@ -21,11 +21,10 @@
<body> <body>
{% if logged_in %} {% if logged_in %}
<ul id="admin-menu"> <ul id="admin-menu">
<li class="left"><span class="admin-title">Admin</span></li> <li class="left"><a href="/admin" class="admin-title{% if request.path == "/admin" %} selected{% endif %}">Admin</a></li>
<li class="left"><a href="/admin/new"{% if request.path == "/admin/new" %} class="selected" {% endif %}>New</a></li> <li class="left"><a href="/admin/new"{% if request.path == "/admin/new" %} class="selected" {% endif %}>New</a></li>
<li class="left"><a href="/admin/stream"{% if request.path == "/admin/stream" %} class="selected" {% endif %}>Stream</a></li> <li class="left"><a href="/admin/stream"{% if request.path == "/admin/stream" %} class="selected" {% endif %}>Stream</a></li>
<li class="left"><a href="/admin/notifications"{% if request.path == "/admin/notifications" %} class="selected" {% endif %}>Notifications</a></li> <li class="left"><a href="/admin/notifications"{% if request.path == "/admin/notifications" %} class="selected" {% endif %}>Notifications</a></li>
<li class="left"><a href="/admin/stats"{% if request.path == "/admin/stats" %} class="selected" {% endif %}>Stats</a></li>
<li class="right"><a href="/admin/logout">Logout</a></li> <li class="right"><a href="/admin/logout">Logout</a></li>
</ul> </ul>
{% endif %} {% endif %}