diff --git a/app/micropub.py b/app/micropub.py index 6d96b0a..e377e02 100644 --- a/app/micropub.py +++ b/app/micropub.py @@ -5,6 +5,7 @@ from fastapi import Depends from fastapi import Request from fastapi.responses import JSONResponse from fastapi.responses import RedirectResponse +from loguru import logger from app import activitypub as ap from app.boxes import get_outbox_object_by_ap_id @@ -55,6 +56,14 @@ async def micropub_endpoint( return {} +def _prop_get(dat: dict[str, Any], key: str) -> str: + val = dat[key] + if isinstance(val, list): + return val[0] + else: + return val + + @router.post("/micropub") async def post_micropub_endpoint( request: Request, @@ -62,8 +71,17 @@ async def post_micropub_endpoint( db_session: AsyncSession = Depends(get_db_session), ) -> RedirectResponse | JSONResponse: form_data = await request.form() + is_json = False + if not form_data: + form_data = await request.json() + is_json = True + + insufficient_scope_resp = JSONResponse( + status_code=401, content={"error": "insufficient_scope"} + ) + if "action" in form_data: - if form_data["action"] == "delete": + if form_data["action"] in ["delete", "update"]: outbox_object = await get_outbox_object_by_ap_id( db_session, form_data["url"] ) @@ -75,14 +93,48 @@ async def post_micropub_endpoint( }, status_code=400, ) - await send_delete(db_session, outbox_object.ap_id) # type: ignore - return JSONResponse(content={}, status_code=200) - h = "entry" - if "h" in form_data: - h = form_data["h"] + if form_data["action"] == "delete": + if "delete" not in access_token_info.scopes: + return insufficient_scope_resp + logger.info(f"Deleting object {outbox_object.ap_id}") + await send_delete(db_session, outbox_object.ap_id) # type: ignore + return JSONResponse(content={}, status_code=200) - if h != "entry": + elif form_data["action"] == "update": + if "update" not in access_token_info.scopes: + return insufficient_scope_resp + + # TODO(ts): support update + # "replace": {"content": ["new content"]} + + logger.info(f"Updating object {outbox_object.ap_id}: {form_data}") + return JSONResponse(content={}, status_code=200) + else: + raise ValueError("Should never happen") + else: + return JSONResponse( + content={ + "error": "invalid_request", + "error_description": f'Unsupported action: {form_data["action"]}', + }, + status_code=400, + ) + + if "create" not in access_token_info.scopes: + return insufficient_scope_resp + + if is_json: + entry_type = _prop_get(form_data, "type") # type: ignore + else: + h = "entry" + if "h" in form_data: + h = form_data["h"] + entry_type = f"h-{h}" + + logger.info(f"Creating {entry_type}") + + if entry_type != "h-entry": return JSONResponse( content={ "error": "invalid_request", @@ -91,7 +143,13 @@ async def post_micropub_endpoint( status_code=400, ) - content = form_data["content"] + # TODO(ts): support creating Article (with a name) + + if is_json: + content = _prop_get(form_data["properties"], "content") # type: ignore + else: + content = form_data["content"] + public_id = await send_create( db_session, content, diff --git a/app/templates/indieauth_flow.html b/app/templates/indieauth_flow.html index a1ebe9b..6af8071 100644 --- a/app/templates/indieauth_flow.html +++ b/app/templates/indieauth_flow.html @@ -11,7 +11,7 @@
wants you to login as {{ me }}
+wants you to login as {{ me }} with the following redirect URI: {{ redirect_uri }}
.