refactor(dashboard): split api.py into handler modules
This commit is contained in:
120
dashboard/handlers/files.py
Normal file
120
dashboard/handlers/files.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""File-browser + note-index endpoints (sandbox-enforced)."""
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import constants
|
||||
|
||||
|
||||
class FilesHandlers:
|
||||
"""Mixin for /api/files, /api/refresh-index."""
|
||||
|
||||
def _resolve_sandboxed(self, path):
|
||||
"""Resolve `path` against ALLOWED_WORKSPACES. Returns (target, workspace) or (None, None)."""
|
||||
allowed_dirs = constants.ALLOWED_WORKSPACES
|
||||
for base in allowed_dirs:
|
||||
try:
|
||||
candidate = (base / path).resolve()
|
||||
if any(str(candidate).startswith(str(d)) for d in allowed_dirs):
|
||||
return candidate, base
|
||||
except Exception:
|
||||
continue
|
||||
return None, None
|
||||
|
||||
def handle_files_get(self):
|
||||
"""List files or get file content."""
|
||||
params = parse_qs(urlparse(self.path).query)
|
||||
path = params.get('path', [''])[0]
|
||||
action = params.get('action', ['list'])[0]
|
||||
|
||||
target, workspace = self._resolve_sandboxed(path)
|
||||
if target is None:
|
||||
self.send_json({'error': 'Access denied'}, 403)
|
||||
return
|
||||
|
||||
if action != 'list':
|
||||
self.send_json({'error': 'Unknown action'}, 400)
|
||||
return
|
||||
|
||||
if not target.exists():
|
||||
self.send_json({'error': 'Path not found'}, 404)
|
||||
return
|
||||
|
||||
if target.is_file():
|
||||
try:
|
||||
content = target.read_text(encoding='utf-8', errors='replace')
|
||||
self.send_json({
|
||||
'type': 'file',
|
||||
'path': path,
|
||||
'name': target.name,
|
||||
'content': content[:100000],
|
||||
'size': target.stat().st_size,
|
||||
'truncated': target.stat().st_size > 100000,
|
||||
})
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
else:
|
||||
items = []
|
||||
try:
|
||||
for item in sorted(target.iterdir()):
|
||||
stat = item.stat()
|
||||
item_path = f"{path}/{item.name}" if path else item.name
|
||||
items.append({
|
||||
'name': item.name,
|
||||
'type': 'dir' if item.is_dir() else 'file',
|
||||
'size': stat.st_size if item.is_file() else None,
|
||||
'mtime': stat.st_mtime,
|
||||
'path': item_path,
|
||||
})
|
||||
self.send_json({'type': 'dir', 'path': path, 'items': items})
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_files_post(self):
|
||||
"""Save file content."""
|
||||
try:
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length).decode('utf-8')
|
||||
data = json.loads(post_data)
|
||||
|
||||
path = data.get('path', '')
|
||||
content = data.get('content', '')
|
||||
|
||||
target, workspace = self._resolve_sandboxed(path)
|
||||
if target is None:
|
||||
self.send_json({'error': 'Access denied'}, 403)
|
||||
return
|
||||
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(content, encoding='utf-8')
|
||||
|
||||
self.send_json({'status': 'saved', 'path': path, 'size': len(content)})
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_refresh_index(self):
|
||||
"""Regenerate memory/kb/index.json by running tools/update_notes_index.py."""
|
||||
try:
|
||||
script = constants.TOOLS_DIR / 'update_notes_index.py'
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(script)],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
output = result.stdout
|
||||
total_match = re.search(r'with (\d+) notes', output)
|
||||
total = int(total_match.group(1)) if total_match else 0
|
||||
self.send_json({
|
||||
'success': True,
|
||||
'message': f'Index regenerat cu {total} notițe',
|
||||
'total': total,
|
||||
'output': output,
|
||||
})
|
||||
else:
|
||||
self.send_json({'success': False, 'error': result.stderr or 'Unknown error'}, 500)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.send_json({'success': False, 'error': 'Timeout'}, 500)
|
||||
except Exception as e:
|
||||
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||
Reference in New Issue
Block a user