@rpcbase/server 0.476.0 → 0.478.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/{handler-DEEir2xV.js → handler-BOTZftAB.js} +29 -29
- package/dist/{handler-BITFtEr_.js → handler-B_mMDLBO.js} +80 -39
- package/dist/{handler-BYVnU9H-.js → handler-Cl-0-832.js} +1 -1
- package/dist/{handler-CHuOXAtH.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 +102 -87
- package/dist/notifications/api/notifications/handler.d.ts.map +1 -1
- package/dist/notifications.js +1 -1
- 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} +107 -39
- package/dist/{schemas-DI7ewltq.js → schemas-D5T9tDtI.js} +609 -12
- 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 +4 -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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { loadModel, getTenantFilesystemDb } from "@rpcbase/db";
|
|
2
2
|
import { GridFSBucket, ObjectId } from "mongodb";
|
|
3
|
-
import { g as getTenantId, a as getModelCtx, b as
|
|
3
|
+
import { g as getTenantId, a as getModelCtx, b as buildUploadsAbility, c as getUploadSessionAccessQuery, e as ensureUploadIndexes, d as getBucketName, f as getUserId, h as getChunkSizeBytes, i as getSessionTtlMs, j as computeSha256Hex, t as toBufferPayload, n as normalizeSha256Hex, k as getMaxClientUploadBytesPerSecond, l as getRawBodyLimitBytes } from "./shared-UGuDRAKK.js";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
|
-
import { o as object, n as number, s as string, b as boolean, a as array, _ as _enum } from "./schemas-
|
|
5
|
+
import { o as object, n as number, s as string, b as boolean, a as array, _ as _enum } from "./schemas-D5T9tDtI.js";
|
|
6
6
|
const waitForStreamFinished = async (stream) => new Promise((resolve, reject) => {
|
|
7
7
|
stream.once("finish", resolve);
|
|
8
8
|
stream.once("error", reject);
|
|
@@ -37,7 +37,8 @@ const abortUploadStream = async (stream) => {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
|
-
|
|
40
|
+
;
|
|
41
|
+
stream.destroy?.();
|
|
41
42
|
} catch {
|
|
42
43
|
}
|
|
43
44
|
};
|
|
@@ -57,21 +58,21 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
57
58
|
loadModel("RBUploadSession", modelCtx),
|
|
58
59
|
loadModel("RBUploadChunk", modelCtx)
|
|
59
60
|
]);
|
|
60
|
-
const
|
|
61
|
+
const ability = buildUploadsAbility(ctx, tenantId);
|
|
62
|
+
if (!ability.can("update", "RBUploadSession")) {
|
|
63
|
+
ctx.res.status(401);
|
|
64
|
+
return { ok: false, error: "unauthorized" };
|
|
65
|
+
}
|
|
66
|
+
const existing = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, "read")] }).lean();
|
|
61
67
|
if (!existing) {
|
|
62
68
|
ctx.res.status(404);
|
|
63
69
|
return { ok: false, error: "not_found" };
|
|
64
70
|
}
|
|
65
|
-
const ownershipSelector = getOwnershipSelector(ctx, existing);
|
|
66
|
-
if (!ownershipSelector) {
|
|
67
|
-
ctx.res.status(401);
|
|
68
|
-
return { ok: false, error: "unauthorized" };
|
|
69
|
-
}
|
|
70
71
|
if (existing.status === "done" && existing.fileId) {
|
|
71
72
|
return { ok: true, fileId: existing.fileId };
|
|
72
73
|
}
|
|
73
74
|
const locked = await UploadSession.findOneAndUpdate(
|
|
74
|
-
{ _id: uploadId
|
|
75
|
+
{ $and: [{ _id: uploadId }, { status: "uploading" }, getUploadSessionAccessQuery(ability, "update")] },
|
|
75
76
|
{ $set: { status: "assembling" }, $unset: { error: "" } },
|
|
76
77
|
{ new: true }
|
|
77
78
|
).lean();
|
|
@@ -84,7 +85,7 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
84
85
|
const nativeDb = fsDb.db;
|
|
85
86
|
if (!nativeDb) {
|
|
86
87
|
await UploadSession.updateOne(
|
|
87
|
-
{ _id: uploadId,
|
|
88
|
+
{ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, "update")] },
|
|
88
89
|
{ $set: { status: "error", error: "filesystem_db_unavailable" } }
|
|
89
90
|
);
|
|
90
91
|
ctx.res.status(500);
|
|
@@ -106,8 +107,7 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
106
107
|
const cursor = UploadChunk.find({ uploadId }).sort({ index: 1 }).cursor();
|
|
107
108
|
let expectedIndex = 0;
|
|
108
109
|
try {
|
|
109
|
-
for await (const
|
|
110
|
-
const chunkDoc = doc;
|
|
110
|
+
for await (const chunkDoc of cursor) {
|
|
111
111
|
if (chunkDoc.index !== expectedIndex) {
|
|
112
112
|
throw new Error("missing_chunks");
|
|
113
113
|
}
|
|
@@ -131,7 +131,7 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
131
131
|
throw new Error("missing_file_id");
|
|
132
132
|
}
|
|
133
133
|
await UploadSession.updateOne(
|
|
134
|
-
{ _id: uploadId,
|
|
134
|
+
{ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, "update")] },
|
|
135
135
|
{ $set: { status: "done", fileId }, $unset: { error: "" } }
|
|
136
136
|
);
|
|
137
137
|
try {
|
|
@@ -144,14 +144,14 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
144
144
|
await abortUploadStream(uploadStream);
|
|
145
145
|
if (message === "missing_chunks") {
|
|
146
146
|
await UploadSession.updateOne(
|
|
147
|
-
{ _id: uploadId,
|
|
147
|
+
{ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, "update")] },
|
|
148
148
|
{ $set: { status: "uploading" } }
|
|
149
149
|
);
|
|
150
150
|
ctx.res.status(409);
|
|
151
151
|
return { ok: false, error: "missing_chunks" };
|
|
152
152
|
}
|
|
153
153
|
await UploadSession.updateOne(
|
|
154
|
-
{ _id: uploadId,
|
|
154
|
+
{ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, "update")] },
|
|
155
155
|
{ $set: { status: "error", error: message } }
|
|
156
156
|
);
|
|
157
157
|
ctx.res.status(500);
|
|
@@ -174,21 +174,21 @@ const getStatus = async (_payload, ctx) => {
|
|
|
174
174
|
loadModel("RBUploadSession", modelCtx),
|
|
175
175
|
loadModel("RBUploadChunk", modelCtx)
|
|
176
176
|
]);
|
|
177
|
-
const
|
|
177
|
+
const ability = buildUploadsAbility(ctx, tenantId);
|
|
178
|
+
if (!ability.can("read", "RBUploadSession")) {
|
|
179
|
+
ctx.res.status(401);
|
|
180
|
+
return { ok: false, error: "unauthorized" };
|
|
181
|
+
}
|
|
182
|
+
const session = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, "read")] }).lean();
|
|
178
183
|
if (!session) {
|
|
179
184
|
ctx.res.status(404);
|
|
180
185
|
return { ok: false, error: "not_found" };
|
|
181
186
|
}
|
|
182
|
-
const ownershipSelector = getOwnershipSelector(ctx, session);
|
|
183
|
-
if (!ownershipSelector) {
|
|
184
|
-
ctx.res.status(401);
|
|
185
|
-
return { ok: false, error: "unauthorized" };
|
|
186
|
-
}
|
|
187
187
|
const receivedDocs = await UploadChunk.find(
|
|
188
188
|
{ uploadId },
|
|
189
189
|
{ index: 1, _id: 0 }
|
|
190
190
|
).sort({ index: 1 }).lean();
|
|
191
|
-
const received = receivedDocs.map((
|
|
191
|
+
const received = receivedDocs.map((doc) => typeof doc.index === "number" ? doc.index : -1).filter((n) => Number.isInteger(n) && n >= 0);
|
|
192
192
|
return {
|
|
193
193
|
ok: true,
|
|
194
194
|
status: session.status,
|
|
@@ -294,16 +294,16 @@ const uploadChunk = async (payload, ctx) => {
|
|
|
294
294
|
loadModel("RBUploadSession", modelCtx),
|
|
295
295
|
loadModel("RBUploadChunk", modelCtx)
|
|
296
296
|
]);
|
|
297
|
-
const
|
|
297
|
+
const ability = buildUploadsAbility(ctx, tenantId);
|
|
298
|
+
if (!ability.can("update", "RBUploadSession")) {
|
|
299
|
+
ctx.res.status(401);
|
|
300
|
+
return { ok: false, error: "unauthorized" };
|
|
301
|
+
}
|
|
302
|
+
const session = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, "update")] }).lean();
|
|
298
303
|
if (!session) {
|
|
299
304
|
ctx.res.status(404);
|
|
300
305
|
return { ok: false, error: "not_found" };
|
|
301
306
|
}
|
|
302
|
-
const ownershipSelector = getOwnershipSelector(ctx, session);
|
|
303
|
-
if (!ownershipSelector) {
|
|
304
|
-
ctx.res.status(401);
|
|
305
|
-
return { ok: false, error: "unauthorized" };
|
|
306
|
-
}
|
|
307
307
|
if (session.status !== "uploading") {
|
|
308
308
|
ctx.res.status(409);
|
|
309
309
|
return { ok: false, error: "not_uploading" };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadModel } from "@rpcbase/db";
|
|
2
|
+
import { buildAbilityFromSession, getAccessibleByQuery } from "@rpcbase/db/acl";
|
|
2
3
|
import { createNotification, sendNotificationsDigestForUser } from "./notifications.js";
|
|
3
|
-
import { o as object, b as boolean, n as number, a as array, s as string, r as record, u as unknown, _ as _enum } from "./schemas-
|
|
4
|
+
import { o as object, b as boolean, n as number, a as array, s as string, r as record, u as unknown, _ as _enum } from "./schemas-D5T9tDtI.js";
|
|
4
5
|
const getSessionUser = (ctx) => {
|
|
5
6
|
const rawSessionUser = ctx.req.session?.user;
|
|
6
7
|
const userId = typeof rawSessionUser?.id === "string" ? rawSessionUser.id.trim() : "";
|
|
@@ -123,6 +124,11 @@ const listNotifications = async (payload, ctx) => {
|
|
|
123
124
|
if (!session) {
|
|
124
125
|
return { ok: false, error: "unauthorized" };
|
|
125
126
|
}
|
|
127
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
128
|
+
if (!ability.can("read", "RBNotification")) {
|
|
129
|
+
ctx.res.status(403);
|
|
130
|
+
return { ok: false, error: "forbidden" };
|
|
131
|
+
}
|
|
126
132
|
const parsed = listRequestSchema.safeParse(payload);
|
|
127
133
|
if (!parsed.success) {
|
|
128
134
|
ctx.res.status(400);
|
|
@@ -137,33 +143,31 @@ const listNotifications = async (payload, ctx) => {
|
|
|
137
143
|
const settings = await SettingsModel.findOne({ userId }).lean();
|
|
138
144
|
const disabledTopics = buildDisabledTopics(settings, "inApp");
|
|
139
145
|
const NotificationModel = await loadModel("RBNotification", ctx);
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
query.topic = { $nin: disabledTopics };
|
|
149
|
-
}
|
|
146
|
+
const queryFilters = [
|
|
147
|
+
{ userId },
|
|
148
|
+
getAccessibleByQuery(ability, "read", "RBNotification")
|
|
149
|
+
];
|
|
150
|
+
if (!includeArchived) queryFilters.push({ archivedAt: { $exists: false } });
|
|
151
|
+
if (unreadOnly) queryFilters.push({ readAt: { $exists: false } });
|
|
152
|
+
if (disabledTopics.length > 0) queryFilters.push({ topic: { $nin: disabledTopics } });
|
|
153
|
+
const query = { $and: queryFilters };
|
|
150
154
|
const notifications = await NotificationModel.find(query).sort({ createdAt: -1 }).limit(limit).lean();
|
|
151
|
-
const
|
|
152
|
-
userId,
|
|
153
|
-
archivedAt: { $exists: false },
|
|
154
|
-
seenAt: { $exists: false }
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
const
|
|
160
|
-
userId,
|
|
161
|
-
archivedAt: { $exists: false },
|
|
162
|
-
readAt: { $exists: false }
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
155
|
+
const unseenQueryFilters = [
|
|
156
|
+
{ userId },
|
|
157
|
+
{ archivedAt: { $exists: false } },
|
|
158
|
+
{ seenAt: { $exists: false } },
|
|
159
|
+
getAccessibleByQuery(ability, "read", "RBNotification")
|
|
160
|
+
];
|
|
161
|
+
if (disabledTopics.length > 0) unseenQueryFilters.push({ topic: { $nin: disabledTopics } });
|
|
162
|
+
const unseenQuery = { $and: unseenQueryFilters };
|
|
163
|
+
const unreadQueryFilters = [
|
|
164
|
+
{ userId },
|
|
165
|
+
{ archivedAt: { $exists: false } },
|
|
166
|
+
{ readAt: { $exists: false } },
|
|
167
|
+
getAccessibleByQuery(ability, "read", "RBNotification")
|
|
168
|
+
];
|
|
169
|
+
if (disabledTopics.length > 0) unreadQueryFilters.push({ topic: { $nin: disabledTopics } });
|
|
170
|
+
const unreadQuery = { $and: unreadQueryFilters };
|
|
167
171
|
const [unreadCount, unseenCount] = await Promise.all([
|
|
168
172
|
NotificationModel.countDocuments(unreadQuery),
|
|
169
173
|
NotificationModel.countDocuments(unseenQuery)
|
|
@@ -195,6 +199,11 @@ const createNotificationForCurrentUser = async (payload, ctx) => {
|
|
|
195
199
|
if (!session) {
|
|
196
200
|
return { ok: false, error: "unauthorized" };
|
|
197
201
|
}
|
|
202
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
203
|
+
if (!ability.can("create", "RBNotification")) {
|
|
204
|
+
ctx.res.status(403);
|
|
205
|
+
return { ok: false, error: "forbidden" };
|
|
206
|
+
}
|
|
198
207
|
const parsed = createRequestSchema.safeParse(payload);
|
|
199
208
|
if (!parsed.success) {
|
|
200
209
|
ctx.res.status(400);
|
|
@@ -215,6 +224,11 @@ const markRead = async (_payload, ctx) => {
|
|
|
215
224
|
if (!session) {
|
|
216
225
|
return { ok: false, error: "unauthorized" };
|
|
217
226
|
}
|
|
227
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
228
|
+
if (!ability.can("update", "RBNotification")) {
|
|
229
|
+
ctx.res.status(403);
|
|
230
|
+
return { ok: false, error: "forbidden" };
|
|
231
|
+
}
|
|
218
232
|
const notificationId = typeof ctx.req.params.notificationId === "string" ? ctx.req.params.notificationId.trim() : "";
|
|
219
233
|
if (!notificationId) {
|
|
220
234
|
ctx.res.status(400);
|
|
@@ -224,7 +238,7 @@ const markRead = async (_payload, ctx) => {
|
|
|
224
238
|
const now = /* @__PURE__ */ new Date();
|
|
225
239
|
try {
|
|
226
240
|
await NotificationModel.updateOne(
|
|
227
|
-
{ _id: notificationId,
|
|
241
|
+
{ $and: [{ _id: notificationId }, { archivedAt: { $exists: false } }, getAccessibleByQuery(ability, "update", "RBNotification")] },
|
|
228
242
|
{ $set: { readAt: now, seenAt: now } }
|
|
229
243
|
);
|
|
230
244
|
} catch {
|
|
@@ -238,18 +252,23 @@ const markAllRead = async (_payload, ctx) => {
|
|
|
238
252
|
if (!session) {
|
|
239
253
|
return { ok: false, error: "unauthorized" };
|
|
240
254
|
}
|
|
255
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
256
|
+
if (!ability.can("update", "RBNotification")) {
|
|
257
|
+
ctx.res.status(403);
|
|
258
|
+
return { ok: false, error: "forbidden" };
|
|
259
|
+
}
|
|
241
260
|
const SettingsModel = await loadModel("RBNotificationSettings", ctx);
|
|
242
261
|
const settings = await SettingsModel.findOne({ userId: session.userId }).lean();
|
|
243
262
|
const disabledTopics = buildDisabledTopics(settings, "inApp");
|
|
244
263
|
const NotificationModel = await loadModel("RBNotification", ctx);
|
|
245
|
-
const
|
|
246
|
-
userId: session.userId,
|
|
247
|
-
archivedAt: { $exists: false },
|
|
248
|
-
readAt: { $exists: false }
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
264
|
+
const queryFilters = [
|
|
265
|
+
{ userId: session.userId },
|
|
266
|
+
{ archivedAt: { $exists: false } },
|
|
267
|
+
{ readAt: { $exists: false } },
|
|
268
|
+
getAccessibleByQuery(ability, "update", "RBNotification")
|
|
269
|
+
];
|
|
270
|
+
if (disabledTopics.length > 0) queryFilters.push({ topic: { $nin: disabledTopics } });
|
|
271
|
+
const query = { $and: queryFilters };
|
|
253
272
|
const now = /* @__PURE__ */ new Date();
|
|
254
273
|
await NotificationModel.updateMany(query, { $set: { readAt: now, seenAt: now } });
|
|
255
274
|
return { ok: true };
|
|
@@ -259,6 +278,11 @@ const archiveNotification = async (_payload, ctx) => {
|
|
|
259
278
|
if (!session) {
|
|
260
279
|
return { ok: false, error: "unauthorized" };
|
|
261
280
|
}
|
|
281
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
282
|
+
if (!ability.can("update", "RBNotification")) {
|
|
283
|
+
ctx.res.status(403);
|
|
284
|
+
return { ok: false, error: "forbidden" };
|
|
285
|
+
}
|
|
262
286
|
const notificationId = typeof ctx.req.params.notificationId === "string" ? ctx.req.params.notificationId.trim() : "";
|
|
263
287
|
if (!notificationId) {
|
|
264
288
|
ctx.res.status(400);
|
|
@@ -267,7 +291,7 @@ const archiveNotification = async (_payload, ctx) => {
|
|
|
267
291
|
const NotificationModel = await loadModel("RBNotification", ctx);
|
|
268
292
|
try {
|
|
269
293
|
await NotificationModel.updateOne(
|
|
270
|
-
{ _id: notificationId,
|
|
294
|
+
{ $and: [{ _id: notificationId }, { archivedAt: { $exists: false } }, getAccessibleByQuery(ability, "update", "RBNotification")] },
|
|
271
295
|
{ $set: { archivedAt: /* @__PURE__ */ new Date() } }
|
|
272
296
|
);
|
|
273
297
|
} catch {
|
|
@@ -281,8 +305,15 @@ const getSettings = async (_payload, ctx) => {
|
|
|
281
305
|
if (!session) {
|
|
282
306
|
return { ok: false, error: "unauthorized" };
|
|
283
307
|
}
|
|
308
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
309
|
+
if (!ability.can("read", "RBNotificationSettings")) {
|
|
310
|
+
ctx.res.status(403);
|
|
311
|
+
return { ok: false, error: "forbidden" };
|
|
312
|
+
}
|
|
284
313
|
const SettingsModel = await loadModel("RBNotificationSettings", ctx);
|
|
285
|
-
const settings = await SettingsModel.findOne(
|
|
314
|
+
const settings = await SettingsModel.findOne(
|
|
315
|
+
{ $and: [{ userId: session.userId }, getAccessibleByQuery(ability, "read", "RBNotificationSettings")] }
|
|
316
|
+
).lean();
|
|
286
317
|
const digestFrequencyRaw = typeof settings?.digestFrequency === "string" ? settings.digestFrequency : "weekly";
|
|
287
318
|
const digestFrequency = digestFrequencyRaw === "off" || digestFrequencyRaw === "daily" || digestFrequencyRaw === "weekly" ? digestFrequencyRaw : "weekly";
|
|
288
319
|
const topicPreferences = Array.isArray(settings?.topicPreferences) ? settings.topicPreferences.map((pref) => ({
|
|
@@ -305,6 +336,11 @@ const updateSettings = async (payload, ctx) => {
|
|
|
305
336
|
if (!session) {
|
|
306
337
|
return { ok: false, error: "unauthorized" };
|
|
307
338
|
}
|
|
339
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
340
|
+
if (!ability.can("update", "RBNotificationSettings")) {
|
|
341
|
+
ctx.res.status(403);
|
|
342
|
+
return { ok: false, error: "forbidden" };
|
|
343
|
+
}
|
|
308
344
|
const parsed = updateSettingsRequestSchema.safeParse(payload);
|
|
309
345
|
if (!parsed.success) {
|
|
310
346
|
ctx.res.status(400);
|
|
@@ -336,7 +372,7 @@ const updateSettings = async (payload, ctx) => {
|
|
|
336
372
|
ops.$set = nextValues;
|
|
337
373
|
}
|
|
338
374
|
const settings = await SettingsModel.findOneAndUpdate(
|
|
339
|
-
{ userId: session.userId },
|
|
375
|
+
{ $and: [{ userId: session.userId }, getAccessibleByQuery(ability, "update", "RBNotificationSettings")] },
|
|
340
376
|
ops,
|
|
341
377
|
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
342
378
|
).lean();
|
|
@@ -362,6 +398,11 @@ const runDigest = async (payload, ctx) => {
|
|
|
362
398
|
if (!session) {
|
|
363
399
|
return { ok: false, error: "unauthorized" };
|
|
364
400
|
}
|
|
401
|
+
const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session });
|
|
402
|
+
if (!ability.can("read", "RBNotification")) {
|
|
403
|
+
ctx.res.status(403);
|
|
404
|
+
return { ok: false, error: "forbidden" };
|
|
405
|
+
}
|
|
365
406
|
const parsed = digestRunRequestSchema.safeParse(payload);
|
|
366
407
|
if (!parsed.success) {
|
|
367
408
|
ctx.res.status(400);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getTenantFilesystemDb } from "@rpcbase/db";
|
|
2
2
|
import { ObjectId, GridFSBucket } from "mongodb";
|
|
3
|
-
import { g as getTenantId,
|
|
3
|
+
import { g as getTenantId, d as getBucketName } from "./shared-UGuDRAKK.js";
|
|
4
4
|
const deleteFile = async (_payload, ctx) => {
|
|
5
5
|
const tenantId = getTenantId(ctx);
|
|
6
6
|
if (!tenantId) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadModel, ZRBRtsChangeOp } from "@rpcbase/db";
|
|
2
|
-
import {
|
|
2
|
+
import { buildAbilityFromSession } from "@rpcbase/db/acl";
|
|
3
|
+
import { o as object, a as array, s as string, n as number, b as boolean, _ as _enum } from "./schemas-D5T9tDtI.js";
|
|
3
4
|
const Route = "/api/rb/rts/changes";
|
|
4
5
|
const requestSchema = object({
|
|
5
6
|
sinceSeq: number().int().min(0).default(0),
|
|
@@ -68,6 +69,7 @@ const changesHandler = async (payload, ctx) => {
|
|
|
68
69
|
ctx.res.status(401);
|
|
69
70
|
return { ok: false, latestSeq: 0, changes: [] };
|
|
70
71
|
}
|
|
72
|
+
const ability = buildAbilityFromSession({ tenantId, session: ctx.req.session });
|
|
71
73
|
const modelCtx = getModelCtx(ctx, tenantId);
|
|
72
74
|
const [RtsChange, RtsCounter] = await Promise.all([
|
|
73
75
|
loadModel("RBRtsChange", modelCtx),
|
|
@@ -76,24 +78,26 @@ const changesHandler = async (payload, ctx) => {
|
|
|
76
78
|
const counter = await RtsCounter.findOne({ _id: "rts" }, { seq: 1 }).lean();
|
|
77
79
|
const latestSeq = Number(counter?.seq ?? 0) || 0;
|
|
78
80
|
const { sinceSeq, limit, modelNames } = parsed.data;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
const requestedModelNames = Array.isArray(modelNames) && modelNames.length ? modelNames.map((m) => String(m)).filter(Boolean) : null;
|
|
82
|
+
const allowedModelNames = requestedModelNames ? requestedModelNames.filter((m) => ability.can("read", m)) : Array.from(
|
|
83
|
+
new Set(
|
|
84
|
+
(await RtsChange.distinct("modelName")).map((m) => String(m)).filter(Boolean).filter((m) => ability.can("read", m))
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
let earliestSeq;
|
|
88
|
+
if (allowedModelNames.length) {
|
|
89
|
+
const earliest = await RtsChange.findOne({ modelName: { $in: allowedModelNames } }, { seq: 1 }).sort({ seq: 1 }).lean();
|
|
90
|
+
earliestSeq = earliest?.seq ? Number(earliest.seq) : void 0;
|
|
82
91
|
}
|
|
83
|
-
const earliest = await RtsChange.findOne(earliestSelector, { seq: 1 }).sort({ seq: 1 }).lean();
|
|
84
|
-
const earliestSeq = earliest?.seq ? Number(earliest.seq) : void 0;
|
|
85
92
|
const needsFullResync = typeof earliestSeq === "number" && sinceSeq < earliestSeq - 1;
|
|
86
|
-
const selector = { seq: { $gt: sinceSeq } };
|
|
87
|
-
if (Array.isArray(modelNames) && modelNames.length) {
|
|
88
|
-
selector.modelName = { $in: modelNames };
|
|
89
|
-
}
|
|
93
|
+
const selector = { seq: { $gt: sinceSeq }, modelName: { $in: allowedModelNames } };
|
|
90
94
|
const changes = await RtsChange.find(selector, { _id: 0, seq: 1, modelName: 1, op: 1, docId: 1 }).sort({ seq: 1 }).limit(limit).lean();
|
|
91
95
|
return {
|
|
92
96
|
ok: true,
|
|
93
97
|
needsFullResync: needsFullResync || void 0,
|
|
94
98
|
earliestSeq,
|
|
95
99
|
latestSeq,
|
|
96
|
-
changes: Array.isArray(changes) ? changes.filter(isRtsChangeRecord).map((c) => ({
|
|
100
|
+
changes: Array.isArray(changes) ? changes.filter(isRtsChangeRecord).filter((c) => ability.can("read", c.modelName)).map((c) => ({
|
|
97
101
|
seq: Number(c.seq),
|
|
98
102
|
modelName: String(c.modelName),
|
|
99
103
|
op: c.op,
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA"}
|