Seth commited on
Commit ·
5720112
1
Parent(s): d2b0e4f
update
Browse files- backend/app/main.py +243 -14
backend/app/main.py
CHANGED
|
@@ -359,9 +359,156 @@ def _is_unipile_nonsense_display_name(val: str, account_id_hint: str = "") -> bo
|
|
| 359 |
return True
|
| 360 |
if account_id_hint and s == account_id_hint.strip():
|
| 361 |
return True
|
|
|
|
|
|
|
| 362 |
return False
|
| 363 |
|
| 364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
def _extract_unipile_identity_from_layer(data: dict, account_id_hint: str = "") -> tuple[str, str]:
|
| 366 |
"""Read display name + avatar from one object (e.g. GET /accounts/{id} payload)."""
|
| 367 |
candidates_name = [
|
|
@@ -430,13 +577,47 @@ _MAILBOX_PROVIDER_SET = ("GOOGLE", "MICROSOFT", "OUTLOOK", "MAIL", "IMAP")
|
|
| 430 |
|
| 431 |
|
| 432 |
def _mailbox_account_display_name(row: UnipileAccount) -> str:
|
| 433 |
-
|
| 434 |
-
|
|
|
|
|
|
|
|
|
|
| 435 |
if n:
|
| 436 |
return n
|
| 437 |
return (row.label or "").strip() or "Mailbox"
|
| 438 |
|
| 439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
def _try_fetch_unipile_account_profile(account_id: str) -> dict:
|
| 441 |
"""
|
| 442 |
Optional enrichment call. If it fails, we just keep existing metadata.
|
|
@@ -1316,6 +1497,8 @@ async def patch_me_linkedin_prefs(
|
|
| 1316 |
async def unipile_mailbox_campaign_defaults(t: TenantContext = Depends(get_tenant_context)):
|
| 1317 |
db = t.db
|
| 1318 |
user_row = db.query(User).filter(User.id == t.user_id).first()
|
|
|
|
|
|
|
| 1319 |
rows = (
|
| 1320 |
db.query(UnipileAccount)
|
| 1321 |
.filter(
|
|
@@ -1326,6 +1509,32 @@ async def unipile_mailbox_campaign_defaults(t: TenantContext = Depends(get_tenan
|
|
| 1326 |
.order_by(UnipileAccount.created_at.desc(), UnipileAccount.id.desc())
|
| 1327 |
.all()
|
| 1328 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1329 |
accounts = []
|
| 1330 |
for r in rows:
|
| 1331 |
ident = _extract_unipile_identity(r.metadata_json or {})
|
|
@@ -1342,7 +1551,11 @@ async def unipile_mailbox_campaign_defaults(t: TenantContext = Depends(get_tenan
|
|
| 1342 |
"created_at": r.created_at.isoformat() if r.created_at else None,
|
| 1343 |
}
|
| 1344 |
)
|
| 1345 |
-
prof = (
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1346 |
default_ref = getattr(user_row, "default_mailbox_unipile_account_ref_id", None) if user_row else None
|
| 1347 |
valid_default = None
|
| 1348 |
if default_ref is not None and any(a["id"] == default_ref for a in accounts):
|
|
@@ -1365,7 +1578,7 @@ async def patch_me_mailbox_prefs(
|
|
| 1365 |
raise HTTPException(status_code=404, detail="User not found")
|
| 1366 |
patch = body.model_dump(exclude_unset=True)
|
| 1367 |
if "mailbox_profile_display_name" in patch:
|
| 1368 |
-
s = (patch["mailbox_profile_display_name"] or "").strip()
|
| 1369 |
u.mailbox_profile_display_name = s[:200] if s else None
|
| 1370 |
if "default_mailbox_unipile_account_ref_id" in patch:
|
| 1371 |
rid = patch["default_mailbox_unipile_account_ref_id"]
|
|
@@ -1406,7 +1619,7 @@ async def create_unipile_mailbox_hosted_link(
|
|
| 1406 |
expires_at = datetime.utcnow() + timedelta(minutes=20)
|
| 1407 |
u = t.db.query(User).filter(User.id == t.user_id).first()
|
| 1408 |
if u is not None and body.mailbox_profile_display_name is not None:
|
| 1409 |
-
s = (body.mailbox_profile_display_name or "").strip()
|
| 1410 |
if s:
|
| 1411 |
u.mailbox_profile_display_name = s[:200]
|
| 1412 |
state = UnipileHostedAuthState(
|
|
@@ -1482,9 +1695,16 @@ async def unipile_mailbox_hosted_callback(request: Request, db: Session = Depend
|
|
| 1482 |
.first()
|
| 1483 |
)
|
| 1484 |
user_row = db.query(User).filter(User.id == state.user_id).first()
|
| 1485 |
-
box_name =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1486 |
if user_row is not None and box_name:
|
| 1487 |
user_row.mailbox_profile_display_name = box_name[:200]
|
|
|
|
|
|
|
| 1488 |
nice_label = (state.label or "").strip() or "Mailbox"
|
| 1489 |
if box_name:
|
| 1490 |
nice_label = f"{box_name}'s mailbox"
|
|
@@ -1520,8 +1740,10 @@ async def unipile_mailbox_hosted_callback(request: Request, db: Session = Depend
|
|
| 1520 |
|
| 1521 |
@app.get("/api/unipile/mailbox/accounts")
|
| 1522 |
async def list_unipile_mailbox_accounts(t: TenantContext = Depends(get_tenant_context)):
|
|
|
|
|
|
|
| 1523 |
rows = (
|
| 1524 |
-
|
| 1525 |
.filter(
|
| 1526 |
UnipileAccount.tenant_id == t.tenant_id,
|
| 1527 |
UnipileAccount.user_id == t.user_id,
|
|
@@ -1530,6 +1752,15 @@ async def list_unipile_mailbox_accounts(t: TenantContext = Depends(get_tenant_co
|
|
| 1530 |
.order_by(UnipileAccount.created_at.desc(), UnipileAccount.id.desc())
|
| 1531 |
.all()
|
| 1532 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
out = []
|
| 1534 |
for r in rows:
|
| 1535 |
ident = _extract_unipile_identity(r.metadata_json or {})
|
|
@@ -3924,16 +4155,14 @@ async def generate_sequences(
|
|
| 3924 |
db.commit()
|
| 3925 |
|
| 3926 |
user_row = db.query(User).filter(User.id == t.user_id).first()
|
| 3927 |
-
mailbox_sig =
|
| 3928 |
linkedin_sig = ""
|
| 3929 |
if user_row:
|
| 3930 |
-
mailbox_sig = (
|
| 3931 |
-
(getattr(user_row, "mailbox_profile_display_name", None) or "").strip()
|
| 3932 |
-
or (user_row.name or "").strip()
|
| 3933 |
-
)
|
| 3934 |
linkedin_sig = (
|
| 3935 |
-
(
|
| 3936 |
-
|
|
|
|
|
|
|
| 3937 |
)
|
| 3938 |
|
| 3939 |
total_contacts = len(df)
|
|
|
|
| 359 |
return True
|
| 360 |
if account_id_hint and s == account_id_hint.strip():
|
| 361 |
return True
|
| 362 |
+
if _looks_like_email(s):
|
| 363 |
+
return True
|
| 364 |
return False
|
| 365 |
|
| 366 |
|
| 367 |
+
def _looks_like_email(val: str) -> bool:
|
| 368 |
+
s = (val or "").strip()
|
| 369 |
+
if not s or "@" not in s:
|
| 370 |
+
return False
|
| 371 |
+
return bool(re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", s))
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def _display_name_from_email_local(email: str) -> str:
|
| 375 |
+
"""Best-effort human label when UniPile only exposes the mailbox address (e.g. maya@co.com → Maya)."""
|
| 376 |
+
e = (email or "").strip()
|
| 377 |
+
if "@" not in e:
|
| 378 |
+
return ""
|
| 379 |
+
local = e.split("@", 1)[0].strip()
|
| 380 |
+
if not local:
|
| 381 |
+
return ""
|
| 382 |
+
parts = [p for p in re.split(r"[._+\-]+", local) if p and re.search(r"[A-Za-z]", p)]
|
| 383 |
+
if not parts:
|
| 384 |
+
return ""
|
| 385 |
+
return " ".join(p[:1].upper() + p[1:].lower() if len(p) > 1 else p.upper() for p in parts)
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def _mailbox_email_from_unipile_meta(meta: Any) -> str:
|
| 389 |
+
if not isinstance(meta, dict):
|
| 390 |
+
return ""
|
| 391 |
+
for layer in (meta.get("account_profile"), meta):
|
| 392 |
+
if not isinstance(layer, dict):
|
| 393 |
+
continue
|
| 394 |
+
cp = layer.get("connection_params")
|
| 395 |
+
if isinstance(cp, dict):
|
| 396 |
+
mail = cp.get("mail")
|
| 397 |
+
if isinstance(mail, dict):
|
| 398 |
+
for key in ("username", "imap_user", "smtp_user", "email"):
|
| 399 |
+
v = _safe_str(mail.get(key))
|
| 400 |
+
if _looks_like_email(v):
|
| 401 |
+
return v
|
| 402 |
+
for key in ("email", "username"):
|
| 403 |
+
v = _safe_str(layer.get(key))
|
| 404 |
+
if _looks_like_email(v):
|
| 405 |
+
return v
|
| 406 |
+
n = _safe_str(layer.get("name"))
|
| 407 |
+
if _looks_like_email(n):
|
| 408 |
+
return n
|
| 409 |
+
return ""
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
def _name_from_mailbox_label(label: str) -> str:
|
| 413 |
+
lab = (label or "").strip()
|
| 414 |
+
suffix = "'s mailbox"
|
| 415 |
+
if lab.endswith(suffix) and len(lab) > len(suffix):
|
| 416 |
+
cand = lab[: -len(suffix)].strip()
|
| 417 |
+
if cand and not _looks_like_email(cand) and not _is_unipile_nonsense_display_name(cand):
|
| 418 |
+
return cand
|
| 419 |
+
return ""
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def _try_fetch_mailbox_sender_display_name(account_id: str, mailbox_email: str = "") -> str:
|
| 423 |
+
"""
|
| 424 |
+
Gmail 'Send mail as' display name often appears on sent messages as from_attendee.display_name.
|
| 425 |
+
UniPile account `name` is frequently the mailbox address only.
|
| 426 |
+
"""
|
| 427 |
+
if not (account_id or "").strip():
|
| 428 |
+
return ""
|
| 429 |
+
acc_enc = requests.utils.quote(account_id.strip(), safe="")
|
| 430 |
+
want = (mailbox_email or "").strip().lower()
|
| 431 |
+
folder_candidates = ("SENT", "sent", "Sent", "Sent Items", "INBOX")
|
| 432 |
+
for folder in folder_candidates:
|
| 433 |
+
folder_enc = requests.utils.quote(folder, safe="")
|
| 434 |
+
st, data = _unipile_call(
|
| 435 |
+
"GET",
|
| 436 |
+
f"/api/v1/emails?account_id={acc_enc}&limit=15&folder={folder_enc}",
|
| 437 |
+
)
|
| 438 |
+
if st >= 400 or not isinstance(data, dict):
|
| 439 |
+
continue
|
| 440 |
+
items = data.get("items") or data.get("emails") or data.get("data") or []
|
| 441 |
+
if not isinstance(items, list):
|
| 442 |
+
continue
|
| 443 |
+
for item in items:
|
| 444 |
+
if not isinstance(item, dict):
|
| 445 |
+
continue
|
| 446 |
+
fa = item.get("from_attendee") or item.get("from") or {}
|
| 447 |
+
if not isinstance(fa, dict):
|
| 448 |
+
continue
|
| 449 |
+
dn = _safe_str(fa.get("display_name") or fa.get("name"))
|
| 450 |
+
ident = _safe_str(fa.get("identifier") or fa.get("email") or fa.get("address"))
|
| 451 |
+
if not dn or _looks_like_email(dn):
|
| 452 |
+
continue
|
| 453 |
+
if want and ident and ident.lower() != want:
|
| 454 |
+
continue
|
| 455 |
+
return dn
|
| 456 |
+
return ""
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def _resolve_mailbox_sender_name(
|
| 460 |
+
meta: Any,
|
| 461 |
+
*,
|
| 462 |
+
unipile_account_id: str = "",
|
| 463 |
+
label: str = "",
|
| 464 |
+
fetch_sent: bool = True,
|
| 465 |
+
) -> str:
|
| 466 |
+
"""Human sender name for mailbox UI + email sign-offs — never the raw mailbox address when avoidable."""
|
| 467 |
+
mailbox_email = _mailbox_email_from_unipile_meta(meta)
|
| 468 |
+
name = _extract_unipile_identity(meta if isinstance(meta, dict) else {})[0].strip()
|
| 469 |
+
if name and not _looks_like_email(name):
|
| 470 |
+
return name
|
| 471 |
+
if fetch_sent and unipile_account_id:
|
| 472 |
+
from_sent = _try_fetch_mailbox_sender_display_name(unipile_account_id, mailbox_email)
|
| 473 |
+
if from_sent:
|
| 474 |
+
return from_sent
|
| 475 |
+
from_label = _name_from_mailbox_label(label)
|
| 476 |
+
if from_label:
|
| 477 |
+
return from_label
|
| 478 |
+
if mailbox_email:
|
| 479 |
+
derived = _display_name_from_email_local(mailbox_email)
|
| 480 |
+
if derived:
|
| 481 |
+
return derived
|
| 482 |
+
return ""
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
def _sanitize_sender_signoff_name(val: str) -> str:
|
| 486 |
+
s = (val or "").strip()
|
| 487 |
+
if not s or _looks_like_email(s):
|
| 488 |
+
return ""
|
| 489 |
+
return s
|
| 490 |
+
|
| 491 |
+
|
| 492 |
+
def _mailbox_sender_name_for_user(db: Session, user_row: Optional[User]) -> str:
|
| 493 |
+
if not user_row:
|
| 494 |
+
return ""
|
| 495 |
+
pref = _sanitize_sender_signoff_name(getattr(user_row, "mailbox_profile_display_name", None) or "")
|
| 496 |
+
if pref:
|
| 497 |
+
return pref
|
| 498 |
+
ref_id = getattr(user_row, "default_mailbox_unipile_account_ref_id", None)
|
| 499 |
+
if ref_id is not None:
|
| 500 |
+
row = db.query(UnipileAccount).filter(UnipileAccount.id == int(ref_id)).first()
|
| 501 |
+
if row:
|
| 502 |
+
resolved = _resolve_mailbox_sender_name(
|
| 503 |
+
row.metadata_json or {},
|
| 504 |
+
unipile_account_id=row.unipile_account_id or "",
|
| 505 |
+
label=row.label or "",
|
| 506 |
+
)
|
| 507 |
+
if resolved:
|
| 508 |
+
return resolved
|
| 509 |
+
return _sanitize_sender_signoff_name(user_row.name or "")
|
| 510 |
+
|
| 511 |
+
|
| 512 |
def _extract_unipile_identity_from_layer(data: dict, account_id_hint: str = "") -> tuple[str, str]:
|
| 513 |
"""Read display name + avatar from one object (e.g. GET /accounts/{id} payload)."""
|
| 514 |
candidates_name = [
|
|
|
|
| 577 |
|
| 578 |
|
| 579 |
def _mailbox_account_display_name(row: UnipileAccount) -> str:
|
| 580 |
+
n = _resolve_mailbox_sender_name(
|
| 581 |
+
row.metadata_json if isinstance(row.metadata_json, dict) else {},
|
| 582 |
+
unipile_account_id=row.unipile_account_id or "",
|
| 583 |
+
label=row.label or "",
|
| 584 |
+
)
|
| 585 |
if n:
|
| 586 |
return n
|
| 587 |
return (row.label or "").strip() or "Mailbox"
|
| 588 |
|
| 589 |
|
| 590 |
+
def _refresh_mailbox_account_identity(
|
| 591 |
+
db: Session,
|
| 592 |
+
row: UnipileAccount,
|
| 593 |
+
user_row: Optional[User],
|
| 594 |
+
*,
|
| 595 |
+
update_user_pref: bool = False,
|
| 596 |
+
) -> None:
|
| 597 |
+
"""Re-fetch UniPile profile / sent-mail display name when stored metadata only has the email address."""
|
| 598 |
+
account_id = row.unipile_account_id or ""
|
| 599 |
+
if not account_id:
|
| 600 |
+
return
|
| 601 |
+
profile = _try_fetch_unipile_account_profile(account_id)
|
| 602 |
+
meta = dict(row.metadata_json) if isinstance(row.metadata_json, dict) else {}
|
| 603 |
+
if isinstance(profile, dict) and profile:
|
| 604 |
+
meta["account_profile"] = profile
|
| 605 |
+
resolved = _resolve_mailbox_sender_name(
|
| 606 |
+
meta,
|
| 607 |
+
unipile_account_id=account_id,
|
| 608 |
+
label=row.label or "",
|
| 609 |
+
fetch_sent=True,
|
| 610 |
+
)
|
| 611 |
+
row.metadata_json = meta
|
| 612 |
+
if resolved:
|
| 613 |
+
row.label = f"{resolved}'s mailbox"[:255]
|
| 614 |
+
row.updated_at = datetime.utcnow()
|
| 615 |
+
if update_user_pref and user_row is not None:
|
| 616 |
+
cur = _sanitize_sender_signoff_name(getattr(user_row, "mailbox_profile_display_name", None) or "")
|
| 617 |
+
if not cur:
|
| 618 |
+
user_row.mailbox_profile_display_name = resolved[:200]
|
| 619 |
+
|
| 620 |
+
|
| 621 |
def _try_fetch_unipile_account_profile(account_id: str) -> dict:
|
| 622 |
"""
|
| 623 |
Optional enrichment call. If it fails, we just keep existing metadata.
|
|
|
|
| 1497 |
async def unipile_mailbox_campaign_defaults(t: TenantContext = Depends(get_tenant_context)):
|
| 1498 |
db = t.db
|
| 1499 |
user_row = db.query(User).filter(User.id == t.user_id).first()
|
| 1500 |
+
if user_row is not None and _looks_like_email(getattr(user_row, "mailbox_profile_display_name", None) or ""):
|
| 1501 |
+
user_row.mailbox_profile_display_name = None
|
| 1502 |
rows = (
|
| 1503 |
db.query(UnipileAccount)
|
| 1504 |
.filter(
|
|
|
|
| 1509 |
.order_by(UnipileAccount.created_at.desc(), UnipileAccount.id.desc())
|
| 1510 |
.all()
|
| 1511 |
)
|
| 1512 |
+
default_ref = getattr(user_row, "default_mailbox_unipile_account_ref_id", None) if user_row else None
|
| 1513 |
+
identity_dirty = False
|
| 1514 |
+
for r in rows:
|
| 1515 |
+
meta = r.metadata_json if isinstance(r.metadata_json, dict) else {}
|
| 1516 |
+
raw_identity = _extract_unipile_identity(meta)[0]
|
| 1517 |
+
resolved_quick = _resolve_mailbox_sender_name(
|
| 1518 |
+
meta,
|
| 1519 |
+
unipile_account_id=r.unipile_account_id or "",
|
| 1520 |
+
label=r.label or "",
|
| 1521 |
+
fetch_sent=False,
|
| 1522 |
+
)
|
| 1523 |
+
if r.unipile_account_id and (_looks_like_email(raw_identity) or not resolved_quick):
|
| 1524 |
+
_refresh_mailbox_account_identity(
|
| 1525 |
+
db,
|
| 1526 |
+
r,
|
| 1527 |
+
user_row,
|
| 1528 |
+
update_user_pref=(
|
| 1529 |
+
default_ref is not None and int(default_ref) == int(r.id)
|
| 1530 |
+
or (default_ref is None and rows and int(r.id) == int(rows[0].id))
|
| 1531 |
+
),
|
| 1532 |
+
)
|
| 1533 |
+
identity_dirty = True
|
| 1534 |
+
if identity_dirty:
|
| 1535 |
+
db.commit()
|
| 1536 |
+
if user_row is not None:
|
| 1537 |
+
db.refresh(user_row)
|
| 1538 |
accounts = []
|
| 1539 |
for r in rows:
|
| 1540 |
ident = _extract_unipile_identity(r.metadata_json or {})
|
|
|
|
| 1551 |
"created_at": r.created_at.isoformat() if r.created_at else None,
|
| 1552 |
}
|
| 1553 |
)
|
| 1554 |
+
prof = _sanitize_sender_signoff_name(
|
| 1555 |
+
(getattr(user_row, "mailbox_profile_display_name", None) or "").strip() if user_row else ""
|
| 1556 |
+
)
|
| 1557 |
+
if not prof and user_row:
|
| 1558 |
+
prof = _mailbox_sender_name_for_user(db, user_row)
|
| 1559 |
default_ref = getattr(user_row, "default_mailbox_unipile_account_ref_id", None) if user_row else None
|
| 1560 |
valid_default = None
|
| 1561 |
if default_ref is not None and any(a["id"] == default_ref for a in accounts):
|
|
|
|
| 1578 |
raise HTTPException(status_code=404, detail="User not found")
|
| 1579 |
patch = body.model_dump(exclude_unset=True)
|
| 1580 |
if "mailbox_profile_display_name" in patch:
|
| 1581 |
+
s = _sanitize_sender_signoff_name((patch["mailbox_profile_display_name"] or "").strip())
|
| 1582 |
u.mailbox_profile_display_name = s[:200] if s else None
|
| 1583 |
if "default_mailbox_unipile_account_ref_id" in patch:
|
| 1584 |
rid = patch["default_mailbox_unipile_account_ref_id"]
|
|
|
|
| 1619 |
expires_at = datetime.utcnow() + timedelta(minutes=20)
|
| 1620 |
u = t.db.query(User).filter(User.id == t.user_id).first()
|
| 1621 |
if u is not None and body.mailbox_profile_display_name is not None:
|
| 1622 |
+
s = _sanitize_sender_signoff_name((body.mailbox_profile_display_name or "").strip())
|
| 1623 |
if s:
|
| 1624 |
u.mailbox_profile_display_name = s[:200]
|
| 1625 |
state = UnipileHostedAuthState(
|
|
|
|
| 1695 |
.first()
|
| 1696 |
)
|
| 1697 |
user_row = db.query(User).filter(User.id == state.user_id).first()
|
| 1698 |
+
box_name = _resolve_mailbox_sender_name(
|
| 1699 |
+
merged_meta,
|
| 1700 |
+
unipile_account_id=unipile_account_id,
|
| 1701 |
+
label=(state.label or "").strip(),
|
| 1702 |
+
fetch_sent=True,
|
| 1703 |
+
)
|
| 1704 |
if user_row is not None and box_name:
|
| 1705 |
user_row.mailbox_profile_display_name = box_name[:200]
|
| 1706 |
+
elif user_row is not None and _looks_like_email(getattr(user_row, "mailbox_profile_display_name", None) or ""):
|
| 1707 |
+
user_row.mailbox_profile_display_name = None
|
| 1708 |
nice_label = (state.label or "").strip() or "Mailbox"
|
| 1709 |
if box_name:
|
| 1710 |
nice_label = f"{box_name}'s mailbox"
|
|
|
|
| 1740 |
|
| 1741 |
@app.get("/api/unipile/mailbox/accounts")
|
| 1742 |
async def list_unipile_mailbox_accounts(t: TenantContext = Depends(get_tenant_context)):
|
| 1743 |
+
db = t.db
|
| 1744 |
+
user_row = db.query(User).filter(User.id == t.user_id).first()
|
| 1745 |
rows = (
|
| 1746 |
+
db.query(UnipileAccount)
|
| 1747 |
.filter(
|
| 1748 |
UnipileAccount.tenant_id == t.tenant_id,
|
| 1749 |
UnipileAccount.user_id == t.user_id,
|
|
|
|
| 1752 |
.order_by(UnipileAccount.created_at.desc(), UnipileAccount.id.desc())
|
| 1753 |
.all()
|
| 1754 |
)
|
| 1755 |
+
identity_dirty = False
|
| 1756 |
+
for r in rows:
|
| 1757 |
+
meta = r.metadata_json if isinstance(r.metadata_json, dict) else {}
|
| 1758 |
+
raw_identity = _extract_unipile_identity(meta)[0]
|
| 1759 |
+
if r.unipile_account_id and _looks_like_email(raw_identity):
|
| 1760 |
+
_refresh_mailbox_account_identity(db, r, user_row, update_user_pref=False)
|
| 1761 |
+
identity_dirty = True
|
| 1762 |
+
if identity_dirty:
|
| 1763 |
+
db.commit()
|
| 1764 |
out = []
|
| 1765 |
for r in rows:
|
| 1766 |
ident = _extract_unipile_identity(r.metadata_json or {})
|
|
|
|
| 4155 |
db.commit()
|
| 4156 |
|
| 4157 |
user_row = db.query(User).filter(User.id == t.user_id).first()
|
| 4158 |
+
mailbox_sig = _mailbox_sender_name_for_user(db, user_row)
|
| 4159 |
linkedin_sig = ""
|
| 4160 |
if user_row:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4161 |
linkedin_sig = (
|
| 4162 |
+
_sanitize_sender_signoff_name(
|
| 4163 |
+
(getattr(user_row, "linkedin_profile_display_name", None) or "").strip()
|
| 4164 |
+
)
|
| 4165 |
+
or _sanitize_sender_signoff_name((user_row.name or "").strip())
|
| 4166 |
)
|
| 4167 |
|
| 4168 |
total_contacts = len(df)
|