feat: implement initial ObsiGate application with backend API, indexing, search, and basic frontend.

This commit is contained in:
2026-03-24 12:56:00 -04:00
parent d6ae501f51
commit c5e395005f
4 changed files with 318 additions and 2 deletions
+61
View File
@@ -7,8 +7,10 @@ import logging
import mimetypes
import secrets
import string
import time
from concurrent.futures import ThreadPoolExecutor
from contextlib import asynccontextmanager
from datetime import datetime
from functools import partial
from pathlib import Path
from typing import Optional, List, Dict, Any
@@ -614,6 +616,64 @@ async def api_vaults(current_user=Depends(require_auth)):
return result
def humanize_mtime(mtime: float) -> str:
delta = time.time() - mtime
if delta < 60: return "à l'instant"
if delta < 3600: return f"il y a {int(delta/60)} min"
if delta < 86400: return f"il y a {int(delta/3600)} h"
if delta < 604800: return f"il y a {int(delta/86400)} j"
return datetime.fromtimestamp(mtime).strftime("%d %b %Y")
@app.get("/api/recent")
async def api_recent(limit: Optional[int] = Query(None), vault: Optional[str] = Query(None), current_user=Depends(require_auth)):
config = _load_config()
actual_limit = limit if limit is not None else config.get("recent_files_limit", 20)
user_vaults = current_user.get("_token_vaults") or current_user.get("vaults", [])
all_files = []
for v_name, v_data in index.items():
if vault and v_name != vault:
continue
if "*" not in user_vaults and v_name not in user_vaults:
continue
for f in v_data.get("files", []):
all_files.append((v_name, f))
# Sort descending by ISO string "modified"
all_files.sort(key=lambda x: x[1].get("modified", ""), reverse=True)
recent = all_files[:actual_limit]
files_resp = []
for v_name, f in recent:
iso_modified = f.get("modified", "")
try:
mtime_dt = datetime.fromisoformat(iso_modified.replace("Z", "+00:00"))
mtime_val = mtime_dt.timestamp()
except Exception:
mtime_val = time.time()
files_resp.append({
"path": f["path"],
"title": f["title"],
"vault": v_name,
"mtime": mtime_val,
"mtime_human": humanize_mtime(mtime_val),
"mtime_iso": iso_modified,
"size_bytes": f.get("size", 0),
"tags": [f"#{t}" for t in f.get("tags", [])][:5],
"preview": f.get("content_preview", "")[:120]
})
return {
"files": files_resp,
"total": len(all_files),
"limit": actual_limit,
"generated_at": time.time()
}
@app.get("/api/browse/{vault_name}", response_model=BrowseResponse)
async def api_browse(vault_name: str, path: str = "", current_user=Depends(require_auth)):
"""Browse directories and files in a vault at a given path level.
@@ -1348,6 +1408,7 @@ _DEFAULT_CONFIG = {
"watcher_debounce": 2.0,
"tag_boost": 2.0,
"prefix_max_expansions": 50,
"recent_files_limit": 20,
}