From 43c9bc8330bdec02157c5e9593b351597458906e Mon Sep 17 00:00:00 2001 From: Dhairya Date: Wed, 29 Nov 2023 11:29:21 +0100 Subject: [PATCH 1/7] enable image key generation --- components/js-api-client/package.json | 2 + .../js-api-client/src/core/uploadImage.ts | 94 +++++++++++++++++++ components/js-api-client/src/index.ts | 1 + 3 files changed, 97 insertions(+) create mode 100644 components/js-api-client/src/core/uploadImage.ts diff --git a/components/js-api-client/package.json b/components/js-api-client/package.json index 39a284a3..052c55aa 100644 --- a/components/js-api-client/package.json +++ b/components/js-api-client/package.json @@ -27,7 +27,9 @@ }, "dependencies": { "dotenv": "^16.0.0", + "fs": "^0.0.1-security", "json-to-graphql-query": "^2.2.4", + "mime-lite": "^1.0.3", "node-fetch": "^2", "tiny-invariant": "^1.2.0", "typescript": "^4.6.3", diff --git a/components/js-api-client/src/core/uploadImage.ts b/components/js-api-client/src/core/uploadImage.ts new file mode 100644 index 00000000..15fcbd11 --- /dev/null +++ b/components/js-api-client/src/core/uploadImage.ts @@ -0,0 +1,94 @@ +import * as fs from 'fs'; +import * as mime from 'mime-lite'; +import { ClientInterface } from './client'; + +type ImageInputWithReferenceId = { + id: string; + filename: string; + mimeType: string; +}; + +type UploadHandler = ImageInputWithReferenceId & { + buffer: Buffer; + stats: fs.Stats; + apiClient: ClientInterface; +}; + +const MUTATION_UPLOAD_FILE = `#graphql +mutation UPLOAD_FILE ($tenantId: ID!, $filename: String!, $mimeType: String!) { + fileUpload { + generatePresignedRequest( + tenantId: $tenantId + filename: $filename + contentType: $mimeType + type: MEDIA + ) { + url + fields { + name + value + } + } + } +}`; + +export async function uploadImageToTenant({ + id, + mimeType, + filename, + buffer, + stats, + apiClient, +}: UploadHandler): Promise { + const signedRequestResult = await apiClient.pimApi(MUTATION_UPLOAD_FILE, { + tenantId: id, + filename, + mimeType, + }); + + const payload = signedRequestResult.fileUpload.generatePresignedRequest; + const formData: FormData = new FormData(); + payload.fields.forEach((field: { name: string; value: string }) => { + formData.append(field.name, field.value); + }); + formData.append('file', new Blob([buffer])); + + const response = await fetch(payload.url, { + method: 'POST', + headers: new Headers({ 'Content-Length': String(stats.size) }), + body: formData, + }); + + return response.status === 201 ? (formData.get('key') as string) : false; +} + +export async function handleImageUpload(imagePath: any, apiClient: ClientInterface, tenantId: string): Promise { + if (!imagePath) return; + + const extension = imagePath.split('.').pop() as string; + const mimeType = mime.getType(extension); + const filename = imagePath.split('T/').pop() as string; + + if (!mimeType) { + console.log('Could not find mime type for file. Halting upload'); + return null; + } + + const stats = fs.statSync(imagePath); + const buffer = fs.readFileSync(imagePath); + + const data: Omit = { + mimeType, + filename, + stats, + buffer, + apiClient, + }; + + const imageKey = await uploadImageToTenant({ + id: tenantId, + ...data, + }); + + return imageKey; +} diff --git a/components/js-api-client/src/index.ts b/components/js-api-client/src/index.ts index 0853adc0..03c0b471 100644 --- a/components/js-api-client/src/index.ts +++ b/components/js-api-client/src/index.ts @@ -19,6 +19,7 @@ export * from './types/address'; export * from './types/customer'; export * from './types/signature'; export * from './types/pricing'; +export * from './core/uploadImage'; import { createClient } from './core/client'; import { createNavigationFetcher } from './core/navigation'; From 5fc170b030d7dfdc62b973a28398d4e91585fa0e Mon Sep 17 00:00:00 2001 From: Dhairya Date: Wed, 29 Nov 2023 11:44:08 +0100 Subject: [PATCH 2/7] Update uploadImage.ts --- components/js-api-client/src/core/uploadImage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/js-api-client/src/core/uploadImage.ts b/components/js-api-client/src/core/uploadImage.ts index 15fcbd11..6da483f9 100644 --- a/components/js-api-client/src/core/uploadImage.ts +++ b/components/js-api-client/src/core/uploadImage.ts @@ -62,7 +62,7 @@ export async function uploadImageToTenant({ return response.status === 201 ? (formData.get('key') as string) : false; } -export async function handleImageUpload(imagePath: any, apiClient: ClientInterface, tenantId: string): Promise { +export async function handleImageUpload(imagePath: string, apiClient: ClientInterface, tenantId: string): Promise { if (!imagePath) return; const extension = imagePath.split('.').pop() as string; From 1c1ccb253f670ef7da9baeab7ef9a15be9e1b93c Mon Sep 17 00:00:00 2001 From: Dhairya Date: Wed, 29 Nov 2023 11:57:59 +0100 Subject: [PATCH 3/7] Update uploadImage.ts --- components/js-api-client/src/core/uploadImage.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/components/js-api-client/src/core/uploadImage.ts b/components/js-api-client/src/core/uploadImage.ts index 6da483f9..9ca978de 100644 --- a/components/js-api-client/src/core/uploadImage.ts +++ b/components/js-api-client/src/core/uploadImage.ts @@ -62,16 +62,21 @@ export async function uploadImageToTenant({ return response.status === 201 ? (formData.get('key') as string) : false; } -export async function handleImageUpload(imagePath: string, apiClient: ClientInterface, tenantId: string): Promise { - if (!imagePath) return; +export async function handleImageUpload( + imagePath: string, + apiClient: ClientInterface, + tenantId: string, +): Promise { + if (!imagePath) { + return 'No image path provided'; + } const extension = imagePath.split('.').pop() as string; const mimeType = mime.getType(extension); const filename = imagePath.split('T/').pop() as string; if (!mimeType) { - console.log('Could not find mime type for file. Halting upload'); - return null; + return 'Could not find mime type for file. Halting upload'; } const stats = fs.statSync(imagePath); From ead044016e4b8b464a8979fae1fbee89e2d2fc6a Mon Sep 17 00:00:00 2001 From: Dhairya Date: Wed, 29 Nov 2023 13:19:14 +0100 Subject: [PATCH 4/7] allow only images --- components/js-api-client/src/core/uploadImage.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/js-api-client/src/core/uploadImage.ts b/components/js-api-client/src/core/uploadImage.ts index 9ca978de..01357e73 100644 --- a/components/js-api-client/src/core/uploadImage.ts +++ b/components/js-api-client/src/core/uploadImage.ts @@ -79,6 +79,10 @@ export async function handleImageUpload( return 'Could not find mime type for file. Halting upload'; } + if (!mimeType.includes('image')) { + return 'File is not an image. Halting upload'; + } + const stats = fs.statSync(imagePath); const buffer = fs.readFileSync(imagePath); From 71497722fd4d657fd144e2aea31c9bec7b17c660 Mon Sep 17 00:00:00 2001 From: Dhairya Date: Fri, 8 Dec 2023 14:21:15 +0100 Subject: [PATCH 5/7] Update package.json --- components/js-api-client/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/js-api-client/package.json b/components/js-api-client/package.json index 052c55aa..5157f66e 100644 --- a/components/js-api-client/package.json +++ b/components/js-api-client/package.json @@ -34,5 +34,8 @@ "tiny-invariant": "^1.2.0", "typescript": "^4.6.3", "zod": "^3.14.3" + }, + "browser": { + "fs": false } } From 54c4d17a04b550f31f618b383b565e227a4a3345 Mon Sep 17 00:00:00 2001 From: Dhairya Date: Tue, 12 Dec 2023 09:55:12 +0100 Subject: [PATCH 6/7] update version and readme --- components/js-api-client/README.md | 16 ++++++++++++++++ components/js-api-client/package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/components/js-api-client/README.md b/components/js-api-client/README.md index 5154f209..4ae3e76c 100644 --- a/components/js-api-client/README.md +++ b/components/js-api-client/README.md @@ -573,4 +573,20 @@ run(); A full example is here: https://github.com/CrystallizeAPI/libraries/blob/main/components/js-api-client/src/examples/dump-tenant.ts +## Image uploader + +Uploading an image to Crystallize is a three step process: + +- You first need to send a request to the PIM API to get a pre-signed URL to upload the file +- Then, you send another request to upload the file and receive a key +- Lastly, you can register the image in Crystallize using the key received in the previous step + +To simplify this process, there is a _handleImageUpload_ function provided with the library. Here is how you would use it: + +```javascript +const image = await handleImageUpload(path, crystallizeClient, 'tenantID'); +``` + +This takes care of the first two steps, which means you receive a key you can then use to register your image in Crystallize. + [crystallizeobject]: crystallize_marketing|folder|625619f6615e162541535959 diff --git a/components/js-api-client/package.json b/components/js-api-client/package.json index 5157f66e..678f8959 100644 --- a/components/js-api-client/package.json +++ b/components/js-api-client/package.json @@ -1,7 +1,7 @@ { "name": "@crystallize/js-api-client", "license": "MIT", - "version": "1.12.2", + "version": "1.13.0", "author": "Crystallize (https://crystallize.com)", "contributors": [ "Sébastien Morel " From 87e6bd8398ba34d304130fa70505db64c998ebfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morel=20Se=CC=81bastien?= Date: Wed, 13 Dec 2023 16:42:44 -0800 Subject: [PATCH 7/7] :mushroom: Review and fixes --- components/js-api-client/package.json | 17 ++++++++--------- .../js-api-client/src/core/uploadImage.ts | 9 +++++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/components/js-api-client/package.json b/components/js-api-client/package.json index 678f8959..3fdce4d0 100644 --- a/components/js-api-client/package.json +++ b/components/js-api-client/package.json @@ -20,20 +20,19 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "devDependencies": { - "@tsconfig/node16": "^1.0.2", - "@types/node": "^17.0.23", + "@tsconfig/node16": "^16.1.1", + "@types/node": "^20.10.4", "@types/node-fetch": "^2", - "jest": "^27.5.1" + "jest": "^29.7.0" }, "dependencies": { - "dotenv": "^16.0.0", - "fs": "^0.0.1-security", - "json-to-graphql-query": "^2.2.4", + "dotenv": "^16.3.1", + "json-to-graphql-query": "^2.2.5", "mime-lite": "^1.0.3", "node-fetch": "^2", - "tiny-invariant": "^1.2.0", - "typescript": "^4.6.3", - "zod": "^3.14.3" + "tiny-invariant": "^1.3.1", + "typescript": "^5.3.3", + "zod": "^3.22.4" }, "browser": { "fs": false diff --git a/components/js-api-client/src/core/uploadImage.ts b/components/js-api-client/src/core/uploadImage.ts index 01357e73..83324e4c 100644 --- a/components/js-api-client/src/core/uploadImage.ts +++ b/components/js-api-client/src/core/uploadImage.ts @@ -65,7 +65,7 @@ export async function uploadImageToTenant({ export async function handleImageUpload( imagePath: string, apiClient: ClientInterface, - tenantId: string, + tenantId?: string, ): Promise { if (!imagePath) { return 'No image path provided'; @@ -94,8 +94,13 @@ export async function handleImageUpload( apiClient, }; + const tId = apiClient.config.tenantId ?? tenantId; + if (!tId) { + return 'No tenant id provided'; + } + const imageKey = await uploadImageToTenant({ - id: tenantId, + id: tId, ...data, });