social/barkshark_social/server.py
2024-04-18 22:36:06 -04:00

137 lines
3.3 KiB
Python

from __future__ import annotations
import os
import subprocess
import sys
from aputils import Signer, register_validator
from argon2 import PasswordHasher
from bsql import Database
from pathlib import Path
from typing import Any
from . import __version__, middleware as mw
from .config import Config
from .database import Connection
from .misc import get_resource, read_resource_text
from .state import AppState, RequestState
from .translations import Translations
from basgi import (
Application as App,
HttpError,
Request,
Response,
Template,
TemplateResponse
)
class Application(App[Request, RequestState, AppState]):
def __init__(self, cfg_path: Path | str | None = None, language: str = "en") -> None:
App.__init__(
self,
"BarksharkSocial",
template_search = [get_resource("frontend")],
template_context = self.handle_context,
request_state_class = RequestState,
app_state_class = AppState
)
self.state.setup(cfg_path, language)
self.error_handlers[HttpError] = self.handle_http_exception
self.add_static("/static", get_resource("frontend/static"))
self.client.useragent = f"BarksharkSocial/{__version__} (https://{self.config.web_host})"
self.on_request.connect(mw.FrontendAuthMiddleware)
self.on_request.connect(mw.ActivitypubAuthMiddleware)
@property
def config(self) -> Config:
return self.state.config
@property
def database(self) -> Database[Connection]:
return self.state.database
@property
def hasher(self) -> PasswordHasher:
return self.state.hasher
@property
def trans(self) -> Translations:
return self.state.trans
def run(self) -> None:
cmd = [
sys.executable, "-m", "uvicorn",
"barkshark_social._run_app:app",
"--host", self.config.addr,
"--port", str(self.config.port),
"--lifespan", "on",
"--no-server-header",
"--no-access-log"
]
if self.state.config.dev:
cmd.extend(["--reload", "--reload-dir", str(Path(__file__).parent)])
else:
cmd.extend(["--workers", str(self.config.workers)])
env = os.environ.copy()
env["SOCIAL_CONFIG_PATH"] = str(self.state.config.path)
subprocess.run(cmd, env = env)
def run_granian(self) -> None:
cmd = [
sys.executable, "-m", "granian",
"barkshark_social._run_app:app",
"--interface", "asgi",
"--host", self.config.addr,
"--port", str(self.config.port),
"--workers", str(self.config.workers),
"--loop", "uvloop",
"--respawn-failed-workers",
"--opt"
]
if self.state.config.dev:
cmd.append("--reload")
env = os.environ.copy()
env["SOCIAL_CONFIG_PATH"] = str(self.state.config.path)
subprocess.run(cmd, env = env)
def handle_http_exception(self, request: Request, exc: HttpError) -> Response:
if "json" in request.headers.get("Accept", ""):
return Response.new_json(exc.status, {"error": str(exc)})
return TemplateResponse("error.haml", {"error": exc}, exc.status)
def handle_context(self, env: Template, context: dict[str, Any]) -> dict[str, Any]:
context.update({
"app": self,
"read_resource_text": read_resource_text
})
return context
@register_validator(Request)
async def handle_validate_request(signer: Signer, request: Request) -> bool:
return signer.validate_signature(
request.method,
request.path,
{key: value for key, value in request.headers.items()},
await request.body()
)