@rpcbase/server 0.594.0 → 0.596.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/convertHeifToWebp-BT1n72DQ.js +148 -0
- package/dist/convertHeifToWebp-BT1n72DQ.js.map +1 -0
- package/dist/email-Co1GNjlT.js +7204 -0
- package/dist/email-Co1GNjlT.js.map +1 -0
- package/dist/handler-BqlcQ9sE.js +245 -0
- package/dist/handler-BqlcQ9sE.js.map +1 -0
- package/dist/handler-C92_gJkX.js +743 -0
- package/dist/handler-C92_gJkX.js.map +1 -0
- package/dist/handler-CapJTGzc.js +795 -0
- package/dist/handler-CapJTGzc.js.map +1 -0
- package/dist/handler-Fk-5fOSm.js +119 -0
- package/dist/handler-Fk-5fOSm.js.map +1 -0
- package/dist/htmlTemplate.d.ts +16 -0
- package/dist/htmlTemplate.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5140 -5286
- package/dist/index.js.map +1 -1
- package/dist/notifications.js +273 -382
- package/dist/notifications.js.map +1 -1
- package/dist/postProcessors-iffHvje4.js +89 -0
- package/dist/postProcessors-iffHvje4.js.map +1 -0
- package/dist/queryWindow-DP8zD0lb.js +555 -0
- package/dist/queryWindow-DP8zD0lb.js.map +1 -0
- package/dist/render_resend-B1SSQ4f7.js +7 -0
- package/dist/render_resend-B1SSQ4f7.js.map +1 -0
- package/dist/rts/index.js +990 -1207
- package/dist/rts/index.js.map +1 -1
- package/dist/schemas-B2fUvgYo.js +4151 -0
- package/dist/schemas-B2fUvgYo.js.map +1 -0
- package/dist/shared-DfrVDSp0.js +79 -0
- package/dist/shared-DfrVDSp0.js.map +1 -0
- package/dist/ssrMiddleware.d.ts +7 -3
- package/dist/ssrMiddleware.d.ts.map +1 -1
- package/dist/uploads/worker.js +139 -168
- package/dist/uploads/worker.js.map +1 -1
- package/dist/uploads.js +12 -22
- package/dist/uploads.js.map +1 -1
- package/package.json +1 -1
- package/dist/convertHeifToWebp-C-DGXZ2k.js +0 -169
- package/dist/convertHeifToWebp-C-DGXZ2k.js.map +0 -1
- package/dist/email-BCf24GmK.js +0 -8071
- package/dist/email-BCf24GmK.js.map +0 -1
- package/dist/handler-BqoKvylN.js +0 -147
- package/dist/handler-BqoKvylN.js.map +0 -1
- package/dist/handler-CUOJ51-w.js +0 -903
- package/dist/handler-CUOJ51-w.js.map +0 -1
- package/dist/handler-D-XdgeG_.js +0 -986
- package/dist/handler-D-XdgeG_.js.map +0 -1
- package/dist/handler-F0gFTzvh.js +0 -275
- package/dist/handler-F0gFTzvh.js.map +0 -1
- package/dist/postProcessors-D27fGZP0.js +0 -107
- package/dist/postProcessors-D27fGZP0.js.map +0 -1
- package/dist/queryWindow-Cdr7K-S1.js +0 -714
- package/dist/queryWindow-Cdr7K-S1.js.map +0 -1
- package/dist/render_resend_false-MiC__Smr.js +0 -6
- package/dist/render_resend_false-MiC__Smr.js.map +0 -1
- package/dist/schemas-Drf83ni9.js +0 -4517
- package/dist/schemas-Drf83ni9.js.map +0 -1
- package/dist/shared-CJrm9Wjp.js +0 -104
- package/dist/shared-CJrm9Wjp.js.map +0 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { d as getUploadKeyHash, i as getBucketName, p as getUserId, u as getTenantId } from "./shared-DfrVDSp0.js";
|
|
2
|
+
import { GridFSBucket, ObjectId } from "mongodb";
|
|
3
|
+
import { getTenantFilesystemDb } from "@rpcbase/db";
|
|
4
|
+
//#region src/uploads/api/files/handlers/deleteFile.ts
|
|
5
|
+
var resolveHeaderString$1 = (value) => {
|
|
6
|
+
if (typeof value !== "string") return null;
|
|
7
|
+
const normalized = value.trim();
|
|
8
|
+
return normalized ? normalized : null;
|
|
9
|
+
};
|
|
10
|
+
var isFileNotFoundError = (error) => {
|
|
11
|
+
return (error instanceof Error ? error.message : String(error)).includes("FileNotFound");
|
|
12
|
+
};
|
|
13
|
+
var deleteGridFsFile = async (bucket, fileId) => {
|
|
14
|
+
try {
|
|
15
|
+
await bucket.delete(fileId);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (!isFileNotFoundError(error)) throw error;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var deleteFile = async (_payload, ctx) => {
|
|
21
|
+
const tenantId = getTenantId(ctx);
|
|
22
|
+
if (!tenantId) {
|
|
23
|
+
ctx.res.status(400);
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
error: "tenant_missing"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const fileIdRaw = String(ctx.req.params?.fileId ?? "").trim();
|
|
30
|
+
let fileObjectId;
|
|
31
|
+
try {
|
|
32
|
+
fileObjectId = new ObjectId(fileIdRaw);
|
|
33
|
+
} catch {
|
|
34
|
+
ctx.res.status(400);
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: "invalid_file_id"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const nativeDb = (await getTenantFilesystemDb(tenantId)).db;
|
|
41
|
+
if (!nativeDb) {
|
|
42
|
+
ctx.res.status(500);
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
error: "filesystem_db_unavailable"
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const bucket = new GridFSBucket(nativeDb, { bucketName: getBucketName() });
|
|
49
|
+
const userId = getUserId(ctx);
|
|
50
|
+
const uploadKeyHash = getUploadKeyHash(ctx);
|
|
51
|
+
if (!userId && !uploadKeyHash) {
|
|
52
|
+
ctx.res.status(401);
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
error: "unauthorized"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray();
|
|
59
|
+
if (!file) {
|
|
60
|
+
ctx.res.status(204);
|
|
61
|
+
return { ok: true };
|
|
62
|
+
}
|
|
63
|
+
const metadataUserId = resolveHeaderString$1(file?.metadata?.userId);
|
|
64
|
+
const ownerKeyHash = resolveHeaderString$1(file?.metadata?.ownerKeyHash);
|
|
65
|
+
if (!Boolean(userId && metadataUserId && userId === metadataUserId) && !Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)) {
|
|
66
|
+
ctx.res.status(401);
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: "unauthorized"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const variantIds = (await bucket.find({ "metadata.variantOf": fileObjectId.toHexString() }).toArray()).map((variant) => variant?._id).filter((id) => id instanceof ObjectId);
|
|
74
|
+
await deleteGridFsFile(bucket, fileObjectId);
|
|
75
|
+
for (const variantId of variantIds) await deleteGridFsFile(bucket, variantId);
|
|
76
|
+
} catch {
|
|
77
|
+
ctx.res.status(500);
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: "delete_failed"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
ctx.res.status(204);
|
|
84
|
+
return { ok: true };
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/uploads/api/files/handlers/getFile.ts
|
|
88
|
+
var resolveHeaderString = (value) => {
|
|
89
|
+
if (typeof value !== "string") return null;
|
|
90
|
+
const normalized = value.trim();
|
|
91
|
+
return normalized ? normalized : null;
|
|
92
|
+
};
|
|
93
|
+
var resolveHeaderBoolean = (value) => {
|
|
94
|
+
if (typeof value === "boolean") return value;
|
|
95
|
+
if (typeof value !== "string") return null;
|
|
96
|
+
const normalized = value.trim().toLowerCase();
|
|
97
|
+
if (!normalized) return null;
|
|
98
|
+
if (normalized === "true") return true;
|
|
99
|
+
if (normalized === "false") return false;
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
var escapeHeaderFilename = (filename) => filename.replace(/[\\"]/g, "_");
|
|
103
|
+
var parseRequestedWidth = (value) => {
|
|
104
|
+
if (typeof value !== "string") return null;
|
|
105
|
+
const parsed = Number(value.trim());
|
|
106
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
107
|
+
return Math.floor(parsed);
|
|
108
|
+
};
|
|
109
|
+
var resolveImageVariants = (file) => {
|
|
110
|
+
const metadata = file?.metadata;
|
|
111
|
+
if (!metadata || typeof metadata !== "object") return null;
|
|
112
|
+
const image = metadata.image;
|
|
113
|
+
if (!image || typeof image !== "object") return null;
|
|
114
|
+
const variants = image.variants;
|
|
115
|
+
if (!variants || typeof variants !== "object") return null;
|
|
116
|
+
return variants;
|
|
117
|
+
};
|
|
118
|
+
var resolveVariantFileId = (variant) => {
|
|
119
|
+
if (!variant || typeof variant !== "object") return null;
|
|
120
|
+
const fileId = variant.fileId;
|
|
121
|
+
if (typeof fileId !== "string") return null;
|
|
122
|
+
try {
|
|
123
|
+
return new ObjectId(fileId);
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var selectVariantFileId = (file, requestedWidth, requestedVariant) => {
|
|
129
|
+
const variants = resolveImageVariants(file);
|
|
130
|
+
if (!variants) return null;
|
|
131
|
+
let aliasWidth = null;
|
|
132
|
+
if (typeof requestedVariant === "string") {
|
|
133
|
+
const normalized = requestedVariant.trim().toLowerCase();
|
|
134
|
+
aliasWidth = normalized === "thumb" ? 320 : normalized === "small" ? 640 : normalized === "medium" ? 960 : normalized === "large" ? 1600 : null;
|
|
135
|
+
if (aliasWidth) {
|
|
136
|
+
const variantFileId = resolveVariantFileId(variants[String(aliasWidth)]);
|
|
137
|
+
if (variantFileId) return variantFileId;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const targetWidth = aliasWidth ?? requestedWidth;
|
|
141
|
+
if (!targetWidth) return null;
|
|
142
|
+
const selectedWidth = Object.keys(variants).map((width) => Number(width)).filter((width) => Number.isInteger(width) && width > 0).sort((a, b) => a - b).reduce((selected, width) => {
|
|
143
|
+
if (selected === null) return width;
|
|
144
|
+
const selectedDistance = Math.abs(selected - targetWidth);
|
|
145
|
+
const distance = Math.abs(width - targetWidth);
|
|
146
|
+
if (distance < selectedDistance) return width;
|
|
147
|
+
if (distance === selectedDistance && width > selected) return width;
|
|
148
|
+
return selected;
|
|
149
|
+
}, null);
|
|
150
|
+
return selectedWidth ? resolveVariantFileId(variants[String(selectedWidth)]) : null;
|
|
151
|
+
};
|
|
152
|
+
var getFile = async (_payload, ctx) => {
|
|
153
|
+
const tenantId = getTenantId(ctx);
|
|
154
|
+
if (!tenantId) {
|
|
155
|
+
ctx.res.status(400).end();
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
const fileIdRaw = String(ctx.req.params?.fileId ?? "").trim();
|
|
159
|
+
let fileObjectId;
|
|
160
|
+
try {
|
|
161
|
+
fileObjectId = new ObjectId(fileIdRaw);
|
|
162
|
+
} catch {
|
|
163
|
+
ctx.res.status(400).end();
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
const nativeDb = (await getTenantFilesystemDb(tenantId)).db;
|
|
167
|
+
if (!nativeDb) {
|
|
168
|
+
ctx.res.status(500).end();
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
const bucket = new GridFSBucket(nativeDb, { bucketName: getBucketName() });
|
|
172
|
+
const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray();
|
|
173
|
+
if (!file) {
|
|
174
|
+
ctx.res.status(404).end();
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
177
|
+
if (!(resolveHeaderBoolean(file?.metadata?.isPublic) ?? false)) {
|
|
178
|
+
const userId = getUserId(ctx);
|
|
179
|
+
const uploadKeyHash = getUploadKeyHash(ctx);
|
|
180
|
+
const metadataUserId = resolveHeaderString(file?.metadata?.userId);
|
|
181
|
+
const ownerKeyHash = resolveHeaderString(file?.metadata?.ownerKeyHash);
|
|
182
|
+
if (!Boolean(userId && metadataUserId && userId === metadataUserId) && !Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)) {
|
|
183
|
+
ctx.res.status(401).end();
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const variantFileObjectId = selectVariantFileId(file, parseRequestedWidth(ctx.req.query?.w), ctx.req.query?.variant);
|
|
188
|
+
const [variantFile] = variantFileObjectId ? await bucket.find({ _id: variantFileObjectId }).limit(1).toArray() : [];
|
|
189
|
+
const selectedFileObjectId = variantFile ? variantFileObjectId : fileObjectId;
|
|
190
|
+
const selectedFile = variantFile ?? file;
|
|
191
|
+
const mimeType = resolveHeaderString(selectedFile?.metadata?.mimeType) ?? "application/octet-stream";
|
|
192
|
+
const filenameSafe = escapeHeaderFilename(resolveHeaderString(selectedFile?.filename) ?? fileIdRaw);
|
|
193
|
+
const cacheControl = "private, max-age=0, must-revalidate";
|
|
194
|
+
const md5 = resolveHeaderString(selectedFile?.md5);
|
|
195
|
+
const uploadDate = selectedFile?.uploadDate instanceof Date ? selectedFile.uploadDate : null;
|
|
196
|
+
const etagValue = md5 ?? `${selectedFileObjectId.toHexString()}-${String(selectedFile?.length ?? 0)}-${String(uploadDate?.getTime() ?? 0)}`;
|
|
197
|
+
const etag = md5 ? `"${etagValue}"` : `W/"${etagValue}"`;
|
|
198
|
+
const ifNoneMatch = resolveHeaderString(ctx.req.headers?.["if-none-match"]);
|
|
199
|
+
if (ifNoneMatch) {
|
|
200
|
+
const candidates = ifNoneMatch.split(",").map((value) => value.trim()).filter(Boolean);
|
|
201
|
+
if (candidates.includes("*") || candidates.includes(etag)) {
|
|
202
|
+
ctx.res.status(304);
|
|
203
|
+
ctx.res.setHeader("Cache-Control", cacheControl);
|
|
204
|
+
ctx.res.setHeader("ETag", etag);
|
|
205
|
+
ctx.res.end();
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
ctx.res.status(200);
|
|
210
|
+
ctx.res.setHeader("Content-Type", mimeType);
|
|
211
|
+
ctx.res.setHeader("Content-Length", String(selectedFile?.length ?? 0));
|
|
212
|
+
ctx.res.setHeader("Content-Disposition", `inline; filename="${filenameSafe}"`);
|
|
213
|
+
ctx.res.setHeader("Cache-Control", cacheControl);
|
|
214
|
+
ctx.res.setHeader("ETag", etag);
|
|
215
|
+
ctx.res.setHeader("X-Content-Type-Options", "nosniff");
|
|
216
|
+
if (mimeType === "image/svg+xml") ctx.res.setHeader("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox; base-uri 'none'; form-action 'none'");
|
|
217
|
+
ctx.res.flushHeaders();
|
|
218
|
+
if (ctx.req.method === "HEAD") {
|
|
219
|
+
ctx.res.end();
|
|
220
|
+
return {};
|
|
221
|
+
}
|
|
222
|
+
const stream = bucket.openDownloadStream(selectedFileObjectId);
|
|
223
|
+
stream.on("error", () => {
|
|
224
|
+
try {
|
|
225
|
+
ctx.res.destroy();
|
|
226
|
+
} catch {}
|
|
227
|
+
});
|
|
228
|
+
stream.pipe(ctx.res);
|
|
229
|
+
return {};
|
|
230
|
+
};
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/uploads/api/files/index.ts
|
|
233
|
+
var Route = "/api/rb/files/:fileId";
|
|
234
|
+
var GetRoute = Route;
|
|
235
|
+
var DeleteRoute = Route;
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/uploads/api/files/handler.ts
|
|
238
|
+
var handler_default = (api) => {
|
|
239
|
+
api.get(GetRoute, getFile);
|
|
240
|
+
api.delete(DeleteRoute, deleteFile);
|
|
241
|
+
};
|
|
242
|
+
//#endregion
|
|
243
|
+
export { handler_default as default };
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=handler-BqlcQ9sE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler-BqlcQ9sE.js","names":["ApiHandler","getTenantFilesystemDb","GridFSBucket","ObjectId","getBucketName","getTenantId","getUploadKeyHash","getUserId","SessionUser","DeleteResponsePayload","ok","error","resolveHeaderString","value","normalized","trim","isFileNotFoundError","message","Error","String","includes","deleteGridFsFile","bucket","fileId","Promise","delete","deleteFile","Record","_payload","ctx","tenantId","res","status","fileIdRaw","req","params","fileObjectId","fsDb","nativeDb","db","bucketName","userId","uploadKeyHash","file","find","_id","limit","toArray","metadataUserId","metadata","ownerKeyHash","authorizedByUser","Boolean","authorizedByKey","variants","toHexString","variantIds","map","variant","filter","id","variantId","ApiHandler","getTenantFilesystemDb","GridFSBucket","ObjectId","getBucketName","getTenantId","getUploadKeyHash","getUserId","SessionUser","resolveHeaderString","value","normalized","trim","resolveHeaderBoolean","toLowerCase","escapeHeaderFilename","filename","replace","parseRequestedWidth","parsed","Number","isFinite","Math","floor","resolveImageVariants","file","Record","metadata","image","variants","resolveVariantFileId","variant","fileId","selectVariantFileId","requestedWidth","requestedVariant","aliasWidth","variantFileId","String","targetWidth","widths","Object","keys","map","width","filter","isInteger","sort","a","b","selectedWidth","reduce","selected","selectedDistance","abs","distance","getFile","_payload","ctx","Promise","tenantId","res","status","end","fileIdRaw","req","params","fileObjectId","fsDb","nativeDb","db","bucketName","bucket","find","_id","limit","toArray","isPublic","userId","uploadKeyHash","metadataUserId","ownerKeyHash","authorizedByUser","Boolean","authorizedByKey","query","w","variantFileObjectId","variantFile","selectedFileObjectId","selectedFile","mimeTypeFromMetadata","mimeType","filenameFromDb","filenameSafe","cacheControl","md5","uploadDate","Date","etagValue","toHexString","length","getTime","etag","ifNoneMatch","headers","candidates","split","includes","setHeader","flushHeaders","method","stream","openDownloadStream","on","destroy","pipe","Route","GetRoute","DeleteRoute"],"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\nconst isFileNotFoundError = (error: unknown): boolean => {\n const message = error instanceof Error ? error.message : String(error)\n return message.includes(\"FileNotFound\")\n}\n\nconst deleteGridFsFile = async (bucket: GridFSBucket, fileId: ObjectId): Promise<void> => {\n try {\n await bucket.delete(fileId)\n } catch (error) {\n if (!isFileNotFoundError(error)) throw error\n }\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 const variants = await bucket.find({ \"metadata.variantOf\": fileObjectId.toHexString() }).toArray()\n const variantIds = variants\n .map((variant) => (variant as any)?._id)\n .filter((id): id is ObjectId => id instanceof ObjectId)\n\n await deleteGridFsFile(bucket, fileObjectId)\n for (const variantId of variantIds) {\n await deleteGridFsFile(bucket, variantId)\n }\n } catch {\n ctx.res.status(500)\n return { ok: false, error: \"delete_failed\" }\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\nconst parseRequestedWidth = (value: unknown): number | null => {\n if (typeof value !== \"string\") return null\n const parsed = Number(value.trim())\n if (!Number.isFinite(parsed) || parsed <= 0) return null\n return Math.floor(parsed)\n}\n\nconst resolveImageVariants = (file: unknown): Record<string, unknown> | null => {\n const metadata = (file as any)?.metadata\n if (!metadata || typeof metadata !== \"object\") return null\n const image = (metadata as Record<string, unknown>).image\n if (!image || typeof image !== \"object\") return null\n const variants = (image as Record<string, unknown>).variants\n if (!variants || typeof variants !== \"object\") return null\n return variants as Record<string, unknown>\n}\n\nconst resolveVariantFileId = (variant: unknown): ObjectId | null => {\n if (!variant || typeof variant !== \"object\") return null\n const fileId = (variant as Record<string, unknown>).fileId\n if (typeof fileId !== \"string\") return null\n try {\n return new ObjectId(fileId)\n } catch {\n return null\n }\n}\n\nconst selectVariantFileId = (file: unknown, requestedWidth: number | null, requestedVariant: unknown): ObjectId | null => {\n const variants = resolveImageVariants(file)\n if (!variants) return null\n\n let aliasWidth: number | null = null\n if (typeof requestedVariant === \"string\") {\n const normalized = requestedVariant.trim().toLowerCase()\n aliasWidth = normalized === \"thumb\" ? 320 : normalized === \"small\" ? 640 : normalized === \"medium\" ? 960 : normalized === \"large\" ? 1600 : null\n if (aliasWidth) {\n const variantFileId = resolveVariantFileId(variants[String(aliasWidth)])\n if (variantFileId) return variantFileId\n }\n }\n\n const targetWidth = aliasWidth ?? requestedWidth\n if (!targetWidth) return null\n\n const widths = Object.keys(variants)\n .map((width) => Number(width))\n .filter((width) => Number.isInteger(width) && width > 0)\n .sort((a, b) => a - b)\n\n const selectedWidth = widths.reduce<number | null>((selected, width) => {\n if (selected === null) return width\n\n const selectedDistance = Math.abs(selected - targetWidth)\n const distance = Math.abs(width - targetWidth)\n if (distance < selectedDistance) return width\n if (distance === selectedDistance && width > selected) return width\n return selected\n }, null)\n return selectedWidth ? resolveVariantFileId(variants[String(selectedWidth)]) : null\n}\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 requestedWidth = parseRequestedWidth((ctx.req.query as any)?.w)\n const variantFileObjectId = selectVariantFileId(file, requestedWidth, (ctx.req.query as any)?.variant)\n const [variantFile] = variantFileObjectId\n ? await bucket.find({ _id: variantFileObjectId }).limit(1).toArray()\n : []\n const selectedFileObjectId = variantFile ? variantFileObjectId as ObjectId : fileObjectId\n const selectedFile = variantFile ?? file\n\n const mimeTypeFromMetadata = resolveHeaderString((selectedFile as any)?.metadata?.mimeType)\n const mimeType = mimeTypeFromMetadata ?? \"application/octet-stream\"\n const filenameFromDb = resolveHeaderString((selectedFile 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((selectedFile as any)?.md5)\n const uploadDate = (selectedFile as any)?.uploadDate instanceof Date ? (selectedFile as any).uploadDate : null\n const etagValue = md5 ?? `${selectedFileObjectId.toHexString()}-${String((selectedFile 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((selectedFile 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(selectedFileObjectId)\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"],"mappings":"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"}
|