From 650d0def5190dba7e75fb4837d3ae0dce14ed841 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Fri, 3 Apr 2026 12:54:47 +0200 Subject: [PATCH] Fix Starface contact field mapping based on actual API response Read mapping: Match by 'name' field instead of 'displayKey' because many fields use USER_DEFINED as displayKey (street, city, state, comment). Actual name fields: firstname, familyname, company, phone, mobile, homephone, fax, e-mail, url, comment, street, city, postcode, state, country. Write mapping: Use correct 4-block structure (contact, address, telephone, email) with proper resourceKey values matching the Starface internal format. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Services/StarfaceApiClient.cs | 157 ++++++++++++------ 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs b/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs index 5ea40e72..6e07b8be 100644 --- a/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs +++ b/src/StarfaceOutlookSync/Services/StarfaceApiClient.cs @@ -284,7 +284,10 @@ namespace StarfaceOutlookSync.Services var contact = new UnifiedContact(); contact.StarfaceId = item["id"]?.ToString() ?? ""; - var attrs = new Dictionary(); + // Attribute per "name"-Feld mappen (zuverlaessiger als displayKey, + // weil viele Felder USER_DEFINED als displayKey haben) + var byName = new Dictionary(StringComparer.OrdinalIgnoreCase); + var byDisplayKey = new Dictionary(StringComparer.OrdinalIgnoreCase); var blocks = item["blocks"] as JArray; if (blocks != null) { @@ -294,79 +297,123 @@ namespace StarfaceOutlookSync.Services if (blockAttrs == null) continue; foreach (var attr in blockAttrs) { - var key = attr["displayKey"]?.ToString() ?? ""; + var name = attr["name"]?.ToString() ?? ""; + var displayKey = attr["displayKey"]?.ToString() ?? ""; var val = attr["value"]?.ToString() ?? ""; if (!string.IsNullOrEmpty(val)) - attrs[key] = val; + { + if (!string.IsNullOrEmpty(name)) + byName[name] = val; + // displayKey nur als Fallback (viele sind USER_DEFINED) + if (!string.IsNullOrEmpty(displayKey) && displayKey != "USER_DEFINED") + byDisplayKey[displayKey] = val; + } } } } - contact.FirstName = attrs.GetValueOrDefault("NAME", ""); - contact.LastName = attrs.GetValueOrDefault("SURNAME", ""); - contact.Company = attrs.GetValueOrDefault("COMPANY", ""); - contact.JobTitle = attrs.GetValueOrDefault("JOB_TITLE", ""); - contact.Email = attrs.GetValueOrDefault("EMAIL", ""); - contact.PhoneWork = attrs.GetValueOrDefault("OFFICE_PHONE_NUMBER", ""); - contact.PhoneMobile = attrs.GetValueOrDefault("MOBILE_PHONE_NUMBER", ""); - contact.PhoneHome = attrs.GetValueOrDefault("PRIVATE_PHONE_NUMBER", ""); - contact.Fax = attrs.GetValueOrDefault("FAX_NUMBER", ""); - contact.Street = attrs.GetValueOrDefault("STREET", ""); - contact.City = attrs.GetValueOrDefault("CITY", ""); - contact.PostalCode = attrs.GetValueOrDefault("POSTAL_CODE", ""); - contact.State = attrs.GetValueOrDefault("STATE", ""); - contact.Country = attrs.GetValueOrDefault("COUNTRY", ""); - contact.Website = attrs.GetValueOrDefault("URL", ""); - contact.Notes = attrs.GetValueOrDefault("NOTE", ""); - contact.Salutation = attrs.GetValueOrDefault("SALUTATION", ""); - contact.Title = attrs.GetValueOrDefault("TITLE", ""); - contact.Birthday = attrs.GetValueOrDefault("BIRTHDAY", ""); + // Primaer nach name-Feld mappen, Fallback auf displayKey + string Get(string name, string displayKey = null) + { + if (byName.TryGetValue(name, out var v)) return v; + if (displayKey != null && byDisplayKey.TryGetValue(displayKey, out v)) return v; + return ""; + } + + contact.FirstName = Get("firstname", "NAME"); + contact.LastName = Get("familyname", "SURNAME"); + contact.Company = Get("company", "COMPANY"); + contact.JobTitle = Get("jobtitle", "JOB_TITLE"); + contact.Email = Get("e-mail", "EMAIL"); + contact.PhoneWork = Get("phone", "PHONE_NUMBER"); + contact.PhoneMobile = Get("mobile", "MOBILE_PHONE_NUMBER"); + contact.PhoneHome = Get("homephone", "PRIVATE_PHONE_NUMBER"); + contact.Fax = Get("fax", "FAX_NUMBER"); + contact.Street = Get("street", "STREET"); + contact.City = Get("city", "CITY"); + contact.PostalCode = Get("postcode", "POSTAL_CODE"); + contact.State = Get("state", "STATE"); + contact.Country = Get("country", "COUNTRY"); + contact.Website = Get("url", "URL"); + contact.Notes = Get("comment", "NOTE"); + contact.Salutation = Get("salutation", "SALUTATION"); + contact.Title = Get("title", "TITLE"); + contact.Birthday = Get("birthday", "BIRTHDAY"); return contact; } private JObject MapToStarface(UnifiedContact contact) { - var attrs = new JArray(); - - void AddAttr(string displayKey, string name, string value) + JArray MakeAttrs(params (string displayKey, string name, string value)[] fields) { - if (!string.IsNullOrEmpty(value)) - attrs.Add(new JObject { ["displayKey"] = displayKey, ["name"] = name, ["value"] = value }); + var arr = new JArray(); + foreach (var (dk, n, v) in fields) + { + if (!string.IsNullOrEmpty(v)) + arr.Add(new JObject { ["displayKey"] = dk, ["name"] = n, ["value"] = v }); + } + return arr; } - AddAttr("NAME", "firstName", contact.FirstName); - AddAttr("SURNAME", "lastName", contact.LastName); - AddAttr("COMPANY", "company", contact.Company); - AddAttr("JOB_TITLE", "jobTitle", contact.JobTitle); - AddAttr("EMAIL", "email", contact.Email); - AddAttr("OFFICE_PHONE_NUMBER", "businessPhone", contact.PhoneWork); - AddAttr("MOBILE_PHONE_NUMBER", "mobilePhone", contact.PhoneMobile); - AddAttr("PRIVATE_PHONE_NUMBER", "homePhone", contact.PhoneHome); - AddAttr("FAX_NUMBER", "fax", contact.Fax); - AddAttr("STREET", "street", contact.Street); - AddAttr("CITY", "city", contact.City); - AddAttr("POSTAL_CODE", "postalCode", contact.PostalCode); - AddAttr("STATE", "state", contact.State); - AddAttr("COUNTRY", "country", contact.Country); - AddAttr("URL", "website", contact.Website); - AddAttr("NOTE", "notes", contact.Notes); - AddAttr("SALUTATION", "salutation", contact.Salutation); - AddAttr("TITLE", "title", contact.Title); - AddAttr("BIRTHDAY", "birthday", contact.Birthday); + // Block-Struktur wie von der Starface erwartet + var contactBlock = MakeAttrs( + ("NAME", "firstname", contact.FirstName), + ("SURNAME", "familyname", contact.LastName), + ("COMPANY", "company", contact.Company) + ); + + var addressBlock = MakeAttrs( + ("USER_DEFINED", "street", contact.Street), + ("POSTAL_CODE", "postcode", contact.PostalCode), + ("USER_DEFINED", "city", contact.City), + ("USER_DEFINED", "state", contact.State), + ("USER_DEFINED", "country", contact.Country) + ); + + var phoneBlock = MakeAttrs( + ("PHONE_NUMBER", "phone", contact.PhoneWork), + ("PRIVATE_PHONE_NUMBER", "homephone", contact.PhoneHome), + ("MOBILE_PHONE_NUMBER", "mobile", contact.PhoneMobile), + ("FAX_NUMBER", "fax", contact.Fax) + ); + + var emailBlock = MakeAttrs( + ("EMAIL", "e-mail", contact.Email), + ("URL", "url", contact.Website), + ("USER_DEFINED", "comment", contact.Notes) + ); + + var blocks = new JArray(); + blocks.Add(new JObject + { + ["name"] = "contact", + ["resourceKey"] = "de.vertico.starface.addressbook.block.label_contact", + ["attributes"] = contactBlock + }); + blocks.Add(new JObject + { + ["name"] = "address", + ["resourceKey"] = "de.vertico.starface.addressbook.block.label_address", + ["attributes"] = addressBlock + }); + blocks.Add(new JObject + { + ["name"] = "telephone", + ["resourceKey"] = "de.vertico.starface.addressbook.block.label_telephone", + ["attributes"] = phoneBlock + }); + blocks.Add(new JObject + { + ["name"] = "email", + ["resourceKey"] = "de.vertico.starface.addressbook.block.label_email", + ["attributes"] = emailBlock + }); return new JObject { ["id"] = contact.StarfaceId ?? "", - ["blocks"] = new JArray - { - new JObject - { - ["name"] = "contact", - ["resourceKey"] = "contact", - ["attributes"] = attrs - } - } + ["blocks"] = blocks }; }