Add basic Micropub support (only create support for now)

Fixes #11
This commit is contained in:
Thomas Sileo 2019-10-25 22:39:44 +02:00
parent afa33b4e7c
commit 126e5e8d59
2 changed files with 67 additions and 18 deletions

View file

@ -77,6 +77,7 @@ def _api_required() -> None:
# Will raise a BadSignature on bad auth # Will raise a BadSignature on bad auth
payload = JWT.loads(token) payload = JWT.loads(token)
flask.g.jwt_payload = payload
app.logger.info(f"api call by {payload}") app.logger.info(f"api call by {payload}")
@ -438,13 +439,50 @@ def api_remove_from_list() -> _Response:
return _user_api_response() return _user_api_response()
@blueprint.route("/new_note", methods=["POST"]) @blueprint.route("/new_note", methods=["POST"]) # noqa: C901 too complex
@api_required @api_required
def api_new_note() -> _Response: def api_new_note() -> _Response:
source = _user_api_arg("content") source = None
summary = None
# Basic Micropub (https://www.w3.org/TR/micropub/) "create" support
is_micropub = False
# First, check if the Micropub specific fields are present
if (
_user_api_arg("h", default=None) == "entry"
or _user_api_arg("type", default=[None])[0] == "h-entry"
):
is_micropub = True
# Ensure the "create" scope is set
if "jwt_payload" not in flask.g or "create" not in flask.g.jwt_payload["scope"]:
abort(403)
# Handle JSON microformats2 data
if _user_api_arg("type", default=None):
try:
source = request.json["properties"]["content"][0]
except (ValueError, KeyError):
pass
try:
summary = request.json["properties"]["name"][0]
except (ValueError, KeyError):
pass
# Try to parse the name as summary if the payload is POSTed using form-data
if summary is None:
summary = _user_api_arg("name", default=None)
# This step will also parse content from Micropub request
if source is None:
source = _user_api_arg("content", default=None)
if not source: if not source:
raise ValueError("missing content") raise ValueError("missing content")
if summary is None:
summary = _user_api_arg("summary", default="")
# All the following fields are specific to the API (i.e. not Micropub related)
_reply, reply = None, None _reply, reply = None, None
try: try:
_reply = _user_api_arg("reply") _reply = _user_api_arg("reply")
@ -490,7 +528,7 @@ def api_new_note() -> _Response:
attributedTo=MY_PERSON.id, attributedTo=MY_PERSON.id,
cc=list(set(cc)), cc=list(set(cc)),
to=list(set(to)), to=list(set(to)),
summary=_user_api_arg("summary", default=""), summary=summary,
content=content, content=content,
tag=tags, tag=tags,
source={"mediaType": "text/markdown", "content": source}, source={"mediaType": "text/markdown", "content": source},
@ -498,27 +536,37 @@ def api_new_note() -> _Response:
context=context, context=context,
) )
if "file" in request.files and request.files["file"].filename: if request.files:
file = request.files["file"] for f in request.files.keys():
rfilename = secure_filename(file.filename) if not request.files[f].filename:
with BytesIO() as buf: continue
file.save(buf)
oid = MEDIA_CACHE.save_upload(buf, rfilename)
mtype = mimetypes.guess_type(rfilename)[0]
raw_note["attachment"] = [ file = request.files[f]
{ rfilename = secure_filename(file.filename)
"mediaType": mtype, with BytesIO() as buf:
"name": _user_api_arg("file_description", default=rfilename), file.save(buf)
"type": "Document", oid = MEDIA_CACHE.save_upload(buf, rfilename)
"url": f"{BASE_URL}/uploads/{oid}/{rfilename}", mtype = mimetypes.guess_type(rfilename)[0]
}
] raw_note["attachment"] = [
{
"mediaType": mtype,
"name": _user_api_arg("file_description", default=rfilename),
"type": "Document",
"url": f"{BASE_URL}/uploads/{oid}/{rfilename}",
}
]
note = ap.Note(**raw_note) note = ap.Note(**raw_note)
create = note.build_create() create = note.build_create()
create_id = post_to_outbox(create) create_id = post_to_outbox(create)
# Return a 201 with the note URL in the Location header if this was a Micropub request
if is_micropub:
resp = flask.Response("", headers={"Location": create_id})
resp.status_code = 201
return resp
return _user_api_response(activity=create_id) return _user_api_response(activity=create_id)

View file

@ -8,6 +8,7 @@
<link rel="stylesheet" href="/static/pure.css"> <link rel="stylesheet" href="/static/pure.css">
<link rel="authorization_endpoint" href="{{ config.ID }}/indieauth"> <link rel="authorization_endpoint" href="{{ config.ID }}/indieauth">
<link rel="token_endpoint" href="{{ config.ID }}/token"> <link rel="token_endpoint" href="{{ config.ID }}/token">
<link rel="micropub" href="{{config.ID}}/api/new_note">
{% if not request.args.get("older_than") and not request.args.get("previous_than") %}<link rel="canonical" href="https://{{ config.DOMAIN }}{{ request.path }}">{% endif %} {% if not request.args.get("older_than") and not request.args.get("previous_than") %}<link rel="canonical" href="https://{{ config.DOMAIN }}{{ request.path }}">{% endif %}
{% block links %}{% endblock %} {% block links %}{% endblock %}
{% if config.THEME_COLOR %}<meta name="theme-color" content="{{ config.THEME_COLOR }}">{% endif %} {% if config.THEME_COLOR %}<meta name="theme-color" content="{{ config.THEME_COLOR }}">{% endif %}