feat: implement direct upload (#12328)
* feat: implement direct upload * feat: add direct-upload endpoint * refactor: implement feedback * refactor: have a dedicated endpoint for direct uploads * refactor: convert responses to snakecase * refactor: rename method to createImport * test: add tests for the presigned-urls endpoint
This commit is contained in:
@@ -17,16 +17,16 @@ export class Product {
|
||||
/**
|
||||
* This method creates a product import. The products are only imported after
|
||||
* the import is confirmed using the {@link confirmImport} method.
|
||||
*
|
||||
* This method sends a request to the
|
||||
*
|
||||
* This method sends a request to the
|
||||
* [Create Product Import](https://docs.medusajs.com/api/admin#products_postproductsimport)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param body - The import's details.
|
||||
* @param query - Query parameters to pass to the request.
|
||||
* @param headers - Headers to pass in the request.
|
||||
* @returns The import's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.import({
|
||||
* file // uploaded File instance
|
||||
@@ -58,17 +58,93 @@ export class Product {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a product import. The products are only imported after
|
||||
* the import is confirmed using the {@link confirmImport} method.
|
||||
*
|
||||
* This method sends a request to the
|
||||
* [Create Product Import](https://docs.medusajs.com/api/admin#products_postproductsimport)
|
||||
* API route.
|
||||
*
|
||||
* @param body - The import's details.
|
||||
* @param query - Query parameters to pass to the request.
|
||||
* @param headers - Headers to pass in the request.
|
||||
* @returns The import's details.
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.createImport({
|
||||
* file // uploaded File instance
|
||||
* })
|
||||
* .then(({ transaction_id }) => {
|
||||
* console.log(transaction_id)
|
||||
* })
|
||||
*/
|
||||
async createImport(
|
||||
body: HttpTypes.AdminImportProductRequest,
|
||||
query?: {},
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
/**
|
||||
* Get signed URL for file uploads
|
||||
*/
|
||||
const response =
|
||||
await this.client.fetch<HttpTypes.AdminUploadPreSignedUrlResponse>(
|
||||
"admin/uploads/presigned-urls",
|
||||
{
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
body: {
|
||||
originalname: body.file.name,
|
||||
mime_type: body.file.type,
|
||||
size: body.file.size,
|
||||
} satisfies HttpTypes.AdminUploadPreSignedUrlRequest,
|
||||
query,
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Upload file using the signed URL. We cannot send cookies or any other
|
||||
* special headers in this request, since external services like S3 will
|
||||
* give a CORS error.
|
||||
*/
|
||||
await fetch(response.url, {
|
||||
method: "PUT",
|
||||
body: body.file,
|
||||
})
|
||||
|
||||
/**
|
||||
* Perform products import using the uploaded file name
|
||||
*/
|
||||
return await this.client.fetch<HttpTypes.AdminImportProductsResponse>(
|
||||
"/admin/products/imports",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
},
|
||||
body: {
|
||||
file_key: response.filename,
|
||||
originalname: response.originalname,
|
||||
extension: response.extension,
|
||||
size: response.size,
|
||||
mime_type: response.mime_type,
|
||||
} satisfies HttpTypes.AdminImportProductsRequest,
|
||||
query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method confirms a product import created using the method {@link import}.
|
||||
* It sends a request to the
|
||||
* It sends a request to the
|
||||
* [Confirm Product Import](https://docs.medusajs.com/api/admin#products_postproductsimporttransaction_idconfirm)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param transactionId - The ID of the transaction of the created product import. This is returned
|
||||
* by the API route used to create the product import.
|
||||
* @param query - Query parameters to pass in the request.
|
||||
* @param headers - Headers to pass in the request.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.confirmImport("transaction_123")
|
||||
* .then(() => {
|
||||
@@ -93,23 +169,23 @@ export class Product {
|
||||
|
||||
/**
|
||||
* This method starts a product export process to retrieve a CSV of exported products.
|
||||
*
|
||||
* You'll receive in the response the transaction ID of the workflow generating the CSV file.
|
||||
* To check the status of the execution, send a `GET` request to
|
||||
* `/admin/workflows-executions/export-products/:transaction-id`.
|
||||
*
|
||||
* Once the execution finishes successfully, a notification is created for the export.
|
||||
* You can retrieve the notifications using the `/admin/notification` API route to
|
||||
*
|
||||
* You'll receive in the response the transaction ID of the workflow generating the CSV file.
|
||||
* To check the status of the execution, send a `GET` request to
|
||||
* `/admin/workflows-executions/export-products/:transaction-id`.
|
||||
*
|
||||
* Once the execution finishes successfully, a notification is created for the export.
|
||||
* You can retrieve the notifications using the `/admin/notification` API route to
|
||||
* retrieve the file's download URL.
|
||||
*
|
||||
*
|
||||
* This method sends a request to the [Export Product](https://docs.medusajs.com/api/admin#products_postproductsexport)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param body - The export's details.
|
||||
* @param query - Filters to specify which products to export.
|
||||
* @param headers - Headers to pass in the request.
|
||||
* @param headers - Headers to pass in the request.
|
||||
* @returns The export's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.export({})
|
||||
* .then(({ transaction_id }) => {
|
||||
@@ -133,15 +209,15 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method manages products to create, update, or delete them. It sends a request to the
|
||||
* This method manages products to create, update, or delete them. It sends a request to the
|
||||
* [Manage Products](https://docs.medusajs.com/api/admin#products_postproductsbatch)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param body - The products to create, update, or delete.
|
||||
* @param query - Configure the fields to retrieve in the products.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The batch operations details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.batch({
|
||||
* create: [
|
||||
@@ -189,15 +265,15 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a product. It sends a request to the
|
||||
* This method creates a product. It sends a request to the
|
||||
* [Create Product](https://docs.medusajs.com/api/admin#products_postproducts)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param body - The product's details.
|
||||
* @param query - Configure the fields to retrieve in the product.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.create({
|
||||
* title: "Shirt",
|
||||
@@ -240,13 +316,13 @@ export class Product {
|
||||
* This method updates a product. It sends a request to the
|
||||
* [Update Product](https://docs.medusajs.com/api/admin#products_postproductsid)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param id - The product's ID.
|
||||
* @param body - The data to update in the product.
|
||||
* @param query - Configure the fields to retrieve in the product.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.update("prod_123", {
|
||||
* title: "Shirt",
|
||||
@@ -273,27 +349,27 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves a paginated list of products. It sends a request to the
|
||||
* This method retrieves a paginated list of products. It sends a request to the
|
||||
* [List Products](https://docs.medusajs.com/api/admin#products_getproducts) API route.
|
||||
*
|
||||
*
|
||||
* @param queryParams - Filters and pagination configurations.
|
||||
* @param headers - Headers to pass in the request.
|
||||
* @returns The paginated list of products.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve the list of products:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.list()
|
||||
* .then(({ products, count, limit, offset }) => {
|
||||
* console.log(products)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To configure the pagination, pass the `limit` and `offset` query parameters.
|
||||
*
|
||||
*
|
||||
* For example, to retrieve only 10 items and skip 10 items:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.list({
|
||||
* limit: 10,
|
||||
@@ -303,10 +379,10 @@ export class Product {
|
||||
* console.log(products)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Using the `fields` query parameter, you can specify the fields and relations to retrieve
|
||||
* in each products:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.list({
|
||||
* fields: "id,*variants"
|
||||
@@ -315,7 +391,7 @@ export class Product {
|
||||
* console.log(products)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||
*/
|
||||
async list(
|
||||
@@ -332,27 +408,27 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves a product by its ID. It sends a request to the
|
||||
* This method retrieves a product by its ID. It sends a request to the
|
||||
* [Get Product](https://docs.medusajs.com/api/admin#products_getproductsid)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param id - The product's ID.
|
||||
* @param query - Configure the fields to retrieve in the product.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve a product by its ID:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.retrieve("prod_123")
|
||||
* .then(({ product }) => {
|
||||
* console.log(product)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To specify the fields and relations to retrieve:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.retrieve("prod_123", {
|
||||
* fields: "id,*variants"
|
||||
@@ -361,7 +437,7 @@ export class Product {
|
||||
* console.log(product)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||
*/
|
||||
async retrieve(id: string, query?: SelectParams, headers?: ClientHeaders) {
|
||||
@@ -378,11 +454,11 @@ export class Product {
|
||||
* This method deletes a product. It sends a request to the
|
||||
* [Delete Product](https://docs.medusajs.com/api/admin#products_deleteproductsid)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param id - The product's ID.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The deletion's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.delete("prod_123")
|
||||
* .then(({ deleted }) => {
|
||||
@@ -403,13 +479,13 @@ export class Product {
|
||||
* This method manages the variants of a product. It sends a request to the
|
||||
* [Manage Variants](https://docs.medusajs.com/api/admin#products_postproductsidvariantsbatch)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param body - The variants to create, update, or delete.
|
||||
* @param query - Configure the fields to retrieve in the product variants.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product variants' details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.batchVariants("prod_123", {
|
||||
* create: [
|
||||
@@ -454,13 +530,13 @@ export class Product {
|
||||
* This method creates a variant for a product. It sends a request to the
|
||||
* [Create Variant](https://docs.medusajs.com/api/admin#products_postproductsidvariants)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param body - The variant's details.
|
||||
* @param query - Configure the fields to retrieve in the product.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.createVariant("prod_123", {
|
||||
* title: "Blue Shirt",
|
||||
@@ -499,17 +575,17 @@ export class Product {
|
||||
* This method updates a variant of a product. It sends a request to the
|
||||
* [Update Variant](https://docs.medusajs.com/api/admin#products_postproductsidvariantsvariant_id)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param id - The variant's ID.
|
||||
* @param body - The data to update in the variant.
|
||||
* @param query - Configure the fields to retrieve in the product.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.updateVariant(
|
||||
* "prod_123",
|
||||
* "prod_123",
|
||||
* "variant_123",
|
||||
* {
|
||||
* title: "Blue Shirt",
|
||||
@@ -538,28 +614,28 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves a paginated list of products. It sends a request to the
|
||||
* This method retrieves a paginated list of products. It sends a request to the
|
||||
* [List Products](https://docs.medusajs.com/api/admin#products_getproductsidvariants) API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The ID of the product to retrieve its variants.
|
||||
* @param query - Filters and pagination configurations.
|
||||
* @param headers - Headers to pass in the request.
|
||||
* @returns The paginated list of product variants.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve the list of product variants:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.listVariants("prod_123")
|
||||
* .then(({ variants, count, limit, offset }) => {
|
||||
* console.log(variants)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To configure the pagination, pass the `limit` and `offset` query parameters.
|
||||
*
|
||||
*
|
||||
* For example, to retrieve only 10 items and skip 10 items:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.listVariants("prod_123", {
|
||||
* limit: 10,
|
||||
@@ -569,10 +645,10 @@ export class Product {
|
||||
* console.log(variants)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Using the `fields` query parameter, you can specify the fields and relations to retrieve
|
||||
* in each product variant:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.listVariants("prod_123", {
|
||||
* fields: "id,*product"
|
||||
@@ -581,7 +657,7 @@ export class Product {
|
||||
* console.log(variants)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||
*/
|
||||
async listVariants(
|
||||
@@ -602,16 +678,16 @@ export class Product {
|
||||
* This method retrieves a product's variant. It sends a request to the
|
||||
* [Retrieve Variant](https://docs.medusajs.com/api/admin#products_getproductsidvariantsvariant_id)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param id - The variant's ID.
|
||||
* @param query - Configure the fields to retrieve in the product variant.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product variant's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve a product variant by its ID:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.retrieveVariant(
|
||||
* "prod_123",
|
||||
@@ -621,9 +697,9 @@ export class Product {
|
||||
* console.log(variant)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To specify the fields and relations to retrieve:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.retrieveVariant(
|
||||
* "prod_123",
|
||||
@@ -636,7 +712,7 @@ export class Product {
|
||||
* console.log(variant)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||
*/
|
||||
async retrieveVariant(
|
||||
@@ -658,12 +734,12 @@ export class Product {
|
||||
* This method deletes a product's variant. It sends a request to the
|
||||
* [Delete Variant](https://docs.medusajs.com/api/admin#products_deleteproductsidvariantsvariant_id)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param id - The ID of the variant.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The deletion's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.deleteVariant("prod_123", "variant_123")
|
||||
* .then(({ deleted }) => {
|
||||
@@ -681,22 +757,22 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method manages a product's variant's inventories to associate them with inventory items,
|
||||
* This method manages a product's variant's inventories to associate them with inventory items,
|
||||
* update their inventory items, or delete their association with inventory items.
|
||||
*
|
||||
* It sends a request to the
|
||||
*
|
||||
* It sends a request to the
|
||||
* [Manage Variant Inventory](https://docs.medusajs.com/api/admin#products_postproductsidvariantsinventoryitemsbatch)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The ID of the product that the variant belongs to.
|
||||
* @param body - The inventory items to create, update, or delete.
|
||||
* @param query - Pass query parameters in the request.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The details of the created, updated, or deleted inventory items.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.batchVariantInventoryItems(
|
||||
* "prod_123",
|
||||
* "prod_123",
|
||||
* {
|
||||
* create: [
|
||||
* {
|
||||
@@ -745,16 +821,16 @@ export class Product {
|
||||
* This method creates an option in a product. It sends a request to the
|
||||
* [Create Option](https://docs.medusajs.com/api/admin#products_postproductsidoptions)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param body - The option's details.
|
||||
* @param query - Configure the fields to retrieve in the product.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.createOption(
|
||||
* "prod_123",
|
||||
* "prod_123",
|
||||
* {
|
||||
* title: "Color",
|
||||
* values: ["Green", "Blue"]
|
||||
@@ -785,17 +861,17 @@ export class Product {
|
||||
* This method updates a product's option. It sends a request to the
|
||||
* [Update Option](https://docs.medusajs.com/api/admin#products_postproductsidoptionsoption_id)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param id - The ID of the option to update.
|
||||
* @param body - The data to update in the option.
|
||||
* @param query - Configure the fields to retrieve in the product.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.updateOption(
|
||||
* "prod_123",
|
||||
* "prod_123",
|
||||
* "prodopt_123",
|
||||
* {
|
||||
* title: "Color"
|
||||
@@ -824,28 +900,28 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves a paginated list of product options. It sends a request to the
|
||||
* This method retrieves a paginated list of product options. It sends a request to the
|
||||
* [List Options](https://docs.medusajs.com/api/admin#products_getproductsidoptions) API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The ID of the product to retrieve its options
|
||||
* @param query - Filters and pagination configurations.
|
||||
* @param headers - Headers to pass in the request.
|
||||
* @returns The paginated list of product options.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve the list of product options:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.listOptions("prod_123")
|
||||
* .then(({ product_options, count, limit, offset }) => {
|
||||
* console.log(product_options)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To configure the pagination, pass the `limit` and `offset` query parameters.
|
||||
*
|
||||
*
|
||||
* For example, to retrieve only 10 items and skip 10 items:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.listOptions("prod_123", {
|
||||
* limit: 10,
|
||||
@@ -855,10 +931,10 @@ export class Product {
|
||||
* console.log(product_options)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Using the `fields` query parameter, you can specify the fields and relations to retrieve
|
||||
* in each product options:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.listOptions("prod_123", {
|
||||
* fields: "id,title"
|
||||
@@ -867,7 +943,7 @@ export class Product {
|
||||
* console.log(product_options)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||
*/
|
||||
async listOptions(
|
||||
@@ -888,16 +964,16 @@ export class Product {
|
||||
* This method retrieves a product's option. It sends a request to the
|
||||
* [Get Option](https://docs.medusajs.com/api/admin#products_getproductsidoptionsoption_id)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param id - The product option's ID.
|
||||
* @param query - Configure the fields to retrieve in the product option.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The product option's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* To retrieve a product option by its ID:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.retrieveOption(
|
||||
* "prod_123",
|
||||
@@ -907,9 +983,9 @@ export class Product {
|
||||
* console.log(product_option)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To specify the fields and relations to retrieve:
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* sdk.admin.product.retrieveOption(
|
||||
* "prod_123",
|
||||
@@ -922,7 +998,7 @@ export class Product {
|
||||
* console.log(product_option)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||
*/
|
||||
async retrieveOption(
|
||||
@@ -941,15 +1017,15 @@ export class Product {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method deletes a product's option. It sends a request to the
|
||||
* This method deletes a product's option. It sends a request to the
|
||||
* [Delete Option](https://docs.medusajs.com/api/admin#products_deleteproductsidoptionsoption_id)
|
||||
* API route.
|
||||
*
|
||||
*
|
||||
* @param productId - The product's ID.
|
||||
* @param id - The option's ID.
|
||||
* @param headers - Headers to pass in the request
|
||||
* @returns The deletion's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* sdk.admin.product.deleteOption("prod_123", "prodopt_123")
|
||||
* .then(({ deleted }) => {
|
||||
|
||||
@@ -13,7 +13,12 @@ export type Config = {
|
||||
auth?: {
|
||||
type?: "jwt" | "session"
|
||||
jwtTokenStorageKey?: string
|
||||
jwtTokenStorageMethod?: "local" | "session" | "memory" | "custom" | "nostore"
|
||||
jwtTokenStorageMethod?:
|
||||
| "local"
|
||||
| "session"
|
||||
| "memory"
|
||||
| "custom"
|
||||
| "nostore"
|
||||
fetchCredentials?: "include" | "omit" | "same-origin"
|
||||
storage?: CustomStorage
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ export interface IFileProvider {
|
||||
/**
|
||||
* Get the file contents as a readable stream.
|
||||
*/
|
||||
getAsStream(fileData: ProviderGetFileDTO): Promise<Readable>
|
||||
getDownloadStream(fileData: ProviderGetFileDTO): Promise<Readable>
|
||||
|
||||
/**
|
||||
* Get the file contents as a Node.js Buffer
|
||||
|
||||
@@ -169,10 +169,10 @@ export interface IFileModuleService extends IModuleService {
|
||||
* Get the file contents as a readable stream.
|
||||
*
|
||||
* @example
|
||||
* const stream = await fileModuleService.getAsStream("file_123")
|
||||
* const stream = await fileModuleService.getDownloadStream("file_123")
|
||||
* writeable.pipe(stream)
|
||||
*/
|
||||
getAsStream(id: string, sharedContext?: Context): Promise<Readable>
|
||||
getDownloadStream(id: string, sharedContext?: Context): Promise<Readable>
|
||||
|
||||
/**
|
||||
* Get the file contents as a Node.js Buffer
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import { BaseUploadFile } from "../common"
|
||||
|
||||
export type AdminUploadFile = BaseUploadFile
|
||||
|
||||
export interface AdminUploadPreSignedUrlRequest {
|
||||
originalname: string
|
||||
mime_type: string
|
||||
size: number
|
||||
access?: "public" | "private"
|
||||
}
|
||||
|
||||
@@ -16,3 +16,36 @@ export interface AdminFileListResponse {
|
||||
}
|
||||
|
||||
export type AdminFileDeleteResponse = DeleteResponse<"file">
|
||||
|
||||
export interface AdminUploadPreSignedUrlResponse {
|
||||
/**
|
||||
* The URL to be used for uploading the file
|
||||
*/
|
||||
url: string
|
||||
|
||||
/**
|
||||
* The unique filename that can be used with the file provider
|
||||
* to fetch the file
|
||||
*/
|
||||
filename: string
|
||||
|
||||
/**
|
||||
* The original name of the file on the user's computer (aka clientName)
|
||||
*/
|
||||
originalname: string
|
||||
|
||||
/**
|
||||
* The mime type of the file.
|
||||
*/
|
||||
mime_type: string
|
||||
|
||||
/**
|
||||
* Extension of the file to be uploaded
|
||||
*/
|
||||
extension: string
|
||||
|
||||
/**
|
||||
* Size of the file to be uploaded (in bytes)
|
||||
*/
|
||||
size: number
|
||||
}
|
||||
|
||||
@@ -570,3 +570,11 @@ interface AdminDeleteProductVariantInventoryItem {
|
||||
*/
|
||||
variant_id: string
|
||||
}
|
||||
|
||||
export interface AdminImportProductsRequest {
|
||||
file_key: string
|
||||
originalname: string
|
||||
extension: string
|
||||
size: number
|
||||
mime_type: string
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export interface AdminExportProductResponse {
|
||||
|
||||
export interface AdminImportProductResponse {
|
||||
/**
|
||||
* The ID of the import product workflow execution's transaction.
|
||||
* The ID of the import product workflow execution's transaction.
|
||||
* This is useful to confirm the import using the `/admin/products/:transaction-id/import` API route.
|
||||
*/
|
||||
transaction_id: string
|
||||
@@ -73,6 +73,9 @@ export interface AdminImportProductResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AdminImportProductsResponse
|
||||
extends AdminImportProductResponse {}
|
||||
|
||||
export interface AdminBatchProductVariantResponse
|
||||
extends BatchResponse<AdminProductVariant> {}
|
||||
|
||||
|
||||
@@ -187,8 +187,8 @@ export class AbstractFileProviderService implements IFileProvider {
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
getAsStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
throw Error("getAsStream must be overridden by the child class")
|
||||
getDownloadStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
throw Error("getDownloadStream must be overridden by the child class")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
import {
|
||||
importProductsWorkflowId,
|
||||
waitConfirmationProductImportStepId,
|
||||
} from "@medusajs/core-flows"
|
||||
import { IWorkflowEngineService } from "@medusajs/framework/types"
|
||||
import { Modules, TransactionHandlerType } from "@medusajs/framework/utils"
|
||||
import { StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const workflowEngineService: IWorkflowEngineService = req.scope.resolve(
|
||||
Modules.WORKFLOW_ENGINE
|
||||
)
|
||||
const transactionId = req.params.transaction_id
|
||||
|
||||
await workflowEngineService.setStepSuccess({
|
||||
idempotencyKey: {
|
||||
action: TransactionHandlerType.INVOKE,
|
||||
transactionId,
|
||||
stepId: waitConfirmationProductImportStepId,
|
||||
workflowId: importProductsWorkflowId,
|
||||
},
|
||||
stepResponse: new StepResponse(true),
|
||||
})
|
||||
|
||||
res.status(202).json({})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
MedusaResponse,
|
||||
AuthenticatedMedusaRequest,
|
||||
} from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import type { HttpTypes } from "@medusajs/framework/types"
|
||||
import { importProductsWorkflow } from "@medusajs/core-flows"
|
||||
import type { AdminImportProductsType } from "../validators"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminImportProductsType>,
|
||||
res: MedusaResponse<HttpTypes.AdminImportProductResponse>
|
||||
) => {
|
||||
const fileProvider = req.scope.resolve(Modules.FILE)
|
||||
const file = await fileProvider.getAsBuffer(req.validatedBody.file_key)
|
||||
|
||||
const { result, transaction } = await importProductsWorkflow(req.scope).run({
|
||||
input: {
|
||||
filename: req.validatedBody.originalname,
|
||||
fileContent: file.toString("utf-8"),
|
||||
},
|
||||
})
|
||||
|
||||
res
|
||||
.status(202)
|
||||
.json({ transaction_id: transaction.transactionId, summary: result })
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
validateAndTransformBody,
|
||||
validateAndTransformQuery,
|
||||
} from "@medusajs/framework"
|
||||
import { maybeApplyLinkFilter, MiddlewareRoute } from "@medusajs/framework/http"
|
||||
import multer from "multer"
|
||||
import { maybeApplyLinkFilter, MiddlewareRoute } from "@medusajs/framework/http"
|
||||
import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils/middlewares"
|
||||
import { createBatchBody } from "../../utils/validators"
|
||||
import * as QueryConfig from "./query-config"
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
AdminGetProductsParams,
|
||||
AdminGetProductVariantParams,
|
||||
AdminGetProductVariantsParams,
|
||||
AdminImportProducts,
|
||||
AdminUpdateProduct,
|
||||
AdminUpdateProductOption,
|
||||
AdminUpdateProductVariant,
|
||||
@@ -34,9 +35,6 @@ import {
|
||||
} from "./validators"
|
||||
import IndexEngineFeatureFlag from "../../../loaders/feature-flags/index-engine"
|
||||
|
||||
// TODO: For now we keep the files in memory, as that's how they get passed to the workflows
|
||||
// This will need revisiting once we are closer to prod-ready v2, since with workflows and potentially
|
||||
// services on other machines using streams is not as simple as it used to be.
|
||||
const upload = multer({ storage: multer.memoryStorage() })
|
||||
|
||||
export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
@@ -104,6 +102,11 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
matcher: "/admin/products/import",
|
||||
middlewares: [upload.single("file")],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/imports",
|
||||
middlewares: [validateAndTransformBody(AdminImportProducts)],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/import/:transaction_id/confirm",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BatchMethodRequest } from "@medusajs/framework/types"
|
||||
import { BatchMethodRequest, HttpTypes } from "@medusajs/framework/types"
|
||||
import { ProductStatus } from "@medusajs/framework/utils"
|
||||
import { z } from "zod"
|
||||
import { z, ZodType } from "zod"
|
||||
import {
|
||||
applyAndAndOrOperators,
|
||||
booleanString,
|
||||
@@ -341,3 +341,12 @@ export type AdminBatchVariantInventoryItemsType = BatchMethodRequest<
|
||||
AdminBatchUpdateVariantInventoryItemType,
|
||||
AdminBatchDeleteVariantInventoryItemType
|
||||
>
|
||||
|
||||
export const AdminImportProducts = z.object({
|
||||
file_key: z.string(),
|
||||
originalname: z.string(),
|
||||
extension: z.string(),
|
||||
size: z.number(),
|
||||
mime_type: z.string(),
|
||||
}) satisfies ZodType<HttpTypes.AdminImportProductsRequest>
|
||||
export type AdminImportProductsType = z.infer<typeof AdminImportProducts>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import multer from "multer"
|
||||
import { MiddlewareRoute } from "@medusajs/framework/http"
|
||||
import {
|
||||
MiddlewareRoute,
|
||||
validateAndTransformBody,
|
||||
} from "@medusajs/framework/http"
|
||||
import { validateAndTransformQuery } from "@medusajs/framework"
|
||||
import { retrieveUploadConfig } from "./query-config"
|
||||
import { AdminGetUploadParams } from "./validators"
|
||||
import { AdminGetUploadParams, AdminUploadPreSignedUrl } from "./validators"
|
||||
|
||||
// TODO: For now we keep the files in memory, as that's how they get passed to the workflows
|
||||
// This will need revisiting once we are closer to prod-ready v2, since with workflows and potentially
|
||||
@@ -31,4 +34,9 @@ export const adminUploadRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
matcher: "/admin/uploads/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/uploads/presigned-urls",
|
||||
middlewares: [validateAndTransformBody(AdminUploadPreSignedUrl)],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ulid } from "ulid"
|
||||
import { MIMEType } from "util"
|
||||
import type {
|
||||
MedusaResponse,
|
||||
AuthenticatedMedusaRequest,
|
||||
} from "@medusajs/framework/http"
|
||||
import {
|
||||
Modules,
|
||||
MedusaError,
|
||||
MedusaErrorTypes,
|
||||
} from "@medusajs/framework/utils"
|
||||
import type { HttpTypes } from "@medusajs/framework/types"
|
||||
import type { AdminUploadPreSignedUrlType } from "../validators"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminUploadPreSignedUrlType>,
|
||||
res: MedusaResponse<HttpTypes.AdminUploadPreSignedUrlResponse>
|
||||
) => {
|
||||
const fileProvider = req.scope.resolve(Modules.FILE)
|
||||
let type: MIMEType
|
||||
|
||||
try {
|
||||
type = new MIMEType(req.validatedBody.mime_type)
|
||||
} catch {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
`Invalid file type "${req.validatedBody.mime_type}"`,
|
||||
MedusaErrorTypes.INVALID_DATA
|
||||
)
|
||||
}
|
||||
|
||||
const extension = type.subtype
|
||||
const uniqueFilename = `${ulid()}.${extension}`
|
||||
|
||||
const response = await fileProvider.getUploadFileUrls({
|
||||
filename: uniqueFilename,
|
||||
mimeType: req.validatedBody.mime_type,
|
||||
access: req.validatedBody.access ?? "private",
|
||||
})
|
||||
|
||||
res.json({
|
||||
url: response.url,
|
||||
filename: response.key,
|
||||
mime_type: type.toString(),
|
||||
size: req.validatedBody.size,
|
||||
extension,
|
||||
originalname: req.validatedBody.originalname,
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,17 @@
|
||||
import { z, ZodType } from "zod"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { createSelectParams } from "../../utils/validators"
|
||||
import { z } from "zod"
|
||||
|
||||
export type AdminGetUploadParamsType = z.infer<typeof AdminGetUploadParams>
|
||||
export const AdminGetUploadParams = createSelectParams()
|
||||
|
||||
export const AdminUploadPreSignedUrl = z.object({
|
||||
originalname: z.string(),
|
||||
mime_type: z.string(),
|
||||
size: z.number(),
|
||||
access: z.enum(["public", "private"]).optional(),
|
||||
}) satisfies ZodType<HttpTypes.AdminUploadPreSignedUrlRequest>
|
||||
|
||||
export type AdminUploadPreSignedUrlType = z.infer<
|
||||
typeof AdminUploadPreSignedUrl
|
||||
>
|
||||
|
||||
@@ -167,8 +167,8 @@ export default class FileModuleService implements FileTypes.IFileModuleService {
|
||||
* const stream = await fileModuleService.getAsStream("file_123")
|
||||
* writeable.pipe(stream)
|
||||
*/
|
||||
getAsStream(id: string): Promise<Readable> {
|
||||
return this.fileProviderService_.getAsStream({ fileKey: id })
|
||||
getDownloadStream(id: string): Promise<Readable> {
|
||||
return this.fileProviderService_.getDownloadStream({ fileKey: id })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,8 +70,8 @@ export default class FileProviderService {
|
||||
return this.fileProvider_.getPresignedUploadUrl(fileData)
|
||||
}
|
||||
|
||||
getAsStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
return this.fileProvider_.getAsStream(fileData)
|
||||
getDownloadStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
return this.fileProvider_.getDownloadStream(fileData)
|
||||
}
|
||||
|
||||
getAsBuffer(fileData: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
|
||||
|
||||
@@ -85,7 +85,9 @@ export class LocalFileService extends AbstractFileProviderService {
|
||||
return
|
||||
}
|
||||
|
||||
async getAsStream(file: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
async getDownloadStream(
|
||||
file: FileTypes.ProviderGetFileDTO
|
||||
): Promise<Readable> {
|
||||
const baseDir = file.fileKey.startsWith("private-")
|
||||
? this.privateUploadDir_
|
||||
: this.uploadDir_
|
||||
@@ -124,6 +126,47 @@ export class LocalFileService extends AbstractFileProviderService {
|
||||
return this.getUploadFileUrl(file.fileKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pre-signed URL that the client (frontend) can use to trigger
|
||||
* a file upload. In this case, the Medusa backend will implement the
|
||||
* "/upload" endpoint to perform the file upload.
|
||||
*
|
||||
* Since, we do not want the client to perform link detection on the frontend
|
||||
* and then prepare a different kind of request for cloud providers and different
|
||||
* request for the local server, we will have to make these URLs self sufficient.
|
||||
*
|
||||
* What is a self sufficient URL
|
||||
*
|
||||
* - There should be no need to specify the MIME type or filename separately in request body (cloud providers don't allow it).
|
||||
* - There should be no need to pass auth headers like cookies. Again cloud providers
|
||||
* won't allow it and will likely result in a CORS error.
|
||||
*/
|
||||
async getPresignedUploadUrl(
|
||||
fileData: FileTypes.ProviderGetPresignedUploadUrlDTO
|
||||
): Promise<FileTypes.ProviderFileResultDTO> {
|
||||
if (!fileData?.filename) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`No filename provided`
|
||||
)
|
||||
}
|
||||
|
||||
const uploadUrl = new URL(
|
||||
"upload",
|
||||
`${this.backendUrl_.replace(/\/$/, "")}/`
|
||||
)
|
||||
|
||||
uploadUrl.searchParams.set("filename", fileData.filename)
|
||||
if (fileData.mimeType) {
|
||||
uploadUrl.searchParams.set("type", fileData.mimeType)
|
||||
}
|
||||
|
||||
return {
|
||||
url: uploadUrl.toString(),
|
||||
key: fileData.filename,
|
||||
}
|
||||
}
|
||||
|
||||
private getUploadFilePath = (baseDir: string, fileKey: string) => {
|
||||
return path.join(baseDir, fileKey)
|
||||
}
|
||||
|
||||
@@ -217,15 +217,17 @@ export class S3FileService extends AbstractFileProviderService {
|
||||
}
|
||||
}
|
||||
|
||||
async getAsStream(file: FileTypes.ProviderGetFileDTO): Promise<Readable> {
|
||||
if (!file?.filename) {
|
||||
async getDownloadStream(
|
||||
file: FileTypes.ProviderGetFileDTO
|
||||
): Promise<Readable> {
|
||||
if (!file?.fileKey) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`No filename provided`
|
||||
`No fileKey provided`
|
||||
)
|
||||
}
|
||||
|
||||
const fileKey = `${this.config_.prefix}${file.filename}`
|
||||
const fileKey = `${this.config_.prefix}${file.fileKey}`
|
||||
const response = await this.client_.send(
|
||||
new GetObjectCommand({
|
||||
Key: fileKey,
|
||||
@@ -237,14 +239,14 @@ export class S3FileService extends AbstractFileProviderService {
|
||||
}
|
||||
|
||||
async getAsBuffer(file: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
|
||||
if (!file?.filename) {
|
||||
if (!file?.fileKey) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`No filename provided`
|
||||
`No fileKey provided`
|
||||
)
|
||||
}
|
||||
|
||||
const fileKey = `${this.config_.prefix}${file.filename}`
|
||||
const fileKey = `${this.config_.prefix}${file.fileKey}`
|
||||
const response = await this.client_.send(
|
||||
new GetObjectCommand({
|
||||
Key: fileKey,
|
||||
|
||||
Reference in New Issue
Block a user