@rpcbase/server 0.475.0 → 0.477.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.
- package/dist/email-DEw8keax.js +8041 -0
- package/dist/{handler-xi0XKR-Y.js → handler-BOTZftAB.js} +29 -29
- package/dist/handler-B_mMDLBO.js +437 -0
- package/dist/{handler-BYVnU9H-.js → handler-Cl-0-832.js} +1 -1
- package/dist/{handler-CTL2iQCj.js → handler-Dd20DHyz.js} +15 -11
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +169 -8191
- package/dist/notifications/api/notifications/handler.d.ts +4 -0
- package/dist/notifications/api/notifications/handler.d.ts.map +1 -0
- package/dist/notifications/api/notifications/index.d.ts +168 -0
- package/dist/notifications/api/notifications/index.d.ts.map +1 -0
- package/dist/notifications/api/notifications/shared.d.ts +6 -0
- package/dist/notifications/api/notifications/shared.d.ts.map +1 -0
- package/dist/notifications/createNotification.d.ts +13 -0
- package/dist/notifications/createNotification.d.ts.map +1 -0
- package/dist/notifications/digest.d.ts +13 -0
- package/dist/notifications/digest.d.ts.map +1 -0
- package/dist/notifications/routes.d.ts +2 -0
- package/dist/notifications/routes.d.ts.map +1 -0
- package/dist/notifications.d.ts +4 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +126 -0
- package/dist/rts/api/changes/handler.d.ts.map +1 -1
- package/dist/rts/index.d.ts +3 -1
- package/dist/rts/index.d.ts.map +1 -1
- package/dist/{index-Ckx0UHs6.js → rts/index.js} +99 -32
- package/dist/{schemas-CyxqObur.js → schemas-D5T9tDtI.js} +712 -4
- package/dist/{shared-Chfrv8o6.js → shared-UGuDRAKK.js} +16 -30
- package/dist/uploads/api/file-uploads/handlers/completeUpload.d.ts.map +1 -1
- package/dist/uploads/api/file-uploads/handlers/getStatus.d.ts.map +1 -1
- package/dist/uploads/api/file-uploads/handlers/uploadChunk.d.ts.map +1 -1
- package/dist/uploads/api/file-uploads/shared.d.ts +3 -0
- package/dist/uploads/api/file-uploads/shared.d.ts.map +1 -1
- package/dist/uploads.js +1 -1
- package/package.json +9 -4
- package/dist/passwordHashStorage.test.d.ts +0 -2
- package/dist/passwordHashStorage.test.d.ts.map +0 -1
- package/dist/rts/api/changes/handler.test.d.ts +0 -2
- package/dist/rts/api/changes/handler.test.d.ts.map +0 -1
- package/dist/rts/index.ws.test.d.ts +0 -2
- package/dist/rts/index.ws.test.d.ts.map +0 -1
- package/dist/rts.d.ts +0 -3
- package/dist/rts.d.ts.map +0 -1
- package/dist/rts.js +0 -13
- package/dist/uploads/api/files/handlers/getFile.test.d.ts +0 -2
- package/dist/uploads/api/files/handlers/getFile.test.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/notifications/api/notifications/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,cAAc,CAAA;yBAubnC,KAAK,GAAG;AAAxB,wBASC"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from '../../../../../vite/node_modules/zod';
|
|
2
|
+
export declare const ListRoute = "/api/rb/notifications";
|
|
3
|
+
export declare const CreateRoute = "/api/rb/notifications/create";
|
|
4
|
+
export declare const MarkReadRoute = "/api/rb/notifications/:notificationId/read";
|
|
5
|
+
export declare const ArchiveRoute = "/api/rb/notifications/:notificationId/archive";
|
|
6
|
+
export declare const MarkAllReadRoute = "/api/rb/notifications/mark-all-read";
|
|
7
|
+
export declare const SettingsRoute = "/api/rb/notifications/settings";
|
|
8
|
+
export declare const DigestRunRoute = "/api/rb/notifications/digest/run";
|
|
9
|
+
export declare const listRequestSchema: z.ZodObject<{
|
|
10
|
+
includeArchived: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
unreadOnly: z.ZodOptional<z.ZodBoolean>;
|
|
12
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
markSeen: z.ZodOptional<z.ZodBoolean>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type ListRequestPayload = z.infer<typeof listRequestSchema>;
|
|
16
|
+
export declare const createRequestSchema: z.ZodObject<{
|
|
17
|
+
topic: z.ZodOptional<z.ZodString>;
|
|
18
|
+
title: z.ZodString;
|
|
19
|
+
body: z.ZodOptional<z.ZodString>;
|
|
20
|
+
url: z.ZodOptional<z.ZodString>;
|
|
21
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
22
|
+
}, z.core.$strip>;
|
|
23
|
+
export type CreateRequestPayload = z.infer<typeof createRequestSchema>;
|
|
24
|
+
export declare const notificationSchema: z.ZodObject<{
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
topic: z.ZodOptional<z.ZodString>;
|
|
27
|
+
title: z.ZodString;
|
|
28
|
+
body: z.ZodOptional<z.ZodString>;
|
|
29
|
+
url: z.ZodOptional<z.ZodString>;
|
|
30
|
+
createdAt: z.ZodString;
|
|
31
|
+
seenAt: z.ZodOptional<z.ZodString>;
|
|
32
|
+
readAt: z.ZodOptional<z.ZodString>;
|
|
33
|
+
archivedAt: z.ZodOptional<z.ZodString>;
|
|
34
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
export type NotificationPayload = z.infer<typeof notificationSchema>;
|
|
37
|
+
export declare const listResponseSchema: z.ZodObject<{
|
|
38
|
+
ok: z.ZodBoolean;
|
|
39
|
+
error: z.ZodOptional<z.ZodString>;
|
|
40
|
+
notifications: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
41
|
+
id: z.ZodString;
|
|
42
|
+
topic: z.ZodOptional<z.ZodString>;
|
|
43
|
+
title: z.ZodString;
|
|
44
|
+
body: z.ZodOptional<z.ZodString>;
|
|
45
|
+
url: z.ZodOptional<z.ZodString>;
|
|
46
|
+
createdAt: z.ZodString;
|
|
47
|
+
seenAt: z.ZodOptional<z.ZodString>;
|
|
48
|
+
readAt: z.ZodOptional<z.ZodString>;
|
|
49
|
+
archivedAt: z.ZodOptional<z.ZodString>;
|
|
50
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
51
|
+
}, z.core.$strip>>>;
|
|
52
|
+
unreadCount: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
unseenCount: z.ZodOptional<z.ZodNumber>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
export type ListResponsePayload = z.infer<typeof listResponseSchema>;
|
|
56
|
+
export declare const createResponseSchema: z.ZodObject<{
|
|
57
|
+
ok: z.ZodBoolean;
|
|
58
|
+
error: z.ZodOptional<z.ZodString>;
|
|
59
|
+
id: z.ZodOptional<z.ZodString>;
|
|
60
|
+
}, z.core.$strip>;
|
|
61
|
+
export type CreateResponsePayload = z.infer<typeof createResponseSchema>;
|
|
62
|
+
export declare const markReadResponseSchema: z.ZodObject<{
|
|
63
|
+
ok: z.ZodBoolean;
|
|
64
|
+
error: z.ZodOptional<z.ZodString>;
|
|
65
|
+
}, z.core.$strip>;
|
|
66
|
+
export type MarkReadResponsePayload = z.infer<typeof markReadResponseSchema>;
|
|
67
|
+
export declare const archiveResponseSchema: z.ZodObject<{
|
|
68
|
+
ok: z.ZodBoolean;
|
|
69
|
+
error: z.ZodOptional<z.ZodString>;
|
|
70
|
+
}, z.core.$strip>;
|
|
71
|
+
export type ArchiveResponsePayload = z.infer<typeof archiveResponseSchema>;
|
|
72
|
+
export declare const markAllReadResponseSchema: z.ZodObject<{
|
|
73
|
+
ok: z.ZodBoolean;
|
|
74
|
+
error: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
export type MarkAllReadResponsePayload = z.infer<typeof markAllReadResponseSchema>;
|
|
77
|
+
export declare const digestFrequencySchema: z.ZodEnum<{
|
|
78
|
+
off: "off";
|
|
79
|
+
daily: "daily";
|
|
80
|
+
weekly: "weekly";
|
|
81
|
+
}>;
|
|
82
|
+
export type DigestFrequency = z.infer<typeof digestFrequencySchema>;
|
|
83
|
+
export declare const topicPreferenceSchema: z.ZodObject<{
|
|
84
|
+
topic: z.ZodString;
|
|
85
|
+
inApp: z.ZodBoolean;
|
|
86
|
+
emailDigest: z.ZodBoolean;
|
|
87
|
+
push: z.ZodBoolean;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
export type TopicPreferencePayload = z.infer<typeof topicPreferenceSchema>;
|
|
90
|
+
export declare const settingsSchema: z.ZodObject<{
|
|
91
|
+
digestFrequency: z.ZodEnum<{
|
|
92
|
+
off: "off";
|
|
93
|
+
daily: "daily";
|
|
94
|
+
weekly: "weekly";
|
|
95
|
+
}>;
|
|
96
|
+
topicPreferences: z.ZodArray<z.ZodObject<{
|
|
97
|
+
topic: z.ZodString;
|
|
98
|
+
inApp: z.ZodBoolean;
|
|
99
|
+
emailDigest: z.ZodBoolean;
|
|
100
|
+
push: z.ZodBoolean;
|
|
101
|
+
}, z.core.$strip>>;
|
|
102
|
+
lastDigestSentAt: z.ZodOptional<z.ZodString>;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
export type SettingsPayload = z.infer<typeof settingsSchema>;
|
|
105
|
+
export declare const settingsResponseSchema: z.ZodObject<{
|
|
106
|
+
ok: z.ZodBoolean;
|
|
107
|
+
error: z.ZodOptional<z.ZodString>;
|
|
108
|
+
settings: z.ZodOptional<z.ZodObject<{
|
|
109
|
+
digestFrequency: z.ZodEnum<{
|
|
110
|
+
off: "off";
|
|
111
|
+
daily: "daily";
|
|
112
|
+
weekly: "weekly";
|
|
113
|
+
}>;
|
|
114
|
+
topicPreferences: z.ZodArray<z.ZodObject<{
|
|
115
|
+
topic: z.ZodString;
|
|
116
|
+
inApp: z.ZodBoolean;
|
|
117
|
+
emailDigest: z.ZodBoolean;
|
|
118
|
+
push: z.ZodBoolean;
|
|
119
|
+
}, z.core.$strip>>;
|
|
120
|
+
lastDigestSentAt: z.ZodOptional<z.ZodString>;
|
|
121
|
+
}, z.core.$strip>>;
|
|
122
|
+
}, z.core.$strip>;
|
|
123
|
+
export type SettingsResponsePayload = z.infer<typeof settingsResponseSchema>;
|
|
124
|
+
export declare const updateSettingsRequestSchema: z.ZodObject<{
|
|
125
|
+
digestFrequency: z.ZodOptional<z.ZodEnum<{
|
|
126
|
+
off: "off";
|
|
127
|
+
daily: "daily";
|
|
128
|
+
weekly: "weekly";
|
|
129
|
+
}>>;
|
|
130
|
+
topicPreferences: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
131
|
+
topic: z.ZodString;
|
|
132
|
+
inApp: z.ZodBoolean;
|
|
133
|
+
emailDigest: z.ZodBoolean;
|
|
134
|
+
push: z.ZodBoolean;
|
|
135
|
+
}, z.core.$strip>>>;
|
|
136
|
+
}, z.core.$strip>;
|
|
137
|
+
export type UpdateSettingsRequestPayload = z.infer<typeof updateSettingsRequestSchema>;
|
|
138
|
+
export declare const updateSettingsResponseSchema: z.ZodObject<{
|
|
139
|
+
ok: z.ZodBoolean;
|
|
140
|
+
error: z.ZodOptional<z.ZodString>;
|
|
141
|
+
settings: z.ZodOptional<z.ZodObject<{
|
|
142
|
+
digestFrequency: z.ZodEnum<{
|
|
143
|
+
off: "off";
|
|
144
|
+
daily: "daily";
|
|
145
|
+
weekly: "weekly";
|
|
146
|
+
}>;
|
|
147
|
+
topicPreferences: z.ZodArray<z.ZodObject<{
|
|
148
|
+
topic: z.ZodString;
|
|
149
|
+
inApp: z.ZodBoolean;
|
|
150
|
+
emailDigest: z.ZodBoolean;
|
|
151
|
+
push: z.ZodBoolean;
|
|
152
|
+
}, z.core.$strip>>;
|
|
153
|
+
lastDigestSentAt: z.ZodOptional<z.ZodString>;
|
|
154
|
+
}, z.core.$strip>>;
|
|
155
|
+
}, z.core.$strip>;
|
|
156
|
+
export type UpdateSettingsResponsePayload = z.infer<typeof updateSettingsResponseSchema>;
|
|
157
|
+
export declare const digestRunRequestSchema: z.ZodObject<{
|
|
158
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
159
|
+
}, z.core.$strip>;
|
|
160
|
+
export type DigestRunRequestPayload = z.infer<typeof digestRunRequestSchema>;
|
|
161
|
+
export declare const digestRunResponseSchema: z.ZodObject<{
|
|
162
|
+
ok: z.ZodBoolean;
|
|
163
|
+
error: z.ZodOptional<z.ZodString>;
|
|
164
|
+
sent: z.ZodOptional<z.ZodBoolean>;
|
|
165
|
+
skippedReason: z.ZodOptional<z.ZodString>;
|
|
166
|
+
}, z.core.$strip>;
|
|
167
|
+
export type DigestRunResponsePayload = z.infer<typeof digestRunResponseSchema>;
|
|
168
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/notifications/api/notifications/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/notifications/api/notifications/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAQvC,eAAO,MAAM,cAAc,GAAI,KAAK,GAAG;;;QAgBtC,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Ctx } from '../../../api/src';
|
|
2
|
+
export type CreateNotificationInput = {
|
|
3
|
+
userId: string;
|
|
4
|
+
topic?: string;
|
|
5
|
+
title: string;
|
|
6
|
+
body?: string;
|
|
7
|
+
url?: string;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
export declare const createNotification: (ctx: Ctx, input: CreateNotificationInput) => Promise<{
|
|
11
|
+
id: string;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=createNotification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNotification.d.ts","sourceRoot":"","sources":["../../src/notifications/createNotification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAIvC,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,KAAK,GAAG,EACR,OAAO,uBAAuB,KAC7B,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAyBxB,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Ctx } from '../../../api/src';
|
|
2
|
+
export declare const sendNotificationsDigestForUser: (ctx: Ctx, { userId, force, }: {
|
|
3
|
+
userId: string;
|
|
4
|
+
force?: boolean;
|
|
5
|
+
}) => Promise<{
|
|
6
|
+
ok: true;
|
|
7
|
+
sent: boolean;
|
|
8
|
+
skippedReason?: string;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
error: string;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=digest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/notifications/digest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAuCvC,eAAO,MAAM,8BAA8B,GACzC,KAAK,GAAG,EACR,oBAGG;IACD,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,KACA,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA0F5F,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/notifications/routes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,yBAKb,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAA;AACtC,cAAc,oCAAoC,CAAA;AAClD,cAAc,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { loadModel, loadRbModel } from "@rpcbase/db";
|
|
2
|
+
import { s as sendEmail } from "./email-DEw8keax.js";
|
|
3
|
+
const routes = Object.entries({
|
|
4
|
+
.../* @__PURE__ */ Object.assign({ "./api/notifications/handler.ts": () => import("./handler-B_mMDLBO.js") })
|
|
5
|
+
}).reduce((acc, [path, mod]) => {
|
|
6
|
+
acc[path.replace("./api/", "@rpcbase/server/notifications/api/")] = mod;
|
|
7
|
+
return acc;
|
|
8
|
+
}, {});
|
|
9
|
+
const createNotification = async (ctx, input) => {
|
|
10
|
+
const userId = input.userId.trim();
|
|
11
|
+
const title = input.title.trim();
|
|
12
|
+
if (!userId) {
|
|
13
|
+
throw new Error("createNotification: userId is required");
|
|
14
|
+
}
|
|
15
|
+
if (!title) {
|
|
16
|
+
throw new Error("createNotification: title is required");
|
|
17
|
+
}
|
|
18
|
+
const topic = typeof input.topic === "string" ? input.topic.trim() : "";
|
|
19
|
+
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
20
|
+
const url = typeof input.url === "string" ? input.url.trim() : "";
|
|
21
|
+
const NotificationModel = await loadModel("RBNotification", ctx);
|
|
22
|
+
const doc = await NotificationModel.create({
|
|
23
|
+
userId,
|
|
24
|
+
...topic ? { topic } : {},
|
|
25
|
+
title,
|
|
26
|
+
...body ? { body } : {},
|
|
27
|
+
...url ? { url } : {},
|
|
28
|
+
...input.metadata ? { metadata: input.metadata } : {}
|
|
29
|
+
});
|
|
30
|
+
return { id: doc._id.toString() };
|
|
31
|
+
};
|
|
32
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
33
|
+
const WEEK_MS = 7 * DAY_MS;
|
|
34
|
+
const getDigestWindowMs = (frequency) => {
|
|
35
|
+
if (frequency === "daily") return DAY_MS;
|
|
36
|
+
if (frequency === "weekly") return WEEK_MS;
|
|
37
|
+
return 0;
|
|
38
|
+
};
|
|
39
|
+
const formatIso = (value) => {
|
|
40
|
+
if (!(value instanceof Date)) return void 0;
|
|
41
|
+
return value.toISOString();
|
|
42
|
+
};
|
|
43
|
+
const buildPreferencesByTopic = (settings) => {
|
|
44
|
+
const record = {};
|
|
45
|
+
const raw = settings?.topicPreferences;
|
|
46
|
+
if (!Array.isArray(raw)) return record;
|
|
47
|
+
for (const pref of raw) {
|
|
48
|
+
if (!pref || typeof pref !== "object") continue;
|
|
49
|
+
const topic = typeof pref.topic === "string" ? pref.topic.trim() : "";
|
|
50
|
+
if (!topic) continue;
|
|
51
|
+
const emailDigest = pref.emailDigest;
|
|
52
|
+
record[topic] = { emailDigest: emailDigest === true };
|
|
53
|
+
}
|
|
54
|
+
return record;
|
|
55
|
+
};
|
|
56
|
+
const sendNotificationsDigestForUser = async (ctx, {
|
|
57
|
+
userId,
|
|
58
|
+
force = false
|
|
59
|
+
}) => {
|
|
60
|
+
const SettingsModel = await loadModel("RBNotificationSettings", ctx);
|
|
61
|
+
const NotificationModel = await loadModel("RBNotification", ctx);
|
|
62
|
+
const settings = await SettingsModel.findOne({ userId }).lean();
|
|
63
|
+
const digestFrequencyRaw = typeof settings?.digestFrequency === "string" ? settings.digestFrequency : "weekly";
|
|
64
|
+
const digestFrequency = digestFrequencyRaw === "daily" || digestFrequencyRaw === "weekly" || digestFrequencyRaw === "off" ? digestFrequencyRaw : "weekly";
|
|
65
|
+
if (digestFrequency === "off") {
|
|
66
|
+
return { ok: true, sent: false, skippedReason: "digest_off" };
|
|
67
|
+
}
|
|
68
|
+
const now = /* @__PURE__ */ new Date();
|
|
69
|
+
const windowMs = getDigestWindowMs(digestFrequency);
|
|
70
|
+
const lastSentAt = settings?.lastDigestSentAt instanceof Date ? settings.lastDigestSentAt : null;
|
|
71
|
+
const since = lastSentAt ?? new Date(now.getTime() - windowMs);
|
|
72
|
+
if (!force && lastSentAt && now.getTime() - lastSentAt.getTime() < windowMs) {
|
|
73
|
+
return { ok: true, sent: false, skippedReason: "not_due" };
|
|
74
|
+
}
|
|
75
|
+
const preferencesByTopic = buildPreferencesByTopic(settings);
|
|
76
|
+
const disabledTopics = Object.entries(preferencesByTopic).filter(([, pref]) => pref.emailDigest === false).map(([topic]) => topic);
|
|
77
|
+
const query = {
|
|
78
|
+
userId,
|
|
79
|
+
archivedAt: { $exists: false },
|
|
80
|
+
readAt: { $exists: false },
|
|
81
|
+
createdAt: { $gt: since }
|
|
82
|
+
};
|
|
83
|
+
if (disabledTopics.length > 0) {
|
|
84
|
+
query.topic = { $nin: disabledTopics };
|
|
85
|
+
}
|
|
86
|
+
const notifications = await NotificationModel.find(query).sort({ createdAt: -1 }).limit(50).lean();
|
|
87
|
+
if (!notifications.length) {
|
|
88
|
+
await SettingsModel.updateOne(
|
|
89
|
+
{ userId },
|
|
90
|
+
{ $set: { lastDigestSentAt: now }, $setOnInsert: { userId, digestFrequency: "weekly" } },
|
|
91
|
+
{ upsert: true }
|
|
92
|
+
);
|
|
93
|
+
return { ok: true, sent: false, skippedReason: "empty" };
|
|
94
|
+
}
|
|
95
|
+
const UserModel = await loadRbModel("RBUser", ctx);
|
|
96
|
+
const user = await UserModel.findById(userId, { email: 1 }).lean();
|
|
97
|
+
const email = typeof user?.email === "string" ? user.email.trim() : "";
|
|
98
|
+
if (!email) {
|
|
99
|
+
return { ok: true, sent: false, skippedReason: "missing_email" };
|
|
100
|
+
}
|
|
101
|
+
const subject = "Notifications digest";
|
|
102
|
+
const rows = notifications.map((n) => {
|
|
103
|
+
const title = typeof n.title === "string" ? n.title : "";
|
|
104
|
+
const body = typeof n.body === "string" ? n.body : "";
|
|
105
|
+
const url = typeof n.url === "string" ? n.url : "";
|
|
106
|
+
const createdAt = formatIso(n.createdAt) ?? "";
|
|
107
|
+
const line = url ? `<li><strong>${title}</strong><br>${body}<br><a href="${url}">${url}</a><br><small>${createdAt}</small></li>` : `<li><strong>${title}</strong><br>${body}<br><small>${createdAt}</small></li>`;
|
|
108
|
+
return line;
|
|
109
|
+
}).join("");
|
|
110
|
+
const html = `<div><p>Here is your notifications digest:</p><ul>${rows}</ul></div>`;
|
|
111
|
+
const emailResult = await sendEmail({ to: email, subject, html });
|
|
112
|
+
if (emailResult.error) {
|
|
113
|
+
return { ok: false, error: emailResult.error };
|
|
114
|
+
}
|
|
115
|
+
await SettingsModel.updateOne(
|
|
116
|
+
{ userId },
|
|
117
|
+
{ $set: { lastDigestSentAt: now }, $setOnInsert: { userId, digestFrequency: "weekly" } },
|
|
118
|
+
{ upsert: true }
|
|
119
|
+
);
|
|
120
|
+
return { ok: true, sent: emailResult.skipped !== true, ...emailResult.skipped ? { skippedReason: "email_skipped" } : {} };
|
|
121
|
+
};
|
|
122
|
+
export {
|
|
123
|
+
createNotification,
|
|
124
|
+
routes,
|
|
125
|
+
sendNotificationsDigestForUser
|
|
126
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/rts/api/changes/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/rts/api/changes/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,cAAc,CAAA;AAQnD,KAAK,WAAW,GAAG;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B,CAAA;yBAuIe,KAAK,GAAG,CAAC,WAAW,CAAC;AAArC,wBAEC"}
|
package/dist/rts/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Server as HttpServer } from 'node:http';
|
|
2
2
|
import { RequestHandler } from 'express';
|
|
3
|
+
import { AppAbility } from '../../../db/acl/src';
|
|
3
4
|
import { WebSocket } from 'ws';
|
|
4
5
|
type SocketMeta = {
|
|
5
6
|
tenantId: string;
|
|
6
7
|
userId: string;
|
|
8
|
+
ability: AppAbility;
|
|
7
9
|
};
|
|
8
10
|
type HandlerFn = (socket: RtsSocket) => void | (() => void);
|
|
9
11
|
declare class RtsSocket {
|
|
@@ -34,5 +36,5 @@ export declare const initRts: ({ server, path, sessionMiddleware, maxPayloadByte
|
|
|
34
36
|
}) => void;
|
|
35
37
|
export declare const registerRtsHandler: (handler: HandlerFn) => void;
|
|
36
38
|
export declare const notifyRtsModelChanged: (tenantId: string, modelName: string) => void;
|
|
37
|
-
export
|
|
39
|
+
export * from './routes';
|
|
38
40
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/rts/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,OAAO,EAAmH,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAGlK,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,IAAI,CAAA;AAqBlE,KAAK,UAAU,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,UAAU,CAAA;CACpB,CAAA;AAoBD,KAAK,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;AAiC3D,cAAM,SAAS;IACb,SAAgB,EAAE,EAAE,MAAM,CAAA;IAC1B,SAAgB,QAAQ,EAAE,MAAM,CAAA;IAChC,SAAgB,MAAM,EAAE,MAAM,CAAA;IAE9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqD;gBAE3D,EACjB,EAAE,EACF,EAAE,EACF,IAAI,GACL,EAAE;QACD,EAAE,EAAE,MAAM,CAAA;QACV,EAAE,EAAE,SAAS,CAAA;QACb,IAAI,EAAE,UAAU,CAAA;KACjB;IAOM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAOlE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAO7D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAI5C,KAAK,IAAI,IAAI;IAQb,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;CAOvD;AA+iBD,eAAO,MAAM,OAAO,GAAI,4NAQrB;IACD,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,cAAc,CAAA;IAClC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,KAAG,IA8GH,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,SAAS,SAAS,KAAG,IAEvD,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,KAAG,IAE3E,CAAA;AAED,cAAc,UAAU,CAAA"}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { loadModel } from "@rpcbase/db";
|
|
2
|
+
import { loadRbModel, loadModel } from "@rpcbase/db";
|
|
3
|
+
import { buildAbilityFromSession, getTenantRolesFromSessionUser, buildAbility, getAccessibleByQuery } from "@rpcbase/db/acl";
|
|
3
4
|
import { WebSocketServer } from "ws";
|
|
5
|
+
const routes = Object.entries({
|
|
6
|
+
.../* @__PURE__ */ Object.assign({ "./api/changes/handler.ts": () => import("../handler-Dd20DHyz.js") })
|
|
7
|
+
}).reduce((acc, [path, mod]) => {
|
|
8
|
+
acc[path.replace("./api/", "@rpcbase/server/rts/api/")] = mod;
|
|
9
|
+
return acc;
|
|
10
|
+
}, {});
|
|
4
11
|
const TENANT_ID_QUERY_PARAM = "rb-tenant-id";
|
|
5
12
|
const USER_ID_HEADER = "rb-user-id";
|
|
6
13
|
const QUERY_KEY_MAX_LEN = 4096;
|
|
@@ -150,14 +157,25 @@ const parseUpgradeMeta = async ({
|
|
|
150
157
|
} else {
|
|
151
158
|
throw new Error("Tenant not authorized for this session");
|
|
152
159
|
}
|
|
153
|
-
|
|
160
|
+
const ability2 = buildAbilityFromSession({ tenantId, session: upgradeReq.session });
|
|
161
|
+
return { tenantId, userId: sessionUserId, ability: ability2 };
|
|
154
162
|
}
|
|
155
163
|
const raw = req.headers[USER_ID_HEADER];
|
|
156
164
|
const headerUserId = Array.isArray(raw) ? raw[0] : raw;
|
|
157
165
|
if (!headerUserId) {
|
|
158
166
|
throw new Error("Missing rb-user-id header (reverse-proxy) and no session middleware configured");
|
|
159
167
|
}
|
|
160
|
-
|
|
168
|
+
const rbCtx = { req: { session: null } };
|
|
169
|
+
const User = await loadRbModel("RBUser", rbCtx);
|
|
170
|
+
const user = await User.findById(headerUserId, { tenants: 1, tenantRoles: 1 }).lean();
|
|
171
|
+
const tenantsRaw = user?.tenants;
|
|
172
|
+
const tenants = Array.isArray(tenantsRaw) ? tenantsRaw.map((t) => String(t)) : [];
|
|
173
|
+
if (!tenants.includes(tenantId)) {
|
|
174
|
+
throw new Error("Tenant not authorized for this session");
|
|
175
|
+
}
|
|
176
|
+
const roles = getTenantRolesFromSessionUser(user, tenantId);
|
|
177
|
+
const ability = buildAbility({ tenantId, userId: headerUserId, roles: roles.length ? roles : ["owner"] });
|
|
178
|
+
return { tenantId, userId: headerUserId, ability };
|
|
161
179
|
};
|
|
162
180
|
const getTenantModel = async (tenantId, modelName) => {
|
|
163
181
|
const ctx = {
|
|
@@ -208,16 +226,28 @@ const scheduleDispatchSubscriptionsForModel = (tenantId, modelName) => {
|
|
|
208
226
|
const runAndSendQuery = async ({
|
|
209
227
|
tenantId,
|
|
210
228
|
targetSocketIds,
|
|
229
|
+
ability,
|
|
211
230
|
modelName,
|
|
212
231
|
queryKey,
|
|
213
232
|
query,
|
|
214
233
|
options
|
|
215
234
|
}) => {
|
|
235
|
+
if (!ability.can("read", modelName)) {
|
|
236
|
+
const payload2 = { type: "query_payload", modelName, queryKey, error: "forbidden" };
|
|
237
|
+
for (const socketId of targetSocketIds) {
|
|
238
|
+
const ws = sockets.get(socketId);
|
|
239
|
+
if (!ws) continue;
|
|
240
|
+
sendWs(ws, payload2);
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
216
244
|
const model = await getTenantModel(tenantId, modelName);
|
|
217
245
|
const projection = options.projection ?? void 0;
|
|
218
246
|
const sort = options.sort;
|
|
219
247
|
const limit = normalizeLimit(options.limit);
|
|
220
|
-
const
|
|
248
|
+
const accessQuery = getAccessibleByQuery(ability, "read", modelName);
|
|
249
|
+
const finalQuery = { $and: [query, accessQuery] };
|
|
250
|
+
const queryPromise = model.find(finalQuery, projection);
|
|
221
251
|
if (sort && Object.keys(sort).length) {
|
|
222
252
|
queryPromise.sort(sort);
|
|
223
253
|
}
|
|
@@ -232,27 +262,35 @@ const runAndSendQuery = async ({
|
|
|
232
262
|
};
|
|
233
263
|
const dispatchSubscriptionsForModel = async (tenantId, modelName) => {
|
|
234
264
|
const tenantSubs = subscriptions.get(tenantId);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
265
|
+
if (!tenantSubs || !tenantSubs.size) return;
|
|
266
|
+
for (const userSubs of tenantSubs.values()) {
|
|
267
|
+
const modelSubs = userSubs.get(modelName);
|
|
268
|
+
if (!modelSubs || !modelSubs.size) continue;
|
|
269
|
+
for (const [queryKey, sub] of modelSubs.entries()) {
|
|
270
|
+
const targetSocketIds = Array.from(sub.socketIds);
|
|
271
|
+
if (!targetSocketIds.length) continue;
|
|
272
|
+
const socketId = targetSocketIds[0];
|
|
273
|
+
const meta = socketMeta.get(socketId);
|
|
274
|
+
const ability = meta?.ability;
|
|
275
|
+
if (!ability) continue;
|
|
276
|
+
try {
|
|
277
|
+
await runAndSendQuery({
|
|
278
|
+
tenantId,
|
|
279
|
+
targetSocketIds,
|
|
280
|
+
ability,
|
|
281
|
+
modelName,
|
|
282
|
+
queryKey,
|
|
283
|
+
query: sub.query,
|
|
284
|
+
options: sub.options
|
|
285
|
+
});
|
|
286
|
+
} catch (err) {
|
|
287
|
+
const error = redactErrorMessage(err);
|
|
288
|
+
const payload = { type: "query_payload", modelName, queryKey, error };
|
|
289
|
+
for (const socketId2 of targetSocketIds) {
|
|
290
|
+
const ws = sockets.get(socketId2);
|
|
291
|
+
if (!ws) continue;
|
|
292
|
+
sendWs(ws, payload);
|
|
293
|
+
}
|
|
256
294
|
}
|
|
257
295
|
}
|
|
258
296
|
}
|
|
@@ -286,6 +324,7 @@ const ensureChangeStream = async (tenantId, modelName) => {
|
|
|
286
324
|
const addSocketSubscription = ({
|
|
287
325
|
socketId,
|
|
288
326
|
tenantId,
|
|
327
|
+
userId,
|
|
289
328
|
modelName,
|
|
290
329
|
queryKey,
|
|
291
330
|
query,
|
|
@@ -293,8 +332,10 @@ const addSocketSubscription = ({
|
|
|
293
332
|
}) => {
|
|
294
333
|
const tenantSubs = subscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
|
|
295
334
|
subscriptions.set(tenantId, tenantSubs);
|
|
296
|
-
const
|
|
297
|
-
tenantSubs.set(
|
|
335
|
+
const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
|
|
336
|
+
tenantSubs.set(userId, userSubs);
|
|
337
|
+
const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
|
|
338
|
+
userSubs.set(modelName, modelSubs);
|
|
298
339
|
const existing = modelSubs.get(queryKey);
|
|
299
340
|
if (existing) {
|
|
300
341
|
existing.socketIds.add(socketId);
|
|
@@ -314,11 +355,13 @@ const addSocketSubscription = ({
|
|
|
314
355
|
const removeSocketSubscription = ({
|
|
315
356
|
socketId,
|
|
316
357
|
tenantId,
|
|
358
|
+
userId,
|
|
317
359
|
modelName,
|
|
318
360
|
queryKey
|
|
319
361
|
}) => {
|
|
320
362
|
const tenantSubs = subscriptions.get(tenantId);
|
|
321
|
-
const
|
|
363
|
+
const userSubs = tenantSubs?.get(userId);
|
|
364
|
+
const modelSubs = userSubs?.get(modelName);
|
|
322
365
|
const sub = modelSubs?.get(queryKey);
|
|
323
366
|
if (sub) {
|
|
324
367
|
sub.socketIds.delete(socketId);
|
|
@@ -335,7 +378,21 @@ const removeSocketSubscription = ({
|
|
|
335
378
|
}
|
|
336
379
|
}
|
|
337
380
|
if (modelSubs && modelSubs.size === 0) {
|
|
338
|
-
|
|
381
|
+
userSubs?.delete(modelName);
|
|
382
|
+
}
|
|
383
|
+
if (userSubs && userSubs.size === 0) {
|
|
384
|
+
tenantSubs?.delete(userId);
|
|
385
|
+
}
|
|
386
|
+
const hasAnyModelSubs = (() => {
|
|
387
|
+
const byUser = subscriptions.get(tenantId);
|
|
388
|
+
if (!byUser) return false;
|
|
389
|
+
for (const subs of byUser.values()) {
|
|
390
|
+
const modelSubs2 = subs.get(modelName);
|
|
391
|
+
if (modelSubs2 && modelSubs2.size > 0) return true;
|
|
392
|
+
}
|
|
393
|
+
return false;
|
|
394
|
+
})();
|
|
395
|
+
if (!hasAnyModelSubs) {
|
|
339
396
|
const tenantStreams = changeStreams.get(tenantId);
|
|
340
397
|
const stream = tenantStreams?.get(modelName);
|
|
341
398
|
if (stream) {
|
|
@@ -361,6 +418,7 @@ const cleanupSocket = (socketId) => {
|
|
|
361
418
|
removeSocketSubscription({
|
|
362
419
|
socketId,
|
|
363
420
|
tenantId: meta.tenantId,
|
|
421
|
+
userId: meta.userId,
|
|
364
422
|
modelName,
|
|
365
423
|
queryKey
|
|
366
424
|
});
|
|
@@ -404,6 +462,7 @@ const handleClientMessage = async ({
|
|
|
404
462
|
removeSocketSubscription({
|
|
405
463
|
socketId,
|
|
406
464
|
tenantId: meta.tenantId,
|
|
465
|
+
userId: meta.userId,
|
|
407
466
|
modelName: message.modelName,
|
|
408
467
|
queryKey: message.queryKey
|
|
409
468
|
});
|
|
@@ -411,6 +470,11 @@ const handleClientMessage = async ({
|
|
|
411
470
|
}
|
|
412
471
|
if (!message.query || typeof message.query !== "object") return;
|
|
413
472
|
const options = normalizeOptions(message.options);
|
|
473
|
+
const ability = meta.ability;
|
|
474
|
+
if (!ability.can("read", message.modelName)) {
|
|
475
|
+
sendWs(ws, { type: "query_payload", modelName: message.modelName, queryKey: message.queryKey, error: "forbidden" });
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
414
478
|
if (message.type === "registerQuery") {
|
|
415
479
|
const existing = socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false;
|
|
416
480
|
if (!existing) {
|
|
@@ -427,6 +491,7 @@ const handleClientMessage = async ({
|
|
|
427
491
|
addSocketSubscription({
|
|
428
492
|
socketId,
|
|
429
493
|
tenantId: meta.tenantId,
|
|
494
|
+
userId: meta.userId,
|
|
430
495
|
modelName: message.modelName,
|
|
431
496
|
queryKey: message.queryKey,
|
|
432
497
|
query: message.query,
|
|
@@ -444,6 +509,7 @@ const handleClientMessage = async ({
|
|
|
444
509
|
await runAndSendQuery({
|
|
445
510
|
tenantId: meta.tenantId,
|
|
446
511
|
targetSocketIds: [socketId],
|
|
512
|
+
ability,
|
|
447
513
|
modelName: message.modelName,
|
|
448
514
|
queryKey: message.queryKey,
|
|
449
515
|
query: message.query,
|
|
@@ -556,7 +622,8 @@ const notifyRtsModelChanged = (tenantId, modelName) => {
|
|
|
556
622
|
scheduleDispatchSubscriptionsForModel(tenantId, modelName);
|
|
557
623
|
};
|
|
558
624
|
export {
|
|
559
|
-
initRts
|
|
560
|
-
notifyRtsModelChanged
|
|
561
|
-
registerRtsHandler
|
|
625
|
+
initRts,
|
|
626
|
+
notifyRtsModelChanged,
|
|
627
|
+
registerRtsHandler,
|
|
628
|
+
routes
|
|
562
629
|
};
|