feat: Kontakte mit Outlook-Feldern + CardDAV-Server + Sharing
Komplette Kontakte-Ueberarbeitung analog zum Kalender-Ausbau.
Backend-Model:
* AddressBook: color (pro Buch), ausserdem Per-User-Color via
AddressBookShare.color wie bei CalendarShare.
* Contact: volle Outlook-artige Struktur - prefix/first/middle/
last/suffix, display_name, nickname, organization, department,
job_title, birthday, anniversary, notes, photo sowie JSON-
Spalten fuer mehrfach vorhandene Felder (emails, phones,
addresses mit allen Adressteilen, websites, impp, categories).
Backend-API:
* REST CRUD uebernimmt die neuen Felder und generiert vCard 3.0
als Source of Truth fuer CardDAV. Voller vCard-Parser +
-Builder mit Escape/Unescape, TYPE-Parametern, Line-Folding.
* Neuer Endpoint PUT /addressbooks/<id>/my-color - persoenliche
Farbe pro Buch ohne den Besitzer zu beeinflussen.
* SSE-Events vom Typ 'addressbook' an Besitzer + alle Share-
Empfaenger bei jeder Aenderung.
CardDAV-Server (backend/app/dav/carddav.py):
* Volle Discovery via principal - addressbook-home-set wird
neben calendar-home-set annonciert.
* PROPFIND/REPORT/GET/PUT/DELETE/MKCOL fuer
/dav/<user>/ab-<id>/ und /<...>/{uid}.vcf
* addressbook-query + addressbook-multiget REPORTs
* ETag-basierte Konfliktpruefung via If-Match/If-None-Match
Frontend (ContactsView.vue):
* Komplett neuer Editor mit vier Tabs: Allgemein (Name, Org),
Kommunikation (Emails/Phones/Websites/IMPP dynamisch),
Adressen (mehrere mit allen Teilen), Details (Geburtstag,
Jahrestag, Kategorien, Notizen).
* Avatar mit Fotoauswahl oder Initialen-Farbkreis.
* Kalender-Sharing-Flow 1:1 uebernommen: Autocomplete fuer
Benutzersuche, Share-Liste mit Stift zum Bearbeiten, Muelleimer
zum Entfernen, Per-User-Farbe, CardDAV-URL-Info-Block pro
Adressbuch, Live-Refresh via SSE.
* Suche durchsucht Displayname, E-Mail und Firma.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -180,6 +180,10 @@ def _principal_response(user: User) -> ET.Element:
|
||||
ET.SubElement(pu, _qn('d', 'href')).text = href
|
||||
home = ET.SubElement(prop, _qn('c', 'calendar-home-set'))
|
||||
ET.SubElement(home, _qn('d', 'href')).text = href
|
||||
# CardDAV address-book home set - same principal URL, addressbook
|
||||
# collections live next to calendars under /dav/<username>/
|
||||
ab_home = ET.SubElement(prop, '{urn:ietf:params:xml:ns:carddav}addressbook-home-set')
|
||||
ET.SubElement(ab_home, _qn('d', 'href')).text = href
|
||||
return _make_response(href, populate)
|
||||
|
||||
|
||||
@@ -265,7 +269,7 @@ def propfind(subpath=''):
|
||||
multistatus.append(_principal_response(user))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/ : principal + list calendars
|
||||
# /dav/<username>/ : principal + list calendars AND addressbooks
|
||||
if len(parts) == 1:
|
||||
if parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
@@ -273,6 +277,11 @@ def propfind(subpath=''):
|
||||
if depth != '0':
|
||||
for cal in _user_calendars(user):
|
||||
multistatus.append(_calendar_response(user, cal))
|
||||
# Addressbooks live next to calendars. Import here to avoid a
|
||||
# circular import at module load time.
|
||||
from .carddav import _addressbook_response, _user_addressbooks
|
||||
for ab in _user_addressbooks(user):
|
||||
multistatus.append(_addressbook_response(user, ab))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/cal-<id>/ : calendar + events
|
||||
|
||||
Reference in New Issue
Block a user