mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-12-26 04:41:07 +00:00
route handler changes
* rename `register_route` to `register_view` * add `register_route` function * convert frontend and misc routes to use `register_route`
This commit is contained in:
parent
e0ca93ab93
commit
f9d6d7b18d
|
@ -29,7 +29,7 @@ from .database.schema import Instance
|
|||
from .http_client import HttpClient
|
||||
from .misc import JSON_PATHS, TOKEN_PATHS, Message, Response
|
||||
from .template import Template
|
||||
from .views import VIEWS
|
||||
from .views import ROUTES, VIEWS
|
||||
from .views.api import handle_api_path
|
||||
from .views.frontend import handle_frontend_path
|
||||
from .workers import PushWorkers
|
||||
|
@ -87,6 +87,9 @@ class Application(web.Application):
|
|||
for path, view in VIEWS:
|
||||
self.router.add_view(path, view)
|
||||
|
||||
for method, path, handler in ROUTES:
|
||||
self.router.add_route(method, path, handler)
|
||||
|
||||
setup_swagger(
|
||||
self,
|
||||
ui_version = 3,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from . import activitypub, api, frontend, misc
|
||||
from .base import VIEWS, View
|
||||
from .base import ROUTES, VIEWS, View
|
||||
|
|
|
@ -1,19 +1,117 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import aputils
|
||||
import traceback
|
||||
|
||||
from aiohttp import ClientConnectorError
|
||||
from aiohttp.web import Request
|
||||
from aputils import Signature, SignatureFailureError, Signer
|
||||
from blib import HttpError
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import View, register_route
|
||||
from .base import View, register_view
|
||||
|
||||
from .. import logger as logging
|
||||
from ..database import schema
|
||||
from ..misc import Message, Response
|
||||
from ..processors import run_processor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..application import Application
|
||||
|
||||
try:
|
||||
from typing import Self
|
||||
|
||||
except ImportError:
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
# def route(
|
||||
# method: HttpMethod | str,
|
||||
# *path: str,
|
||||
# activity: bool = True) -> Callable[[Application, Request], JsonBase[Any]]:
|
||||
#
|
||||
# def wrapper
|
||||
|
||||
|
||||
@dataclass(slots = True)
|
||||
class InboxData:
|
||||
signature: Signature
|
||||
message: Message
|
||||
actor: Message
|
||||
signer: Signer
|
||||
instance: schema.Instance | None
|
||||
|
||||
|
||||
@classmethod
|
||||
async def parse_request(cls: type[Self], app: Application, request: Request) -> Self:
|
||||
signature: Signature | None = None
|
||||
message: Message | None = None
|
||||
actor: Message | None = None
|
||||
signer: Signer | None = None
|
||||
|
||||
try:
|
||||
signature = Signature.parse(request.headers['signature'])
|
||||
|
||||
except KeyError:
|
||||
logging.verbose('Missing signature header')
|
||||
raise HttpError(400, 'missing signature header')
|
||||
|
||||
try:
|
||||
message = await request.json(loads = Message.parse)
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
logging.verbose('Failed to parse message from actor: %s', signature.keyid)
|
||||
raise HttpError(400, 'failed to parse message')
|
||||
|
||||
if message is None:
|
||||
logging.verbose('empty message')
|
||||
raise HttpError(400, 'missing message')
|
||||
|
||||
if 'actor' not in message:
|
||||
logging.verbose('actor not in message')
|
||||
raise HttpError(400, 'no actor in message')
|
||||
|
||||
try:
|
||||
actor = await app.client.get(signature.keyid, True, Message)
|
||||
|
||||
except HttpError as e:
|
||||
# ld signatures aren't handled atm, so just ignore it
|
||||
if message.type == 'Delete':
|
||||
logging.verbose('Instance sent a delete which cannot be handled')
|
||||
raise HttpError(202, '')
|
||||
|
||||
logging.verbose('Failed to fetch actor: %s', signature.keyid)
|
||||
logging.debug('HTTP Status %i: %s', e.status, e.message)
|
||||
raise HttpError(400, 'failed to fetch actor')
|
||||
|
||||
except ClientConnectorError as e:
|
||||
logging.warning('Error when trying to fetch actor: %s, %s', signature.keyid, str(e))
|
||||
raise HttpError(400, 'failed to fetch actor')
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise HttpError(500, 'unexpected error when fetching actor')
|
||||
|
||||
try:
|
||||
signer = actor.signer
|
||||
|
||||
except KeyError:
|
||||
logging.verbose('Actor missing public key: %s', signature.keyid)
|
||||
raise HttpError(400, 'actor missing public key')
|
||||
|
||||
try:
|
||||
await signer.validate_request_async(request)
|
||||
|
||||
except SignatureFailureError as e:
|
||||
logging.verbose('signature validation failed for "%s": %s', actor.id, e)
|
||||
raise HttpError(401, str(e))
|
||||
|
||||
return cls(signature, message, actor, signer, None)
|
||||
|
||||
|
||||
@register_route('/actor', '/inbox')
|
||||
class ActorView(View):
|
||||
signature: aputils.Signature
|
||||
message: Message
|
||||
|
@ -128,7 +226,7 @@ class ActorView(View):
|
|||
raise HttpError(401, str(e))
|
||||
|
||||
|
||||
@register_route('/outbox')
|
||||
@register_view('/outbox')
|
||||
class OutboxView(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
msg = aputils.Message.new(
|
||||
|
@ -143,7 +241,7 @@ class OutboxView(View):
|
|||
return Response.new(msg, ctype = 'activity')
|
||||
|
||||
|
||||
@register_route('/following', '/followers')
|
||||
@register_view('/following', '/followers')
|
||||
class RelationshipView(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session(False) as s:
|
||||
|
@ -161,7 +259,7 @@ class RelationshipView(View):
|
|||
return Response.new(msg, ctype = 'activity')
|
||||
|
||||
|
||||
@register_route('/.well-known/webfinger')
|
||||
@register_view('/.well-known/webfinger')
|
||||
class WebfingerView(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
try:
|
||||
|
|
|
@ -6,7 +6,7 @@ from blib import HttpError, convert_to_boolean
|
|||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from .base import View, register_route
|
||||
from .base import View, register_view
|
||||
|
||||
from .. import __version__
|
||||
from ..database import ConfigData, schema
|
||||
|
@ -57,8 +57,8 @@ async def handle_api_path(
|
|||
return response
|
||||
|
||||
|
||||
@register_route('/oauth/authorize')
|
||||
@register_route('/api/oauth/authorize')
|
||||
@register_view('/oauth/authorize')
|
||||
@register_view('/api/oauth/authorize')
|
||||
class OauthAuthorize(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
data = await self.get_api_data(['response_type', 'client_id', 'redirect_uri'], [])
|
||||
|
@ -122,8 +122,8 @@ class OauthAuthorize(View):
|
|||
return Response.new_redir('/')
|
||||
|
||||
|
||||
@register_route('/oauth/token')
|
||||
@register_route('/api/oauth/token')
|
||||
@register_view('/oauth/token')
|
||||
@register_view('/api/oauth/token')
|
||||
class OauthToken(View):
|
||||
async def post(self, request: Request) -> Response:
|
||||
data = await self.get_api_data(
|
||||
|
@ -148,8 +148,8 @@ class OauthToken(View):
|
|||
return Response.new(app.get_api_data(True), ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/oauth/revoke')
|
||||
@register_route('/api/oauth/revoke')
|
||||
@register_view('/oauth/revoke')
|
||||
@register_view('/api/oauth/revoke')
|
||||
class OauthRevoke(View):
|
||||
async def post(self, request: Request) -> Response:
|
||||
data = await self.get_api_data(['client_id', 'client_secret', 'token'], [])
|
||||
|
@ -167,7 +167,7 @@ class OauthRevoke(View):
|
|||
return Response.new({'msg': 'Token deleted'}, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/app')
|
||||
@register_view('/api/v1/app')
|
||||
class App(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
return Response.new(request['token'].get_api_data(), ctype = 'json')
|
||||
|
@ -196,7 +196,7 @@ class App(View):
|
|||
return Response.new({'msg': 'Token deleted'}, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/login')
|
||||
@register_view('/api/v1/login')
|
||||
class Login(View):
|
||||
async def post(self, request: Request) -> Response:
|
||||
data = await self.get_api_data(['username', 'password'], [])
|
||||
|
@ -228,7 +228,7 @@ class Login(View):
|
|||
return resp
|
||||
|
||||
|
||||
@register_route('/api/v1/relay')
|
||||
@register_view('/api/v1/relay')
|
||||
class RelayInfo(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
|
@ -250,7 +250,7 @@ class RelayInfo(View):
|
|||
return Response.new(data, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/config')
|
||||
@register_view('/api/v1/config')
|
||||
class Config(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
data = {}
|
||||
|
@ -299,7 +299,7 @@ class Config(View):
|
|||
return Response.new({'message': 'Updated config'}, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/instance')
|
||||
@register_view('/api/v1/instance')
|
||||
class Inbox(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
|
@ -378,7 +378,7 @@ class Inbox(View):
|
|||
return Response.new({'message': 'Deleted instance'}, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/request')
|
||||
@register_view('/api/v1/request')
|
||||
class RequestView(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
|
@ -422,7 +422,7 @@ class RequestView(View):
|
|||
return Response.new(resp_message, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/domain_ban')
|
||||
@register_view('/api/v1/domain_ban')
|
||||
class DomainBan(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
|
@ -482,7 +482,7 @@ class DomainBan(View):
|
|||
return Response.new({'message': 'Unbanned domain'}, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/software_ban')
|
||||
@register_view('/api/v1/software_ban')
|
||||
class SoftwareBan(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
|
@ -538,7 +538,7 @@ class SoftwareBan(View):
|
|||
return Response.new({'message': 'Unbanned software'}, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/user')
|
||||
@register_view('/api/v1/user')
|
||||
class User(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
|
@ -594,7 +594,7 @@ class User(View):
|
|||
return Response.new({'message': 'Deleted user'}, ctype = 'json')
|
||||
|
||||
|
||||
@register_route('/api/v1/whitelist')
|
||||
@register_view('/api/v1/whitelist')
|
||||
class Whitelist(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from aiohttp.abc import AbstractView
|
||||
from aiohttp.hdrs import METH_ALL as METHODS
|
||||
from aiohttp.web import Request
|
||||
from blib import HttpError
|
||||
from blib import HttpError, HttpMethod
|
||||
from bsql import Database
|
||||
from collections.abc import Awaitable, Callable, Generator, Sequence, Mapping
|
||||
from functools import cached_property
|
||||
|
@ -21,16 +21,19 @@ if TYPE_CHECKING:
|
|||
from ..application import Application
|
||||
from ..template import Template
|
||||
|
||||
RouteHandler = Callable[[Application, Request], Awaitable[Response]]
|
||||
HandlerCallback = Callable[[Request], Awaitable[Response]]
|
||||
|
||||
|
||||
HandlerCallback = Callable[[Request], Awaitable[Response]]
|
||||
VIEWS: list[tuple[str, type[View]]] = []
|
||||
ROUTES: list[tuple[str, str, HandlerCallback]] = []
|
||||
|
||||
|
||||
def convert_data(data: Mapping[str, Any]) -> dict[str, str]:
|
||||
return {key: str(value) for key, value in data.items()}
|
||||
|
||||
|
||||
def register_route(*paths: str) -> Callable[[type[View]], type[View]]:
|
||||
def register_view(*paths: str) -> Callable[[type[View]], type[View]]:
|
||||
def wrapper(view: type[View]) -> type[View]:
|
||||
for path in paths:
|
||||
VIEWS.append((path, view))
|
||||
|
@ -39,6 +42,20 @@ def register_route(*paths: str) -> Callable[[type[View]], type[View]]:
|
|||
return wrapper
|
||||
|
||||
|
||||
def register_route(
|
||||
method: HttpMethod | str, *paths: str) -> Callable[[RouteHandler], HandlerCallback]:
|
||||
|
||||
def wrapper(handler: RouteHandler) -> HandlerCallback:
|
||||
async def inner(request: Request) -> Response:
|
||||
return await handler(get_app(), request, **request.match_info)
|
||||
|
||||
for path in paths:
|
||||
ROUTES.append((HttpMethod.parse(method), path, inner))
|
||||
|
||||
return inner
|
||||
return wrapper
|
||||
|
||||
|
||||
class View(AbstractView):
|
||||
def __await__(self) -> Generator[Any, None, Response]:
|
||||
if self.request.method not in METHODS:
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
from aiohttp import web
|
||||
from __future__ import annotations
|
||||
|
||||
from aiohttp.web import Request, middleware
|
||||
from blib import HttpMethod
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import unquote
|
||||
|
||||
from .base import View, register_route
|
||||
from .base import register_route
|
||||
|
||||
from ..database import THEMES
|
||||
from ..logger import LogLevel
|
||||
from ..misc import TOKEN_PATHS, Response
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..application import Application
|
||||
|
||||
@web.middleware
|
||||
|
||||
@middleware
|
||||
async def handle_frontend_path(
|
||||
request: web.Request,
|
||||
handler: Callable[[web.Request], Awaitable[Response]]) -> Response:
|
||||
request: Request,
|
||||
handler: Callable[[Request], Awaitable[Response]]) -> Response:
|
||||
|
||||
if request['user'] is not None and request.path == '/login':
|
||||
return Response.new_redir('/')
|
||||
|
@ -38,211 +44,208 @@ async def handle_frontend_path(
|
|||
return response
|
||||
|
||||
|
||||
@register_route('/')
|
||||
class HomeView(View):
|
||||
async def get(self, request: web.Request) -> Response:
|
||||
with self.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'instances': tuple(conn.get_inboxes())
|
||||
}
|
||||
|
||||
data = self.template.render('page/home.haml', self.request, **context)
|
||||
return Response.new(data, ctype='html')
|
||||
|
||||
|
||||
@register_route('/login')
|
||||
class Login(View):
|
||||
async def get(self, request: web.Request) -> Response:
|
||||
redir = unquote(request.query.get('redir', '/'))
|
||||
data = self.template.render('page/login.haml', self.request, redir = redir)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route('/logout')
|
||||
class Logout(View):
|
||||
async def get(self, request: web.Request) -> Response:
|
||||
with self.database.session(True) as conn:
|
||||
conn.del_app(request['token'].client_id, request['token'].client_secret)
|
||||
|
||||
resp = Response.new_redir('/')
|
||||
resp.del_cookie('user-token', domain = self.config.domain, path = '/')
|
||||
return resp
|
||||
|
||||
|
||||
@register_route('/admin')
|
||||
class Admin(View):
|
||||
async def get(self, request: web.Request) -> Response:
|
||||
return Response.new_redir(f'/login?redir={request.path}', 301)
|
||||
|
||||
|
||||
@register_route('/admin/instances')
|
||||
class AdminInstances(View):
|
||||
async def get(self,
|
||||
request: web.Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with self.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'instances': tuple(conn.get_inboxes()),
|
||||
'requests': tuple(conn.get_requests())
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = self.template.render('page/admin-instances.haml', self.request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route('/admin/whitelist')
|
||||
class AdminWhitelist(View):
|
||||
async def get(self,
|
||||
request: web.Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with self.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'whitelist': tuple(conn.execute('SELECT * FROM whitelist ORDER BY domain ASC'))
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = self.template.render('page/admin-whitelist.haml', self.request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route('/admin/domain_bans')
|
||||
class AdminDomainBans(View):
|
||||
async def get(self,
|
||||
request: web.Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with self.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'bans': tuple(conn.execute('SELECT * FROM domain_bans ORDER BY domain ASC'))
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = self.template.render('page/admin-domain_bans.haml', self.request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route('/admin/software_bans')
|
||||
class AdminSoftwareBans(View):
|
||||
async def get(self,
|
||||
request: web.Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with self.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'bans': tuple(conn.execute('SELECT * FROM software_bans ORDER BY name ASC'))
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = self.template.render('page/admin-software_bans.haml', self.request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route('/admin/users')
|
||||
class AdminUsers(View):
|
||||
async def get(self,
|
||||
request: web.Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with self.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'users': tuple(conn.execute('SELECT * FROM users ORDER BY username ASC'))
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = self.template.render('page/admin-users.haml', self.request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route('/admin/config')
|
||||
class AdminConfig(View):
|
||||
async def get(self, request: web.Request, message: str | None = None) -> Response:
|
||||
@register_route(HttpMethod.GET, "/")
|
||||
async def handle_home(app: Application, request: Request) -> Response:
|
||||
with app.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'themes': tuple(THEMES.keys()),
|
||||
'levels': tuple(level.name for level in LogLevel),
|
||||
'message': message,
|
||||
'desc': {
|
||||
"name": "Name of the relay to be displayed in the header of the pages and in " +
|
||||
"the actor endpoint.", # noqa: E131
|
||||
"note": "Description of the relay to be displayed on the front page and as the " +
|
||||
"bio in the actor endpoint.",
|
||||
"theme": "Color theme to use on the web pages.",
|
||||
"log_level": "Minimum level of logging messages to print to the console.",
|
||||
"whitelist_enabled": "Only allow instances in the whitelist to be able to follow.",
|
||||
"approval_required": "Require instances not on the whitelist to be approved by " +
|
||||
"and admin. The `whitelist-enabled` setting is ignored when this is enabled."
|
||||
}
|
||||
'instances': tuple(conn.get_inboxes())
|
||||
}
|
||||
|
||||
data = self.template.render('page/admin-config.haml', self.request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
data = app.template.render('page/home.haml', request, **context)
|
||||
return Response.new(data, ctype='html')
|
||||
|
||||
|
||||
@register_route('/manifest.json')
|
||||
class ManifestJson(View):
|
||||
async def get(self, request: web.Request) -> Response:
|
||||
with self.database.session(False) as conn:
|
||||
config = conn.get_config_all()
|
||||
theme = THEMES[config.theme]
|
||||
@register_route(HttpMethod.GET, '/login')
|
||||
async def handle_login(app: Application, request: Request) -> Response:
|
||||
redir = unquote(request.query.get('redir', '/'))
|
||||
data = app.template.render('page/login.haml', request, redir = redir)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
data = {
|
||||
'background_color': theme['background'],
|
||||
'categories': ['activitypub'],
|
||||
'description': 'Message relay for the ActivityPub network',
|
||||
'display': 'standalone',
|
||||
'name': config['name'],
|
||||
'orientation': 'portrait',
|
||||
'scope': f"https://{self.config.domain}/",
|
||||
'short_name': 'ActivityRelay',
|
||||
'start_url': f"https://{self.config.domain}/",
|
||||
'theme_color': theme['primary']
|
||||
|
||||
@register_route(HttpMethod.GET, '/logout')
|
||||
async def handle_logout(app: Application, request: Request) -> Response:
|
||||
with app.database.session(True) as conn:
|
||||
conn.del_app(request['token'].client_id, request['token'].client_secret)
|
||||
|
||||
resp = Response.new_redir('/')
|
||||
resp.del_cookie('user-token', domain = app.config.domain, path = '/')
|
||||
return resp
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/admin')
|
||||
async def handle_admin(app: Application, request: Request) -> Response:
|
||||
return Response.new_redir(f'/login?redir={request.path}', 301)
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/admin/instances')
|
||||
async def handle_admin_instances(
|
||||
app: Application,
|
||||
request: Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with app.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'instances': tuple(conn.get_inboxes()),
|
||||
'requests': tuple(conn.get_requests())
|
||||
}
|
||||
|
||||
return Response.new(data, ctype = 'webmanifest')
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = app.template.render('page/admin-instances.haml', request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route('/theme/{theme}.css')
|
||||
class ThemeCss(View):
|
||||
async def get(self, request: web.Request, theme: str) -> Response:
|
||||
try:
|
||||
context: dict[str, Any] = {
|
||||
'theme': THEMES[theme]
|
||||
}
|
||||
@register_route(HttpMethod.GET, '/admin/whitelist')
|
||||
async def handle_admin_whitelist(
|
||||
app: Application,
|
||||
request: Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
except KeyError:
|
||||
return Response.new('Invalid theme', 404)
|
||||
with app.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'whitelist': tuple(conn.execute('SELECT * FROM whitelist ORDER BY domain ASC'))
|
||||
}
|
||||
|
||||
data = self.template.render('variables.css', self.request, **context)
|
||||
return Response.new(data, ctype = 'css')
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = app.template.render('page/admin-whitelist.haml', request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/admin/domain_bans')
|
||||
async def handle_admin_instance_bans(
|
||||
app: Application,
|
||||
request: Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with app.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'bans': tuple(conn.execute('SELECT * FROM domain_bans ORDER BY domain ASC'))
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = app.template.render('page/admin-domain_bans.haml', request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/admin/software_bans')
|
||||
async def handle_admin_software_bans(
|
||||
app: Application,
|
||||
request: Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with app.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'bans': tuple(conn.execute('SELECT * FROM software_bans ORDER BY name ASC'))
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = app.template.render('page/admin-software_bans.haml', request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/admin/users')
|
||||
async def handle_admin_users(
|
||||
app: Application,
|
||||
request: Request,
|
||||
error: str | None = None,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
with app.database.session() as conn:
|
||||
context: dict[str, Any] = {
|
||||
'users': tuple(conn.execute('SELECT * FROM users ORDER BY username ASC'))
|
||||
}
|
||||
|
||||
if error:
|
||||
context['error'] = error
|
||||
|
||||
if message:
|
||||
context['message'] = message
|
||||
|
||||
data = app.template.render('page/admin-users.haml', request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/admin/config')
|
||||
async def handle_admin_config(
|
||||
app: Application,
|
||||
request: Request,
|
||||
message: str | None = None) -> Response:
|
||||
|
||||
context: dict[str, Any] = {
|
||||
'themes': tuple(THEMES.keys()),
|
||||
'levels': tuple(level.name for level in LogLevel),
|
||||
'message': message,
|
||||
'desc': {
|
||||
"name": "Name of the relay to be displayed in the header of the pages and in " +
|
||||
"the actor endpoint.", # noqa: E131
|
||||
"note": "Description of the relay to be displayed on the front page and as the " +
|
||||
"bio in the actor endpoint.",
|
||||
"theme": "Color theme to use on the web pages.",
|
||||
"log_level": "Minimum level of logging messages to print to the console.",
|
||||
"whitelist_enabled": "Only allow instances in the whitelist to be able to follow.",
|
||||
"approval_required": "Require instances not on the whitelist to be approved by " +
|
||||
"and admin. The `whitelist-enabled` setting is ignored when this is enabled."
|
||||
}
|
||||
}
|
||||
|
||||
data = app.template.render('page/admin-config.haml', request, **context)
|
||||
return Response.new(data, ctype = 'html')
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/manifest.json')
|
||||
async def handle_manifest(app: Application, request: Request) -> Response:
|
||||
with app.database.session(False) as conn:
|
||||
config = conn.get_config_all()
|
||||
theme = THEMES[config.theme]
|
||||
|
||||
data = {
|
||||
'background_color': theme['background'],
|
||||
'categories': ['activitypub'],
|
||||
'description': 'Message relay for the ActivityPub network',
|
||||
'display': 'standalone',
|
||||
'name': config['name'],
|
||||
'orientation': 'portrait',
|
||||
'scope': f"https://{app.config.domain}/",
|
||||
'short_name': 'ActivityRelay',
|
||||
'start_url': f"https://{app.config.domain}/",
|
||||
'theme_color': theme['primary']
|
||||
}
|
||||
|
||||
return Response.new(data, ctype = 'webmanifest')
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/theme/{theme}.css') # type: ignore[arg-type]
|
||||
async def handle_theme(app: Application, request: Request, theme: str) -> Response:
|
||||
try:
|
||||
context: dict[str, Any] = {
|
||||
'theme': THEMES[theme]
|
||||
}
|
||||
|
||||
except KeyError:
|
||||
return Response.new('Invalid theme', 404)
|
||||
|
||||
data = app.template.render('variables.css', request, **context)
|
||||
return Response.new(data, ctype = 'css')
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import aputils
|
||||
import subprocess
|
||||
|
||||
from aiohttp.web import Request
|
||||
from pathlib import Path
|
||||
from blib import File, HttpMethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import View, register_route
|
||||
from .base import register_route
|
||||
|
||||
from .. import __version__
|
||||
from ..misc import Response
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..application import Application
|
||||
|
||||
|
||||
VERSION = __version__
|
||||
|
||||
|
||||
if Path(__file__).parent.parent.joinpath('.git').exists():
|
||||
if File(__file__).join("../../../.git").resolve().exists:
|
||||
try:
|
||||
commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii')
|
||||
VERSION = f'{__version__} {commit_label}'
|
||||
|
@ -22,31 +28,35 @@ if Path(__file__).parent.parent.joinpath('.git').exists():
|
|||
pass
|
||||
|
||||
|
||||
@register_route('/nodeinfo/{niversion:\\d.\\d}.json', '/nodeinfo/{niversion:\\d.\\d}')
|
||||
class NodeinfoView(View):
|
||||
async def get(self, request: Request, niversion: str) -> Response:
|
||||
with self.database.session() as conn:
|
||||
inboxes = conn.get_inboxes()
|
||||
|
||||
nodeinfo = aputils.Nodeinfo.new(
|
||||
name = 'activityrelay',
|
||||
version = VERSION,
|
||||
protocols = ['activitypub'],
|
||||
open_regs = not conn.get_config('whitelist-enabled'),
|
||||
users = 1,
|
||||
repo = 'https://git.pleroma.social/pleroma/relay' if niversion == '2.1' else None,
|
||||
metadata = {
|
||||
'approval_required': conn.get_config('approval-required'),
|
||||
'peers': [inbox['domain'] for inbox in inboxes]
|
||||
}
|
||||
)
|
||||
|
||||
return Response.new(nodeinfo, ctype = 'json')
|
||||
NODEINFO_PATHS = [
|
||||
'/nodeinfo/{niversion:\\d.\\d}.json',
|
||||
'/nodeinfo/{niversion:\\d.\\d}'
|
||||
]
|
||||
|
||||
|
||||
@register_route('/.well-known/nodeinfo')
|
||||
class WellknownNodeinfoView(View):
|
||||
async def get(self, request: Request) -> Response:
|
||||
data = aputils.WellKnownNodeinfo.new_template(self.config.domain)
|
||||
@register_route(HttpMethod.GET, *NODEINFO_PATHS) # type: ignore[arg-type]
|
||||
async def handle_nodeinfo(app: Application, request: Request, niversion: str) -> Response:
|
||||
with app.database.session() as conn:
|
||||
inboxes = conn.get_inboxes()
|
||||
|
||||
return Response.new(data, ctype = 'json')
|
||||
nodeinfo = aputils.Nodeinfo.new(
|
||||
name = 'activityrelay',
|
||||
version = VERSION,
|
||||
protocols = ['activitypub'],
|
||||
open_regs = not conn.get_config('whitelist-enabled'),
|
||||
users = 1,
|
||||
repo = 'https://git.pleroma.social/pleroma/relay' if niversion == '2.1' else None,
|
||||
metadata = {
|
||||
'approval_required': conn.get_config('approval-required'),
|
||||
'peers': [inbox['domain'] for inbox in inboxes]
|
||||
}
|
||||
)
|
||||
|
||||
return Response.new(nodeinfo, ctype = 'json')
|
||||
|
||||
|
||||
@register_route(HttpMethod.GET, '/.well-known/nodeinfo')
|
||||
async def handle_wk_nodeinfo(app: Application, request: Request) -> Response:
|
||||
data = aputils.WellKnownNodeinfo.new_template(app.config.domain)
|
||||
|
||||
return Response.new(data, ctype = 'json')
|
||||
|
|
Loading…
Reference in a new issue