feat(prduct, utils, types): Create soft delete pattern for link module (#4649)

* feat(prouct, utils, types): Create soft delete pattern for link module

* add comment

* add comment

* finalise

* remove linkable keys

* cleanup and tests

* cleanup

* add some comments and renaming

* re work

* fix tests

---------

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
This commit is contained in:
Adrien de Peretti
2023-08-02 19:29:01 +02:00
committed by GitHub
co-authored by Riqwan Thamir
parent fc6c9df035
commit ce3326c5fb
17 changed files with 448 additions and 166 deletions
+54
View File
@@ -1,5 +1,59 @@
import { Modules } from "@medusajs/modules-sdk"
import { JoinerServiceConfig } from "@medusajs/types"
import {
Product,
ProductCategory,
ProductCollection,
ProductOption,
ProductTag,
ProductType,
ProductVariant,
} from "@models"
import ProductImage from "./models/product-image"
import { MapToConfig } from "@medusajs/utils"
export enum LinkableKeys {
PRODUCT_ID = "product_id",
PRODUCT_HANDLE = "product_handle",
VARIANT_ID = "variant_id",
VARIANT_SKU = "variant_sku",
PRODUCT_OPTION_ID = "product_option_id",
PRODUCT_TYPE_ID = "product_type_id",
PRODUCT_CATEGORY_ID = "product_category_id",
PRODUCT_COLLECTION_ID = "product_collection_id",
PRODUCT_TAG_ID = "product_tag_id",
PRODUCT_IMAGE_ID = "product_image_id",
}
export const entityNameToLinkableKeysMap: MapToConfig = {
[Product.name]: [
{ mapTo: LinkableKeys.PRODUCT_ID, valueFrom: "id" },
{
mapTo: LinkableKeys.PRODUCT_HANDLE,
valueFrom: "handle",
},
],
[ProductVariant.name]: [
{ mapTo: LinkableKeys.VARIANT_ID, valueFrom: "id" },
{ mapTo: LinkableKeys.VARIANT_SKU, valueFrom: "sku" },
],
[ProductOption.name]: [
{ mapTo: LinkableKeys.PRODUCT_OPTION_ID, valueFrom: "id" },
],
[ProductType.name]: [
{ mapTo: LinkableKeys.PRODUCT_TYPE_ID, valueFrom: "id" },
],
[ProductCategory.name]: [
{ mapTo: LinkableKeys.PRODUCT_CATEGORY_ID, valueFrom: "id" },
],
[ProductCollection.name]: [
{ mapTo: LinkableKeys.PRODUCT_COLLECTION_ID, valueFrom: "id" },
],
[ProductTag.name]: [{ mapTo: LinkableKeys.PRODUCT_TAG_ID, valueFrom: "id" }],
[ProductImage.name]: [
{ mapTo: LinkableKeys.PRODUCT_IMAGE_ID, valueFrom: "id" },
],
}
export const joinerConfig: JoinerServiceConfig = {
serviceName: Modules.PRODUCT,
@@ -2,12 +2,12 @@ import { ProductCategory } from "@models"
import { ProductCategoryRepository } from "@repositories"
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
import {
ModulesSdkUtils,
MedusaError,
isDefined,
InjectTransactionManager,
InjectManager,
MedusaContext
InjectTransactionManager,
isDefined,
MedusaContext,
MedusaError,
ModulesSdkUtils,
} from "@medusajs/utils"
import { shouldForceTransaction } from "../utils"
@@ -39,9 +39,12 @@ export default class ProductCategoryService<
)
}
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>({
id: productCategoryId,
}, config)
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>(
{
id: productCategoryId,
},
config
)
const transformOptions = {
includeDescendantsTree: true,
@@ -111,31 +114,37 @@ export default class ProductCategoryService<
)) as [TEntity[], number]
}
@InjectTransactionManager(shouldForceTransaction, "productCategoryRepository_")
@InjectTransactionManager(
shouldForceTransaction,
"productCategoryRepository_"
)
async create(
data: ProductCategoryServiceTypes.CreateProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity> {
return (await (this.productCategoryRepository_ as ProductCategoryRepository).create(
data,
sharedContext
)) as TEntity
return (await (
this.productCategoryRepository_ as unknown as ProductCategoryRepository
).create(data, sharedContext)) as TEntity
}
@InjectTransactionManager(shouldForceTransaction, "productCategoryRepository_")
@InjectTransactionManager(
shouldForceTransaction,
"productCategoryRepository_"
)
async update(
id: string,
data: ProductCategoryServiceTypes.UpdateProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity> {
return (await (this.productCategoryRepository_ as ProductCategoryRepository).update(
id,
data,
sharedContext
)) as TEntity
return (await (
this.productCategoryRepository_ as unknown as ProductCategoryRepository
).update(id, data, sharedContext)) as TEntity
}
@InjectTransactionManager(shouldForceTransaction, "productCategoryRepository_")
@InjectTransactionManager(
shouldForceTransaction,
"productCategoryRepository_"
)
async delete(
id: string,
@MedusaContext() sharedContext: Context = {}
@@ -1,10 +1,10 @@
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
import {
ModulesSdkUtils,
retrieveEntity,
InjectManager,
InjectTransactionManager,
MedusaContext,
InjectManager,
ModulesSdkUtils,
retrieveEntity,
} from "@medusajs/utils"
import { shouldForceTransaction } from "../utils"
@@ -85,29 +85,36 @@ export default class ProductCollectionService<
return queryOptions
}
@InjectTransactionManager(shouldForceTransaction, "productCollectionRepository_")
@InjectTransactionManager(
shouldForceTransaction,
"productCollectionRepository_"
)
async create(
data: ProductTypes.CreateProductCollectionDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
return (await (this.productCollectionRepository_ as ProductCollectionRepository).create(
data,
sharedContext
)) as TEntity[]
return (await (
this.productCollectionRepository_ as ProductCollectionRepository
).create(data, sharedContext)) as TEntity[]
}
@InjectTransactionManager(shouldForceTransaction, "productCollectionRepository_")
@InjectTransactionManager(
shouldForceTransaction,
"productCollectionRepository_"
)
async update(
data: ProductTypes.UpdateProductCollectionDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
return (await (this.productCollectionRepository_ as ProductCollectionRepository).update(
data,
sharedContext
)) as TEntity[]
return (await (
this.productCollectionRepository_ as ProductCollectionRepository
).update(data, sharedContext)) as TEntity[]
}
@InjectTransactionManager(shouldForceTransaction, "productCollectionRepository_")
@InjectTransactionManager(
shouldForceTransaction,
"productCollectionRepository_"
)
async delete(
ids: string[],
@MedusaContext() sharedContext: Context = {}
@@ -38,12 +38,17 @@ import {
isDefined,
isString,
kebabCase,
mapObjectTo,
MedusaContext,
MedusaError,
} from "@medusajs/utils"
import { shouldForceTransaction } from "../utils"
import { joinerConfig } from "./../joiner-config"
import {
entityNameToLinkableKeysMap,
joinerConfig,
LinkableKeys,
} from "./../joiner-config"
import { ProductCategoryServiceTypes } from "../types"
type InjectedDependencies = {
@@ -958,7 +963,7 @@ export default class ProductModuleService<
) {
if (isDefined(productData.type)) {
const productType = await this.productTypeService_.upsert(
[productData.type],
[productData.type!],
sharedContext
)
@@ -974,22 +979,41 @@ export default class ProductModuleService<
await this.productService_.delete(productIds, sharedContext)
}
async softDelete(
async softDelete<
TReturnableLinkableKeys extends string = Lowercase<
keyof typeof LinkableKeys
>
>(
productIds: string[],
{
returnLinkableKeys,
}: { returnLinkableKeys?: TReturnableLinkableKeys[] } = {
returnLinkableKeys: [],
},
sharedContext: Context = {}
): Promise<ProductTypes.ProductDTO[]> {
const products = await this.softDelete_(productIds, sharedContext)
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
let [, cascadedEntitiesMap] = await this.softDelete_(
productIds,
sharedContext
)
return this.baseRepository_.serialize<ProductTypes.ProductDTO[]>(products, {
populate: true,
})
let mappedCascadedEntitiesMap
if (returnLinkableKeys) {
mappedCascadedEntitiesMap = mapObjectTo<
Record<Lowercase<keyof typeof LinkableKeys>, string[]>
>(cascadedEntitiesMap, entityNameToLinkableKeysMap, {
pick: returnLinkableKeys as string[],
})
}
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
protected async softDelete_(
productIds: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<TProduct[]> {
): Promise<[TProduct[], Record<string, unknown[]>]> {
return await this.productService_.softDelete(productIds, sharedContext)
}
+10 -7
View File
@@ -10,10 +10,10 @@ import {
import {
InjectManager,
InjectTransactionManager,
isDefined,
MedusaContext,
MedusaError,
ModulesSdkUtils,
isDefined,
} from "@medusajs/utils"
import { ProductRepository } from "@repositories"
@@ -44,9 +44,12 @@ export default class ProductService<TEntity extends Product = Product> {
)
}
const queryOptions = ModulesSdkUtils.buildQuery<Product>({
id: productId,
}, config)
const queryOptions = ModulesSdkUtils.buildQuery<Product>(
{
id: productId,
},
config
)
const product = await this.productRepository_.find(
queryOptions,
@@ -140,7 +143,7 @@ export default class ProductService<TEntity extends Product = Product> {
data: ProductServiceTypes.UpdateProductDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
return await (this.productRepository_ as ProductRepository).update(
return (await (this.productRepository_ as ProductRepository).update(
data as WithRequiredProperty<
ProductServiceTypes.UpdateProductDTO,
"id"
@@ -148,7 +151,7 @@ export default class ProductService<TEntity extends Product = Product> {
{
transactionManager: sharedContext.transactionManager,
}
) as TEntity[]
)) as TEntity[]
}
@InjectTransactionManager(doNotForceTransaction, "productRepository_")
@@ -165,7 +168,7 @@ export default class ProductService<TEntity extends Product = Product> {
async softDelete(
productIds: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
): Promise<[TEntity[], Record<string, unknown[]>]> {
return await this.productRepository_.softDelete(productIds, {
transactionManager: sharedContext.transactionManager,
})