feat: add check for uniqueness when creating links with isList=false (#11767)
This commit is contained in:
@@ -5,7 +5,13 @@ import {
|
||||
ModuleJoinerRelationship,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { isObject, Modules, promiseAll, toPascalCase } from "@medusajs/utils"
|
||||
import {
|
||||
isObject,
|
||||
MedusaError,
|
||||
Modules,
|
||||
promiseAll,
|
||||
toPascalCase,
|
||||
} from "@medusajs/utils"
|
||||
import { MedusaModule } from "./medusa-module"
|
||||
import { convertRecordsToLinkDefinition } from "./utils/convert-data-to-link-definition"
|
||||
import { linkingErrorMessage } from "./utils/linking-error"
|
||||
@@ -380,18 +386,88 @@ export class Link {
|
||||
const allLinks = Array.isArray(link) ? link : [link]
|
||||
const serviceLinks = new Map<
|
||||
string,
|
||||
[string | string[], string, Record<string, unknown>?][]
|
||||
{
|
||||
linksToCreate: [string | string[], string, Record<string, unknown>?][]
|
||||
linksToValidateForUniqueness: {
|
||||
filters: { [key: string]: string }[]
|
||||
services: string[]
|
||||
}
|
||||
}
|
||||
>()
|
||||
|
||||
for (const link of allLinks) {
|
||||
const service = this.getLinkModuleOrThrow(link)
|
||||
const relationships = service.__joinerConfig.relationships
|
||||
const { moduleA, moduleB, moduleBKey, primaryKeys } =
|
||||
this.getLinkDataConfig(link)
|
||||
|
||||
if (!serviceLinks.has(service.__definition.key)) {
|
||||
serviceLinks.set(service.__definition.key, [])
|
||||
serviceLinks.set(service.__definition.key, {
|
||||
/**
|
||||
* Tuple of foreign key and the primary keys that must be
|
||||
* persisted to the pivot table for representing the
|
||||
* link
|
||||
*/
|
||||
linksToCreate: [],
|
||||
|
||||
/**
|
||||
* An array of objects to validate for uniqueness before persisting
|
||||
* data to the pivot table. When a link uses "isList: false", we
|
||||
* have to limit a relationship with this entity to be a one-to-one
|
||||
* or one-to-many
|
||||
*/
|
||||
linksToValidateForUniqueness: {
|
||||
filters: [],
|
||||
services: [],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
relationships?.forEach((relationship) => {
|
||||
const linksToValidateForUniqueness = serviceLinks.get(
|
||||
service.__definition.key
|
||||
)!.linksToValidateForUniqueness!
|
||||
|
||||
linksToValidateForUniqueness.services.push(relationship.serviceName)
|
||||
|
||||
/**
|
||||
* When isList is set on false on the relationship, then it means
|
||||
* we have a one-to-one or many-to-one relationship with the
|
||||
* other side and we have limit duplicate entries from other
|
||||
* entity. For example:
|
||||
*
|
||||
* - A brand has a many to one relationship with a product.
|
||||
* - A product can have only one brand. Aka (brand.isList = false)
|
||||
* - A brand can have multiple products. Aka (products.isList = true)
|
||||
*
|
||||
* A result of this, we have to ensure that a product_id can only appear
|
||||
* once in the pivot table that is used for tracking "brand<>products"
|
||||
* relationship.
|
||||
*/
|
||||
if (relationship.isList === false) {
|
||||
const otherSide = relationships.find(
|
||||
(other) => other.foreignKey !== relationship.foreignKey
|
||||
)
|
||||
if (!otherSide) {
|
||||
return
|
||||
}
|
||||
|
||||
if (moduleBKey === otherSide.foreignKey) {
|
||||
linksToValidateForUniqueness.filters.push({
|
||||
[otherSide.foreignKey]: link[moduleB][moduleBKey],
|
||||
})
|
||||
} else {
|
||||
primaryKeys.forEach((pk) => {
|
||||
if (pk === otherSide.foreignKey) {
|
||||
linksToValidateForUniqueness.filters.push({
|
||||
[otherSide.foreignKey]: link[moduleA][pk],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const pkValue =
|
||||
primaryKeys.length === 1
|
||||
? link[moduleA][primaryKeys[0]]
|
||||
@@ -403,15 +479,39 @@ export class Link {
|
||||
fields.push(link.data)
|
||||
}
|
||||
|
||||
serviceLinks.get(service.__definition.key)?.push(fields as any)
|
||||
serviceLinks
|
||||
.get(service.__definition.key)
|
||||
?.linksToCreate.push(fields as any)
|
||||
}
|
||||
|
||||
for (const [serviceName, data] of serviceLinks) {
|
||||
if (data.linksToValidateForUniqueness.filters.length) {
|
||||
const service = this.modulesMap.get(serviceName)!
|
||||
const existingLinks = await service.list(
|
||||
{
|
||||
$or: data.linksToValidateForUniqueness.filters,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
if (existingLinks.length > 0) {
|
||||
const serviceA = data.linksToValidateForUniqueness.services[0]
|
||||
const serviceB = data.linksToValidateForUniqueness.services[1]
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Cannot create multiple links between '${serviceA}' and '${serviceB}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const promises: Promise<unknown[]>[] = []
|
||||
|
||||
for (const [serviceName, links] of serviceLinks) {
|
||||
for (const [serviceName, data] of serviceLinks) {
|
||||
const service = this.modulesMap.get(serviceName)!
|
||||
|
||||
promises.push(service.create(links))
|
||||
promises.push(service.create(data.linksToCreate))
|
||||
}
|
||||
|
||||
return (await promiseAll(promises)).flat()
|
||||
|
||||
Reference in New Issue
Block a user