mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
openspec: change contacts-caldav-server (CardDAV sync для контактов из почты)
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user