feat: add product admin v2 endpoints (#6579)
This implementation obviously lacks a lot of things, and there are a lot of TODOs. However, there are already a lot of questions I'd rather get answered soon, so I figured it's much easier to do the implementation in steps. I wrote down all breaking changes, suggested changes, and new additions with comments (TODO and Note). In a follow-up PR I will: Add the remaining/missing models Make the workflows handle all interactions between the different models/modules Add integration tests
This commit is contained in:
@@ -13,7 +13,7 @@ import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
describe("CreateProduct workflow", function () {
|
||||
describe.skip("CreateProduct workflow", function () {
|
||||
let medusaContainer
|
||||
let shutdownServer
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { simpleProductFactory } from "../../../../factories"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
describe("UpdateProduct workflow", function () {
|
||||
describe.skip("UpdateProduct workflow", function () {
|
||||
let dbConnection
|
||||
let medusaContainer
|
||||
let shutdownServer
|
||||
|
||||
@@ -10,3 +10,4 @@ export * from "./user"
|
||||
export * from "./tax"
|
||||
export * from "./api-key"
|
||||
export * from "./store"
|
||||
export * from "./product"
|
||||
|
||||
2
packages/core-flows/src/product/index.ts
Normal file
2
packages/core-flows/src/product/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./steps"
|
||||
export * from "./workflows"
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const createProductOptionsStepId = "create-product-options"
|
||||
export const createProductOptionsStep = createStep(
|
||||
createProductOptionsStepId,
|
||||
async (data: ProductTypes.CreateProductOptionDTO[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const created = await service.createOptions(data)
|
||||
return new StepResponse(
|
||||
created,
|
||||
created.map((productOption) => productOption.id)
|
||||
)
|
||||
},
|
||||
async (createdIds, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.deleteOptions(createdIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const createProductVariantsStepId = "create-product-variants"
|
||||
export const createProductVariantsStep = createStep(
|
||||
createProductVariantsStepId,
|
||||
async (data: ProductTypes.CreateProductVariantDTO[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const created = await service.createVariants(data)
|
||||
return new StepResponse(
|
||||
created,
|
||||
created.map((productVariant) => productVariant.id)
|
||||
)
|
||||
},
|
||||
async (createdIds, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.deleteVariants(createdIds)
|
||||
}
|
||||
)
|
||||
30
packages/core-flows/src/product/steps/create-products.ts
Normal file
30
packages/core-flows/src/product/steps/create-products.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const createProductsStepId = "create-products"
|
||||
export const createProductsStep = createStep(
|
||||
createProductsStepId,
|
||||
async (data: ProductTypes.CreateProductDTO[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const created = await service.create(data)
|
||||
return new StepResponse(
|
||||
created,
|
||||
created.map((product) => product.id)
|
||||
)
|
||||
},
|
||||
async (createdIds, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.delete(createdIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const deleteProductOptionsStepId = "delete-product-options"
|
||||
export const deleteProductOptionsStep = createStep(
|
||||
deleteProductOptionsStepId,
|
||||
async (ids: string[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.softDeleteOptions(ids)
|
||||
return new StepResponse(void 0, ids)
|
||||
},
|
||||
async (prevIds, { container }) => {
|
||||
if (!prevIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.restoreOptions(prevIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const deleteProductVariantsStepId = "delete-product-variants"
|
||||
export const deleteProductVariantsStep = createStep(
|
||||
deleteProductVariantsStepId,
|
||||
async (ids: string[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.softDeleteVariants(ids)
|
||||
return new StepResponse(void 0, ids)
|
||||
},
|
||||
async (prevIds, { container }) => {
|
||||
if (!prevIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.restoreVariants(prevIds)
|
||||
}
|
||||
)
|
||||
27
packages/core-flows/src/product/steps/delete-products.ts
Normal file
27
packages/core-flows/src/product/steps/delete-products.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
export const deleteProductsStepId = "delete-products"
|
||||
export const deleteProductsStep = createStep(
|
||||
deleteProductsStepId,
|
||||
async (ids: string[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.softDelete(ids)
|
||||
return new StepResponse(void 0, ids)
|
||||
},
|
||||
async (prevIds, { container }) => {
|
||||
if (!prevIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.restore(prevIds)
|
||||
}
|
||||
)
|
||||
9
packages/core-flows/src/product/steps/index.ts
Normal file
9
packages/core-flows/src/product/steps/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from "./create-products"
|
||||
export * from "./update-products"
|
||||
export * from "./delete-products"
|
||||
export * from "./create-product-options"
|
||||
export * from "./update-product-options"
|
||||
export * from "./delete-product-options"
|
||||
export * from "./create-product-variants"
|
||||
export * from "./update-product-variants"
|
||||
export * from "./delete-product-variants"
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type UpdateProductOptionsStepInput = {
|
||||
selector: ProductTypes.FilterableProductOptionProps
|
||||
update: ProductTypes.UpdateProductOptionDTO
|
||||
}
|
||||
|
||||
export const updateProductOptionsStepId = "update-product-options"
|
||||
export const updateProductOptionsStep = createStep(
|
||||
updateProductOptionsStepId,
|
||||
async (data: UpdateProductOptionsStepInput, { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.listOptions(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
// TODO: We need to update the module's signature
|
||||
// const productOptions = await service.updateOptions(data.selector, data.update)
|
||||
const productOptions = []
|
||||
return new StepResponse(productOptions, prevData)
|
||||
},
|
||||
async (prevData, { container }) => {
|
||||
if (!prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
// TODO: We need to update the module's signature
|
||||
// await service.upsertOptions(
|
||||
// prevData.map((r) => ({
|
||||
// ...r,
|
||||
// }))
|
||||
// )
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type UpdateProductVariantsStepInput = {
|
||||
selector: ProductTypes.FilterableProductVariantProps
|
||||
update: ProductTypes.UpdateProductVariantDTO
|
||||
}
|
||||
|
||||
export const updateProductVariantsStepId = "update-product-variants"
|
||||
export const updateProductVariantsStep = createStep(
|
||||
updateProductVariantsStepId,
|
||||
async (data: UpdateProductVariantsStepInput, { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.listVariants(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
// TODO: We need to update the module's signature
|
||||
// const productVariants = await service.updateVariants(data.selector, data.update)
|
||||
const productVariants = []
|
||||
return new StepResponse(productVariants, prevData)
|
||||
},
|
||||
async (prevData, { container }) => {
|
||||
if (!prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
// TODO: We need to update the module's signature
|
||||
// await service.upsertVariants(
|
||||
// prevData.map((r) => ({
|
||||
// ...r,
|
||||
// }))
|
||||
// )
|
||||
}
|
||||
)
|
||||
49
packages/core-flows/src/product/steps/update-products.ts
Normal file
49
packages/core-flows/src/product/steps/update-products.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type UpdateProductsStepInput = {
|
||||
selector: ProductTypes.FilterableProductProps
|
||||
update: ProductTypes.UpdateProductDTO
|
||||
}
|
||||
|
||||
export const updateProductsStepId = "update-products"
|
||||
export const updateProductsStep = createStep(
|
||||
updateProductsStepId,
|
||||
async (data: UpdateProductsStepInput, { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.list(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
// TODO: We need to update the module's signature
|
||||
// const products = await service.update(data.selector, data.update)
|
||||
const products = []
|
||||
return new StepResponse(products, prevData)
|
||||
},
|
||||
async (prevData, { container }) => {
|
||||
if (!prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
// TODO: We need to update the module's signature
|
||||
// await service.upsert(
|
||||
// prevData.map((r) => ({
|
||||
// ...r,
|
||||
// }))
|
||||
// )
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { createProductOptionsStep } from "../steps"
|
||||
|
||||
type WorkflowInput = { product_options: ProductTypes.CreateProductOptionDTO[] }
|
||||
|
||||
export const createProductOptionsWorkflowId = "create-product-options"
|
||||
export const createProductOptionsWorkflow = createWorkflow(
|
||||
createProductOptionsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductOptionDTO[]> => {
|
||||
return createProductOptionsStep(input.product_options)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { createProductVariantsStep } from "../steps"
|
||||
|
||||
type WorkflowInput = {
|
||||
product_variants: ProductTypes.CreateProductVariantDTO[]
|
||||
}
|
||||
|
||||
export const createProductVariantsWorkflowId = "create-product-variants"
|
||||
export const createProductVariantsWorkflow = createWorkflow(
|
||||
createProductVariantsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductVariantDTO[]> => {
|
||||
return createProductVariantsStep(input.product_variants)
|
||||
}
|
||||
)
|
||||
15
packages/core-flows/src/product/workflows/create-products.ts
Normal file
15
packages/core-flows/src/product/workflows/create-products.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { createProductsStep } from "../steps"
|
||||
|
||||
type WorkflowInput = { products: ProductTypes.CreateProductDTO[] }
|
||||
|
||||
export const createProductsWorkflowId = "create-products"
|
||||
export const createProductsWorkflow = createWorkflow(
|
||||
createProductsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductDTO[]> => {
|
||||
return createProductsStep(input.products)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { deleteProductOptionsStep } from "../steps"
|
||||
|
||||
type WorkflowInput = { ids: string[] }
|
||||
|
||||
export const deleteProductOptionsWorkflowId = "delete-product-options"
|
||||
export const deleteProductOptionsWorkflow = createWorkflow(
|
||||
deleteProductOptionsWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
|
||||
return deleteProductOptionsStep(input.ids)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { deleteProductVariantsStep } from "../steps"
|
||||
|
||||
type WorkflowInput = { ids: string[] }
|
||||
|
||||
export const deleteProductVariantsWorkflowId = "delete-product-variants"
|
||||
export const deleteProductVariantsWorkflow = createWorkflow(
|
||||
deleteProductVariantsWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
|
||||
return deleteProductVariantsStep(input.ids)
|
||||
}
|
||||
)
|
||||
12
packages/core-flows/src/product/workflows/delete-products.ts
Normal file
12
packages/core-flows/src/product/workflows/delete-products.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { deleteProductsStep } from "../steps"
|
||||
|
||||
type WorkflowInput = { ids: string[] }
|
||||
|
||||
export const deleteProductsWorkflowId = "delete-products"
|
||||
export const deleteProductsWorkflow = createWorkflow(
|
||||
deleteProductsWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
|
||||
return deleteProductsStep(input.ids)
|
||||
}
|
||||
)
|
||||
9
packages/core-flows/src/product/workflows/index.ts
Normal file
9
packages/core-flows/src/product/workflows/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from "./create-products"
|
||||
export * from "./delete-products"
|
||||
export * from "./update-products"
|
||||
export * from "./create-product-options"
|
||||
export * from "./delete-product-options"
|
||||
export * from "./update-product-options"
|
||||
export * from "./create-product-variants"
|
||||
export * from "./delete-product-variants"
|
||||
export * from "./update-product-variants"
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { updateProductOptionsStep } from "../steps"
|
||||
|
||||
type UpdateProductOptionsStepInput = {
|
||||
selector: ProductTypes.FilterableProductOptionProps
|
||||
update: ProductTypes.UpdateProductOptionDTO
|
||||
}
|
||||
|
||||
type WorkflowInput = UpdateProductOptionsStepInput
|
||||
|
||||
export const updateProductOptionsWorkflowId = "update-product-options"
|
||||
export const updateProductOptionsWorkflow = createWorkflow(
|
||||
updateProductOptionsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductOptionDTO[]> => {
|
||||
return updateProductOptionsStep(input)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { updateProductVariantsStep } from "../steps"
|
||||
|
||||
type UpdateProductVariantsStepInput = {
|
||||
selector: ProductTypes.FilterableProductVariantProps
|
||||
update: ProductTypes.UpdateProductVariantDTO
|
||||
}
|
||||
|
||||
type WorkflowInput = UpdateProductVariantsStepInput
|
||||
|
||||
export const updateProductVariantsWorkflowId = "update-product-variants"
|
||||
export const updateProductVariantsWorkflow = createWorkflow(
|
||||
updateProductVariantsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductVariantDTO[]> => {
|
||||
return updateProductVariantsStep(input)
|
||||
}
|
||||
)
|
||||
20
packages/core-flows/src/product/workflows/update-products.ts
Normal file
20
packages/core-flows/src/product/workflows/update-products.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { updateProductsStep } from "../steps"
|
||||
|
||||
type UpdateProductsStepInput = {
|
||||
selector: ProductTypes.FilterableProductProps
|
||||
update: ProductTypes.UpdateProductDTO
|
||||
}
|
||||
|
||||
type WorkflowInput = UpdateProductsStepInput
|
||||
|
||||
export const updateProductsWorkflowId = "update-products"
|
||||
export const updateProductsWorkflow = createWorkflow(
|
||||
updateProductsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductDTO[]> => {
|
||||
return updateProductsStep(input)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../types/routing"
|
||||
import {
|
||||
deleteProductOptionsWorkflow,
|
||||
updateProductOptionsWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
|
||||
import { UpdateProductDTO } from "@medusajs/types"
|
||||
import { defaultAdminProductsOptionFields } from "../../../query-config"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
// TODO: Should we allow fetching a option without knowing the product ID? In such case we'll need to change the route to /admin/products/options/:id
|
||||
const productId = req.params.id
|
||||
const optionId = req.params.option_id
|
||||
|
||||
const variables = { id: optionId, product_id: productId }
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_option",
|
||||
variables,
|
||||
fields: defaultAdminProductsOptionFields,
|
||||
})
|
||||
|
||||
const [product_option] = await remoteQuery(queryObject)
|
||||
res.status(200).json({ product_option })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<UpdateProductDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// TODO: Should we allow fetching a option without knowing the product ID? In such case we'll need to change the route to /admin/products/options/:id
|
||||
const productId = req.params.id
|
||||
const optionId = req.params.option_id
|
||||
|
||||
const { result, errors } = await updateProductOptionsWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: optionId, product_id: productId },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ product_option: result[0] })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// TODO: Should we allow fetching a option without knowing the product ID? In such case we'll need to change the route to /admin/products/options/:id
|
||||
const productId = req.params.id
|
||||
const optionId = req.params.option_id
|
||||
|
||||
// TODO: I believe here we cannot even enforce the product ID based on the standard API we provide?
|
||||
const { errors } = await deleteProductOptionsWorkflow(req.scope).run({
|
||||
input: { ids: [optionId] /* product_id: productId */ },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
id: optionId,
|
||||
object: "product_option",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
|
||||
import { CreateProductOptionDTO } from "@medusajs/types"
|
||||
import { createProductOptionsWorkflow } from "@medusajs/core-flows"
|
||||
import { defaultAdminProductsOptionFields } from "../../query-config"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
const productId = req.params.id
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_option",
|
||||
variables: {
|
||||
filters: { ...req.filterableFields, product_id: productId },
|
||||
order: req.listConfig.order,
|
||||
skip: req.listConfig.skip,
|
||||
take: req.listConfig.take,
|
||||
},
|
||||
fields: defaultAdminProductsOptionFields,
|
||||
})
|
||||
|
||||
const { rows: product_options, metadata } = await remoteQuery(queryObject)
|
||||
|
||||
res.json({
|
||||
product_options,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<CreateProductOptionDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const input = [
|
||||
{
|
||||
...req.validatedBody,
|
||||
},
|
||||
]
|
||||
|
||||
const { result, errors } = await createProductOptionsWorkflow(req.scope).run({
|
||||
input: { product_options: input },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ product_option: result[0] })
|
||||
}
|
||||
72
packages/medusa/src/api-v2/admin/products/[id]/route.ts
Normal file
72
packages/medusa/src/api-v2/admin/products/[id]/route.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../types/routing"
|
||||
import {
|
||||
deleteProductsWorkflow,
|
||||
updateProductsWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
|
||||
import { UpdateProductDTO } from "@medusajs/types"
|
||||
import { defaultAdminProductFields } from "../query-config"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
const variables = { id: req.params.id }
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables,
|
||||
fields: defaultAdminProductFields,
|
||||
})
|
||||
|
||||
const [product] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ product })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<UpdateProductDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const { result, errors } = await updateProductsWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ product: result[0] })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
|
||||
const { errors } = await deleteProductsWorkflow(req.scope).run({
|
||||
input: { ids: [id] },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
id,
|
||||
object: "product",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../types/routing"
|
||||
import {
|
||||
deleteProductVariantsWorkflow,
|
||||
updateProductVariantsWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
|
||||
import { UpdateProductVariantDTO } from "@medusajs/types"
|
||||
import { defaultAdminProductsVariantFields } from "../../../query-config"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
// TODO: Should we allow fetching a variant without knowing the product ID? In such case we'll need to change the route to /admin/products/variants/:id
|
||||
const productId = req.params.id
|
||||
const variantId = req.params.variant_id
|
||||
|
||||
const variables = { id: variantId, product_id: productId }
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_variant",
|
||||
variables,
|
||||
fields: defaultAdminProductsVariantFields,
|
||||
})
|
||||
|
||||
const [product_variant] = await remoteQuery(queryObject)
|
||||
res.status(200).json({ product_variant })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<UpdateProductVariantDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// TODO: Should we allow fetching a variant without knowing the product ID? In such case we'll need to change the route to /admin/products/variants/:id
|
||||
const productId = req.params.id
|
||||
const variantId = req.params.variant_id
|
||||
|
||||
const { result, errors } = await updateProductVariantsWorkflow(req.scope).run(
|
||||
{
|
||||
input: {
|
||||
selector: { id: variantId, product_id: productId },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
throwOnError: false,
|
||||
}
|
||||
)
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ product_variant: result[0] })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// TODO: Should we allow fetching a variant without knowing the product ID? In such case we'll need to change the route to /admin/products/variants/:id
|
||||
const productId = req.params.id
|
||||
const variantId = req.params.variant_id
|
||||
|
||||
// TODO: I believe here we cannot even enforce the product ID based on the standard API we provide?
|
||||
const { errors } = await deleteProductVariantsWorkflow(req.scope).run({
|
||||
input: { ids: [variantId] /* product_id: productId */ },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
id: variantId,
|
||||
object: "product_variant",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
|
||||
import { CreateProductVariantDTO } from "@medusajs/types"
|
||||
import { createProductVariantsWorkflow } from "@medusajs/core-flows"
|
||||
import { defaultAdminProductsVariantFields } from "../../query-config"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
const productId = req.params.id
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_variant",
|
||||
variables: {
|
||||
filters: { ...req.filterableFields, product_id: productId },
|
||||
order: req.listConfig.order,
|
||||
skip: req.listConfig.skip,
|
||||
take: req.listConfig.take,
|
||||
},
|
||||
fields: defaultAdminProductsVariantFields,
|
||||
})
|
||||
|
||||
const { rows: product_variants, metadata } = await remoteQuery(queryObject)
|
||||
|
||||
res.json({
|
||||
product_variants,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<CreateProductVariantDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const input = [
|
||||
{
|
||||
...req.validatedBody,
|
||||
},
|
||||
]
|
||||
|
||||
const { result, errors } = await createProductVariantsWorkflow(req.scope).run(
|
||||
{
|
||||
input: { product_variants: input },
|
||||
throwOnError: false,
|
||||
}
|
||||
)
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ product_variant: result[0] })
|
||||
}
|
||||
139
packages/medusa/src/api-v2/admin/products/middlewares.ts
Normal file
139
packages/medusa/src/api-v2/admin/products/middlewares.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
|
||||
import {
|
||||
AdminGetProductsOptionsParams,
|
||||
AdminGetProductsParams,
|
||||
AdminGetProductsProductOptionsOptionParams,
|
||||
AdminGetProductsProductParams,
|
||||
AdminGetProductsProductVariantsVariantParams,
|
||||
AdminGetProductsVariantsParams,
|
||||
AdminPostProductsProductOptionsOptionReq,
|
||||
AdminPostProductsProductOptionsReq,
|
||||
AdminPostProductsProductReq,
|
||||
AdminPostProductsProductVariantsReq,
|
||||
AdminPostProductsProductVariantsVariantReq,
|
||||
AdminPostProductsReq,
|
||||
} from "./validators"
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
|
||||
export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["ALL"],
|
||||
matcher: "/admin/products*",
|
||||
middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
|
||||
},
|
||||
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductsParams,
|
||||
QueryConfig.listTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductsProductParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products",
|
||||
middlewares: [transformBody(AdminPostProductsReq)],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id",
|
||||
middlewares: [transformBody(AdminPostProductsProductReq)],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/products/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id/variants",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductsVariantsParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
// Note: New endpoint in v2
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id/variants/:variant_id",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductsProductVariantsVariantParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/variants",
|
||||
middlewares: [transformBody(AdminPostProductsProductVariantsReq)],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/variants/:variant_id",
|
||||
middlewares: [transformBody(AdminPostProductsProductVariantsVariantReq)],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/products/:id/variants/:variant_id",
|
||||
middlewares: [],
|
||||
},
|
||||
|
||||
// Note: New endpoint in v2
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id/options",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductsOptionsParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
// Note: New endpoint in v2
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/products/:id/options/:option_id",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductsProductOptionsOptionParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/options",
|
||||
middlewares: [transformBody(AdminPostProductsProductOptionsReq)],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/options/:option_id",
|
||||
middlewares: [transformBody(AdminPostProductsProductOptionsOptionReq)],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/products/:id/options/:option_id",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
85
packages/medusa/src/api-v2/admin/products/query-config.ts
Normal file
85
packages/medusa/src/api-v2/admin/products/query-config.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
export const defaultAdminProductRelations = [
|
||||
"variants",
|
||||
// TODO: Add in next iteration
|
||||
// "variants.prices",
|
||||
// TODO: See how this should be handled
|
||||
// "variants.options",
|
||||
"images",
|
||||
// TODO: What is this?
|
||||
// "profiles",
|
||||
"options",
|
||||
// TODO: See how this should be handled
|
||||
// "options.values",
|
||||
// TODO: Handle in next iteration
|
||||
// "tags",
|
||||
// "type",
|
||||
// "collection",
|
||||
]
|
||||
export const allowedAdminProductRelations = [...defaultAdminProductRelations]
|
||||
export const defaultAdminProductFields = [
|
||||
"id",
|
||||
"title",
|
||||
"subtitle",
|
||||
"status",
|
||||
"external_id",
|
||||
"description",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"discountable",
|
||||
"thumbnail",
|
||||
// TODO: Handle in next iteration
|
||||
// "collection_id",
|
||||
// "type_id",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
"width",
|
||||
"hs_code",
|
||||
"origin_country",
|
||||
"mid_code",
|
||||
"material",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
]
|
||||
|
||||
export const retrieveTransformQueryConfig = {
|
||||
defaultFields: defaultAdminProductFields,
|
||||
defaultRelations: defaultAdminProductRelations,
|
||||
allowedRelations: allowedAdminProductRelations,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listTransformQueryConfig = {
|
||||
defaultLimit: 50,
|
||||
isList: true,
|
||||
}
|
||||
|
||||
export const defaultAdminProductsVariantFields = [
|
||||
"id",
|
||||
"product_id",
|
||||
"title",
|
||||
"sku",
|
||||
"inventory_quantity",
|
||||
"allow_backorder",
|
||||
"manage_inventory",
|
||||
"hs_code",
|
||||
"origin_country",
|
||||
"mid_code",
|
||||
"material",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
"width",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"variant_rank",
|
||||
"ean",
|
||||
"upc",
|
||||
"barcode",
|
||||
]
|
||||
|
||||
export const defaultAdminProductsOptionFields = ["id", "title"]
|
||||
58
packages/medusa/src/api-v2/admin/products/route.ts
Normal file
58
packages/medusa/src/api-v2/admin/products/route.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../types/routing"
|
||||
|
||||
import { CreateProductDTO } from "@medusajs/types"
|
||||
import { createProductsWorkflow } from "@medusajs/core-flows"
|
||||
import { defaultAdminProductFields } from "./query-config"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables: {
|
||||
filters: req.filterableFields,
|
||||
order: req.listConfig.order,
|
||||
skip: req.listConfig.skip,
|
||||
take: req.listConfig.take,
|
||||
},
|
||||
fields: defaultAdminProductFields,
|
||||
})
|
||||
|
||||
const { rows: products, metadata } = await remoteQuery(queryObject)
|
||||
|
||||
res.json({
|
||||
products,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<CreateProductDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const input = [
|
||||
{
|
||||
...req.validatedBody,
|
||||
},
|
||||
]
|
||||
|
||||
const { result, errors } = await createProductsWorkflow(req.scope).run({
|
||||
input: { products: input },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ product: result[0] })
|
||||
}
|
||||
662
packages/medusa/src/api-v2/admin/products/validators.ts
Normal file
662
packages/medusa/src/api-v2/admin/products/validators.ts
Normal file
@@ -0,0 +1,662 @@
|
||||
import { OperatorMap } from "@medusajs/types"
|
||||
import { Transform, Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
NotEquals,
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
import { OperatorMapValidator } from "../../../types/validators/operator-map"
|
||||
import { ProductStatus } from "@medusajs/utils"
|
||||
import { IsType } from "../../../utils"
|
||||
import { optionalBooleanMapper } from "../../../utils/validators/is-boolean"
|
||||
|
||||
export class AdminGetProductsProductParams extends FindParams {}
|
||||
export class AdminGetProductsProductVariantsVariantParams extends FindParams {}
|
||||
export class AdminGetProductsProductOptionsOptionParams extends FindParams {}
|
||||
|
||||
/**
|
||||
* Parameters used to filter and configure the pagination of the retrieved regions.
|
||||
*/
|
||||
export class AdminGetProductsParams extends extendedFindParamsMixin({
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
}) {
|
||||
// TODO: Will search be handled the same way? Should it be part of the `findParams` class instead, or the mixin?
|
||||
/**
|
||||
* Search term to search products' title, description, variants' title and sku, and collections' title.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
q?: string
|
||||
|
||||
/**
|
||||
* IDs to filter products by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsType([String, [String]])
|
||||
id?: string | string[]
|
||||
|
||||
/**
|
||||
* Statuses to filter products by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsEnum(ProductStatus, { each: true })
|
||||
status?: ProductStatus[]
|
||||
|
||||
/**
|
||||
* Title to filter products by.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* Handle to filter products by.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
handle?: string
|
||||
|
||||
// TODO: Should we remove this? It makes sense for search, but not for equality comparison
|
||||
/**
|
||||
* Description to filter products by.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Filter products by whether they're gift cards.
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
|
||||
is_giftcard?: boolean
|
||||
|
||||
// TODO: Add in next iteration
|
||||
// /**
|
||||
// * Filter products by their associated price lists' ID.
|
||||
// */
|
||||
// @IsArray()
|
||||
// @IsOptional()
|
||||
// price_list_id?: string[]
|
||||
|
||||
// /**
|
||||
// * Filter products by their associated product collection's ID.
|
||||
// */
|
||||
// @IsArray()
|
||||
// @IsOptional()
|
||||
// collection_id?: string[]
|
||||
|
||||
// /**
|
||||
// * Filter products by their associated tags' value.
|
||||
// */
|
||||
// @IsArray()
|
||||
// @IsOptional()
|
||||
// tags?: string[]
|
||||
|
||||
// /**
|
||||
// * Filter products by their associated product type's ID.
|
||||
// */
|
||||
// @IsArray()
|
||||
// @IsOptional()
|
||||
// type_id?: string[]
|
||||
|
||||
// /**
|
||||
// * Filter products by their associated sales channels' ID.
|
||||
// */
|
||||
// @FeatureFlagDecorators(SalesChannelFeatureFlag.key, [IsOptional(), IsArray()])
|
||||
// sales_channel_id?: string[]
|
||||
|
||||
// /**
|
||||
// * Filter products by their associated discount condition's ID.
|
||||
// */
|
||||
// @IsString()
|
||||
// @IsOptional()
|
||||
// discount_condition_id?: string
|
||||
|
||||
// /**
|
||||
// * Filter products by their associated product category's ID.
|
||||
// */
|
||||
// @IsArray()
|
||||
// @IsOptional()
|
||||
// category_id?: string[]
|
||||
|
||||
// /**
|
||||
// * Whether to include product category children in the response.
|
||||
// *
|
||||
// * @featureFlag product_categories
|
||||
// */
|
||||
// @IsBoolean()
|
||||
// @IsOptional()
|
||||
// @Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
|
||||
// include_category_children?: boolean
|
||||
|
||||
// TODO: The OperatorMap and DateOperator are slightly different, so the date comparisons is a breaking change.
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
created_at?: OperatorMap<string>
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
updated_at?: OperatorMap<string>
|
||||
|
||||
@ValidateNested()
|
||||
@IsOptional()
|
||||
@Type(() => OperatorMapValidator)
|
||||
deleted_at?: OperatorMap<string>
|
||||
|
||||
// Note: These are new in v2
|
||||
// Additional filters from BaseFilterable
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductsParams)
|
||||
$and?: AdminGetProductsParams[]
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductsParams)
|
||||
$or?: AdminGetProductsParams[]
|
||||
}
|
||||
|
||||
export class AdminGetProductsVariantsParams extends extendedFindParamsMixin({
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
}) {
|
||||
/**
|
||||
* Search term to search product variants' title, sku, and products' title.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
q?: string
|
||||
|
||||
/**
|
||||
* IDs to filter product variants by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsType([String, [String]])
|
||||
id?: string | string[]
|
||||
|
||||
// TODO: This should be part of the Mixin or base FindParams
|
||||
// /**
|
||||
// * The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`.
|
||||
// */
|
||||
// @IsString()
|
||||
// @IsOptional()
|
||||
// order?: string
|
||||
|
||||
/**
|
||||
* Filter product variants by whether their inventory is managed or not.
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
|
||||
manage_inventory?: boolean
|
||||
|
||||
/**
|
||||
* Filter product variants by whether they are allowed to be backordered or not.
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
|
||||
allow_backorder?: boolean
|
||||
|
||||
// TODO: The OperatorMap and DateOperator are slightly different, so the date comparisons is a breaking change.
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
created_at?: OperatorMap<string>
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
updated_at?: OperatorMap<string>
|
||||
|
||||
@ValidateNested()
|
||||
@IsOptional()
|
||||
@Type(() => OperatorMapValidator)
|
||||
deleted_at?: OperatorMap<string>
|
||||
|
||||
// Note: These are new in v2
|
||||
// Additional filters from BaseFilterable
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductsVariantsParams)
|
||||
$and?: AdminGetProductsVariantsParams[]
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductsVariantsParams)
|
||||
$or?: AdminGetProductsVariantsParams[]
|
||||
}
|
||||
|
||||
// Note: This model and endpoint are new in v2
|
||||
export class AdminGetProductsOptionsParams extends extendedFindParamsMixin({
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
}) {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
title?: string
|
||||
|
||||
// Note: These are new in v2
|
||||
// Additional filters from BaseFilterable
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductsOptionsParams)
|
||||
$and?: AdminGetProductsOptionsParams[]
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductsOptionsParams)
|
||||
$or?: AdminGetProductsOptionsParams[]
|
||||
}
|
||||
|
||||
export class AdminPostProductsReq {
|
||||
@IsString()
|
||||
title: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
subtitle?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string
|
||||
|
||||
@IsBoolean()
|
||||
is_giftcard = false
|
||||
|
||||
@IsBoolean()
|
||||
discountable = true
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
images?: string[]
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
thumbnail?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
handle?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ProductStatus)
|
||||
status?: ProductStatus = ProductStatus.DRAFT
|
||||
|
||||
// TODO: Add in next iteration
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductTypeReq)
|
||||
// @ValidateNested()
|
||||
// type?: ProductTypeReq
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// collection_id?: string
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductTagReq)
|
||||
// @ValidateNested({ each: true })
|
||||
// @IsArray()
|
||||
// tags?: ProductTagReq[]
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductProductCategoryReq)
|
||||
// @ValidateNested({ each: true })
|
||||
// @IsArray()
|
||||
// categories?: ProductProductCategoryReq[]
|
||||
|
||||
// TODO: Deal with in next iteration
|
||||
// @FeatureFlagDecorators(SalesChannelFeatureFlag.key, [
|
||||
// IsOptional(),
|
||||
// Type(() => ProductSalesChannelReq),
|
||||
// ValidateNested({ each: true }),
|
||||
// IsArray(),
|
||||
// ])
|
||||
// sales_channels?: ProductSalesChannelReq[]
|
||||
|
||||
// TODO: I suggest we don't allow creation options and variants in 1 call, but rather do it through separate endpoints.
|
||||
@IsOptional()
|
||||
@Type(() => AdminPostProductsProductOptionsReq)
|
||||
@ValidateNested({ each: true })
|
||||
@IsArray()
|
||||
options?: AdminPostProductsProductOptionsReq[]
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => AdminPostProductsProductVariantsReq)
|
||||
@ValidateNested({ each: true })
|
||||
@IsArray()
|
||||
variants?: AdminPostProductsProductVariantsReq[]
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
weight?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
length?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
height?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
width?: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
hs_code?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
origin_country?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
mid_code?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
material?: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export class AdminPostProductsProductReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
subtitle?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
discountable?: boolean
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
images?: string[]
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
thumbnail?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
handle?: string
|
||||
|
||||
@IsEnum(ProductStatus)
|
||||
@NotEquals(null)
|
||||
@ValidateIf((_, value) => value !== undefined)
|
||||
status?: ProductStatus
|
||||
|
||||
// TODO: Deal with in next iteration
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductTypeReq)
|
||||
// @ValidateNested()
|
||||
// type?: ProductTypeReq
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// collection_id?: string
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductTagReq)
|
||||
// @ValidateNested({ each: true })
|
||||
// @IsArray()
|
||||
// tags?: ProductTagReq[]
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductProductCategoryReq)
|
||||
// @ValidateNested({ each: true })
|
||||
// @IsArray()
|
||||
// categories?: ProductProductCategoryReq[]
|
||||
|
||||
// @FeatureFlagDecorators(SalesChannelFeatureFlag.key, [
|
||||
// IsOptional(),
|
||||
// Type(() => ProductSalesChannelReq),
|
||||
// ValidateNested({ each: true }),
|
||||
// IsArray(),
|
||||
// ])
|
||||
// sales_channels?: ProductSalesChannelReq[] | null
|
||||
|
||||
// TODO: Should we remove this on update?
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductVariantReq)
|
||||
// @ValidateNested({ each: true })
|
||||
// @IsArray()
|
||||
// variants?: ProductVariantReq[]
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
weight?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
length?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
height?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
width?: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
hs_code?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
origin_country?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
mid_code?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
material?: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export class AdminPostProductsProductVariantsReq {
|
||||
@IsString()
|
||||
title: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
sku?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
ean?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
upc?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
barcode?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
hs_code?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
mid_code?: string
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
inventory_quantity?: number = 0
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
allow_backorder?: boolean
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
manage_inventory?: boolean = true
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
weight?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
length?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
height?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
width?: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
origin_country?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
material?: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
// TODO: Add on next iteration
|
||||
// @IsArray()
|
||||
// @ValidateNested({ each: true })
|
||||
// @Type(() => ProductVariantPricesCreateReq)
|
||||
// prices: ProductVariantPricesCreateReq[]
|
||||
|
||||
// TODO: Think how these link to the `options` on the product-level
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductVariantOptionReq)
|
||||
// @ValidateNested({ each: true })
|
||||
// @IsArray()
|
||||
// options?: ProductVariantOptionReq[] = []
|
||||
}
|
||||
|
||||
export class AdminPostProductsProductVariantsVariantReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
sku?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
ean?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
upc?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
barcode?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
hs_code?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
mid_code?: string
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
inventory_quantity?: number
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
allow_backorder?: boolean
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
manage_inventory?: boolean
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
weight?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
length?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
height?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
width?: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
origin_country?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
material?: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
// TODO: Deal with in next iteration
|
||||
// @IsArray()
|
||||
// @IsOptional()
|
||||
// @ValidateNested({ each: true })
|
||||
// @Type(() => ProductVariantPricesUpdateReq)
|
||||
// prices?: ProductVariantPricesUpdateReq[]
|
||||
|
||||
// TODO: Align handling with the create case.
|
||||
// @Type(() => ProductVariantOptionReq)
|
||||
// @ValidateNested({ each: true })
|
||||
// @IsOptional()
|
||||
// @IsArray()
|
||||
// options?: ProductVariantOptionReq[] = []
|
||||
}
|
||||
|
||||
export class AdminPostProductsProductOptionsReq {
|
||||
@IsString()
|
||||
title: string
|
||||
}
|
||||
|
||||
export class AdminPostProductsProductOptionsOptionReq {
|
||||
@IsString()
|
||||
title: string
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { adminCurrencyRoutesMiddlewares } from "./admin/currencies/middlewares"
|
||||
import { adminCustomerGroupRoutesMiddlewares } from "./admin/customer-groups/middlewares"
|
||||
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
||||
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
||||
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||
import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares"
|
||||
@@ -41,5 +42,6 @@ export const config: MiddlewaresConfig = {
|
||||
...adminStoreRoutesMiddlewares,
|
||||
...adminCurrencyRoutesMiddlewares,
|
||||
...storeCurrencyRoutesMiddlewares,
|
||||
...adminProductRoutesMiddlewares,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1332,6 +1332,74 @@ export interface IProductModuleService extends IModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
/**
|
||||
* This method is used to delete options. Unlike the {@link delete} method, this method won't completely remove the option. It can still be accessed or retrieved using methods like {@link retrieve} if you pass the `withDeleted` property to the `config` object parameter.
|
||||
*
|
||||
* The soft-deleted options can be restored using the {@link restore} method.
|
||||
*
|
||||
* @param {string[]} optionIds - The IDs of the options to soft-delete.
|
||||
* @param {SoftDeleteReturn<TReturnableLinkableKeys>} config -
|
||||
* Configurations determining which relations to soft delete along with the each of the options. You can pass to its `returnLinkableKeys`
|
||||
* property any of the option's relation attribute names, such as `option_value_id`.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<Record<string, string[]> | void>}
|
||||
* An object that includes the IDs of related records that were also soft deleted. The object's keys are the ID attribute names of the option entity's relations, and its value is an array of strings, each being the ID of a record associated with the option through this relation.
|
||||
*
|
||||
* If there are no related records, the promise resolved to `void`.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function deleteOptions (ids: string[]) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const cascadedEntities = await productModule.softDeleteOptions(ids)
|
||||
*
|
||||
* // do something with the returned cascaded entity IDs or return them
|
||||
* }
|
||||
*/
|
||||
softDeleteOptions<TReturnableLinkableKeys extends string = string>(
|
||||
optionIds: string[],
|
||||
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
/**
|
||||
* This method is used to restore options which were deleted using the {@link softDelete} method.
|
||||
*
|
||||
* @param {string[]} optionIds - The IDs of the options to restore.
|
||||
* @param {RestoreReturn<TReturnableLinkableKeys>} config -
|
||||
* Configurations determining which relations to restore along with each of the options. You can pass to its `returnLinkableKeys`
|
||||
* property any of the option's relation attribute names.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<Record<string, string[]> | void>}
|
||||
* An object that includes the IDs of related records that were restored. The object's keys are the ID attribute names of the option entity's relations, and its value is an array of strings, each being the ID of the record associated with the option through this relation.
|
||||
*
|
||||
* If there are no related records that were restored, the promise resolved to `void`.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function restoreOptions (ids: string[]) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const cascadedEntities = await productModule.restoreOptions(ids, {
|
||||
* returnLinkableKeys: ["option_value_id"]
|
||||
* })
|
||||
*
|
||||
* // do something with the returned cascaded entity IDs or return them
|
||||
* }
|
||||
*/
|
||||
restoreOptions<TReturnableLinkableKeys extends string = string>(
|
||||
optionIds: string[],
|
||||
config?: RestoreReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
/**
|
||||
* This method is used to retrieve a product variant by its ID.
|
||||
*
|
||||
@@ -1674,6 +1742,74 @@ export interface IProductModuleService extends IModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductVariantDTO[], number]>
|
||||
|
||||
/**
|
||||
* This method is used to delete variants. Unlike the {@link delete} method, this method won't completely remove the variant. It can still be accessed or retrieved using methods like {@link retrieve} if you pass the `withDeleted` property to the `config` object parameter.
|
||||
*
|
||||
* The soft-deleted variants can be restored using the {@link restore} method.
|
||||
*
|
||||
* @param {string[]} variantIds - The IDs of the variants to soft-delete.
|
||||
* @param {SoftDeleteReturn<TReturnableLinkableKeys>} config -
|
||||
* Configurations determining which relations to soft delete along with the each of the variants. You can pass to its `returnLinkableKeys`
|
||||
* property any of the variant's relation attribute names, such as `option_value_id`.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<Record<string, string[]> | void>}
|
||||
* An object that includes the IDs of related records that were also soft deleted. The object's keys are the ID attribute names of the variant entity's relations, and its value is an array of strings, each being the ID of a record associated with the variant through this relation.
|
||||
*
|
||||
* If there are no related records, the promise resolved to `void`.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function deleteProductVariants (ids: string[]) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const cascadedEntities = await productModule.softDeleteVariants(ids)
|
||||
*
|
||||
* // do something with the returned cascaded entity IDs or return them
|
||||
* }
|
||||
*/
|
||||
softDeleteVariants<TReturnableLinkableKeys extends string = string>(
|
||||
variantIds: string[],
|
||||
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
/**
|
||||
* This method is used to restore variants which were deleted using the {@link softDelete} method.
|
||||
*
|
||||
* @param {string[]} variantIds - The IDs of the variants to restore.
|
||||
* @param {RestoreReturn<TReturnableLinkableKeys>} config -
|
||||
* Configurations determining which relations to restore along with each of the variants. You can pass to its `returnLinkableKeys`
|
||||
* property any of the variant's relation attribute names.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<Record<string, string[]> | void>}
|
||||
* An object that includes the IDs of related records that were restored. The object's keys are the ID attribute names of the variant entity's relations, and its value is an array of strings, each being the ID of the record associated with the variant through this relation.
|
||||
*
|
||||
* If there are no related records that were restored, the promise resolved to `void`.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function restoreVariants (ids: string[]) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const cascadedEntities = await productModule.restoreVariants(ids, {
|
||||
* returnLinkableKeys: ["option_value_id"]
|
||||
* })
|
||||
*
|
||||
* // do something with the returned cascaded entity IDs or return them
|
||||
* }
|
||||
*/
|
||||
restoreVariants<TReturnableLinkableKeys extends string = string>(
|
||||
variantIds: string[],
|
||||
config?: RestoreReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
/**
|
||||
* This method is used to retrieve a product collection by its ID.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user