mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-10 02:17:59 +00:00
split views.py
This commit is contained in:
parent
b8e0641733
commit
226f940cdc
|
@ -5,12 +5,8 @@ import os
|
||||||
import socket
|
import socket
|
||||||
import typing
|
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 import Response as AiohttpResponse
|
||||||
from aiohttp.web_exceptions import HTTPMethodNotAllowed
|
|
||||||
from aputils.message import Message as ApMessage
|
from aputils.message import Message as ApMessage
|
||||||
from functools import cached_property
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
|
@ -232,67 +228,3 @@ class Response(AiohttpResponse):
|
||||||
@location.setter
|
@location.setter
|
||||||
def location(self, value: str) -> None:
|
def location(self, value: str) -> None:
|
||||||
self.headers['Location'] = value
|
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
|
|
||||||
|
|
4
relay/views/__init__.py
Normal file
4
relay/views/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from . import activitypub, frontend, misc
|
||||||
|
from .base import VIEWS
|
|
@ -1,95 +1,27 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import traceback
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from aputils.errors import SignatureFailureError
|
from aputils.errors import SignatureFailureError
|
||||||
from aputils.misc import Digest, HttpDate, Signature
|
from aputils.misc import Digest, HttpDate, Signature
|
||||||
from aputils.objects import Nodeinfo, Webfinger, WellKnownNodeinfo
|
from aputils.objects import Webfinger
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from . import __version__
|
from .base import View, register_route
|
||||||
from . import logger as logging
|
|
||||||
from .database.connection import Connection
|
from .. import logger as logging
|
||||||
from .misc import Message, Response, View
|
from ..misc import Message, Response
|
||||||
from .processors import run_processor
|
from ..processors import run_processor
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from aiohttp.web import Request
|
from aiohttp.web import Request
|
||||||
from aputils.signer import Signer
|
from aputils.signer import Signer
|
||||||
from collections.abc import Callable
|
|
||||||
from tinysql import Row
|
from tinysql import Row
|
||||||
|
from ..database.connection import Connection
|
||||||
|
|
||||||
VIEWS = []
|
|
||||||
VERSION = __version__
|
|
||||||
HOME_TEMPLATE = """
|
|
||||||
<html><head>
|
|
||||||
<title>ActivityPub Relay at {host}</title>
|
|
||||||
<style>
|
|
||||||
p {{ color: #FFFFFF; font-family: monospace, arial; font-size: 100%; }}
|
|
||||||
body {{ background-color: #000000; }}
|
|
||||||
a {{ color: #26F; }}
|
|
||||||
a:visited {{ color: #46C; }}
|
|
||||||
a:hover {{ color: #8AF; }}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>This is an Activity Relay for fediverse instances.</p>
|
|
||||||
<p>{note}</p>
|
|
||||||
<p>
|
|
||||||
You may subscribe to this relay with the address:
|
|
||||||
<a href="https://{host}/actor">https://{host}/actor</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To host your own relay, you may download the code at this address:
|
|
||||||
<a href="https://git.pleroma.social/pleroma/relay">
|
|
||||||
https://git.pleroma.social/pleroma/relay
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<br><p>List of {count} registered instances:<br>{targets}</p>
|
|
||||||
</body></html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# 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 = '<br>'.join(inbox['domain'] for inbox in inboxes)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response.new(text, ctype='html')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/actor', '/inbox')
|
@register_route('/actor', '/inbox')
|
||||||
class ActorView(View):
|
class ActorView(View):
|
||||||
def __init__(self, request: Request):
|
def __init__(self, request: Request):
|
||||||
|
@ -247,31 +179,3 @@ class WebfingerView(View):
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response.new(data, ctype = 'json')
|
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')
|
|
93
relay/views/base.py
Normal file
93
relay/views/base.py
Normal file
|
@ -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
|
62
relay/views/frontend.py
Normal file
62
relay/views/frontend.py
Normal file
|
@ -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 = """
|
||||||
|
<html><head>
|
||||||
|
<title>ActivityPub Relay at {host}</title>
|
||||||
|
<style>
|
||||||
|
p {{ color: #FFFFFF; font-family: monospace, arial; font-size: 100%; }}
|
||||||
|
body {{ background-color: #000000; }}
|
||||||
|
a {{ color: #26F; }}
|
||||||
|
a:visited {{ color: #46C; }}
|
||||||
|
a:hover {{ color: #8AF; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This is an Activity Relay for fediverse instances.</p>
|
||||||
|
<p>{note}</p>
|
||||||
|
<p>
|
||||||
|
You may subscribe to this relay with the address:
|
||||||
|
<a href="https://{host}/actor">https://{host}/actor</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To host your own relay, you may download the code at this address:
|
||||||
|
<a href="https://git.pleroma.social/pleroma/relay">
|
||||||
|
https://git.pleroma.social/pleroma/relay
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<br><p>List of {count} registered instances:<br>{targets}</p>
|
||||||
|
</body></html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 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 = '<br>'.join(inbox['domain'] for inbox in inboxes)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response.new(text, ctype='html')
|
58
relay/views/misc.py
Normal file
58
relay/views/misc.py
Normal file
|
@ -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')
|
Loading…
Reference in a new issue