diff --git a/relay/misc.py b/relay/misc.py
index e71845d..296082b 100644
--- a/relay/misc.py
+++ b/relay/misc.py
@@ -5,12 +5,8 @@ import os
import socket
import typing
-from aiohttp.abc import AbstractView
-from aiohttp.hdrs import METH_ALL as METHODS
from aiohttp.web import Response as AiohttpResponse
-from aiohttp.web_exceptions import HTTPMethodNotAllowed
from aputils.message import Message as ApMessage
-from functools import cached_property
from uuid import uuid4
if typing.TYPE_CHECKING:
@@ -232,67 +228,3 @@ class Response(AiohttpResponse):
@location.setter
def location(self, value: str) -> None:
self.headers['Location'] = value
-
-
-class View(AbstractView):
- def __await__(self) -> Generator[Response]:
- if self.request.method not in METHODS:
- raise HTTPMethodNotAllowed(self.request.method, self.allowed_methods)
-
- if not (handler := self.handlers.get(self.request.method)):
- raise HTTPMethodNotAllowed(self.request.method, self.allowed_methods) from None
-
- return self._run_handler(handler).__await__()
-
-
- async def _run_handler(self, handler: Awaitable) -> Response:
- with self.database.config.connection_class(self.database) as conn:
- # todo: remove on next tinysql release
- conn.open()
-
- return await handler(self.request, conn, **self.request.match_info)
-
-
- @cached_property
- def allowed_methods(self) -> tuple[str]:
- return tuple(self.handlers.keys())
-
-
- @cached_property
- def handlers(self) -> dict[str, Coroutine]:
- data = {}
-
- for method in METHODS:
- try:
- data[method] = getattr(self, method.lower())
-
- except AttributeError:
- continue
-
- return data
-
-
- # app components
- @property
- def app(self) -> Application:
- return self.request.app
-
-
- @property
- def cache(self) -> Cache:
- return self.app.cache
-
-
- @property
- def client(self) -> HttpClient:
- return self.app.client
-
-
- @property
- def config(self) -> Config:
- return self.app.config
-
-
- @property
- def database(self) -> Database:
- return self.app.database
diff --git a/relay/views/__init__.py b/relay/views/__init__.py
new file mode 100644
index 0000000..85c2bd3
--- /dev/null
+++ b/relay/views/__init__.py
@@ -0,0 +1,4 @@
+from __future__ import annotations
+
+from . import activitypub, frontend, misc
+from .base import VIEWS
diff --git a/relay/views.py b/relay/views/activitypub.py
similarity index 65%
rename from relay/views.py
rename to relay/views/activitypub.py
index cb648a2..be51047 100644
--- a/relay/views.py
+++ b/relay/views/activitypub.py
@@ -1,95 +1,27 @@
from __future__ import annotations
-import subprocess
import traceback
import typing
from aputils.errors import SignatureFailureError
from aputils.misc import Digest, HttpDate, Signature
-from aputils.objects import Nodeinfo, Webfinger, WellKnownNodeinfo
-from pathlib import Path
+from aputils.objects import Webfinger
-from . import __version__
-from . import logger as logging
-from .database.connection import Connection
-from .misc import Message, Response, View
-from .processors import run_processor
+from .base import View, register_route
+
+from .. import logger as logging
+from ..misc import Message, Response
+from ..processors import run_processor
if typing.TYPE_CHECKING:
from aiohttp.web import Request
from aputils.signer import Signer
- from collections.abc import Callable
from tinysql import Row
-
-
-VIEWS = []
-VERSION = __version__
-HOME_TEMPLATE = """
-
- ActivityPub Relay at {host}
-
-
-
- This is an Activity Relay for fediverse instances.
- {note}
-
- You may subscribe to this relay with the address:
- https://{host}/actor
-
-
- To host your own relay, you may download the code at this address:
-
- https://git.pleroma.social/pleroma/relay
-
-
-
List of {count} registered instances:
{targets}
-
-"""
-
-
-if Path(__file__).parent.parent.joinpath('.git').exists():
- try:
- commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii')
- VERSION = f'{__version__} {commit_label}'
-
- except Exception:
- pass
-
-
-def register_route(*paths: str) -> Callable:
- def wrapper(view: View) -> View:
- for path in paths:
- VIEWS.append([path, view])
-
- return View
- return wrapper
+ from ..database.connection import Connection
# pylint: disable=unused-argument
-@register_route('/')
-class HomeView(View):
- async def get(self, request: Request, conn: Connection) -> Response:
- config = conn.get_config_all()
- inboxes = conn.execute('SELECT * FROM inboxes').all()
-
- text = HOME_TEMPLATE.format(
- host = self.config.domain,
- note = config['note'],
- count = len(inboxes),
- targets = '
'.join(inbox['domain'] for inbox in inboxes)
- )
-
- return Response.new(text, ctype='html')
-
-
-
@register_route('/actor', '/inbox')
class ActorView(View):
def __init__(self, request: Request):
@@ -247,31 +179,3 @@ class WebfingerView(View):
)
return Response.new(data, ctype = 'json')
-
-
-@register_route('/nodeinfo/{niversion:\\d.\\d}.json', '/nodeinfo/{niversion:\\d.\\d}')
-class NodeinfoView(View):
- # pylint: disable=no-self-use
- async def get(self, request: Request, conn: Connection, niversion: str) -> Response:
- inboxes = conn.execute('SELECT * FROM inboxes').all()
-
- data = {
- 'name': 'activityrelay',
- 'version': VERSION,
- 'protocols': ['activitypub'],
- 'open_regs': not conn.get_config('whitelist-enabled'),
- 'users': 1,
- 'metadata': {'peers': [inbox['domain'] for inbox in inboxes]}
- }
-
- if niversion == '2.1':
- data['repo'] = 'https://git.pleroma.social/pleroma/relay'
-
- return Response.new(Nodeinfo.new(**data), ctype = 'json')
-
-
-@register_route('/.well-known/nodeinfo')
-class WellknownNodeinfoView(View):
- async def get(self, request: Request, conn: Connection) -> Response:
- data = WellKnownNodeinfo.new_template(self.config.domain)
- return Response.new(data, ctype = 'json')
diff --git a/relay/views/base.py b/relay/views/base.py
new file mode 100644
index 0000000..95b6562
--- /dev/null
+++ b/relay/views/base.py
@@ -0,0 +1,93 @@
+from __future__ import annotations
+
+import typing
+
+from aiohttp.abc import AbstractView
+from aiohttp.hdrs import METH_ALL as METHODS
+from aiohttp.web import HTTPMethodNotAllowed
+from functools import cached_property
+
+if typing.TYPE_CHECKING:
+ from collections.abc import Callable, Coroutine, Generator
+ from tinysql import Database
+ from ..application import Application
+ from ..cache import Cache
+ from ..config import Config
+ from ..http_client import HttpClient
+ from ..misc import Response
+
+
+VIEWS = []
+
+
+def register_route(*paths: str) -> Callable:
+ def wrapper(view: View) -> View:
+ for path in paths:
+ VIEWS.append([path, view])
+
+ return View
+ return wrapper
+
+
+class View(AbstractView):
+ def __await__(self) -> Generator[Response]:
+ if self.request.method not in METHODS:
+ raise HTTPMethodNotAllowed(self.request.method, self.allowed_methods)
+
+ if not (handler := self.handlers.get(self.request.method)):
+ raise HTTPMethodNotAllowed(self.request.method, self.allowed_methods)
+
+ return self._run_handler(handler).__await__()
+
+
+ async def _run_handler(self, handler: Coroutine) -> Response:
+ with self.database.config.connection_class(self.database) as conn:
+ # todo: remove on next tinysql release
+ conn.open()
+
+ return await handler(self.request, conn, **self.request.match_info)
+
+
+ @cached_property
+ def allowed_methods(self) -> tuple[str]:
+ return tuple(self.handlers.keys())
+
+
+ @cached_property
+ def handlers(self) -> dict[str, Coroutine]:
+ data = {}
+
+ for method in METHODS:
+ try:
+ data[method] = getattr(self, method.lower())
+
+ except AttributeError:
+ continue
+
+ return data
+
+
+ # app components
+ @property
+ def app(self) -> Application:
+ return self.request.app
+
+
+ @property
+ def cache(self) -> Cache:
+ return self.app.cache
+
+
+ @property
+ def client(self) -> HttpClient:
+ return self.app.client
+
+
+ @property
+ def config(self) -> Config:
+ return self.app.config
+
+
+ @property
+ def database(self) -> Database:
+ return self.app.database
diff --git a/relay/views/frontend.py b/relay/views/frontend.py
new file mode 100644
index 0000000..987b9b0
--- /dev/null
+++ b/relay/views/frontend.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+import typing
+
+from .base import View, register_route
+
+from .. import __version__
+from ..misc import Response
+
+if typing.TYPE_CHECKING:
+ from aiohttp.web import Request
+ from aputils.signer import Signer
+ from collections.abc import Callable
+ from tinysql import Row
+ from ..database.connection import Connection
+
+
+HOME_TEMPLATE = """
+
+ ActivityPub Relay at {host}
+
+
+
+ This is an Activity Relay for fediverse instances.
+ {note}
+
+ You may subscribe to this relay with the address:
+ https://{host}/actor
+
+
+ To host your own relay, you may download the code at this address:
+
+ https://git.pleroma.social/pleroma/relay
+
+
+
List of {count} registered instances:
{targets}
+
+"""
+
+
+# pylint: disable=unused-argument
+
+@register_route('/')
+class HomeView(View):
+ async def get(self, request: Request, conn: Connection) -> Response:
+ config = conn.get_config_all()
+ inboxes = conn.execute('SELECT * FROM inboxes').all()
+
+ text = HOME_TEMPLATE.format(
+ host = self.config.domain,
+ note = config['note'],
+ count = len(inboxes),
+ targets = '
'.join(inbox['domain'] for inbox in inboxes)
+ )
+
+ return Response.new(text, ctype='html')
diff --git a/relay/views/misc.py b/relay/views/misc.py
new file mode 100644
index 0000000..e41ae2b
--- /dev/null
+++ b/relay/views/misc.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+import subprocess
+import typing
+
+from aputils.objects import Nodeinfo, WellKnownNodeinfo
+from pathlib import Path
+
+from .base import View, register_route
+
+from .. import __version__
+from ..misc import Response
+
+if typing.TYPE_CHECKING:
+ from aiohttp.web import Request
+ from ..database.connection import Connection
+
+
+VERSION = __version__
+
+
+if Path(__file__).parent.parent.joinpath('.git').exists():
+ try:
+ commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii')
+ VERSION = f'{__version__} {commit_label}'
+
+ except Exception:
+ pass
+
+
+# pylint: disable=unused-argument
+
+@register_route('/nodeinfo/{niversion:\\d.\\d}.json', '/nodeinfo/{niversion:\\d.\\d}')
+class NodeinfoView(View):
+ # pylint: disable=no-self-use
+ async def get(self, request: Request, conn: Connection, niversion: str) -> Response:
+ inboxes = conn.execute('SELECT * FROM inboxes').all()
+
+ data = {
+ 'name': 'activityrelay',
+ 'version': VERSION,
+ 'protocols': ['activitypub'],
+ 'open_regs': not conn.get_config('whitelist-enabled'),
+ 'users': 1,
+ 'metadata': {'peers': [inbox['domain'] for inbox in inboxes]}
+ }
+
+ if niversion == '2.1':
+ data['repo'] = 'https://git.pleroma.social/pleroma/relay'
+
+ return Response.new(Nodeinfo.new(**data), ctype = 'json')
+
+
+@register_route('/.well-known/nodeinfo')
+class WellknownNodeinfoView(View):
+ async def get(self, request: Request, conn: Connection) -> Response:
+ data = WellKnownNodeinfo.new_template(self.config.domain)
+ return Response.new(data, ctype = 'json')