feat(backend): push notifications module (FCM HTTP v1)
New global push/ module mirroring mail/ and files/storage/: - PushProvider abstraction; default LogPushProvider (no delivery, logs), PUSH_PROVIDER=fcm switches to FcmPushProvider — Firebase Cloud Messaging HTTP v1, authenticated by a service-account JWT exchanged for an OAuth token (no extra dependency; jsonwebtoken does the signing). Prunes tokens FCM reports as invalid. - DeviceToken model (token + platform, bound to a User or GuestAccount), migration + added to the sync log. - POST /api/push/register + /unregister (any of the three token kinds). - PushService.notifyChannel() resolves a channel's readable audience (DIREKT participants / LT / Gemeinde members + guests / whole KC for broadcast), looks up their device tokens (minus the sender), sends. - ChatService.sendMessage() fires it best-effort after persisting. New env: PUSH_PROVIDER, FCM_PROJECT_ID (default konfi-castle-app), GOOGLE_APPLICATION_CREDENTIALS. Verified against local Postgres: register a token, send a Gemeinde-group chat message from another member -> log-push logs "would push ... to 1 device". Real FCM send needs the service-account JSON. npm test 56. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,7 @@ model User {
|
||||
memberships Membership[]
|
||||
messages ChatMessage[]
|
||||
chatParticipations ChatParticipant[]
|
||||
deviceTokens DeviceToken[]
|
||||
}
|
||||
|
||||
/// Scopes a User's role to a specific Kc (and Gemeinde, if applicable).
|
||||
@@ -107,10 +108,27 @@ model GuestAccount {
|
||||
lastName String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade)
|
||||
gemeinde Gemeinde? @relation(fields: [gemeindeId], references: [id], onDelete: Cascade)
|
||||
messages ChatMessage[]
|
||||
teilnehmer Teilnehmer[]
|
||||
kc Kc @relation(fields: [kcId], references: [id], onDelete: Cascade)
|
||||
gemeinde Gemeinde? @relation(fields: [gemeindeId], references: [id], onDelete: Cascade)
|
||||
messages ChatMessage[]
|
||||
teilnehmer Teilnehmer[]
|
||||
deviceTokens DeviceToken[]
|
||||
}
|
||||
|
||||
/// A push-notification target (FCM registration token) bound to whoever
|
||||
/// registered it — a team `User` or a `GuestAccount`. Replicated so a
|
||||
/// notification can be sent from either server.
|
||||
model DeviceToken {
|
||||
id String @id @default(cuid())
|
||||
token String @unique
|
||||
platform String
|
||||
userId String?
|
||||
guestAccountId String?
|
||||
createdAt DateTime @default(now())
|
||||
lastSeenAt DateTime @default(now())
|
||||
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
guestAccount GuestAccount? @relation(fields: [guestAccountId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
/// Invitation issued by a Gemeinde Verantwortliche/r so new Gemeinde Teamer
|
||||
|
||||
Reference in New Issue
Block a user