feat: POST /api/push/dav-bulk — push masivo por disco + 1 commit (segundos vs minutos)

Vía rápida DB→Xandikos para operaciones masivas: genera todos los vCards de agenda desde
DuckDB a un tmpdir, rsync de golpe al working tree de la colección en magnus (excluyendo
.git/.xandikos), UN solo git commit, y 1 PROPFIND para capturar todos los etags en batch.
~0.5s vs ~6min del push HTTP (que hace N PUTs + N PROPFINDs + N commits). El push HTTP
push_all_dav se mantiene como fallback (y para CalDAV). Config DAV_BULK_SSH_HOST/REMOTE_DIR.
22 tests verdes.
This commit is contained in:
2026-06-13 11:11:32 +02:00
parent 27e9be1ab7
commit 058180ea1a
5 changed files with 535 additions and 1 deletions
+30 -1
View File
@@ -131,7 +131,36 @@ Health check: `curl http://127.0.0.1:8771/api/health`.
| POST/PUT/DELETE | `/api/event[/{uid}]` | CRUD de eventos CalDAV. Push `caldav_put_event`/`dav_delete_resource` |
| POST | `/api/addressbook` | `{slug, display_name?, description?, color?}``dav_make_addressbook` + INSERT en `addressbooks` |
| POST | `/api/calendar` | `{slug, display_name?, color?}``dav_make_calendar` (paridad) |
| POST | `/api/push/dav` | reconcilia en bloque: recorre `contacts` y `events` de la DB y los empuja a Xandikos (PUT, sin borrar). Útil tras la migración |
| POST | `/api/push/dav` | reconcilia en bloque por HTTP: recorre `contacts` y `events` de la DB y los empuja a Xandikos (1 PUT + 1 PROPFIND + 1 commit git por recurso, sin borrar). Fallback cuando no hay SSH al host de Xandikos |
| POST | `/api/push/dav-bulk` | vía RÁPIDA del push de **contactos** por DISCO: genera todos los `.vcf` en un tmpdir local y hace **1 rsync + 1 commit + 1 PROPFIND** contra el working tree git que Xandikos sirve. Reconcilia ~1000 contactos en <1s en vez de ~6 min. Requiere SSH por clave |
### `/api/push/dav` (HTTP) vs `/api/push/dav-bulk` (disco)
Ambos vuelcan los contactos de la DB a Xandikos, pero por mecanismos distintos:
| | `/api/push/dav` (HTTP) | `/api/push/dav-bulk` (disco) |
|---|---|---|
| Mecanismo | N PUT WebDAV (uno por contacto) | 1 rsync de todos los `.vcf` a la vez |
| PROPFIND | 1 por contacto (lee el etag tras cada PUT) | 1 al final, lee todos los etags de golpe |
| Commits git en el remoto | N (Xandikos commitea cada PUT) | 1 (`git add -A && commit` único) |
| Coste para ~1000 contactos | ~6 min (≥1 PROPFIND completo/contacto) | <1s (dominado por 2-3 round-trips SSH) |
| Eventos CalDAV | sí (también empuja `events`) | no (solo `contacts`) |
| Borra huérfanos remotos | no | sí — `rsync --delete` deja la colección `.vcf` == DB |
| Requisitos | red HTTPS a Xandikos + `pass` | SSH por clave al host + `rsync` ambos lados |
**Usa `/api/push/dav-bulk`** para reconciliar en bloque (tras una migración o un
ingest masivo) cuando hay SSH al host de Xandikos: es el camino normal por
rapidez. **Usa `/api/push/dav`** como fallback cuando no hay SSH, o cuando
también necesitas empujar eventos CalDAV.
Cómo lo ve Xandikos: sirve cada colección desde el working tree de un repo git y
calcula el ctag desde el HEAD. El push por disco escribe los `.vcf` directamente
en ese working tree y hace un único commit; Xandikos ve el nuevo HEAD en el
siguiente request (nuevo ctag → DAVx5 detecta el cambio). El rsync sincroniza
SOLO los `*.vcf` (`--include='*.vcf' --exclude='*'`), preservando `.git/`,
`.xandikos` (tipo de colección) y `push-subscriptions.json` (suscripciones
WebDAV-Push). Config del host/working tree: `DAV_BULK_SSH_HOST` /
`DAV_BULK_REMOTE_DIR` en `server/config.py`.
Queries con nombre incluidas: `personas_por_contexto`, `personas_recientes`,
`eventos_proximos`, `contactos_sin_nota`, `stats_personas`,