Add structure-scoped notifications

This commit is contained in:
Your Name 2026-08-21 11:35:38 -04:00
parent e7064a7c6a
commit 464774ef4b
4 changed files with 460 additions and 1 deletions

99
db.js
View file

@ -296,6 +296,100 @@ function setStructurePublic(structureId, isPublic) {
);
}
// ---- kernel notifications -------------------------------------------------
//
// Subscriptions are owned by the platform, not by structure databases. Each row
// is tied to exactly one structure and one logged-in Bliss user; send-time code
// re-checks canAccessStructure before handing anything to a push service.
function upsertNotificationSubscription({
structureId,
userId,
endpoint,
clientId,
subscription,
contentEncoding,
}) {
return db
.prepare(
`INSERT INTO notification_subscriptions
(structure_id, user_id, endpoint, client_id, subscription, content_encoding, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(structure_id, endpoint) DO UPDATE SET
user_id = excluded.user_id,
client_id = excluded.client_id,
subscription = excluded.subscription,
content_encoding = excluded.content_encoding,
updated_at = CURRENT_TIMESTAMP`,
)
.run(
structureId,
userId,
endpoint,
clientId == null ? null : String(clientId),
JSON.stringify(subscription),
contentEncoding === "aesgcm" ? "aesgcm" : "aes128gcm",
);
}
function deleteNotificationSubscription(structureId, endpoint) {
return db
.prepare(
`DELETE FROM notification_subscriptions
WHERE structure_id = ? AND endpoint = ?`,
)
.run(structureId, endpoint);
}
function getNotificationSubscriptions(structureId, userIds = null) {
if (userIds && userIds.length === 0) return [];
const rows = userIds
? db
.prepare(
`SELECT *
FROM notification_subscriptions
WHERE structure_id = ?
AND user_id IN (${userIds.map(() => "?").join(",")})`,
)
.all(structureId, ...userIds)
: db
.prepare(
`SELECT *
FROM notification_subscriptions
WHERE structure_id = ?`,
)
.all(structureId);
return rows.map((row) => ({
...row,
subscription: JSON.parse(row.subscription),
}));
}
function getSubscribedNotificationUserIds(structureId) {
return db
.prepare(
`SELECT DISTINCT user_id
FROM notification_subscriptions
WHERE structure_id = ?
ORDER BY user_id`,
)
.all(structureId)
.map((row) => row.user_id);
}
function getStructureMemberUserIds(structureId) {
return db
.prepare(
`SELECT user_id
FROM structure_members
WHERE structure_id = ?
ORDER BY user_id`,
)
.all(structureId)
.map((row) => row.user_id);
}
function createRoute(verb, path, structureId, handler) {
path = encodeURI(path);
@ -890,6 +984,11 @@ module.exports = {
removeMember,
setStructurePrivacy,
setStructurePublic,
upsertNotificationSubscription,
deleteNotificationSubscription,
getNotificationSubscriptions,
getSubscribedNotificationUserIds,
getStructureMemberUserIds,
createRoute,
getRoutes,
getRoute,