PhoenixKitCalendar.Participants (PhoenixKitCalendar v0.1.0)

Copy Markdown View Source

Attaching people to calendar events.

Authorization

Editing an event's participants requires edit access to the event (PhoenixKitCalendar.Events.can_edit?/2 semantics). Per-KIND gating on top (quorum-hardened, and validated HERE — never only in the UI):

kindrequires
usercalendar.invite_platform_users
staff_personcalendar.invite_staff + staff module enabled
crm_contact / crm_companycalendar.invite_crm + CRM module enabled
free_textnothing beyond event edit access

Replace semantics

replace_participants/3 is a full-replace-with-diff inside one transaction: rows not in the new set are deleted (visibility revoked immediately), new entries are inserted, unchanged rows are kept untouched. Only NEWLY added entries notify — each is live-resolved to a platform user (PhoenixKitCalendar.Sources.resolve_user/1) and logged with target_uuid, which core's notification system fans out to an in-app notification. Company adds create no per-member notifications (members see the event via live visibility instead).

Summary

Functions

Dedup/identity key for a participant entry — free-text keys on the downcased name, everything else on {kind, target_uuid}. This MUST mirror the DB partial-unique index; the LiveView's pending-chip dedup reuses it so the two can't drift.

Whether the scope may add participants of the given kind at all — drives which picker sources the UI offers (the context re-validates on save regardless).

Lists an event's participants (insertion order), authorized against the event's persisted owner. Returns [] unless the scope may read the event (owner-view or participant, module enabled) — so this public read honors the same boundary as Events.get_event/2 and can't be used to enumerate a disabled module's participants.

Replaces the event's participant set with entries ([%{kind, target_uuid, display_name}]), diffing against the current rows. Returns {:ok, participants} or {:error, :unauthorized} / {:error, changeset}.

Functions

entry_key(map)

@spec entry_key(map()) :: {String.t(), String.t()}

Dedup/identity key for a participant entry — free-text keys on the downcased name, everything else on {kind, target_uuid}. This MUST mirror the DB partial-unique index; the LiveView's pending-chip dedup reuses it so the two can't drift.

kind_allowed?(scope, kind)

@spec kind_allowed?(PhoenixKit.Users.Auth.Scope.t() | nil, String.t()) :: boolean()

Whether the scope may add participants of the given kind at all — drives which picker sources the UI offers (the context re-validates on save regardless).

list_for_event(scope, event)

Lists an event's participants (insertion order), authorized against the event's persisted owner. Returns [] unless the scope may read the event (owner-view or participant, module enabled) — so this public read honors the same boundary as Events.get_event/2 and can't be used to enumerate a disabled module's participants.

replace_participants(scope, event, entries)

@spec replace_participants(
  PhoenixKit.Users.Auth.Scope.t() | nil,
  PhoenixKitCalendar.Schemas.Event.t(),
  [map()]
) ::
  {:ok, [PhoenixKitCalendar.Schemas.Participant.t()]}
  | {:error,
     :not_found | :unauthorized | :invalid_participant | Ecto.Changeset.t()}

Replaces the event's participant set with entries ([%{kind, target_uuid, display_name}]), diffing against the current rows. Returns {:ok, participants} or {:error, :unauthorized} / {:error, changeset}.

Authorization: event edit access, plus the per-kind invite permission for every NEWLY ADDED entry (existing rows of a kind the editor can't grant are preserved — an editor without invite_crm can't add clients but doesn't silently strip someone else's).