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.
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.
## Running your instance

View file

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

134
app.py
View file

@ -132,12 +132,23 @@ def inject_config():
"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(
microblogpub_version=VERSION,
config=config,
logged_in=session.get("logged_in", False),
followers_count=DB.followers.count(),
following_count=DB.following.count(),
followers_count=DB.activities.count(followers_q),
following_count=DB.activities.count(following_q),
notes_count=notes_count,
liked_count=liked_count,
with_replies_count=with_replies_count,
@ -499,7 +510,14 @@ def authorize_follow():
actor = get_actor_url(request.form.get("profile"))
if not actor:
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")
follow = ap.Follow(actor=MY_PERSON.id, object=actor)
@ -589,6 +607,38 @@ def tmp_migrate3():
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"):
older_than = newer_than = None
query_sort = -1
@ -765,7 +815,7 @@ def nodeinfo():
q = {
"box": Box.OUTBOX.value,
"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(
headers={
@ -781,10 +831,7 @@ def nodeinfo():
"protocols": ["activitypub"],
"services": {"inbound": [], "outbound": []},
"openRegistrations": False,
"usage": {
"users": {"total": 1},
"localPosts": DB.activities.count(q),
},
"usage": {"users": {"total": 1}, "localPosts": DB.activities.count(q)},
"metadata": {
"sourceCode": "https://github.com/tsileo/microblog.pub",
"nodeName": f"@{USERNAME}@{DOMAIN}",
@ -895,7 +942,7 @@ def outbox():
q = {
"box": Box.OUTBOX.value,
"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(
**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
def admin_stats():
def admin():
q = {
"meta.deleted": False,
"meta.undo": False,
@ -1084,11 +1131,21 @@ def admin_stats():
instances=list(DB.instances.find()),
inbox_size=DB.activities.count({"box": Box.INBOX.value}),
outbox_size=DB.activities.count({"box": Box.OUTBOX.value}),
object_cache_size=0,
actor_cache_size=0,
col_liked=col_liked,
col_followers=DB.followers.count(),
col_following=DB.following.count(),
col_followers=DB.activities.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():
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:
return _user_api_response(activity=existing["activity"]["id"])
@ -1464,36 +1528,50 @@ def api_follow():
@app.route("/followers")
def followers():
q = {"box": Box.INBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
if is_api_request():
return jsonify(
**activitypub.build_ordered_collection(
DB.followers,
DB.activities,
q=q,
cursor=request.args.get("cursor"),
map_func=lambda doc: doc["remote_actor"],
map_func=lambda doc: doc["activity"]["object"],
)
)
followers = [
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.followers.find(limit=50)
]
return render_template("followers.html", followers_data=followers)
followers, older_than, newer_than = paginated_query(DB.activities, q)
followers = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in followers]
return render_template(
"followers.html",
followers_data=followers,
older_than=older_than,
newer_than=newer_than,
)
@app.route("/following")
def following():
q = {"box": Box.OUTBOX.value, "type": ActivityType.FOLLOW.value, "meta.undo": False}
if is_api_request():
return jsonify(
**activitypub.build_ordered_collection(
DB.following,
DB.activities,
q=q,
cursor=request.args.get("cursor"),
map_func=lambda doc: doc["remote_actor"],
map_func=lambda doc: doc["activity"]["object"],
)
)
following = [
ACTOR_SERVICE.get(doc["remote_actor"]) for doc in DB.following.find(limit=50)
]
return render_template("following.html", following_data=following)
following, older_than, newer_than = paginated_query(DB.activities, q)
following = [ACTOR_SERVICE.get(doc["activity"]["object"]) for doc in following]
return render_template(
"following.html",
following_data=following,
older_than=older_than,
newer_than=newer_than,
)
@app.route("/tags/<tag>")

View file

@ -5,13 +5,11 @@
<div id="container">
{% include "header.html" %}
<div id="admin">
<h3>Stats</h3>
<h3>Admin</h3>
<h4>DB</h4>
<ul>
<li>Inbox size: <strong>{{ inbox_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>
<h4>Collections</h4>
<ul>
@ -19,12 +17,6 @@
<li>following: <strong>{{ col_following }}</strong></li>
<li>liked: <strong>{{col_liked }}</strong></li>
</ul>
<h4>Known Instances</h4>
<ul>
{% for instance in instances %}
<li><a href="{{ instance.instance }}">{{ instance.instance }}</a></li>
{% endfor %}
</ul>
</div>
</div>

View file

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

View file

@ -21,11 +21,10 @@
<body>
{% if logged_in %}
<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/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/stats"{% if request.path == "/admin/stats" %} class="selected" {% endif %}>Stats</a></li>
<li class="right"><a href="/admin/logout">Logout</a></li>
</ul>
{% endif %}