Реализована проверка версионности и требование soft\force обновлений приложения
This commit is contained in:
@@ -63,6 +63,7 @@ from app.schemas import (
|
||||
MessageRequest,
|
||||
OpenLinesInbox,
|
||||
OtpSettingsResponse,
|
||||
PublicAppConfigResponse,
|
||||
SessionStartRequest,
|
||||
decode_cursor,
|
||||
encode_cursor,
|
||||
@@ -192,6 +193,15 @@ app.include_router(notification_router)
|
||||
log = structlog.get_logger()
|
||||
|
||||
|
||||
def etag_matches(if_none_match: str | None, etag: str) -> bool:
|
||||
if not if_none_match:
|
||||
return False
|
||||
return any(
|
||||
candidate.strip().removeprefix("W/") in {"*", etag}
|
||||
for candidate in if_none_match.split(",")
|
||||
)
|
||||
|
||||
|
||||
def client_ip(request: Request) -> str | None:
|
||||
"""Trust forwarded client addresses only from configured reverse proxies."""
|
||||
peer = request.client.host if request.client else ""
|
||||
@@ -501,8 +511,17 @@ async def ready(request: Request, db: Session):
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/v1/public/app-config", tags=["public"])
|
||||
async def app_config(request: Request, response: Response, settings: SnapshotDep):
|
||||
@app.get(
|
||||
"/api/v1/public/app-config",
|
||||
tags=["public"],
|
||||
response_model=PublicAppConfigResponse,
|
||||
responses={304: {"description": "Cached configuration is still current"}},
|
||||
)
|
||||
async def app_config(
|
||||
request: Request,
|
||||
settings: SnapshotDep,
|
||||
if_none_match: Annotated[str | None, Header()] = None,
|
||||
):
|
||||
await enforce_limit(
|
||||
request,
|
||||
"ip",
|
||||
@@ -511,12 +530,15 @@ async def app_config(request: Request, response: Response, settings: SnapshotDep
|
||||
settings.limit("rate_limit.public_endpoints.per_ip"),
|
||||
fail_closed=False,
|
||||
)
|
||||
response.headers["Cache-Control"] = (
|
||||
headers = {"Cache-Control": (
|
||||
f"public, max-age={settings.integer('security.public_cache.max_age_seconds')}"
|
||||
)
|
||||
response.headers["ETag"] = f'"{settings.version}"'
|
||||
)}
|
||||
etag = f'"{settings.version}"'
|
||||
headers["ETag"] = etag
|
||||
if etag_matches(if_none_match, etag):
|
||||
return Response(status_code=304, headers=headers)
|
||||
values = settings.values
|
||||
return {
|
||||
body = {
|
||||
"auth": {
|
||||
"phone_enabled": settings.boolean("auth.phone.enabled"),
|
||||
"password_enabled": settings.boolean("auth.password.enabled"),
|
||||
@@ -557,7 +579,28 @@ async def app_config(request: Request, response: Response, settings: SnapshotDep
|
||||
),
|
||||
},
|
||||
"ux": {"idle_timeout_minutes": settings.integer("ux.session.idle_timeout_minutes")},
|
||||
"mobile_update": {
|
||||
store: {
|
||||
"enabled": settings.boolean(f"mobile_update.{store}.enabled"),
|
||||
"latest_build": (
|
||||
settings.integer(f"mobile_update.{store}.latest_build")
|
||||
if values[f"mobile_update.{store}.latest_build"]
|
||||
else None
|
||||
),
|
||||
"minimum_build": (
|
||||
settings.integer(f"mobile_update.{store}.minimum_build")
|
||||
if values[f"mobile_update.{store}.minimum_build"]
|
||||
else None
|
||||
),
|
||||
"latest_version": values[f"mobile_update.{store}.latest_version"] or None,
|
||||
"store_url": values[f"mobile_update.{store}.store_url"] or None,
|
||||
"release_notes": values[f"mobile_update.{store}.release_notes"] or None,
|
||||
}
|
||||
for store in ("google_play", "rustore", "app_store")
|
||||
},
|
||||
}
|
||||
validated = PublicAppConfigResponse.model_validate(body)
|
||||
return JSONResponse(validated.model_dump(mode="json"), headers=headers)
|
||||
|
||||
|
||||
@app.get("/api/v1/public/content", tags=["public"])
|
||||
|
||||
Reference in New Issue
Block a user