team-3/src/c3nav/mapdata/api/updates.py
2024-12-25 21:29:53 +01:00

136 lines
4.9 KiB
Python

from typing import Annotated, Optional, Union
from urllib.parse import urlparse
from django.http import HttpResponse
from ninja import Field as APIField
from ninja import Router as APIRouter
from pydantic import PositiveInt
from c3nav.api.auth import auth_responses
from c3nav.api.schema import BaseSchema
from c3nav.api.utils import NonEmptyStr
from c3nav.mapdata.models import MapUpdate
from c3nav.mapdata.schemas.models import DataOverlaySchema
from c3nav.mapdata.utils.cache.stats import increment_cache_key
from c3nav.mapdata.utils.user import get_user_data
from c3nav.mapdata.views import set_tile_access_cookie
updates_api_router = APIRouter(tags=["updates"])
class QuestTypeSchema(BaseSchema):
label: str
icon: str
class UserDataSchema(BaseSchema):
# use in more places
logged_in: bool = APIField(
title="logged in",
description="whether a user is logged in",
)
allow_editor: bool = APIField(
title="editor access",
description="whether the user signed in can access the editor (or accessing the editor is possible as guest)."
"this does not mean that the current API authorization allows accessing the editor API.",
)
allow_control_panel: bool = APIField(
title="control panel access",
description="whether the user signed in can access the control panel.",
)
mesh_control: bool = APIField(
title="mesh control permission",
description="whether the user signed in can manage the mesh.",
)
has_positions: bool = APIField(
title="user has positions",
description="whether the user signed in has created any positions",
)
title: NonEmptyStr = APIField(
title="user data title",
description="data to show in the top right corner. can be the user name or `Login` or similar",
example="ada_lovelace",
)
subtitle: Union[
Annotated[NonEmptyStr, APIField(
title="user data subtitle",
description="a description of the current user data state to display below the user data title",
example="3 areas unlocked",
)],
None
]
permissions: list[PositiveInt] = APIField(
title="access permissions",
description="IDs of access restrictions that this user (even if maybe not signed in) has access to",
example=[2, 5],
)
overlays: list[DataOverlaySchema]
quests: dict[str, QuestTypeSchema]
class FetchUpdatesResponseSchema(BaseSchema):
last_site_update: Optional[PositiveInt] = APIField(
title="ID of the last site update",
description="If this ID increments, it means a major code change may have occurred. "
"A reload of all data is recommended. If there never has been a site update, this is `Null`.",
example=1,
)
last_map_update: NonEmptyStr = APIField(
title="string identifier of the last map update",
description="Map updates are incremental, not every map update will change all data. API endpoitns will be "
"aware of this. Use `E-Tag` and `If-None-Match` on API endpoints to query if the data has changed.",
)
user_data: Union[
Annotated[UserDataSchema, APIField(
title="user data",
description="always supplied, unless it is a cross-origin request",
)],
Annotated[None, APIField(
title="null",
description="only for cross-origin requests",
)],
] = APIField(
None,
title="user data",
description="user data of this request. ommited for cross-origin requests.",
)
@updates_api_router.get('/fetch/', summary="fetch updates",
response={200: FetchUpdatesResponseSchema, **auth_responses})
def fetch_updates(request, response: HttpResponse):
"""
Get regular updates.
This endpoint also sets/updates the tile access cookie.
If not called regularly, the tileserver will ignore your access permissions.
This endpoint can be called cross-origin, but it will have no user data then.
"""
cross_origin = request.META.get('HTTP_ORIGIN')
if cross_origin is not None:
try:
if request.META['HTTP_HOST'] == urlparse(cross_origin).hostname:
cross_origin = None
except ValueError:
pass
increment_cache_key('api_updates_fetch_requests%s' % ('_cross_origin' if cross_origin is not None else ''))
from c3nav.site.models import SiteUpdate
result = {
'last_site_update': SiteUpdate.last_update(),
'last_map_update': MapUpdate.current_processed_cache_key(),
}
if cross_origin is None:
result.update({
'user_data': get_user_data(request),
})
if cross_origin is not None:
response['Access-Control-Allow-Origin'] = cross_origin
response['Access-Control-Allow-Credentials'] = 'true'
set_tile_access_cookie(request, response)
return result