@rpcbase/server 0.516.0 → 0.518.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 (36) hide show
  1. package/dist/{handler-3gksYOQv.js → handler-0rPClEv4.js} +36 -11
  2. package/dist/handler-0rPClEv4.js.map +1 -0
  3. package/dist/{handler-GZgk5k3c.js → handler-3uwH4f67.js} +103 -59
  4. package/dist/handler-3uwH4f67.js.map +1 -0
  5. package/dist/{handler-DY5UEwlw.js → handler-DnSJAQ_B.js} +2 -2
  6. package/dist/{handler-DY5UEwlw.js.map → handler-DnSJAQ_B.js.map} +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/notifications/api/notifications/handler.d.ts.map +1 -1
  9. package/dist/notifications/createNotification.d.ts +2 -1
  10. package/dist/notifications/createNotification.d.ts.map +1 -1
  11. package/dist/notifications/digest.d.ts +3 -1
  12. package/dist/notifications/digest.d.ts.map +1 -1
  13. package/dist/notifications.js +13 -5
  14. package/dist/notifications.js.map +1 -1
  15. package/dist/{queryExecutor-Dn62mIDL.js → queryExecutor-B7lb2FR1.js} +7 -6
  16. package/dist/queryExecutor-B7lb2FR1.js.map +1 -0
  17. package/dist/rts/index.js +2 -2
  18. package/dist/rts/index.js.map +1 -1
  19. package/dist/rts/queryExecutor.d.ts.map +1 -1
  20. package/dist/{shared-Dy9x-P7F.js → shared-BqZiSOmf.js} +4 -3
  21. package/dist/shared-BqZiSOmf.js.map +1 -0
  22. package/dist/uploads/api/file-uploads/handlers/completeUpload.d.ts.map +1 -1
  23. package/dist/uploads/api/file-uploads/handlers/initUpload.d.ts.map +1 -1
  24. package/dist/uploads/api/file-uploads/postProcessors.d.ts +30 -0
  25. package/dist/uploads/api/file-uploads/postProcessors.d.ts.map +1 -0
  26. package/dist/uploads/api/file-uploads/shared.d.ts +1 -1
  27. package/dist/uploads/api/file-uploads/shared.d.ts.map +1 -1
  28. package/dist/uploads.d.ts +1 -0
  29. package/dist/uploads.d.ts.map +1 -1
  30. package/dist/uploads.js +82 -2
  31. package/dist/uploads.js.map +1 -1
  32. package/package.json +1 -1
  33. package/dist/handler-3gksYOQv.js.map +0 -1
  34. package/dist/handler-GZgk5k3c.js.map +0 -1
  35. package/dist/queryExecutor-Dn62mIDL.js.map +0 -1
  36. package/dist/shared-Dy9x-P7F.js.map +0 -1
@@ -156,12 +156,18 @@ const listNotifications = async (payload, ctx) => {
156
156
  const unreadOnly = parsed.data.unreadOnly === true;
157
157
  const limit = parsed.data.limit ?? 50;
158
158
  const markSeen = parsed.data.markSeen === true;
159
- const SettingsModel = await models.get("RBNotificationSettings", ctx);
159
+ const SettingsModel = await models.get("RBNotificationSettings", {
160
+ req: ctx.req,
161
+ ability
162
+ });
160
163
  const settings = await SettingsModel.findOne({
161
164
  userId
162
165
  }).lean();
163
166
  const disabledTopics = buildDisabledTopics(settings, "inApp");
164
- const NotificationModel = await models.get("RBNotification", ctx);
167
+ const NotificationModel = await models.get("RBNotification", {
168
+ req: ctx.req,
169
+ ability
170
+ });
165
171
  const queryFilters = [{
166
172
  userId
167
173
  }, getAccessibleByQuery(ability, "read", "RBNotification")];
@@ -285,7 +291,7 @@ const createNotificationForCurrentUser = async (payload, ctx) => {
285
291
  body: parsed.data.body,
286
292
  url: parsed.data.url,
287
293
  metadata: parsed.data.metadata
288
- });
294
+ }, ability);
289
295
  return createResponseSchema.parse({
290
296
  ok: true,
291
297
  id: created.id
@@ -318,7 +324,10 @@ const markRead = async (_payload, ctx) => {
318
324
  error: "missing_notification_id"
319
325
  };
320
326
  }
321
- const NotificationModel = await models.get("RBNotification", ctx);
327
+ const NotificationModel = await models.get("RBNotification", {
328
+ req: ctx.req,
329
+ ability
330
+ });
322
331
  const now = /* @__PURE__ */ new Date();
323
332
  try {
324
333
  await NotificationModel.updateOne({
@@ -365,12 +374,18 @@ const markAllRead = async (_payload, ctx) => {
365
374
  error: "forbidden"
366
375
  };
367
376
  }
368
- const SettingsModel = await models.get("RBNotificationSettings", ctx);
377
+ const SettingsModel = await models.get("RBNotificationSettings", {
378
+ req: ctx.req,
379
+ ability
380
+ });
369
381
  const settings = await SettingsModel.findOne({
370
382
  userId: session.userId
371
383
  }).lean();
372
384
  const disabledTopics = buildDisabledTopics(settings, "inApp");
373
- const NotificationModel = await models.get("RBNotification", ctx);
385
+ const NotificationModel = await models.get("RBNotification", {
386
+ req: ctx.req,
387
+ ability
388
+ });
374
389
  const queryFilters = [{
375
390
  userId: session.userId
376
391
  }, {
@@ -428,7 +443,10 @@ const archiveNotification = async (_payload, ctx) => {
428
443
  error: "missing_notification_id"
429
444
  };
430
445
  }
431
- const NotificationModel = await models.get("RBNotification", ctx);
446
+ const NotificationModel = await models.get("RBNotification", {
447
+ req: ctx.req,
448
+ ability
449
+ });
432
450
  try {
433
451
  await NotificationModel.updateOne({
434
452
  $and: [{
@@ -473,7 +491,10 @@ const getSettings = async (_payload, ctx) => {
473
491
  error: "forbidden"
474
492
  };
475
493
  }
476
- const SettingsModel = await models.get("RBNotificationSettings", ctx);
494
+ const SettingsModel = await models.get("RBNotificationSettings", {
495
+ req: ctx.req,
496
+ ability
497
+ });
477
498
  const settings = await SettingsModel.findOne({
478
499
  $and: [{
479
500
  userId: session.userId
@@ -523,7 +544,10 @@ const updateSettings = async (payload, ctx) => {
523
544
  error: "invalid_payload"
524
545
  };
525
546
  }
526
- const SettingsModel = await models.get("RBNotificationSettings", ctx);
547
+ const SettingsModel = await models.get("RBNotificationSettings", {
548
+ req: ctx.req,
549
+ ability
550
+ });
527
551
  const nextValues = {};
528
552
  if (parsed.data.digestFrequency) {
529
553
  nextValues.digestFrequency = parsed.data.digestFrequency;
@@ -556,7 +580,7 @@ const updateSettings = async (payload, ctx) => {
556
580
  }, getAccessibleByQuery(ability, "update", "RBNotificationSettings")]
557
581
  }, ops, {
558
582
  upsert: true,
559
- new: true,
583
+ returnDocument: "after",
560
584
  setDefaultsOnInsert: true
561
585
  }).lean();
562
586
  const digestFrequencyRaw = typeof settings?.digestFrequency === "string" ? settings.digestFrequency : "weekly";
@@ -605,6 +629,7 @@ const runDigest = async (payload, ctx) => {
605
629
  }
606
630
  const result = await sendNotificationsDigestForUser(ctx, {
607
631
  userId: session.userId,
632
+ ability,
608
633
  force: parsed.data.force === true
609
634
  });
610
635
  if (!result.ok) {
@@ -635,4 +660,4 @@ const handler = (api) => {
635
660
  export {
636
661
  handler as default
637
662
  };
638
- //# sourceMappingURL=handler-3gksYOQv.js.map
663
+ //# sourceMappingURL=handler-0rPClEv4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-0rPClEv4.js","sources":["../src/notifications/api/notifications/shared.ts","../src/notifications/api/notifications/index.ts","../src/notifications/api/notifications/handler.ts"],"sourcesContent":["import type { Ctx } from \"@rpcbase/api\"\n\n\ntype NotificationSessionUser = {\n id?: unknown\n currentTenantId?: unknown\n}\n\nexport const getSessionUser = (ctx: Ctx) => {\n const rawSessionUser = (ctx.req.session as { user?: NotificationSessionUser } | null | undefined)?.user\n const userId = typeof rawSessionUser?.id === \"string\" ? rawSessionUser.id.trim() : \"\"\n const tenantId = typeof rawSessionUser?.currentTenantId === \"string\" ? rawSessionUser.currentTenantId.trim() : \"\"\n\n if (!userId) {\n ctx.res.status(401)\n return null\n }\n\n if (!tenantId) {\n ctx.res.status(400)\n return null\n }\n\n return { userId, tenantId }\n}\n\n","import { z } from \"zod\"\n\n\nexport const ListRoute = \"/api/rb/notifications\"\nexport const CreateRoute = \"/api/rb/notifications/create\"\nexport const MarkReadRoute = \"/api/rb/notifications/:notificationId/read\"\nexport const ArchiveRoute = \"/api/rb/notifications/:notificationId/archive\"\nexport const MarkAllReadRoute = \"/api/rb/notifications/mark-all-read\"\nexport const SettingsRoute = \"/api/rb/notifications/settings\"\nexport const DigestRunRoute = \"/api/rb/notifications/digest/run\"\n\nexport const listRequestSchema = z.object({\n includeArchived: z.boolean().optional(),\n unreadOnly: z.boolean().optional(),\n limit: z.number().int().min(1).max(200).optional(),\n markSeen: z.boolean().optional(),\n})\n\nexport type ListRequestPayload = z.infer<typeof listRequestSchema>\n\nexport const createRequestSchema = z.object({\n topic: z.string().trim().min(1).optional(),\n title: z.string().trim().min(1),\n body: z.string().trim().optional(),\n url: z.string().trim().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport type CreateRequestPayload = z.infer<typeof createRequestSchema>\n\nexport const notificationSchema = z.object({\n id: z.string(),\n topic: z.string().optional(),\n title: z.string(),\n body: z.string().optional(),\n url: z.string().optional(),\n createdAt: z.string(),\n seenAt: z.string().optional(),\n readAt: z.string().optional(),\n archivedAt: z.string().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport type NotificationPayload = z.infer<typeof notificationSchema>\n\nexport const listResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n notifications: z.array(notificationSchema).optional(),\n unreadCount: z.number().int().min(0).optional(),\n unseenCount: z.number().int().min(0).optional(),\n})\n\nexport type ListResponsePayload = z.infer<typeof listResponseSchema>\n\nexport const createResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n id: z.string().optional(),\n})\n\nexport type CreateResponsePayload = z.infer<typeof createResponseSchema>\n\nexport const markReadResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n})\n\nexport type MarkReadResponsePayload = z.infer<typeof markReadResponseSchema>\n\nexport const archiveResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n})\n\nexport type ArchiveResponsePayload = z.infer<typeof archiveResponseSchema>\n\nexport const markAllReadResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n})\n\nexport type MarkAllReadResponsePayload = z.infer<typeof markAllReadResponseSchema>\n\nexport const digestFrequencySchema = z.enum([\"off\", \"daily\", \"weekly\"])\n\nexport type DigestFrequency = z.infer<typeof digestFrequencySchema>\n\nexport const topicPreferenceSchema = z.object({\n topic: z.string(),\n inApp: z.boolean(),\n emailDigest: z.boolean(),\n push: z.boolean(),\n})\n\nexport type TopicPreferencePayload = z.infer<typeof topicPreferenceSchema>\n\nexport const settingsSchema = z.object({\n digestFrequency: digestFrequencySchema,\n topicPreferences: z.array(topicPreferenceSchema),\n lastDigestSentAt: z.string().optional(),\n})\n\nexport type SettingsPayload = z.infer<typeof settingsSchema>\n\nexport const settingsResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n settings: settingsSchema.optional(),\n})\n\nexport type SettingsResponsePayload = z.infer<typeof settingsResponseSchema>\n\nexport const updateSettingsRequestSchema = z.object({\n digestFrequency: digestFrequencySchema.optional(),\n topicPreferences: z.array(topicPreferenceSchema).optional(),\n})\n\nexport type UpdateSettingsRequestPayload = z.infer<typeof updateSettingsRequestSchema>\n\nexport const updateSettingsResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n settings: settingsSchema.optional(),\n})\n\nexport type UpdateSettingsResponsePayload = z.infer<typeof updateSettingsResponseSchema>\n\nexport const digestRunRequestSchema = z.object({\n force: z.boolean().optional(),\n})\n\nexport type DigestRunRequestPayload = z.infer<typeof digestRunRequestSchema>\n\nexport const digestRunResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n sent: z.boolean().optional(),\n skippedReason: z.string().optional(),\n})\n\nexport type DigestRunResponsePayload = z.infer<typeof digestRunResponseSchema>\n","import { Api, type ApiHandler } from \"@rpcbase/api\"\nimport { models, type IRBNotification, type IRBNotificationSettings } from \"@rpcbase/db\"\nimport { buildAbilityFromSession, getAccessibleByQuery } from \"@rpcbase/db/acl\"\n\nimport { createNotification } from \"../../createNotification\"\nimport { sendNotificationsDigestForUser } from \"../../digest\"\nimport { getSessionUser } from \"./shared\"\n\nimport * as Notifications from \"./index\"\n\n\ntype NotificationDoc = IRBNotification & { _id: unknown }\ntype SettingsDoc = IRBNotificationSettings & { _id: unknown }\n\nconst toIso = (value: unknown): string | undefined => (value instanceof Date ? value.toISOString() : undefined)\n\nconst buildDisabledTopics = (\n settings: SettingsDoc | null,\n key: \"inApp\" | \"emailDigest\",\n): string[] => {\n const raw = settings?.topicPreferences\n if (!Array.isArray(raw) || raw.length === 0) return []\n\n return raw\n .map((pref) => {\n if (!pref || typeof pref !== \"object\") return null\n const topic = typeof (pref as { topic?: unknown }).topic === \"string\" ? (pref as { topic: string }).topic.trim() : \"\"\n if (!topic) return null\n const enabled = (pref as Record<string, unknown>)[key] === true\n return enabled ? null : topic\n })\n .filter((topic): topic is string => Boolean(topic))\n}\n\nconst listNotifications: ApiHandler<Notifications.ListRequestPayload, Notifications.ListResponsePayload> = async(\n payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"read\", \"RBNotification\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const parsed = Notifications.listRequestSchema.safeParse(payload)\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_payload\" }\n }\n\n const { userId } = session\n const includeArchived = parsed.data.includeArchived === true\n const unreadOnly = parsed.data.unreadOnly === true\n const limit = parsed.data.limit ?? 50\n const markSeen = parsed.data.markSeen === true\n\n const SettingsModel = await models.get(\"RBNotificationSettings\", { req: ctx.req, ability })\n const settings = (await SettingsModel.findOne({ userId }).lean()) as SettingsDoc | null\n const disabledTopics = buildDisabledTopics(settings, \"inApp\")\n\n const NotificationModel = await models.get(\"RBNotification\", { req: ctx.req, ability })\n\n const queryFilters: Record<string, unknown>[] = [\n { userId },\n getAccessibleByQuery(ability, \"read\", \"RBNotification\"),\n ]\n if (!includeArchived) queryFilters.push({ archivedAt: { $exists: false } })\n if (unreadOnly) queryFilters.push({ readAt: { $exists: false } })\n if (disabledTopics.length > 0) queryFilters.push({ topic: { $nin: disabledTopics } })\n const query: Record<string, unknown> = { $and: queryFilters }\n\n const notifications = (await NotificationModel.find(query)\n .sort({ createdAt: -1 })\n .limit(limit)\n .lean()) as NotificationDoc[]\n\n const unseenQueryFilters: Record<string, unknown>[] = [\n { userId },\n { archivedAt: { $exists: false } },\n { seenAt: { $exists: false } },\n getAccessibleByQuery(ability, \"read\", \"RBNotification\"),\n ]\n if (disabledTopics.length > 0) unseenQueryFilters.push({ topic: { $nin: disabledTopics } })\n const unseenQuery: Record<string, unknown> = { $and: unseenQueryFilters }\n\n const unreadQueryFilters: Record<string, unknown>[] = [\n { userId },\n { archivedAt: { $exists: false } },\n { readAt: { $exists: false } },\n getAccessibleByQuery(ability, \"read\", \"RBNotification\"),\n ]\n if (disabledTopics.length > 0) unreadQueryFilters.push({ topic: { $nin: disabledTopics } })\n const unreadQuery: Record<string, unknown> = { $and: unreadQueryFilters }\n\n const [unreadCount, unseenCount] = await Promise.all([\n NotificationModel.countDocuments(unreadQuery),\n NotificationModel.countDocuments(unseenQuery),\n ])\n\n const now = markSeen ? new Date() : null\n if (now && unseenCount > 0) {\n await NotificationModel.updateMany(unseenQuery, { $set: { seenAt: now } })\n }\n\n return Notifications.listResponseSchema.parse({\n ok: true,\n notifications: notifications.map((n) => ({\n id: String(n._id),\n topic: typeof n.topic === \"string\" ? n.topic : undefined,\n title: typeof n.title === \"string\" ? n.title : \"\",\n body: typeof n.body === \"string\" ? n.body : undefined,\n url: typeof n.url === \"string\" ? n.url : undefined,\n createdAt: toIso(n.createdAt) ?? new Date().toISOString(),\n seenAt: toIso(n.seenAt) ?? (now && !n.archivedAt && !n.seenAt ? now.toISOString() : undefined),\n readAt: toIso(n.readAt),\n archivedAt: toIso(n.archivedAt),\n metadata: typeof n.metadata === \"object\" && n.metadata !== null ? (n.metadata as Record<string, unknown>) : undefined,\n })),\n unreadCount,\n unseenCount: now ? 0 : unseenCount,\n })\n}\n\nconst createNotificationForCurrentUser: ApiHandler<Notifications.CreateRequestPayload, Notifications.CreateResponsePayload> = async(\n payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"create\", \"RBNotification\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const parsed = Notifications.createRequestSchema.safeParse(payload)\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_payload\" }\n }\n\n const created = await createNotification(ctx, {\n userId: session.userId,\n topic: parsed.data.topic,\n title: parsed.data.title,\n body: parsed.data.body,\n url: parsed.data.url,\n metadata: parsed.data.metadata,\n }, ability)\n\n return Notifications.createResponseSchema.parse({ ok: true, id: created.id })\n}\n\nconst markRead: ApiHandler<unknown, Notifications.MarkReadResponsePayload> = async(\n _payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"update\", \"RBNotification\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const notificationId = typeof ctx.req.params.notificationId === \"string\" ? ctx.req.params.notificationId.trim() : \"\"\n if (!notificationId) {\n ctx.res.status(400)\n return { ok: false, error: \"missing_notification_id\" }\n }\n\n const NotificationModel = await models.get(\"RBNotification\", { req: ctx.req, ability })\n const now = new Date()\n\n try {\n await NotificationModel.updateOne(\n { $and: [{ _id: notificationId }, { archivedAt: { $exists: false } }, getAccessibleByQuery(ability, \"update\", \"RBNotification\")] },\n { $set: { readAt: now, seenAt: now } },\n )\n } catch {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_notification_id\" }\n }\n\n return { ok: true }\n}\n\nconst markAllRead: ApiHandler<unknown, Notifications.MarkAllReadResponsePayload> = async(\n _payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"update\", \"RBNotification\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const SettingsModel = await models.get(\"RBNotificationSettings\", { req: ctx.req, ability })\n const settings = (await SettingsModel.findOne({ userId: session.userId }).lean()) as SettingsDoc | null\n const disabledTopics = buildDisabledTopics(settings, \"inApp\")\n\n const NotificationModel = await models.get(\"RBNotification\", { req: ctx.req, ability })\n\n const queryFilters: Record<string, unknown>[] = [\n { userId: session.userId },\n { archivedAt: { $exists: false } },\n { readAt: { $exists: false } },\n getAccessibleByQuery(ability, \"update\", \"RBNotification\"),\n ]\n if (disabledTopics.length > 0) queryFilters.push({ topic: { $nin: disabledTopics } })\n const query: Record<string, unknown> = { $and: queryFilters }\n\n const now = new Date()\n await NotificationModel.updateMany(query, { $set: { readAt: now, seenAt: now } })\n\n return { ok: true }\n}\n\nconst archiveNotification: ApiHandler<unknown, Notifications.ArchiveResponsePayload> = async(\n _payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"update\", \"RBNotification\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const notificationId = typeof ctx.req.params.notificationId === \"string\" ? ctx.req.params.notificationId.trim() : \"\"\n if (!notificationId) {\n ctx.res.status(400)\n return { ok: false, error: \"missing_notification_id\" }\n }\n\n const NotificationModel = await models.get(\"RBNotification\", { req: ctx.req, ability })\n\n try {\n await NotificationModel.updateOne(\n { $and: [{ _id: notificationId }, { archivedAt: { $exists: false } }, getAccessibleByQuery(ability, \"update\", \"RBNotification\")] },\n { $set: { archivedAt: new Date() } },\n )\n } catch {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_notification_id\" }\n }\n\n return { ok: true }\n}\n\nconst getSettings: ApiHandler<unknown, Notifications.SettingsResponsePayload> = async(\n _payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"read\", \"RBNotificationSettings\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const SettingsModel = await models.get(\"RBNotificationSettings\", { req: ctx.req, ability })\n const settings = (await SettingsModel.findOne(\n { $and: [{ userId: session.userId }, getAccessibleByQuery(ability, \"read\", \"RBNotificationSettings\")] },\n ).lean()) as SettingsDoc | null\n\n const digestFrequencyRaw = typeof settings?.digestFrequency === \"string\" ? settings.digestFrequency : \"weekly\"\n const digestFrequency: Notifications.DigestFrequency =\n digestFrequencyRaw === \"off\" || digestFrequencyRaw === \"daily\" || digestFrequencyRaw === \"weekly\"\n ? digestFrequencyRaw\n : \"weekly\"\n\n const topicPreferences = Array.isArray(settings?.topicPreferences)\n ? settings!.topicPreferences.map((pref) => ({\n topic: typeof pref.topic === \"string\" ? pref.topic : \"\",\n inApp: pref.inApp === true,\n emailDigest: pref.emailDigest === true,\n push: pref.push === true,\n })).filter((pref) => pref.topic.length > 0)\n : []\n\n return Notifications.settingsResponseSchema.parse({\n ok: true,\n settings: {\n digestFrequency,\n topicPreferences,\n lastDigestSentAt: toIso(settings?.lastDigestSentAt),\n },\n })\n}\n\nconst updateSettings: ApiHandler<Notifications.UpdateSettingsRequestPayload, Notifications.UpdateSettingsResponsePayload> = async(\n payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"update\", \"RBNotificationSettings\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const parsed = Notifications.updateSettingsRequestSchema.safeParse(payload)\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_payload\" }\n }\n\n const SettingsModel = await models.get(\"RBNotificationSettings\", { req: ctx.req, ability })\n const nextValues: Record<string, unknown> = {}\n\n if (parsed.data.digestFrequency) {\n nextValues.digestFrequency = parsed.data.digestFrequency\n }\n\n if (parsed.data.topicPreferences) {\n const seen = new Set<string>()\n const next = parsed.data.topicPreferences\n .map((pref) => ({\n topic: pref.topic.trim(),\n inApp: pref.inApp,\n emailDigest: pref.emailDigest,\n push: pref.push,\n }))\n .filter((pref) => pref.topic.length > 0)\n .filter((pref) => {\n if (seen.has(pref.topic)) return false\n seen.add(pref.topic)\n return true\n })\n\n nextValues.topicPreferences = next\n }\n\n const ops: Record<string, unknown> = {\n $setOnInsert: { userId: session.userId },\n }\n\n if (Object.keys(nextValues).length > 0) {\n ops.$set = nextValues\n }\n\n const settings = (await SettingsModel.findOneAndUpdate(\n { $and: [{ userId: session.userId }, getAccessibleByQuery(ability, \"update\", \"RBNotificationSettings\")] },\n ops,\n { upsert: true, returnDocument: \"after\", setDefaultsOnInsert: true },\n ).lean()) as SettingsDoc | null\n\n const digestFrequencyRaw = typeof settings?.digestFrequency === \"string\" ? settings.digestFrequency : \"weekly\"\n const digestFrequency: Notifications.DigestFrequency =\n digestFrequencyRaw === \"off\" || digestFrequencyRaw === \"daily\" || digestFrequencyRaw === \"weekly\"\n ? digestFrequencyRaw\n : \"weekly\"\n\n const topicPreferences = Array.isArray(settings?.topicPreferences)\n ? settings!.topicPreferences.map((pref) => ({\n topic: typeof pref.topic === \"string\" ? pref.topic : \"\",\n inApp: pref.inApp === true,\n emailDigest: pref.emailDigest === true,\n push: pref.push === true,\n })).filter((pref) => pref.topic.length > 0)\n : []\n\n return Notifications.updateSettingsResponseSchema.parse({\n ok: true,\n settings: {\n digestFrequency,\n topicPreferences,\n lastDigestSentAt: toIso(settings?.lastDigestSentAt),\n },\n })\n}\n\nconst runDigest: ApiHandler<Notifications.DigestRunRequestPayload, Notifications.DigestRunResponsePayload> = async(\n payload,\n ctx,\n) => {\n const session = getSessionUser(ctx)\n if (!session) {\n return { ok: false, error: \"unauthorized\" }\n }\n\n const ability = buildAbilityFromSession({ tenantId: session.tenantId, session: ctx.req.session })\n if (!ability.can(\"read\", \"RBNotification\")) {\n ctx.res.status(403)\n return { ok: false, error: \"forbidden\" }\n }\n\n const parsed = Notifications.digestRunRequestSchema.safeParse(payload)\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_payload\" }\n }\n\n const result = await sendNotificationsDigestForUser(ctx, {\n userId: session.userId,\n ability,\n force: parsed.data.force === true,\n })\n\n if (!result.ok) {\n ctx.res.status(500)\n return { ok: false, error: result.error }\n }\n\n return {\n ok: true,\n sent: result.sent,\n ...(result.skippedReason ? { skippedReason: result.skippedReason } : {}),\n }\n}\n\nexport default (api: Api) => {\n api.post(Notifications.ListRoute, listNotifications)\n api.post(Notifications.CreateRoute, createNotificationForCurrentUser)\n api.post(Notifications.MarkReadRoute, markRead)\n api.post(Notifications.MarkAllReadRoute, markAllRead)\n api.post(Notifications.ArchiveRoute, archiveNotification)\n api.get(Notifications.SettingsRoute, getSettings)\n api.put(Notifications.SettingsRoute, updateSettings)\n api.post(Notifications.DigestRunRoute, runDigest)\n}\n"],"names":["getSessionUser","ctx","rawSessionUser","req","session","user","userId","id","trim","tenantId","currentTenantId","res","status","ListRoute","CreateRoute","MarkReadRoute","ArchiveRoute","MarkAllReadRoute","SettingsRoute","DigestRunRoute","listRequestSchema","z","includeArchived","boolean","optional","unreadOnly","limit","int","min","max","markSeen","createRequestSchema","topic","string","title","body","url","metadata","unknown","notificationSchema","createdAt","seenAt","readAt","archivedAt","listResponseSchema","ok","error","notifications","unreadCount","number","unseenCount","createResponseSchema","digestFrequencySchema","topicPreferenceSchema","inApp","emailDigest","push","settingsSchema","digestFrequency","topicPreferences","lastDigestSentAt","settingsResponseSchema","settings","updateSettingsRequestSchema","updateSettingsResponseSchema","digestRunRequestSchema","force","sent","skippedReason","toIso","value","Date","toISOString","undefined","buildDisabledTopics","key","raw","Array","isArray","length","map","pref","enabled","filter","Boolean","listNotifications","payload","ability","buildAbilityFromSession","can","parsed","Notifications","safeParse","success","data","SettingsModel","models","get","findOne","lean","disabledTopics","NotificationModel","queryFilters","getAccessibleByQuery","$exists","$nin","query","$and","find","sort","unseenQueryFilters","unseenQuery","unreadQueryFilters","unreadQuery","Promise","all","countDocuments","now","updateMany","$set","parse","n","String","_id","createNotificationForCurrentUser","created","createNotification","markRead","_payload","notificationId","params","updateOne","markAllRead","archiveNotification","getSettings","digestFrequencyRaw","updateSettings","nextValues","seen","Set","next","has","add","ops","$setOnInsert","Object","keys","findOneAndUpdate","upsert","returnDocument","setDefaultsOnInsert","runDigest","result","sendNotificationsDigestForUser","api","post","put"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { models, getTenantFilesystemDb } from "@rpcbase/db";
2
2
  import { GridFSBucket, ObjectId } from "mongodb";
3
+ import { runUploadPostProcessors } from "./uploads.js";
3
4
  import { JSDOM } from "jsdom";
4
5
  import createDOMPurify from "dompurify";
5
- 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-Dy9x-P7F.js";
6
+ import { g as getTenantId, b as buildUploadsAbility, a as getModelCtx, 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-BqZiSOmf.js";
6
7
  import { randomBytes } from "node:crypto";
7
8
  import { o as object, n as number, b as boolean, s as string, a as array, _ as _enum } from "./schemas-Cjdjgehl.js";
8
9
  const MAX_SVG_BYTES = 128 * 1024;
@@ -136,9 +137,9 @@ const completeUpload = async (_payload, ctx) => {
136
137
  error: "invalid_upload_id"
137
138
  };
138
139
  }
139
- const modelCtx = getModelCtx(ctx, tenantId);
140
- const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
141
140
  const ability = buildUploadsAbility(ctx, tenantId);
141
+ const modelCtx = getModelCtx(ctx, tenantId, ability);
142
+ const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
142
143
  if (!ability.can("update", "RBUploadSession")) {
143
144
  ctx.res.status(401);
144
145
  return {
@@ -178,7 +179,7 @@ const completeUpload = async (_payload, ctx) => {
178
179
  error: ""
179
180
  }
180
181
  }, {
181
- new: true
182
+ returnDocument: "after"
182
183
  }).lean();
183
184
  if (!locked) {
184
185
  ctx.res.status(409);
@@ -217,6 +218,23 @@ const completeUpload = async (_payload, ctx) => {
217
218
  const declaredMimeType = locked.mimeType.trim().toLowerCase();
218
219
  const declaredSvg = declaredMimeType === "image/svg+xml" || locked.filename.trim().toLowerCase().endsWith(".svg");
219
220
  let uploadStream = null;
221
+ let finalMimeType = locked.mimeType;
222
+ let inlineProcessors = [];
223
+ let finalMetadata = {
224
+ uploadId,
225
+ tenantId,
226
+ mimeType: locked.mimeType,
227
+ totalSize: locked.totalSize,
228
+ ...typeof locked.isPublic === "boolean" ? {
229
+ isPublic: locked.isPublic
230
+ } : {},
231
+ ...typeof locked.ownerKeyHash === "string" ? {
232
+ ownerKeyHash: locked.ownerKeyHash
233
+ } : {},
234
+ ...lockedUserId ? {
235
+ userId: lockedUserId
236
+ } : {}
237
+ };
220
238
  try {
221
239
  if (!shouldBufferForProcessing && declaredSvg) {
222
240
  throw new Error("svg_too_large");
@@ -261,22 +279,23 @@ const completeUpload = async (_payload, ctx) => {
261
279
  if (processors.length) {
262
280
  throw new Error("svg_too_large");
263
281
  }
282
+ finalMetadata = {
283
+ uploadId,
284
+ tenantId,
285
+ mimeType: locked.mimeType,
286
+ totalSize: locked.totalSize,
287
+ ...typeof locked.isPublic === "boolean" ? {
288
+ isPublic: locked.isPublic
289
+ } : {},
290
+ ...typeof locked.ownerKeyHash === "string" ? {
291
+ ownerKeyHash: locked.ownerKeyHash
292
+ } : {},
293
+ ...lockedUserId ? {
294
+ userId: lockedUserId
295
+ } : {}
296
+ };
264
297
  uploadStream = bucket.openUploadStream(locked.filename, {
265
- metadata: {
266
- uploadId,
267
- tenantId,
268
- mimeType: locked.mimeType,
269
- totalSize: locked.totalSize,
270
- ...typeof locked.isPublic === "boolean" ? {
271
- isPublic: locked.isPublic
272
- } : {},
273
- ...typeof locked.ownerKeyHash === "string" ? {
274
- ownerKeyHash: locked.ownerKeyHash
275
- } : {},
276
- ...lockedUserId ? {
277
- userId: lockedUserId
278
- } : {}
279
- }
298
+ metadata: finalMetadata
280
299
  });
281
300
  for (const pending of pendingChunks) {
282
301
  await writeToStream(uploadStream, pending);
@@ -307,26 +326,29 @@ const completeUpload = async (_payload, ctx) => {
307
326
  filename: locked.filename,
308
327
  clientMimeType: locked.mimeType
309
328
  });
329
+ finalMimeType = processedMimeType;
330
+ inlineProcessors = applied;
331
+ finalMetadata = {
332
+ uploadId,
333
+ tenantId,
334
+ mimeType: processedMimeType,
335
+ totalSize: locked.totalSize,
336
+ ...applied.length ? {
337
+ processors: applied,
338
+ processedSize: processed.length
339
+ } : {},
340
+ ...typeof locked.isPublic === "boolean" ? {
341
+ isPublic: locked.isPublic
342
+ } : {},
343
+ ...typeof locked.ownerKeyHash === "string" ? {
344
+ ownerKeyHash: locked.ownerKeyHash
345
+ } : {},
346
+ ...lockedUserId ? {
347
+ userId: lockedUserId
348
+ } : {}
349
+ };
310
350
  uploadStream = bucket.openUploadStream(locked.filename, {
311
- metadata: {
312
- uploadId,
313
- tenantId,
314
- mimeType: processedMimeType,
315
- totalSize: locked.totalSize,
316
- ...applied.length ? {
317
- processors: applied,
318
- processedSize: processed.length
319
- } : {},
320
- ...typeof locked.isPublic === "boolean" ? {
321
- isPublic: locked.isPublic
322
- } : {},
323
- ...typeof locked.ownerKeyHash === "string" ? {
324
- ownerKeyHash: locked.ownerKeyHash
325
- } : {},
326
- ...lockedUserId ? {
327
- userId: lockedUserId
328
- } : {}
329
- }
351
+ metadata: finalMetadata
330
352
  });
331
353
  const finished = waitForStreamFinished(uploadStream);
332
354
  uploadStream.end(processed);
@@ -343,22 +365,23 @@ const completeUpload = async (_payload, ctx) => {
343
365
  if (processors.length) {
344
366
  throw new Error("svg_too_large");
345
367
  }
368
+ finalMetadata = {
369
+ uploadId,
370
+ tenantId,
371
+ mimeType: locked.mimeType,
372
+ totalSize: locked.totalSize,
373
+ ...typeof locked.isPublic === "boolean" ? {
374
+ isPublic: locked.isPublic
375
+ } : {},
376
+ ...typeof locked.ownerKeyHash === "string" ? {
377
+ ownerKeyHash: locked.ownerKeyHash
378
+ } : {},
379
+ ...lockedUserId ? {
380
+ userId: lockedUserId
381
+ } : {}
382
+ };
346
383
  uploadStream = bucket.openUploadStream(locked.filename, {
347
- metadata: {
348
- uploadId,
349
- tenantId,
350
- mimeType: locked.mimeType,
351
- totalSize: locked.totalSize,
352
- ...typeof locked.isPublic === "boolean" ? {
353
- isPublic: locked.isPublic
354
- } : {},
355
- ...typeof locked.ownerKeyHash === "string" ? {
356
- ownerKeyHash: locked.ownerKeyHash
357
- } : {},
358
- ...lockedUserId ? {
359
- userId: lockedUserId
360
- } : {}
361
- }
384
+ metadata: finalMetadata
362
385
  });
363
386
  for (const pending of pendingChunks) {
364
387
  await writeToStream(uploadStream, pending);
@@ -386,6 +409,26 @@ const completeUpload = async (_payload, ctx) => {
386
409
  error: ""
387
410
  }
388
411
  });
412
+ await runUploadPostProcessors({
413
+ tenantId,
414
+ uploadId,
415
+ fileId,
416
+ filename: locked.filename,
417
+ mimeType: finalMimeType,
418
+ clientMimeType: locked.mimeType,
419
+ totalSize: locked.totalSize,
420
+ ...typeof locked.isPublic === "boolean" ? {
421
+ isPublic: locked.isPublic
422
+ } : {},
423
+ ...typeof locked.ownerKeyHash === "string" ? {
424
+ ownerKeyHash: locked.ownerKeyHash
425
+ } : {},
426
+ ...lockedUserId ? {
427
+ userId: lockedUserId
428
+ } : {},
429
+ inlineProcessors,
430
+ metadata: finalMetadata
431
+ });
389
432
  try {
390
433
  await UploadChunk.deleteMany({
391
434
  uploadId
@@ -483,9 +526,9 @@ const getStatus = async (_payload, ctx) => {
483
526
  error: "invalid_upload_id"
484
527
  };
485
528
  }
486
- const modelCtx = getModelCtx(ctx, tenantId);
487
- const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
488
529
  const ability = buildUploadsAbility(ctx, tenantId);
530
+ const modelCtx = getModelCtx(ctx, tenantId, ability);
531
+ const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
489
532
  if (!ability.can("read", "RBUploadSession")) {
490
533
  ctx.res.status(401);
491
534
  return {
@@ -583,7 +626,8 @@ const initUpload = async (payload, ctx) => {
583
626
  isPublic
584
627
  } = parsed.data;
585
628
  const chunksTotal = Math.ceil(totalSize / chunkSize);
586
- const modelCtx = getModelCtx(ctx, tenantId);
629
+ const ability = buildUploadsAbility(ctx, tenantId);
630
+ const modelCtx = getModelCtx(ctx, tenantId, ability);
587
631
  const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
588
632
  await ensureUploadIndexes(UploadSession, UploadChunk);
589
633
  const uploadId = new ObjectId().toString();
@@ -640,9 +684,9 @@ const uploadChunk = async (payload, ctx) => {
640
684
  error: "invalid_chunk_ref"
641
685
  };
642
686
  }
643
- const modelCtx = getModelCtx(ctx, tenantId);
644
- const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
645
687
  const ability = buildUploadsAbility(ctx, tenantId);
688
+ const modelCtx = getModelCtx(ctx, tenantId, ability);
689
+ const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
646
690
  if (!ability.can("update", "RBUploadSession")) {
647
691
  ctx.res.status(401);
648
692
  return {
@@ -877,4 +921,4 @@ const handler = (api) => {
877
921
  export {
878
922
  handler as default
879
923
  };
880
- //# sourceMappingURL=handler-GZgk5k3c.js.map
924
+ //# sourceMappingURL=handler-3uwH4f67.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-3uwH4f67.js","sources":["../src/uploads/api/file-uploads/processors/sanitizeSvg.ts","../src/uploads/api/file-uploads/processors/index.ts","../src/uploads/api/file-uploads/handlers/completeUpload.ts","../src/uploads/api/file-uploads/handlers/getStatus.ts","../src/uploads/api/file-uploads/index.ts","../src/uploads/api/file-uploads/handlers/initUpload.ts","../src/uploads/api/file-uploads/handlers/uploadChunk.ts","../src/uploads/api/file-uploads/middleware/rawBodyParser.ts","../src/uploads/api/file-uploads/handler.ts"],"sourcesContent":["import { JSDOM } from \"jsdom\"\nimport createDOMPurify from \"dompurify\"\n\nimport type { UploadFileProcessor } from \"./index\"\n\n\nconst MAX_SVG_BYTES = 128 * 1024\n\nconst window = new JSDOM(\"\").window\nconst DOMPurify = createDOMPurify(window)\n\nconst normalizeForSniff = (raw: string): string => raw.replace(/^\\uFEFF/, \"\").trimStart()\n\nconst looksLikeSvgText = (text: string): boolean => {\n const normalized = normalizeForSniff(text)\n if (!normalized.startsWith(\"<\")) return false\n return /<svg(?:\\s|>)/i.test(normalized)\n}\n\nexport const looksLikeSvg = (sniff: Buffer): boolean => looksLikeSvgText(sniff.toString(\"utf8\"))\n\nexport const sanitizeSvg = (svg: string): string =>\n DOMPurify.sanitize(svg, {\n USE_PROFILES: { svg: true, svgFilters: true },\n })\n\nexport const sanitizeSvgProcessor: UploadFileProcessor = {\n id: \"sanitize-svg\",\n maxBytes: MAX_SVG_BYTES,\n match: ({ sniff }) => looksLikeSvg(sniff),\n process: (data): { data: Buffer; mimeType: string } => {\n if (data.length > MAX_SVG_BYTES) {\n throw new Error(\"svg_too_large\")\n }\n\n const svgText = data.toString(\"utf8\")\n if (!looksLikeSvgText(svgText)) {\n throw new Error(\"svg_invalid\")\n }\n\n const sanitized = sanitizeSvg(svgText)\n if (!sanitized.trim() || !looksLikeSvgText(sanitized)) {\n throw new Error(\"svg_sanitize_failed\")\n }\n\n const sanitizedBuffer = Buffer.from(sanitized, \"utf8\")\n if (sanitizedBuffer.length > MAX_SVG_BYTES) {\n throw new Error(\"svg_too_large\")\n }\n\n return { data: sanitizedBuffer, mimeType: \"image/svg+xml\" }\n },\n}\n\n","import { sanitizeSvgProcessor } from \"./sanitizeSvg\"\n\n\nexport type UploadFileProcessorContext = {\n filename: string\n clientMimeType: string\n totalSize: number\n sniff: Buffer\n}\n\nexport type UploadFileProcessorResult = {\n data: Buffer\n mimeType?: string\n}\n\nexport type UploadFileProcessor = {\n id: string\n maxBytes: number\n match: (ctx: UploadFileProcessorContext) => boolean\n process: (data: Buffer, ctx: UploadFileProcessorContext) => Promise<UploadFileProcessorResult> | UploadFileProcessorResult\n}\n\nexport const uploadProcessors = Object.freeze([sanitizeSvgProcessor] satisfies UploadFileProcessor[])\n\nexport const getMaxUploadProcessorBytes = (): number =>\n uploadProcessors.reduce((max, processor) => Math.max(max, processor.maxBytes), 0)\n\nexport const selectUploadProcessors = (ctx: UploadFileProcessorContext): UploadFileProcessor[] =>\n uploadProcessors.filter((processor) => processor.match(ctx))\n\nexport const applyUploadProcessors = async (\n data: Buffer,\n ctx: Omit<UploadFileProcessorContext, \"sniff\" | \"totalSize\">,\n): Promise<{ data: Buffer; mimeType: string; applied: string[] }> => {\n let currentData = data\n let currentMimeType = ctx.clientMimeType\n const applied: string[] = []\n\n for (const processor of uploadProcessors) {\n const processorCtx: UploadFileProcessorContext = {\n filename: ctx.filename,\n clientMimeType: currentMimeType,\n totalSize: currentData.length,\n sniff: currentData,\n }\n\n if (!processor.match(processorCtx)) continue\n\n if (currentData.length > processor.maxBytes) {\n throw new Error(\"processor_input_too_large\")\n }\n\n const result = await processor.process(currentData, processorCtx)\n currentData = result.data\n if (typeof result.mimeType === \"string\" && result.mimeType.trim()) {\n currentMimeType = result.mimeType.trim()\n }\n applied.push(processor.id)\n }\n\n return {\n data: currentData,\n mimeType: currentMimeType,\n applied,\n }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb, models } from \"@rpcbase/db\"\nimport { GridFSBucket } from \"mongodb\"\nimport type { Model } from \"mongoose\"\n\nimport * as Uploads from \"../index\"\nimport { runUploadPostProcessors } from \"../postProcessors\"\nimport { applyUploadProcessors, getMaxUploadProcessorBytes, selectUploadProcessors } from \"../processors\"\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n ensureUploadIndexes,\n getBucketName,\n getModelCtx,\n getUploadSessionAccessQuery,\n getTenantId,\n} from \"../shared\"\n\n\nconst waitForStreamFinished = async (stream: NodeJS.WritableStream): Promise<void> => new Promise((resolve, reject) => {\n stream.once(\"finish\", resolve)\n stream.once(\"error\", reject)\n})\n\nconst writeToStream = async (stream: NodeJS.WritableStream, chunk: Buffer): Promise<void> => {\n const ok = stream.write(chunk)\n if (ok) return\n await new Promise<void>((resolve, reject) => {\n const onDrain = () => {\n cleanup()\n resolve()\n }\n\n const onError = (error: unknown) => {\n cleanup()\n reject(error)\n }\n\n const cleanup = () => {\n stream.off(\"drain\", onDrain)\n stream.off(\"error\", onError)\n }\n\n stream.on(\"drain\", onDrain)\n stream.on(\"error\", onError)\n })\n}\n\nconst abortUploadStream = async (stream: unknown): Promise<void> => {\n if (!stream) return\n if (typeof (stream as { abort?: unknown }).abort === \"function\") {\n try {\n await (stream as { abort: () => Promise<void> | void }).abort()\n return\n } catch {\n //\n }\n }\n try {\n ;(stream as { destroy?: () => void }).destroy?.()\n } catch {\n //\n }\n}\n\nexport const completeUpload: ApiHandler<Record<string, never>, Uploads.CompleteResponsePayload, SessionUser> = async (\n _payload,\n ctx,\n): Promise<Uploads.CompleteResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const uploadId = String(ctx.req.params?.uploadId ?? \"\").trim()\n if (!uploadId) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_upload_id\" }\n }\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n if (!ability.can(\"update\", \"RBUploadSession\")) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const existing = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"read\")] }).lean()\n if (!existing) {\n ctx.res.status(404)\n return { ok: false, error: \"not_found\" }\n }\n\n if (existing.status === \"done\" && existing.fileId) {\n return { ok: true, fileId: existing.fileId }\n }\n\n const locked = await UploadSession.findOneAndUpdate(\n { $and: [{ _id: uploadId }, { status: \"uploading\" }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"assembling\" }, $unset: { error: \"\" } },\n { returnDocument: \"after\" },\n ).lean()\n\n if (!locked) {\n ctx.res.status(409)\n return { ok: false, error: \"not_uploading\" }\n }\n\n await ensureUploadIndexes(UploadSession, UploadChunk)\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: \"filesystem_db_unavailable\" } },\n )\n ctx.res.status(500)\n return { ok: false, error: \"assembly_failed\" }\n }\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const lockedUserId = typeof locked.userId === \"string\" ? locked.userId : undefined\n const maxProcessorBytes = getMaxUploadProcessorBytes()\n const shouldBufferForProcessing = locked.totalSize <= maxProcessorBytes\n const declaredMimeType = locked.mimeType.trim().toLowerCase()\n const declaredSvg = declaredMimeType === \"image/svg+xml\" || locked.filename.trim().toLowerCase().endsWith(\".svg\")\n\n let uploadStream: NodeJS.WritableStream | null = null\n let finalMimeType = locked.mimeType\n let inlineProcessors: string[] = []\n let finalMetadata: Record<string, unknown> = {\n uploadId,\n tenantId,\n mimeType: locked.mimeType,\n totalSize: locked.totalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n\n try {\n if (!shouldBufferForProcessing && declaredSvg) {\n throw new Error(\"svg_too_large\")\n }\n\n const cursor = UploadChunk.find({ uploadId }).sort({ index: 1 }).cursor() as unknown as AsyncIterable<UploadChunkDoc> & {\n close: () => Promise<void>\n }\n\n let expectedIndex = 0\n const chunks: Buffer[] = []\n let bufferedBytes = 0\n\n const pendingChunks: Buffer[] = []\n const sniffParts: Buffer[] = []\n let sniffBytes = 0\n\n try {\n for await (const chunkDoc of cursor) {\n if (chunkDoc.index !== expectedIndex) {\n throw new Error(\"missing_chunks\")\n }\n\n const chunk = chunkDoc.data\n\n if (shouldBufferForProcessing) {\n chunks.push(chunk)\n bufferedBytes += chunk.length\n } else if (!uploadStream) {\n pendingChunks.push(chunk)\n\n if (sniffBytes < maxProcessorBytes) {\n const slice = chunk.subarray(0, Math.min(chunk.length, maxProcessorBytes - sniffBytes))\n if (slice.length) {\n sniffParts.push(slice)\n sniffBytes += slice.length\n }\n }\n\n if (sniffBytes >= maxProcessorBytes) {\n const sniff = Buffer.concat(sniffParts, sniffBytes)\n const processors = selectUploadProcessors({\n filename: locked.filename,\n clientMimeType: locked.mimeType,\n totalSize: locked.totalSize,\n sniff,\n })\n\n if (processors.length) {\n throw new Error(\"svg_too_large\")\n }\n\n finalMetadata = {\n uploadId,\n tenantId,\n mimeType: locked.mimeType,\n totalSize: locked.totalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n uploadStream = bucket.openUploadStream(locked.filename, {\n metadata: finalMetadata,\n })\n\n for (const pending of pendingChunks) {\n await writeToStream(uploadStream, pending)\n }\n pendingChunks.length = 0\n }\n } else {\n await writeToStream(uploadStream, chunk)\n }\n\n expectedIndex += 1\n }\n } finally {\n try {\n await cursor.close()\n } catch {\n //\n }\n }\n\n if (expectedIndex !== locked.chunksTotal) {\n throw new Error(\"missing_chunks\")\n }\n\n if (shouldBufferForProcessing) {\n const assembled = Buffer.concat(chunks, bufferedBytes)\n const { data: processed, mimeType: processedMimeType, applied } = await applyUploadProcessors(assembled, {\n filename: locked.filename,\n clientMimeType: locked.mimeType,\n })\n\n finalMimeType = processedMimeType\n inlineProcessors = applied\n finalMetadata = {\n uploadId,\n tenantId,\n mimeType: processedMimeType,\n totalSize: locked.totalSize,\n ...(applied.length ? { processors: applied, processedSize: processed.length } : {}),\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n uploadStream = bucket.openUploadStream(locked.filename, {\n metadata: finalMetadata,\n })\n\n const finished = waitForStreamFinished(uploadStream)\n uploadStream.end(processed)\n await finished\n } else {\n if (!uploadStream) {\n const sniff = Buffer.concat(sniffParts, sniffBytes)\n const processors = selectUploadProcessors({\n filename: locked.filename,\n clientMimeType: locked.mimeType,\n totalSize: locked.totalSize,\n sniff,\n })\n\n if (processors.length) {\n throw new Error(\"svg_too_large\")\n }\n\n finalMetadata = {\n uploadId,\n tenantId,\n mimeType: locked.mimeType,\n totalSize: locked.totalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n uploadStream = bucket.openUploadStream(locked.filename, {\n metadata: finalMetadata,\n })\n\n for (const pending of pendingChunks) {\n await writeToStream(uploadStream, pending)\n }\n pendingChunks.length = 0\n }\n\n const finished = waitForStreamFinished(uploadStream)\n uploadStream.end()\n await finished\n }\n\n const fileId = String((uploadStream as unknown as { id?: unknown }).id ?? \"\")\n if (!fileId) {\n throw new Error(\"missing_file_id\")\n }\n\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"done\", fileId }, $unset: { error: \"\" } },\n )\n\n await runUploadPostProcessors({\n tenantId,\n uploadId,\n fileId,\n filename: locked.filename,\n mimeType: finalMimeType,\n clientMimeType: locked.mimeType,\n totalSize: locked.totalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n inlineProcessors,\n metadata: finalMetadata,\n })\n\n try {\n await UploadChunk.deleteMany({ uploadId })\n } catch {\n //\n }\n\n return { ok: true, fileId }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n\n await abortUploadStream(uploadStream)\n\n if (message === \"missing_chunks\") {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"uploading\" } },\n )\n ctx.res.status(409)\n return { ok: false, error: \"missing_chunks\" }\n }\n\n if (message === \"svg_too_large\") {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n ctx.res.status(413)\n return { ok: false, error: message }\n }\n\n if (message === \"svg_invalid\" || message === \"svg_sanitize_failed\") {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n ctx.res.status(400)\n return { ok: false, error: message }\n }\n\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n\n ctx.res.status(500)\n return { ok: false, error: \"assembly_failed\" }\n }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport type { Model } from \"mongoose\"\n\nimport * as Uploads from \"../index\"\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n getModelCtx,\n getUploadSessionAccessQuery,\n getTenantId,\n} from \"../shared\"\n\n\nexport const getStatus: ApiHandler<Record<string, never>, Uploads.StatusResponsePayload, SessionUser> = async (\n _payload,\n ctx,\n): Promise<Uploads.StatusResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const uploadId = String(ctx.req.params?.uploadId ?? \"\").trim()\n if (!uploadId) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_upload_id\" }\n }\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n if (!ability.can(\"read\", \"RBUploadSession\")) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const session = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"read\")] }).lean()\n if (!session) {\n ctx.res.status(404)\n return { ok: false, error: \"not_found\" }\n }\n\n const receivedDocs = await UploadChunk.find(\n { uploadId },\n { index: 1, _id: 0 },\n ).sort({ index: 1 }).lean()\n\n const received = (receivedDocs as unknown as Array<{ index?: unknown }>)\n .map((doc) => (typeof doc.index === \"number\" ? doc.index : -1))\n .filter((n) => Number.isInteger(n) && n >= 0)\n\n return {\n ok: true,\n status: session.status,\n chunkSize: session.chunkSize,\n chunksTotal: session.chunksTotal,\n received,\n ...(session.fileId ? { fileId: session.fileId } : {}),\n }\n}\n","import { z } from \"zod\"\n\n\nexport const InitRoute = \"/api/rb/file-uploads\"\nexport const ChunkRoute = \"/api/rb/file-uploads/:uploadId/chunks/:index\"\nexport const StatusRoute = \"/api/rb/file-uploads/:uploadId/status\"\nexport const CompleteRoute = \"/api/rb/file-uploads/:uploadId/complete\"\n\nexport const initRequestSchema = z.object({\n filename: z.string().min(1),\n mimeType: z.string().min(1),\n isPublic: z.boolean().optional(),\n totalSize: z.number().int().min(1),\n})\n\nexport type InitRequestPayload = z.infer<typeof initRequestSchema>\n\nexport const initResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n uploadId: z.string().optional(),\n uploadKey: z.string().optional(),\n chunkSize: z.number().int().optional(),\n chunksTotal: z.number().int().optional(),\n})\n\nexport type InitResponsePayload = z.infer<typeof initResponseSchema>\n\nexport const statusResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n status: z.enum([\"uploading\", \"assembling\", \"done\", \"error\"]).optional(),\n chunkSize: z.number().int().optional(),\n chunksTotal: z.number().int().optional(),\n received: z.array(z.number().int().min(0)).optional(),\n fileId: z.string().optional(),\n})\n\nexport type StatusResponsePayload = z.infer<typeof statusResponseSchema>\n\nexport const completeResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n fileId: z.string().optional(),\n})\n\nexport type CompleteResponsePayload = z.infer<typeof completeResponseSchema>\n","import { randomBytes } from \"node:crypto\"\n\nimport { ApiHandler } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport { ObjectId } from \"mongodb\"\nimport type { Model } from \"mongoose\"\n\nimport * as Uploads from \"../index\"\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n computeSha256Hex,\n ensureUploadIndexes,\n getChunkSizeBytes,\n getModelCtx,\n getSessionTtlMs,\n getTenantId,\n getUserId,\n} from \"../shared\"\n\n\nexport const initUpload: ApiHandler<Uploads.InitRequestPayload, Uploads.InitResponsePayload, SessionUser> = async (\n payload,\n ctx,\n): Promise<Uploads.InitResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const userId = getUserId(ctx)\n\n const parsed = Uploads.initRequestSchema.safeParse(payload ?? {})\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_payload\" }\n }\n\n const chunkSize = getChunkSizeBytes()\n const { filename, mimeType, totalSize, isPublic } = parsed.data\n const chunksTotal = Math.ceil(totalSize / chunkSize)\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n await ensureUploadIndexes(UploadSession, UploadChunk)\n\n const uploadId = new ObjectId().toString()\n const now = Date.now()\n const expiresAt = new Date(now + getSessionTtlMs())\n\n const uploadKey = userId ? null : randomBytes(32).toString(\"base64url\")\n const ownerKeyHash = uploadKey ? computeSha256Hex(Buffer.from(uploadKey)) : undefined\n\n await UploadSession.create({\n _id: uploadId,\n ...(userId ? { userId } : {}),\n ...(ownerKeyHash ? { ownerKeyHash } : {}),\n filename,\n mimeType,\n ...(typeof isPublic === \"boolean\" ? { isPublic } : {}),\n totalSize,\n chunkSize,\n chunksTotal,\n status: \"uploading\",\n createdAt: new Date(now),\n expiresAt,\n })\n\n return {\n ok: true,\n uploadId,\n chunkSize,\n chunksTotal,\n ...(uploadKey ? { uploadKey } : {}),\n }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport type { Model } from \"mongoose\"\n\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n computeSha256Hex,\n ensureUploadIndexes,\n getModelCtx,\n getUploadSessionAccessQuery,\n getTenantId,\n normalizeSha256Hex,\n toBufferPayload,\n} from \"../shared\"\n\n\ntype ChunkResponsePayload = {\n ok: boolean\n error?: string\n}\n\nexport const uploadChunk: ApiHandler<Buffer, ChunkResponsePayload, SessionUser> = async (\n payload,\n ctx,\n): Promise<ChunkResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const uploadId = String(ctx.req.params?.uploadId ?? \"\").trim()\n const indexRaw = String(ctx.req.params?.index ?? \"\").trim()\n const index = Number(indexRaw)\n\n if (!uploadId || !Number.isInteger(index) || index < 0) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_chunk_ref\" }\n }\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n if (!ability.can(\"update\", \"RBUploadSession\")) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const session = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] }).lean()\n if (!session) {\n ctx.res.status(404)\n return { ok: false, error: \"not_found\" }\n }\n\n if (session.status !== \"uploading\") {\n ctx.res.status(409)\n return { ok: false, error: \"not_uploading\" }\n }\n\n if (index >= session.chunksTotal) {\n ctx.res.status(400)\n return { ok: false, error: \"index_out_of_range\" }\n }\n\n const data = toBufferPayload(payload)\n if (!data) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_body\" }\n }\n\n const expectedSize = index === session.chunksTotal - 1\n ? session.totalSize - session.chunkSize * (session.chunksTotal - 1)\n : session.chunkSize\n\n if (data.length > expectedSize) {\n ctx.res.status(413)\n return { ok: false, error: \"chunk_too_large\" }\n }\n\n if (data.length !== expectedSize) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_chunk_size\" }\n }\n\n const checksumHeader = ctx.req.get(\"X-Chunk-SHA256\")\n const sha256 = checksumHeader ? computeSha256Hex(data) : undefined\n\n if (checksumHeader) {\n const expectedSha256 = normalizeSha256Hex(checksumHeader)\n if (sha256 !== expectedSha256) {\n ctx.res.status(400)\n return { ok: false, error: \"checksum_mismatch\" }\n }\n }\n\n await ensureUploadIndexes(UploadSession, UploadChunk)\n\n await UploadChunk.updateOne(\n { uploadId, index },\n {\n $set: {\n uploadId,\n index,\n data,\n size: data.length,\n sha256,\n expiresAt: session.expiresAt,\n },\n $setOnInsert: {\n createdAt: new Date(),\n },\n },\n { upsert: true },\n )\n\n ctx.res.status(204)\n return { ok: true }\n}\n","export const rawBodyParser = ({\n limitBytes,\n maxClientBytesPerSecond,\n}: {\n limitBytes: number\n maxClientBytesPerSecond?: number | null\n}) => {\n return (req: any, res: any, next: any) => {\n const contentType = typeof req?.headers?.[\"content-type\"] === \"string\"\n ? String(req.headers[\"content-type\"])\n : \"\"\n\n if (!contentType.includes(\"application/octet-stream\")) {\n next()\n return\n }\n\n let total = 0\n const chunks: Buffer[] = []\n let done = false\n let paused = false\n let throttleTimeout: ReturnType<typeof setTimeout> | null = null\n\n const rateBytesPerSecond = typeof maxClientBytesPerSecond === \"number\" && maxClientBytesPerSecond > 0\n ? maxClientBytesPerSecond\n : null\n\n const cleanup = () => {\n req.off(\"data\", onData)\n req.off(\"end\", onEnd)\n req.off(\"error\", onError)\n req.off(\"aborted\", onAborted)\n if (throttleTimeout) {\n clearTimeout(throttleTimeout)\n throttleTimeout = null\n }\n }\n\n const finish = (error?: unknown) => {\n if (done) return\n done = true\n\n cleanup()\n\n if (error) {\n next(error)\n return\n }\n\n req.body = Buffer.concat(chunks, total)\n next()\n }\n\n const onData = (chunk: any) => {\n if (done) return\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)\n total += buffer.length\n\n if (total > limitBytes) {\n done = true\n cleanup()\n req.destroy()\n res.status(413).json({ ok: false, error: \"chunk_too_large\" })\n return\n }\n\n chunks.push(buffer)\n\n if (!rateBytesPerSecond) return\n\n const now = Date.now()\n const clientKey = getClientKey(req)\n const state = getClientRateState(clientKey, rateBytesPerSecond, now)\n const waitMs = consumeRateBudget(state, buffer.length, rateBytesPerSecond, now)\n\n if (waitMs > 0 && !paused) {\n paused = true\n req.pause()\n throttleTimeout = setTimeout(() => {\n throttleTimeout = null\n paused = false\n if (done) return\n try {\n req.resume()\n } catch {\n //\n }\n }, waitMs)\n }\n }\n\n const onEnd = () => finish()\n const onError = (err: unknown) => finish(err)\n const onAborted = () => finish(new Error(\"request_aborted\"))\n\n req.on(\"data\", onData)\n req.on(\"end\", onEnd)\n req.on(\"error\", onError)\n req.on(\"aborted\", onAborted)\n }\n}\n\ntype ClientRateState = {\n tokens: number\n lastRefillMs: number\n lastSeenMs: number\n}\n\nconst MAX_BURST_SECONDS = 1\nconst STALE_CLIENT_MS = 15 * 60 * 1000\n\nconst clientRateStates = new Map<string, ClientRateState>()\nlet lastCleanupMs = 0\n\nconst getClientKey = (req: any): string => {\n const rawClientIp = typeof req?.clientIp === \"string\" ? req.clientIp : \"\"\n if (rawClientIp.trim()) return rawClientIp.trim()\n const rawIp = typeof req?.ip === \"string\" ? req.ip : \"\"\n return rawIp.trim() || \"unknown\"\n}\n\nconst maybeCleanupStates = (now: number) => {\n if (now - lastCleanupMs < 60_000) return\n lastCleanupMs = now\n\n if (clientRateStates.size < 2000) return\n\n for (const [key, state] of clientRateStates) {\n if (now - state.lastSeenMs > STALE_CLIENT_MS) {\n clientRateStates.delete(key)\n }\n }\n}\n\nconst getClientRateState = (key: string, rateBytesPerSecond: number, now: number): ClientRateState => {\n maybeCleanupStates(now)\n\n const capacity = rateBytesPerSecond * MAX_BURST_SECONDS\n const existing = clientRateStates.get(key)\n if (existing) {\n existing.lastSeenMs = now\n existing.tokens = Math.min(capacity, existing.tokens)\n return existing\n }\n\n const next: ClientRateState = {\n tokens: capacity,\n lastRefillMs: now,\n lastSeenMs: now,\n }\n clientRateStates.set(key, next)\n return next\n}\n\nconst consumeRateBudget = (\n state: ClientRateState,\n bytes: number,\n rateBytesPerSecond: number,\n now: number,\n): number => {\n const capacity = rateBytesPerSecond * MAX_BURST_SECONDS\n const elapsedMs = Math.max(0, now - state.lastRefillMs)\n\n if (elapsedMs > 0) {\n state.tokens = Math.min(capacity, state.tokens + (elapsedMs * rateBytesPerSecond) / 1000)\n state.lastRefillMs = now\n }\n\n state.tokens -= bytes\n\n if (state.tokens >= 0) return 0\n return Math.ceil((-state.tokens / rateBytesPerSecond) * 1000)\n}\n","import { Api } from \"@rpcbase/api\"\n\nimport { completeUpload } from \"./handlers/completeUpload\"\nimport { getStatus } from \"./handlers/getStatus\"\nimport { initUpload } from \"./handlers/initUpload\"\nimport { uploadChunk } from \"./handlers/uploadChunk\"\nimport { rawBodyParser } from \"./middleware/rawBodyParser\"\nimport { getChunkSizeBytes, getMaxClientUploadBytesPerSecond, getRawBodyLimitBytes, type SessionUser } from \"./shared\"\n\nimport * as Uploads from \"./index\"\n\n\nexport default (api: Api<SessionUser>) => {\n const chunkSizeBytes = getChunkSizeBytes()\n api.use(\n Uploads.InitRoute,\n rawBodyParser({\n limitBytes: getRawBodyLimitBytes(chunkSizeBytes),\n maxClientBytesPerSecond: getMaxClientUploadBytesPerSecond(),\n }),\n )\n\n api.post(Uploads.InitRoute, initUpload)\n api.put(Uploads.ChunkRoute, uploadChunk)\n api.get(Uploads.StatusRoute, getStatus)\n api.post(Uploads.CompleteRoute, completeUpload)\n}\n"],"names":["MAX_SVG_BYTES","window","JSDOM","DOMPurify","createDOMPurify","normalizeForSniff","raw","replace","trimStart","looksLikeSvgText","text","normalized","startsWith","test","looksLikeSvg","sniff","toString","sanitizeSvg","svg","sanitize","USE_PROFILES","svgFilters","sanitizeSvgProcessor","id","maxBytes","match","process","data","length","Error","svgText","sanitized","trim","sanitizedBuffer","Buffer","from","mimeType","uploadProcessors","Object","freeze","getMaxUploadProcessorBytes","reduce","max","processor","Math","selectUploadProcessors","ctx","filter","applyUploadProcessors","currentData","currentMimeType","clientMimeType","applied","processorCtx","filename","totalSize","result","push","waitForStreamFinished","stream","Promise","resolve","reject","once","writeToStream","chunk","ok","write","onDrain","cleanup","onError","error","off","on","abortUploadStream","abort","destroy","completeUpload","_payload","tenantId","getTenantId","res","status","uploadId","String","req","params","ability","buildUploadsAbility","modelCtx","getModelCtx","UploadSession","UploadChunk","all","models","get","can","existing","findOne","$and","_id","getUploadSessionAccessQuery","lean","fileId","locked","findOneAndUpdate","$set","$unset","returnDocument","ensureUploadIndexes","fsDb","getTenantFilesystemDb","nativeDb","db","updateOne","bucketName","getBucketName","bucket","GridFSBucket","lockedUserId","userId","undefined","maxProcessorBytes","shouldBufferForProcessing","declaredMimeType","toLowerCase","declaredSvg","endsWith","uploadStream","finalMimeType","inlineProcessors","finalMetadata","isPublic","ownerKeyHash","cursor","find","sort","index","expectedIndex","chunks","bufferedBytes","pendingChunks","sniffParts","sniffBytes","chunkDoc","slice","subarray","min","concat","processors","openUploadStream","metadata","pending","close","chunksTotal","assembled","processed","processedMimeType","processedSize","finished","end","runUploadPostProcessors","deleteMany","message","getStatus","session","receivedDocs","received","map","doc","n","Number","isInteger","chunkSize","InitRoute","ChunkRoute","StatusRoute","CompleteRoute","initRequestSchema","z","string","boolean","optional","number","int","uploadKey","initUpload","payload","getUserId","parsed","Uploads","safeParse","success","getChunkSizeBytes","ceil","ObjectId","now","Date","expiresAt","getSessionTtlMs","randomBytes","computeSha256Hex","create","createdAt","uploadChunk","indexRaw","toBufferPayload","expectedSize","checksumHeader","sha256","expectedSha256","normalizeSha256Hex","size","$setOnInsert","upsert","rawBodyParser","limitBytes","maxClientBytesPerSecond","next","contentType","headers","includes","total","done","paused","throttleTimeout","rateBytesPerSecond","onData","onEnd","onAborted","clearTimeout","finish","body","buffer","isBuffer","json","clientKey","getClientKey","state","getClientRateState","waitMs","consumeRateBudget","pause","setTimeout","resume","err","MAX_BURST_SECONDS","STALE_CLIENT_MS","clientRateStates","Map","lastCleanupMs","rawClientIp","clientIp","rawIp","ip","maybeCleanupStates","key","lastSeenMs","delete","capacity","tokens","lastRefillMs","set","bytes","elapsedMs","api","chunkSizeBytes","use","getRawBodyLimitBytes","getMaxClientUploadBytesPerSecond","post","put"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { getTenantFilesystemDb } from "@rpcbase/db";
2
2
  import { ObjectId, GridFSBucket } from "mongodb";
3
- import { g as getTenantId, d as getBucketName, f as getUserId, m as getUploadKeyHash } from "./shared-Dy9x-P7F.js";
3
+ import { g as getTenantId, d as getBucketName, f as getUserId, m as getUploadKeyHash } from "./shared-BqZiSOmf.js";
4
4
  const resolveHeaderString$1 = (value) => {
5
5
  if (typeof value !== "string") return null;
6
6
  const normalized = value.trim();
@@ -200,4 +200,4 @@ const handler = (api) => {
200
200
  export {
201
201
  handler as default
202
202
  };
203
- //# sourceMappingURL=handler-DY5UEwlw.js.map
203
+ //# sourceMappingURL=handler-DnSJAQ_B.js.map