|
|
|
@@ -99,6 +99,42 @@ function nextId(prefix: string): string {
|
|
|
|
|
return `${prefix}-${++idCounter}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The STABLE user-principal prefix. A Participation stores its user (`fp:user`) as
|
|
|
|
|
// this principal derived from the login identifier — `urn:festipod:user:<key>` —
|
|
|
|
|
// NOT as the UserProfile's `did:ng:` document NURI. `currentUserId` is minted with
|
|
|
|
|
// the SAME prefix below, so a participation keyed on it stays consistent with the
|
|
|
|
|
// identity the SDK/caps derive. The single source of truth for the prefix, shared
|
|
|
|
|
// by the WRITE (currentUserId) and the READ (resolveParticipantUser) so they never
|
|
|
|
|
// drift.
|
|
|
|
|
const USER_PRINCIPAL_PREFIX = 'urn:festipod:user:';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resolve a Participation's `fp:user` to its UserProfile across the TWO id spaces
|
|
|
|
|
* that meet at this join (the root cause of the "unknown participant" bug):
|
|
|
|
|
* • a Participation stores `urn:festipod:user:<normalized-identifier>` (the stable
|
|
|
|
|
* principal = `currentUserId`), while
|
|
|
|
|
* • a UserProfile's `id` is its `did:ng:` document NURI — never that principal.
|
|
|
|
|
* The bridge is the NORMALIZED IDENTIFIER, which equals `normalizeIdentifier(username)`
|
|
|
|
|
* for the matching profile (the exact equality `currentUser` resolution already uses).
|
|
|
|
|
* So: strip the principal prefix off the participation's userId, and compare the
|
|
|
|
|
* remainder to `normalizeIdentifier(profile.username)`. In demo/local mode both sides
|
|
|
|
|
* are the bare seed id (`user-1`), matched directly by `u.id === userId` — which is
|
|
|
|
|
* why the direct match is tried FIRST (the seed username `@mariedupont` would not
|
|
|
|
|
* normalize to `user-1`). A per-deposit materializer uid (`mint...`, e.g.
|
|
|
|
|
* `mrktnoke-rzd699dk`) is a THIRD, unrelated space: it identifies an inbox deposit
|
|
|
|
|
* for the count, never a user — it does not participate in this join.
|
|
|
|
|
*/
|
|
|
|
|
function resolveParticipantUser(userId: string, users: FpUserData[]): FpUserData | undefined {
|
|
|
|
|
// 1) Direct id match — demo/local seed space (`user-1`), or any coincident space.
|
|
|
|
|
const direct = users.find(u => u.id === userId);
|
|
|
|
|
if (direct) return direct;
|
|
|
|
|
// 2) NG principal space: `urn:festipod:user:<key>` → match on normalized username.
|
|
|
|
|
const key = userId.startsWith(USER_PRINCIPAL_PREFIX)
|
|
|
|
|
? userId.slice(USER_PRINCIPAL_PREFIX.length)
|
|
|
|
|
: userId;
|
|
|
|
|
return users.find(u => u.username && normalizeIdentifier(u.username) === key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NG shape → app type mapping lives in `../data/shapeAdapters` (domain adapters
|
|
|
|
|
// over the SDK's `watchShape` subjects).
|
|
|
|
|
|
|
|
|
@@ -118,8 +154,18 @@ function buildQueries(
|
|
|
|
|
const getUser = (id: string) => users.find(u => u.id === id);
|
|
|
|
|
|
|
|
|
|
const getEventParticipants = (eventId: string) => {
|
|
|
|
|
const partUserIds = participations.filter(p => p.eventId === eventId).map(p => p.userId);
|
|
|
|
|
return users.filter(u => partUserIds.includes(u.id));
|
|
|
|
|
// Resolve each of the event's participations to its UserProfile across the two
|
|
|
|
|
// id spaces (participation principal vs profile NURI) — see
|
|
|
|
|
// `resolveParticipantUser`. A raw `partUserIds.includes(u.id)` join never
|
|
|
|
|
// matched in connected mode (principal ≠ NURI) → every participant rendered as
|
|
|
|
|
// "unknown". Deduped, returned in `users` order to match the prior contract.
|
|
|
|
|
const eventParts = participations.filter(p => p.eventId === eventId);
|
|
|
|
|
const resolvedIds = new Set<string>();
|
|
|
|
|
for (const p of eventParts) {
|
|
|
|
|
const u = resolveParticipantUser(p.userId, users);
|
|
|
|
|
if (u) resolvedIds.add(u.id);
|
|
|
|
|
}
|
|
|
|
|
return users.filter(u => resolvedIds.has(u.id));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getUserEvents = (userId: string) => {
|
|
|
|
@@ -462,7 +508,7 @@ function useNgData(): FestipodDataContextValue {
|
|
|
|
|
// participations keyed on it are consistent with reads and isolation. Falls
|
|
|
|
|
// back to the read profile's IRI only when there is no login (dev/demo).
|
|
|
|
|
const currentUserId =
|
|
|
|
|
(identifier ? `urn:festipod:user:${normalizeIdentifier(identifier)}` : (currentUser?.id || ''));
|
|
|
|
|
(identifier ? `${USER_PRINCIPAL_PREFIX}${normalizeIdentifier(identifier)}` : (currentUser?.id || ''));
|
|
|
|
|
// Identity-first log prefix, reused by every DATA log below (including the
|
|
|
|
|
// closures defined earlier in this function body — they only execute after
|
|
|
|
|
// this render has finished, by which point `logPrefix` is initialized).
|
|
|
|
|