refactor(pres_mgmt): make person_id optional in presenter sign-in URLs

Email-only presenters (no Person record) no longer have person_id in
their sign-in URLs — sign_in_out falls back to presenter_id and
expand_auth_for_person resolves identity via Dexie lookup.
Person-linked presenters still include person_id as before.

Removes the confusing case where person_id == event_presenter_id in
URLs for email-only presenters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-23 15:37:09 -04:00
parent 85870b67f5
commit c2e42e1c0a
3 changed files with 18 additions and 11 deletions

View File

@@ -595,7 +595,6 @@ export async function email_sign_in__event_presenter({
}) {
if (
!to_email ||
!person_id ||
!person_passcode ||
!event_id ||
!event_presenter_id
@@ -609,10 +608,14 @@ export async function email_sign_in__event_presenter({
// Routes to the session page (which has the sign-in handler mounted) not /presenter/[id]
// which has no sign-in handler. Includes presenter_id + presentation_id so the handler
// can grant presenter-level auth (not just session read access).
// Per-param encodeURIComponent is required for query values — encodeURI() does not
// encode '+', which URLSearchParams.get() then decodes as a space, breaking email
// addresses that contain '+' (e.g. test+alias@example.com).
const sign_in_url = `${base_url}/events/${event_id}/session/${event_session_id}?person_id=${encodeURIComponent(person_id ?? '')}&person_pass=${encodeURIComponent(person_passcode ?? '')}&presenter_id=${encodeURIComponent(event_presenter_id ?? '')}&presentation_id=${encodeURIComponent(event_presentation_id ?? '')}`;
// person_id is omitted for email-only presenters (no Person record) — the sign-in handler
// falls back to presenter_id and resolves their identity via Dexie lookup.
const sign_in_parts: string[] = [];
if (person_id) sign_in_parts.push(`person_id=${encodeURIComponent(person_id)}`);
sign_in_parts.push(`person_pass=${encodeURIComponent(person_passcode ?? '')}`);
sign_in_parts.push(`presenter_id=${encodeURIComponent(event_presenter_id ?? '')}`);
if (event_presentation_id) sign_in_parts.push(`presentation_id=${encodeURIComponent(event_presentation_id)}`);
const sign_in_url = `${base_url}/events/${event_id}/session/${event_session_id}?${sign_in_parts.join('&')}`;
const body_html = `<div>${to_name},<p>Your sign-in link for ${presentation_name ?? 'Presentation'} (Session: ${session_name ?? 'Session'}): <a href="${sign_in_url}">${sign_in_url}</a></p><p>This link takes you to the session page — your presentation and file upload sections will be available after you sign in.</p></div>`;
return await api.send_email({
api_cfg,

View File

@@ -193,7 +193,8 @@ let ae_tmp: key_val = $state({});
// Prefer Person-record email; fall back to presenter.email for
// presenters without a Person record (common for LCI/iMIS gaps).
const use_email = event_presenter_obj.person_primary_email ?? event_presenter_obj.email;
const use_person_id = event_presenter_obj.person_id ?? event_presenter_obj.event_presenter_id;
// null for email-only presenters — email function omits person_id from URL; sign-in derives identity from presenter_id
const use_person_id = event_presenter_obj.person_id;
const use_passcode = event_presenter_obj.person_passcode ?? event_presenter_obj.passcode;
if (!use_email) {

View File

@@ -57,11 +57,14 @@ onMount(() => {
let url_presenter_id = data.url.searchParams.get('presenter_id');
let url_session_id = data.url.searchParams.get('session_id');
// Gate on having a sign-in target (presenter or session), not on the passcode value.
// WHY: passcode may be empty/null for presenter records without one configured;
// the old gate `if (url_person_pass)` broke when passcode was empty string.
// The presenter_id / session_id in the URL is the real sign-in signal.
if (url_person_id !== null && (url_presenter_id || url_session_id)) {
// Gate on having a sign-in target (presenter or session) — person_id is optional.
// WHY: email-only presenters (no Person record) have no separate person_id to put
// in the URL. expand_auth_for_person resolves their identity via presenter_id_hint.
// For session POC links, person_id is always present (it's poc_person_id).
if (url_presenter_id || url_session_id) {
// person_id is absent for email-only presenter links — fall back to presenter_id.
// POC links always include person_id, so this branch is only hit for presenters.
if (!url_person_id) url_person_id = url_presenter_id ?? url_session_id;
console.log(
`ae_events_pres_mgmt session [slug] +page.svelte: event_session_id=${$events_slct.event_session_id}; person_id=${url_person_id}; person_pass=${url_person_pass}; presentation_id=${url_presentation_id}; presenter_id=${url_presenter_id}`
);