PhoenixKitCalendar.Sources (PhoenixKitCalendar v0.1.0)

Copy Markdown View Source

Search facade over the participant/location sources the event form can draw from — platform users (core), staff people, CRM contacts, CRM companies, and stored locations.

Standalone by construction

Every query here is SCHEMALESS against the physical tables, which exist in every install (they ship as core versioned migrations) — no sibling module's code is ever required, and an unused module's empty tables simply return nothing. A source is OFFERED only when its module is enabled (Permissions.feature_enabled?/1, which is false when the module code is absent) AND the viewer holds the matching invite sub-permission — the permission composes with enablement, it never substitutes for it.

Leak hygiene (quorum-hardened; browse mode added on the boss's call)

Searches cap results per source, return only {kind, target uuid, display name} — no phones or profile data beyond the label — and exclude soft-deleted rows (status = 'trashed'). An EMPTY query is "browse mode": the first page (same per-source cap) of each source the scope may search, so clicking the picker offers people before any typing. That exposes nothing a permitted search couldn't already enumerate — the per-source invite permissions remain the gate. A user's label falls back to their email when no name is set, matching how users are identified everywhere else in the admin (the person panel, the users list); pickers therefore expose emails exactly as far as the rest of the admin already does.

Summary

Functions

Which participant sources this scope may search. Order = display order.

Resolves the CANONICAL display name for a participant target from its source table — {:ok, name} or :error when the target doesn't exist (or is soft-deleted). Participants calls this at insert time so a client can never store a spoofed label or a fabricated uuid: whatever the picker claimed, the persisted name is what the source table says.

Stored locations for the form's datalist suggestions (name-ordered, capped). Empty when the locations module is unavailable.

Whether the locations module is available for the location picker.

Live-resolves the platform user a participant entry maps to right now — used for add-notifications. Companies fan out to nobody (their members see the event via live visibility, but are not individually notified).

Searches every source the scope may use. Returns {[{source, [%{kind, target_uuid, display_name}]}], has_more?} in source order. An empty query lists each source's first page (browse mode — the picker opens on click with people already offered); limit is the per-source page size and grows with the picker's "Load more".

A person's display label: "First Last", falling back to the email when no name is set. Shared by the search sources and the calendar's people panel so both render identities the same way.

Functions

available_participant_sources(scope)

@spec available_participant_sources(PhoenixKit.Users.Auth.Scope.t() | nil) :: [atom()]

Which participant sources this scope may search. Order = display order.

canonical_name(kind, target_uuid)

@spec canonical_name(String.t(), String.t()) :: {:ok, String.t()} | :error

Resolves the CANONICAL display name for a participant target from its source table — {:ok, name} or :error when the target doesn't exist (or is soft-deleted). Participants calls this at insert time so a client can never store a spoofed label or a fabricated uuid: whatever the picker claimed, the persisted name is what the source table says.

list_locations()

@spec list_locations() :: [%{uuid: String.t(), name: String.t()}]

Stored locations for the form's datalist suggestions (name-ordered, capped). Empty when the locations module is unavailable.

locations_available?()

@spec locations_available?() :: boolean()

Whether the locations module is available for the location picker.

resolve_user(arg1)

@spec resolve_user(map()) :: String.t() | nil

Live-resolves the platform user a participant entry maps to right now — used for add-notifications. Companies fan out to nobody (their members see the event via live visibility, but are not individually notified).

search_participants(scope, query, limit \\ 8)

@spec search_participants(
  PhoenixKit.Users.Auth.Scope.t() | nil,
  String.t(),
  pos_integer()
) ::
  {[{atom(), [map()]}], boolean()}

Searches every source the scope may use. Returns {[{source, [%{kind, target_uuid, display_name}]}], has_more?} in source order. An empty query lists each source's first page (browse mode — the picker opens on click with people already offered); limit is the per-source page size and grows with the picker's "Load more".

Deduplication

The same human often exists as a platform user AND a staff person AND a CRM contact (linked via user_uuid). Rows resolving to a user already listed by an earlier source are dropped — source order wins, so the user kind (the most direct visibility/notification link) shadows its staff/CRM mirrors, and a staff row shadows a CRM contact linking the same account. Rows with no user link are always kept.

user_label(map)

@spec user_label(%{
  optional(any()) => any(),
  first_name: String.t() | nil,
  last_name: String.t() | nil,
  email: String.t()
}) :: String.t()

A person's display label: "First Last", falling back to the email when no name is set. Shared by the search sources and the calendar's people panel so both render identities the same way.