fix(05-review): IN-01 resolve actor display name in eventChangeDispatcher for D-02/D-03
This commit is contained in:
@@ -15,9 +15,9 @@
|
||||
* Fire-and-forget: sync correctness does not depend on push success.
|
||||
*/
|
||||
|
||||
import { ne } from 'drizzle-orm'
|
||||
import { eq, ne } from 'drizzle-orm'
|
||||
import { db } from '../db/client.js'
|
||||
import { pushSubscriptions } from '../db/schema.js'
|
||||
import { users, pushSubscriptions } from '../db/schema.js'
|
||||
import { dispatchPush } from './pushDispatcher.js'
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────────
|
||||
@@ -71,32 +71,32 @@ export function isMeaningfulChange(change: EventChange): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the notification title for a change.
|
||||
* Actor name is omitted here (we don't have it from userId alone in the fast path);
|
||||
* the copy follows the D-02 spec using generic "A member" as fallback. For the
|
||||
* two-person household the non-actor will always be informed by the other member's
|
||||
* action, so the body context is sufficient.
|
||||
* Builds the notification copy for a change (D-02/D-03).
|
||||
*
|
||||
* Note: actorName resolution (querying users table) is a future D-02 enhancement.
|
||||
* For MVP, use generic copy that still satisfies the acceptance criteria.
|
||||
* @param change - The detected calendar event change.
|
||||
* @param actorName - Display name of the actor (D-03: named in every notification).
|
||||
* Falls back to 'A family member' when the users row is absent.
|
||||
*/
|
||||
function buildCopy(
|
||||
change: EventChange,
|
||||
actorName: string,
|
||||
): { notifTitle: string; notifBody: string; navigate: string } {
|
||||
const eventTitle = change.title ?? change.uid
|
||||
|
||||
let notifTitle: string
|
||||
let notifBody: string
|
||||
|
||||
// D-02: event notifications show specifics — actor + title.
|
||||
// D-03: name the actor in every change notification.
|
||||
if (change.operation === 'create') {
|
||||
notifTitle = 'New calendar event'
|
||||
notifTitle = `${actorName} added an event`
|
||||
notifBody = eventTitle
|
||||
} else if (change.operation === 'delete') {
|
||||
notifTitle = 'Calendar event removed'
|
||||
notifTitle = `${actorName} removed an event`
|
||||
notifBody = eventTitle
|
||||
} else {
|
||||
// update
|
||||
notifTitle = 'Calendar event updated'
|
||||
notifTitle = `${actorName} updated an event`
|
||||
notifBody = eventTitle
|
||||
}
|
||||
|
||||
@@ -128,13 +128,24 @@ export async function dispatchEventChange(
|
||||
return
|
||||
}
|
||||
|
||||
// D-13: query push_subscriptions from MariaDB only
|
||||
// D-03: ne() filter excludes the actor at DB level; application-level filter
|
||||
// below provides defence-in-depth (also makes the mock-based tests deterministic).
|
||||
const allSubs = await db
|
||||
.select()
|
||||
.from(pushSubscriptions)
|
||||
.where(ne(pushSubscriptions.userId, actorUserId))
|
||||
// IN-01: resolve actor display name for D-02/D-03 notification copy.
|
||||
// Runs in parallel with subscription query for minimal latency.
|
||||
const [actorRows, allSubs] = await Promise.all([
|
||||
db
|
||||
.select({ displayName: users.displayName })
|
||||
.from(users)
|
||||
.where(eq(users.id, actorUserId))
|
||||
.limit(1),
|
||||
// D-13: query push_subscriptions from MariaDB only
|
||||
// D-03: ne() filter excludes the actor at DB level; application-level filter
|
||||
// below provides defence-in-depth (also makes the mock-based tests deterministic).
|
||||
db
|
||||
.select()
|
||||
.from(pushSubscriptions)
|
||||
.where(ne(pushSubscriptions.userId, actorUserId)),
|
||||
])
|
||||
|
||||
const actorName: string = actorRows[0]?.displayName ?? 'A family member'
|
||||
|
||||
// D-03: additional application-level actor exclusion (defence-in-depth)
|
||||
const subs = allSubs.filter((s) => s.userId !== actorUserId)
|
||||
@@ -143,7 +154,7 @@ export async function dispatchEventChange(
|
||||
return
|
||||
}
|
||||
|
||||
const { notifTitle, notifBody, navigate } = buildCopy(change)
|
||||
const { notifTitle, notifBody, navigate } = buildCopy(change, actorName)
|
||||
|
||||
// Fan out to all non-actor subscriptions (D-03 already enforced by ne() filter)
|
||||
for (const sub of subs) {
|
||||
|
||||
Reference in New Issue
Block a user