from django.urls import Resolver404, resolve from django.utils.translation import gettext_lazy as _ from ninja import Router as APIRouter from c3nav.api.auth import APIKeyAuth, auth_permission_responses from c3nav.api.exceptions import API404 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 from c3nav.editor.views.base import editor_etag_func from c3nav.mapdata.api.base import api_etag from c3nav.mapdata.models import Source from c3nav.mapdata.schemas.responses import WithBoundsSchema editor_api_router = APIRouter(tags=["editor"], auth=APIKeyAuth(permissions={"editor_access"})) @editor_api_router.get('/bounds/', summary="boundaries", description="get maximum boundaries of everything on the map", response={200: WithBoundsSchema, **auth_permission_responses}, openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]}) @api_etag() def bounds(request): return { "bounds": Source.max_bounds(), } @editor_api_router.get('/geometrystyles/', summary="geometry styles", description="get the default colors for each geometry type", response={200: GeometryStylesSchema, **auth_permission_responses}, openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]}) @api_etag(permissions=False) def geometrystyles(request): 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', } @editor_api_router.get('/geometries/space/{space_id}/', summary="space geometries", description="get the geometries to display on the editor map for a space", response={200: list[EditorGeometriesElemSchema], **API404.dict(), **auth_permission_responses}, openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]}) @api_etag_with_update_cache_key(etag_func=editor_etag_func) 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"] ) @editor_api_router.get('/geometries/level/{level_id}/', summary="level geometries", description="get the geometries to display on the editor map for a space", response={200: list[EditorGeometriesElemSchema], **API404.dict(), **auth_permission_responses}, openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]}) @api_etag_with_update_cache_key(etag_func=editor_etag_func) 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"] ) # todo: need a way to pass the changeset if it's not a session API key def resolve_editor_path_api(request, path): resolved = None if path: try: resolved = resolve('/editor/'+path+'/') except Resolver404: pass if not resolved: try: resolved = resolve('/editor/'+path) except Resolver404: pass request.sub_resolver_match = resolved return resolved @editor_api_router.get('/as_api/{path:path}', summary="raw editor access", response={200: dict, **API404.dict(), **auth_permission_responses}, openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]}) @api_etag() # todo: correct? def view_as_api(request, path: str): """ 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. """ resolved = resolve_editor_path_api(request, path) 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 @editor_api_router.post('/as_api/{path:path}', summary="raw editor access", response={200: dict, **API404.dict(), **auth_permission_responses}, openapi_extra={"security": [{"APITokenAuth": ["editor_access", "write"]}]}) @api_etag() # todo: correct? def view_as_api(request, path: str): """ 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