Tweak AP ctx, and gzip support for HTML resp

This commit is contained in:
Thomas Sileo 2019-08-24 10:41:31 +02:00
parent 784362b532
commit dad3dae988
7 changed files with 188 additions and 128 deletions

131
app.py
View file

@ -63,9 +63,10 @@ from core.meta import is_public
from core.meta import not_undo from core.meta import not_undo
from core.shared import _build_thread from core.shared import _build_thread
from core.shared import _get_ip from core.shared import _get_ip
from core.shared import activitypubify
from core.shared import csrf from core.shared import csrf
from core.shared import htmlify
from core.shared import is_api_request from core.shared import is_api_request
from core.shared import jsonify
from core.shared import login_required from core.shared import login_required
from core.shared import noindex from core.shared import noindex
from core.shared import paginated_query from core.shared import paginated_query
@ -322,7 +323,7 @@ def serve_uploads(oid, fname):
def remote_follow(): def remote_follow():
"""Form to allow visitor to perform the remote follow dance.""" """Form to allow visitor to perform the remote follow dance."""
if request.method == "GET": if request.method == "GET":
return render_template("remote_follow.html") return htmlify(render_template("remote_follow.html"))
csrf.protect() csrf.protect()
profile = request.form.get("profile") profile = request.form.get("profile")
@ -339,8 +340,7 @@ def remote_follow():
def index(): def index():
if is_api_request(): if is_api_request():
_log_sig() _log_sig()
print(ME) return activitypubify(**ME)
return jsonify(**ME)
q = { q = {
"box": Box.OUTBOX.value, "box": Box.OUTBOX.value,
@ -369,14 +369,15 @@ def index():
DB.activities, q, limit=25 - len(pinned) DB.activities, q, limit=25 - len(pinned)
) )
resp = render_template( return htmlify(
"index.html", render_template(
outbox_data=outbox_data, "index.html",
older_than=older_than, outbox_data=outbox_data,
newer_than=newer_than, older_than=older_than,
pinned=pinned, newer_than=newer_than,
pinned=pinned,
)
) )
return resp
@app.route("/all") @app.route("/all")
@ -391,11 +392,13 @@ def all():
} }
outbox_data, older_than, newer_than = paginated_query(DB.activities, q) outbox_data, older_than, newer_than = paginated_query(DB.activities, q)
return render_template( return htmlify(
"index.html", render_template(
outbox_data=outbox_data, "index.html",
older_than=older_than, outbox_data=outbox_data,
newer_than=newer_than, older_than=older_than,
newer_than=newer_than,
)
) )
@ -458,8 +461,10 @@ def note_by_id(note_id):
app.logger.exception(f"invalid doc: {doc!r}") app.logger.exception(f"invalid doc: {doc!r}")
app.logger.info(f"shares={shares!r}") app.logger.info(f"shares={shares!r}")
return render_template( return htmlify(
"note.html", likes=likes, shares=shares, thread=thread, note=data render_template(
"note.html", likes=likes, shares=shares, thread=thread, note=data
)
) )
@ -477,7 +482,7 @@ def outbox():
"meta.public": True, "meta.public": True,
"type": {"$in": [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]}, "type": {"$in": [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]},
} }
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,
@ -503,7 +508,9 @@ def outbox():
@app.route("/emoji/<name>") @app.route("/emoji/<name>")
def ap_emoji(name): def ap_emoji(name):
if name in EMOJIS: if name in EMOJIS:
return jsonify(**{**EMOJIS[name].to_dict(), "@context": config.DEFAULT_CTX}) return activitypubify(
**{**EMOJIS[name].to_dict(), "@context": config.DEFAULT_CTX}
)
abort(404) abort(404)
@ -523,7 +530,7 @@ def outbox_detail(item_id):
if doc["meta"].get("deleted", False): if doc["meta"].get("deleted", False):
abort(404) abort(404)
return jsonify(**activity_from_doc(doc)) return activitypubify(**activity_from_doc(doc))
@app.route("/outbox/<item_id>/activity") @app.route("/outbox/<item_id>/activity")
@ -541,7 +548,7 @@ def outbox_activity(item_id):
if obj["type"] != ActivityType.CREATE.value: if obj["type"] != ActivityType.CREATE.value:
abort(404) abort(404)
return jsonify(**obj["object"]) return activitypubify(**obj["object"])
@app.route("/outbox/<item_id>/replies") @app.route("/outbox/<item_id>/replies")
@ -570,7 +577,7 @@ def outbox_activity_replies(item_id):
"activity.object.inReplyTo": obj.get_object().id, "activity.object.inReplyTo": obj.get_object().id,
} }
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,
@ -610,7 +617,7 @@ def outbox_activity_likes(item_id):
], ],
} }
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,
@ -649,7 +656,7 @@ def outbox_activity_shares(item_id):
], ],
} }
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,
@ -672,7 +679,7 @@ def inbox():
except BadSignature: except BadSignature:
abort(404) abort(404)
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q={"meta.deleted": False, "box": Box.INBOX.value}, q={"meta.deleted": False, "box": Box.INBOX.value},
@ -800,7 +807,7 @@ def followers():
if is_api_request(): if is_api_request():
_log_sig() _log_sig()
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,
@ -814,11 +821,13 @@ def followers():
followers = [ followers = [
doc["meta"]["actor"] for doc in raw_followers if "actor" in doc.get("meta", {}) doc["meta"]["actor"] for doc in raw_followers if "actor" in doc.get("meta", {})
] ]
return render_template( return htmlify(
"followers.html", render_template(
followers_data=followers, "followers.html",
older_than=older_than, followers_data=followers,
newer_than=newer_than, older_than=older_than,
newer_than=newer_than,
)
) )
@ -829,11 +838,11 @@ def following():
if is_api_request(): if is_api_request():
_log_sig() _log_sig()
if config.HIDE_FOLLOWING: if config.HIDE_FOLLOWING:
return jsonify( return activitypubify(
**activitypub.simple_build_ordered_collection("following", []) **activitypub.simple_build_ordered_collection("following", [])
) )
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,
@ -853,12 +862,14 @@ def following():
if "remote_id" in doc and "object" in doc.get("meta", {}) if "remote_id" in doc and "object" in doc.get("meta", {})
] ]
lists = list(DB.lists.find()) lists = list(DB.lists.find())
return render_template( return htmlify(
"following.html", render_template(
following_data=following, "following.html",
older_than=older_than, following_data=following,
newer_than=newer_than, older_than=older_than,
lists=lists, newer_than=newer_than,
lists=lists,
)
) )
@ -873,18 +884,20 @@ def tags(tag):
): ):
abort(404) abort(404)
if not is_api_request(): if not is_api_request():
return render_template( return htmlify(
"tags.html", render_template(
tag=tag, "tags.html",
outbox_data=DB.activities.find( tag=tag,
{ outbox_data=DB.activities.find(
"box": Box.OUTBOX.value, {
"type": ActivityType.CREATE.value, "box": Box.OUTBOX.value,
"meta.deleted": False, "type": ActivityType.CREATE.value,
"activity.object.tag.type": "Hashtag", "meta.deleted": False,
"activity.object.tag.name": "#" + tag, "activity.object.tag.type": "Hashtag",
} "activity.object.tag.name": "#" + tag,
), }
),
)
) )
_log_sig() _log_sig()
q = { q = {
@ -895,7 +908,7 @@ def tags(tag):
"activity.object.tag.type": "Hashtag", "activity.object.tag.type": "Hashtag",
"activity.object.tag.name": "#" + tag, "activity.object.tag.name": "#" + tag,
} }
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,
@ -920,7 +933,9 @@ def featured():
"meta.pinned": True, "meta.pinned": True,
} }
data = [clean_activity(doc["activity"]["object"]) for doc in DB.activities.find(q)] data = [clean_activity(doc["activity"]["object"]) for doc in DB.activities.find(q)]
return jsonify(**activitypub.simple_build_ordered_collection("featured", data)) return activitypubify(
**activitypub.simple_build_ordered_collection("featured", data)
)
@app.route("/liked") @app.route("/liked")
@ -936,12 +951,14 @@ def liked():
liked, older_than, newer_than = paginated_query(DB.activities, q) liked, older_than, newer_than = paginated_query(DB.activities, q)
return render_template( return htmlify(
"liked.html", liked=liked, older_than=older_than, newer_than=newer_than render_template(
"liked.html", liked=liked, older_than=older_than, newer_than=newer_than
)
) )
q = {"meta.deleted": False, "meta.undo": False, "type": ActivityType.LIKE.value} q = {"meta.deleted": False, "meta.undo": False, "type": ActivityType.LIKE.value}
return jsonify( return activitypubify(
**activitypub.build_ordered_collection( **activitypub.build_ordered_collection(
DB.activities, DB.activities,
q=q, q=q,

View file

@ -32,6 +32,7 @@ from core.shared import MY_PERSON
from core.shared import _build_thread from core.shared import _build_thread
from core.shared import _Response from core.shared import _Response
from core.shared import csrf from core.shared import csrf
from core.shared import htmlify
from core.shared import login_required from core.shared import login_required
from core.shared import noindex from core.shared import noindex
from core.shared import p from core.shared import p
@ -113,7 +114,9 @@ def admin_login() -> _Response:
payload = u2f.begin_authentication(ID, devices) payload = u2f.begin_authentication(ID, devices)
session["challenge"] = payload session["challenge"] = payload
return render_template("login.html", u2f_enabled=u2f_enabled, payload=payload) return htmlify(
render_template("login.html", u2f_enabled=u2f_enabled, payload=payload)
)
@blueprint.route("/admin", methods=["GET"]) @blueprint.route("/admin", methods=["GET"])
@ -127,47 +130,53 @@ def admin_index() -> _Response:
} }
col_liked = DB.activities.count(q) col_liked = DB.activities.count(q)
return render_template( return htmlify(
"admin.html", render_template(
instances=list(DB.instances.find()), "admin.html",
inbox_size=DB.activities.count({"box": Box.INBOX.value}), instances=list(DB.instances.find()),
outbox_size=DB.activities.count({"box": Box.OUTBOX.value}), inbox_size=DB.activities.count({"box": Box.INBOX.value}),
col_liked=col_liked, outbox_size=DB.activities.count({"box": Box.OUTBOX.value}),
col_followers=DB.activities.count( col_liked=col_liked,
{ col_followers=DB.activities.count(
"box": Box.INBOX.value, {
"type": ap.ActivityType.FOLLOW.value, "box": Box.INBOX.value,
"meta.undo": False, "type": ap.ActivityType.FOLLOW.value,
} "meta.undo": False,
), }
col_following=DB.activities.count( ),
{ col_following=DB.activities.count(
"box": Box.OUTBOX.value, {
"type": ap.ActivityType.FOLLOW.value, "box": Box.OUTBOX.value,
"meta.undo": False, "type": ap.ActivityType.FOLLOW.value,
} "meta.undo": False,
), }
),
)
) )
@blueprint.route("/admin/indieauth", methods=["GET"]) @blueprint.route("/admin/indieauth", methods=["GET"])
@login_required @login_required
def admin_indieauth() -> _Response: def admin_indieauth() -> _Response:
return render_template( return htmlify(
"admin_indieauth.html", render_template(
indieauth_actions=DB.indieauth.find().sort("ts", -1).limit(100), "admin_indieauth.html",
indieauth_actions=DB.indieauth.find().sort("ts", -1).limit(100),
)
) )
@blueprint.route("/admin/tasks", methods=["GET"]) @blueprint.route("/admin/tasks", methods=["GET"])
@login_required @login_required
def admin_tasks() -> _Response: def admin_tasks() -> _Response:
return render_template( return htmlify(
"admin_tasks.html", render_template(
success=p.get_success(), "admin_tasks.html",
dead=p.get_dead(), success=p.get_success(),
waiting=p.get_waiting(), dead=p.get_dead(),
cron=p.get_cron(), waiting=p.get_waiting(),
cron=p.get_cron(),
)
) )
@ -191,8 +200,10 @@ def admin_lookup() -> _Response:
print(data) print(data)
app.logger.debug(data.to_dict()) app.logger.debug(data.to_dict())
return render_template( return htmlify(
"lookup.html", data=data, meta=meta, url=request.args.get("url") render_template(
"lookup.html", data=data, meta=meta, url=request.args.get("url")
)
) )
@ -214,7 +225,7 @@ def admin_thread() -> _Response:
tpl = "note.html" tpl = "note.html"
if request.args.get("debug"): if request.args.get("debug"):
tpl = "note_debug.html" tpl = "note_debug.html"
return render_template(tpl, thread=thread, note=data) return htmlify(render_template(tpl, thread=thread, note=data))
@blueprint.route("/admin/new", methods=["GET"]) @blueprint.route("/admin/new", methods=["GET"])
@ -246,14 +257,16 @@ def admin_new() -> _Response:
content = f"@{actor.preferredUsername}@{domain} " content = f"@{actor.preferredUsername}@{domain} "
thread = _build_thread(data) thread = _build_thread(data)
return render_template( return htmlify(
"new.html", render_template(
reply=reply_id, "new.html",
content=content, reply=reply_id,
thread=thread, content=content,
visibility=ap.Visibility, thread=thread,
emojis=config.EMOJIS.split(" "), visibility=ap.Visibility,
custom_emojis=EMOJIS_BY_NAME, emojis=config.EMOJIS.split(" "),
custom_emojis=EMOJIS_BY_NAME,
)
) )
@ -262,7 +275,7 @@ def admin_new() -> _Response:
def admin_lists() -> _Response: def admin_lists() -> _Response:
lists = list(DB.lists.find()) lists = list(DB.lists.find())
return render_template("lists.html", lists=lists) return htmlify(render_template("lists.html", lists=lists))
@blueprint.route("/admin/notifications") @blueprint.route("/admin/notifications")
@ -341,12 +354,14 @@ def admin_notifications() -> _Response:
inbox_data, reverse=True, key=lambda doc: doc["_id"].generation_time inbox_data, reverse=True, key=lambda doc: doc["_id"].generation_time
) )
return render_template( return htmlify(
"stream.html", render_template(
inbox_data=inbox_data, "stream.html",
older_than=older_than, inbox_data=inbox_data,
newer_than=newer_than, older_than=older_than,
nid=nid, newer_than=newer_than,
nid=nid,
)
) )
@ -365,8 +380,10 @@ def admin_stream() -> _Response:
DB.activities, q, limit=int(request.args.get("limit", 25)) DB.activities, q, limit=int(request.args.get("limit", 25))
) )
return render_template( return htmlify(
tpl, inbox_data=inbox_data, older_than=older_than, newer_than=newer_than render_template(
tpl, inbox_data=inbox_data, older_than=older_than, newer_than=newer_than
)
) )
@ -393,8 +410,10 @@ def admin_list(name: str) -> _Response:
DB.activities, q, limit=int(request.args.get("limit", 25)) DB.activities, q, limit=int(request.args.get("limit", 25))
) )
return render_template( return htmlify(
tpl, inbox_data=inbox_data, older_than=older_than, newer_than=newer_than render_template(
tpl, inbox_data=inbox_data, older_than=older_than, newer_than=newer_than
)
) )
@ -413,8 +432,10 @@ def admin_bookmarks() -> _Response:
DB.activities, q, limit=int(request.args.get("limit", 25)) DB.activities, q, limit=int(request.args.get("limit", 25))
) )
return render_template( return htmlify(
tpl, inbox_data=inbox_data, older_than=older_than, newer_than=newer_than render_template(
tpl, inbox_data=inbox_data, older_than=older_than, newer_than=newer_than
)
) )
@ -425,7 +446,7 @@ def u2f_register():
if request.method == "GET": if request.method == "GET":
payload = u2f.begin_registration(ID) payload = u2f.begin_registration(ID)
session["challenge"] = payload session["challenge"] = payload
return render_template("u2f.html", payload=payload) return htmlify(render_template("u2f.html", payload=payload))
else: else:
resp = json.loads(request.form.get("resp")) resp = json.loads(request.form.get("resp"))
device, device_cert = u2f.complete_registration(session["challenge"], resp) device, device_cert = u2f.complete_registration(session["challenge"], resp)
@ -439,8 +460,10 @@ def u2f_register():
@login_required @login_required
def authorize_follow(): def authorize_follow():
if request.method == "GET": if request.method == "GET":
return render_template( return htmlify(
"authorize_remote_follow.html", profile=request.args.get("profile") render_template(
"authorize_remote_follow.html", profile=request.args.get("profile")
)
) )
actor = get_actor_url(request.form.get("profile")) actor = get_actor_url(request.form.get("profile"))

View file

@ -19,6 +19,7 @@ from itsdangerous import BadSignature
from config import DB from config import DB
from config import JWT from config import JWT
from core.shared import _get_ip from core.shared import _get_ip
from core.shared import htmlify
from core.shared import login_required from core.shared import login_required
blueprint = flask.Blueprint("indieauth", __name__) blueprint = flask.Blueprint("indieauth", __name__)
@ -105,15 +106,17 @@ def indieauth_endpoint():
scope = request.args.get("scope", "").split() scope = request.args.get("scope", "").split()
print("STATE", state) print("STATE", state)
return render_template( return htmlify(
"indieauth_flow.html", render_template(
client=get_client_id_data(client_id), "indieauth_flow.html",
scopes=scope, client=get_client_id_data(client_id),
redirect_uri=redirect_uri, scopes=scope,
state=state, redirect_uri=redirect_uri,
response_type=response_type, state=state,
client_id=client_id, response_type=response_type,
me=me, client_id=client_id,
me=me,
)
) )
# Auth verification via POST # Auth verification via POST

View file

@ -554,10 +554,14 @@ def task_process_reply() -> _Response:
root_reply = in_reply_to root_reply = in_reply_to
# Fetch the activity reply
reply = ap.fetch_remote_activity(in_reply_to) reply = ap.fetch_remote_activity(in_reply_to)
if reply.has_type(ap.ActivityType.CREATE): if reply.has_type(ap.ActivityType.CREATE):
reply = reply.get_object() reply = reply.get_object()
# Store some metadata for the UI
# FIXME(tsileo): be able to display: "In reply to @user@domain.tld"?
new_replies = [activity, reply] new_replies = [activity, reply]
while 1: while 1:

View file

@ -8,7 +8,7 @@ from pathlib import Path
import yaml import yaml
from itsdangerous import JSONWebSignatureSerializer from itsdangerous import JSONWebSignatureSerializer
from little_boxes import strtobool from little_boxes import strtobool
from little_boxes.activitypub import DEFAULT_CTX as AP_DEFAULT_CTX from little_boxes.activitypub import CTX_AS as AP_DEFAULT_CTX
from pymongo import MongoClient from pymongo import MongoClient
import sass import sass

View file

@ -263,6 +263,9 @@ def post_to_outbox(activity: ap.BaseActivity) -> str:
class MicroblogPubBackend(Backend): class MicroblogPubBackend(Backend):
"""Implements a Little Boxes backend, backed by MongoDB.""" """Implements a Little Boxes backend, backed by MongoDB."""
def ap_context(self) -> Any:
return DEFAULT_CTX
def base_url(self) -> str: def base_url(self) -> str:
return BASE_URL return BASE_URL

View file

@ -59,7 +59,19 @@ def build_resp(resp):
return resp, headers return resp, headers
def jsonify(**data): def htmlify(data):
resp, headers = build_resp(data)
return Response(
response=resp,
headers={
**headers,
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "max-age=0, private, must-revalidate",
},
)
def activitypubify(**data):
if "@context" not in data: if "@context" not in data:
data["@context"] = config.DEFAULT_CTX data["@context"] = config.DEFAULT_CTX
resp, headers = build_resp(json.dumps(data)) resp, headers = build_resp(json.dumps(data))
@ -68,9 +80,7 @@ def jsonify(**data):
headers={ headers={
**headers, **headers,
"Cache-Control": "max-age=0, private, must-revalidate", "Cache-Control": "max-age=0, private, must-revalidate",
"Content-Type": "application/json" "Content-Type": "application/activity+json",
if app.debug
else "application/activity+json",
}, },
) )