From a3df7d6d301ec18569ed5c5dc9d763b7f3799ed3 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Wed, 24 May 2023 16:09:44 -0700 Subject: [PATCH] make /render redirect to /convert/... #512 --- app.py | 2 +- convert.py | 7 ++ render.py | 46 ------------ tests/test_convert.py | 6 ++ tests/test_render.py | 165 ------------------------------------------ 5 files changed, 14 insertions(+), 212 deletions(-) delete mode 100644 render.py delete mode 100644 tests/test_render.py diff --git a/app.py b/app.py index 3cf62cf..e09d964 100644 --- a/app.py +++ b/app.py @@ -6,4 +6,4 @@ registered. from flask_app import app # import all modules to register their Flask handlers -import activitypub, convert, follow, pages, redirect, render, superfeedr, webfinger, webmention, xrpc_actor, xrpc_feed, xrpc_graph +import activitypub, convert, follow, pages, redirect, superfeedr, webfinger, webmention, xrpc_actor, xrpc_feed, xrpc_graph diff --git a/convert.py b/convert.py index a670268..4e221ff 100644 --- a/convert.py +++ b/convert.py @@ -73,3 +73,10 @@ def convert(src, dest, _): # convert and serve return protocols[dest].serve(obj) + + +@app.get('/render') +def render_redirect(): + """Redirect from old /render?id=... endpoint to /convert/...""" + id = flask_util.get_required_param('id') + return redirect(f'/convert/activitypub/webmention/{id}', code=301) diff --git a/render.py b/render.py deleted file mode 100644 index 4abec59..0000000 --- a/render.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Renders mf2 proxy pages based on stored Object entities.""" -import datetime -import logging -from urllib.parse import urlencode - -from flask import redirect, request -from granary import as1, as2, atom, microformats2 -from oauth_dropins.webutil import flask_util -from oauth_dropins.webutil.flask_util import error -from oauth_dropins.webutil import util - -import activitypub -from flask_app import app, cache -import common -from models import Object -from webmention import Webmention - -logger = logging.getLogger(__name__) - - -@app.get('/render') -@flask_util.cached(cache, common.CACHE_TIME) -def render(): - """Fetches a stored Object and renders it as HTML.""" - id = flask_util.get_required_param('id') - obj = Object.get_by_id(id) - if not obj: - error(f'No stored object for {id}', status=404) - elif not obj.as1: - error(f'Stored object for {id} has no AS1', status=404) - - # redirect creates, updates, etc to inner object - type = as1.object_type(obj.as1) - if type in ('post', 'update', 'delete'): - obj_id = as1.get_object(obj.as1).get('id') - if obj_id: - obj_obj = Object.get_by_id(obj_id) - if (obj_obj and obj_obj.as1 and - not obj_obj.as1.keys() <= set(['id', 'url', 'objectType'])): - logger.info(f'{type} activity, redirecting to Object {obj_id}') - return redirect('/render?' + urlencode({'id': obj_id}), code=301) - - if obj.deleted or type == 'delete': - return '', 410 - - return Webmention.serve(obj) diff --git a/tests/test_convert.py b/tests/test_convert.py index a4b3b97..2ec64e1 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -206,3 +206,9 @@ A ☕ reply """, resp.get_data(as_text=True), ignore_blanks=True) + + def test_render_endpoint_redirect(self): + resp = self.client.get('/render?id=http://foo%3Fbar') + self.assertEqual(301, resp.status_code) + self.assertEqual(f'/convert/activitypub/webmention/http://foo?bar', + resp.headers['Location']) diff --git a/tests/test_render.py b/tests/test_render.py deleted file mode 100644 index 809c3c0..0000000 --- a/tests/test_render.py +++ /dev/null @@ -1,165 +0,0 @@ -"""Unit tests for render.py.""" -import copy - -from granary import as2 -from granary.tests.test_as1 import ACTOR, COMMENT, DELETE_OF_ID, UPDATE - -from flask_app import app -import common -from models import Object -import render -from . import testutil - -EXPECTED_HTML = """\ - - - - - -
- tag:fake.com:123456 - - fake.com/123456 -
- A ☕ reply -
- -
- - -""" -EXPECTED_AUTHOR_HTML = """\ - - - - - -
- tag:fake.com:123456 - - - - Bob - - - fake.com/123456 -
- A ☕ reply -
- -
- - -""" - -class RenderTest(testutil.TestCase): - - def test_render_errors(self): - resp = self.client.get(f'/render?id=') - self.assertEqual(400, resp.status_code) - - resp = self.client.get(f'/render') - self.assertEqual(400, resp.status_code) - - # no Object - resp = self.client.get('/render?id=abc') - self.assertEqual(404, resp.status_code) - - def test_render(self): - with self.request_context: - Object(id='abc', as2=as2.from_as1(COMMENT)).put() - resp = self.client.get('/render?id=abc') - self.assertEqual(200, resp.status_code) - self.assert_multiline_equals(EXPECTED_HTML, resp.get_data(as_text=True), ignore_blanks=True) - - def test_render_with_author(self): - with self.request_context: - Object(id='abc', as2=as2.from_as1({**COMMENT, 'author': 'def'}), - source_protocol='activitypub').put() - Object(id='def', as2=as2.from_as1(ACTOR), - source_protocol='activitypub').put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(200, resp.status_code) - self.assert_multiline_equals( - EXPECTED_AUTHOR_HTML, resp.get_data(as_text=True), ignore_blanks=True) - - def test_render_no_url(self): - comment = copy.deepcopy(COMMENT) - del comment['url'] - with self.request_context: - Object(id='abc', as2=as2.from_as1(comment)).put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(200, resp.status_code) - expected = EXPECTED_HTML.replace( - '\n', '' - ).replace('fake.com/123456', '') - self.assert_multiline_equals(expected, resp.get_data(as_text=True), - ignore_blanks=True) - - def test_render_deleted_object(self): - with self.request_context: - Object(id='abc', as2={'content': 'foo'}, deleted=True).put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(410, resp.status_code) - - def test_render_delete_activity(self): - with self.request_context: - Object(id='abc', as2=as2.from_as1(DELETE_OF_ID)).put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(410, resp.status_code) - - def test_render_update_inner_obj_exists_redirect(self): - with self.request_context: - # UPDATE's object field is a full object - Object(id='abc', as2=as2.from_as1(UPDATE)).put() - Object(id=UPDATE['object']['id'], as2={'content': 'foo'}).put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(301, resp.status_code) - self.assertEqual(f'/render?id=tag%3Afake.com%3A123456', - resp.headers['Location']) - - def test_render_delete_inner_obj_exists_redirect(self): - with self.request_context: - # DELETE_OF_ID's object field is a bare string id - Object(id='abc', as2=as2.from_as1(DELETE_OF_ID)).put() - Object(id=DELETE_OF_ID['object'], as2={'content': 'foo'}).put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(301, resp.status_code) - self.assertEqual(f'/render?id=tag%3Afake.com%3A123456', - resp.headers['Location']) - - def test_render_update_no_inner_obj_serve_as_is(self): - with self.request_context: - # UPDATE's object field is a full object - Object(id='abc', as2=as2.from_as1(UPDATE)).put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(200, resp.status_code) - self.assert_multiline_in("""\ -
-A ☕ reply -
- -""", resp.get_data(as_text=True), ignore_blanks=True) - - def test_render_update_inner_obj_too_minimal_serve_as_is(self): - with self.request_context: - # UPDATE's object field is a full object - Object(id='abc', as2=as2.from_as1(UPDATE)).put() - Object(id=UPDATE['object']['id'], as2={'id': 'foo'}).put() - - resp = self.client.get('/render?id=abc') - self.assertEqual(200, resp.status_code) - self.assert_multiline_in("""\ -
-A ☕ reply -
- -""", resp.get_data(as_text=True), ignore_blanks=True) -