From 11a08822e6b3bc09e64054b47196066f0c313fe3 Mon Sep 17 00:00:00 2001 From: Eoic Date: Sat, 27 Jun 2026 17:47:51 +0300 Subject: [PATCH] Add public data sync settings endpoint --- papyrus/api/routes/sync.py | 24 +++++++++++++++++++++++- papyrus/config.py | 1 + papyrus/schemas/sync.py | 14 ++++++++++++++ tests/api/routes/test_sync.py | 20 ++++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/papyrus/api/routes/sync.py b/papyrus/api/routes/sync.py index 84dfdb3..682fd1c 100644 --- a/papyrus/api/routes/sync.py +++ b/papyrus/api/routes/sync.py @@ -9,13 +9,35 @@ from papyrus.config import get_settings from papyrus.core.database import get_db from papyrus.core.rate_limit import limiter -from papyrus.schemas.sync import PowerSyncUploadRequest, PowerSyncUploadResponse +from papyrus.schemas.sync import ( + DataSyncSettingsResponse, + FileStorageSettings, + PowerSyncUploadRequest, + PowerSyncUploadResponse, +) from papyrus.services import sync as sync_service router = APIRouter() DBSession = Annotated[AsyncSession, Depends(get_db)] +@router.get( + "/settings", + response_model=DataSyncSettingsResponse, + summary="Get public data sync settings", +) +async def get_data_sync_settings() -> DataSyncSettingsResponse: + """Return public sync capabilities for client custom-server setup.""" + settings = get_settings() + return DataSyncSettingsResponse( + data_sync_url=settings.powersync_service_url, + file_storage=FileStorageSettings( + supported=True, + quota_bytes=settings.file_storage_quota_bytes, + ), + ) + + @router.post( "/powersync-upload", response_model=PowerSyncUploadResponse, diff --git a/papyrus/config.py b/papyrus/config.py index 786eef3..e9c73b7 100644 --- a/papyrus/config.py +++ b/papyrus/config.py @@ -60,6 +60,7 @@ class Settings(BaseSettings): powersync_token_expire_minutes: int = 5 powersync_service_url: str = "http://localhost:8081" powersync_service_port: int = 8081 + file_storage_quota_bytes: int = 1_073_741_824 powersync_jwks_uri: str | None = None powersync_source_role: str | None = None powersync_source_password: str | None = None diff --git a/papyrus/schemas/sync.py b/papyrus/schemas/sync.py index 3bfae0d..7f38509 100644 --- a/papyrus/schemas/sync.py +++ b/papyrus/schemas/sync.py @@ -64,3 +64,17 @@ class PowerSyncUploadResponse(BaseModel): """Summary of an applied PowerSync upload transaction.""" applied_count: int + + +class FileStorageSettings(BaseModel): + """Public file storage capability advertised by this server.""" + + supported: bool + quota_bytes: int + + +class DataSyncSettingsResponse(BaseModel): + """Public sync settings used by clients for custom server discovery.""" + + data_sync_url: str + file_storage: FileStorageSettings diff --git a/tests/api/routes/test_sync.py b/tests/api/routes/test_sync.py index 48a5b5f..164c42c 100644 --- a/tests/api/routes/test_sync.py +++ b/tests/api/routes/test_sync.py @@ -9,6 +9,26 @@ from papyrus.models import SyncBook, User +async def test_sync_settings_are_public_and_hide_implementation_details(client: AsyncClient, monkeypatch): + """Return public data sync settings for one-URL custom server discovery.""" + from papyrus.main import settings as app_settings + + monkeypatch.setattr(app_settings, "powersync_service_url", "https://sync.papyrus.test") + monkeypatch.setattr(app_settings, "file_storage_quota_bytes", 1_073_741_824) + + response = await client.get("/v1/sync/settings") + + assert response.status_code == 200 + assert response.json() == { + "data_sync_url": "https://sync.papyrus.test", + "file_storage": { + "supported": True, + "quota_bytes": 1_073_741_824, + }, + } + assert "powersync" not in response.text.lower() + + async def test_legacy_sync_routes_are_removed(client: AsyncClient, auth_headers: dict[str, str]): """PowerSync is the only supported synchronization contract.""" assert (await client.get("/v1/sync/status", headers=auth_headers)).status_code == 404