@rpcbase/server 0.488.0 → 0.490.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 (107) hide show
  1. package/package.json +1 -1
  2. package/dist/applyRouteLoaders.d.ts +0 -9
  3. package/dist/applyRouteLoaders.d.ts.map +0 -1
  4. package/dist/checkInitReplicaSet.d.ts +0 -6
  5. package/dist/checkInitReplicaSet.d.ts.map +0 -1
  6. package/dist/dev/coverage.d.ts +0 -3
  7. package/dist/dev/coverage.d.ts.map +0 -1
  8. package/dist/email-DEw8keax.js +0 -8042
  9. package/dist/email-DEw8keax.js.map +0 -1
  10. package/dist/email.d.ts +0 -19
  11. package/dist/email.d.ts.map +0 -1
  12. package/dist/getDerivedKey.d.ts +0 -3
  13. package/dist/getDerivedKey.d.ts.map +0 -1
  14. package/dist/handler-BwK8qxLn.js +0 -438
  15. package/dist/handler-BwK8qxLn.js.map +0 -1
  16. package/dist/handler-CedzJJg0.js +0 -114
  17. package/dist/handler-CedzJJg0.js.map +0 -1
  18. package/dist/handler-Cohj3cz3.js +0 -176
  19. package/dist/handler-Cohj3cz3.js.map +0 -1
  20. package/dist/handler-qCAUmVgd.js +0 -684
  21. package/dist/handler-qCAUmVgd.js.map +0 -1
  22. package/dist/hashPassword.d.ts +0 -2
  23. package/dist/hashPassword.d.ts.map +0 -1
  24. package/dist/index.d.ts +0 -7
  25. package/dist/index.d.ts.map +0 -1
  26. package/dist/index.js +0 -4628
  27. package/dist/index.js.map +0 -1
  28. package/dist/initServer.d.ts +0 -9
  29. package/dist/initServer.d.ts.map +0 -1
  30. package/dist/metricsIngestProxyMiddleware.d.ts +0 -3
  31. package/dist/metricsIngestProxyMiddleware.d.ts.map +0 -1
  32. package/dist/notifications/api/notifications/handler.d.ts +0 -4
  33. package/dist/notifications/api/notifications/handler.d.ts.map +0 -1
  34. package/dist/notifications/api/notifications/index.d.ts +0 -168
  35. package/dist/notifications/api/notifications/index.d.ts.map +0 -1
  36. package/dist/notifications/api/notifications/shared.d.ts +0 -6
  37. package/dist/notifications/api/notifications/shared.d.ts.map +0 -1
  38. package/dist/notifications/createNotification.d.ts +0 -13
  39. package/dist/notifications/createNotification.d.ts.map +0 -1
  40. package/dist/notifications/digest.d.ts +0 -13
  41. package/dist/notifications/digest.d.ts.map +0 -1
  42. package/dist/notifications/routes.d.ts +0 -2
  43. package/dist/notifications/routes.d.ts.map +0 -1
  44. package/dist/notifications.d.ts +0 -4
  45. package/dist/notifications.d.ts.map +0 -1
  46. package/dist/notifications.js +0 -127
  47. package/dist/notifications.js.map +0 -1
  48. package/dist/passwordHashStorage.d.ts +0 -11
  49. package/dist/passwordHashStorage.d.ts.map +0 -1
  50. package/dist/posthog.d.ts +0 -9
  51. package/dist/posthog.d.ts.map +0 -1
  52. package/dist/renderSSR.d.ts +0 -12
  53. package/dist/renderSSR.d.ts.map +0 -1
  54. package/dist/render_resend_false-MiC__Smr.js +0 -6
  55. package/dist/render_resend_false-MiC__Smr.js.map +0 -1
  56. package/dist/rts/api/changes/handler.d.ts +0 -9
  57. package/dist/rts/api/changes/handler.d.ts.map +0 -1
  58. package/dist/rts/api/changes/index.d.ts +0 -25
  59. package/dist/rts/api/changes/index.d.ts.map +0 -1
  60. package/dist/rts/index.d.ts +0 -40
  61. package/dist/rts/index.d.ts.map +0 -1
  62. package/dist/rts/index.js +0 -631
  63. package/dist/rts/index.js.map +0 -1
  64. package/dist/rts/routes.d.ts +0 -2
  65. package/dist/rts/routes.d.ts.map +0 -1
  66. package/dist/schemas-7qqi9OQy.js +0 -4225
  67. package/dist/schemas-7qqi9OQy.js.map +0 -1
  68. package/dist/shared-BJomDDWK.js +0 -107
  69. package/dist/shared-BJomDDWK.js.map +0 -1
  70. package/dist/ssrMiddleware.d.ts +0 -18
  71. package/dist/ssrMiddleware.d.ts.map +0 -1
  72. package/dist/types/index.d.ts +0 -6
  73. package/dist/types/index.d.ts.map +0 -1
  74. package/dist/uploads/api/file-uploads/handler.d.ts +0 -5
  75. package/dist/uploads/api/file-uploads/handler.d.ts.map +0 -1
  76. package/dist/uploads/api/file-uploads/handlers/completeUpload.d.ts +0 -5
  77. package/dist/uploads/api/file-uploads/handlers/completeUpload.d.ts.map +0 -1
  78. package/dist/uploads/api/file-uploads/handlers/getStatus.d.ts +0 -5
  79. package/dist/uploads/api/file-uploads/handlers/getStatus.d.ts.map +0 -1
  80. package/dist/uploads/api/file-uploads/handlers/initUpload.d.ts +0 -5
  81. package/dist/uploads/api/file-uploads/handlers/initUpload.d.ts.map +0 -1
  82. package/dist/uploads/api/file-uploads/handlers/uploadChunk.d.ts +0 -9
  83. package/dist/uploads/api/file-uploads/handlers/uploadChunk.d.ts.map +0 -1
  84. package/dist/uploads/api/file-uploads/index.d.ts +0 -43
  85. package/dist/uploads/api/file-uploads/index.d.ts.map +0 -1
  86. package/dist/uploads/api/file-uploads/middleware/rawBodyParser.d.ts +0 -5
  87. package/dist/uploads/api/file-uploads/middleware/rawBodyParser.d.ts.map +0 -1
  88. package/dist/uploads/api/file-uploads/processors/index.d.ts +0 -25
  89. package/dist/uploads/api/file-uploads/processors/index.d.ts.map +0 -1
  90. package/dist/uploads/api/file-uploads/processors/sanitizeSvg.d.ts +0 -5
  91. package/dist/uploads/api/file-uploads/processors/sanitizeSvg.d.ts.map +0 -1
  92. package/dist/uploads/api/file-uploads/shared.d.ts +0 -32
  93. package/dist/uploads/api/file-uploads/shared.d.ts.map +0 -1
  94. package/dist/uploads/api/files/handler.d.ts +0 -4
  95. package/dist/uploads/api/files/handler.d.ts.map +0 -1
  96. package/dist/uploads/api/files/handlers/deleteFile.d.ts +0 -9
  97. package/dist/uploads/api/files/handlers/deleteFile.d.ts.map +0 -1
  98. package/dist/uploads/api/files/handlers/getFile.d.ts +0 -4
  99. package/dist/uploads/api/files/handlers/getFile.d.ts.map +0 -1
  100. package/dist/uploads/api/files/index.d.ts +0 -4
  101. package/dist/uploads/api/files/index.d.ts.map +0 -1
  102. package/dist/uploads/routes.d.ts +0 -2
  103. package/dist/uploads/routes.d.ts.map +0 -1
  104. package/dist/uploads.d.ts +0 -2
  105. package/dist/uploads.d.ts.map +0 -1
  106. package/dist/uploads.js +0 -10
  107. package/dist/uploads.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-CedzJJg0.js","sources":["../src/rts/api/changes/index.ts","../src/rts/api/changes/handler.ts"],"sourcesContent":["import { z } from \"zod\"\n\n\nexport const Route = \"/api/rb/rts/changes\"\n\nexport const requestSchema = z.object({\n sinceSeq: z.number().int().min(0).default(0),\n limit: z.number().int().min(1).max(5000).default(2000),\n modelNames: z.array(z.string().min(1)).optional(),\n})\n\nexport type RequestPayload = z.infer<typeof requestSchema>\n\nexport const responseSchema = z.object({\n ok: z.boolean(),\n needsFullResync: z.boolean().optional(),\n earliestSeq: z.number().int().min(0).optional(),\n latestSeq: z.number().int().min(0),\n changes: z.array(z.object({\n seq: z.number().int().min(1),\n modelName: z.string().min(1),\n op: z.enum([\"delete\", \"reset_model\"]),\n docId: z.string().optional(),\n })),\n})\n\nexport type ResponsePayload = z.infer<typeof responseSchema>\n\n","import { Api, ApiHandler, Ctx } from \"@rpcbase/api\"\nimport { models, ZRBRtsChangeOp, type IRBRtsChange, type IRBRtsCounter, type LoadModelCtx } from \"@rpcbase/db\"\nimport { buildAbilityFromSession, type AclSubjectType } from \"@rpcbase/db/acl\"\nimport type { Model } from \"mongoose\"\n\nimport * as Changes from \"./index\"\n\n\ntype SessionUser = {\n id?: string\n currentTenantId?: string\n signedInTenants?: string[]\n}\n\ntype RtsCounterDoc = IRBRtsCounter\ntype RtsChangeDoc = IRBRtsChange\n\nconst getTenantId = (ctx: Ctx<SessionUser>): string | null => {\n const raw = ctx.req.query?.[\"rb-tenant-id\"]\n const queryTenantId = Array.isArray(raw) ? raw[0] : raw\n if (typeof queryTenantId === \"string\" && queryTenantId.trim()) return queryTenantId.trim()\n\n const sessionTenantId = ctx.req.session?.user?.currentTenantId\n if (typeof sessionTenantId === \"string\" && sessionTenantId.trim()) return sessionTenantId.trim()\n\n return null\n}\n\nconst ensureAuthorized = (ctx: Ctx<SessionUser>, tenantId: string): string | null => {\n const userId = ctx.req.session?.user?.id\n if (!userId) return null\n\n const signedInTenants = ctx.req.session?.user?.signedInTenants\n const currentTenantId = ctx.req.session?.user?.currentTenantId\n\n const hasTenantAccessFromList = Array.isArray(signedInTenants) && signedInTenants.includes(tenantId)\n\n const normalizedCurrentTenantId = typeof currentTenantId === \"string\" ? currentTenantId.trim() : \"\"\n const hasTenantAccessFromCurrent = Boolean(normalizedCurrentTenantId) && normalizedCurrentTenantId === tenantId\n\n if (!hasTenantAccessFromList && !hasTenantAccessFromCurrent) return null\n return userId\n}\n\nconst getModelCtx = (_ctx: Ctx<SessionUser>, tenantId: string): LoadModelCtx => ({\n req: {\n session: {\n user: {\n currentTenantId: tenantId,\n },\n },\n },\n})\n\nconst isRtsChangeRecord = (value: unknown): value is RtsChangeDoc => {\n if (!value || typeof value !== \"object\") return false\n const obj = value as Partial<RtsChangeDoc>\n const isOp = ZRBRtsChangeOp.safeParse(obj.op).success\n return typeof obj.seq === \"number\" && typeof obj.modelName === \"string\" && isOp\n}\n\nconst changesHandler: ApiHandler<Changes.RequestPayload, Changes.ResponsePayload, SessionUser> = async(\n payload,\n ctx,\n): Promise<Changes.ResponsePayload> => {\n const parsed = Changes.requestSchema.safeParse(payload ?? {})\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, latestSeq: 0, changes: [] }\n }\n\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, latestSeq: 0, changes: [] }\n }\n\n const userId = ensureAuthorized(ctx, tenantId)\n if (!userId) {\n ctx.res.status(401)\n return { ok: false, latestSeq: 0, changes: [] }\n }\n\n const ability = buildAbilityFromSession({ tenantId, session: ctx.req.session })\n\n const modelCtx = getModelCtx(ctx, tenantId)\n\n const [RtsChange, RtsCounter] = await Promise.all([\n models.get(\"RBRtsChange\", modelCtx) as Promise<Model<RtsChangeDoc>>,\n models.get(\"RBRtsCounter\", modelCtx) as Promise<Model<RtsCounterDoc>>,\n ])\n\n const counter = await RtsCounter.findOne({ _id: \"rts\" }, { seq: 1 }).lean()\n const latestSeq = Number(counter?.seq ?? 0) || 0\n\n const { sinceSeq, limit, modelNames } = parsed.data\n\n const requestedModelNames = Array.isArray(modelNames) && modelNames.length\n ? modelNames.map((m) => String(m)).filter(Boolean)\n : null\n\n const allowedModelNames = requestedModelNames\n ? requestedModelNames.filter((m) => ability.can(\"read\", m as AclSubjectType))\n : Array.from(\n new Set(\n (await RtsChange.distinct(\"modelName\"))\n .map((m) => String(m))\n .filter(Boolean)\n .filter((m) => ability.can(\"read\", m as AclSubjectType)),\n ),\n )\n\n let earliestSeq: number | undefined\n if (allowedModelNames.length) {\n const earliest = await RtsChange.findOne({ modelName: { $in: allowedModelNames } }, { seq: 1 }).sort({ seq: 1 }).lean()\n earliestSeq = earliest?.seq ? Number(earliest.seq) : undefined\n }\n\n const needsFullResync = typeof earliestSeq === \"number\" && sinceSeq < earliestSeq - 1\n\n const selector: Record<string, unknown> = { seq: { $gt: sinceSeq }, modelName: { $in: allowedModelNames } }\n\n const changes = await RtsChange\n .find(selector, { _id: 0, seq: 1, modelName: 1, op: 1, docId: 1 })\n .sort({ seq: 1 })\n .limit(limit)\n .lean()\n\n return {\n ok: true,\n needsFullResync: needsFullResync || undefined,\n earliestSeq,\n latestSeq,\n changes: Array.isArray(changes)\n ? changes\n .filter(isRtsChangeRecord)\n .filter((c) => ability.can(\"read\", c.modelName as AclSubjectType))\n .map((c) => ({\n seq: Number(c.seq),\n modelName: String(c.modelName),\n op: c.op,\n docId: c.docId ? String(c.docId) : undefined,\n }))\n : [],\n }\n}\n\nexport default (api: Api<SessionUser>) => {\n api.post(Changes.Route, changesHandler)\n}\n"],"names":["z.object","z.number","z.array","z.string","z.boolean","z.enum","Changes.requestSchema","Changes.Route"],"mappings":"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"}
@@ -1,176 +0,0 @@
1
- import { getTenantFilesystemDb } from "@rpcbase/db";
2
- import { ObjectId, GridFSBucket } from "mongodb";
3
- import { g as getTenantId, d as getBucketName, f as getUserId, m as getUploadKeyHash } from "./shared-BJomDDWK.js";
4
- const resolveHeaderString$1 = (value) => {
5
- if (typeof value !== "string") return null;
6
- const normalized = value.trim();
7
- return normalized ? normalized : null;
8
- };
9
- const deleteFile = async (_payload, ctx) => {
10
- const tenantId = getTenantId(ctx);
11
- if (!tenantId) {
12
- ctx.res.status(400);
13
- return { ok: false, error: "tenant_missing" };
14
- }
15
- const fileIdRaw = String(ctx.req.params?.fileId ?? "").trim();
16
- let fileObjectId;
17
- try {
18
- fileObjectId = new ObjectId(fileIdRaw);
19
- } catch {
20
- ctx.res.status(400);
21
- return { ok: false, error: "invalid_file_id" };
22
- }
23
- const fsDb = await getTenantFilesystemDb(tenantId);
24
- const nativeDb = fsDb.db;
25
- if (!nativeDb) {
26
- ctx.res.status(500);
27
- return { ok: false, error: "filesystem_db_unavailable" };
28
- }
29
- const bucketName = getBucketName();
30
- const bucket = new GridFSBucket(nativeDb, { bucketName });
31
- const userId = getUserId(ctx);
32
- const uploadKeyHash = getUploadKeyHash(ctx);
33
- if (!userId && !uploadKeyHash) {
34
- ctx.res.status(401);
35
- return { ok: false, error: "unauthorized" };
36
- }
37
- const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray();
38
- if (!file) {
39
- ctx.res.status(204);
40
- return { ok: true };
41
- }
42
- const metadataUserId = resolveHeaderString$1(file?.metadata?.userId);
43
- const ownerKeyHash = resolveHeaderString$1(file?.metadata?.ownerKeyHash);
44
- const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId);
45
- const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash);
46
- if (!authorizedByUser && !authorizedByKey) {
47
- ctx.res.status(401);
48
- return { ok: false, error: "unauthorized" };
49
- }
50
- try {
51
- await bucket.delete(fileObjectId);
52
- } catch (error) {
53
- const message = error instanceof Error ? error.message : String(error);
54
- if (!message.includes("FileNotFound")) {
55
- ctx.res.status(500);
56
- return { ok: false, error: "delete_failed" };
57
- }
58
- }
59
- ctx.res.status(204);
60
- return { ok: true };
61
- };
62
- const resolveHeaderString = (value) => {
63
- if (typeof value !== "string") return null;
64
- const normalized = value.trim();
65
- return normalized ? normalized : null;
66
- };
67
- const resolveHeaderBoolean = (value) => {
68
- if (typeof value === "boolean") return value;
69
- if (typeof value !== "string") return null;
70
- const normalized = value.trim().toLowerCase();
71
- if (!normalized) return null;
72
- if (normalized === "true") return true;
73
- if (normalized === "false") return false;
74
- return null;
75
- };
76
- const escapeHeaderFilename = (filename) => filename.replace(/[\\"]/g, "_");
77
- const getFile = async (_payload, ctx) => {
78
- const tenantId = getTenantId(ctx);
79
- if (!tenantId) {
80
- ctx.res.status(400).end();
81
- return {};
82
- }
83
- const fileIdRaw = String(ctx.req.params?.fileId ?? "").trim();
84
- let fileObjectId;
85
- try {
86
- fileObjectId = new ObjectId(fileIdRaw);
87
- } catch {
88
- ctx.res.status(400).end();
89
- return {};
90
- }
91
- const fsDb = await getTenantFilesystemDb(tenantId);
92
- const nativeDb = fsDb.db;
93
- if (!nativeDb) {
94
- ctx.res.status(500).end();
95
- return {};
96
- }
97
- const bucketName = getBucketName();
98
- const bucket = new GridFSBucket(nativeDb, { bucketName });
99
- const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray();
100
- if (!file) {
101
- ctx.res.status(404).end();
102
- return {};
103
- }
104
- const isPublic = resolveHeaderBoolean(file?.metadata?.isPublic) ?? false;
105
- if (!isPublic) {
106
- const userId = getUserId(ctx);
107
- const uploadKeyHash = getUploadKeyHash(ctx);
108
- const metadataUserId = resolveHeaderString(file?.metadata?.userId);
109
- const ownerKeyHash = resolveHeaderString(file?.metadata?.ownerKeyHash);
110
- const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId);
111
- const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash);
112
- if (!authorizedByUser && !authorizedByKey) {
113
- ctx.res.status(401).end();
114
- return {};
115
- }
116
- }
117
- const mimeTypeFromMetadata = resolveHeaderString(file?.metadata?.mimeType);
118
- const mimeType = mimeTypeFromMetadata ?? "application/octet-stream";
119
- const filenameFromDb = resolveHeaderString(file?.filename);
120
- const filename = filenameFromDb ?? fileIdRaw;
121
- const filenameSafe = escapeHeaderFilename(filename);
122
- const cacheControl = "private, max-age=0, must-revalidate";
123
- const md5 = resolveHeaderString(file?.md5);
124
- const uploadDate = file?.uploadDate instanceof Date ? file.uploadDate : null;
125
- const etagValue = md5 ?? `${fileObjectId.toHexString()}-${String(file?.length ?? 0)}-${String(uploadDate?.getTime() ?? 0)}`;
126
- const etag = md5 ? `"${etagValue}"` : `W/"${etagValue}"`;
127
- const ifNoneMatch = resolveHeaderString(ctx.req.headers?.["if-none-match"]);
128
- if (ifNoneMatch) {
129
- const candidates = ifNoneMatch.split(",").map((value) => value.trim()).filter(Boolean);
130
- if (candidates.includes("*") || candidates.includes(etag)) {
131
- ctx.res.status(304);
132
- ctx.res.setHeader("Cache-Control", cacheControl);
133
- ctx.res.setHeader("ETag", etag);
134
- ctx.res.end();
135
- return {};
136
- }
137
- }
138
- ctx.res.status(200);
139
- ctx.res.setHeader("Content-Type", mimeType);
140
- ctx.res.setHeader("Content-Length", String(file?.length ?? 0));
141
- ctx.res.setHeader("Content-Disposition", `inline; filename="${filenameSafe}"`);
142
- ctx.res.setHeader("Cache-Control", cacheControl);
143
- ctx.res.setHeader("ETag", etag);
144
- ctx.res.setHeader("X-Content-Type-Options", "nosniff");
145
- if (mimeType === "image/svg+xml") {
146
- ctx.res.setHeader(
147
- "Content-Security-Policy",
148
- "default-src 'none'; style-src 'unsafe-inline'; sandbox; base-uri 'none'; form-action 'none'"
149
- );
150
- }
151
- ctx.res.flushHeaders();
152
- if (ctx.req.method === "HEAD") {
153
- ctx.res.end();
154
- return {};
155
- }
156
- const stream = bucket.openDownloadStream(fileObjectId);
157
- stream.on("error", () => {
158
- try {
159
- ctx.res.destroy();
160
- } catch {
161
- }
162
- });
163
- stream.pipe(ctx.res);
164
- return {};
165
- };
166
- const Route = "/api/rb/files/:fileId";
167
- const GetRoute = Route;
168
- const DeleteRoute = Route;
169
- const handler = (api) => {
170
- api.get(GetRoute, getFile);
171
- api.delete(DeleteRoute, deleteFile);
172
- };
173
- export {
174
- handler as default
175
- };
176
- //# sourceMappingURL=handler-Cohj3cz3.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-Cohj3cz3.js","sources":["../src/uploads/api/files/handlers/deleteFile.ts","../src/uploads/api/files/handlers/getFile.ts","../src/uploads/api/files/index.ts","../src/uploads/api/files/handler.ts"],"sourcesContent":["import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb } from \"@rpcbase/db\"\nimport { GridFSBucket, ObjectId } from \"mongodb\"\n\nimport { getBucketName, getTenantId, getUploadKeyHash, getUserId, type SessionUser } from \"../../file-uploads/shared\"\n\n\ntype DeleteResponsePayload = {\n ok: boolean\n error?: string\n}\n\nconst resolveHeaderString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nexport const deleteFile: ApiHandler<Record<string, never>, DeleteResponsePayload, SessionUser> = async (\n _payload,\n ctx,\n): Promise<DeleteResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const fileIdRaw = String(ctx.req.params?.fileId ?? \"\").trim()\n let fileObjectId: ObjectId\n try {\n fileObjectId = new ObjectId(fileIdRaw)\n } catch {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_file_id\" }\n }\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n ctx.res.status(500)\n return { ok: false, error: \"filesystem_db_unavailable\" }\n }\n\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const userId = getUserId(ctx)\n const uploadKeyHash = getUploadKeyHash(ctx)\n if (!userId && !uploadKeyHash) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray()\n if (!file) {\n ctx.res.status(204)\n return { ok: true }\n }\n\n const metadataUserId = resolveHeaderString((file as any)?.metadata?.userId)\n const ownerKeyHash = resolveHeaderString((file as any)?.metadata?.ownerKeyHash)\n\n const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId)\n const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)\n\n if (!authorizedByUser && !authorizedByKey) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n try {\n await bucket.delete(fileObjectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (!message.includes(\"FileNotFound\")) {\n ctx.res.status(500)\n return { ok: false, error: \"delete_failed\" }\n }\n }\n\n ctx.res.status(204)\n return { ok: true }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb } from \"@rpcbase/db\"\nimport { GridFSBucket, ObjectId } from \"mongodb\"\n\nimport { getBucketName, getTenantId, getUploadKeyHash, getUserId, type SessionUser } from \"../../file-uploads/shared\"\n\n\nconst resolveHeaderString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nconst resolveHeaderBoolean = (value: unknown): boolean | null => {\n if (typeof value === \"boolean\") return value\n if (typeof value !== \"string\") return null\n const normalized = value.trim().toLowerCase()\n if (!normalized) return null\n if (normalized === \"true\") return true\n if (normalized === \"false\") return false\n return null\n}\n\nconst escapeHeaderFilename = (filename: string): string => filename.replace(/[\\\\\"]/g, \"_\")\n\nexport const getFile: ApiHandler<Record<string, never>, Record<string, never>, SessionUser> = async (\n _payload,\n ctx,\n): Promise<Record<string, never>> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400).end()\n return {}\n }\n\n const fileIdRaw = String(ctx.req.params?.fileId ?? \"\").trim()\n let fileObjectId: ObjectId\n try {\n fileObjectId = new ObjectId(fileIdRaw)\n } catch {\n ctx.res.status(400).end()\n return {}\n }\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n ctx.res.status(500).end()\n return {}\n }\n\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray()\n if (!file) {\n ctx.res.status(404).end()\n return {}\n }\n\n const isPublic = resolveHeaderBoolean((file as any)?.metadata?.isPublic) ?? false\n if (!isPublic) {\n const userId = getUserId(ctx)\n const uploadKeyHash = getUploadKeyHash(ctx)\n const metadataUserId = resolveHeaderString((file as any)?.metadata?.userId)\n const ownerKeyHash = resolveHeaderString((file as any)?.metadata?.ownerKeyHash)\n\n const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId)\n const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)\n\n if (!authorizedByUser && !authorizedByKey) {\n ctx.res.status(401).end()\n return {}\n }\n }\n\n const mimeTypeFromMetadata = resolveHeaderString((file as any)?.metadata?.mimeType)\n const mimeType = mimeTypeFromMetadata ?? \"application/octet-stream\"\n const filenameFromDb = resolveHeaderString((file as any)?.filename)\n const filename = filenameFromDb ?? fileIdRaw\n const filenameSafe = escapeHeaderFilename(filename)\n\n const cacheControl = \"private, max-age=0, must-revalidate\"\n const md5 = resolveHeaderString((file as any)?.md5)\n const uploadDate = (file as any)?.uploadDate instanceof Date ? (file as any).uploadDate : null\n const etagValue = md5 ?? `${fileObjectId.toHexString()}-${String((file as any)?.length ?? 0)}-${String(uploadDate?.getTime() ?? 0)}`\n const etag = md5 ? `\"${etagValue}\"` : `W/\"${etagValue}\"`\n const ifNoneMatch = resolveHeaderString((ctx.req.headers as any)?.[\"if-none-match\"])\n if (ifNoneMatch) {\n const candidates = ifNoneMatch.split(\",\").map((value) => value.trim()).filter(Boolean)\n if (candidates.includes(\"*\") || candidates.includes(etag)) {\n ctx.res.status(304)\n ctx.res.setHeader(\"Cache-Control\", cacheControl)\n ctx.res.setHeader(\"ETag\", etag)\n ctx.res.end()\n return {}\n }\n }\n\n ctx.res.status(200)\n ctx.res.setHeader(\"Content-Type\", mimeType)\n ctx.res.setHeader(\"Content-Length\", String((file as any)?.length ?? 0))\n ctx.res.setHeader(\"Content-Disposition\", `inline; filename=\"${filenameSafe}\"`)\n ctx.res.setHeader(\"Cache-Control\", cacheControl)\n ctx.res.setHeader(\"ETag\", etag)\n ctx.res.setHeader(\"X-Content-Type-Options\", \"nosniff\")\n if (mimeType === \"image/svg+xml\") {\n ctx.res.setHeader(\n \"Content-Security-Policy\",\n \"default-src 'none'; style-src 'unsafe-inline'; sandbox; base-uri 'none'; form-action 'none'\",\n )\n }\n ctx.res.flushHeaders()\n\n if (ctx.req.method === \"HEAD\") {\n ctx.res.end()\n return {}\n }\n\n const stream = bucket.openDownloadStream(fileObjectId)\n\n stream.on(\"error\", () => {\n try {\n ctx.res.destroy()\n } catch {\n //\n }\n })\n\n stream.pipe(ctx.res)\n return {}\n}\n","export const Route = \"/api/rb/files/:fileId\"\n\nexport const GetRoute = Route\nexport const DeleteRoute = Route\n","import { Api } from \"@rpcbase/api\"\n\nimport { deleteFile } from \"./handlers/deleteFile\"\nimport { getFile } from \"./handlers/getFile\"\n\nimport * as Files from \"./index\"\n\n\nexport default (api: Api) => {\n api.get(Files.GetRoute, getFile)\n api.delete(Files.DeleteRoute, deleteFile)\n}\n"],"names":["resolveHeaderString","Files.GetRoute","Files.DeleteRoute"],"mappings":"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"}