import mimetypes import os from django.http import FileResponse, HttpResponseNotFound from django.views.decorators.csrf import ensure_csrf_cookie _FE_DIR_CACHE = None _INDEX_CACHE = None def _get_frontend_dir(): global _FE_DIR_CACHE if _FE_DIR_CACHE is None: import sys if getattr(sys, 'frozen', False): _FE_DIR_CACHE = os.path.join(sys._MEIPASS, 'backend', 'static', 'frontend') else: env_dir = os.environ.get('FRONTEND_DIR') if env_dir and os.path.isdir(env_dir): _FE_DIR_CACHE = env_dir else: _FE_DIR_CACHE = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'static', 'frontend', ) return _FE_DIR_CACHE def _get_index_path(): global _INDEX_CACHE if _INDEX_CACHE is None: p = os.path.join(_get_frontend_dir(), 'index.html') _INDEX_CACHE = p if os.path.isfile(p) else '' return _INDEX_CACHE def _serve_file(filepath): ct, _ = mimetypes.guess_type(filepath) return FileResponse(open(filepath, 'rb'), content_type=ct or 'application/octet-stream') @ensure_csrf_cookie def spa_serve(request, path=None): """Serve built React assets or fall back to index.html.""" fdir = _get_frontend_dir() req_path = (path or '').lstrip('/') if req_path: target = os.path.normpath(os.path.join(fdir, req_path)) if target.startswith(os.path.normpath(fdir)) and os.path.isfile(target): return _serve_file(target) index = _get_index_path() if index: return _serve_file(index) return HttpResponseNotFound('Frontend not built. Run build.py first.')