feat: Add v2 product types endpoints (#6880)
Also adjusts the product type module APIs to follow the conventions
This commit is contained in:
8
.changeset/fifty-badgers-crash.md
Normal file
8
.changeset/fifty-badgers-crash.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@medusajs/product": minor
|
||||
"@medusajs/medusa": minor
|
||||
"@medusajs/types": minor
|
||||
"@medusajs/core-flows": patch
|
||||
---
|
||||
|
||||
Add v2 product type endpoints and adjust the product module
|
||||
@@ -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 createProductTypesStepId = "create-product-types"
|
||||
export const createProductTypesStep = createStep(
|
||||
createProductTypesStepId,
|
||||
async (data: ProductTypes.CreateProductTypeDTO[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const created = await service.createTypes(data)
|
||||
return new StepResponse(
|
||||
created,
|
||||
created.map((productType) => productType.id)
|
||||
)
|
||||
},
|
||||
async (createdIds, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.deleteTypes(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 deleteProductTypesStepId = "delete-product-types"
|
||||
export const deleteProductTypesStep = createStep(
|
||||
deleteProductTypesStepId,
|
||||
async (ids: string[], { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.softDeleteTypes(ids)
|
||||
return new StepResponse(void 0, ids)
|
||||
},
|
||||
async (prevIds, { container }) => {
|
||||
if (!prevIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.restoreTypes(prevIds)
|
||||
}
|
||||
)
|
||||
@@ -13,3 +13,6 @@ export * from "./delete-product-variants"
|
||||
export * from "./create-collections"
|
||||
export * from "./update-collections"
|
||||
export * from "./delete-collections"
|
||||
export * from "./create-product-types"
|
||||
export * from "./update-product-types"
|
||||
export * from "./delete-product-types"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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 UpdateProductTypesStepInput = {
|
||||
selector: ProductTypes.FilterableProductTypeProps
|
||||
update: ProductTypes.UpdateProductTypeDTO
|
||||
}
|
||||
|
||||
export const updateProductTypesStepId = "update-product-types"
|
||||
export const updateProductTypesStep = createStep(
|
||||
updateProductTypesStepId,
|
||||
async (data: UpdateProductTypesStepInput, { container }) => {
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.listTypes(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
const productTypes = await service.updateTypes(data.selector, data.update)
|
||||
return new StepResponse(productTypes, prevData)
|
||||
},
|
||||
async (prevData, { container }) => {
|
||||
if (!prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IProductModuleService>(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await service.upsertTypes(prevData)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { createProductTypesStep } from "../steps"
|
||||
|
||||
type WorkflowInput = { product_types: ProductTypes.CreateProductTypeDTO[] }
|
||||
|
||||
export const createProductTypesWorkflowId = "create-product-types"
|
||||
export const createProductTypesWorkflow = createWorkflow(
|
||||
createProductTypesWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductTypeDTO[]> => {
|
||||
return createProductTypesStep(input.product_types)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { deleteProductTypesStep } from "../steps"
|
||||
|
||||
type WorkflowInput = { ids: string[] }
|
||||
|
||||
export const deleteProductTypesWorkflowId = "delete-product-types"
|
||||
export const deleteProductTypesWorkflow = createWorkflow(
|
||||
deleteProductTypesWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
|
||||
return deleteProductTypesStep(input.ids)
|
||||
}
|
||||
)
|
||||
@@ -10,3 +10,6 @@ export * from "./update-product-variants"
|
||||
export * from "./create-collections"
|
||||
export * from "./delete-collections"
|
||||
export * from "./update-collections"
|
||||
export * from "./create-product-types"
|
||||
export * from "./delete-product-types"
|
||||
export * from "./update-product-types"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { updateProductTypesStep } from "../steps"
|
||||
|
||||
type UpdateProductTypesStepInput = {
|
||||
selector: ProductTypes.FilterableProductTypeProps
|
||||
update: ProductTypes.UpdateProductTypeDTO
|
||||
}
|
||||
|
||||
type WorkflowInput = UpdateProductTypesStepInput
|
||||
|
||||
export const updateProductTypesWorkflowId = "update-product-types"
|
||||
export const updateProductTypesWorkflow = createWorkflow(
|
||||
updateProductTypesWorkflowId,
|
||||
(
|
||||
input: WorkflowData<WorkflowInput>
|
||||
): WorkflowData<ProductTypes.ProductTypeDTO[]> => {
|
||||
return updateProductTypesStep(input)
|
||||
}
|
||||
)
|
||||
78
packages/medusa/src/api-v2/admin/product-types/[id]/route.ts
Normal file
78
packages/medusa/src/api-v2/admin/product-types/[id]/route.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
deleteProductTypesWorkflow,
|
||||
updateProductTypesWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../types/routing"
|
||||
|
||||
import { UpdateProductTypeDTO } from "@medusajs/types"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
import { refetchProductType } from "../helpers"
|
||||
|
||||
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_type",
|
||||
variables,
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const [product_type] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ product_type })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<UpdateProductTypeDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const { result, errors } = await updateProductTypesWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const productType = await refetchProductType(
|
||||
result[0].id,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.status(200).json({ product_type: productType })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
|
||||
const { errors } = await deleteProductTypesWorkflow(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_type",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
20
packages/medusa/src/api-v2/admin/product-types/helpers.ts
Normal file
20
packages/medusa/src/api-v2/admin/product-types/helpers.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
export const refetchProductType = async (
|
||||
productTypeId: string,
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
const remoteQuery = scope.resolve("remoteQuery")
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_type",
|
||||
variables: {
|
||||
filters: { id: productTypeId },
|
||||
},
|
||||
fields: fields,
|
||||
})
|
||||
|
||||
const productTypes = await remoteQuery(queryObject)
|
||||
return productTypes[0]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
|
||||
import {
|
||||
AdminGetProductTypesProductTypeParams,
|
||||
AdminGetProductTypesParams,
|
||||
AdminPostProductTypesProductTypeReq,
|
||||
AdminPostProductTypesReq,
|
||||
} from "./validators"
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
|
||||
export const adminProductTypeRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["ALL"],
|
||||
matcher: "/admin/product-types/*",
|
||||
middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
|
||||
},
|
||||
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/product-types",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductTypesParams,
|
||||
QueryConfig.listProductTypesTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/product-types/:id",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetProductTypesProductTypeParams,
|
||||
QueryConfig.retrieveProductTypeTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
// Create/update/delete methods are new in v2
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/product-types",
|
||||
middlewares: [
|
||||
transformBody(AdminPostProductTypesReq),
|
||||
transformQuery(
|
||||
AdminGetProductTypesParams,
|
||||
QueryConfig.retrieveProductTypeTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/product-types/:id",
|
||||
middlewares: [
|
||||
transformBody(AdminPostProductTypesProductTypeReq),
|
||||
transformQuery(
|
||||
AdminGetProductTypesProductTypeParams,
|
||||
QueryConfig.retrieveProductTypeTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/product-types/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
export const defaultAdminProductTypeFields = [
|
||||
"id",
|
||||
"value",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
export const retrieveProductTypeTransformQueryConfig = {
|
||||
defaults: defaultAdminProductTypeFields,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listProductTypesTransformQueryConfig = {
|
||||
...retrieveProductTypeTransformQueryConfig,
|
||||
defaultLimit: 20,
|
||||
isList: true,
|
||||
}
|
||||
63
packages/medusa/src/api-v2/admin/product-types/route.ts
Normal file
63
packages/medusa/src/api-v2/admin/product-types/route.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../types/routing"
|
||||
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
AdminGetProductTypesParams,
|
||||
AdminPostProductTypesReq,
|
||||
} from "./validators"
|
||||
import { createProductTypesWorkflow } from "@medusajs/core-flows"
|
||||
import { refetchProductType } from "./helpers"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest<AdminGetProductTypesParams>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables: {
|
||||
filters: req.filterableFields,
|
||||
...req.remoteQueryConfig.pagination,
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const { rows: product_types, metadata } = await remoteQuery(queryObject)
|
||||
|
||||
res.json({
|
||||
product_types: product_types,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminPostProductTypesReq>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const input = [req.validatedBody]
|
||||
|
||||
const { result, errors } = await createProductTypesWorkflow(req.scope).run({
|
||||
input: { product_types: input },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const productType = await refetchProductType(
|
||||
result[0].id,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.status(200).json({ product_type: productType })
|
||||
}
|
||||
113
packages/medusa/src/api-v2/admin/product-types/validators.ts
Normal file
113
packages/medusa/src/api-v2/admin/product-types/validators.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { OperatorMap } from "@medusajs/types"
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
import { OperatorMapValidator } from "../../../types/validators/operator-map"
|
||||
|
||||
export class AdminGetProductTypesProductTypeParams extends FindParams {}
|
||||
|
||||
/**
|
||||
* Parameters used to filter and configure the pagination of the retrieved product types.
|
||||
*/
|
||||
export class AdminGetProductTypesParams extends extendedFindParamsMixin({
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
}) {
|
||||
/**
|
||||
* Term to search product product types by their title and handle.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
q?: string
|
||||
|
||||
/**
|
||||
* Id to filter product product types by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
id?: string | string[]
|
||||
|
||||
/**
|
||||
* Title to filter product product types by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
value?: string | string[] | OperatorMap<string>
|
||||
|
||||
/**
|
||||
* Handle to filter product product types by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
handle?: string | string[]
|
||||
|
||||
/**
|
||||
* Date filters to apply on the product product types' `created_at` date.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
created_at?: OperatorMap<string>
|
||||
|
||||
/**
|
||||
* Date filters to apply on the product product types' `updated_at` date.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
updated_at?: OperatorMap<string>
|
||||
|
||||
/**
|
||||
* Date filters to apply on the product product types' `deleted_at` date.
|
||||
*/
|
||||
@ValidateNested()
|
||||
@IsOptional()
|
||||
@Type(() => OperatorMapValidator)
|
||||
deleted_at?: OperatorMap<string>
|
||||
|
||||
// TODO: To be added in next iteration
|
||||
// /**
|
||||
// * Filter product types by their associated discount condition's ID.
|
||||
// */
|
||||
// @IsString()
|
||||
// @IsOptional()
|
||||
// discount_condition_id?: string
|
||||
|
||||
// Note: These are new in v2
|
||||
// Additional filters from BaseFilterable
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductTypesParams)
|
||||
$and?: AdminGetProductTypesParams[]
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetProductTypesParams)
|
||||
$or?: AdminGetProductTypesParams[]
|
||||
}
|
||||
|
||||
export class AdminPostProductTypesReq {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
value: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export class AdminPostProductTypesProductTypeReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
value?: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares"
|
||||
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
||||
import { adminPricingRoutesMiddlewares } from "./admin/pricing/middlewares"
|
||||
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
||||
import { adminProductTypeRoutesMiddlewares } from "./admin/product-types/middlewares"
|
||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||
import { adminSalesChannelRoutesMiddlewares } from "./admin/sales-channels/middlewares"
|
||||
@@ -59,5 +60,6 @@ export const config: MiddlewaresConfig = {
|
||||
...adminFulfillmentRoutesMiddlewares,
|
||||
...adminSalesChannelRoutesMiddlewares,
|
||||
...adminStockLocationRoutesMiddlewares,
|
||||
...adminProductTypeRoutesMiddlewares,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -211,12 +211,9 @@ moduleIntegrationTestRunner({
|
||||
const typeId = "type-1"
|
||||
|
||||
it("should update the value of the type successfully", async () => {
|
||||
await service.updateTypes([
|
||||
{
|
||||
id: typeId,
|
||||
value: "UK",
|
||||
},
|
||||
])
|
||||
await service.updateTypes(typeId, {
|
||||
value: "UK",
|
||||
})
|
||||
|
||||
const productType = await service.retrieveType(typeId)
|
||||
|
||||
@@ -227,18 +224,15 @@ moduleIntegrationTestRunner({
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.updateTypes([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
value: "UK",
|
||||
},
|
||||
])
|
||||
await service.updateTypes("does-not-exist", {
|
||||
value: "UK",
|
||||
})
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(
|
||||
'ProductType with id "does-not-exist" not found'
|
||||
"ProductType with id: does-not-exist was not found"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
UpdateProductInput,
|
||||
UpdateProductOptionInput,
|
||||
UpdateProductVariantInput,
|
||||
UpdateTypeInput,
|
||||
} from "../types"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "./../joiner-config"
|
||||
|
||||
@@ -425,34 +426,118 @@ export default class ProductModuleService<
|
||||
return await this.baseRepository_.serialize(productTags)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createTypes(
|
||||
createTypes(
|
||||
data: ProductTypes.CreateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTypeDTO[]> {
|
||||
const productTypes = await this.productTypeService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTypeDTO[]>
|
||||
createTypes(
|
||||
data: ProductTypes.CreateProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTypeDTO>
|
||||
|
||||
return await this.baseRepository_.serialize(productTypes, {
|
||||
populate: true,
|
||||
})
|
||||
@InjectManager("baseRepository_")
|
||||
async createTypes(
|
||||
data:
|
||||
| ProductTypes.CreateProductTypeDTO[]
|
||||
| ProductTypes.CreateProductTypeDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const types = await this.productTypeService_.create(input, sharedContext)
|
||||
|
||||
const createdTypes = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductTypeDTO[]
|
||||
>(types)
|
||||
|
||||
return Array.isArray(data) ? createdTypes : createdTypes[0]
|
||||
}
|
||||
|
||||
async upsertTypes(
|
||||
data: ProductTypes.UpsertProductTypeDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTypeDTO[]>
|
||||
async upsertTypes(
|
||||
data: ProductTypes.UpsertProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTypeDTO>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updateTypes(
|
||||
data: ProductTypes.UpdateProductTypeDTO[],
|
||||
async upsertTypes(
|
||||
data:
|
||||
| ProductTypes.UpsertProductTypeDTO[]
|
||||
| ProductTypes.UpsertProductTypeDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTypeDTO[]> {
|
||||
const productTypes = await this.productTypeService_.update(
|
||||
data,
|
||||
): Promise<ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter((type): type is UpdateTypeInput => !!type.id)
|
||||
const forCreate = input.filter(
|
||||
(type): type is ProductTypes.CreateProductTypeDTO => !type.id
|
||||
)
|
||||
|
||||
let created: ProductType[] = []
|
||||
let updated: ProductType[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
created = await this.productTypeService_.create(forCreate, sharedContext)
|
||||
}
|
||||
if (forUpdate.length) {
|
||||
updated = await this.productTypeService_.update(forUpdate, sharedContext)
|
||||
}
|
||||
|
||||
const result = [...created, ...updated]
|
||||
const allTypes = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO
|
||||
>(result)
|
||||
|
||||
return Array.isArray(data) ? allTypes : allTypes[0]
|
||||
}
|
||||
|
||||
updateTypes(
|
||||
id: string,
|
||||
data: ProductTypes.UpdateProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTypeDTO>
|
||||
updateTypes(
|
||||
selector: ProductTypes.FilterableProductTypeProps,
|
||||
data: ProductTypes.UpdateProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTypeDTO[]>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async updateTypes(
|
||||
idOrSelector: string | ProductTypes.FilterableProductTypeProps,
|
||||
data: ProductTypes.UpdateProductTypeDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO> {
|
||||
let normalizedInput: UpdateTypeInput[] = []
|
||||
if (isString(idOrSelector)) {
|
||||
// Check if the type exists in the first place
|
||||
await this.productTypeService_.retrieve(idOrSelector, {}, sharedContext)
|
||||
normalizedInput = [{ id: idOrSelector, ...data }]
|
||||
} else {
|
||||
const types = await this.productTypeService_.list(
|
||||
idOrSelector,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
normalizedInput = types.map((type) => ({
|
||||
id: type.id,
|
||||
...data,
|
||||
}))
|
||||
}
|
||||
|
||||
const types = await this.productTypeService_.update(
|
||||
normalizedInput,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize(productTypes, {
|
||||
populate: true,
|
||||
})
|
||||
const updatedTypes = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductTypeDTO[]
|
||||
>(types)
|
||||
|
||||
return isString(idOrSelector) ? updatedTypes[0] : updatedTypes
|
||||
}
|
||||
|
||||
createOptions(
|
||||
|
||||
@@ -53,6 +53,10 @@ export type UpdateCollectionInput = ProductTypes.UpdateProductCollectionDTO & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type UpdateTypeInput = ProductTypes.UpdateProductTypeDTO & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type UpdateProductVariantInput = ProductTypes.UpdateProductVariantDTO & {
|
||||
id: string
|
||||
product_id?: string | null
|
||||
|
||||
@@ -1002,7 +1002,7 @@ export interface UpdateProductTypeDTO {
|
||||
/**
|
||||
* Holds custom data in key-value pairs.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
UpsertProductCollectionDTO,
|
||||
UpsertProductDTO,
|
||||
UpsertProductOptionDTO,
|
||||
UpsertProductTypeDTO,
|
||||
UpsertProductVariantDTO,
|
||||
} from "./common"
|
||||
|
||||
@@ -1202,32 +1203,150 @@ export interface IProductModuleService extends IModuleService {
|
||||
): Promise<ProductTypeDTO[]>
|
||||
|
||||
/**
|
||||
* This method is used to update a product type
|
||||
* This method is used to create a product type.
|
||||
*
|
||||
* @param {UpdateProductTypeDTO[]} data - The product types to be updated, each having the attributes that should be updated in the product type.
|
||||
* @param {CreateProductTypeDTO} data - The product type to be created.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<ProductTypeDTO[]>} The list of updated product types.
|
||||
* @returns {Promise<ProductTypeDTO>} The created product type.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function updateProductType (id: string, value: string) {
|
||||
* async function createType (title: string) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const productTypes = await productModule.updateTypes([
|
||||
* const type = await productModule.createTypes(
|
||||
* {
|
||||
* value
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* // do something with the product type or return them
|
||||
* }
|
||||
*
|
||||
*/
|
||||
createTypes(
|
||||
data: CreateProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypeDTO>
|
||||
|
||||
/**
|
||||
* This method updates existing types, or creates new ones if they don't exist.
|
||||
*
|
||||
* @param {UpsertProductTypeDTO[]} data - The attributes to update or create for each type.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<ProductTypeDTO[]>} The updated and created types.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function upsertTypes (title: string) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const createdTypes = await productModule.upsertTypes([
|
||||
* {
|
||||
* id,
|
||||
* value
|
||||
* }
|
||||
* ])
|
||||
*
|
||||
* // do something with the product types or return them
|
||||
* // do something with the types or return them
|
||||
* }
|
||||
*/
|
||||
upsertTypes(
|
||||
data: UpsertProductTypeDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypeDTO[]>
|
||||
|
||||
/**
|
||||
* This method updates an existing type, or creates a new one if it doesn't exist.
|
||||
*
|
||||
* @param {UpsertProductTypeDTO} data - The attributes to update or create for the type.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<ProductTypeDTO>} The updated or created type.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function upsertType (title: string) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const createdType = await productModule.upsertTypes(
|
||||
* {
|
||||
* value
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* // do something with the type or return it
|
||||
* }
|
||||
*/
|
||||
upsertTypes(
|
||||
data: UpsertProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypeDTO>
|
||||
|
||||
/**
|
||||
* This method is used to update a type.
|
||||
*
|
||||
* @param {string} id - The ID of the type to be updated.
|
||||
* @param {UpdateProductTypeDTO} data - The attributes of the type to be updated
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<ProductTypeDTO>} The updated type.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function updateType (id: string, title: string) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const type = await productModule.updateTypes(id, {
|
||||
* value
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* // do something with the type or return it
|
||||
* }
|
||||
*/
|
||||
updateTypes(
|
||||
data: UpdateProductTypeDTO[],
|
||||
id: string,
|
||||
data: UpdateProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypeDTO>
|
||||
|
||||
/**
|
||||
* This method is used to update a list of types determined by the selector filters.
|
||||
*
|
||||
* @param {FilterableProductTypeProps} selector - The filters that will determine which types will be updated.
|
||||
* @param {UpdateProductTypeDTO} data - The attributes to be updated on the selected types
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<ProductTypeDTO[]>} The updated types.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function updateTypes(ids: string[], title: string) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const types = await productModule.updateTypes({id: ids}, {
|
||||
* value
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* // do something with the types or return them
|
||||
* }
|
||||
*/
|
||||
updateTypes(
|
||||
selector: FilterableProductTypeProps,
|
||||
data: UpdateProductTypeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypeDTO[]>
|
||||
|
||||
@@ -1251,6 +1370,74 @@ export interface IProductModuleService extends IModuleService {
|
||||
*/
|
||||
deleteTypes(productTypeIds: string[], sharedContext?: Context): Promise<void>
|
||||
|
||||
/**
|
||||
* This method is used to delete types. Unlike the {@link delete} method, this method won't completely remove the type. 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 types can be restored using the {@link restore} method.
|
||||
*
|
||||
* @param {string[]} typeIds - The IDs of the types to soft-delete.
|
||||
* @param {SoftDeleteReturn<TReturnableLinkableKeys>} config -
|
||||
* Configurations determining which relations to soft delete along with the each of the types. You can pass to its `returnLinkableKeys`
|
||||
* property any of the type'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 also soft deleted. The object's keys are the ID attribute names of the type entity's relations, and its value is an array of strings, each being the ID of a record associated with the type through this relation.
|
||||
*
|
||||
* If there are no related records, the promise resolved to `void`.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* initialize as initializeProductModule,
|
||||
* } from "@medusajs/product"
|
||||
*
|
||||
* async function deleteTypes (ids: string[]) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const cascadedEntities = await productModule.softDeleteTypes(ids)
|
||||
*
|
||||
* // do something with the returned cascaded entity IDs or return them
|
||||
* }
|
||||
*/
|
||||
softDeleteTypes<TReturnableLinkableKeys extends string = string>(
|
||||
typeIds: string[],
|
||||
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
/**
|
||||
* This method is used to restore types which were deleted using the {@link softDelete} method.
|
||||
*
|
||||
* @param {string[]} typeIds - The IDs of the types to restore.
|
||||
* @param {RestoreReturn<TReturnableLinkableKeys>} config -
|
||||
* Configurations determining which relations to restore along with each of the types. You can pass to its `returnLinkableKeys`
|
||||
* property any of the type'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 type entity's relations, and its value is an array of strings, each being the ID of the record associated with the type 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 restoreTypes (ids: string[]) {
|
||||
* const productModule = await initializeProductModule()
|
||||
*
|
||||
* const cascadedEntities = await productModule.restoreTypes(ids, {
|
||||
* returnLinkableKeys: []
|
||||
* })
|
||||
*
|
||||
* // do something with the returned cascaded entity IDs or return them
|
||||
* }
|
||||
*/
|
||||
restoreTypes<TReturnableLinkableKeys extends string = string>(
|
||||
typeIds: string[],
|
||||
config?: RestoreReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
/**
|
||||
* This method is used to retrieve a product option by its ID.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user