diff --git a/app.py b/app.py index b0cb623..a3938a7 100644 --- a/app.py +++ b/app.py @@ -14,7 +14,6 @@ from flask import Flask from flask import Response from flask import abort from flask import g -from flask import jsonify as flask_jsonify from flask import redirect from flask import render_template from flask import request @@ -67,6 +66,7 @@ from core.shared import activitypubify from core.shared import csrf from core.shared import htmlify from core.shared import is_api_request +from core.shared import jsonify from core.shared import login_required from core.shared import noindex from core.shared import paginated_query @@ -173,7 +173,7 @@ def handle_value_error(error): logger.error( f"caught value error for {g.request_id}: {error!r}, {traceback.format_tb(error.__traceback__)}" ) - response = flask_jsonify(message=error.args[0], request_id=g.request_id) + response = jsonify({"message": error.args[0], "request_id": g.request_id}) response.status_code = 400 return response @@ -183,7 +183,7 @@ def handle_activitypub_error(error): logger.error( f"caught activitypub error for {g.request_id}: {error!r}, {traceback.format_tb(error.__traceback__)}" ) - response = flask_jsonify({**error.to_dict(), "request_id": g.request_id}) + response = jsonify({**error.to_dict(), "request_id": g.request_id}) response.status_code = error.status_code return response @@ -193,7 +193,7 @@ def handle_task_error(error): logger.error( f"caught activitypub error for {g.request_id}: {error!r}, {traceback.format_tb(error.__traceback__)}" ) - response = flask_jsonify({"traceback": error.message, "request_id": g.request_id}) + response = jsonify({"traceback": error.message, "request_id": g.request_id}) response.status_code = 500 return response diff --git a/blueprints/api.py b/blueprints/api.py index 51a5f39..e34a9bc 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -1,4 +1,3 @@ -import json import mimetypes from datetime import datetime from datetime import timedelta @@ -10,7 +9,6 @@ from typing import List import flask from bson.objectid import ObjectId -from flask import Response from flask import abort from flask import current_app as app from flask import redirect @@ -41,6 +39,7 @@ from core.meta import _meta from core.shared import MY_PERSON from core.shared import _Response from core.shared import csrf +from core.shared import jsonify from core.shared import login_required from core.tasks import Tasks from utils import emojis @@ -124,7 +123,7 @@ def _user_api_response(**kwargs) -> _Response: if _redirect: return redirect(_redirect) - resp = flask.jsonify(**kwargs) + resp = jsonify(kwargs) resp.status_code = 201 return resp @@ -132,7 +131,7 @@ def _user_api_response(**kwargs) -> _Response: @blueprint.route("/api/key") @login_required def api_user_key() -> _Response: - return flask.jsonify(api_key=ADMIN_API_KEY) + return jsonify({"api_key": ADMIN_API_KEY}) @blueprint.route("/note/delete", methods=["POST"]) @@ -575,25 +574,24 @@ def api_follow() -> _Response: def api_debug() -> _Response: """Endpoint used/needed for testing, only works in DEBUG_MODE.""" if not DEBUG_MODE: - return flask.jsonify(message="DEBUG_MODE is off") + return jsonify({"message": "DEBUG_MODE is off"}) if request.method == "DELETE": _drop_db() - return flask.jsonify(message="DB dropped") + return jsonify(dict(message="DB dropped")) - return flask.jsonify( - inbox=DB.activities.count({"box": Box.INBOX.value}), - outbox=DB.activities.count({"box": Box.OUTBOX.value}), - outbox_data=without_id(DB.activities.find({"box": Box.OUTBOX.value})), + return jsonify( + dict( + inbox=DB.activities.count({"box": Box.INBOX.value}), + outbox=DB.activities.count({"box": Box.OUTBOX.value}), + outbox_data=without_id(DB.activities.find({"box": Box.OUTBOX.value})), + ) ) @blueprint.route("/stream") @api_required def api_stream() -> _Response: - return Response( - response=json.dumps( - feed.build_inbox_json_feed("/api/stream", request.args.get("cursor")) - ), - headers={"Content-Type": "application/json"}, + return jsonify( + feed.build_inbox_json_feed("/api/stream", request.args.get("cursor")) ) diff --git a/blueprints/indieauth.py b/blueprints/indieauth.py index 08188e5..76ee66c 100644 --- a/blueprints/indieauth.py +++ b/blueprints/indieauth.py @@ -1,5 +1,4 @@ import binascii -import json import os from datetime import datetime from datetime import timedelta @@ -20,6 +19,7 @@ from config import DB from config import JWT from core.shared import _get_ip from core.shared import htmlify +from core.shared import jsonify from core.shared import login_required blueprint = flask.Blueprint("indieauth", __name__) @@ -27,11 +27,7 @@ blueprint = flask.Blueprint("indieauth", __name__) def build_auth_resp(payload): if request.headers.get("Accept") == "application/json": - return Response( - status=200, - headers={"Content-Type": "application/json"}, - response=json.dumps(payload), - ) + return jsonify(payload) return Response( status=200, headers={"Content-Type": "application/x-www-form-urlencoded"}, diff --git a/blueprints/well_known.py b/blueprints/well_known.py index 1f936c0..bf152f2 100644 --- a/blueprints/well_known.py +++ b/blueprints/well_known.py @@ -1,9 +1,7 @@ -import json import mimetypes from typing import Any import flask -from flask import Response from flask import abort from flask import request from little_boxes import activitypub as ap @@ -11,6 +9,7 @@ from little_boxes import activitypub as ap import config from config import DB from core.meta import Box +from core.shared import jsonify blueprint = flask.Blueprint("well_known", __name__) @@ -45,22 +44,21 @@ def wellknown_webfinger() -> Any: ], } - return Response( - response=json.dumps(out), - headers={"Content-Type": "application/jrd+json; charset=utf-8"}, - ) + return jsonify(out, "application/jrd+json; charset=utf-8") @blueprint.route("/.well-known/nodeinfo") def wellknown_nodeinfo() -> Any: """Exposes the NodeInfo endpoint (http://nodeinfo.diaspora.software/).""" - return flask.jsonify( - links=[ - { - "rel": "http://nodeinfo.diaspora.software/ns/schema/2.1", - "href": f"{config.ID}/nodeinfo", - } - ] + return jsonify( + { + "links": [ + { + "rel": "http://nodeinfo.diaspora.software/ns/schema/2.1", + "href": f"{config.ID}/nodeinfo", + } + ] + } ) @@ -73,29 +71,25 @@ def nodeinfo() -> Any: "type": {"$in": [ap.ActivityType.CREATE.value, ap.ActivityType.ANNOUNCE.value]}, } - response = json.dumps( - { - "version": "2.1", - "software": { - "name": "microblogpub", - "version": config.VERSION, - "repository": "https://github.com/tsileo/microblog.pub", - }, - "protocols": ["activitypub"], - "services": {"inbound": [], "outbound": []}, - "openRegistrations": False, - "usage": {"users": {"total": 1}, "localPosts": DB.activities.count(q)}, - "metadata": { - "nodeName": f"@{config.USERNAME}@{config.DOMAIN}", - "version": config.VERSION, - "versionDate": config.VERSION_DATE, - }, - } - ) - - return Response( - headers={ - "Content-Type": "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#" + out = { + "version": "2.1", + "software": { + "name": "microblogpub", + "version": config.VERSION, + "repository": "https://github.com/tsileo/microblog.pub", }, - response=response, + "protocols": ["activitypub"], + "services": {"inbound": [], "outbound": []}, + "openRegistrations": False, + "usage": {"users": {"total": 1}, "localPosts": DB.activities.count(q)}, + "metadata": { + "nodeName": f"@{config.USERNAME}@{config.DOMAIN}", + "version": config.VERSION, + "versionDate": config.VERSION_DATE, + }, + } + + return jsonify( + out, + "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#", ) diff --git a/core/shared.py b/core/shared.py index 77702ad..f21676f 100644 --- a/core/shared.py +++ b/core/shared.py @@ -48,7 +48,7 @@ MY_PERSON = ap.Person(**ME) @lru_cache(512) def build_resp(resp): """Encode the response to gzip if supported by the client.""" - headers = {} + headers = {"Cache-Control": "max-age=0, private, must-revalidate"} accept_encoding = request.headers.get("Accept-Encoding", "") if "gzip" in accept_encoding.lower(): return ( @@ -59,15 +59,15 @@ def build_resp(resp): return resp, headers +def jsonify(data, content_type="application/json"): + resp, headers = build_resp(json.dumps(data)) + return Response(headers={**headers, "Content-Type": content_type}, response=resp) + + 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", - }, + response=resp, headers={**headers, "Content-Type": "text/html; charset=utf-8"} ) @@ -76,12 +76,7 @@ def activitypubify(**data): data["@context"] = config.DEFAULT_CTX resp, headers = build_resp(json.dumps(data)) return Response( - response=resp, - headers={ - **headers, - "Cache-Control": "max-age=0, private, must-revalidate", - "Content-Type": "application/activity+json", - }, + response=resp, headers={**headers, "Content-Type": "application/activity+json"} )