6.2 KiB
contacts/carddav-sync Specification
Purpose
Односторонняя синхронизация контактов, извлечённых из подписей писем (pipeline contacts_extractor), в CardDAV-сервер Radicale (:5232), чтобы контакты были доступны клиентам (Android/DAVx5, десктопные клиенты).
ADDED Requirements
Requirement: REQ-CON-CARDDAV-001: Адресная книга в Radicale
MUST — в Radicale должна существовать адресная книга пользователя
estorozhenko (коллекция Контакты, доступ по CardDAV
/estorozhenko/Контакты/), созданная до начала синка.
Scenario: Проверка адресной книги
GIVEN Radicale запущен на :5232
WHEN выполняется curl -X PROPFIND -u estorozhenko:<pass> http://127.0.0.1:5232/estorozhenko/Контакты/
THEN возвращается HTTP 207 (Multi-Status) и в ответе есть
<D:resourcetype> с C:addressbook.
Requirement: REQ-CON-CARDDAV-002: vCard для каждого контакта
MUST — для каждого контакта из contacts.json (поле contacts),
имеющего поле email, в адресной книге лежит ровно одна vCard-карточка
(.vcf), идентифицируемая по UID, с полями: FN, EMAIL, TEL (если есть),
ORG (если есть), TITLE (если есть), ADR (если есть).
Scenario: Карточка создана
GIVEN в contacts.json есть контакт {email: "a@b.ru", full_name: "Иван"}
WHEN выполняется curl -X PROPFIND .../Контакты/...a@b.ru.vcf
THEN возвращается 200/207 и vCard содержит FN:Иван, EMAIL:a@b.ru.
Requirement: REQ-CON-CARDDAV-003: Повторная синхронизация идемпотентна
MUST — повторный запуск синка с теми же данными не создаёт дублей (vCard уже существует → только обновление по ETag, не новый ресурс).
Scenario: Двойной запуск
GIVEN синк выполнен один раз WHEN синк выполняется второй раз без изменений данных THEN количество ресурсов в адресной книге не изменяется.
Requirement: REQ-CON-CARDDAV-004: Обновление существующей карточки
MUST — при изменении данных контакта в contacts.json (например, у контакта появился телефон) карточка в Radicale обновляется (PUT с If-Match по ETag). Если клиент на телефоне уже изменил карточку (ETag не совпал) — изменения локальной базы не перезаписывают карточку молча; синк пропускает обновление и логирует конфликт (см. также REQ-CON-CARDDAV-006).
Scenario: Контакт обновлён локально
GIVEN у контакта в базе появился phone
WHEN выполняется синк
THEN карточка в Radicale содержит новый TEL.
Requirement: REQ-CON-CARDDAV-005: Удаление карточек
MUST — контакты, которых больше нет в contacts.json (поле contacts),
могут удаляться из адресной книги (DELETE). Удаление не выполняется по
умолчанию (флаг --prune-caldav); по умолчанию карточки без соответствующего
контакта остаются.
Scenario: Удаление по флагу
GIVEN контакт удалён из contacts.json
WHEN синк запущен с --prune-caldav
THEN соответствующая vCard удаляется из адресной книги.
Requirement: REQ-CON-CARDDAV-006: Неконфликтная работа с клиентами
MUST — синк должен использовать ETag (If-Match/If-None-Match) при PUT, чтобы не затирать изменения, сделанные клиентами на телефоне между запусками синка. При конфликте ETag — пропустить и записать предупреждение в лог (файл лога синка).
Scenario: Конфликт ETag
GIVEN клиент (DAVx5) изменил vCard на сервере после последнего синка WHEN выполняется синк с изменёнными локальными данными этого контакта THEN PUT возвращает 412, синк логирует конфликт и продолжает остальные карточки.
Requirement: REQ-CON-CARDDAV-007: Локальная база остаётся источником
MUST — contacts.json, contacts.vcf, index.json продолжают обновляться как раньше (источник истины для дедупликации и трекинга). CardDAV — цель синка, не замена локальной базе.
Scenario: Локальная база не затронута
GIVEN синк выполнен WHEN проверяется содержимое /opt/hermes/email/contacts/contacts.json THEN файл существует и содержит актуальную базу контактов.
Non-Goals
- Двусторонняя синхронизация (правки на телефоне не пишутся обратно в contacts.json).
- Миграция существующих .vcf из локальных файлов (contacts.vcf остаётся как есть, синк идёт из contacts.json).
- Синхронизация с внешними CardDAV/Google/Cloud — только локальный Radicale.