2023-12-03 17:54:26 +01:00
|
|
|
from django.urls import Resolver404, resolve
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
2023-12-01 22:59:57 +01:00
|
|
|
from ninja import Router as APIRouter
|
|
|
|
|
2023-12-03 21:55:08 +01:00
|
|
|
from c3nav.api.auth import APITokenAuth, auth_permission_responses
|
2023-12-03 16:37:05 +01:00
|
|
|
from c3nav.api.exceptions import API404
|
2023-12-03 21:55:08 +01:00
|
|
|
from c3nav.editor.api.base import api_etag_with_update_cache_key
|
|
|
|
from c3nav.editor.api.geometries import get_level_geometries_result, get_space_geometries_result
|
|
|
|
from c3nav.editor.api.schemas import EditorGeometriesElemSchema, EditorID, GeometryStylesSchema, UpdateCacheKey
|
2023-12-03 16:37:05 +01:00
|
|
|
from c3nav.editor.views.base import editor_etag_func
|
2023-12-03 21:55:08 +01:00
|
|
|
from c3nav.mapdata.api.base import api_etag
|
2023-12-01 22:59:57 +01:00
|
|
|
from c3nav.mapdata.models import Source
|
2023-12-04 18:58:49 +01:00
|
|
|
from c3nav.mapdata.schemas.responses import WithBoundsSchema
|
2023-12-01 22:59:57 +01:00
|
|
|
|
|
|
|
editor_api_router = APIRouter(tags=["editor"], auth=APITokenAuth(permissions={"editor_access"}))
|
|
|
|
|
|
|
|
|
2023-12-03 20:15:41 +01:00
|
|
|
@editor_api_router.get('/bounds/', summary="boundaries",
|
|
|
|
description="get maximum boundaries of everything on the map",
|
2023-12-04 18:58:49 +01:00
|
|
|
response={200: WithBoundsSchema, **auth_permission_responses},
|
2023-12-02 01:14:09 +01:00
|
|
|
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
2023-12-03 21:55:08 +01:00
|
|
|
@api_etag()
|
2023-12-03 17:18:29 +01:00
|
|
|
def bounds(request):
|
2023-12-01 22:59:57 +01:00
|
|
|
return {
|
|
|
|
"bounds": Source.max_bounds(),
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-12-03 20:15:41 +01:00
|
|
|
@editor_api_router.get('/geometrystyles/', summary="geometry styles",
|
|
|
|
description="get the default colors for each geometry type",
|
2023-12-02 01:14:09 +01:00
|
|
|
response={200: GeometryStylesSchema, **auth_permission_responses},
|
|
|
|
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
2023-12-03 21:55:08 +01:00
|
|
|
@api_etag(permissions=False)
|
2023-12-03 17:18:29 +01:00
|
|
|
def geometrystyles(request):
|
2023-12-01 22:59:57 +01:00
|
|
|
return {
|
|
|
|
'building': '#aaaaaa',
|
|
|
|
'space': '#eeeeee',
|
|
|
|
'hole': 'rgba(255, 0, 0, 0.3)',
|
|
|
|
'door': '#ffffff',
|
|
|
|
'area': '#55aaff',
|
|
|
|
'stair': '#a000a0',
|
|
|
|
'ramp': 'rgba(160, 0, 160, 0.2)',
|
|
|
|
'obstacle': '#999999',
|
|
|
|
'lineobstacle': '#999999',
|
|
|
|
'column': 'rgba(0, 0, 50, 0.3)',
|
|
|
|
'poi': '#4488cc',
|
|
|
|
'shadow': '#000000',
|
|
|
|
'graphnode': '#009900',
|
|
|
|
'graphedge': '#00CC00',
|
|
|
|
'altitudemarker': '#0000FF',
|
|
|
|
'wifimeasurement': '#DDDD00',
|
|
|
|
'rangingbeacon': '#CC00CC',
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-12-03 20:15:41 +01:00
|
|
|
@editor_api_router.get('/geometries/space/{space_id}/', summary="space geometries",
|
|
|
|
description="get the geometries to display on the editor map for a space",
|
2023-12-03 18:11:53 +01:00
|
|
|
response={200: list[EditorGeometriesElemSchema], **API404.dict(),
|
2023-12-02 01:14:09 +01:00
|
|
|
**auth_permission_responses},
|
|
|
|
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
2023-12-03 21:55:08 +01:00
|
|
|
@api_etag_with_update_cache_key(etag_func=editor_etag_func)
|
2023-12-03 16:37:05 +01:00
|
|
|
def space_geometries(request, space_id: EditorID, update_cache_key: UpdateCacheKey = None, **kwargs):
|
|
|
|
# newapi_etag_with_update_cache_key does the following, don't let it confuse you:
|
|
|
|
# - update_cache_key becomes the actual update_cache_key, not the one supplied be the user
|
|
|
|
# - kwargs has "update_cache_key_match", which is true if update_cache_key matches the one supplied be the user
|
|
|
|
# this is done so the api etag is correctly generated, as it takes the function arguments into account
|
|
|
|
return get_space_geometries_result(
|
|
|
|
request,
|
|
|
|
space_id=space_id,
|
|
|
|
update_cache_key=update_cache_key,
|
|
|
|
update_cache_key_match=kwargs["update_cache_key_match"]
|
|
|
|
)
|
2023-12-01 22:59:57 +01:00
|
|
|
|
|
|
|
|
2023-12-03 20:15:41 +01:00
|
|
|
@editor_api_router.get('/geometries/level/{level_id}/', summary="level geometries",
|
|
|
|
description="get the geometries to display on the editor map for a space",
|
2023-12-03 18:11:53 +01:00
|
|
|
response={200: list[EditorGeometriesElemSchema], **API404.dict(),
|
2023-12-02 01:14:09 +01:00
|
|
|
**auth_permission_responses},
|
|
|
|
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
2023-12-03 21:55:08 +01:00
|
|
|
@api_etag_with_update_cache_key(etag_func=editor_etag_func)
|
2023-12-03 16:37:05 +01:00
|
|
|
def level_geometries(request, level_id: EditorID, update_cache_key: UpdateCacheKey = None, **kwargs):
|
|
|
|
# newapi_etag_with_update_cache_key does the following, don't let it confuse you:
|
|
|
|
# - update_cache_key becomes the actual update_cache_key, not the one supplied be the user
|
|
|
|
# - kwargs has "update_cache_key_match", which is true if update_cache_key matches the one supplied be the user
|
|
|
|
# this is done so the api etag is correctly generated, as it takes the function arguments into account
|
|
|
|
return get_level_geometries_result(
|
|
|
|
request,
|
|
|
|
level_id=level_id,
|
|
|
|
update_cache_key=update_cache_key,
|
|
|
|
update_cache_key_match=kwargs["update_cache_key_match"]
|
|
|
|
)
|
2023-12-01 22:59:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
# todo: need a way to pass the changeset if it's not a session API key
|
|
|
|
|
2023-12-03 17:54:26 +01:00
|
|
|
def resolve_editor_path_api(request, path):
|
|
|
|
resolved = None
|
|
|
|
if path:
|
|
|
|
try:
|
|
|
|
resolved = resolve('/editor/'+path+'/')
|
|
|
|
except Resolver404:
|
|
|
|
pass
|
2023-12-01 22:59:57 +01:00
|
|
|
|
2023-12-03 17:54:26 +01:00
|
|
|
if not resolved:
|
|
|
|
try:
|
|
|
|
resolved = resolve('/editor/'+path)
|
|
|
|
except Resolver404:
|
|
|
|
pass
|
|
|
|
|
|
|
|
request.sub_resolver_match = resolved
|
|
|
|
|
|
|
|
return resolved
|
|
|
|
|
|
|
|
|
2023-12-03 20:15:41 +01:00
|
|
|
@editor_api_router.get('/as_api/{path:path}', summary="raw editor access",
|
2023-12-02 01:14:09 +01:00
|
|
|
response={200: dict, **API404.dict(), **auth_permission_responses},
|
|
|
|
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
2023-12-03 21:55:08 +01:00
|
|
|
@api_etag() # todo: correct?
|
2023-12-03 17:18:29 +01:00
|
|
|
def view_as_api(request, path: str):
|
2023-12-01 22:59:57 +01:00
|
|
|
"""
|
|
|
|
get editor views rendered as JSON instead of HTML.
|
|
|
|
`path` is the path after /editor/.
|
|
|
|
this is a mess. good luck. if you actually want to use this, poke us so we might add better documentation.
|
|
|
|
"""
|
2023-12-03 17:54:26 +01:00
|
|
|
resolved = resolve_editor_path_api(request, path)
|
2023-12-01 22:59:57 +01:00
|
|
|
|
2023-12-03 17:54:26 +01:00
|
|
|
if not resolved:
|
|
|
|
raise API404(str(_('No matching editor view endpoint found.')))
|
|
|
|
|
|
|
|
if not getattr(resolved.func, 'api_hybrid', False):
|
|
|
|
raise API404(_('Matching editor view point does not provide an API.'))
|
|
|
|
|
|
|
|
response = resolved.func(request, api=True, *resolved.args, **resolved.kwargs)
|
|
|
|
return response
|
2023-12-01 22:59:57 +01:00
|
|
|
|
|
|
|
|
2023-12-03 20:15:41 +01:00
|
|
|
@editor_api_router.post('/as_api/{path:path}', summary="raw editor access",
|
2023-12-02 01:14:09 +01:00
|
|
|
response={200: dict, **API404.dict(), **auth_permission_responses},
|
|
|
|
openapi_extra={"security": [{"APITokenAuth": ["editor_access", "write"]}]})
|
2023-12-03 21:55:08 +01:00
|
|
|
@api_etag() # todo: correct?
|
2023-12-03 17:18:29 +01:00
|
|
|
def view_as_api(request, path: str):
|
2023-12-01 22:59:57 +01:00
|
|
|
"""
|
|
|
|
get editor views rendered as JSON instead of HTML.
|
|
|
|
`path` is the path after /editor/.
|
|
|
|
this is a mess. good luck. if you actually want to use this, poke us so we might add better documentation.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|