Files
medusa-store/packages/utils/src/dal/mikro-orm/utils.ts
Oli Juhl 8c57e61cb8 feat: Refresh payment collection + delete session (#6594)
### What
Add workflow for refreshing a payment collection. 

The idea is that on all cart updates, we want two things to happen (in the context of payments) 1. the currently active payment sessions should be destroyed, and 2. the payment collection should be updated with the new cart total.

We do this to ensure that we always collect the correct payment amount. 

From a customer perspective, this would mean that every time something on the cart is updated, the customer would need to enter their payment details anew. 

To me, this is a good tradeoff to avoid inconsistencies with payment collection.

Additionally, I updated the Payment Module interface with `upsert` and `updated` following our established convention.

### Note
This PR depends on a fix to the `remoteJoiner` that @carlos-r-l-rodrigues is working on.

Update: Fix merged in #6602 

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
2024-03-07 13:32:20 +00:00

178 lines
4.6 KiB
TypeScript

import { buildQuery } from "../../modules-sdk"
import { EntityMetadata, FindOptions, wrap } from "@mikro-orm/core"
import { SqlEntityManager } from "@mikro-orm/postgresql"
function detectCircularDependency(
manager: SqlEntityManager,
entityMetadata: EntityMetadata,
visited: Set<string> = new Set(),
shouldStop: boolean = false
) {
if (shouldStop) {
return
}
visited.add(entityMetadata.className)
const relations = entityMetadata.relations
const relationsToCascade = relations.filter((relation) =>
relation.cascade.includes("soft-remove" as any)
)
for (const relation of relationsToCascade) {
const branchVisited = new Set(Array.from(visited))
const isSelfCircularDependency = entityMetadata.class === relation.entity()
if (!isSelfCircularDependency && branchVisited.has(relation.name)) {
const dependencies = Array.from(visited)
dependencies.push(entityMetadata.className)
const circularDependencyStr = dependencies.join(" -> ")
throw new Error(
`Unable to soft delete the ${relation.name}. Circular dependency detected: ${circularDependencyStr}`
)
}
branchVisited.add(relation.name)
const relationEntityMetadata = manager
.getDriver()
.getMetadata()
.get(relation.type)
detectCircularDependency(
manager,
relationEntityMetadata,
branchVisited,
isSelfCircularDependency
)
}
}
async function performCascadingSoftDeletion<T>(
manager: SqlEntityManager,
entity: T & { id: string; deleted_at?: string | Date | null },
value: Date | null
) {
if (!("deleted_at" in entity)) return
entity.deleted_at = value
const entityName = entity.constructor.name
const relations = manager.getDriver().getMetadata().get(entityName).relations
const relationsToCascade = relations.filter((relation) =>
relation.cascade.includes("soft-remove" as any)
)
for (const relation of relationsToCascade) {
let entityRelation = entity[relation.name]
// Handle optional relationships
if (relation.nullable && !entityRelation) {
continue
}
const retrieveEntity = async () => {
const query = buildQuery(
{
id: entity.id,
},
{
relations: [relation.name],
withDeleted: true,
}
)
return await manager.findOne(
entity.constructor.name,
query.where,
query.options as FindOptions<any>
)
}
if (!entityRelation) {
// Fixes the case of many to many through pivot table
entityRelation = await retrieveEntity()
if (!entityRelation) {
continue
}
}
const isCollection = "toArray" in entityRelation
let relationEntities: any[] = []
if (isCollection) {
if (!entityRelation.isInitialized()) {
entityRelation = await retrieveEntity()
entityRelation = entityRelation[relation.name]
}
relationEntities = entityRelation.getItems()
} else {
const wrappedEntity = wrap(entityRelation)
const initializedEntityRelation = wrappedEntity.isInitialized()
? entityRelation
: await wrap(entityRelation).init()
relationEntities = [initializedEntityRelation]
}
if (!relationEntities.length) {
continue
}
await mikroOrmUpdateDeletedAtRecursively(manager, relationEntities, value)
}
await manager.persist(entity)
}
export const mikroOrmUpdateDeletedAtRecursively = async <
T extends object = any
>(
manager: SqlEntityManager,
entities: (T & { id: string; deleted_at?: string | Date | null })[],
value: Date | null
) => {
for (const entity of entities) {
const entityMetadata = manager
.getDriver()
.getMetadata()
.get(entity.constructor.name)
detectCircularDependency(manager, entityMetadata)
await performCascadingSoftDeletion(manager, entity, value)
}
}
export const mikroOrmSerializer = async <TOutput extends object>(
data: any,
options?: any
): Promise<TOutput> => {
options ??= {}
const data_ = (Array.isArray(data) ? data : [data]).filter(Boolean)
const forSerialization: unknown[] = []
const notForSerialization: unknown[] = []
data_.forEach((object) => {
if (object.__meta) {
return forSerialization.push(object)
}
return notForSerialization.push(object)
})
const { serialize } = await import("@mikro-orm/core")
let result: any = serialize(forSerialization, {
forceObject: true,
populate: true,
...options,
}) as TOutput[]
if (notForSerialization.length) {
result = result.concat(notForSerialization)
}
return Array.isArray(data) ? result : result[0]
}