feat: implement initial ObsiGate application with backend API, indexing, search, and basic frontend.
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user