aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/api/hub.py
blob: 82234008a2ad84dccd624bf98f73eef69da41d7e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"""Hub info endpoints — /v1/hub/*"""

from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession

from meshbay_common import MNP_VERSION, MHP_VERSION
from meshbay_hub import __version__, hub_settings
from meshbay_hub.auth import hub_public_key_pem
from meshbay_hub.config import HubConfig
from meshbay_hub.db.engine import get_db, get_engine

router = APIRouter(prefix="/v1/hub", tags=["hub"])

_cfg: HubConfig | None = None


def set_config(cfg: HubConfig) -> None:
    global _cfg
    _cfg = cfg


@router.get("/info")
async def hub_info(db: AsyncSession = Depends(get_db)):
    engine = get_engine()
    return {
        "hub_version": __version__,
        "mnp_version": MNP_VERSION,
        "mhp_version": MHP_VERSION,
        "db_dialect":  engine.dialect.name,
        # Instance policy the SPA needs before drawing the create-group form.
        # Unauthenticated on purpose: it is not a secret, and the form is
        # reachable before the group list loads. The hub enforces it regardless
        # of what any client does with this flag.
        "allow_public_groups": await hub_settings.public_groups_allowed(db),
        "captcha_site_key": _cfg.captcha.site_key if _cfg and _cfg.captcha.enabled else "",
    }


@router.get("/pubkey")
async def hub_pubkey():
    """Hub Ed25519 public key PEM — cached by nodes on first contact."""
    return {"pk_hub_pem": hub_public_key_pem().decode()}


# What an installed client must be, to talk to this hub.
#
# Until a client ships, the SPA and the hub deploy together and are always in
# sync: a /v1/ response shape changes and app.js is fixed in the same commit.
# The moment the interface is installed rather than served, an old client meets
# a new hub — for the first time in this project's life — and there is no way to
# fix it from here.
#
# `minimum` refuses; `recommended` warns. Both are stated so a client can tell a
# user "update to keep using this" before it becomes "this stopped working".
# Raise `minimum` only for a change a client genuinely cannot survive, and
# remember store review latency makes that expensive on Android.
MIN_CLIENT_VERSION = "0.1.0"
RECOMMENDED_CLIENT_VERSION = "0.1.0"


@router.get("/version")
async def hub_version():
    """Version check endpoint for clients to detect updates."""
    return {
        "hub": __version__,
        "mnp": MNP_VERSION,
        "mhp": MHP_VERSION,
        # A client compares its own version against these before doing anything
        # else. The browser SPA always matches the hub by construction and can
        # ignore them.
        "client": {
            "minimum": MIN_CLIENT_VERSION,
            "recommended": RECOMMENDED_CLIENT_VERSION,
        },
    }