feat: FastAPI backend (vault osint + agenda/calendario Xandikos)
Reescribe el backend a FastAPI + uvicorn y añade los endpoints DAV
(CardDAV/CalDAV) sobre el servidor Xandikos, además de las vistas del vault
osint de Obsidian.
Vault (grupo obsidian del registry):
- /api/graph, /api/nodes, /api/node/{slug}, /api/attachment, /api/search,
/api/refresh. Allowlist estricta de path traversal en /api/attachment.
- Resolución de embeds por path relativo al vault y por basename (registry).
Xandikos (grupo dav del registry + pass_get_secret):
- /api/contacts, /api/contact/{uid} (CardDAV, parseo vCard a JSON).
- /api/calendar?from=&to= (CalDAV, parseo VEVENT a JSON, filtro por rango).
- Credencial vía pass dav/xandikos-enmanuel; degradación clara sin red (502/503).
Solo escucha en 127.0.0.1 (datos sensibles). 13 tests verdes (pytest).
frontend/README.md describe el montaje React+Vite+Mantine+sigma.js posterior.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: osint_web
|
||||
lang: py
|
||||
domain: tools
|
||||
domain: osint
|
||||
version: 0.1.0
|
||||
description: "App web local para explorar el vault OSINT de Obsidian: grafo sigma.js, tablas por tipo y fichas con galería de attachments. Backend Python stdlib que orquesta el grupo obsidian; escucha solo en 127.0.0.1 (datos sensibles)."
|
||||
tags: [osint, web, graph, sigma, obsidian, vault, dashboard, mantine]
|
||||
description: "App web local OSINT: explora el vault de Obsidian osint (grafo sigma.js, tablas por tipo, fichas con galería de attachments) y la agenda/calendario del servidor Xandikos (CardDAV/CalDAV). Backend FastAPI que orquesta los grupos obsidian y dav del registry; escucha solo en 127.0.0.1 (datos sensibles)."
|
||||
tags: [osint, web, sigma, graph, mantine, dav, obsidian, vault, dashboard]
|
||||
uses_functions:
|
||||
- build_obsidian_graph_py_obsidian
|
||||
- list_obsidian_notes_py_obsidian
|
||||
@@ -13,6 +13,10 @@ uses_functions:
|
||||
- resolve_obsidian_embed_py_obsidian
|
||||
- slugify_obsidian_name_py_obsidian
|
||||
- search_obsidian_notes_py_obsidian
|
||||
- dav_list_resources_py_infra
|
||||
- dav_get_resource_py_infra
|
||||
- split_vcards_py_infra
|
||||
- pass_get_secret_py_infra
|
||||
uses_types: []
|
||||
framework: "react-vite-mantine"
|
||||
entry_point: "server/main.py"
|
||||
@@ -20,75 +24,106 @@ dir_path: "projects/osint/apps/osint_web"
|
||||
repo_url: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/dataforge/osint_web"
|
||||
e2e_checks:
|
||||
- id: tests
|
||||
cmd: "../../../../python/.venv/bin/python3 -m pytest server -q"
|
||||
cmd: ".venv/bin/python -m pytest tests -q"
|
||||
timeout_s: 120
|
||||
- id: vault_missing
|
||||
cmd: "../../../../python/.venv/bin/python3 server/main.py --vault /no/existe"
|
||||
cmd: ".venv/bin/python server/main.py --vault /no/existe --port 0"
|
||||
expect_exit: 2
|
||||
timeout_s: 30
|
||||
---
|
||||
|
||||
## Qué es
|
||||
|
||||
App del issue 0172 (project `osint`). Lee directamente los `.md` del vault de
|
||||
Obsidian `~/Obsidian/osint` (sin BD intermedia — decisión KISS) y ofrece tres
|
||||
vistas: grafo explorable (sigma.js), tablas filtradas por tipo y fichas con la
|
||||
galería de attachments de cada nodo.
|
||||
App del issue 0172 (project `osint`). Combina dos fuentes de datos en un frontend
|
||||
web local:
|
||||
|
||||
Registry-first: el backend NO parsea el vault — orquesta las funciones del
|
||||
grupo de capacidad `obsidian` (`build_obsidian_graph`, `read_obsidian_note`,
|
||||
`resolve_obsidian_embed`, ...) declaradas en `uses_functions`.
|
||||
1. **El vault de Obsidian `~/Obsidian/osint`** (sin BD intermedia — decisión
|
||||
KISS): grafo explorable (sigma.js), tablas filtradas por tipo y fichas con la
|
||||
galería de attachments de cada nodo.
|
||||
2. **El servidor Xandikos** (CardDAV/CalDAV): agenda de contactos y calendario de
|
||||
eventos.
|
||||
|
||||
Registry-first: el backend NO parsea el vault ni habla DAV a mano — orquesta las
|
||||
funciones del grupo `obsidian` (`build_obsidian_graph`, `read_obsidian_note`,
|
||||
`resolve_obsidian_embed`, ...) y del grupo `dav` (`dav_list_resources`,
|
||||
`dav_get_resource`, `split_vcards`) más `pass_get_secret` para la credencial,
|
||||
todas declaradas en `uses_functions`.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Backend**: FastAPI + uvicorn (venv propio en `.venv/`, deps en
|
||||
`pyproject.toml`). Escucha solo en `127.0.0.1`.
|
||||
- **Frontend** (pendiente — otro agente): React + Vite + Mantine v9 +
|
||||
`@fn_library` + sigma.js / graphology. Ver `frontend/README.md`.
|
||||
|
||||
## Arrancar el backend
|
||||
|
||||
```bash
|
||||
cd projects/osint/apps/osint_web
|
||||
../../../../python/.venv/bin/python3 server/main.py --vault ~/Obsidian/osint --port 8470
|
||||
.venv/bin/python server/main.py --vault ~/Obsidian/osint --port 8470
|
||||
```
|
||||
|
||||
El servidor cachea el grafo en memoria al arrancar; `POST /api/refresh`
|
||||
re-escanea el vault bajo demanda (botón "refrescar" del frontend).
|
||||
re-escanea el vault bajo demanda (botón "refrescar" del frontend). Los datos DAV
|
||||
no se cachean (se piden a Xandikos en cada llamada).
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Método | Ruta | Devuelve |
|
||||
|---|---|---|
|
||||
| GET | `/api/health` | estado + nº de nodos/aristas cacheados |
|
||||
| GET | `/api/graph` | grafo completo `{nodes, edges}` para sigma.js |
|
||||
| GET | `/api/graph` | grafo completo `{nodes, edges, counts}` para sigma.js |
|
||||
| GET | `/api/nodes?tipo=persona` | filas de la tabla de ese tipo (id, label, tipo, frontmatter) |
|
||||
| GET | `/api/node/<slug>` | ficha: frontmatter + body Markdown + attachments + wikilinks |
|
||||
| GET | `/api/attachment?path=<rel>` | binario del attachment (path relativo al vault, allowlist) |
|
||||
| GET | `/api/search?q=...` | nodos cuyo contenido matchea la query |
|
||||
| GET | `/api/contacts` | contactos del addressbook Xandikos (CardDAV) a JSON |
|
||||
| GET | `/api/contact/<uid>` | un vCard concreto a JSON |
|
||||
| GET | `/api/calendar?from=&to=` | eventos del calendario Xandikos (CalDAV) en el rango |
|
||||
| POST | `/api/refresh` | re-escanea el vault y reconstruye la caché |
|
||||
|
||||
## Configuración Xandikos
|
||||
|
||||
- Base URL: `https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com`
|
||||
- Usuario: `enmanuel`; password en `pass dav/xandikos-enmanuel` (vía
|
||||
`pass_get_secret`, nunca hardcodeada).
|
||||
- Colecciones: `/enmanuel/contacts/addressbook/` (CardDAV),
|
||||
`/enmanuel/calendars/calendar/` (CalDAV).
|
||||
|
||||
## Seguridad
|
||||
|
||||
- El vault contiene datos personales sensibles (DNIs, fotos): el server escucha
|
||||
**solo en `127.0.0.1`** — no hay flag para exponerlo a red y NO es un service
|
||||
**solo en `127.0.0.1`** — `--host` distinto avisa en stderr y NO es un service
|
||||
desplegable a VPS (sin tag `service`).
|
||||
- `/api/attachment` bloquea path traversal: `realpath` del candidato debe quedar
|
||||
estrictamente bajo el `realpath` del vault; cualquier otro caso → 403.
|
||||
estrictamente bajo el `realpath` del vault; cualquier otro caso → 403 (404 si
|
||||
el path es legítimo dentro del vault pero el archivo no existe).
|
||||
- Vault inexistente al arrancar → error claro en stderr + exit 2 (nunca 500
|
||||
silencioso).
|
||||
- Sin red / Xandikos caído → los endpoints DAV devuelven `{"status":"error"}`
|
||||
con código 502/503, nunca un crash.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
cd projects/osint/apps/osint_web
|
||||
../../../../python/.venv/bin/python3 -m pytest server -q
|
||||
.venv/bin/python -m pytest tests -q
|
||||
```
|
||||
|
||||
Cubren el DoD backend del issue 0172: grafo golden, tabla por tipo, ficha con
|
||||
attachments, wikilink dangling (nodo fantasma), slug con acentos
|
||||
(`[[María del Mar Pérez]]` → `maria-del-mar-perez`), path traversal bloqueado,
|
||||
vault inexistente y un e2e HTTP contra el server real en puerto efímero.
|
||||
Cubren el DoD backend del issue 0172 + las extensiones DAV: grafo golden, tabla
|
||||
por tipo, ficha con attachments (embed por path), wikilink dangling (nodo
|
||||
fantasma), slug con acentos (`María del Mar` → `maria-del-mar`), path traversal
|
||||
bloqueado, attachment legítimo servido, vault inexistente con error claro,
|
||||
parseo vCard/iCalendar a JSON y degradación de los endpoints DAV sin red.
|
||||
|
||||
## Estado / pendiente
|
||||
|
||||
- **Hecho (fase 5b)**: scaffold del sub-repo + backend completo con tests.
|
||||
- **Pendiente (fase siguiente)**: `frontend/` React + Vite + Mantine v9 +
|
||||
`@fn_library` con sigma.js + graphology (GraphView, TablesView, NodeCard).
|
||||
Onboarding previsto: `pnpm dev` en `frontend/` + backend en 8470 → abrir
|
||||
`http://127.0.0.1:5173`.
|
||||
- **Hecho**: scaffold del sub-repo + backend FastAPI completo (vault + DAV) con
|
||||
13 tests verdes.
|
||||
- **Pendiente (siguiente agente)**: `frontend/` React + Vite + Mantine v9 +
|
||||
`@fn_library` con sigma.js + graphology (GraphView, TablesView, NodeCard,
|
||||
ContactsView, CalendarView). Onboarding previsto: backend en 8470 +
|
||||
`pnpm dev` en `frontend/` → abrir `http://127.0.0.1:5173`. Ver
|
||||
`frontend/README.md`.
|
||||
- Cuando exista el manifest de sub-repos del project (issue 0171), añadir esta
|
||||
app a `projects/osint/subrepos.yaml`.
|
||||
|
||||
Reference in New Issue
Block a user