diff --git a/sql/schema.sql b/sql/schema.sql index 3e2d3d5..a82249f 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -20,6 +20,7 @@ CREATE TABLE IF NOT EXISTS contacts ( anniversary DATE NULL, notes TEXT NULL, photo_base64 LONGTEXT NULL, + photo_url VARCHAR(2048) NULL, emails JSON NULL, phones JSON NULL, addresses JSON NULL, diff --git a/src/api/main.py b/src/api/main.py index fbee19d..2295d54 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -76,7 +76,7 @@ def list_contacts( cur.execute( f"""SELECT id, account, uid, full_name, given_name, family_name, organization, - job_title, birthday, notes, emails, phones, addresses, urls, social_profiles, categories, updated_at + job_title, birthday, notes, photo_url, emails, phones, addresses, urls, social_profiles, categories, updated_at FROM contacts {where_clause}{search_clause} ORDER BY full_name LIMIT %s OFFSET %s""", @@ -98,7 +98,7 @@ def get_contact(contact_id: int, current_user: str = Depends(get_current_user)): with conn.cursor() as cur: cur.execute( f"""SELECT id, account, uid, full_name, given_name, family_name, organization, - job_title, birthday, notes, emails, phones, addresses, urls, social_profiles, categories, updated_at + job_title, birthday, notes, photo_url, emails, phones, addresses, urls, social_profiles, categories, updated_at FROM contacts {where_clause} {id_clause}""", params + [contact_id], ) @@ -121,7 +121,7 @@ def birthdays_today(current_user: str = Depends(get_current_user)): with conn.cursor() as cur: cur.execute( f"""SELECT id, account, uid, full_name, given_name, family_name, organization, - job_title, birthday, notes, emails, phones, addresses, urls, social_profiles, categories, updated_at + job_title, birthday, notes, photo_url, emails, phones, addresses, urls, social_profiles, categories, updated_at FROM contacts {where_clause} {month_day_clause} ORDER BY full_name""", params + [today.month, today.day], @@ -293,7 +293,7 @@ def web_contact( with conn.cursor() as cur: cur.execute( f"""SELECT id, account, uid, full_name, given_name, family_name, organization, - job_title, birthday, notes, emails, phones, addresses, urls, social_profiles, categories, updated_at + job_title, birthday, notes, photo_url, emails, phones, addresses, urls, social_profiles, categories, updated_at FROM contacts {where_clause} {id_clause}""", params + [contact_id], ) diff --git a/src/api/schemas.py b/src/api/schemas.py index 600ed68..c61e165 100644 --- a/src/api/schemas.py +++ b/src/api/schemas.py @@ -15,6 +15,7 @@ class ContactOut(BaseModel): job_title: str | None birthday: date | None notes: str | None + photo_url: str | None emails: list phones: list addresses: list diff --git a/src/api/templates/contact.html b/src/api/templates/contact.html index 93c0d75..4c2611e 100644 --- a/src/api/templates/contact.html +++ b/src/api/templates/contact.html @@ -65,6 +65,14 @@ border-bottom: 1px solid #eee; } + .contact-photo { + width: 80px; + height: 80px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; + } + .contact-name { font-size: 1.5rem; font-weight: 600; @@ -207,6 +215,9 @@ {% if is_admin and contact.account %} {{ contact.account }} {% endif %} + {% if contact.photo_url %} + Foto + {% endif %} {% if contact.birthday %} diff --git a/src/vcard_parser.py b/src/vcard_parser.py index af4af2d..7ede460 100644 --- a/src/vcard_parser.py +++ b/src/vcard_parser.py @@ -86,6 +86,19 @@ def parse_vcard(raw_text: str, account: str, etag: str | None = None) -> dict | categories = [c.strip() for c in vcard.categories.value] if hasattr(vcard, "categories") else [] + photo_url = None + photo_base64 = None + if hasattr(vcard, "photo"): + photo_obj = vcard.photo + params = getattr(photo_obj, "params", {}) + encoding = params.get("ENCODING") or params.get("encoding") + value = photo_obj.value + if isinstance(value, str) and value.startswith("http"): + photo_url = value + elif encoding and str(encoding).upper() == "B" and isinstance(value, bytes): + import base64 + photo_base64 = base64.b64encode(value).decode("ascii") + birthday = str(vcard.bday.value)[:10] if hasattr(vcard, "bday") else None org = None @@ -110,7 +123,8 @@ def parse_vcard(raw_text: str, account: str, etag: str | None = None) -> dict | "birthday": birthday, "anniversary": None, "notes": _scalar(_get(vcard, "note")), - "photo_base64": None, + "photo_base64": photo_base64, + "photo_url": photo_url, "emails": json.dumps(emails, ensure_ascii=False), "phones": json.dumps(phones, ensure_ascii=False), "addresses": json.dumps(addresses, ensure_ascii=False),