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:
co-authored by
Riqwan Thamir
parent
fc6c9df035
commit
ce3326c5fb
@@ -1,5 +1,6 @@
|
||||
export * from "./mikro-orm/mikro-orm-repository"
|
||||
export * from "./repository"
|
||||
export * from "./utils"
|
||||
export * from "./mikro-orm/utils"
|
||||
export * from "./mikro-orm/mikro-orm-create-connection"
|
||||
export * from "./mikro-orm/mikro-orm-soft-deletable-filter"
|
||||
|
||||
@@ -2,10 +2,10 @@ import { Context, DAL, RepositoryTransformOptions } from "@medusajs/types"
|
||||
import { MedusaContext } from "../../decorators"
|
||||
import { buildQuery, InjectTransactionManager } from "../../modules-sdk"
|
||||
import {
|
||||
mikroOrmSerializer,
|
||||
mikroOrmUpdateDeletedAtRecursively,
|
||||
getSoftDeletedCascadedEntitiesIdsMappedBy,
|
||||
transactionWrapper,
|
||||
} from "../utils"
|
||||
import { mikroOrmSerializer, mikroOrmUpdateDeletedAtRecursively } from "./utils"
|
||||
|
||||
class MikroOrmBase<T = any> {
|
||||
protected readonly manager_: any
|
||||
@@ -71,13 +71,17 @@ export abstract class MikroOrmAbstractBaseRepository<T = any>
|
||||
ids: string[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
): Promise<T[]> {
|
||||
): Promise<[T[], Record<string, unknown[]>]> {
|
||||
const entities = await this.find({ where: { id: { $in: ids } } as any })
|
||||
const date = new Date()
|
||||
|
||||
await mikroOrmUpdateDeletedAtRecursively(manager, entities, date)
|
||||
|
||||
return entities
|
||||
const softDeletedEntitiesMap = getSoftDeletedCascadedEntitiesIdsMappedBy({
|
||||
entities,
|
||||
})
|
||||
|
||||
return [entities, softDeletedEntitiesMap]
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { SoftDeletableFilterKey } from "./mikro-orm-soft-deletable-filter"
|
||||
|
||||
export const mikroOrmUpdateDeletedAtRecursively = async <
|
||||
T extends object = any
|
||||
>(
|
||||
manager: any,
|
||||
entities: (T & { id: string; deleted_at?: string | Date | null })[],
|
||||
value: Date | null
|
||||
) => {
|
||||
for (const entity of entities) {
|
||||
if (!("deleted_at" in entity)) continue
|
||||
|
||||
entity.deleted_at = value
|
||||
|
||||
const relations = manager
|
||||
.getDriver()
|
||||
.getMetadata()
|
||||
.get(entity.constructor.name).relations
|
||||
|
||||
const relationsToCascade = relations.filter((relation) =>
|
||||
relation.cascade.includes("soft-remove" as any)
|
||||
)
|
||||
|
||||
for (const relation of relationsToCascade) {
|
||||
let collectionRelation = entity[relation.name]
|
||||
|
||||
if (!collectionRelation.isInitialized()) {
|
||||
await collectionRelation.init()
|
||||
}
|
||||
|
||||
const relationEntities = await collectionRelation.getItems({
|
||||
filters: {
|
||||
[SoftDeletableFilterKey]: {
|
||||
withDeleted: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await mikroOrmUpdateDeletedAtRecursively(manager, relationEntities, value)
|
||||
}
|
||||
|
||||
await manager.persist(entity)
|
||||
}
|
||||
}
|
||||
|
||||
export const mikroOrmSerializer = async <TOutput extends object>(
|
||||
data: any,
|
||||
options?: any
|
||||
): Promise<TOutput> => {
|
||||
options ??= {}
|
||||
const { serialize } = await import("@mikro-orm/core")
|
||||
const result = serialize(data, options)
|
||||
return result as unknown as Promise<TOutput>
|
||||
}
|
||||
@@ -49,7 +49,10 @@ export abstract class AbstractBaseRepository<T = any>
|
||||
|
||||
abstract delete(ids: string[], context?: Context): Promise<void>
|
||||
|
||||
abstract softDelete(ids: string[], context?: Context): Promise<T[]>
|
||||
abstract softDelete(
|
||||
ids: string[],
|
||||
context?: Context
|
||||
): Promise<[T[], Record<string, unknown[]>]>
|
||||
|
||||
abstract restore(ids: string[], context?: Context): Promise<T[]>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SoftDeletableFilterKey } from "../dal"
|
||||
import { isObject } from "../common"
|
||||
|
||||
export async function transactionWrapper<TManager = unknown>(
|
||||
this: any,
|
||||
@@ -33,54 +33,52 @@ export async function transactionWrapper<TManager = unknown>(
|
||||
return await transactionMethod.bind(this.manager_)(task, options)
|
||||
}
|
||||
|
||||
export const mikroOrmUpdateDeletedAtRecursively = async <
|
||||
T extends object = any
|
||||
>(
|
||||
manager: any,
|
||||
entities: T[],
|
||||
value: Date | null
|
||||
) => {
|
||||
/**
|
||||
* Can be used to create a new Object that collect the entities
|
||||
* based on the columnLookup. This is useful when you want to soft delete entities and return
|
||||
* an object where the keys are the entities name and the values are the entities
|
||||
* that were soft deleted.
|
||||
*
|
||||
* @param entities
|
||||
* @param deletedEntitiesMap
|
||||
* @param getEntityName
|
||||
*/
|
||||
export function getSoftDeletedCascadedEntitiesIdsMappedBy({
|
||||
entities,
|
||||
deletedEntitiesMap,
|
||||
getEntityName,
|
||||
}: {
|
||||
entities: any[]
|
||||
deletedEntitiesMap?: Map<string, any[]>
|
||||
getEntityName?: (entity: any) => string
|
||||
}): Record<string, any[]> {
|
||||
deletedEntitiesMap ??= new Map<string, any[]>()
|
||||
getEntityName ??= (entity) => entity.constructor.name
|
||||
|
||||
for (const entity of entities) {
|
||||
if (!("deleted_at" in entity)) continue
|
||||
;(entity as any).deleted_at = value
|
||||
const entityName = getEntityName(entity)
|
||||
const shouldSkip = !!deletedEntitiesMap
|
||||
.get(entityName)
|
||||
?.some((e) => e.id === entity.id)
|
||||
|
||||
const relations = manager
|
||||
.getDriver()
|
||||
.getMetadata()
|
||||
.get(entity.constructor.name).relations
|
||||
|
||||
const relationsToCascade = relations.filter((relation) =>
|
||||
relation.cascade.includes("soft-remove" as any)
|
||||
)
|
||||
|
||||
for (const relation of relationsToCascade) {
|
||||
let collectionRelation = entity[relation.name]
|
||||
|
||||
if (!collectionRelation.isInitialized()) {
|
||||
await collectionRelation.init()
|
||||
}
|
||||
|
||||
const relationEntities = await collectionRelation.getItems({
|
||||
filters: {
|
||||
[SoftDeletableFilterKey]: {
|
||||
withDeleted: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await mikroOrmUpdateDeletedAtRecursively(manager, relationEntities, value)
|
||||
if (!entity.deleted_at || shouldSkip) {
|
||||
continue
|
||||
}
|
||||
|
||||
await manager.persist(entity)
|
||||
}
|
||||
}
|
||||
const values = deletedEntitiesMap.get(entityName) ?? []
|
||||
values.push(entity)
|
||||
deletedEntitiesMap.set(entityName, values)
|
||||
|
||||
export const mikroOrmSerializer = async <TOutput extends object>(
|
||||
data: any,
|
||||
options?: any
|
||||
): Promise<TOutput> => {
|
||||
options ??= {}
|
||||
const { serialize } = await import("@mikro-orm/core")
|
||||
const result = serialize(data, options)
|
||||
return result as unknown as Promise<TOutput>
|
||||
Object.values(entity).forEach((propValue: any) => {
|
||||
if (propValue != null && isObject(propValue[0])) {
|
||||
getSoftDeletedCascadedEntitiesIdsMappedBy({
|
||||
entities: propValue,
|
||||
deletedEntitiesMap,
|
||||
getEntityName,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Object.fromEntries(deletedEntitiesMap)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user