@payloadcms/plugin-cloud-storage 3.91.0-internal.c1569dc → 4.0.0-beta.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/LICENSE.md +1 -1
- package/README.md +4 -4
- package/dist/client/createClientUploadHandler.d.ts +8 -7
- package/dist/client/createClientUploadHandler.d.ts.map +1 -1
- package/dist/client/createClientUploadHandler.js +26 -22
- package/dist/client/createClientUploadHandler.js.map +1 -1
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/utilities.d.ts +2 -4
- package/dist/exports/utilities.d.ts.map +1 -1
- package/dist/exports/utilities.js +2 -4
- package/dist/exports/utilities.js.map +1 -1
- package/dist/fields/getFields.d.ts +1 -5
- package/dist/fields/getFields.d.ts.map +1 -1
- package/dist/fields/getFields.js +102 -35
- package/dist/fields/getFields.js.map +1 -1
- package/dist/hooks/afterChange.d.ts +3 -1
- package/dist/hooks/afterChange.d.ts.map +1 -1
- package/dist/hooks/afterChange.js +93 -27
- package/dist/hooks/afterChange.js.map +1 -1
- package/dist/hooks/afterDelete.d.ts.map +1 -1
- package/dist/hooks/afterDelete.js +10 -2
- package/dist/hooks/afterDelete.js.map +1 -1
- package/dist/hooks/afterRead.d.ts +6 -1
- package/dist/hooks/afterRead.d.ts.map +1 -1
- package/dist/hooks/afterRead.js +27 -11
- package/dist/hooks/afterRead.js.map +1 -1
- package/dist/hooks/beforeChange.d.ts +2 -1
- package/dist/hooks/beforeChange.d.ts.map +1 -1
- package/dist/hooks/beforeChange.js +14 -9
- package/dist/hooks/beforeChange.js.map +1 -1
- package/dist/hooks/publicOriginalURL.d.ts +8 -0
- package/dist/hooks/publicOriginalURL.d.ts.map +1 -0
- package/dist/hooks/publicOriginalURL.js +34 -0
- package/dist/hooks/publicOriginalURL.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +198 -133
- package/dist/plugin.js.map +1 -1
- package/dist/types.d.ts +48 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities/buildStoragePathData.d.ts +0 -6
- package/dist/utilities/buildStoragePathData.d.ts.map +1 -1
- package/dist/utilities/buildStoragePathData.js +0 -2
- package/dist/utilities/buildStoragePathData.js.map +1 -1
- package/dist/utilities/createFileOperations.d.ts +13 -0
- package/dist/utilities/createFileOperations.d.ts.map +1 -0
- package/dist/utilities/createFileOperations.js +258 -0
- package/dist/utilities/createFileOperations.js.map +1 -0
- package/dist/utilities/getFilePrefix.d.ts +9 -20
- package/dist/utilities/getFilePrefix.d.ts.map +1 -1
- package/dist/utilities/getFilePrefix.js +42 -31
- package/dist/utilities/getFilePrefix.js.map +1 -1
- package/dist/utilities/getIncomingFiles.js +4 -4
- package/dist/utilities/getIncomingFiles.js.map +1 -1
- package/dist/utilities/getStorageLocationSelect.d.ts +9 -0
- package/dist/utilities/getStorageLocationSelect.d.ts.map +1 -0
- package/dist/utilities/getStorageLocationSelect.js +54 -0
- package/dist/utilities/getStorageLocationSelect.js.map +1 -0
- package/dist/utilities/resolveSignedURLKey.d.ts +10 -10
- package/dist/utilities/resolveSignedURLKey.d.ts.map +1 -1
- package/dist/utilities/resolveSignedURLKey.js +33 -26
- package/dist/utilities/resolveSignedURLKey.js.map +1 -1
- package/dist/utilities/sanitizePrefix.d.ts +1 -9
- package/dist/utilities/sanitizePrefix.d.ts.map +1 -1
- package/dist/utilities/sanitizePrefix.js +1 -21
- package/dist/utilities/sanitizePrefix.js.map +1 -1
- package/package.json +6 -6
- package/dist/admin/fields/getFields.d.ts +0 -17
- package/dist/admin/fields/getFields.d.ts.map +0 -1
- package/dist/admin/fields/getFields.js +0 -98
- package/dist/admin/fields/getFields.js.map +0 -1
- package/dist/admin/index.d.ts +0 -4
- package/dist/admin/index.d.ts.map +0 -1
- package/dist/admin/index.js +0 -34
- package/dist/admin/index.js.map +0 -1
- package/dist/hooks/afterChange.spec.js +0 -150
- package/dist/hooks/afterChange.spec.js.map +0 -1
- package/dist/hooks/normalizeUploadPrefix.spec.js +0 -154
- package/dist/hooks/normalizeUploadPrefix.spec.js.map +0 -1
- package/dist/plugin.spec.js +0 -186
- package/dist/plugin.spec.js.map +0 -1
- package/dist/utilities/assertReceiptMatchesFileKey.d.ts +0 -15
- package/dist/utilities/assertReceiptMatchesFileKey.d.ts.map +0 -1
- package/dist/utilities/assertReceiptMatchesFileKey.js +0 -31
- package/dist/utilities/assertReceiptMatchesFileKey.js.map +0 -1
- package/dist/utilities/buildStoragePathData.spec.js +0 -254
- package/dist/utilities/buildStoragePathData.spec.js.map +0 -1
- package/dist/utilities/getFilePrefix.spec.js +0 -291
- package/dist/utilities/getFilePrefix.spec.js.map +0 -1
- package/dist/utilities/initClientUploads.d.ts +0 -13
- package/dist/utilities/initClientUploads.d.ts.map +0 -1
- package/dist/utilities/initClientUploads.js +0 -66
- package/dist/utilities/initClientUploads.js.map +0 -1
- package/dist/utilities/initClientUploads.spec.js +0 -31
- package/dist/utilities/initClientUploads.spec.js.map +0 -1
- package/dist/utilities/resolveSignedURLKey.spec.js +0 -56
- package/dist/utilities/resolveSignedURLKey.spec.js.map +0 -1
- package/dist/utilities/verifyClientUploadReceiptForFileKey.d.ts +0 -20
- package/dist/utilities/verifyClientUploadReceiptForFileKey.d.ts.map +0 -1
- package/dist/utilities/verifyClientUploadReceiptForFileKey.js +0 -24
- package/dist/utilities/verifyClientUploadReceiptForFileKey.js.map +0 -1
- package/dist/utilities/verifyClientUploadReceiptForFileKey.spec.js +0 -108
- package/dist/utilities/verifyClientUploadReceiptForFileKey.spec.js.map +0 -1
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
export const initClientUploads = ({ clientHandler, collections, config, enabled, extraClientHandlerProps, serverHandler, serverHandlerPath })=>{
|
|
2
|
-
if (enabled) {
|
|
3
|
-
if (!config.endpoints) {
|
|
4
|
-
config.endpoints = [];
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Tracks how many times the same handler was already applied.
|
|
8
|
-
* This allows to apply the same plugin multiple times, for example
|
|
9
|
-
* to use different buckets for different collections.
|
|
10
|
-
*/ let handlerCount = 0;
|
|
11
|
-
for (const endpoint of config.endpoints){
|
|
12
|
-
// We want to match on 'path', 'path-1', 'path-2', etc.
|
|
13
|
-
if (endpoint.path?.startsWith(serverHandlerPath)) {
|
|
14
|
-
handlerCount++;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
if (handlerCount) {
|
|
18
|
-
serverHandlerPath = `${serverHandlerPath}-${handlerCount}`;
|
|
19
|
-
}
|
|
20
|
-
config.endpoints.push({
|
|
21
|
-
handler: serverHandler,
|
|
22
|
-
method: 'post',
|
|
23
|
-
path: serverHandlerPath
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
if (!config.admin) {
|
|
27
|
-
config.admin = {};
|
|
28
|
-
}
|
|
29
|
-
if (!config.admin.dependencies) {
|
|
30
|
-
config.admin.dependencies = {};
|
|
31
|
-
}
|
|
32
|
-
// Ensure client handler is always part of the import map, to avoid
|
|
33
|
-
// import map discrepancies between dev and prod
|
|
34
|
-
config.admin.dependencies[clientHandler] = {
|
|
35
|
-
type: 'function',
|
|
36
|
-
path: clientHandler
|
|
37
|
-
};
|
|
38
|
-
if (!enabled) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (!config.admin.components) {
|
|
42
|
-
config.admin.components = {};
|
|
43
|
-
}
|
|
44
|
-
if (!config.admin.components.providers) {
|
|
45
|
-
config.admin.components.providers = [];
|
|
46
|
-
}
|
|
47
|
-
for(const collectionSlug in collections){
|
|
48
|
-
const collection = collections[collectionSlug];
|
|
49
|
-
let collectionPrefix;
|
|
50
|
-
if (collection && typeof collection === 'object' && 'prefix' in collection && typeof collection.prefix === 'string') {
|
|
51
|
-
collectionPrefix = collection.prefix;
|
|
52
|
-
}
|
|
53
|
-
config.admin.components.providers.push({
|
|
54
|
-
clientProps: {
|
|
55
|
-
collectionSlug,
|
|
56
|
-
enabled,
|
|
57
|
-
extra: extraClientHandlerProps ? extraClientHandlerProps(collection) : undefined,
|
|
58
|
-
prefix: collectionPrefix,
|
|
59
|
-
serverHandlerPath
|
|
60
|
-
},
|
|
61
|
-
path: clientHandler
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
//# sourceMappingURL=initClientUploads.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/initClientUploads.ts"],"sourcesContent":["import type { Config, PayloadHandler } from 'payload'\n\nexport const initClientUploads = <ExtraProps extends Record<string, unknown>, T>({\n clientHandler,\n collections,\n config,\n enabled,\n extraClientHandlerProps,\n serverHandler,\n serverHandlerPath,\n}: {\n /** Path to clientHandler component */\n clientHandler: string\n collections: Record<string, T>\n config: Config\n enabled: boolean\n /** extra props to pass to the client handler */\n extraClientHandlerProps?: (collection: T) => ExtraProps\n serverHandler: PayloadHandler\n serverHandlerPath: string\n}) => {\n if (enabled) {\n if (!config.endpoints) {\n config.endpoints = []\n }\n\n /**\n * Tracks how many times the same handler was already applied.\n * This allows to apply the same plugin multiple times, for example\n * to use different buckets for different collections.\n */\n let handlerCount = 0\n\n for (const endpoint of config.endpoints) {\n // We want to match on 'path', 'path-1', 'path-2', etc.\n if (endpoint.path?.startsWith(serverHandlerPath)) {\n handlerCount++\n }\n }\n\n if (handlerCount) {\n serverHandlerPath = `${serverHandlerPath}-${handlerCount}`\n }\n\n config.endpoints.push({\n handler: serverHandler,\n method: 'post',\n path: serverHandlerPath,\n })\n }\n\n if (!config.admin) {\n config.admin = {}\n }\n\n if (!config.admin.dependencies) {\n config.admin.dependencies = {}\n }\n // Ensure client handler is always part of the import map, to avoid\n // import map discrepancies between dev and prod\n config.admin.dependencies[clientHandler] = {\n type: 'function',\n path: clientHandler,\n }\n\n if (!enabled) {\n return\n }\n\n if (!config.admin.components) {\n config.admin.components = {}\n }\n\n if (!config.admin.components.providers) {\n config.admin.components.providers = []\n }\n\n for (const collectionSlug in collections) {\n const collection = collections[collectionSlug]\n\n let collectionPrefix: string | undefined\n\n if (\n collection &&\n typeof collection === 'object' &&\n 'prefix' in collection &&\n typeof collection.prefix === 'string'\n ) {\n collectionPrefix = collection.prefix\n }\n\n config.admin.components.providers.push({\n clientProps: {\n collectionSlug,\n enabled,\n extra: extraClientHandlerProps ? extraClientHandlerProps(collection!) : undefined,\n prefix: collectionPrefix,\n serverHandlerPath,\n },\n path: clientHandler,\n })\n }\n}\n"],"names":["initClientUploads","clientHandler","collections","config","enabled","extraClientHandlerProps","serverHandler","serverHandlerPath","endpoints","handlerCount","endpoint","path","startsWith","push","handler","method","admin","dependencies","type","components","providers","collectionSlug","collection","collectionPrefix","prefix","clientProps","extra","undefined"],"mappings":"AAEA,OAAO,MAAMA,oBAAoB,CAAgD,EAC/EC,aAAa,EACbC,WAAW,EACXC,MAAM,EACNC,OAAO,EACPC,uBAAuB,EACvBC,aAAa,EACbC,iBAAiB,EAWlB;IACC,IAAIH,SAAS;QACX,IAAI,CAACD,OAAOK,SAAS,EAAE;YACrBL,OAAOK,SAAS,GAAG,EAAE;QACvB;QAEA;;;;KAIC,GACD,IAAIC,eAAe;QAEnB,KAAK,MAAMC,YAAYP,OAAOK,SAAS,CAAE;YACvC,uDAAuD;YACvD,IAAIE,SAASC,IAAI,EAAEC,WAAWL,oBAAoB;gBAChDE;YACF;QACF;QAEA,IAAIA,cAAc;YAChBF,oBAAoB,GAAGA,kBAAkB,CAAC,EAAEE,cAAc;QAC5D;QAEAN,OAAOK,SAAS,CAACK,IAAI,CAAC;YACpBC,SAASR;YACTS,QAAQ;YACRJ,MAAMJ;QACR;IACF;IAEA,IAAI,CAACJ,OAAOa,KAAK,EAAE;QACjBb,OAAOa,KAAK,GAAG,CAAC;IAClB;IAEA,IAAI,CAACb,OAAOa,KAAK,CAACC,YAAY,EAAE;QAC9Bd,OAAOa,KAAK,CAACC,YAAY,GAAG,CAAC;IAC/B;IACA,mEAAmE;IACnE,gDAAgD;IAChDd,OAAOa,KAAK,CAACC,YAAY,CAAChB,cAAc,GAAG;QACzCiB,MAAM;QACNP,MAAMV;IACR;IAEA,IAAI,CAACG,SAAS;QACZ;IACF;IAEA,IAAI,CAACD,OAAOa,KAAK,CAACG,UAAU,EAAE;QAC5BhB,OAAOa,KAAK,CAACG,UAAU,GAAG,CAAC;IAC7B;IAEA,IAAI,CAAChB,OAAOa,KAAK,CAACG,UAAU,CAACC,SAAS,EAAE;QACtCjB,OAAOa,KAAK,CAACG,UAAU,CAACC,SAAS,GAAG,EAAE;IACxC;IAEA,IAAK,MAAMC,kBAAkBnB,YAAa;QACxC,MAAMoB,aAAapB,WAAW,CAACmB,eAAe;QAE9C,IAAIE;QAEJ,IACED,cACA,OAAOA,eAAe,YACtB,YAAYA,cACZ,OAAOA,WAAWE,MAAM,KAAK,UAC7B;YACAD,mBAAmBD,WAAWE,MAAM;QACtC;QAEArB,OAAOa,KAAK,CAACG,UAAU,CAACC,SAAS,CAACP,IAAI,CAAC;YACrCY,aAAa;gBACXJ;gBACAjB;gBACAsB,OAAOrB,0BAA0BA,wBAAwBiB,cAAeK;gBACxEH,QAAQD;gBACRhB;YACF;YACAI,MAAMV;QACR;IACF;AACF,EAAC"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { initClientUploads } from './initClientUploads.js';
|
|
3
|
-
describe('initClientUploads', ()=>{
|
|
4
|
-
it('does not add Admin providers when client uploads are disabled', ()=>{
|
|
5
|
-
const clientHandler = '@payloadcms/storage-s3/client#S3ClientUploadHandler';
|
|
6
|
-
const collections = Object.fromEntries(Array.from({
|
|
7
|
-
length: 50
|
|
8
|
-
}, (_, index)=>[
|
|
9
|
-
`upload-${index}`,
|
|
10
|
-
true
|
|
11
|
-
]));
|
|
12
|
-
const config = {};
|
|
13
|
-
initClientUploads({
|
|
14
|
-
clientHandler,
|
|
15
|
-
collections,
|
|
16
|
-
config,
|
|
17
|
-
enabled: false,
|
|
18
|
-
serverHandler: vi.fn(),
|
|
19
|
-
serverHandlerPath: '/storage-s3-generate-signed-url'
|
|
20
|
-
});
|
|
21
|
-
expect(config.admin?.dependencies).toEqual({
|
|
22
|
-
[clientHandler]: {
|
|
23
|
-
path: clientHandler,
|
|
24
|
-
type: 'function'
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
expect(config.admin?.components?.providers).toBeUndefined();
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
//# sourceMappingURL=initClientUploads.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/initClientUploads.spec.ts"],"sourcesContent":["import type { Config, PayloadHandler } from 'payload'\n\nimport { describe, expect, it, vi } from 'vitest'\n\nimport { initClientUploads } from './initClientUploads.js'\n\ndescribe('initClientUploads', () => {\n it('does not add Admin providers when client uploads are disabled', () => {\n const clientHandler = '@payloadcms/storage-s3/client#S3ClientUploadHandler'\n const collections = Object.fromEntries(\n Array.from({ length: 50 }, (_, index) => [`upload-${index}`, true] as const),\n )\n const config = {} as Config\n\n initClientUploads({\n clientHandler,\n collections,\n config,\n enabled: false,\n serverHandler: vi.fn() as PayloadHandler,\n serverHandlerPath: '/storage-s3-generate-signed-url',\n })\n\n expect(config.admin?.dependencies).toEqual({\n [clientHandler]: {\n path: clientHandler,\n type: 'function',\n },\n })\n expect(config.admin?.components?.providers).toBeUndefined()\n })\n})\n"],"names":["describe","expect","it","vi","initClientUploads","clientHandler","collections","Object","fromEntries","Array","from","length","_","index","config","enabled","serverHandler","fn","serverHandlerPath","admin","dependencies","toEqual","path","type","components","providers","toBeUndefined"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAEjD,SAASC,iBAAiB,QAAQ,yBAAwB;AAE1DJ,SAAS,qBAAqB;IAC5BE,GAAG,iEAAiE;QAClE,MAAMG,gBAAgB;QACtB,MAAMC,cAAcC,OAAOC,WAAW,CACpCC,MAAMC,IAAI,CAAC;YAAEC,QAAQ;QAAG,GAAG,CAACC,GAAGC,QAAU;gBAAC,CAAC,OAAO,EAAEA,OAAO;gBAAE;aAAK;QAEpE,MAAMC,SAAS,CAAC;QAEhBV,kBAAkB;YAChBC;YACAC;YACAQ;YACAC,SAAS;YACTC,eAAeb,GAAGc,EAAE;YACpBC,mBAAmB;QACrB;QAEAjB,OAAOa,OAAOK,KAAK,EAAEC,cAAcC,OAAO,CAAC;YACzC,CAAChB,cAAc,EAAE;gBACfiB,MAAMjB;gBACNkB,MAAM;YACR;QACF;QACAtB,OAAOa,OAAOK,KAAK,EAAEK,YAAYC,WAAWC,aAAa;IAC3D;AACF"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { createClientUploadReceipt, getSafeFileName } from 'payload/internal';
|
|
3
|
-
import { resolveSignedURLKey } from './resolveSignedURLKey.js';
|
|
4
|
-
vi.mock('payload/internal', ()=>({
|
|
5
|
-
createClientUploadReceipt: vi.fn(()=>'receipt'),
|
|
6
|
-
getSafeFileName: vi.fn()
|
|
7
|
-
}));
|
|
8
|
-
describe('resolveSignedURLKey', ()=>{
|
|
9
|
-
beforeEach(()=>{
|
|
10
|
-
vi.clearAllMocks();
|
|
11
|
-
});
|
|
12
|
-
it('should deduplicate within the resolved document prefix', async ()=>{
|
|
13
|
-
vi.mocked(getSafeFileName).mockImplementation(async ({ desiredFilename })=>{
|
|
14
|
-
expect(desiredFilename).toBe('photo.jpg');
|
|
15
|
-
return 'photo-1.jpg';
|
|
16
|
-
});
|
|
17
|
-
const result = await resolveSignedURLKey({
|
|
18
|
-
collectionPrefix: 'media',
|
|
19
|
-
collectionSlug: 'media',
|
|
20
|
-
docPrefix: 'media-archive',
|
|
21
|
-
filename: 'photo.jpg',
|
|
22
|
-
req: {}
|
|
23
|
-
});
|
|
24
|
-
expect(result.sanitizedDocPrefix).toBe('media/media-archive');
|
|
25
|
-
expect(result.fileKey).toMatch(/^media\/media-archive\/[0-9a-f-]+\/photo-1\.jpg$/);
|
|
26
|
-
expect(result.sanitizedFilename).toBe('photo-1.jpg');
|
|
27
|
-
expect(result._objectKey).toMatch(/^[0-9a-f-]+$/);
|
|
28
|
-
expect(result.clientUploadContext).toMatchObject({
|
|
29
|
-
_objectKey: result._objectKey,
|
|
30
|
-
prefix: 'media/media-archive',
|
|
31
|
-
signedReceipt: 'receipt'
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
it('should mint the receipt for a prefix contained by the collection prefix', async ()=>{
|
|
35
|
-
vi.mocked(getSafeFileName).mockResolvedValue('photo-1.jpg');
|
|
36
|
-
const { _objectKey, sanitizedDocPrefix } = await resolveSignedURLKey({
|
|
37
|
-
collectionPrefix: 'media',
|
|
38
|
-
collectionSlug: 'media',
|
|
39
|
-
docPrefix: 'media-archive',
|
|
40
|
-
filename: 'photo.jpg',
|
|
41
|
-
req: {}
|
|
42
|
-
});
|
|
43
|
-
expect(sanitizedDocPrefix).toBe('media/media-archive');
|
|
44
|
-
expect(createClientUploadReceipt).toHaveBeenCalledWith({
|
|
45
|
-
collectionSlug: 'media',
|
|
46
|
-
context: {
|
|
47
|
-
_objectKey,
|
|
48
|
-
prefix: sanitizedDocPrefix
|
|
49
|
-
},
|
|
50
|
-
filename: 'photo-1.jpg',
|
|
51
|
-
req: {}
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
//# sourceMappingURL=resolveSignedURLKey.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/resolveSignedURLKey.spec.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport { createClientUploadReceipt, getSafeFileName } from 'payload/internal'\n\nimport { resolveSignedURLKey } from './resolveSignedURLKey.js'\n\nvi.mock('payload/internal', () => ({\n createClientUploadReceipt: vi.fn(() => 'receipt'),\n getSafeFileName: vi.fn(),\n}))\n\ndescribe('resolveSignedURLKey', () => {\n beforeEach(() => {\n vi.clearAllMocks()\n })\n\n it('should deduplicate within the resolved document prefix', async () => {\n vi.mocked(getSafeFileName).mockImplementation(async ({ desiredFilename }) => {\n expect(desiredFilename).toBe('photo.jpg')\n return 'photo-1.jpg'\n })\n\n const result = await resolveSignedURLKey({\n collectionPrefix: 'media',\n collectionSlug: 'media',\n docPrefix: 'media-archive',\n filename: 'photo.jpg',\n req: {} as PayloadRequest,\n })\n\n expect(result.sanitizedDocPrefix).toBe('media/media-archive')\n expect(result.fileKey).toMatch(/^media\\/media-archive\\/[0-9a-f-]+\\/photo-1\\.jpg$/)\n expect(result.sanitizedFilename).toBe('photo-1.jpg')\n expect(result._objectKey).toMatch(/^[0-9a-f-]+$/)\n expect(result.clientUploadContext).toMatchObject({\n _objectKey: result._objectKey,\n prefix: 'media/media-archive',\n signedReceipt: 'receipt',\n })\n })\n\n it('should mint the receipt for a prefix contained by the collection prefix', async () => {\n vi.mocked(getSafeFileName).mockResolvedValue('photo-1.jpg')\n\n const { _objectKey, sanitizedDocPrefix } = await resolveSignedURLKey({\n collectionPrefix: 'media',\n collectionSlug: 'media',\n docPrefix: 'media-archive',\n filename: 'photo.jpg',\n req: {} as PayloadRequest,\n })\n\n expect(sanitizedDocPrefix).toBe('media/media-archive')\n expect(createClientUploadReceipt).toHaveBeenCalledWith({\n collectionSlug: 'media',\n context: { _objectKey, prefix: sanitizedDocPrefix },\n filename: 'photo-1.jpg',\n req: {},\n })\n })\n})\n"],"names":["beforeEach","describe","expect","it","vi","createClientUploadReceipt","getSafeFileName","resolveSignedURLKey","mock","fn","clearAllMocks","mocked","mockImplementation","desiredFilename","toBe","result","collectionPrefix","collectionSlug","docPrefix","filename","req","sanitizedDocPrefix","fileKey","toMatch","sanitizedFilename","_objectKey","clientUploadContext","toMatchObject","prefix","signedReceipt","mockResolvedValue","toHaveBeenCalledWith","context"],"mappings":"AAEA,SAASA,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAE7D,SAASC,yBAAyB,EAAEC,eAAe,QAAQ,mBAAkB;AAE7E,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9DH,GAAGI,IAAI,CAAC,oBAAoB,IAAO,CAAA;QACjCH,2BAA2BD,GAAGK,EAAE,CAAC,IAAM;QACvCH,iBAAiBF,GAAGK,EAAE;IACxB,CAAA;AAEAR,SAAS,uBAAuB;IAC9BD,WAAW;QACTI,GAAGM,aAAa;IAClB;IAEAP,GAAG,0DAA0D;QAC3DC,GAAGO,MAAM,CAACL,iBAAiBM,kBAAkB,CAAC,OAAO,EAAEC,eAAe,EAAE;YACtEX,OAAOW,iBAAiBC,IAAI,CAAC;YAC7B,OAAO;QACT;QAEA,MAAMC,SAAS,MAAMR,oBAAoB;YACvCS,kBAAkB;YAClBC,gBAAgB;YAChBC,WAAW;YACXC,UAAU;YACVC,KAAK,CAAC;QACR;QAEAlB,OAAOa,OAAOM,kBAAkB,EAAEP,IAAI,CAAC;QACvCZ,OAAOa,OAAOO,OAAO,EAAEC,OAAO,CAAC;QAC/BrB,OAAOa,OAAOS,iBAAiB,EAAEV,IAAI,CAAC;QACtCZ,OAAOa,OAAOU,UAAU,EAAEF,OAAO,CAAC;QAClCrB,OAAOa,OAAOW,mBAAmB,EAAEC,aAAa,CAAC;YAC/CF,YAAYV,OAAOU,UAAU;YAC7BG,QAAQ;YACRC,eAAe;QACjB;IACF;IAEA1B,GAAG,2EAA2E;QAC5EC,GAAGO,MAAM,CAACL,iBAAiBwB,iBAAiB,CAAC;QAE7C,MAAM,EAAEL,UAAU,EAAEJ,kBAAkB,EAAE,GAAG,MAAMd,oBAAoB;YACnES,kBAAkB;YAClBC,gBAAgB;YAChBC,WAAW;YACXC,UAAU;YACVC,KAAK,CAAC;QACR;QAEAlB,OAAOmB,oBAAoBP,IAAI,CAAC;QAChCZ,OAAOG,2BAA2B0B,oBAAoB,CAAC;YACrDd,gBAAgB;YAChBe,SAAS;gBAAEP;gBAAYG,QAAQP;YAAmB;YAClDF,UAAU;YACVC,KAAK,CAAC;QACR;IACF;AACF"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { PayloadRequest } from 'payload';
|
|
2
|
-
import type { ClientUploadReceipt } from 'payload/internal';
|
|
3
|
-
type Args = {
|
|
4
|
-
collectionPrefix?: string;
|
|
5
|
-
collectionSlug: string;
|
|
6
|
-
expectedFileKey: string;
|
|
7
|
-
req: PayloadRequest;
|
|
8
|
-
signedReceipt: string;
|
|
9
|
-
useCompositePrefixes?: boolean;
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Authenticates a client-upload receipt and asserts it authorizes writing to
|
|
13
|
-
* `expectedFileKey`, returning the verified receipt. Combines
|
|
14
|
-
* {@link verifyClientUploadReceipt} (authentication + collection binding) with
|
|
15
|
-
* {@link assertReceiptMatchesFileKey} (storage-key binding) so an adapter binds
|
|
16
|
-
* the receipt to its request in a single call.
|
|
17
|
-
*/
|
|
18
|
-
export declare function verifyClientUploadReceiptForFileKey({ collectionPrefix, collectionSlug, expectedFileKey, req, signedReceipt, useCompositePrefixes, }: Args): ClientUploadReceipt;
|
|
19
|
-
export {};
|
|
20
|
-
//# sourceMappingURL=verifyClientUploadReceiptForFileKey.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"verifyClientUploadReceiptForFileKey.d.ts","sourceRoot":"","sources":["../../src/utilities/verifyClientUploadReceiptForFileKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAM3D,KAAK,IAAI,GAAG;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CAAC,EAClD,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,GAAG,EACH,aAAa,EACb,oBAAoB,GACrB,EAAE,IAAI,GAAG,mBAAmB,CAM5B"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { verifyClientUploadReceipt } from 'payload/internal';
|
|
2
|
-
import { assertReceiptMatchesFileKey } from './assertReceiptMatchesFileKey.js';
|
|
3
|
-
/**
|
|
4
|
-
* Authenticates a client-upload receipt and asserts it authorizes writing to
|
|
5
|
-
* `expectedFileKey`, returning the verified receipt. Combines
|
|
6
|
-
* {@link verifyClientUploadReceipt} (authentication + collection binding) with
|
|
7
|
-
* {@link assertReceiptMatchesFileKey} (storage-key binding) so an adapter binds
|
|
8
|
-
* the receipt to its request in a single call.
|
|
9
|
-
*/ export function verifyClientUploadReceiptForFileKey({ collectionPrefix, collectionSlug, expectedFileKey, req, signedReceipt, useCompositePrefixes }) {
|
|
10
|
-
const receipt = verifyClientUploadReceipt({
|
|
11
|
-
collectionSlug,
|
|
12
|
-
req,
|
|
13
|
-
signedReceipt
|
|
14
|
-
});
|
|
15
|
-
assertReceiptMatchesFileKey({
|
|
16
|
-
collectionPrefix,
|
|
17
|
-
expectedFileKey,
|
|
18
|
-
receipt,
|
|
19
|
-
useCompositePrefixes
|
|
20
|
-
});
|
|
21
|
-
return receipt;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
//# sourceMappingURL=verifyClientUploadReceiptForFileKey.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/verifyClientUploadReceiptForFileKey.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport type { ClientUploadReceipt } from 'payload/internal'\n\nimport { verifyClientUploadReceipt } from 'payload/internal'\n\nimport { assertReceiptMatchesFileKey } from './assertReceiptMatchesFileKey.js'\n\ntype Args = {\n collectionPrefix?: string\n collectionSlug: string\n expectedFileKey: string\n req: PayloadRequest\n signedReceipt: string\n useCompositePrefixes?: boolean\n}\n\n/**\n * Authenticates a client-upload receipt and asserts it authorizes writing to\n * `expectedFileKey`, returning the verified receipt. Combines\n * {@link verifyClientUploadReceipt} (authentication + collection binding) with\n * {@link assertReceiptMatchesFileKey} (storage-key binding) so an adapter binds\n * the receipt to its request in a single call.\n */\nexport function verifyClientUploadReceiptForFileKey({\n collectionPrefix,\n collectionSlug,\n expectedFileKey,\n req,\n signedReceipt,\n useCompositePrefixes,\n}: Args): ClientUploadReceipt {\n const receipt = verifyClientUploadReceipt({ collectionSlug, req, signedReceipt })\n\n assertReceiptMatchesFileKey({ collectionPrefix, expectedFileKey, receipt, useCompositePrefixes })\n\n return receipt\n}\n"],"names":["verifyClientUploadReceipt","assertReceiptMatchesFileKey","verifyClientUploadReceiptForFileKey","collectionPrefix","collectionSlug","expectedFileKey","req","signedReceipt","useCompositePrefixes","receipt"],"mappings":"AAGA,SAASA,yBAAyB,QAAQ,mBAAkB;AAE5D,SAASC,2BAA2B,QAAQ,mCAAkC;AAW9E;;;;;;CAMC,GACD,OAAO,SAASC,oCAAoC,EAClDC,gBAAgB,EAChBC,cAAc,EACdC,eAAe,EACfC,GAAG,EACHC,aAAa,EACbC,oBAAoB,EACf;IACL,MAAMC,UAAUT,0BAA0B;QAAEI;QAAgBE;QAAKC;IAAc;IAE/EN,4BAA4B;QAAEE;QAAkBE;QAAiBI;QAASD;IAAqB;IAE/F,OAAOC;AACT"}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { createClientUploadReceipt } from 'payload/internal';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { verifyClientUploadReceiptForFileKey } from './verifyClientUploadReceiptForFileKey.js';
|
|
4
|
-
const req = {
|
|
5
|
-
payload: {
|
|
6
|
-
secret: 'test-secret'
|
|
7
|
-
}
|
|
8
|
-
};
|
|
9
|
-
const issueReceipt = ({ collectionSlug = 'media', filename = 'photo.png', prefix })=>createClientUploadReceipt({
|
|
10
|
-
collectionSlug,
|
|
11
|
-
context: {
|
|
12
|
-
prefix
|
|
13
|
-
},
|
|
14
|
-
filename,
|
|
15
|
-
req
|
|
16
|
-
});
|
|
17
|
-
const verify = ({ expectedFileKey, signedReceipt, useCompositePrefixes })=>verifyClientUploadReceiptForFileKey({
|
|
18
|
-
collectionPrefix: 'media',
|
|
19
|
-
collectionSlug: 'media',
|
|
20
|
-
expectedFileKey,
|
|
21
|
-
req,
|
|
22
|
-
signedReceipt,
|
|
23
|
-
useCompositePrefixes
|
|
24
|
-
});
|
|
25
|
-
/**
|
|
26
|
-
* This guard is what lets adapters accept a caller-supplied storage key: the key is
|
|
27
|
-
* recomputed from the signed prefix and filename, so a receipt issued for one location
|
|
28
|
-
* cannot be replayed against another.
|
|
29
|
-
*/ describe('verifyClientUploadReceiptForFileKey', ()=>{
|
|
30
|
-
it('should accept the key the receipt was issued for', ()=>{
|
|
31
|
-
const signedReceipt = issueReceipt({
|
|
32
|
-
prefix: 'media/documents'
|
|
33
|
-
});
|
|
34
|
-
expect(verify({
|
|
35
|
-
expectedFileKey: 'media/documents/photo.png',
|
|
36
|
-
signedReceipt
|
|
37
|
-
}).filename).toBe('photo.png');
|
|
38
|
-
});
|
|
39
|
-
it.each([
|
|
40
|
-
[
|
|
41
|
-
'a sibling of the collection prefix',
|
|
42
|
-
'media-archive/photo.png'
|
|
43
|
-
],
|
|
44
|
-
[
|
|
45
|
-
'another collection',
|
|
46
|
-
'invoices/photo.png'
|
|
47
|
-
],
|
|
48
|
-
[
|
|
49
|
-
'another document prefix in the same collection',
|
|
50
|
-
'media/other-doc/photo.png'
|
|
51
|
-
],
|
|
52
|
-
[
|
|
53
|
-
'another filename under the issued prefix',
|
|
54
|
-
'media/documents/payroll.pdf'
|
|
55
|
-
]
|
|
56
|
-
])('should reject %s', (_case, expectedFileKey)=>{
|
|
57
|
-
const signedReceipt = issueReceipt({
|
|
58
|
-
prefix: 'media/documents'
|
|
59
|
-
});
|
|
60
|
-
expect(()=>verify({
|
|
61
|
-
expectedFileKey,
|
|
62
|
-
signedReceipt
|
|
63
|
-
})).toThrow('Client upload reference does not match this request.');
|
|
64
|
-
});
|
|
65
|
-
it('should reject a receipt issued for another collection', ()=>{
|
|
66
|
-
const signedReceipt = issueReceipt({
|
|
67
|
-
collectionSlug: 'invoices',
|
|
68
|
-
prefix: 'media/documents'
|
|
69
|
-
});
|
|
70
|
-
expect(()=>verify({
|
|
71
|
-
expectedFileKey: 'media/documents/photo.png',
|
|
72
|
-
signedReceipt
|
|
73
|
-
})).toThrow('Invalid or expired client upload reference.');
|
|
74
|
-
});
|
|
75
|
-
it('should reject a receipt whose prefix is not a string', ()=>{
|
|
76
|
-
const signedReceipt = issueReceipt({
|
|
77
|
-
prefix: {
|
|
78
|
-
nested: true
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
expect(()=>verify({
|
|
82
|
-
expectedFileKey: 'media/photo.png',
|
|
83
|
-
signedReceipt
|
|
84
|
-
})).toThrow('Client upload reference does not match this request.');
|
|
85
|
-
});
|
|
86
|
-
it('should reject a tampered signature', ()=>{
|
|
87
|
-
const signedReceipt = issueReceipt({
|
|
88
|
-
prefix: 'media/documents'
|
|
89
|
-
});
|
|
90
|
-
const [encodedReceipt] = signedReceipt.split('.');
|
|
91
|
-
expect(()=>verify({
|
|
92
|
-
expectedFileKey: 'media/documents/photo.png',
|
|
93
|
-
signedReceipt: `${encodedReceipt}.not-the-signature`
|
|
94
|
-
})).toThrow();
|
|
95
|
-
});
|
|
96
|
-
it('should recompute the key in composite mode', ()=>{
|
|
97
|
-
const signedReceipt = issueReceipt({
|
|
98
|
-
prefix: 'documents'
|
|
99
|
-
});
|
|
100
|
-
expect(verify({
|
|
101
|
-
expectedFileKey: 'media/documents/photo.png',
|
|
102
|
-
signedReceipt,
|
|
103
|
-
useCompositePrefixes: true
|
|
104
|
-
}).filename).toBe('photo.png');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
//# sourceMappingURL=verifyClientUploadReceiptForFileKey.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/verifyClientUploadReceiptForFileKey.spec.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { createClientUploadReceipt } from 'payload/internal'\nimport { describe, expect, it } from 'vitest'\n\nimport { verifyClientUploadReceiptForFileKey } from './verifyClientUploadReceiptForFileKey.js'\n\nconst req = { payload: { secret: 'test-secret' } } as unknown as PayloadRequest\n\nconst issueReceipt = ({\n collectionSlug = 'media',\n filename = 'photo.png',\n prefix,\n}: {\n collectionSlug?: string\n filename?: string\n prefix: unknown\n}) =>\n createClientUploadReceipt({\n collectionSlug,\n context: { prefix },\n filename,\n req,\n })\n\nconst verify = ({\n expectedFileKey,\n signedReceipt,\n useCompositePrefixes,\n}: {\n expectedFileKey: string\n signedReceipt: string\n useCompositePrefixes?: boolean\n}) =>\n verifyClientUploadReceiptForFileKey({\n collectionPrefix: 'media',\n collectionSlug: 'media',\n expectedFileKey,\n req,\n signedReceipt,\n useCompositePrefixes,\n })\n\n/**\n * This guard is what lets adapters accept a caller-supplied storage key: the key is\n * recomputed from the signed prefix and filename, so a receipt issued for one location\n * cannot be replayed against another.\n */\ndescribe('verifyClientUploadReceiptForFileKey', () => {\n it('should accept the key the receipt was issued for', () => {\n const signedReceipt = issueReceipt({ prefix: 'media/documents' })\n\n expect(verify({ expectedFileKey: 'media/documents/photo.png', signedReceipt }).filename).toBe(\n 'photo.png',\n )\n })\n\n it.each([\n ['a sibling of the collection prefix', 'media-archive/photo.png'],\n ['another collection', 'invoices/photo.png'],\n ['another document prefix in the same collection', 'media/other-doc/photo.png'],\n ['another filename under the issued prefix', 'media/documents/payroll.pdf'],\n ])('should reject %s', (_case, expectedFileKey) => {\n const signedReceipt = issueReceipt({ prefix: 'media/documents' })\n\n expect(() => verify({ expectedFileKey, signedReceipt })).toThrow(\n 'Client upload reference does not match this request.',\n )\n })\n\n it('should reject a receipt issued for another collection', () => {\n const signedReceipt = issueReceipt({ collectionSlug: 'invoices', prefix: 'media/documents' })\n\n expect(() => verify({ expectedFileKey: 'media/documents/photo.png', signedReceipt })).toThrow(\n 'Invalid or expired client upload reference.',\n )\n })\n\n it('should reject a receipt whose prefix is not a string', () => {\n const signedReceipt = issueReceipt({ prefix: { nested: true } })\n\n expect(() => verify({ expectedFileKey: 'media/photo.png', signedReceipt })).toThrow(\n 'Client upload reference does not match this request.',\n )\n })\n\n it('should reject a tampered signature', () => {\n const signedReceipt = issueReceipt({ prefix: 'media/documents' })\n const [encodedReceipt] = signedReceipt.split('.')\n\n expect(() =>\n verify({\n expectedFileKey: 'media/documents/photo.png',\n signedReceipt: `${encodedReceipt}.not-the-signature`,\n }),\n ).toThrow()\n })\n\n it('should recompute the key in composite mode', () => {\n const signedReceipt = issueReceipt({ prefix: 'documents' })\n\n expect(\n verify({\n expectedFileKey: 'media/documents/photo.png',\n signedReceipt,\n useCompositePrefixes: true,\n }).filename,\n ).toBe('photo.png')\n })\n})\n"],"names":["createClientUploadReceipt","describe","expect","it","verifyClientUploadReceiptForFileKey","req","payload","secret","issueReceipt","collectionSlug","filename","prefix","context","verify","expectedFileKey","signedReceipt","useCompositePrefixes","collectionPrefix","toBe","each","_case","toThrow","nested","encodedReceipt","split"],"mappings":"AAEA,SAASA,yBAAyB,QAAQ,mBAAkB;AAC5D,SAASC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,mCAAmC,QAAQ,2CAA0C;AAE9F,MAAMC,MAAM;IAAEC,SAAS;QAAEC,QAAQ;IAAc;AAAE;AAEjD,MAAMC,eAAe,CAAC,EACpBC,iBAAiB,OAAO,EACxBC,WAAW,WAAW,EACtBC,MAAM,EAKP,GACCX,0BAA0B;QACxBS;QACAG,SAAS;YAAED;QAAO;QAClBD;QACAL;IACF;AAEF,MAAMQ,SAAS,CAAC,EACdC,eAAe,EACfC,aAAa,EACbC,oBAAoB,EAKrB,GACCZ,oCAAoC;QAClCa,kBAAkB;QAClBR,gBAAgB;QAChBK;QACAT;QACAU;QACAC;IACF;AAEF;;;;CAIC,GACDf,SAAS,uCAAuC;IAC9CE,GAAG,oDAAoD;QACrD,MAAMY,gBAAgBP,aAAa;YAAEG,QAAQ;QAAkB;QAE/DT,OAAOW,OAAO;YAAEC,iBAAiB;YAA6BC;QAAc,GAAGL,QAAQ,EAAEQ,IAAI,CAC3F;IAEJ;IAEAf,GAAGgB,IAAI,CAAC;QACN;YAAC;YAAsC;SAA0B;QACjE;YAAC;YAAsB;SAAqB;QAC5C;YAAC;YAAkD;SAA4B;QAC/E;YAAC;YAA4C;SAA8B;KAC5E,EAAE,oBAAoB,CAACC,OAAON;QAC7B,MAAMC,gBAAgBP,aAAa;YAAEG,QAAQ;QAAkB;QAE/DT,OAAO,IAAMW,OAAO;gBAAEC;gBAAiBC;YAAc,IAAIM,OAAO,CAC9D;IAEJ;IAEAlB,GAAG,yDAAyD;QAC1D,MAAMY,gBAAgBP,aAAa;YAAEC,gBAAgB;YAAYE,QAAQ;QAAkB;QAE3FT,OAAO,IAAMW,OAAO;gBAAEC,iBAAiB;gBAA6BC;YAAc,IAAIM,OAAO,CAC3F;IAEJ;IAEAlB,GAAG,wDAAwD;QACzD,MAAMY,gBAAgBP,aAAa;YAAEG,QAAQ;gBAAEW,QAAQ;YAAK;QAAE;QAE9DpB,OAAO,IAAMW,OAAO;gBAAEC,iBAAiB;gBAAmBC;YAAc,IAAIM,OAAO,CACjF;IAEJ;IAEAlB,GAAG,sCAAsC;QACvC,MAAMY,gBAAgBP,aAAa;YAAEG,QAAQ;QAAkB;QAC/D,MAAM,CAACY,eAAe,GAAGR,cAAcS,KAAK,CAAC;QAE7CtB,OAAO,IACLW,OAAO;gBACLC,iBAAiB;gBACjBC,eAAe,GAAGQ,eAAe,kBAAkB,CAAC;YACtD,IACAF,OAAO;IACX;IAEAlB,GAAG,8CAA8C;QAC/C,MAAMY,gBAAgBP,aAAa;YAAEG,QAAQ;QAAY;QAEzDT,OACEW,OAAO;YACLC,iBAAiB;YACjBC;YACAC,sBAAsB;QACxB,GAAGN,QAAQ,EACXQ,IAAI,CAAC;IACT;AACF"}
|