Spaces:
Paused
Paused
| """ | |
| API views for datastore access. | |
| """ | |
| import logging | |
| from django.http import JsonResponse, HttpRequest | |
| from django.views.decorators.csrf import csrf_exempt | |
| from django.views.decorators.http import require_http_methods | |
| from core.models import DataStore, DataStoreEntry | |
| from core.views.api.decorators import api_token_required, add_cors_headers | |
| logger = logging.getLogger(__name__) | |
| # Pagination settings | |
| DEFAULT_PAGE_SIZE = 50 | |
| MAX_PAGE_SIZE = 100 | |
| def list_datastores(request: HttpRequest) -> JsonResponse: | |
| """ | |
| List all datastores accessible to the token. | |
| GET /api/v1/datastores/ | |
| Returns: | |
| { | |
| "datastores": [ | |
| { | |
| "name": "store_name", | |
| "description": "...", | |
| "entry_count": 42, | |
| "created_at": "2026-01-15T10:30:00Z", | |
| "updated_at": "2026-02-10T14:22:00Z" | |
| } | |
| ], | |
| "count": 1 | |
| } | |
| """ | |
| if request.method == "OPTIONS": | |
| response = JsonResponse({}) | |
| return add_cors_headers(response) | |
| token = request.api_token | |
| if token.datastore: | |
| # Token restricted to single datastore | |
| datastores = [token.datastore] | |
| else: | |
| # Global token - list all datastores | |
| datastores = DataStore.objects.all() | |
| data = { | |
| "datastores": [ | |
| { | |
| "name": ds.name, | |
| "description": ds.description, | |
| "entry_count": ds.entry_count, | |
| "created_at": ds.created_at.isoformat(), | |
| "updated_at": ds.updated_at.isoformat(), | |
| } | |
| for ds in datastores | |
| ], | |
| "count": len(datastores) if token.datastore else datastores.count(), | |
| } | |
| response = JsonResponse(data) | |
| return add_cors_headers(response) | |
| def get_datastore(request: HttpRequest, name: str) -> JsonResponse: | |
| """ | |
| Get datastore metadata. | |
| GET /api/v1/datastores/<name>/ | |
| Returns: | |
| { | |
| "name": "store_name", | |
| "description": "...", | |
| "entry_count": 42, | |
| "created_at": "2026-01-15T10:30:00Z", | |
| "updated_at": "2026-02-10T14:22:00Z" | |
| } | |
| """ | |
| if request.method == "OPTIONS": | |
| response = JsonResponse({}) | |
| return add_cors_headers(response) | |
| datastore = _get_authorized_datastore(request, name) | |
| if isinstance(datastore, JsonResponse): | |
| return datastore # Error response | |
| data = { | |
| "name": datastore.name, | |
| "description": datastore.description, | |
| "entry_count": datastore.entry_count, | |
| "created_at": datastore.created_at.isoformat(), | |
| "updated_at": datastore.updated_at.isoformat(), | |
| } | |
| response = JsonResponse(data) | |
| return add_cors_headers(response) | |
| def list_entries(request: HttpRequest, name: str) -> JsonResponse: | |
| """ | |
| List entries in a datastore with pagination. | |
| GET /api/v1/datastores/<name>/entries/ | |
| Query params: | |
| - page: Page number (default: 1) | |
| - page_size: Items per page (default: 50, max: 100) | |
| Returns: | |
| { | |
| "entries": [ | |
| { | |
| "key": "config", | |
| "value": {"retries": 3}, | |
| "created_at": "2026-01-20T08:00:00Z", | |
| "updated_at": "2026-02-01T12:00:00Z" | |
| } | |
| ], | |
| "count": 42, | |
| "page": 1, | |
| "page_size": 50, | |
| "total_pages": 1 | |
| } | |
| """ | |
| if request.method == "OPTIONS": | |
| response = JsonResponse({}) | |
| return add_cors_headers(response) | |
| datastore = _get_authorized_datastore(request, name) | |
| if isinstance(datastore, JsonResponse): | |
| return datastore # Error response | |
| # Parse pagination params | |
| try: | |
| page = max(1, int(request.GET.get("page", 1))) | |
| except (ValueError, TypeError): | |
| page = 1 | |
| try: | |
| page_size = min(MAX_PAGE_SIZE, max(1, int(request.GET.get("page_size", DEFAULT_PAGE_SIZE)))) | |
| except (ValueError, TypeError): | |
| page_size = DEFAULT_PAGE_SIZE | |
| # Get total count and calculate pagination | |
| total_count = datastore.entries.count() | |
| total_pages = max(1, (total_count + page_size - 1) // page_size) | |
| # Ensure page is within bounds | |
| page = min(page, total_pages) | |
| # Get paginated entries | |
| offset = (page - 1) * page_size | |
| entries = datastore.entries.all()[offset:offset + page_size] | |
| data = { | |
| "entries": [ | |
| { | |
| "key": entry.key, | |
| "value": entry.get_value(), | |
| "created_at": entry.created_at.isoformat(), | |
| "updated_at": entry.updated_at.isoformat(), | |
| } | |
| for entry in entries | |
| ], | |
| "count": total_count, | |
| "page": page, | |
| "page_size": page_size, | |
| "total_pages": total_pages, | |
| } | |
| response = JsonResponse(data) | |
| return add_cors_headers(response) | |
| def get_entry(request: HttpRequest, name: str, key: str) -> JsonResponse: | |
| """ | |
| Get a single entry by key. | |
| GET /api/v1/datastores/<name>/entries/<key>/ | |
| Returns: | |
| { | |
| "key": "config", | |
| "value": {"retries": 3}, | |
| "created_at": "2026-01-20T08:00:00Z", | |
| "updated_at": "2026-02-01T12:00:00Z" | |
| } | |
| """ | |
| if request.method == "OPTIONS": | |
| response = JsonResponse({}) | |
| return add_cors_headers(response) | |
| datastore = _get_authorized_datastore(request, name) | |
| if isinstance(datastore, JsonResponse): | |
| return datastore # Error response | |
| try: | |
| entry = DataStoreEntry.objects.get(datastore=datastore, key=key) | |
| except DataStoreEntry.DoesNotExist: | |
| response = JsonResponse( | |
| {"error": {"code": "NOT_FOUND", "message": f"Entry '{key}' not found"}}, | |
| status=404, | |
| ) | |
| return add_cors_headers(response) | |
| data = { | |
| "key": entry.key, | |
| "value": entry.get_value(), | |
| "created_at": entry.created_at.isoformat(), | |
| "updated_at": entry.updated_at.isoformat(), | |
| } | |
| response = JsonResponse(data) | |
| return add_cors_headers(response) | |
| def _get_authorized_datastore(request: HttpRequest, name: str): | |
| """ | |
| Get datastore if token has access. | |
| Returns the DataStore object or a JsonResponse error. | |
| """ | |
| token = request.api_token | |
| try: | |
| datastore = DataStore.objects.get(name=name) | |
| except DataStore.DoesNotExist: | |
| response = JsonResponse( | |
| {"error": {"code": "NOT_FOUND", "message": f"Datastore '{name}' not found"}}, | |
| status=404, | |
| ) | |
| return add_cors_headers(response) | |
| # Check token access | |
| if token.datastore and token.datastore != datastore: | |
| response = JsonResponse( | |
| {"error": {"code": "FORBIDDEN", "message": "Token does not have access to this datastore"}}, | |
| status=403, | |
| ) | |
| return add_cors_headers(response) | |
| return datastore | |