@rpcbase/server 0.538.0 → 0.540.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/email-DK8uUU4X.js +8045 -0
  2. package/dist/email-DK8uUU4X.js.map +1 -0
  3. package/dist/handler--FFBJMl6.js +153 -0
  4. package/dist/handler--FFBJMl6.js.map +1 -0
  5. package/dist/handler-0rPClEv4.js +663 -0
  6. package/dist/handler-0rPClEv4.js.map +1 -0
  7. package/dist/handler-COnCnprN.js +203 -0
  8. package/dist/handler-COnCnprN.js.map +1 -0
  9. package/dist/handler-ClQF4MOn.js +931 -0
  10. package/dist/handler-ClQF4MOn.js.map +1 -0
  11. package/dist/index.js +4988 -4830
  12. package/dist/index.js.map +1 -1
  13. package/dist/notifications.js +199 -134
  14. package/dist/notifications.js.map +1 -1
  15. package/dist/queryExecutor-Bol_iR8f.js +453 -0
  16. package/dist/queryExecutor-Bol_iR8f.js.map +1 -0
  17. package/dist/render_resend_false-MiC__Smr.js +6 -0
  18. package/dist/render_resend_false-MiC__Smr.js.map +1 -0
  19. package/dist/rts/index.d.ts +0 -1
  20. package/dist/rts/index.d.ts.map +1 -1
  21. package/dist/rts/index.js +1003 -842
  22. package/dist/rts/index.js.map +1 -1
  23. package/dist/schemas-Cjdjgehl.js +4225 -0
  24. package/dist/schemas-Cjdjgehl.js.map +1 -0
  25. package/dist/shared-nE84Or5W.js +111 -0
  26. package/dist/shared-nE84Or5W.js.map +1 -0
  27. package/dist/ssrMiddleware.d.ts +1 -1
  28. package/dist/uploads.js +99 -84
  29. package/dist/uploads.js.map +1 -1
  30. package/package.json +9 -9
  31. package/dist/email-H8nTAGxe.js +0 -12449
  32. package/dist/email-H8nTAGxe.js.map +0 -1
  33. package/dist/handler-BBzEodA0.js +0 -182
  34. package/dist/handler-BBzEodA0.js.map +0 -1
  35. package/dist/handler-BLwgdQv-.js +0 -544
  36. package/dist/handler-BLwgdQv-.js.map +0 -1
  37. package/dist/handler-CZD5p1Jv.js +0 -28
  38. package/dist/handler-CZD5p1Jv.js.map +0 -1
  39. package/dist/handler-Cq6MsoD4.js +0 -124
  40. package/dist/handler-Cq6MsoD4.js.map +0 -1
  41. package/dist/handler-DBtnVvP2.js +0 -756
  42. package/dist/handler-DBtnVvP2.js.map +0 -1
  43. package/dist/queryExecutor-JadZcQSQ.js +0 -318
  44. package/dist/queryExecutor-JadZcQSQ.js.map +0 -1
  45. package/dist/render_resend-DQANggpW.js +0 -7
  46. package/dist/render_resend-DQANggpW.js.map +0 -1
  47. package/dist/rts/api/cleanup/handler.d.ts +0 -9
  48. package/dist/rts/api/cleanup/handler.d.ts.map +0 -1
  49. package/dist/rts/api/cleanup/index.d.ts +0 -11
  50. package/dist/rts/api/cleanup/index.d.ts.map +0 -1
  51. package/dist/schemas-BR3K5Luo.js +0 -3824
  52. package/dist/schemas-BR3K5Luo.js.map +0 -1
  53. package/dist/shared-DhZ_rDdo.js +0 -87
  54. package/dist/shared-DhZ_rDdo.js.map +0 -1
@@ -0,0 +1,663 @@
1
+ import { models } from "@rpcbase/db";
2
+ import { buildAbilityFromSession, getAccessibleByQuery } from "@rpcbase/db/acl";
3
+ import { createNotification, sendNotificationsDigestForUser } from "./notifications.js";
4
+ import { o as object, b as boolean, n as number, a as array, s as string, r as record, _ as _enum, u as unknown } from "./schemas-Cjdjgehl.js";
5
+ const getSessionUser = (ctx) => {
6
+ const rawSessionUser = ctx.req.session?.user;
7
+ const userId = typeof rawSessionUser?.id === "string" ? rawSessionUser.id.trim() : "";
8
+ const tenantId = typeof rawSessionUser?.currentTenantId === "string" ? rawSessionUser.currentTenantId.trim() : "";
9
+ if (!userId) {
10
+ ctx.res.status(401);
11
+ return null;
12
+ }
13
+ if (!tenantId) {
14
+ ctx.res.status(400);
15
+ return null;
16
+ }
17
+ return {
18
+ userId,
19
+ tenantId
20
+ };
21
+ };
22
+ const ListRoute = "/api/rb/notifications";
23
+ const CreateRoute = "/api/rb/notifications/create";
24
+ const MarkReadRoute = "/api/rb/notifications/:notificationId/read";
25
+ const ArchiveRoute = "/api/rb/notifications/:notificationId/archive";
26
+ const MarkAllReadRoute = "/api/rb/notifications/mark-all-read";
27
+ const SettingsRoute = "/api/rb/notifications/settings";
28
+ const DigestRunRoute = "/api/rb/notifications/digest/run";
29
+ const listRequestSchema = object({
30
+ includeArchived: boolean().optional(),
31
+ unreadOnly: boolean().optional(),
32
+ limit: number().int().min(1).max(200).optional(),
33
+ markSeen: boolean().optional()
34
+ });
35
+ const createRequestSchema = object({
36
+ topic: string().trim().min(1).optional(),
37
+ title: string().trim().min(1),
38
+ body: string().trim().optional(),
39
+ url: string().trim().optional(),
40
+ metadata: record(string(), unknown()).optional()
41
+ });
42
+ const notificationSchema = object({
43
+ id: string(),
44
+ topic: string().optional(),
45
+ title: string(),
46
+ body: string().optional(),
47
+ url: string().optional(),
48
+ createdAt: string(),
49
+ seenAt: string().optional(),
50
+ readAt: string().optional(),
51
+ archivedAt: string().optional(),
52
+ metadata: record(string(), unknown()).optional()
53
+ });
54
+ const listResponseSchema = object({
55
+ ok: boolean(),
56
+ error: string().optional(),
57
+ notifications: array(notificationSchema).optional(),
58
+ unreadCount: number().int().min(0).optional(),
59
+ unseenCount: number().int().min(0).optional()
60
+ });
61
+ const createResponseSchema = object({
62
+ ok: boolean(),
63
+ error: string().optional(),
64
+ id: string().optional()
65
+ });
66
+ object({
67
+ ok: boolean(),
68
+ error: string().optional()
69
+ });
70
+ object({
71
+ ok: boolean(),
72
+ error: string().optional()
73
+ });
74
+ object({
75
+ ok: boolean(),
76
+ error: string().optional()
77
+ });
78
+ const digestFrequencySchema = _enum(["off", "daily", "weekly"]);
79
+ const topicPreferenceSchema = object({
80
+ topic: string(),
81
+ inApp: boolean(),
82
+ emailDigest: boolean(),
83
+ push: boolean()
84
+ });
85
+ const settingsSchema = object({
86
+ digestFrequency: digestFrequencySchema,
87
+ topicPreferences: array(topicPreferenceSchema),
88
+ lastDigestSentAt: string().optional()
89
+ });
90
+ const settingsResponseSchema = object({
91
+ ok: boolean(),
92
+ error: string().optional(),
93
+ settings: settingsSchema.optional()
94
+ });
95
+ const updateSettingsRequestSchema = object({
96
+ digestFrequency: digestFrequencySchema.optional(),
97
+ topicPreferences: array(topicPreferenceSchema).optional()
98
+ });
99
+ const updateSettingsResponseSchema = object({
100
+ ok: boolean(),
101
+ error: string().optional(),
102
+ settings: settingsSchema.optional()
103
+ });
104
+ const digestRunRequestSchema = object({
105
+ force: boolean().optional()
106
+ });
107
+ object({
108
+ ok: boolean(),
109
+ error: string().optional(),
110
+ sent: boolean().optional(),
111
+ skippedReason: string().optional()
112
+ });
113
+ const toIso = (value) => value instanceof Date ? value.toISOString() : void 0;
114
+ const buildDisabledTopics = (settings, key) => {
115
+ const raw = settings?.topicPreferences;
116
+ if (!Array.isArray(raw) || raw.length === 0) return [];
117
+ return raw.map((pref) => {
118
+ if (!pref || typeof pref !== "object") return null;
119
+ const topic = typeof pref.topic === "string" ? pref.topic.trim() : "";
120
+ if (!topic) return null;
121
+ const enabled = pref[key] === true;
122
+ return enabled ? null : topic;
123
+ }).filter((topic) => Boolean(topic));
124
+ };
125
+ const listNotifications = async (payload, ctx) => {
126
+ const session = getSessionUser(ctx);
127
+ if (!session) {
128
+ return {
129
+ ok: false,
130
+ error: "unauthorized"
131
+ };
132
+ }
133
+ const ability = buildAbilityFromSession({
134
+ tenantId: session.tenantId,
135
+ session: ctx.req.session
136
+ });
137
+ if (!ability.can("read", "RBNotification")) {
138
+ ctx.res.status(403);
139
+ return {
140
+ ok: false,
141
+ error: "forbidden"
142
+ };
143
+ }
144
+ const parsed = listRequestSchema.safeParse(payload);
145
+ if (!parsed.success) {
146
+ ctx.res.status(400);
147
+ return {
148
+ ok: false,
149
+ error: "invalid_payload"
150
+ };
151
+ }
152
+ const {
153
+ userId
154
+ } = session;
155
+ const includeArchived = parsed.data.includeArchived === true;
156
+ const unreadOnly = parsed.data.unreadOnly === true;
157
+ const limit = parsed.data.limit ?? 50;
158
+ const markSeen = parsed.data.markSeen === true;
159
+ const SettingsModel = await models.get("RBNotificationSettings", {
160
+ req: ctx.req,
161
+ ability
162
+ });
163
+ const settings = await SettingsModel.findOne({
164
+ userId
165
+ }).lean();
166
+ const disabledTopics = buildDisabledTopics(settings, "inApp");
167
+ const NotificationModel = await models.get("RBNotification", {
168
+ req: ctx.req,
169
+ ability
170
+ });
171
+ const queryFilters = [{
172
+ userId
173
+ }, getAccessibleByQuery(ability, "read", "RBNotification")];
174
+ if (!includeArchived) queryFilters.push({
175
+ archivedAt: {
176
+ $exists: false
177
+ }
178
+ });
179
+ if (unreadOnly) queryFilters.push({
180
+ readAt: {
181
+ $exists: false
182
+ }
183
+ });
184
+ if (disabledTopics.length > 0) queryFilters.push({
185
+ topic: {
186
+ $nin: disabledTopics
187
+ }
188
+ });
189
+ const query = {
190
+ $and: queryFilters
191
+ };
192
+ const notifications = await NotificationModel.find(query).sort({
193
+ createdAt: -1
194
+ }).limit(limit).lean();
195
+ const unseenQueryFilters = [{
196
+ userId
197
+ }, {
198
+ archivedAt: {
199
+ $exists: false
200
+ }
201
+ }, {
202
+ seenAt: {
203
+ $exists: false
204
+ }
205
+ }, getAccessibleByQuery(ability, "read", "RBNotification")];
206
+ if (disabledTopics.length > 0) unseenQueryFilters.push({
207
+ topic: {
208
+ $nin: disabledTopics
209
+ }
210
+ });
211
+ const unseenQuery = {
212
+ $and: unseenQueryFilters
213
+ };
214
+ const unreadQueryFilters = [{
215
+ userId
216
+ }, {
217
+ archivedAt: {
218
+ $exists: false
219
+ }
220
+ }, {
221
+ readAt: {
222
+ $exists: false
223
+ }
224
+ }, getAccessibleByQuery(ability, "read", "RBNotification")];
225
+ if (disabledTopics.length > 0) unreadQueryFilters.push({
226
+ topic: {
227
+ $nin: disabledTopics
228
+ }
229
+ });
230
+ const unreadQuery = {
231
+ $and: unreadQueryFilters
232
+ };
233
+ const [unreadCount, unseenCount] = await Promise.all([NotificationModel.countDocuments(unreadQuery), NotificationModel.countDocuments(unseenQuery)]);
234
+ const now = markSeen ? /* @__PURE__ */ new Date() : null;
235
+ if (now && unseenCount > 0) {
236
+ await NotificationModel.updateMany(unseenQuery, {
237
+ $set: {
238
+ seenAt: now
239
+ }
240
+ });
241
+ }
242
+ return listResponseSchema.parse({
243
+ ok: true,
244
+ notifications: notifications.map((n) => ({
245
+ id: String(n._id),
246
+ topic: typeof n.topic === "string" ? n.topic : void 0,
247
+ title: typeof n.title === "string" ? n.title : "",
248
+ body: typeof n.body === "string" ? n.body : void 0,
249
+ url: typeof n.url === "string" ? n.url : void 0,
250
+ createdAt: toIso(n.createdAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
251
+ seenAt: toIso(n.seenAt) ?? (now && !n.archivedAt && !n.seenAt ? now.toISOString() : void 0),
252
+ readAt: toIso(n.readAt),
253
+ archivedAt: toIso(n.archivedAt),
254
+ metadata: typeof n.metadata === "object" && n.metadata !== null ? n.metadata : void 0
255
+ })),
256
+ unreadCount,
257
+ unseenCount: now ? 0 : unseenCount
258
+ });
259
+ };
260
+ const createNotificationForCurrentUser = async (payload, ctx) => {
261
+ const session = getSessionUser(ctx);
262
+ if (!session) {
263
+ return {
264
+ ok: false,
265
+ error: "unauthorized"
266
+ };
267
+ }
268
+ const ability = buildAbilityFromSession({
269
+ tenantId: session.tenantId,
270
+ session: ctx.req.session
271
+ });
272
+ if (!ability.can("create", "RBNotification")) {
273
+ ctx.res.status(403);
274
+ return {
275
+ ok: false,
276
+ error: "forbidden"
277
+ };
278
+ }
279
+ const parsed = createRequestSchema.safeParse(payload);
280
+ if (!parsed.success) {
281
+ ctx.res.status(400);
282
+ return {
283
+ ok: false,
284
+ error: "invalid_payload"
285
+ };
286
+ }
287
+ const created = await createNotification(ctx, {
288
+ userId: session.userId,
289
+ topic: parsed.data.topic,
290
+ title: parsed.data.title,
291
+ body: parsed.data.body,
292
+ url: parsed.data.url,
293
+ metadata: parsed.data.metadata
294
+ }, ability);
295
+ return createResponseSchema.parse({
296
+ ok: true,
297
+ id: created.id
298
+ });
299
+ };
300
+ const markRead = async (_payload, ctx) => {
301
+ const session = getSessionUser(ctx);
302
+ if (!session) {
303
+ return {
304
+ ok: false,
305
+ error: "unauthorized"
306
+ };
307
+ }
308
+ const ability = buildAbilityFromSession({
309
+ tenantId: session.tenantId,
310
+ session: ctx.req.session
311
+ });
312
+ if (!ability.can("update", "RBNotification")) {
313
+ ctx.res.status(403);
314
+ return {
315
+ ok: false,
316
+ error: "forbidden"
317
+ };
318
+ }
319
+ const notificationId = typeof ctx.req.params.notificationId === "string" ? ctx.req.params.notificationId.trim() : "";
320
+ if (!notificationId) {
321
+ ctx.res.status(400);
322
+ return {
323
+ ok: false,
324
+ error: "missing_notification_id"
325
+ };
326
+ }
327
+ const NotificationModel = await models.get("RBNotification", {
328
+ req: ctx.req,
329
+ ability
330
+ });
331
+ const now = /* @__PURE__ */ new Date();
332
+ try {
333
+ await NotificationModel.updateOne({
334
+ $and: [{
335
+ _id: notificationId
336
+ }, {
337
+ archivedAt: {
338
+ $exists: false
339
+ }
340
+ }, getAccessibleByQuery(ability, "update", "RBNotification")]
341
+ }, {
342
+ $set: {
343
+ readAt: now,
344
+ seenAt: now
345
+ }
346
+ });
347
+ } catch {
348
+ ctx.res.status(400);
349
+ return {
350
+ ok: false,
351
+ error: "invalid_notification_id"
352
+ };
353
+ }
354
+ return {
355
+ ok: true
356
+ };
357
+ };
358
+ const markAllRead = async (_payload, ctx) => {
359
+ const session = getSessionUser(ctx);
360
+ if (!session) {
361
+ return {
362
+ ok: false,
363
+ error: "unauthorized"
364
+ };
365
+ }
366
+ const ability = buildAbilityFromSession({
367
+ tenantId: session.tenantId,
368
+ session: ctx.req.session
369
+ });
370
+ if (!ability.can("update", "RBNotification")) {
371
+ ctx.res.status(403);
372
+ return {
373
+ ok: false,
374
+ error: "forbidden"
375
+ };
376
+ }
377
+ const SettingsModel = await models.get("RBNotificationSettings", {
378
+ req: ctx.req,
379
+ ability
380
+ });
381
+ const settings = await SettingsModel.findOne({
382
+ userId: session.userId
383
+ }).lean();
384
+ const disabledTopics = buildDisabledTopics(settings, "inApp");
385
+ const NotificationModel = await models.get("RBNotification", {
386
+ req: ctx.req,
387
+ ability
388
+ });
389
+ const queryFilters = [{
390
+ userId: session.userId
391
+ }, {
392
+ archivedAt: {
393
+ $exists: false
394
+ }
395
+ }, {
396
+ readAt: {
397
+ $exists: false
398
+ }
399
+ }, getAccessibleByQuery(ability, "update", "RBNotification")];
400
+ if (disabledTopics.length > 0) queryFilters.push({
401
+ topic: {
402
+ $nin: disabledTopics
403
+ }
404
+ });
405
+ const query = {
406
+ $and: queryFilters
407
+ };
408
+ const now = /* @__PURE__ */ new Date();
409
+ await NotificationModel.updateMany(query, {
410
+ $set: {
411
+ readAt: now,
412
+ seenAt: now
413
+ }
414
+ });
415
+ return {
416
+ ok: true
417
+ };
418
+ };
419
+ const archiveNotification = async (_payload, ctx) => {
420
+ const session = getSessionUser(ctx);
421
+ if (!session) {
422
+ return {
423
+ ok: false,
424
+ error: "unauthorized"
425
+ };
426
+ }
427
+ const ability = buildAbilityFromSession({
428
+ tenantId: session.tenantId,
429
+ session: ctx.req.session
430
+ });
431
+ if (!ability.can("update", "RBNotification")) {
432
+ ctx.res.status(403);
433
+ return {
434
+ ok: false,
435
+ error: "forbidden"
436
+ };
437
+ }
438
+ const notificationId = typeof ctx.req.params.notificationId === "string" ? ctx.req.params.notificationId.trim() : "";
439
+ if (!notificationId) {
440
+ ctx.res.status(400);
441
+ return {
442
+ ok: false,
443
+ error: "missing_notification_id"
444
+ };
445
+ }
446
+ const NotificationModel = await models.get("RBNotification", {
447
+ req: ctx.req,
448
+ ability
449
+ });
450
+ try {
451
+ await NotificationModel.updateOne({
452
+ $and: [{
453
+ _id: notificationId
454
+ }, {
455
+ archivedAt: {
456
+ $exists: false
457
+ }
458
+ }, getAccessibleByQuery(ability, "update", "RBNotification")]
459
+ }, {
460
+ $set: {
461
+ archivedAt: /* @__PURE__ */ new Date()
462
+ }
463
+ });
464
+ } catch {
465
+ ctx.res.status(400);
466
+ return {
467
+ ok: false,
468
+ error: "invalid_notification_id"
469
+ };
470
+ }
471
+ return {
472
+ ok: true
473
+ };
474
+ };
475
+ const getSettings = async (_payload, ctx) => {
476
+ const session = getSessionUser(ctx);
477
+ if (!session) {
478
+ return {
479
+ ok: false,
480
+ error: "unauthorized"
481
+ };
482
+ }
483
+ const ability = buildAbilityFromSession({
484
+ tenantId: session.tenantId,
485
+ session: ctx.req.session
486
+ });
487
+ if (!ability.can("read", "RBNotificationSettings")) {
488
+ ctx.res.status(403);
489
+ return {
490
+ ok: false,
491
+ error: "forbidden"
492
+ };
493
+ }
494
+ const SettingsModel = await models.get("RBNotificationSettings", {
495
+ req: ctx.req,
496
+ ability
497
+ });
498
+ const settings = await SettingsModel.findOne({
499
+ $and: [{
500
+ userId: session.userId
501
+ }, getAccessibleByQuery(ability, "read", "RBNotificationSettings")]
502
+ }).lean();
503
+ const digestFrequencyRaw = typeof settings?.digestFrequency === "string" ? settings.digestFrequency : "weekly";
504
+ const digestFrequency = digestFrequencyRaw === "off" || digestFrequencyRaw === "daily" || digestFrequencyRaw === "weekly" ? digestFrequencyRaw : "weekly";
505
+ const topicPreferences = Array.isArray(settings?.topicPreferences) ? settings.topicPreferences.map((pref) => ({
506
+ topic: typeof pref.topic === "string" ? pref.topic : "",
507
+ inApp: pref.inApp === true,
508
+ emailDigest: pref.emailDigest === true,
509
+ push: pref.push === true
510
+ })).filter((pref) => pref.topic.length > 0) : [];
511
+ return settingsResponseSchema.parse({
512
+ ok: true,
513
+ settings: {
514
+ digestFrequency,
515
+ topicPreferences,
516
+ lastDigestSentAt: toIso(settings?.lastDigestSentAt)
517
+ }
518
+ });
519
+ };
520
+ const updateSettings = async (payload, ctx) => {
521
+ const session = getSessionUser(ctx);
522
+ if (!session) {
523
+ return {
524
+ ok: false,
525
+ error: "unauthorized"
526
+ };
527
+ }
528
+ const ability = buildAbilityFromSession({
529
+ tenantId: session.tenantId,
530
+ session: ctx.req.session
531
+ });
532
+ if (!ability.can("update", "RBNotificationSettings")) {
533
+ ctx.res.status(403);
534
+ return {
535
+ ok: false,
536
+ error: "forbidden"
537
+ };
538
+ }
539
+ const parsed = updateSettingsRequestSchema.safeParse(payload);
540
+ if (!parsed.success) {
541
+ ctx.res.status(400);
542
+ return {
543
+ ok: false,
544
+ error: "invalid_payload"
545
+ };
546
+ }
547
+ const SettingsModel = await models.get("RBNotificationSettings", {
548
+ req: ctx.req,
549
+ ability
550
+ });
551
+ const nextValues = {};
552
+ if (parsed.data.digestFrequency) {
553
+ nextValues.digestFrequency = parsed.data.digestFrequency;
554
+ }
555
+ if (parsed.data.topicPreferences) {
556
+ const seen = /* @__PURE__ */ new Set();
557
+ const next = parsed.data.topicPreferences.map((pref) => ({
558
+ topic: pref.topic.trim(),
559
+ inApp: pref.inApp,
560
+ emailDigest: pref.emailDigest,
561
+ push: pref.push
562
+ })).filter((pref) => pref.topic.length > 0).filter((pref) => {
563
+ if (seen.has(pref.topic)) return false;
564
+ seen.add(pref.topic);
565
+ return true;
566
+ });
567
+ nextValues.topicPreferences = next;
568
+ }
569
+ const ops = {
570
+ $setOnInsert: {
571
+ userId: session.userId
572
+ }
573
+ };
574
+ if (Object.keys(nextValues).length > 0) {
575
+ ops.$set = nextValues;
576
+ }
577
+ const settings = await SettingsModel.findOneAndUpdate({
578
+ $and: [{
579
+ userId: session.userId
580
+ }, getAccessibleByQuery(ability, "update", "RBNotificationSettings")]
581
+ }, ops, {
582
+ upsert: true,
583
+ returnDocument: "after",
584
+ setDefaultsOnInsert: true
585
+ }).lean();
586
+ const digestFrequencyRaw = typeof settings?.digestFrequency === "string" ? settings.digestFrequency : "weekly";
587
+ const digestFrequency = digestFrequencyRaw === "off" || digestFrequencyRaw === "daily" || digestFrequencyRaw === "weekly" ? digestFrequencyRaw : "weekly";
588
+ const topicPreferences = Array.isArray(settings?.topicPreferences) ? settings.topicPreferences.map((pref) => ({
589
+ topic: typeof pref.topic === "string" ? pref.topic : "",
590
+ inApp: pref.inApp === true,
591
+ emailDigest: pref.emailDigest === true,
592
+ push: pref.push === true
593
+ })).filter((pref) => pref.topic.length > 0) : [];
594
+ return updateSettingsResponseSchema.parse({
595
+ ok: true,
596
+ settings: {
597
+ digestFrequency,
598
+ topicPreferences,
599
+ lastDigestSentAt: toIso(settings?.lastDigestSentAt)
600
+ }
601
+ });
602
+ };
603
+ const runDigest = async (payload, ctx) => {
604
+ const session = getSessionUser(ctx);
605
+ if (!session) {
606
+ return {
607
+ ok: false,
608
+ error: "unauthorized"
609
+ };
610
+ }
611
+ const ability = buildAbilityFromSession({
612
+ tenantId: session.tenantId,
613
+ session: ctx.req.session
614
+ });
615
+ if (!ability.can("read", "RBNotification")) {
616
+ ctx.res.status(403);
617
+ return {
618
+ ok: false,
619
+ error: "forbidden"
620
+ };
621
+ }
622
+ const parsed = digestRunRequestSchema.safeParse(payload);
623
+ if (!parsed.success) {
624
+ ctx.res.status(400);
625
+ return {
626
+ ok: false,
627
+ error: "invalid_payload"
628
+ };
629
+ }
630
+ const result = await sendNotificationsDigestForUser(ctx, {
631
+ userId: session.userId,
632
+ ability,
633
+ force: parsed.data.force === true
634
+ });
635
+ if (!result.ok) {
636
+ ctx.res.status(500);
637
+ return {
638
+ ok: false,
639
+ error: result.error
640
+ };
641
+ }
642
+ return {
643
+ ok: true,
644
+ sent: result.sent,
645
+ ...result.skippedReason ? {
646
+ skippedReason: result.skippedReason
647
+ } : {}
648
+ };
649
+ };
650
+ const handler = (api) => {
651
+ api.post(ListRoute, listNotifications);
652
+ api.post(CreateRoute, createNotificationForCurrentUser);
653
+ api.post(MarkReadRoute, markRead);
654
+ api.post(MarkAllReadRoute, markAllRead);
655
+ api.post(ArchiveRoute, archiveNotification);
656
+ api.get(SettingsRoute, getSettings);
657
+ api.put(SettingsRoute, updateSettings);
658
+ api.post(DigestRunRoute, runDigest);
659
+ };
660
+ export {
661
+ handler as default
662
+ };
663
+ //# sourceMappingURL=handler-0rPClEv4.js.map