From 3edcc99c615d49c1e5503821a27d5d5ba37ff110 Mon Sep 17 00:00:00 2001 From: Stefan Koelle Date: Mon, 10 Aug 2026 23:20:42 +0200 Subject: [PATCH] related contact details --- src/api/main.py | 26 ++++++++++++++++++++++++-- src/api/templates/contact.html | 4 +++- src/db.py | 12 ++++++++++++ src/vcard_parser.py | 5 ++++- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/api/main.py b/src/api/main.py index ad26e50..4fabe48 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -66,6 +66,25 @@ def _row_to_contact_out(row: dict, group_names: list[str] | None = None) -> dict return row +def _enrich_related_names(conn, contact: dict) -> None: + related = contact.get("related_names", []) + if not related: + return + uids = [r["value"] for r in related if r.get("value")] + if not uids: + return + resolved = db.resolve_related_names(conn, contact["account"], uids) + for r in related: + uid = r.get("value", "") + info = resolved.get(uid) + if info: + r["id"] = info["id"] + r["name"] = info["name"] + else: + r["id"] = None + r["name"] = uid + + def _account_filter_clause(account_name: str | None) -> tuple[str, list]: if account_name is None: return "", [] @@ -146,7 +165,9 @@ def get_contact(contact_id: int, current_user: str = Depends(get_current_user)): groups = db.get_groups_for_contact(conn, row["account"], row["uid"]) group_names = [g["name"] for g in groups if g.get("name")] - return _row_to_contact_out(row, group_names=group_names) + contact = _row_to_contact_out(row, group_names=group_names) + _enrich_related_names(conn, contact) + return contact @app.get("/api/contacts/birthdays/today", response_model=list[ContactOut]) @@ -638,7 +659,8 @@ def web_contact( groups = db.get_groups_for_contact(conn, row["account"], row["uid"]) group_names = [g["name"] for g in groups if g.get("name")] - contact = _row_to_contact_out(row, group_names=group_names) + contact = _row_to_contact_out(row, group_names=group_names) + _enrich_related_names(conn, contact) homecity = "" workcity = "" diff --git a/src/api/templates/contact.html b/src/api/templates/contact.html index a1b20e9..146d18a 100644 --- a/src/api/templates/contact.html +++ b/src/api/templates/contact.html @@ -443,7 +443,9 @@ {% for rn in contact.related_names %}
{{ rn.type }}
-
{{ rn.value }}
+
+ {% if rn.id %}{{ rn.name }}{% else %}{{ rn.name }}{% endif %} +
{% endfor %} diff --git a/src/db.py b/src/db.py index 4299e60..52d9bed 100644 --- a/src/db.py +++ b/src/db.py @@ -383,6 +383,18 @@ def get_groups_for_contact(conn, account: str, member_uid: str) -> list[dict]: return cur.fetchall() +def resolve_related_names(conn, account: str, uids: list[str]) -> dict[str, dict]: + if not uids: + return {} + placeholders = ", ".join(["%s"] * len(uids)) + with conn.cursor() as cur: + cur.execute( + f"SELECT uid, id, full_name FROM contacts WHERE account = %s AND uid IN ({placeholders})", + [account] + uids, + ) + return {row["uid"]: {"id": row["id"], "name": row["full_name"]} for row in cur.fetchall()} + + def get_group_count(conn, account: str | None) -> int: where_clause, params = _account_filter_clause(account) with conn.cursor() as cur: diff --git a/src/vcard_parser.py b/src/vcard_parser.py index 69c888f..2a040c5 100644 --- a/src/vcard_parser.py +++ b/src/vcard_parser.py @@ -130,9 +130,12 @@ def parse_vcard(raw_text: str, account: str, etag: str | None = None) -> dict | type_val = params.get("TYPE") or params.get("type") or "other" if isinstance(type_val, list): type_val = type_val[0] if type_val else "other" + value = r.value if r.value else "" + if value.startswith(_MEMBER_PREFIX): + value = value[len(_MEMBER_PREFIX):] related_names.append({ "type": type_val, - "value": r.value if r.value else "", + "value": value, }) photo_url = None