@dudousxd/nestjs-media-dashboard 0.1.0 → 0.1.1
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/client/media-console-client.d.ts +13 -0
- package/dist/client/media-console-client.d.ts.map +1 -1
- package/dist/client/media-console-client.js +40 -0
- package/dist/client/media-console-client.js.map +1 -1
- package/dist/server/index.cjs +94 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +15 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +100 -7
- package/dist/server/index.js.map +1 -1
- package/dist/spa/assets/{index-v1mdOPqe.js → index-DkLtmflr.js} +1 -1
- package/dist/spa/assets/{preview-Bdq6BX0n.js → preview-BrziTKG2.js} +1 -1
- package/dist/spa/assets/styles-DQHMfSU4.css +1 -0
- package/dist/spa/assets/styles-I-dUYhsq.js +78 -0
- package/dist/spa/index.html +9 -3
- package/dist/spa/preview.html +9 -3
- package/package.json +2 -1
- package/dist/spa/assets/styles-B93akCcR.js +0 -40
- package/dist/spa/assets/styles-DStlWvmW.css +0 -1
|
@@ -17,6 +17,13 @@ export declare const mediaConsoleClient: {
|
|
|
17
17
|
limit?: number;
|
|
18
18
|
}) => Promise<ObjectListResponse>;
|
|
19
19
|
object: (disk: string, key: string) => Promise<ObjectDetailResponse>;
|
|
20
|
+
/** Same-origin URL that streams the object bytes inline (Content-Disposition: inline) — used to
|
|
21
|
+
* embed previews that a cross-origin signed URL would download (PDFs) or that CORS would block. */
|
|
22
|
+
objectRawUrl: (disk: string, key: string) => string;
|
|
23
|
+
/** Fetch an object's bytes as text (for CSV/JSON/text previews) through the inline proxy above. */
|
|
24
|
+
objectText: (disk: string, key: string) => Promise<string>;
|
|
25
|
+
/** Fetch an object's raw bytes (for binary previews like XLSX) through the inline proxy. */
|
|
26
|
+
objectBytes: (disk: string, key: string) => Promise<ArrayBuffer>;
|
|
20
27
|
uploads: (params?: {
|
|
21
28
|
disk?: string;
|
|
22
29
|
prefix?: string;
|
|
@@ -35,6 +42,12 @@ export declare const mediaConsoleClient: {
|
|
|
35
42
|
moveObject: (disk: string, from: string, to: string) => Promise<void>;
|
|
36
43
|
abortUpload: (id: string) => Promise<void>;
|
|
37
44
|
deleteLibraryRecord: (id: string) => Promise<void>;
|
|
45
|
+
/** Upload a file to `key` on the disk (raw bytes; MIME preserved via the `type` param). */
|
|
46
|
+
uploadObject: (disk: string, key: string, file: Blob & {
|
|
47
|
+
type: string;
|
|
48
|
+
}) => Promise<void>;
|
|
49
|
+
/** Create a folder (a zero-byte marker at `<prefix>/`) on the disk. */
|
|
50
|
+
createFolder: (disk: string, prefix: string) => Promise<void>;
|
|
38
51
|
};
|
|
39
52
|
export type MediaConsoleClient = typeof mediaConsoleClient;
|
|
40
53
|
//# sourceMappingURL=media-console-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-console-client.d.ts","sourceRoot":"","sources":["../../src/client/media-console-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,EACR,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;CACF;AAED,iGAAiG;AACjG,wBAAgB,OAAO,IAAI,MAAM,CAMhC;
|
|
1
|
+
{"version":3,"file":"media-console-client.d.ts","sourceRoot":"","sources":["../../src/client/media-console-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,EACR,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;CACF;AAED,iGAAiG;AACjG,wBAAgB,OAAO,IAAI,MAAM,CAMhC;AA4CD,iGAAiG;AACjG,eAAO,MAAM,kBAAkB;oBACf,OAAO,CAAC,QAAQ,CAAC;iBACpB,OAAO,CAAC,gBAAgB,CAAC;oBAE5B,MAAM,WACJ;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAC3D,OAAO,CAAC,kBAAkB,CAAC;mBAEf,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC,oBAAoB,CAAC;IAElE;wGACoG;yBAC/E,MAAM,OAAO,MAAM,KAAG,MAAM;IAEjD,mGAAmG;uBAC1E,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;IAO9D,4FAA4F;wBAClE,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC,WAAW,CAAC;uBAOlD;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAQ,OAAO,CAAC,kBAAkB,CAAC;iBAE1E,MAAM,KAAG,OAAO,CAAC,oBAAoB,CAAC;uBAElC,OAAO,CAAC,mBAAmB,CAAC;uBAEnC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9E,OAAO,CAAC,mBAAmB,CAAC;wBACX,MAAM,KAAG,OAAO,CAAC,qBAAqB,CAAC;yBAEtC,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;uBAErC,MAAM,QAAQ,MAAM,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;uBAEhD,MAAM,QAAQ,MAAM,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;sBAEjD,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;8BAEd,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAEhD,2FAA2F;yBACtE,MAAM,OAAO,MAAM,QAAQ,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAG,OAAO,CAAC,IAAI,CAAC;IAQvF,uEAAuE;yBAClD,MAAM,UAAU,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;CAE5D,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC"}
|
|
@@ -34,12 +34,45 @@ async function send(method, path, body) {
|
|
|
34
34
|
if (!response.ok)
|
|
35
35
|
throw new Error(`${response.status} ${response.statusText}`);
|
|
36
36
|
}
|
|
37
|
+
/** POST raw bytes as `application/octet-stream` (a file upload) — the host's JSON parser leaves the
|
|
38
|
+
* stream intact, so the server reads it straight from the request. */
|
|
39
|
+
async function sendRaw(path, body) {
|
|
40
|
+
const response = await fetch(`${apiBase()}${path}`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
credentials: 'same-origin',
|
|
43
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
44
|
+
body,
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok)
|
|
47
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
48
|
+
}
|
|
37
49
|
/** Typed browser client for the /media console JSON API. Rides ambient cookies (host guards). */
|
|
38
50
|
export const mediaConsoleClient = {
|
|
39
51
|
topology: () => getJson('/topology'),
|
|
40
52
|
disks: () => getJson('/disks'),
|
|
41
53
|
objects: (disk, params = {}) => getJson(withQuery(`/disks/${encodeURIComponent(disk)}/objects`, params)),
|
|
42
54
|
object: (disk, key) => getJson(withQuery(`/disks/${encodeURIComponent(disk)}/object`, { key })),
|
|
55
|
+
/** Same-origin URL that streams the object bytes inline (Content-Disposition: inline) — used to
|
|
56
|
+
* embed previews that a cross-origin signed URL would download (PDFs) or that CORS would block. */
|
|
57
|
+
objectRawUrl: (disk, key) => `${apiBase()}${withQuery(`/disks/${encodeURIComponent(disk)}/object/raw`, { key })}`,
|
|
58
|
+
/** Fetch an object's bytes as text (for CSV/JSON/text previews) through the inline proxy above. */
|
|
59
|
+
objectText: async (disk, key) => {
|
|
60
|
+
const response = await fetch(mediaConsoleClient.objectRawUrl(disk, key), {
|
|
61
|
+
credentials: 'same-origin',
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok)
|
|
64
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
65
|
+
return response.text();
|
|
66
|
+
},
|
|
67
|
+
/** Fetch an object's raw bytes (for binary previews like XLSX) through the inline proxy. */
|
|
68
|
+
objectBytes: async (disk, key) => {
|
|
69
|
+
const response = await fetch(mediaConsoleClient.objectRawUrl(disk, key), {
|
|
70
|
+
credentials: 'same-origin',
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok)
|
|
73
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
74
|
+
return response.arrayBuffer();
|
|
75
|
+
},
|
|
43
76
|
uploads: (params = {}) => getJson(withQuery('/uploads', params)),
|
|
44
77
|
upload: (id) => getJson(`/uploads/${encodeURIComponent(id)}`),
|
|
45
78
|
collections: () => getJson('/library/collections'),
|
|
@@ -50,5 +83,12 @@ export const mediaConsoleClient = {
|
|
|
50
83
|
moveObject: (disk, from, to) => send('POST', `/disks/${encodeURIComponent(disk)}/move`, { from, to }),
|
|
51
84
|
abortUpload: (id) => send('POST', `/uploads/${encodeURIComponent(id)}/abort`),
|
|
52
85
|
deleteLibraryRecord: (id) => send('DELETE', `/library/${encodeURIComponent(id)}`),
|
|
86
|
+
/** Upload a file to `key` on the disk (raw bytes; MIME preserved via the `type` param). */
|
|
87
|
+
uploadObject: (disk, key, file) => sendRaw(withQuery(`/disks/${encodeURIComponent(disk)}/upload`, {
|
|
88
|
+
key,
|
|
89
|
+
...(file.type ? { type: file.type } : {}),
|
|
90
|
+
}), file),
|
|
91
|
+
/** Create a folder (a zero-byte marker at `<prefix>/`) on the disk. */
|
|
92
|
+
createFolder: (disk, prefix) => send('POST', `/disks/${encodeURIComponent(disk)}/folder`, { prefix }),
|
|
53
93
|
};
|
|
54
94
|
//# sourceMappingURL=media-console-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-console-client.js","sourceRoot":"","sources":["../../src/client/media-console-client.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"media-console-client.js","sourceRoot":"","sources":["../../src/client/media-console-client.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.cjs
CHANGED
|
@@ -162,7 +162,9 @@ var MediaConsoleService = class {
|
|
|
162
162
|
name: lastSegment(prefix),
|
|
163
163
|
prefix
|
|
164
164
|
})),
|
|
165
|
-
|
|
165
|
+
// Drop the zero-byte "folder marker" (a key ending in `/`, whose name is empty after the
|
|
166
|
+
// prefix) that `createFolder` writes — it's the folder itself, not a file inside it.
|
|
167
|
+
files: result.files.filter((entry) => entry.name !== "").map((entry) => ({
|
|
166
168
|
key: entry.key,
|
|
167
169
|
name: entry.name,
|
|
168
170
|
sizeBytes: entry.sizeBytes,
|
|
@@ -191,6 +193,35 @@ var MediaConsoleService = class {
|
|
|
191
193
|
url
|
|
192
194
|
};
|
|
193
195
|
}
|
|
196
|
+
/** The object's raw byte stream plus the metadata the controller needs to serve it inline. Used by
|
|
197
|
+
* the console's same-origin preview proxy so text/PDF render in the browser instead of downloading
|
|
198
|
+
* (and so a CORS-locked bucket is still previewable). */
|
|
199
|
+
async objectStream(disk, key) {
|
|
200
|
+
const driver = this.diskOrThrow(disk);
|
|
201
|
+
const stat = driver.stat ? await driver.stat(key) : {
|
|
202
|
+
size: await driver.size(key)
|
|
203
|
+
};
|
|
204
|
+
const stream = await driver.stream(key);
|
|
205
|
+
return {
|
|
206
|
+
stream,
|
|
207
|
+
contentType: stat.contentType ?? "application/octet-stream",
|
|
208
|
+
size: stat.size
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/** Writes an object from a byte stream (a browser upload). The caller supplies the full key
|
|
212
|
+
* (prefix + filename); an unknown disk 404s via {@link diskOrThrow}. Actions-gated. */
|
|
213
|
+
async putObject(disk, key, body, contentType) {
|
|
214
|
+
await this.diskOrThrow(disk).put(key, body, contentType ? {
|
|
215
|
+
contentType
|
|
216
|
+
} : void 0);
|
|
217
|
+
}
|
|
218
|
+
/** Creates a "folder" — a zero-byte marker object at `<prefix>/`, which S3-style listing surfaces
|
|
219
|
+
* as a navigable folder. Normalizes to exactly one trailing slash; rejects an empty prefix. */
|
|
220
|
+
async createFolder(disk, prefix) {
|
|
221
|
+
const normalized = prefix.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
222
|
+
if (normalized === "") throw new import_common.BadRequestException("Folder name is required");
|
|
223
|
+
await this.diskOrThrow(disk).put(`${normalized}/`, Buffer.alloc(0));
|
|
224
|
+
}
|
|
194
225
|
async deleteObject(disk, key) {
|
|
195
226
|
await this.diskOrThrow(disk).delete(key);
|
|
196
227
|
}
|
|
@@ -357,6 +388,17 @@ var MediaConsoleActionsController = class {
|
|
|
357
388
|
moveObject(disk, body) {
|
|
358
389
|
return this.service.moveObject(disk, body.from, body.to);
|
|
359
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Uploads a file to `key` on the disk. The body is the raw bytes sent as `application/octet-stream`
|
|
393
|
+
* so the host's JSON/urlencoded body parsers never consume the stream; the real MIME rides as the
|
|
394
|
+
* `type` query param and becomes the stored object's Content-Type. `@Req()` is the request stream.
|
|
395
|
+
*/
|
|
396
|
+
uploadObject(disk, key, request, type) {
|
|
397
|
+
return this.service.putObject(disk, key, request, type);
|
|
398
|
+
}
|
|
399
|
+
createFolder(disk, body) {
|
|
400
|
+
return this.service.createFolder(disk, body.prefix);
|
|
401
|
+
}
|
|
360
402
|
abortUpload(id) {
|
|
361
403
|
return this.service.abortUpload(id);
|
|
362
404
|
}
|
|
@@ -400,6 +442,34 @@ _ts_decorate2([
|
|
|
400
442
|
]),
|
|
401
443
|
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
402
444
|
], MediaConsoleActionsController.prototype, "moveObject", null);
|
|
445
|
+
_ts_decorate2([
|
|
446
|
+
(0, import_common2.Post)("disks/:disk/upload"),
|
|
447
|
+
(0, import_common2.HttpCode)(204),
|
|
448
|
+
_ts_param2(0, (0, import_common2.Param)("disk")),
|
|
449
|
+
_ts_param2(1, (0, import_common2.Query)("key")),
|
|
450
|
+
_ts_param2(2, (0, import_common2.Req)()),
|
|
451
|
+
_ts_param2(3, (0, import_common2.Query)("type")),
|
|
452
|
+
_ts_metadata2("design:type", Function),
|
|
453
|
+
_ts_metadata2("design:paramtypes", [
|
|
454
|
+
String,
|
|
455
|
+
String,
|
|
456
|
+
typeof Readable === "undefined" ? Object : Readable,
|
|
457
|
+
String
|
|
458
|
+
]),
|
|
459
|
+
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
460
|
+
], MediaConsoleActionsController.prototype, "uploadObject", null);
|
|
461
|
+
_ts_decorate2([
|
|
462
|
+
(0, import_common2.Post)("disks/:disk/folder"),
|
|
463
|
+
(0, import_common2.HttpCode)(204),
|
|
464
|
+
_ts_param2(0, (0, import_common2.Param)("disk")),
|
|
465
|
+
_ts_param2(1, (0, import_common2.Body)()),
|
|
466
|
+
_ts_metadata2("design:type", Function),
|
|
467
|
+
_ts_metadata2("design:paramtypes", [
|
|
468
|
+
String,
|
|
469
|
+
typeof CreateFolderBody === "undefined" ? Object : CreateFolderBody
|
|
470
|
+
]),
|
|
471
|
+
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
472
|
+
], MediaConsoleActionsController.prototype, "createFolder", null);
|
|
403
473
|
_ts_decorate2([
|
|
404
474
|
(0, import_common2.Post)("uploads/:id/abort"),
|
|
405
475
|
(0, import_common2.HttpCode)(204),
|
|
@@ -482,6 +552,18 @@ var MediaConsoleReadController = class {
|
|
|
482
552
|
object(disk, key) {
|
|
483
553
|
return this.service.objectDetail(disk, key);
|
|
484
554
|
}
|
|
555
|
+
/** Streams the object's bytes inline (Content-Disposition: inline) from the same origin, so the SPA
|
|
556
|
+
* can render text/PDF previews the browser would otherwise download, and read text past CORS. */
|
|
557
|
+
async objectRaw(disk, key) {
|
|
558
|
+
const { stream, contentType, size } = await this.service.objectStream(disk, key);
|
|
559
|
+
return new import_common3.StreamableFile(stream, {
|
|
560
|
+
type: contentType,
|
|
561
|
+
disposition: "inline",
|
|
562
|
+
...Number.isFinite(size) ? {
|
|
563
|
+
length: size
|
|
564
|
+
} : {}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
485
567
|
uploads(disk, prefix) {
|
|
486
568
|
return this.service.listUploads({
|
|
487
569
|
...disk ? {
|
|
@@ -554,6 +636,17 @@ _ts_decorate3([
|
|
|
554
636
|
]),
|
|
555
637
|
_ts_metadata3("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
556
638
|
], MediaConsoleReadController.prototype, "object", null);
|
|
639
|
+
_ts_decorate3([
|
|
640
|
+
(0, import_common3.Get)("disks/:disk/object/raw"),
|
|
641
|
+
_ts_param3(0, (0, import_common3.Param)("disk")),
|
|
642
|
+
_ts_param3(1, (0, import_common3.Query)("key")),
|
|
643
|
+
_ts_metadata3("design:type", Function),
|
|
644
|
+
_ts_metadata3("design:paramtypes", [
|
|
645
|
+
String,
|
|
646
|
+
String
|
|
647
|
+
]),
|
|
648
|
+
_ts_metadata3("design:returntype", Promise)
|
|
649
|
+
], MediaConsoleReadController.prototype, "objectRaw", null);
|
|
557
650
|
_ts_decorate3([
|
|
558
651
|
(0, import_common3.Get)("uploads"),
|
|
559
652
|
_ts_param3(0, (0, import_common3.Query)("disk")),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/index.ts","../../../../node_modules/.pnpm/tsup@8.3.5_@swc+core@1.15.41_jiti@1.21.7_postcss@8.4.49_typescript@5.9.3_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js","../../src/server/media-dashboard.module.ts","../../src/server/media-console-api.module.ts","../../src/server/media-console-actions.controller.ts","../../src/server/media-console.service.ts","../../src/server/tokens.ts","../../src/server/media-console-read.controller.ts","../../src/server/media-dashboard-ui.controller.ts"],"sourcesContent":["export { MediaDashboardModule, type MediaDashboardOptions } from './media-dashboard.module.js';\nexport { MediaConsoleApiModule } from './media-console-api.module.js';\nexport { MediaConsoleService } from './media-console.service.js';\nexport {\n MEDIA_STORAGE_SHARED,\n MEDIA_STORE,\n MEDIA_UPLOAD_SESSIONS,\n} from './tokens.js';\n// The API response types (also published at the `./client` entry) for host reuse.\nexport type * from '../client/types.js';\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import { type DynamicModule, Module } from '@nestjs/common';\nimport { RouterModule } from '@nestjs/core';\nimport { MediaConsoleApiModule } from './media-console-api.module.js';\nimport { MediaDashboardUiController } from './media-dashboard-ui.controller.js';\nimport { MEDIA_DASHBOARD_API_PATH, MEDIA_DASHBOARD_BASE_PATH } from './tokens.js';\n\nexport interface MediaDashboardOptions {\n /**\n * Where the SPA (UI) is served. Default `/media`. A page route — keep it out of an `/api`\n * prefix so it reads as a UI, not an endpoint.\n */\n basePath?: string;\n /**\n * Where the JSON API is mounted (what the SPA fetches). Default `<basePath>/api`. Set it under\n * your app's `/api` prefix — e.g. `/api/media/console` — so the API inherits the app's\n * auth/proxy rules while the UI stays at `basePath`.\n */\n apiBasePath?: string;\n /**\n * Enable the destructive endpoints (delete object/record, copy/move, abort upload). Default\n * `false` — the read API is always available. Front the mount with your own guard either way.\n */\n actions?: boolean;\n}\n\n/** Leading slash, no trailing slash. */\nfunction normalize(path: string): string {\n return `/${path.replace(/^\\/+|\\/+$/g, '')}`;\n}\n\n/**\n * Mounts the /media console: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`\n * (default `<basePath>/api`). Import via `MediaDashboardModule.forRoot(...)` alongside\n * `MediaModule` (global), so it resolves the storage/store/upload tokens. No auth is built in —\n * front the routes with a guard, and exclude the UI/API paths from any global `/api` prefix.\n */\n@Module({})\nexport class MediaDashboardModule {\n static forRoot(options: MediaDashboardOptions = {}): DynamicModule {\n const basePath = normalize(options.basePath ?? '/media');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n const actions = options.actions === true;\n return {\n module: MediaDashboardModule,\n imports: [\n MediaConsoleApiModule.register(actions),\n RouterModule.register([\n { path: basePath, module: MediaDashboardModule }, // the UI controller below\n { path: apiBasePath, module: MediaConsoleApiModule },\n ]),\n ],\n controllers: [MediaDashboardUiController],\n providers: [\n { provide: MEDIA_DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: MEDIA_DASHBOARD_API_PATH, useValue: apiBasePath },\n ],\n // Re-export the API module so its MediaConsoleService reaches importers if they want it.\n exports: [MediaConsoleApiModule],\n };\n }\n}\n","import { type DynamicModule, Module } from '@nestjs/common';\nimport { MediaConsoleActionsController } from './media-console-actions.controller.js';\nimport { MediaConsoleReadController } from './media-console-read.controller.js';\nimport { MediaConsoleService } from './media-console.service.js';\nimport { MEDIA_DASHBOARD_ACTIONS } from './tokens.js';\n\n/**\n * Holds the console's JSON API controllers + the read/action service, mounted on its own path by\n * `MediaDashboardModule.forRoot`. The destructive actions controller is only registered when the\n * host opts in via `{ actions: true }`.\n */\n@Module({})\nexport class MediaConsoleApiModule {\n static register(actions: boolean): DynamicModule {\n return {\n module: MediaConsoleApiModule,\n controllers: actions\n ? [MediaConsoleReadController, MediaConsoleActionsController]\n : [MediaConsoleReadController],\n providers: [MediaConsoleService, { provide: MEDIA_DASHBOARD_ACTIONS, useValue: actions }],\n exports: [MediaConsoleService],\n };\n }\n}\n","import { Body, Controller, Delete, HttpCode, Inject, Param, Post, Query } from '@nestjs/common';\nimport { MediaConsoleService } from './media-console.service.js';\n\ninterface CopyMoveBody {\n from: string;\n to: string;\n}\n\n/**\n * Destructive JSON API for the /media console. Only registered when the host opts in with\n * `MediaDashboardModule.forRoot({ actions: true })` (default off). Bare `@Controller()` — the path\n * prefix comes from `RouterModule`, sharing the API base with the read controller.\n */\n@Controller()\nexport class MediaConsoleActionsController {\n constructor(@Inject(MediaConsoleService) private readonly service: MediaConsoleService) {}\n\n @Delete('disks/:disk/object')\n @HttpCode(204)\n deleteObject(@Param('disk') disk: string, @Query('key') key: string): Promise<void> {\n return this.service.deleteObject(disk, key);\n }\n\n @Post('disks/:disk/copy')\n @HttpCode(204)\n copyObject(@Param('disk') disk: string, @Body() body: CopyMoveBody): Promise<void> {\n return this.service.copyObject(disk, body.from, body.to);\n }\n\n @Post('disks/:disk/move')\n @HttpCode(204)\n moveObject(@Param('disk') disk: string, @Body() body: CopyMoveBody): Promise<void> {\n return this.service.moveObject(disk, body.from, body.to);\n }\n\n @Post('uploads/:id/abort')\n @HttpCode(204)\n abortUpload(@Param('id') id: string): Promise<void> {\n return this.service.abortUpload(id);\n }\n\n @Delete('library/:id')\n @HttpCode(204)\n deleteLibraryRecord(@Param('id') id: string): Promise<void> {\n return this.service.deleteLibraryRecord(id);\n }\n}\n","import type {\n MediaRecord,\n MediaStore,\n StorageDriver,\n StorageManager,\n UploadSession,\n UploadSessionStore,\n} from '@dudousxd/nestjs-media-core';\nimport { Inject, Injectable, NotFoundException, Optional } from '@nestjs/common';\nimport type {\n CollectionsResponse,\n DiskListResponse,\n LibraryDetailResponse,\n LibraryListResponse,\n LibraryRecord,\n ObjectDetailResponse,\n ObjectListResponse,\n Topology,\n UploadDetailResponse,\n UploadInfo,\n UploadListResponse,\n} from '../client/types.js';\nimport {\n MEDIA_DASHBOARD_ACTIONS,\n MEDIA_STORAGE_SHARED,\n MEDIA_STORE,\n MEDIA_UPLOAD_SESSIONS,\n} from './tokens.js';\n\n/** Seconds a generated preview/download URL stays valid. */\nconst URL_TTL_SECONDS = 300;\nconst DEFAULT_PAGE_LIMIT = 50;\n\n/** Last path segment of a folder prefix, ignoring a trailing slash. */\nfunction lastSegment(prefix: string): string {\n const trimmed = prefix.replace(/\\/+$/, '');\n const slash = trimmed.lastIndexOf('/');\n return slash === -1 ? trimmed : trimmed.slice(slash + 1);\n}\n\nfunction mapUpload(session: UploadSession): UploadInfo {\n const percent =\n session.size !== undefined && session.size > 0\n ? Math.min(100, Math.round((session.offset / session.size) * 100))\n : null;\n return {\n id: session.id,\n disk: session.disk,\n key: session.key,\n offset: session.offset,\n size: session.size ?? null,\n percent,\n parts: session.parts,\n multipart: session.multipartUploadId !== undefined,\n ...(session.createdAt ? { createdAt: session.createdAt.toISOString() } : {}),\n };\n}\n\nfunction mapRecord(record: MediaRecord): LibraryRecord {\n return {\n id: record.id,\n ownerType: record.ownerType,\n ownerId: record.ownerId,\n collection: record.collection,\n name: record.name,\n fileName: record.fileName,\n mimeType: record.mimeType,\n size: record.size,\n disk: record.disk,\n path: record.path,\n createdAt: record.createdAt.toISOString(),\n };\n}\n\n/**\n * Read + action logic behind the console's JSON API. Resolves the three by-value media tokens\n * with `@Optional()` so a host without a `MediaStore`/upload store still boots — every method\n * degrades to an empty shape instead of throwing when its capability is absent.\n */\n@Injectable()\nexport class MediaConsoleService {\n constructor(\n @Optional() @Inject(MEDIA_STORAGE_SHARED) private readonly storage: StorageManager | null,\n @Optional() @Inject(MEDIA_STORE) private readonly store: MediaStore | null,\n @Optional() @Inject(MEDIA_UPLOAD_SESSIONS) private readonly uploads: UploadSessionStore | null,\n @Optional() @Inject(MEDIA_DASHBOARD_ACTIONS) private readonly actionsEnabled: boolean | null,\n ) {}\n\n private diskOrThrow(disk: string): StorageDriver {\n if (!this.storage || !this.storage.diskNames().includes(disk)) {\n throw new NotFoundException(`Unknown disk: ${disk}`);\n }\n return this.storage.disk(disk);\n }\n\n listDisks(): DiskListResponse {\n const storage = this.storage;\n if (!storage) return { disks: [] };\n const defaultDisk = storage.defaultDisk;\n return {\n disks: storage.diskNames().map((name) => ({\n name,\n default: name === defaultDisk,\n capabilities: storage.disk(name).capabilities,\n })),\n };\n }\n\n async listObjects(\n disk: string,\n options: { prefix?: string; cursor?: string; limit?: number },\n ): Promise<ObjectListResponse> {\n const driver = this.diskOrThrow(disk);\n const result = await driver.list(options.prefix ?? '', {\n delimiter: '/',\n ...(options.cursor ? { cursor: options.cursor } : {}),\n limit: options.limit ?? DEFAULT_PAGE_LIMIT,\n });\n return {\n folders: result.folders.map((prefix) => ({ name: lastSegment(prefix), prefix })),\n files: result.files.map((entry) => ({\n key: entry.key,\n name: entry.name,\n sizeBytes: entry.sizeBytes,\n lastModified: entry.lastModified ? entry.lastModified.toISOString() : null,\n })),\n ...(result.cursor ? { cursor: result.cursor } : {}),\n };\n }\n\n async objectDetail(disk: string, key: string): Promise<ObjectDetailResponse> {\n const driver = this.diskOrThrow(disk);\n const stat = driver.stat ? await driver.stat(key) : { size: await driver.size(key) };\n const url = driver.capabilities.presign\n ? await driver.temporaryUrl(key, URL_TTL_SECONDS)\n : await driver.url(key);\n return {\n key,\n size: stat.size,\n ...(stat.contentType ? { contentType: stat.contentType } : {}),\n ...(stat.lastModified ? { lastModified: stat.lastModified.toISOString() } : {}),\n url,\n };\n }\n\n async deleteObject(disk: string, key: string): Promise<void> {\n await this.diskOrThrow(disk).delete(key);\n }\n\n async copyObject(disk: string, from: string, to: string): Promise<void> {\n await this.diskOrThrow(disk).copy(from, to);\n }\n\n async moveObject(disk: string, from: string, to: string): Promise<void> {\n await this.diskOrThrow(disk).move(from, to);\n }\n\n async listUploads(filter: { disk?: string; prefix?: string }): Promise<UploadListResponse> {\n if (!this.uploads || typeof this.uploads.list !== 'function') return { uploads: [] };\n const sessions = await this.uploads.list({\n ...(filter.disk ? { disk: filter.disk } : {}),\n ...(filter.prefix ? { keyPrefix: filter.prefix } : {}),\n });\n return { uploads: sessions.map(mapUpload) };\n }\n\n async uploadDetail(id: string): Promise<UploadDetailResponse> {\n if (!this.uploads) throw new NotFoundException('No upload store configured');\n const session = await this.uploads.get(id);\n if (!session) throw new NotFoundException(`Unknown upload: ${id}`);\n const parts = this.uploads.listParts ? await this.uploads.listParts(id) : [];\n return { upload: mapUpload(session), parts };\n }\n\n /**\n * Cancels a resumable session by removing its record from the upload store, so it stops\n * appearing as in-progress. NOTE: this does NOT tear down an underlying native multipart upload\n * (e.g. an S3 multipart) or its temporary parts — the decoupled console resolves only the\n * `UploadSessionStore`, not the `ResumableUploadManager` that owns `abort()`. An incomplete\n * multipart is reaped by the bucket's lifecycle policy. Surfaced as \"Cancel session\" in the UI.\n */\n async abortUpload(id: string): Promise<void> {\n if (!this.uploads) throw new NotFoundException('No upload store configured');\n await this.uploads.delete(id);\n }\n\n async listCollections(): Promise<CollectionsResponse> {\n if (!this.store || typeof this.store.aggregate !== 'function') return { collections: [] };\n const buckets = await this.store.aggregate({ groupBy: 'collection', sum: 'size' });\n return { collections: buckets };\n }\n\n async listLibrary(filter: {\n collection?: string;\n disk?: string;\n cursor?: string;\n limit?: number;\n }): Promise<LibraryListResponse> {\n if (!this.store || typeof this.store.list !== 'function') return { records: [] };\n const page = await this.store.list(\n {\n ...(filter.collection ? { collection: filter.collection } : {}),\n ...(filter.disk ? { disk: filter.disk } : {}),\n },\n {\n limit: filter.limit ?? DEFAULT_PAGE_LIMIT,\n ...(filter.cursor ? { cursor: filter.cursor } : {}),\n },\n );\n return {\n records: page.records.map(mapRecord),\n ...(page.cursor ? { cursor: page.cursor } : {}),\n };\n }\n\n async libraryDetail(id: string): Promise<LibraryDetailResponse> {\n if (!this.store) throw new NotFoundException('No media store configured');\n const record = await this.store.find(id);\n if (!record) throw new NotFoundException(`Unknown media record: ${id}`);\n const variants = await Promise.all(\n Object.entries(record.conversions).map(async ([name, conversion]) => {\n const driver = this.storage?.diskNames().includes(conversion.disk)\n ? this.storage.disk(conversion.disk)\n : null;\n const url = driver?.capabilities.presign\n ? await driver.temporaryUrl(conversion.path, URL_TTL_SECONDS)\n : ((await driver?.url(conversion.path)) ?? '');\n return { name, url };\n }),\n );\n return { record: mapRecord(record), variants };\n }\n\n async deleteLibraryRecord(id: string): Promise<void> {\n if (!this.store) throw new NotFoundException('No media store configured');\n await this.store.delete(id);\n }\n\n topology(): Topology {\n return {\n hasStore: this.store !== null && typeof this.store.list === 'function',\n hasUploads: this.uploads !== null && typeof this.uploads.list === 'function',\n disks: this.storage ? this.storage.diskNames().length : 0,\n actions: this.actionsEnabled === true,\n };\n }\n}\n","// Cross-package by-value tokens. These MUST use the exact same `Symbol.for` keys as\n// `@dudousxd/nestjs-media`'s `tokens.ts`. The global symbol registry guarantees identity across\n// packages, so the console resolves the host-provided values without importing the (heavier,\n// controller-carrying) nestjs package — this package depends only on `-core`. Do not change these\n// keys without changing them there too. Mirrors `@dudousxd/nestjs-media-telescope`'s media-tokens.\n\n/** The configured `MediaStore` (or `null`), provided by `MediaModule`. */\nexport const MEDIA_STORE: symbol = Symbol.for('nestjs-media:store');\n/** The configured `UploadSessionStore` (or `null`), provided by `MediaModule`. */\nexport const MEDIA_UPLOAD_SESSIONS: symbol = Symbol.for('nestjs-media:upload-sessions');\n/** The `StorageManager` (alias of `MediaModule`'s internal `MEDIA_STORAGE` token). */\nexport const MEDIA_STORAGE_SHARED: symbol = Symbol.for('nestjs-media:storage');\n\n/** Carries the resolved UI mount base (e.g. `/media`) to the UI controller. */\nexport const MEDIA_DASHBOARD_BASE_PATH: symbol = Symbol('MEDIA_DASHBOARD_BASE_PATH');\n/** Carries the resolved JSON API base (e.g. `/api/media/console`) the SPA fetches from. */\nexport const MEDIA_DASHBOARD_API_PATH: symbol = Symbol('MEDIA_DASHBOARD_API_PATH');\n/** Carries whether destructive action routes were enabled (`options.actions`). */\nexport const MEDIA_DASHBOARD_ACTIONS: symbol = Symbol('MEDIA_DASHBOARD_ACTIONS');\n","import { Controller, Get, Inject, Param, Query } from '@nestjs/common';\nimport type {\n CollectionsResponse,\n DiskListResponse,\n LibraryDetailResponse,\n LibraryListResponse,\n ObjectDetailResponse,\n ObjectListResponse,\n Topology,\n UploadDetailResponse,\n UploadListResponse,\n} from '../client/types.js';\nimport { MediaConsoleService } from './media-console.service.js';\n\n/** Parse an optional numeric query param; undefined when absent or not a finite number. */\nfunction toLimit(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : undefined;\n}\n\n/**\n * Read-only JSON API for the /media console. Bare `@Controller()` — the path prefix is applied by\n * `RouterModule` (set in `MediaDashboardModule.forRoot({ apiBasePath })`). Always mounted.\n */\n@Controller()\nexport class MediaConsoleReadController {\n constructor(@Inject(MediaConsoleService) private readonly service: MediaConsoleService) {}\n\n @Get('disks')\n disks(): DiskListResponse {\n return this.service.listDisks();\n }\n\n @Get('disks/:disk/objects')\n objects(\n @Param('disk') disk: string,\n @Query('prefix') prefix?: string,\n @Query('cursor') cursor?: string,\n @Query('limit') limit?: string,\n ): Promise<ObjectListResponse> {\n const limitValue = toLimit(limit);\n return this.service.listObjects(disk, {\n ...(prefix ? { prefix } : {}),\n ...(cursor ? { cursor } : {}),\n ...(limitValue !== undefined ? { limit: limitValue } : {}),\n });\n }\n\n @Get('disks/:disk/object')\n object(@Param('disk') disk: string, @Query('key') key: string): Promise<ObjectDetailResponse> {\n return this.service.objectDetail(disk, key);\n }\n\n @Get('uploads')\n uploads(\n @Query('disk') disk?: string,\n @Query('prefix') prefix?: string,\n ): Promise<UploadListResponse> {\n return this.service.listUploads({\n ...(disk ? { disk } : {}),\n ...(prefix ? { prefix } : {}),\n });\n }\n\n @Get('uploads/:id')\n upload(@Param('id') id: string): Promise<UploadDetailResponse> {\n return this.service.uploadDetail(id);\n }\n\n @Get('library/collections')\n collections(): Promise<CollectionsResponse> {\n return this.service.listCollections();\n }\n\n @Get('library')\n library(\n @Query('collection') collection?: string,\n @Query('disk') disk?: string,\n @Query('cursor') cursor?: string,\n @Query('limit') limit?: string,\n ): Promise<LibraryListResponse> {\n const limitValue = toLimit(limit);\n return this.service.listLibrary({\n ...(collection ? { collection } : {}),\n ...(disk ? { disk } : {}),\n ...(cursor ? { cursor } : {}),\n ...(limitValue !== undefined ? { limit: limitValue } : {}),\n });\n }\n\n @Get('library/:id')\n libraryRecord(@Param('id') id: string): Promise<LibraryDetailResponse> {\n return this.service.libraryDetail(id);\n }\n\n @Get('topology')\n topology(): Topology {\n return this.service.topology();\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, extname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n StreamableFile,\n} from '@nestjs/common';\nimport { MEDIA_DASHBOARD_API_PATH, MEDIA_DASHBOARD_BASE_PATH } from './tokens.js';\n\n/** The base the SPA bundle was built with (Vite `base`); rewritten to the configured base at serve time. */\nconst BUILD_BASE = '/media';\n\n/** dist/server/index.js -> ../spa (the Vite build output). */\nfunction spaDir(): string {\n return fileURLToPath(new URL('../spa', import.meta.url));\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json; charset=utf-8',\n '.woff2': 'font/woff2',\n '.ico': 'image/x-icon',\n};\n\n/**\n * Serves the bundled /media console SPA at the configured base (+ hashed assets at `<base>/assets`).\n * The path comes from `RouterModule` (set by `MediaDashboardModule.forRoot({ basePath })`), so the\n * controller routes are relative.\n */\n@Controller()\nexport class MediaDashboardUiController {\n private readonly dir = spaDir();\n\n constructor(\n @Inject(MEDIA_DASHBOARD_BASE_PATH) private readonly basePath: string,\n @Inject(MEDIA_DASHBOARD_API_PATH) private readonly apiBasePath: string,\n ) {}\n\n // index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = \"stuck\n // loading after a deploy\"). The hashed assets below are immutable.\n @Get()\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n index(): string {\n const indexPath = join(this.dir, 'index.html');\n if (!existsSync(indexPath)) {\n throw new NotFoundException('Console SPA is not built. Run the package build.');\n }\n // Built with Vite base `/media/`; rewrite asset URLs to the configured base so the SPA loads\n // from `<base>/assets` wherever it's mounted, and tell the client its API base.\n const html = readFileSync(indexPath, 'utf8').replaceAll(\n `=\"${BUILD_BASE}/`,\n `=\"${this.basePath}/`,\n );\n const inject = `<script>window.__MEDIA_BASE__='${this.basePath}';window.__MEDIA_API__='${this.apiBasePath}';</script>`;\n return html.includes('</head>') ? html.replace('</head>', `${inject}</head>`) : inject + html;\n }\n\n @Get('assets/:file')\n @Header('Cache-Control', 'public, max-age=31536000, immutable')\n asset(@Param('file') file: string): StreamableFile {\n const safe = basename(file);\n if (safe !== file) throw new NotFoundException();\n const root = resolve(this.dir, 'assets');\n const assetPath = resolve(root, safe);\n if (!assetPath.startsWith(root + sep) || !existsSync(assetPath)) {\n throw new NotFoundException();\n }\n const type = CONTENT_TYPES[extname(safe)] ?? 'application/octet-stream';\n return new StreamableFile(readFileSync(assetPath), { type });\n }\n}\n"],"mappings":"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","names":["getImportMetaUrl","document","URL","__filename","href","currentScript","src","baseURI","importMetaUrl","import_common","import_common","import_common","MEDIA_STORE","Symbol","for","MEDIA_UPLOAD_SESSIONS","MEDIA_STORAGE_SHARED","MEDIA_DASHBOARD_BASE_PATH","MEDIA_DASHBOARD_API_PATH","MEDIA_DASHBOARD_ACTIONS","URL_TTL_SECONDS","DEFAULT_PAGE_LIMIT","lastSegment","prefix","trimmed","replace","slash","lastIndexOf","slice","mapUpload","session","percent","size","undefined","Math","min","round","offset","id","disk","key","parts","multipart","multipartUploadId","createdAt","toISOString","mapRecord","record","ownerType","ownerId","collection","name","fileName","mimeType","path","MediaConsoleService","storage","store","uploads","actionsEnabled","diskOrThrow","diskNames","includes","NotFoundException","listDisks","disks","defaultDisk","map","default","capabilities","listObjects","options","driver","result","list","delimiter","cursor","limit","folders","files","entry","sizeBytes","lastModified","objectDetail","stat","url","presign","temporaryUrl","contentType","deleteObject","delete","copyObject","from","to","copy","moveObject","move","listUploads","filter","sessions","keyPrefix","uploadDetail","get","listParts","upload","abortUpload","listCollections","aggregate","collections","buckets","groupBy","sum","listLibrary","records","page","libraryDetail","find","variants","Promise","all","Object","entries","conversions","conversion","deleteLibraryRecord","topology","hasStore","hasUploads","length","actions","MediaConsoleActionsController","service","deleteObject","disk","key","copyObject","body","from","to","moveObject","abortUpload","id","deleteLibraryRecord","import_common","toLimit","value","undefined","parsed","Number","isFinite","Math","floor","MediaConsoleReadController","service","disks","listDisks","objects","disk","prefix","cursor","limit","limitValue","listObjects","object","key","objectDetail","uploads","listUploads","upload","id","uploadDetail","collections","listCollections","library","collection","listLibrary","libraryRecord","libraryDetail","topology","MediaConsoleApiModule","register","actions","module","controllers","MediaConsoleReadController","MediaConsoleActionsController","providers","MediaConsoleService","provide","MEDIA_DASHBOARD_ACTIONS","useValue","exports","import_common","BUILD_BASE","spaDir","fileURLToPath","URL","CONTENT_TYPES","MediaDashboardUiController","dir","basePath","apiBasePath","index","indexPath","join","existsSync","NotFoundException","html","readFileSync","replaceAll","inject","includes","replace","asset","file","safe","basename","root","resolve","assetPath","startsWith","sep","type","extname","StreamableFile","normalize","path","replace","MediaDashboardModule","forRoot","options","basePath","apiBasePath","actions","module","imports","MediaConsoleApiModule","register","RouterModule","controllers","MediaDashboardUiController","providers","provide","MEDIA_DASHBOARD_BASE_PATH","useValue","MEDIA_DASHBOARD_API_PATH","exports"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts","../../../../node_modules/.pnpm/tsup@8.3.5_@swc+core@1.15.41_jiti@1.21.7_postcss@8.4.49_typescript@5.9.3_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js","../../src/server/media-dashboard.module.ts","../../src/server/media-console-api.module.ts","../../src/server/media-console-actions.controller.ts","../../src/server/media-console.service.ts","../../src/server/tokens.ts","../../src/server/media-console-read.controller.ts","../../src/server/media-dashboard-ui.controller.ts"],"sourcesContent":["export { MediaDashboardModule, type MediaDashboardOptions } from './media-dashboard.module.js';\nexport { MediaConsoleApiModule } from './media-console-api.module.js';\nexport { MediaConsoleService } from './media-console.service.js';\nexport {\n MEDIA_STORAGE_SHARED,\n MEDIA_STORE,\n MEDIA_UPLOAD_SESSIONS,\n} from './tokens.js';\n// The API response types (also published at the `./client` entry) for host reuse.\nexport type * from '../client/types.js';\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import { type DynamicModule, Module } from '@nestjs/common';\nimport { RouterModule } from '@nestjs/core';\nimport { MediaConsoleApiModule } from './media-console-api.module.js';\nimport { MediaDashboardUiController } from './media-dashboard-ui.controller.js';\nimport { MEDIA_DASHBOARD_API_PATH, MEDIA_DASHBOARD_BASE_PATH } from './tokens.js';\n\nexport interface MediaDashboardOptions {\n /**\n * Where the SPA (UI) is served. Default `/media`. A page route — keep it out of an `/api`\n * prefix so it reads as a UI, not an endpoint.\n */\n basePath?: string;\n /**\n * Where the JSON API is mounted (what the SPA fetches). Default `<basePath>/api`. Set it under\n * your app's `/api` prefix — e.g. `/api/media/console` — so the API inherits the app's\n * auth/proxy rules while the UI stays at `basePath`.\n */\n apiBasePath?: string;\n /**\n * Enable the destructive endpoints (delete object/record, copy/move, abort upload). Default\n * `false` — the read API is always available. Front the mount with your own guard either way.\n */\n actions?: boolean;\n}\n\n/** Leading slash, no trailing slash. */\nfunction normalize(path: string): string {\n return `/${path.replace(/^\\/+|\\/+$/g, '')}`;\n}\n\n/**\n * Mounts the /media console: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`\n * (default `<basePath>/api`). Import via `MediaDashboardModule.forRoot(...)` alongside\n * `MediaModule` (global), so it resolves the storage/store/upload tokens. No auth is built in —\n * front the routes with a guard, and exclude the UI/API paths from any global `/api` prefix.\n */\n@Module({})\nexport class MediaDashboardModule {\n static forRoot(options: MediaDashboardOptions = {}): DynamicModule {\n const basePath = normalize(options.basePath ?? '/media');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n const actions = options.actions === true;\n return {\n module: MediaDashboardModule,\n imports: [\n MediaConsoleApiModule.register(actions),\n RouterModule.register([\n { path: basePath, module: MediaDashboardModule }, // the UI controller below\n { path: apiBasePath, module: MediaConsoleApiModule },\n ]),\n ],\n controllers: [MediaDashboardUiController],\n providers: [\n { provide: MEDIA_DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: MEDIA_DASHBOARD_API_PATH, useValue: apiBasePath },\n ],\n // Re-export the API module so its MediaConsoleService reaches importers if they want it.\n exports: [MediaConsoleApiModule],\n };\n }\n}\n","import { type DynamicModule, Module } from '@nestjs/common';\nimport { MediaConsoleActionsController } from './media-console-actions.controller.js';\nimport { MediaConsoleReadController } from './media-console-read.controller.js';\nimport { MediaConsoleService } from './media-console.service.js';\nimport { MEDIA_DASHBOARD_ACTIONS } from './tokens.js';\n\n/**\n * Holds the console's JSON API controllers + the read/action service, mounted on its own path by\n * `MediaDashboardModule.forRoot`. The destructive actions controller is only registered when the\n * host opts in via `{ actions: true }`.\n */\n@Module({})\nexport class MediaConsoleApiModule {\n static register(actions: boolean): DynamicModule {\n return {\n module: MediaConsoleApiModule,\n controllers: actions\n ? [MediaConsoleReadController, MediaConsoleActionsController]\n : [MediaConsoleReadController],\n providers: [MediaConsoleService, { provide: MEDIA_DASHBOARD_ACTIONS, useValue: actions }],\n exports: [MediaConsoleService],\n };\n }\n}\n","import type { Readable } from 'node:stream';\nimport {\n Body,\n Controller,\n Delete,\n HttpCode,\n Inject,\n Param,\n Post,\n Query,\n Req,\n} from '@nestjs/common';\nimport { MediaConsoleService } from './media-console.service.js';\n\ninterface CopyMoveBody {\n from: string;\n to: string;\n}\n\ninterface CreateFolderBody {\n prefix: string;\n}\n\n/**\n * Destructive JSON API for the /media console. Only registered when the host opts in with\n * `MediaDashboardModule.forRoot({ actions: true })` (default off). Bare `@Controller()` — the path\n * prefix comes from `RouterModule`, sharing the API base with the read controller.\n */\n@Controller()\nexport class MediaConsoleActionsController {\n constructor(@Inject(MediaConsoleService) private readonly service: MediaConsoleService) {}\n\n @Delete('disks/:disk/object')\n @HttpCode(204)\n deleteObject(@Param('disk') disk: string, @Query('key') key: string): Promise<void> {\n return this.service.deleteObject(disk, key);\n }\n\n @Post('disks/:disk/copy')\n @HttpCode(204)\n copyObject(@Param('disk') disk: string, @Body() body: CopyMoveBody): Promise<void> {\n return this.service.copyObject(disk, body.from, body.to);\n }\n\n @Post('disks/:disk/move')\n @HttpCode(204)\n moveObject(@Param('disk') disk: string, @Body() body: CopyMoveBody): Promise<void> {\n return this.service.moveObject(disk, body.from, body.to);\n }\n\n /**\n * Uploads a file to `key` on the disk. The body is the raw bytes sent as `application/octet-stream`\n * so the host's JSON/urlencoded body parsers never consume the stream; the real MIME rides as the\n * `type` query param and becomes the stored object's Content-Type. `@Req()` is the request stream.\n */\n @Post('disks/:disk/upload')\n @HttpCode(204)\n uploadObject(\n @Param('disk') disk: string,\n @Query('key') key: string,\n @Req() request: Readable,\n @Query('type') type?: string,\n ): Promise<void> {\n return this.service.putObject(disk, key, request, type);\n }\n\n @Post('disks/:disk/folder')\n @HttpCode(204)\n createFolder(@Param('disk') disk: string, @Body() body: CreateFolderBody): Promise<void> {\n return this.service.createFolder(disk, body.prefix);\n }\n\n @Post('uploads/:id/abort')\n @HttpCode(204)\n abortUpload(@Param('id') id: string): Promise<void> {\n return this.service.abortUpload(id);\n }\n\n @Delete('library/:id')\n @HttpCode(204)\n deleteLibraryRecord(@Param('id') id: string): Promise<void> {\n return this.service.deleteLibraryRecord(id);\n }\n}\n","import type { Readable } from 'node:stream';\nimport type {\n MediaRecord,\n MediaStore,\n StorageDriver,\n StorageManager,\n UploadSession,\n UploadSessionStore,\n} from '@dudousxd/nestjs-media-core';\nimport {\n BadRequestException,\n Inject,\n Injectable,\n NotFoundException,\n Optional,\n} from '@nestjs/common';\nimport type {\n CollectionsResponse,\n DiskListResponse,\n LibraryDetailResponse,\n LibraryListResponse,\n LibraryRecord,\n ObjectDetailResponse,\n ObjectListResponse,\n Topology,\n UploadDetailResponse,\n UploadInfo,\n UploadListResponse,\n} from '../client/types.js';\nimport {\n MEDIA_DASHBOARD_ACTIONS,\n MEDIA_STORAGE_SHARED,\n MEDIA_STORE,\n MEDIA_UPLOAD_SESSIONS,\n} from './tokens.js';\n\n/** Seconds a generated preview/download URL stays valid. */\nconst URL_TTL_SECONDS = 300;\nconst DEFAULT_PAGE_LIMIT = 50;\n\n/** Last path segment of a folder prefix, ignoring a trailing slash. */\nfunction lastSegment(prefix: string): string {\n const trimmed = prefix.replace(/\\/+$/, '');\n const slash = trimmed.lastIndexOf('/');\n return slash === -1 ? trimmed : trimmed.slice(slash + 1);\n}\n\nfunction mapUpload(session: UploadSession): UploadInfo {\n const percent =\n session.size !== undefined && session.size > 0\n ? Math.min(100, Math.round((session.offset / session.size) * 100))\n : null;\n return {\n id: session.id,\n disk: session.disk,\n key: session.key,\n offset: session.offset,\n size: session.size ?? null,\n percent,\n parts: session.parts,\n multipart: session.multipartUploadId !== undefined,\n ...(session.createdAt ? { createdAt: session.createdAt.toISOString() } : {}),\n };\n}\n\nfunction mapRecord(record: MediaRecord): LibraryRecord {\n return {\n id: record.id,\n ownerType: record.ownerType,\n ownerId: record.ownerId,\n collection: record.collection,\n name: record.name,\n fileName: record.fileName,\n mimeType: record.mimeType,\n size: record.size,\n disk: record.disk,\n path: record.path,\n createdAt: record.createdAt.toISOString(),\n };\n}\n\n/**\n * Read + action logic behind the console's JSON API. Resolves the three by-value media tokens\n * with `@Optional()` so a host without a `MediaStore`/upload store still boots — every method\n * degrades to an empty shape instead of throwing when its capability is absent.\n */\n@Injectable()\nexport class MediaConsoleService {\n constructor(\n @Optional() @Inject(MEDIA_STORAGE_SHARED) private readonly storage: StorageManager | null,\n @Optional() @Inject(MEDIA_STORE) private readonly store: MediaStore | null,\n @Optional() @Inject(MEDIA_UPLOAD_SESSIONS) private readonly uploads: UploadSessionStore | null,\n @Optional() @Inject(MEDIA_DASHBOARD_ACTIONS) private readonly actionsEnabled: boolean | null,\n ) {}\n\n private diskOrThrow(disk: string): StorageDriver {\n if (!this.storage || !this.storage.diskNames().includes(disk)) {\n throw new NotFoundException(`Unknown disk: ${disk}`);\n }\n return this.storage.disk(disk);\n }\n\n listDisks(): DiskListResponse {\n const storage = this.storage;\n if (!storage) return { disks: [] };\n const defaultDisk = storage.defaultDisk;\n return {\n disks: storage.diskNames().map((name) => ({\n name,\n default: name === defaultDisk,\n capabilities: storage.disk(name).capabilities,\n })),\n };\n }\n\n async listObjects(\n disk: string,\n options: { prefix?: string; cursor?: string; limit?: number },\n ): Promise<ObjectListResponse> {\n const driver = this.diskOrThrow(disk);\n const result = await driver.list(options.prefix ?? '', {\n delimiter: '/',\n ...(options.cursor ? { cursor: options.cursor } : {}),\n limit: options.limit ?? DEFAULT_PAGE_LIMIT,\n });\n return {\n folders: result.folders.map((prefix) => ({ name: lastSegment(prefix), prefix })),\n // Drop the zero-byte \"folder marker\" (a key ending in `/`, whose name is empty after the\n // prefix) that `createFolder` writes — it's the folder itself, not a file inside it.\n files: result.files\n .filter((entry) => entry.name !== '')\n .map((entry) => ({\n key: entry.key,\n name: entry.name,\n sizeBytes: entry.sizeBytes,\n lastModified: entry.lastModified ? entry.lastModified.toISOString() : null,\n })),\n ...(result.cursor ? { cursor: result.cursor } : {}),\n };\n }\n\n async objectDetail(disk: string, key: string): Promise<ObjectDetailResponse> {\n const driver = this.diskOrThrow(disk);\n const stat = driver.stat ? await driver.stat(key) : { size: await driver.size(key) };\n const url = driver.capabilities.presign\n ? await driver.temporaryUrl(key, URL_TTL_SECONDS)\n : await driver.url(key);\n return {\n key,\n size: stat.size,\n ...(stat.contentType ? { contentType: stat.contentType } : {}),\n ...(stat.lastModified ? { lastModified: stat.lastModified.toISOString() } : {}),\n url,\n };\n }\n\n /** The object's raw byte stream plus the metadata the controller needs to serve it inline. Used by\n * the console's same-origin preview proxy so text/PDF render in the browser instead of downloading\n * (and so a CORS-locked bucket is still previewable). */\n async objectStream(\n disk: string,\n key: string,\n ): Promise<{ stream: Readable; contentType: string; size: number }> {\n const driver = this.diskOrThrow(disk);\n const stat = driver.stat ? await driver.stat(key) : { size: await driver.size(key) };\n const stream = await driver.stream(key);\n return { stream, contentType: stat.contentType ?? 'application/octet-stream', size: stat.size };\n }\n\n /** Writes an object from a byte stream (a browser upload). The caller supplies the full key\n * (prefix + filename); an unknown disk 404s via {@link diskOrThrow}. Actions-gated. */\n async putObject(disk: string, key: string, body: Readable, contentType?: string): Promise<void> {\n await this.diskOrThrow(disk).put(key, body, contentType ? { contentType } : undefined);\n }\n\n /** Creates a \"folder\" — a zero-byte marker object at `<prefix>/`, which S3-style listing surfaces\n * as a navigable folder. Normalizes to exactly one trailing slash; rejects an empty prefix. */\n async createFolder(disk: string, prefix: string): Promise<void> {\n const normalized = prefix.replace(/^\\/+/, '').replace(/\\/+$/, '');\n if (normalized === '') throw new BadRequestException('Folder name is required');\n await this.diskOrThrow(disk).put(`${normalized}/`, Buffer.alloc(0));\n }\n\n async deleteObject(disk: string, key: string): Promise<void> {\n await this.diskOrThrow(disk).delete(key);\n }\n\n async copyObject(disk: string, from: string, to: string): Promise<void> {\n await this.diskOrThrow(disk).copy(from, to);\n }\n\n async moveObject(disk: string, from: string, to: string): Promise<void> {\n await this.diskOrThrow(disk).move(from, to);\n }\n\n async listUploads(filter: { disk?: string; prefix?: string }): Promise<UploadListResponse> {\n if (!this.uploads || typeof this.uploads.list !== 'function') return { uploads: [] };\n const sessions = await this.uploads.list({\n ...(filter.disk ? { disk: filter.disk } : {}),\n ...(filter.prefix ? { keyPrefix: filter.prefix } : {}),\n });\n return { uploads: sessions.map(mapUpload) };\n }\n\n async uploadDetail(id: string): Promise<UploadDetailResponse> {\n if (!this.uploads) throw new NotFoundException('No upload store configured');\n const session = await this.uploads.get(id);\n if (!session) throw new NotFoundException(`Unknown upload: ${id}`);\n const parts = this.uploads.listParts ? await this.uploads.listParts(id) : [];\n return { upload: mapUpload(session), parts };\n }\n\n /**\n * Cancels a resumable session by removing its record from the upload store, so it stops\n * appearing as in-progress. NOTE: this does NOT tear down an underlying native multipart upload\n * (e.g. an S3 multipart) or its temporary parts — the decoupled console resolves only the\n * `UploadSessionStore`, not the `ResumableUploadManager` that owns `abort()`. An incomplete\n * multipart is reaped by the bucket's lifecycle policy. Surfaced as \"Cancel session\" in the UI.\n */\n async abortUpload(id: string): Promise<void> {\n if (!this.uploads) throw new NotFoundException('No upload store configured');\n await this.uploads.delete(id);\n }\n\n async listCollections(): Promise<CollectionsResponse> {\n if (!this.store || typeof this.store.aggregate !== 'function') return { collections: [] };\n const buckets = await this.store.aggregate({ groupBy: 'collection', sum: 'size' });\n return { collections: buckets };\n }\n\n async listLibrary(filter: {\n collection?: string;\n disk?: string;\n cursor?: string;\n limit?: number;\n }): Promise<LibraryListResponse> {\n if (!this.store || typeof this.store.list !== 'function') return { records: [] };\n const page = await this.store.list(\n {\n ...(filter.collection ? { collection: filter.collection } : {}),\n ...(filter.disk ? { disk: filter.disk } : {}),\n },\n {\n limit: filter.limit ?? DEFAULT_PAGE_LIMIT,\n ...(filter.cursor ? { cursor: filter.cursor } : {}),\n },\n );\n return {\n records: page.records.map(mapRecord),\n ...(page.cursor ? { cursor: page.cursor } : {}),\n };\n }\n\n async libraryDetail(id: string): Promise<LibraryDetailResponse> {\n if (!this.store) throw new NotFoundException('No media store configured');\n const record = await this.store.find(id);\n if (!record) throw new NotFoundException(`Unknown media record: ${id}`);\n const variants = await Promise.all(\n Object.entries(record.conversions).map(async ([name, conversion]) => {\n const driver = this.storage?.diskNames().includes(conversion.disk)\n ? this.storage.disk(conversion.disk)\n : null;\n const url = driver?.capabilities.presign\n ? await driver.temporaryUrl(conversion.path, URL_TTL_SECONDS)\n : ((await driver?.url(conversion.path)) ?? '');\n return { name, url };\n }),\n );\n return { record: mapRecord(record), variants };\n }\n\n async deleteLibraryRecord(id: string): Promise<void> {\n if (!this.store) throw new NotFoundException('No media store configured');\n await this.store.delete(id);\n }\n\n topology(): Topology {\n return {\n hasStore: this.store !== null && typeof this.store.list === 'function',\n hasUploads: this.uploads !== null && typeof this.uploads.list === 'function',\n disks: this.storage ? this.storage.diskNames().length : 0,\n actions: this.actionsEnabled === true,\n };\n }\n}\n","// Cross-package by-value tokens. These MUST use the exact same `Symbol.for` keys as\n// `@dudousxd/nestjs-media`'s `tokens.ts`. The global symbol registry guarantees identity across\n// packages, so the console resolves the host-provided values without importing the (heavier,\n// controller-carrying) nestjs package — this package depends only on `-core`. Do not change these\n// keys without changing them there too. Mirrors `@dudousxd/nestjs-media-telescope`'s media-tokens.\n\n/** The configured `MediaStore` (or `null`), provided by `MediaModule`. */\nexport const MEDIA_STORE: symbol = Symbol.for('nestjs-media:store');\n/** The configured `UploadSessionStore` (or `null`), provided by `MediaModule`. */\nexport const MEDIA_UPLOAD_SESSIONS: symbol = Symbol.for('nestjs-media:upload-sessions');\n/** The `StorageManager` (alias of `MediaModule`'s internal `MEDIA_STORAGE` token). */\nexport const MEDIA_STORAGE_SHARED: symbol = Symbol.for('nestjs-media:storage');\n\n/** Carries the resolved UI mount base (e.g. `/media`) to the UI controller. */\nexport const MEDIA_DASHBOARD_BASE_PATH: symbol = Symbol('MEDIA_DASHBOARD_BASE_PATH');\n/** Carries the resolved JSON API base (e.g. `/api/media/console`) the SPA fetches from. */\nexport const MEDIA_DASHBOARD_API_PATH: symbol = Symbol('MEDIA_DASHBOARD_API_PATH');\n/** Carries whether destructive action routes were enabled (`options.actions`). */\nexport const MEDIA_DASHBOARD_ACTIONS: symbol = Symbol('MEDIA_DASHBOARD_ACTIONS');\n","import { Controller, Get, Inject, Param, Query, StreamableFile } from '@nestjs/common';\nimport type {\n CollectionsResponse,\n DiskListResponse,\n LibraryDetailResponse,\n LibraryListResponse,\n ObjectDetailResponse,\n ObjectListResponse,\n Topology,\n UploadDetailResponse,\n UploadListResponse,\n} from '../client/types.js';\nimport { MediaConsoleService } from './media-console.service.js';\n\n/** Parse an optional numeric query param; undefined when absent or not a finite number. */\nfunction toLimit(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : undefined;\n}\n\n/**\n * Read-only JSON API for the /media console. Bare `@Controller()` — the path prefix is applied by\n * `RouterModule` (set in `MediaDashboardModule.forRoot({ apiBasePath })`). Always mounted.\n */\n@Controller()\nexport class MediaConsoleReadController {\n constructor(@Inject(MediaConsoleService) private readonly service: MediaConsoleService) {}\n\n @Get('disks')\n disks(): DiskListResponse {\n return this.service.listDisks();\n }\n\n @Get('disks/:disk/objects')\n objects(\n @Param('disk') disk: string,\n @Query('prefix') prefix?: string,\n @Query('cursor') cursor?: string,\n @Query('limit') limit?: string,\n ): Promise<ObjectListResponse> {\n const limitValue = toLimit(limit);\n return this.service.listObjects(disk, {\n ...(prefix ? { prefix } : {}),\n ...(cursor ? { cursor } : {}),\n ...(limitValue !== undefined ? { limit: limitValue } : {}),\n });\n }\n\n @Get('disks/:disk/object')\n object(@Param('disk') disk: string, @Query('key') key: string): Promise<ObjectDetailResponse> {\n return this.service.objectDetail(disk, key);\n }\n\n /** Streams the object's bytes inline (Content-Disposition: inline) from the same origin, so the SPA\n * can render text/PDF previews the browser would otherwise download, and read text past CORS. */\n @Get('disks/:disk/object/raw')\n async objectRaw(@Param('disk') disk: string, @Query('key') key: string): Promise<StreamableFile> {\n const { stream, contentType, size } = await this.service.objectStream(disk, key);\n return new StreamableFile(stream, {\n type: contentType,\n disposition: 'inline',\n ...(Number.isFinite(size) ? { length: size } : {}),\n });\n }\n\n @Get('uploads')\n uploads(\n @Query('disk') disk?: string,\n @Query('prefix') prefix?: string,\n ): Promise<UploadListResponse> {\n return this.service.listUploads({\n ...(disk ? { disk } : {}),\n ...(prefix ? { prefix } : {}),\n });\n }\n\n @Get('uploads/:id')\n upload(@Param('id') id: string): Promise<UploadDetailResponse> {\n return this.service.uploadDetail(id);\n }\n\n @Get('library/collections')\n collections(): Promise<CollectionsResponse> {\n return this.service.listCollections();\n }\n\n @Get('library')\n library(\n @Query('collection') collection?: string,\n @Query('disk') disk?: string,\n @Query('cursor') cursor?: string,\n @Query('limit') limit?: string,\n ): Promise<LibraryListResponse> {\n const limitValue = toLimit(limit);\n return this.service.listLibrary({\n ...(collection ? { collection } : {}),\n ...(disk ? { disk } : {}),\n ...(cursor ? { cursor } : {}),\n ...(limitValue !== undefined ? { limit: limitValue } : {}),\n });\n }\n\n @Get('library/:id')\n libraryRecord(@Param('id') id: string): Promise<LibraryDetailResponse> {\n return this.service.libraryDetail(id);\n }\n\n @Get('topology')\n topology(): Topology {\n return this.service.topology();\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, extname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n StreamableFile,\n} from '@nestjs/common';\nimport { MEDIA_DASHBOARD_API_PATH, MEDIA_DASHBOARD_BASE_PATH } from './tokens.js';\n\n/** The base the SPA bundle was built with (Vite `base`); rewritten to the configured base at serve time. */\nconst BUILD_BASE = '/media';\n\n/** dist/server/index.js -> ../spa (the Vite build output). */\nfunction spaDir(): string {\n return fileURLToPath(new URL('../spa', import.meta.url));\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json; charset=utf-8',\n '.woff2': 'font/woff2',\n '.ico': 'image/x-icon',\n};\n\n/**\n * Serves the bundled /media console SPA at the configured base (+ hashed assets at `<base>/assets`).\n * The path comes from `RouterModule` (set by `MediaDashboardModule.forRoot({ basePath })`), so the\n * controller routes are relative.\n */\n@Controller()\nexport class MediaDashboardUiController {\n private readonly dir = spaDir();\n\n constructor(\n @Inject(MEDIA_DASHBOARD_BASE_PATH) private readonly basePath: string,\n @Inject(MEDIA_DASHBOARD_API_PATH) private readonly apiBasePath: string,\n ) {}\n\n // index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = \"stuck\n // loading after a deploy\"). The hashed assets below are immutable.\n @Get()\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n index(): string {\n const indexPath = join(this.dir, 'index.html');\n if (!existsSync(indexPath)) {\n throw new NotFoundException('Console SPA is not built. Run the package build.');\n }\n // Built with Vite base `/media/`; rewrite asset URLs to the configured base so the SPA loads\n // from `<base>/assets` wherever it's mounted, and tell the client its API base.\n const html = readFileSync(indexPath, 'utf8').replaceAll(\n `=\"${BUILD_BASE}/`,\n `=\"${this.basePath}/`,\n );\n const inject = `<script>window.__MEDIA_BASE__='${this.basePath}';window.__MEDIA_API__='${this.apiBasePath}';</script>`;\n return html.includes('</head>') ? html.replace('</head>', `${inject}</head>`) : inject + html;\n }\n\n @Get('assets/:file')\n @Header('Cache-Control', 'public, max-age=31536000, immutable')\n asset(@Param('file') file: string): StreamableFile {\n const safe = basename(file);\n if (safe !== file) throw new NotFoundException();\n const root = resolve(this.dir, 'assets');\n const assetPath = resolve(root, safe);\n if (!assetPath.startsWith(root + sep) || !existsSync(assetPath)) {\n throw new NotFoundException();\n }\n const type = CONTENT_TYPES[extname(safe)] ?? 'application/octet-stream';\n return new StreamableFile(readFileSync(assetPath), { type });\n }\n}\n"],"mappings":"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","names":["getImportMetaUrl","document","URL","__filename","href","currentScript","src","baseURI","importMetaUrl","import_common","import_common","import_common","MEDIA_STORE","Symbol","for","MEDIA_UPLOAD_SESSIONS","MEDIA_STORAGE_SHARED","MEDIA_DASHBOARD_BASE_PATH","MEDIA_DASHBOARD_API_PATH","MEDIA_DASHBOARD_ACTIONS","URL_TTL_SECONDS","DEFAULT_PAGE_LIMIT","lastSegment","prefix","trimmed","replace","slash","lastIndexOf","slice","mapUpload","session","percent","size","undefined","Math","min","round","offset","id","disk","key","parts","multipart","multipartUploadId","createdAt","toISOString","mapRecord","record","ownerType","ownerId","collection","name","fileName","mimeType","path","MediaConsoleService","storage","store","uploads","actionsEnabled","diskOrThrow","diskNames","includes","NotFoundException","listDisks","disks","defaultDisk","map","default","capabilities","listObjects","options","driver","result","list","delimiter","cursor","limit","folders","files","filter","entry","sizeBytes","lastModified","objectDetail","stat","url","presign","temporaryUrl","contentType","objectStream","stream","putObject","body","put","createFolder","normalized","BadRequestException","Buffer","alloc","deleteObject","delete","copyObject","from","to","copy","moveObject","move","listUploads","sessions","keyPrefix","uploadDetail","get","listParts","upload","abortUpload","listCollections","aggregate","collections","buckets","groupBy","sum","listLibrary","records","page","libraryDetail","find","variants","Promise","all","Object","entries","conversions","conversion","deleteLibraryRecord","topology","hasStore","hasUploads","length","actions","MediaConsoleActionsController","service","deleteObject","disk","key","copyObject","body","from","to","moveObject","uploadObject","request","type","putObject","createFolder","prefix","abortUpload","id","deleteLibraryRecord","import_common","toLimit","value","undefined","parsed","Number","isFinite","Math","floor","MediaConsoleReadController","service","disks","listDisks","objects","disk","prefix","cursor","limit","limitValue","listObjects","object","key","objectDetail","objectRaw","stream","contentType","size","objectStream","StreamableFile","type","disposition","length","uploads","listUploads","upload","id","uploadDetail","collections","listCollections","library","collection","listLibrary","libraryRecord","libraryDetail","topology","MediaConsoleApiModule","register","actions","module","controllers","MediaConsoleReadController","MediaConsoleActionsController","providers","MediaConsoleService","provide","MEDIA_DASHBOARD_ACTIONS","useValue","exports","import_common","BUILD_BASE","spaDir","fileURLToPath","URL","CONTENT_TYPES","MediaDashboardUiController","dir","basePath","apiBasePath","index","indexPath","join","existsSync","NotFoundException","html","readFileSync","replaceAll","inject","includes","replace","asset","file","safe","basename","root","resolve","assetPath","startsWith","sep","type","extname","StreamableFile","normalize","path","replace","MediaDashboardModule","forRoot","options","basePath","apiBasePath","actions","module","imports","MediaConsoleApiModule","register","RouterModule","controllers","MediaDashboardUiController","providers","provide","MEDIA_DASHBOARD_BASE_PATH","useValue","MEDIA_DASHBOARD_API_PATH","exports"]}
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
2
3
|
import { StorageManager, MediaStore, UploadSessionStore } from '@dudousxd/nestjs-media-core';
|
|
3
4
|
|
|
4
5
|
interface MediaDashboardOptions {
|
|
@@ -157,6 +158,20 @@ declare class MediaConsoleService {
|
|
|
157
158
|
limit?: number;
|
|
158
159
|
}): Promise<ObjectListResponse>;
|
|
159
160
|
objectDetail(disk: string, key: string): Promise<ObjectDetailResponse>;
|
|
161
|
+
/** The object's raw byte stream plus the metadata the controller needs to serve it inline. Used by
|
|
162
|
+
* the console's same-origin preview proxy so text/PDF render in the browser instead of downloading
|
|
163
|
+
* (and so a CORS-locked bucket is still previewable). */
|
|
164
|
+
objectStream(disk: string, key: string): Promise<{
|
|
165
|
+
stream: Readable;
|
|
166
|
+
contentType: string;
|
|
167
|
+
size: number;
|
|
168
|
+
}>;
|
|
169
|
+
/** Writes an object from a byte stream (a browser upload). The caller supplies the full key
|
|
170
|
+
* (prefix + filename); an unknown disk 404s via {@link diskOrThrow}. Actions-gated. */
|
|
171
|
+
putObject(disk: string, key: string, body: Readable, contentType?: string): Promise<void>;
|
|
172
|
+
/** Creates a "folder" — a zero-byte marker object at `<prefix>/`, which S3-style listing surfaces
|
|
173
|
+
* as a navigable folder. Normalizes to exactly one trailing slash; rejects an empty prefix. */
|
|
174
|
+
createFolder(disk: string, prefix: string): Promise<void>;
|
|
160
175
|
deleteObject(disk: string, key: string): Promise<void>;
|
|
161
176
|
copyObject(disk: string, from: string, to: string): Promise<void>;
|
|
162
177
|
moveObject(disk: string, from: string, to: string): Promise<void>;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
2
3
|
import { StorageManager, MediaStore, UploadSessionStore } from '@dudousxd/nestjs-media-core';
|
|
3
4
|
|
|
4
5
|
interface MediaDashboardOptions {
|
|
@@ -157,6 +158,20 @@ declare class MediaConsoleService {
|
|
|
157
158
|
limit?: number;
|
|
158
159
|
}): Promise<ObjectListResponse>;
|
|
159
160
|
objectDetail(disk: string, key: string): Promise<ObjectDetailResponse>;
|
|
161
|
+
/** The object's raw byte stream plus the metadata the controller needs to serve it inline. Used by
|
|
162
|
+
* the console's same-origin preview proxy so text/PDF render in the browser instead of downloading
|
|
163
|
+
* (and so a CORS-locked bucket is still previewable). */
|
|
164
|
+
objectStream(disk: string, key: string): Promise<{
|
|
165
|
+
stream: Readable;
|
|
166
|
+
contentType: string;
|
|
167
|
+
size: number;
|
|
168
|
+
}>;
|
|
169
|
+
/** Writes an object from a byte stream (a browser upload). The caller supplies the full key
|
|
170
|
+
* (prefix + filename); an unknown disk 404s via {@link diskOrThrow}. Actions-gated. */
|
|
171
|
+
putObject(disk: string, key: string, body: Readable, contentType?: string): Promise<void>;
|
|
172
|
+
/** Creates a "folder" — a zero-byte marker object at `<prefix>/`, which S3-style listing surfaces
|
|
173
|
+
* as a navigable folder. Normalizes to exactly one trailing slash; rejects an empty prefix. */
|
|
174
|
+
createFolder(disk: string, prefix: string): Promise<void>;
|
|
160
175
|
deleteObject(disk: string, key: string): Promise<void>;
|
|
161
176
|
copyObject(disk: string, from: string, to: string): Promise<void>;
|
|
162
177
|
moveObject(disk: string, from: string, to: string): Promise<void>;
|