@adonis-agora/media 0.10.4 → 0.12.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/CHANGELOG.md +42 -0
- package/README.md +105 -11
- package/dist/assets/spa/assets/index--0MZJr6W.css +1 -0
- package/dist/assets/spa/assets/index-Bo658_9z.js +83 -0
- package/dist/assets/spa/assets/index-Bo658_9z.js.map +1 -0
- package/dist/assets/spa/index.html +20 -0
- package/dist/configure.d.ts +6 -2
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +8 -2
- package/dist/configure.js.map +1 -1
- package/dist/providers/dashboard_provider.d.ts +29 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +520 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/providers/media_provider.d.ts.map +1 -1
- package/dist/providers/media_provider.js +34 -2
- package/dist/providers/media_provider.js.map +1 -1
- package/dist/src/dashboard/auth.d.ts +76 -0
- package/dist/src/dashboard/auth.d.ts.map +1 -0
- package/dist/src/dashboard/auth.js +134 -0
- package/dist/src/dashboard/auth.js.map +1 -0
- package/dist/src/dashboard/cookie.d.ts +20 -0
- package/dist/src/dashboard/cookie.d.ts.map +1 -0
- package/dist/src/dashboard/cookie.js +44 -0
- package/dist/src/dashboard/cookie.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +56 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +5 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +10 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +9 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/dashboard/object_insights.d.ts +42 -0
- package/dist/src/dashboard/object_insights.d.ts.map +1 -0
- package/dist/src/dashboard/object_insights.js +42 -0
- package/dist/src/dashboard/object_insights.js.map +1 -0
- package/dist/src/dashboard/service.d.ts +215 -0
- package/dist/src/dashboard/service.d.ts.map +1 -0
- package/dist/src/dashboard/service.js +470 -0
- package/dist/src/dashboard/service.js.map +1 -0
- package/dist/src/dashboard/spa.d.ts +29 -0
- package/dist/src/dashboard/spa.d.ts.map +1 -0
- package/dist/src/dashboard/spa.js +47 -0
- package/dist/src/dashboard/spa.js.map +1 -0
- package/dist/src/dashboard/types.d.ts +201 -0
- package/dist/src/dashboard/types.d.ts.map +1 -0
- package/dist/src/dashboard/types.js +10 -0
- package/dist/src/dashboard/types.js.map +1 -0
- package/dist/stubs/config/media_dashboard.stub +39 -0
- package/package.json +16 -4
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { isExtendedDisk } from '../extended_disk.js';
|
|
2
|
+
import { sanitizeInsight } from './object_insights.js';
|
|
3
|
+
/** TTL of the signed URLs handed to the browser for object preview/download. */
|
|
4
|
+
export const URL_TTL_SECONDS = 300;
|
|
5
|
+
/** Cap on the get→put streaming path used for cross-disk copy/move (drivers can't copy across disks). */
|
|
6
|
+
export const MAX_TRANSFER_BYTES = 100 * 1024 * 1024;
|
|
7
|
+
/** Page size for the flat sweep that recursive folder delete/copy/move paginates over. */
|
|
8
|
+
export const FOLDER_SWEEP_LIMIT = 1000;
|
|
9
|
+
/** Page size for the client-side scan behind {@link DashboardService.collectionsSummary}. */
|
|
10
|
+
export const COLLECTIONS_SUMMARY_PAGE_SIZE = 200;
|
|
11
|
+
/** Hard cap on records scanned by {@link DashboardService.collectionsSummary} — beyond this the
|
|
12
|
+
* rollup is partial rather than unbounded. */
|
|
13
|
+
export const COLLECTIONS_SUMMARY_SCAN_LIMIT = 5000;
|
|
14
|
+
/** Ceiling for a console (direct) upload — buffered in memory, so bounded to protect the process
|
|
15
|
+
* heap. Larger files belong on the resumable (TUS) upload path, not this convenience upload. */
|
|
16
|
+
export const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
17
|
+
/** Strip leading and trailing slashes — a folder prefix normalized to its bare path (no delimiter). */
|
|
18
|
+
function normalizeFolder(prefix) {
|
|
19
|
+
return prefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
20
|
+
}
|
|
21
|
+
/** An error carrying an HTTP status for the provider to surface verbatim. */
|
|
22
|
+
export class DashboardError extends Error {
|
|
23
|
+
status;
|
|
24
|
+
constructor(message, status) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.name = 'DashboardError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* All dashboard read/action logic, expressed over a structural {@link MediaManagerLike}. It reads the
|
|
32
|
+
* *real* `@adonis-agora/media` surface — disk `list`/`stat`/`getSignedUrl`/`copy`/`move`/`deleteMany`
|
|
33
|
+
* and the resumable `UploadSessionStore.list()` — and never invents its own persistence. The provider
|
|
34
|
+
* is a thin HTTP shell around this; the logic is pure and unit-tested without booting AdonisJS.
|
|
35
|
+
*/
|
|
36
|
+
export class DashboardService {
|
|
37
|
+
manager;
|
|
38
|
+
options;
|
|
39
|
+
insightProviders;
|
|
40
|
+
constructor(manager, options,
|
|
41
|
+
/** Host-registered {@link ObjectInsightProvider}s, from `config/media_dashboard.ts`'s `objectInsights`. */
|
|
42
|
+
insightProviders = []) {
|
|
43
|
+
this.manager = manager;
|
|
44
|
+
this.options = options;
|
|
45
|
+
this.insightProviders = insightProviders;
|
|
46
|
+
}
|
|
47
|
+
/** Coarse capability topology for the SPA to enable/disable affordances. */
|
|
48
|
+
topology() {
|
|
49
|
+
return {
|
|
50
|
+
disks: this.options.diskNames.length,
|
|
51
|
+
hasUploads: this.manager.hasResumable,
|
|
52
|
+
actions: this.options.actions,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Enumerate the exposed disks with their capabilities (non-extended disks report `list:false`). */
|
|
56
|
+
disks() {
|
|
57
|
+
const defaultDisk = this.manager.storage.defaultDisk;
|
|
58
|
+
const disks = this.options.diskNames.map((name) => {
|
|
59
|
+
const disk = this.manager.storage.disk(name);
|
|
60
|
+
const capabilities = isExtendedDisk(disk)
|
|
61
|
+
? disk.capabilities
|
|
62
|
+
: { presign: false, multipart: false, publicUrls: false, list: false };
|
|
63
|
+
return { name, default: name === defaultDisk, capabilities };
|
|
64
|
+
});
|
|
65
|
+
return { disks };
|
|
66
|
+
}
|
|
67
|
+
/** List one page of a bucket under `prefix` (cursor-based, folders rolled up on the `/` delimiter). */
|
|
68
|
+
async objects(diskName, params = {}) {
|
|
69
|
+
const disk = this.extended(diskName);
|
|
70
|
+
const result = await disk.list(params.prefix ?? '', {
|
|
71
|
+
delimiter: '/',
|
|
72
|
+
...(params.cursor !== undefined ? { cursor: params.cursor } : {}),
|
|
73
|
+
...(params.limit !== undefined ? { limit: params.limit } : {}),
|
|
74
|
+
});
|
|
75
|
+
// Drop phantom folders whose name is empty — a CommonPrefix of only slashes (`/`, `//`),
|
|
76
|
+
// produced by a stray key with a leading slash. The S3 driver normalizes such a prefix back
|
|
77
|
+
// to the root, so listing INTO one returns the root again (the phantom included) — a self-
|
|
78
|
+
// reference that infinite-loops the browser (endless re-list of root). Filtering it out removes
|
|
79
|
+
// the trap; those leading-slash keys are unreachable from the console anyway (the driver strips
|
|
80
|
+
// the leading slash).
|
|
81
|
+
const folders = result.folders
|
|
82
|
+
.map((prefix) => ({ name: folderName(prefix), prefix }))
|
|
83
|
+
.filter((folder) => folder.name !== '');
|
|
84
|
+
const files = result.files.map((entry) => ({
|
|
85
|
+
key: entry.key,
|
|
86
|
+
name: entry.name,
|
|
87
|
+
sizeBytes: entry.sizeBytes,
|
|
88
|
+
lastModified: entry.lastModified ? entry.lastModified.toISOString() : null,
|
|
89
|
+
}));
|
|
90
|
+
return { folders, files, ...(result.cursor !== undefined ? { cursor: result.cursor } : {}) };
|
|
91
|
+
}
|
|
92
|
+
/** Object metadata + a short-lived signed URL for preview/download. */
|
|
93
|
+
async object(diskName, key) {
|
|
94
|
+
if (!key)
|
|
95
|
+
throw new DashboardError('key is required', 400);
|
|
96
|
+
const disk = this.manager.storage.disk(diskName);
|
|
97
|
+
const stat = isExtendedDisk(disk) ? await disk.stat(key) : await metaAsStat(disk, key);
|
|
98
|
+
const url = await disk.getSignedUrl(key, { expiresIn: URL_TTL_SECONDS });
|
|
99
|
+
return {
|
|
100
|
+
key,
|
|
101
|
+
size: stat.size,
|
|
102
|
+
...(stat.contentType !== undefined ? { contentType: stat.contentType } : {}),
|
|
103
|
+
...(stat.lastModified !== undefined ? { lastModified: stat.lastModified.toISOString() } : {}),
|
|
104
|
+
url,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The object's raw byte stream plus the metadata the provider needs to serve it inline. Backs the
|
|
109
|
+
* console's same-origin preview proxy so text/PDF render in the browser instead of downloading
|
|
110
|
+
* (and so a CORS-locked bucket is still previewable).
|
|
111
|
+
*/
|
|
112
|
+
async objectStream(diskName, key) {
|
|
113
|
+
if (!key)
|
|
114
|
+
throw new DashboardError('key is required', 400);
|
|
115
|
+
const disk = this.manager.storage.disk(diskName);
|
|
116
|
+
const stat = isExtendedDisk(disk) ? await disk.stat(key) : await metaAsStat(disk, key);
|
|
117
|
+
const stream = await disk.getStream(key);
|
|
118
|
+
return { stream, contentType: stat.contentType ?? 'application/octet-stream', size: stat.size };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* What the HOST knows about this object, from every registered `objectInsights` provider (see
|
|
122
|
+
* `config/media_dashboard.ts`). `{ insights: [] }` when none are registered. A provider that
|
|
123
|
+
* throws is skipped and logged — annotation must never be able to stop an admin opening a file, and
|
|
124
|
+
* providers run concurrently so one host lookup being slow doesn't serialize the others.
|
|
125
|
+
*/
|
|
126
|
+
async objectInsights(diskName, key) {
|
|
127
|
+
if (this.insightProviders.length === 0)
|
|
128
|
+
return { insights: [] };
|
|
129
|
+
// Validates the disk the same way every other object read does, so an unknown disk 404s here too.
|
|
130
|
+
this.manager.storage.disk(diskName);
|
|
131
|
+
const settled = await Promise.all(this.insightProviders.map(async (provider) => {
|
|
132
|
+
try {
|
|
133
|
+
return await provider.resolve({ disk: diskName, key });
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.warn(`[media-dashboard] object-insight provider "${provider.id}" failed for ${diskName}:${key} — ${error instanceof Error ? error.message : String(error)}`);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
return {
|
|
141
|
+
insights: settled
|
|
142
|
+
.filter((insight) => insight !== null)
|
|
143
|
+
.map(sanitizeInsight),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** In-progress resumable uploads, projected from the session store (empty when unconfigured). */
|
|
147
|
+
async uploads(filter = {}) {
|
|
148
|
+
if (!this.manager.hasResumable)
|
|
149
|
+
return { uploads: [] };
|
|
150
|
+
const sessions = await this.manager.resumable.list({
|
|
151
|
+
...(filter.disk !== undefined ? { disk: filter.disk } : {}),
|
|
152
|
+
...(filter.prefix !== undefined ? { keyPrefix: filter.prefix } : {}),
|
|
153
|
+
});
|
|
154
|
+
return { uploads: sessions.map(toUploadInfo) };
|
|
155
|
+
}
|
|
156
|
+
/** Full detail (+ recorded parts) of one resumable upload session. */
|
|
157
|
+
async uploadDetail(id) {
|
|
158
|
+
if (!this.manager.hasResumable)
|
|
159
|
+
throw new DashboardError('no upload store configured', 404);
|
|
160
|
+
const sessions = await this.manager.resumable.list();
|
|
161
|
+
const session = sessions.find((candidate) => candidate.id === id);
|
|
162
|
+
if (!session)
|
|
163
|
+
throw new DashboardError(`unknown upload: ${id}`, 404);
|
|
164
|
+
const parts = await this.manager.resumable.listParts(id);
|
|
165
|
+
return { upload: toUploadInfo(session), parts };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Cancels a resumable session: discards its native multipart upload (or buffered parts) and drops
|
|
169
|
+
* its record, so it stops appearing as in-progress. Actions-gated.
|
|
170
|
+
*/
|
|
171
|
+
async abortUpload(id) {
|
|
172
|
+
this.assertActions();
|
|
173
|
+
if (!this.manager.hasResumable)
|
|
174
|
+
throw new DashboardError('no upload store configured', 404);
|
|
175
|
+
await this.manager.resumable.abort(id);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* One cursor-paginated page of stored media-library records across owners/collections, projected to
|
|
179
|
+
* the SPA's {@link MediaEntry}. Delegates to the real {@link MediaStore.list} — no bespoke query.
|
|
180
|
+
*/
|
|
181
|
+
async collections(params = {}) {
|
|
182
|
+
const page = await this.manager.store.list({
|
|
183
|
+
...(params.collection !== undefined ? { collection: params.collection } : {}),
|
|
184
|
+
...(params.ownerType !== undefined ? { ownerType: params.ownerType } : {}),
|
|
185
|
+
...(params.ownerId !== undefined ? { ownerId: params.ownerId } : {}),
|
|
186
|
+
...(params.prefix !== undefined ? { prefix: params.prefix } : {}),
|
|
187
|
+
...(params.cursor !== undefined ? { cursor: params.cursor } : {}),
|
|
188
|
+
...(params.limit !== undefined ? { limit: params.limit } : {}),
|
|
189
|
+
});
|
|
190
|
+
return { items: page.items.map(toMediaEntry), nextCursor: page.nextCursor };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Per-collection rollup (record count + total bytes) for the console's collection chips.
|
|
194
|
+
* `MediaStore` has no aggregate query, so this walks `list()` in batches and sums client-side —
|
|
195
|
+
* bounded at {@link COLLECTIONS_SUMMARY_SCAN_LIMIT} records so a very large library degrades to a
|
|
196
|
+
* partial (still useful) summary instead of an unbounded scan.
|
|
197
|
+
*/
|
|
198
|
+
async collectionsSummary() {
|
|
199
|
+
const totals = new Map();
|
|
200
|
+
let cursor;
|
|
201
|
+
let scanned = 0;
|
|
202
|
+
do {
|
|
203
|
+
const page = await this.manager.store.list({
|
|
204
|
+
limit: COLLECTIONS_SUMMARY_PAGE_SIZE,
|
|
205
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
206
|
+
});
|
|
207
|
+
for (const record of page.items) {
|
|
208
|
+
const bucket = totals.get(record.collection) ?? { count: 0, sumSize: 0 };
|
|
209
|
+
bucket.count += 1;
|
|
210
|
+
bucket.sumSize += record.size;
|
|
211
|
+
totals.set(record.collection, bucket);
|
|
212
|
+
}
|
|
213
|
+
scanned += page.items.length;
|
|
214
|
+
cursor = page.nextCursor ?? undefined;
|
|
215
|
+
} while (cursor !== undefined && scanned < COLLECTIONS_SUMMARY_SCAN_LIMIT);
|
|
216
|
+
return {
|
|
217
|
+
collections: Array.from(totals.entries()).map(([key, agg]) => ({ key, ...agg })),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Full detail of one stored `MediaRecord`, plus signed URLs for its generated conversions. Only
|
|
222
|
+
* conversions carrying a concrete `{disk, path}` artifact get a link — a metadata-only conversion
|
|
223
|
+
* (probe results with no output file) has nothing to preview.
|
|
224
|
+
*/
|
|
225
|
+
async mediaRecord(id) {
|
|
226
|
+
const record = await this.manager.store.find(id);
|
|
227
|
+
if (!record)
|
|
228
|
+
throw new DashboardError(`unknown media record: ${id}`, 404);
|
|
229
|
+
const linkable = Object.entries(record.conversions).filter((entry) => typeof entry[1].disk === 'string' && typeof entry[1].path === 'string');
|
|
230
|
+
const variants = await Promise.all(linkable.map(async ([name, conversion]) => {
|
|
231
|
+
const disk = this.manager.storage.disk(conversion.disk);
|
|
232
|
+
const url = await disk.getSignedUrl(conversion.path, { expiresIn: URL_TTL_SECONDS });
|
|
233
|
+
return { name, url };
|
|
234
|
+
}));
|
|
235
|
+
return { record: toMediaEntry(record), variants };
|
|
236
|
+
}
|
|
237
|
+
/** Deletes a stored `MediaRecord`. Actions-gated (does not touch its underlying disk object). */
|
|
238
|
+
async deleteMediaRecord(id) {
|
|
239
|
+
this.assertActions();
|
|
240
|
+
const record = await this.manager.store.find(id);
|
|
241
|
+
if (!record)
|
|
242
|
+
throw new DashboardError(`unknown media record: ${id}`, 404);
|
|
243
|
+
await this.manager.store.delete(id);
|
|
244
|
+
}
|
|
245
|
+
/** Server-side copy (same disk) or streamed cross-disk copy. Original is kept. */
|
|
246
|
+
async copy(body) {
|
|
247
|
+
await this.transfer(body, false);
|
|
248
|
+
}
|
|
249
|
+
/** Server-side move (same disk) or streamed cross-disk move. Source is deleted after transfer. */
|
|
250
|
+
async move(body) {
|
|
251
|
+
await this.transfer(body, true);
|
|
252
|
+
}
|
|
253
|
+
/** Batched delete of many keys on one disk. */
|
|
254
|
+
async remove(body) {
|
|
255
|
+
this.assertActions();
|
|
256
|
+
if (body.keys.length === 0)
|
|
257
|
+
return;
|
|
258
|
+
const disk = this.extended(body.disk);
|
|
259
|
+
await disk.deleteMany(body.keys);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Writes an object from a byte stream (a browser upload) — the console's convenience upload,
|
|
263
|
+
* distinct from the resumable TUS path. The caller supplies the full key (prefix + filename).
|
|
264
|
+
* Bounded at {@link MAX_UPLOAD_BYTES} so a runaway upload can't exhaust the process heap. Actions-gated.
|
|
265
|
+
*/
|
|
266
|
+
async putObject(diskName, key, body, contentType) {
|
|
267
|
+
this.assertActions();
|
|
268
|
+
if (!key)
|
|
269
|
+
throw new DashboardError('key is required', 400);
|
|
270
|
+
const disk = this.manager.storage.disk(diskName);
|
|
271
|
+
const chunks = [];
|
|
272
|
+
let total = 0;
|
|
273
|
+
for await (const chunk of body) {
|
|
274
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
275
|
+
total += buffer.length;
|
|
276
|
+
if (total > MAX_UPLOAD_BYTES) {
|
|
277
|
+
throw new DashboardError(`upload exceeds the ${MAX_UPLOAD_BYTES / (1024 * 1024)} MB console limit`, 413);
|
|
278
|
+
}
|
|
279
|
+
chunks.push(buffer);
|
|
280
|
+
}
|
|
281
|
+
await disk.put(key, new Uint8Array(Buffer.concat(chunks)), contentType ? { contentType } : {});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Create a "folder" — a zero-byte marker object at `<prefix>/`, which S3-style delimiter listing
|
|
285
|
+
* surfaces as a navigable folder. Normalizes to exactly one trailing slash (so the key never
|
|
286
|
+
* double-slashes); rejects an empty prefix. Actions-gated.
|
|
287
|
+
*/
|
|
288
|
+
async createFolder(body) {
|
|
289
|
+
this.assertActions();
|
|
290
|
+
const disk = this.extended(body.disk);
|
|
291
|
+
const normalized = normalizeFolder(body.prefix);
|
|
292
|
+
if (normalized === '')
|
|
293
|
+
throw new DashboardError('folder name is required', 400);
|
|
294
|
+
await disk.put(`${normalized}/`, new Uint8Array(0));
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Recursively delete a "folder": every object under `<prefix>/` (nested included) plus the
|
|
298
|
+
* zero-byte marker itself. Sweeps with an EMPTY delimiter so the driver lists keys FLAT — the
|
|
299
|
+
* default `/` delimiter would roll nested keys up into folder prefixes and the sweep would miss
|
|
300
|
+
* them, deleting only direct children. Paginates until the disk reports no more, then deletes the
|
|
301
|
+
* marker explicitly (its key equals the sweep prefix, which `list` filters out). Actions-gated.
|
|
302
|
+
*/
|
|
303
|
+
async deleteFolder(body) {
|
|
304
|
+
this.assertActions();
|
|
305
|
+
const disk = this.extended(body.disk);
|
|
306
|
+
const normalized = normalizeFolder(body.prefix);
|
|
307
|
+
if (normalized === '')
|
|
308
|
+
throw new DashboardError('folder is required', 400);
|
|
309
|
+
const sweepPrefix = `${normalized}/`;
|
|
310
|
+
let cursor;
|
|
311
|
+
do {
|
|
312
|
+
const result = await disk.list(sweepPrefix, {
|
|
313
|
+
delimiter: '',
|
|
314
|
+
limit: FOLDER_SWEEP_LIMIT,
|
|
315
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
316
|
+
});
|
|
317
|
+
if (result.files.length > 0)
|
|
318
|
+
await disk.deleteMany(result.files.map((entry) => entry.key));
|
|
319
|
+
cursor = result.cursor;
|
|
320
|
+
} while (cursor !== undefined);
|
|
321
|
+
await disk.delete(sweepPrefix);
|
|
322
|
+
}
|
|
323
|
+
/** Copy a whole "folder" (every object under `<from>/`, nested included) to `<to>/`, same or cross disk. */
|
|
324
|
+
async copyFolder(body) {
|
|
325
|
+
await this.transferFolder('copy', body);
|
|
326
|
+
}
|
|
327
|
+
/** Move a whole "folder" (every object under `<from>/`, nested included) to `<to>/`, same or cross disk. */
|
|
328
|
+
async moveFolder(body) {
|
|
329
|
+
await this.transferFolder('move', body);
|
|
330
|
+
}
|
|
331
|
+
async transfer(body, remove) {
|
|
332
|
+
this.assertActions();
|
|
333
|
+
if (!body.from || !body.to)
|
|
334
|
+
throw new DashboardError('from and to are required', 400);
|
|
335
|
+
const toDisk = body.toDisk ?? body.disk;
|
|
336
|
+
if (toDisk === body.disk && body.from === body.to)
|
|
337
|
+
throw new DashboardError('source and destination are identical', 400);
|
|
338
|
+
await this.transferKey(remove ? 'move' : 'copy', body.disk, body.from, toDisk, body.to);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Relocate a single object, same disk or across disks. Same-disk uses the driver's native
|
|
342
|
+
* `copy`/`move` (server-side, no bytes through the app). Cross-disk has no driver primitive, so the
|
|
343
|
+
* object is streamed through the app (get→put, capped at {@link MAX_TRANSFER_BYTES}); a `move` then
|
|
344
|
+
* deletes the source. Callers gate actions.
|
|
345
|
+
*/
|
|
346
|
+
async transferKey(op, fromDisk, from, toDisk, to) {
|
|
347
|
+
const source = this.extended(fromDisk);
|
|
348
|
+
if (toDisk === fromDisk) {
|
|
349
|
+
if (op === 'move')
|
|
350
|
+
await source.move(from, to);
|
|
351
|
+
else
|
|
352
|
+
await source.copy(from, to);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const dest = this.manager.storage.disk(toDisk);
|
|
356
|
+
const stat = await source.stat(from);
|
|
357
|
+
if (stat.size > MAX_TRANSFER_BYTES) {
|
|
358
|
+
throw new DashboardError('object too large for cross-disk transfer', 413);
|
|
359
|
+
}
|
|
360
|
+
const bytes = await source.getBytes(from);
|
|
361
|
+
await dest.put(to, bytes, {
|
|
362
|
+
...(stat.contentType !== undefined ? { contentType: stat.contentType } : {}),
|
|
363
|
+
});
|
|
364
|
+
if (op === 'move')
|
|
365
|
+
await source.deleteMany([from]);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Shared engine for {@link copyFolder}/{@link moveFolder}. Same flat-listing sweep as
|
|
369
|
+
* {@link deleteFolder}; each key is relocated via {@link transferKey} (so cross-disk works
|
|
370
|
+
* transparently), preserving its path relative to the source. On the SAME disk, rejects a
|
|
371
|
+
* destination inside the source (which would recurse forever); across disks that can't happen. The
|
|
372
|
+
* destination folder marker is written, and for a move the source marker removed, so both listings
|
|
373
|
+
* stay consistent. Actions-gated.
|
|
374
|
+
*/
|
|
375
|
+
async transferFolder(op, body) {
|
|
376
|
+
this.assertActions();
|
|
377
|
+
const source = this.extended(body.disk);
|
|
378
|
+
const toDisk = body.toDisk ?? body.disk;
|
|
379
|
+
const dest = this.extended(toDisk);
|
|
380
|
+
const fromNormalized = normalizeFolder(body.from);
|
|
381
|
+
const toNormalized = normalizeFolder(body.to);
|
|
382
|
+
if (fromNormalized === '')
|
|
383
|
+
throw new DashboardError('source folder is required', 400);
|
|
384
|
+
if (toNormalized === '')
|
|
385
|
+
throw new DashboardError('destination folder is required', 400);
|
|
386
|
+
const fromPrefix = `${fromNormalized}/`;
|
|
387
|
+
const toPrefix = `${toNormalized}/`;
|
|
388
|
+
if (toDisk === body.disk && toPrefix.startsWith(fromPrefix)) {
|
|
389
|
+
throw new DashboardError(`cannot ${op} a folder into itself`, 400);
|
|
390
|
+
}
|
|
391
|
+
let cursor;
|
|
392
|
+
do {
|
|
393
|
+
const result = await source.list(fromPrefix, {
|
|
394
|
+
delimiter: '',
|
|
395
|
+
limit: FOLDER_SWEEP_LIMIT,
|
|
396
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
397
|
+
});
|
|
398
|
+
for (const entry of result.files) {
|
|
399
|
+
const destKey = `${toPrefix}${entry.key.slice(fromPrefix.length)}`;
|
|
400
|
+
await this.transferKey(op, body.disk, entry.key, toDisk, destKey);
|
|
401
|
+
}
|
|
402
|
+
cursor = result.cursor;
|
|
403
|
+
} while (cursor !== undefined);
|
|
404
|
+
// The marker's key equals the sweep prefix, so the sweep skipped it: write the destination
|
|
405
|
+
// marker, and for a move drop the source one. Idempotent if there was no marker.
|
|
406
|
+
await dest.put(toPrefix, new Uint8Array(0));
|
|
407
|
+
if (op === 'move')
|
|
408
|
+
await source.delete(fromPrefix);
|
|
409
|
+
}
|
|
410
|
+
extended(diskName) {
|
|
411
|
+
const disk = this.manager.storage.disk(diskName);
|
|
412
|
+
if (!isExtendedDisk(disk)) {
|
|
413
|
+
throw new DashboardError(`disk "${diskName}" does not support listing/object operations`, 400);
|
|
414
|
+
}
|
|
415
|
+
return disk;
|
|
416
|
+
}
|
|
417
|
+
assertActions() {
|
|
418
|
+
if (!this.options.actions)
|
|
419
|
+
throw new DashboardError('actions are disabled on this console', 403);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/** `photos/2024/` → `2024`; `` → ``. */
|
|
423
|
+
function folderName(prefix) {
|
|
424
|
+
const trimmed = prefix.replace(/\/+$/, '');
|
|
425
|
+
const idx = trimmed.lastIndexOf('/');
|
|
426
|
+
return idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
427
|
+
}
|
|
428
|
+
/** Project a stored `MediaRecord` to the console's `MediaEntry` (dates → ISO, conversions → names). */
|
|
429
|
+
function toMediaEntry(record) {
|
|
430
|
+
return {
|
|
431
|
+
id: record.id,
|
|
432
|
+
ownerType: record.ownerType,
|
|
433
|
+
ownerId: record.ownerId,
|
|
434
|
+
collection: record.collection,
|
|
435
|
+
name: record.name,
|
|
436
|
+
fileName: record.fileName,
|
|
437
|
+
mimeType: record.mimeType,
|
|
438
|
+
sizeBytes: record.size,
|
|
439
|
+
disk: record.disk,
|
|
440
|
+
path: record.path,
|
|
441
|
+
conversions: Object.keys(record.conversions),
|
|
442
|
+
createdAt: record.createdAt.toISOString(),
|
|
443
|
+
updatedAt: record.updatedAt.toISOString(),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function toUploadInfo(session) {
|
|
447
|
+
const size = session.size ?? null;
|
|
448
|
+
const percent = size && size > 0 ? Math.min(100, Math.round((session.offset / size) * 100)) : null;
|
|
449
|
+
return {
|
|
450
|
+
id: session.id,
|
|
451
|
+
disk: session.disk,
|
|
452
|
+
key: session.key,
|
|
453
|
+
offset: session.offset,
|
|
454
|
+
size,
|
|
455
|
+
percent,
|
|
456
|
+
parts: session.parts,
|
|
457
|
+
multipart: session.multipartUploadId !== undefined,
|
|
458
|
+
...(session.createdAt ? { createdAt: session.createdAt.toISOString() } : {}),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
/** Fallback `stat` for non-extended disks via `getMetaData`. */
|
|
462
|
+
async function metaAsStat(disk, key) {
|
|
463
|
+
const meta = await disk.getMetaData(key);
|
|
464
|
+
return {
|
|
465
|
+
size: meta.contentLength,
|
|
466
|
+
contentType: meta.contentType,
|
|
467
|
+
lastModified: meta.lastModified,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/dashboard/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAMrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAqBvD,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAEnC,yGAAyG;AACzG,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC,6FAA6F;AAC7F,MAAM,CAAC,MAAM,6BAA6B,GAAG,GAAG,CAAC;AAEjD;+CAC+C;AAC/C,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;AAEnD;iGACiG;AACjG,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,uGAAuG;AACvG,SAAS,eAAe,CAAC,MAAc;IACrC,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC;AA6BD,6EAA6E;AAC7E,MAAM,OAAO,cAAe,SAAQ,KAAK;IAG5B;IAFX,YACE,OAAe,EACN,MAAc;QAEvB,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,WAAM,GAAN,MAAM,CAAQ;QAGvB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IAER;IACA;IAEA;IAJnB,YACmB,OAAyB,EACzB,OAAgC;IACjD,2GAA2G;IAC1F,mBAA4C,EAAE;QAH9C,YAAO,GAAP,OAAO,CAAkB;QACzB,YAAO,GAAP,OAAO,CAAyB;QAEhC,qBAAgB,GAAhB,gBAAgB,CAA8B;IAC9D,CAAC;IAEJ,4EAA4E;IAC5E,QAAQ;QACN,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM;YACpC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY;YACrC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO;SAC9B,CAAC;IACJ,CAAC;IAED,oGAAoG;IACpG,KAAK;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC;QACrD,MAAM,KAAK,GAAe,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7C,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC;gBACvC,CAAC,CAAC,IAAI,CAAC,YAAY;gBACnB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YACzE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,WAAW,EAAE,YAAY,EAAE,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,uGAAuG;IACvG,KAAK,CAAC,OAAO,CACX,QAAgB,EAChB,SAA+D,EAAE;QAEjE,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE;YAClD,SAAS,EAAE,GAAG;YACd,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;QACH,yFAAyF;QACzF,4FAA4F;QAC5F,2FAA2F;QAC3F,gGAAgG;QAChG,gGAAgG;QAChG,sBAAsB;QACtB,MAAM,OAAO,GAAmB,MAAM,CAAC,OAAO;aAC3C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;aACvD,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAkB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACxD,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,YAAY,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;SAC3E,CAAC,CAAC,CAAC;QACJ,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/F,CAAC;IAED,uEAAuE;IACvE,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,GAAW;QACxC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,cAAc,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC;QACzE,OAAO;YACL,GAAG;YACH,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,IAAI,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7F,GAAG;SACJ,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAChB,QAAgB,EAChB,GAAW;QAEX,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,cAAc,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,0BAA0B,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAClG,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,cAAc,CAAC,QAAgB,EAAE,GAAW;QAChD,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAChE,kGAAkG;QAClG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAiC,EAAE;YAC1E,IAAI,CAAC;gBACH,OAAO,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YACzD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CACV,8CAA8C,QAAQ,CAAC,EAAE,gBAAgB,QAAQ,IAAI,GAAG,MACtF,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,OAAO;iBACd,MAAM,CAAC,CAAC,OAAO,EAA4B,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC;iBAC/D,GAAG,CAAC,eAAe,CAAC;SACxB,CAAC;IACJ,CAAC;IAED,iGAAiG;IACjG,KAAK,CAAC,OAAO,CAAC,SAA6C,EAAE;QAC3D,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY;YAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrE,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;IACjD,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,YAAY,CAAC,EAAU;QAC3B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY;YAAE,MAAM,IAAI,cAAc,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;QAC5F,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,cAAc,CAAC,mBAAmB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACzD,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;IAClD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,EAAU;QAC1B,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY;YAAE,MAAM,IAAI,cAAc,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;QAC5F,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CACf,SAAiE,EAAE;QAEnE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;YACzC,GAAG,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7E,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IAC9E,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,kBAAkB;QACtB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA8C,CAAC;QACrE,IAAI,MAA0B,CAAC;QAC/B,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,GAAG,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;gBACzC,KAAK,EAAE,6BAA6B;gBACpC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5C,CAAC,CAAC;YACH,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;gBACzE,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;gBAClB,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC;gBAC9B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YACxC,CAAC;YACD,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YAC7B,MAAM,GAAG,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC;QACxC,CAAC,QAAQ,MAAM,KAAK,SAAS,IAAI,OAAO,GAAG,8BAA8B,EAAE;QAC3E,OAAO;YACL,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;SACjF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,EAAU;QAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,cAAc,CAAC,yBAAyB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CACxD,CAAC,KAAK,EAAuE,EAAE,CAC7E,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CACzE,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE;YACxC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC;YACrF,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QACvB,CAAC,CAAC,CACH,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpD,CAAC;IAED,iGAAiG;IACjG,KAAK,CAAC,iBAAiB,CAAC,EAAU;QAChC,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,cAAc,CAAC,yBAAyB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QAC1E,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,kFAAkF;IAClF,KAAK,CAAC,IAAI,CAAC,IAAiE;QAC1E,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,kGAAkG;IAClG,KAAK,CAAC,IAAI,CAAC,IAAiE;QAC1E,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,+CAA+C;IAC/C,KAAK,CAAC,MAAM,CAAC,IAAsC;QACjD,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS,CACb,QAAgB,EAChB,GAAW,EACX,IAA+B,EAC/B,WAAoB;QAEpB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,cAAc,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnE,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC;YACvB,IAAI,KAAK,GAAG,gBAAgB,EAAE,CAAC;gBAC7B,MAAM,IAAI,cAAc,CACtB,sBAAsB,gBAAgB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,mBAAmB,EACzE,GAAG,CACJ,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;QACD,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACjG,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAAC,IAAsC;QACvD,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,UAAU,KAAK,EAAE;YAAE,MAAM,IAAI,cAAc,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QAChF,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,GAAG,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,IAAsC;QACvD,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,UAAU,KAAK,EAAE;YAAE,MAAM,IAAI,cAAc,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QAC3E,MAAM,WAAW,GAAG,GAAG,UAAU,GAAG,CAAC;QACrC,IAAI,MAA0B,CAAC;QAC/B,GAAG,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC1C,SAAS,EAAE,EAAE;gBACb,KAAK,EAAE,kBAAkB;gBACzB,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5C,CAAC,CAAC;YACH,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3F,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACzB,CAAC,QAAQ,MAAM,KAAK,SAAS,EAAE;QAC/B,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACjC,CAAC;IAED,4GAA4G;IAC5G,KAAK,CAAC,UAAU,CAAC,IAKhB;QACC,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED,4GAA4G;IAC5G,KAAK,CAAC,UAAU,CAAC,IAKhB;QACC,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAEO,KAAK,CAAC,QAAQ,CACpB,IAAiE,EACjE,MAAe;QAEf,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,cAAc,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;QACtF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;QACxC,IAAI,MAAM,KAAK,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE;YAC/C,MAAM,IAAI,cAAc,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC;QACxE,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,WAAW,CACvB,EAAmB,EACnB,QAAgB,EAChB,IAAY,EACZ,MAAc,EACd,EAAU;QAEV,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,IAAI,EAAE,KAAK,MAAM;gBAAE,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;;gBAC1C,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACjC,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,GAAG,kBAAkB,EAAE,CAAC;YACnC,MAAM,IAAI,cAAc,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE;YACxB,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7E,CAAC,CAAC;QACH,IAAI,EAAE,KAAK,MAAM;YAAE,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;;;;;;OAOG;IACK,KAAK,CAAC,cAAc,CAC1B,EAAmB,EACnB,IAAiE;QAEjE,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9C,IAAI,cAAc,KAAK,EAAE;YAAE,MAAM,IAAI,cAAc,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAC;QACtF,IAAI,YAAY,KAAK,EAAE;YAAE,MAAM,IAAI,cAAc,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC;QACzF,MAAM,UAAU,GAAG,GAAG,cAAc,GAAG,CAAC;QACxC,MAAM,QAAQ,GAAG,GAAG,YAAY,GAAG,CAAC;QACpC,IAAI,MAAM,KAAK,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,cAAc,CAAC,UAAU,EAAE,uBAAuB,EAAE,GAAG,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,MAA0B,CAAC;QAC/B,GAAG,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE;gBAC3C,SAAS,EAAE,EAAE;gBACb,KAAK,EAAE,kBAAkB;gBACzB,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5C,CAAC,CAAC;YACH,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,GAAG,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnE,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACpE,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACzB,CAAC,QAAQ,MAAM,KAAK,SAAS,EAAE;QAC/B,2FAA2F;QAC3F,iFAAiF;QACjF,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,IAAI,EAAE,KAAK,MAAM;YAAE,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACrD,CAAC;IAEO,QAAQ,CAAC,QAAgB;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,cAAc,CACtB,SAAS,QAAQ,8CAA8C,EAC/D,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,aAAa;QACnB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO;YACvB,MAAM,IAAI,cAAc,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC;IAC1E,CAAC;CACF;AAED,wCAAwC;AACxC,SAAS,UAAU,CAAC,MAAc;IAChC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,uGAAuG;AACvG,SAAS,YAAY,CAAC,MAAmB;IACvC,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,SAAS,EAAE,MAAM,CAAC,IAAI;QACtB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;QAC5C,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE;QACzC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE;KAC1C,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,OAAsB;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;IAClC,MAAM,OAAO,GACX,IAAI,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrF,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI;QACJ,OAAO;QACP,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,OAAO,CAAC,iBAAiB,KAAK,SAAS;QAClD,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,KAAK,UAAU,UAAU,CAAC,IAAU,EAAE,GAAW;IAC/C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,aAAa;QACxB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;KAChC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for mounting/serving the `@adonis-agora/media-dashboard` React SPA — split out so
|
|
3
|
+
* they're unit-testable without booting AdonisJS. `dashboard_provider.ts` is a thin HTTP shell around
|
|
4
|
+
* these. Ported byte-for-byte (same placeholder, same content-type table, same `renderIndexHtml`
|
|
5
|
+
* contract) from `@adonis-agora/media-dashboard`'s own `provider/serve.ts`, which is now this
|
|
6
|
+
* package's copy of record — mirrors `@adonis-agora/durable`'s `src/dashboard/spa.ts`, so the two
|
|
7
|
+
* packages' "serve a Vite SPA from an AdonisJS provider" story stays one pattern across the Agora
|
|
8
|
+
* ecosystem.
|
|
9
|
+
*/
|
|
10
|
+
/** Placeholder base Vite bakes into asset URLs (`packages/dashboard/vite.config.ts`); rewritten to
|
|
11
|
+
* the configured `basePath` at serve time — the SAME built bundle mounts at any path with no rebuild. */
|
|
12
|
+
export declare const BASE_PLACEHOLDER = "/__MEDIA_DASHBOARD__/";
|
|
13
|
+
export declare const CONTENT_TYPES: Record<string, string>;
|
|
14
|
+
export interface DashboardBootstrap {
|
|
15
|
+
apiBase: string;
|
|
16
|
+
uploadsBase: string;
|
|
17
|
+
tusBase: string;
|
|
18
|
+
actions: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Normalise a mount path: leading slash, collapsed slashes, no trailing slash (root stays `/`). */
|
|
21
|
+
export declare function normalizePath(path: string): string;
|
|
22
|
+
/** Content type for a served asset filename, defaulting to octet-stream. */
|
|
23
|
+
export declare function contentTypeFor(file: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Rewrite the built `index.html` for serving: point Vite's placeholder base at `basePath` and inject
|
|
26
|
+
* the runtime bootstrap the SPA reads from `window.__MEDIA_DASHBOARD__`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderIndexHtml(html: string, basePath: string, bootstrap: DashboardBootstrap): string;
|
|
29
|
+
//# sourceMappingURL=spa.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spa.d.ts","sourceRoot":"","sources":["../../../src/dashboard/spa.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;0GAC0G;AAC1G,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAchD,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,kBAAkB,GAC5B,MAAM,CAIR"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for mounting/serving the `@adonis-agora/media-dashboard` React SPA — split out so
|
|
3
|
+
* they're unit-testable without booting AdonisJS. `dashboard_provider.ts` is a thin HTTP shell around
|
|
4
|
+
* these. Ported byte-for-byte (same placeholder, same content-type table, same `renderIndexHtml`
|
|
5
|
+
* contract) from `@adonis-agora/media-dashboard`'s own `provider/serve.ts`, which is now this
|
|
6
|
+
* package's copy of record — mirrors `@adonis-agora/durable`'s `src/dashboard/spa.ts`, so the two
|
|
7
|
+
* packages' "serve a Vite SPA from an AdonisJS provider" story stays one pattern across the Agora
|
|
8
|
+
* ecosystem.
|
|
9
|
+
*/
|
|
10
|
+
/** Placeholder base Vite bakes into asset URLs (`packages/dashboard/vite.config.ts`); rewritten to
|
|
11
|
+
* the configured `basePath` at serve time — the SAME built bundle mounts at any path with no rebuild. */
|
|
12
|
+
export const BASE_PLACEHOLDER = '/__MEDIA_DASHBOARD__/';
|
|
13
|
+
export const CONTENT_TYPES = {
|
|
14
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
15
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
16
|
+
'.css': 'text/css; charset=utf-8',
|
|
17
|
+
'.map': 'application/json; charset=utf-8',
|
|
18
|
+
'.json': 'application/json; charset=utf-8',
|
|
19
|
+
'.svg': 'image/svg+xml',
|
|
20
|
+
'.png': 'image/png',
|
|
21
|
+
'.jpg': 'image/jpeg',
|
|
22
|
+
'.jpeg': 'image/jpeg',
|
|
23
|
+
'.gif': 'image/gif',
|
|
24
|
+
'.ico': 'image/x-icon',
|
|
25
|
+
'.woff2': 'font/woff2',
|
|
26
|
+
'.woff': 'font/woff',
|
|
27
|
+
};
|
|
28
|
+
/** Normalise a mount path: leading slash, collapsed slashes, no trailing slash (root stays `/`). */
|
|
29
|
+
export function normalizePath(path) {
|
|
30
|
+
const trimmed = `/${path}`.replace(/\/+/g, '/').replace(/\/+$/, '');
|
|
31
|
+
return trimmed === '' ? '/' : trimmed;
|
|
32
|
+
}
|
|
33
|
+
/** Content type for a served asset filename, defaulting to octet-stream. */
|
|
34
|
+
export function contentTypeFor(file) {
|
|
35
|
+
const ext = file.slice(file.lastIndexOf('.'));
|
|
36
|
+
return CONTENT_TYPES[ext] ?? 'application/octet-stream';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Rewrite the built `index.html` for serving: point Vite's placeholder base at `basePath` and inject
|
|
40
|
+
* the runtime bootstrap the SPA reads from `window.__MEDIA_DASHBOARD__`.
|
|
41
|
+
*/
|
|
42
|
+
export function renderIndexHtml(html, basePath, bootstrap) {
|
|
43
|
+
const based = html.split(BASE_PLACEHOLDER).join(`${basePath === '/' ? '' : basePath}/`);
|
|
44
|
+
const inject = `<script>window.__MEDIA_DASHBOARD__=${JSON.stringify(bootstrap)}</script>`;
|
|
45
|
+
return based.replace('</head>', `${inject}</head>`);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=spa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spa.js","sourceRoot":"","sources":["../../../src/dashboard/spa.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;0GAC0G;AAC1G,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD,KAAK,EAAE,gCAAgC;IACvC,MAAM,EAAE,gCAAgC;IACxC,MAAM,EAAE,yBAAyB;IACjC,MAAM,EAAE,iCAAiC;IACzC,OAAO,EAAE,iCAAiC;IAC1C,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,cAAc;IACtB,QAAQ,EAAE,YAAY;IACtB,OAAO,EAAE,WAAW;CACrB,CAAC;AASF,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACpE,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AACxC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9C,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,QAAgB,EAChB,SAA6B;IAE7B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,sCAAsC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,CAAC;IAC1F,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,MAAM,SAAS,CAAC,CAAC;AACtD,CAAC"}
|