chore: Migrate region module to use DML (#7837)

This commit is contained in:
Oli Juhl
2024-06-29 15:14:52 +02:00
committed by GitHub
parent 7c3f5cc334
commit fa6cd84049
10 changed files with 173 additions and 155 deletions

View File

@@ -1,6 +1,6 @@
import { buildQuery } from "../../modules-sdk"
import { EntityMetadata, FindOptions, wrap } from "@mikro-orm/core"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { buildQuery } from "../../modules-sdk"
function detectCircularDependency(
manager: SqlEntityManager,
@@ -15,8 +15,9 @@ function detectCircularDependency(
visited.add(entityMetadata.className)
const relations = entityMetadata.relations
const relationsToCascade = relations.filter((relation) =>
relation.cascade.includes("soft-remove" as any)
relation.cascade?.includes("soft-remove" as any)
)
for (const relation of relationsToCascade) {
@@ -63,7 +64,7 @@ async function performCascadingSoftDeletion<T>(
const relations = manager.getDriver().getMetadata().get(entityName).relations
const relationsToCascade = relations.filter((relation) =>
relation.cascade.includes("soft-remove" as any)
relation.cascade?.includes("soft-remove" as any)
)
for (const relation of relationsToCascade) {

View File

@@ -9,9 +9,9 @@ import {
BeforeCreate,
ManyToMany,
ManyToOne,
OnInit,
OneToMany,
OneToOne,
OnInit,
Property,
} from "@mikro-orm/core"
import { camelToSnakeCase, pluralize } from "../../../common"

View File

@@ -1,12 +1,7 @@
import { defineMikroOrmCliConfig } from "@medusajs/utils"
import * as entities from "./src/models"
import { TSMigrationGenerator } from "@medusajs/utils"
module.exports = {
export default defineMikroOrmCliConfig({
entities: Object.values(entities),
schema: "public",
clientUrl: "postgres://postgres@localhost/medusa-region",
type: "postgresql",
migrations: {
generator: TSMigrationGenerator,
},
}
databaseName: "medusa-region",
})

View File

@@ -1,13 +1,14 @@
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
import { ContainerRegistrationKeys, DefaultsUtils } from "@medusajs/utils"
import { Country } from "@models"
import { RegionCountry } from "@models"
export default async ({ container }: LoaderOptions): Promise<void> => {
// TODO: Add default logger to the container when running tests
const logger =
container.resolve<Logger>(ContainerRegistrationKeys.LOGGER) ?? console
const countryService_: ModulesSdkTypes.IMedusaInternalService<Country> =
container.resolve("countryService")
const countryService_: ModulesSdkTypes.IMedusaInternalService<
typeof RegionCountry
> = container.resolve("countryService")
try {
const normalizedCountries = DefaultsUtils.defaultCountries.map((c) => ({

View File

@@ -1,5 +1,7 @@
{
"namespaces": ["public"],
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
@@ -86,16 +88,11 @@
"name": "region",
"schema": "public",
"indexes": [
{
"columnNames": ["deleted_at"],
"composite": false,
"keyName": "IDX_region_deleted_at",
"primary": false,
"unique": false
},
{
"keyName": "region_pkey",
"columnNames": ["id"],
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
@@ -159,14 +156,65 @@
"primary": false,
"nullable": true,
"mappedType": "text"
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "region_country",
"schema": "public",
"indexes": [
{
"keyName": "IDX_region_country_region_id_iso_2_unique",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_region_country_region_id_iso_2_unique\" ON \"region_country\" (region_id, iso_2)"
},
{
"keyName": "region_country_pkey",
"columnNames": ["iso_2"],
"columnNames": [
"iso_2"
],
"composite": false,
"primary": true,
"unique": true
@@ -176,9 +224,13 @@
"foreignKeys": {
"region_country_region_id_foreign": {
"constraintName": "region_country_region_id_foreign",
"columnNames": ["region_id"],
"columnNames": [
"region_id"
],
"localTableName": "public.region_country",
"referencedColumnNames": ["id"],
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.region",
"deleteRule": "set null",
"updateRule": "cascade"

View File

@@ -0,0 +1,39 @@
import { generatePostgresAlterColummnIfExistStatement } from "@medusajs/utils"
import { Migration } from "@mikro-orm/migrations"
export class Migration20240624200006 extends Migration {
async up(): Promise<void> {
this.addSql(
'ALTER TABLE IF EXISTS "region_country" ADD COLUMN IF NOT EXISTS "metadata" jsonb null, ADD COLUMN "created_at" timestamptz NOT NULL DEFAULT NOW(), ADD COLUMN "updated_at" timestamptz NOT NULL DEFAULT NOW(), ADD COLUMN "deleted_at" timestamptz NULL;'
)
this.addSql(
generatePostgresAlterColummnIfExistStatement(
"region_country",
["region_id"],
`DROP NOT NULL`
)
)
}
async down(): Promise<void> {
this.addSql(
'ALTER TABLE IF EXISTS "region_country" DROP COLUMN IF EXISTS "metadata";'
)
this.addSql(
'ALTER TABLE IF EXISTS "region_country" DROP COLUMN IF EXISTS "created_at";'
)
this.addSql(
'ALTER TABLE IF EXISTS "region_country" DROP COLUMN IF EXISTS "updated_at";'
)
this.addSql(
'ALTER TABLE IF EXISTS "region_country" DROP COLUMN IF EXISTS "deleted_at";'
)
this.addSql(
generatePostgresAlterColummnIfExistStatement(
"region_country",
["region_id"],
`SET NOT NULL`
)
)
}
}

View File

@@ -1,44 +1,28 @@
import { Entity, ManyToOne, PrimaryKey, Property } from "@mikro-orm/core"
import { createPsqlIndexStatementHelper } from "@medusajs/utils"
import { model } from "@medusajs/utils"
import Region from "./region"
// We don't need a partial index on deleted_at here since we don't support soft deletes on countries
const regionIdIsoIndexName = "IDX_region_country_region_id_iso_2_unique"
const RegionIdIsoIndexStatement = createPsqlIndexStatementHelper({
name: regionIdIsoIndexName,
tableName: "region_country",
columns: ["region_id", "iso_2"],
unique: true,
})
const Country = model
.define(
{ name: "Country", tableName: "region_country" },
{
iso_2: model.text().searchable().primaryKey(),
iso_3: model.text(),
num_code: model.text(),
name: model.text().searchable(),
display_name: model.text(),
region: model
.belongsTo(() => Region, { mappedBy: "countries" })
.nullable(),
metadata: model.json().nullable(),
}
)
.indexes([
{
// TODO: Remove ts-ignore when field inference takes into account the nullable property
// @ts-ignore
on: ["region_id", "iso_2"],
unique: true,
},
])
RegionIdIsoIndexStatement.MikroORMIndex()
@Entity({ tableName: "region_country" })
export default class Country {
@PrimaryKey({ columnType: "text" })
@Property({ columnType: "text" })
iso_2: string
@Property({ columnType: "text" })
iso_3: string
@Property({ columnType: "text" })
num_code: string
@Property({ columnType: "text" })
name: string
@Property({ columnType: "text" })
display_name: string
@Property({ columnType: "text", nullable: true })
region_id?: string | null = null
@ManyToOne({
entity: () => Region,
fieldName: "region_id",
nullable: true,
onDelete: "set null",
})
region?: Region | null
}
export default Country

View File

@@ -1,2 +1,3 @@
export { default as Country } from "./country"
export { default as RegionCountry } from "./country"
export { default as Region } from "./region"

View File

@@ -1,72 +1,13 @@
import { DAL } from "@medusajs/types"
import { DALUtils, Searchable, generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Collection,
Entity,
Filter,
Index,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Country from "./country"
import { model } from "@medusajs/utils"
import RegionCountry from "./country"
type RegionOptionalProps = "countries" | DAL.SoftDeletableEntityDateColumns
const Region = model.define("region", {
id: model.id({ prefix: "reg" }),
name: model.text().searchable(),
currency_code: model.text().searchable(),
automatic_taxes: model.boolean().default(true),
countries: model.hasMany(() => RegionCountry),
metadata: model.json().nullable(),
})
@Entity({ tableName: "region" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class Region {
[OptionalProps]?: RegionOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Searchable()
@Property({ columnType: "text" })
name: string
@Searchable()
@Property({ columnType: "text" })
currency_code: string
@Property({ columnType: "boolean" })
automatic_taxes: boolean = true
@OneToMany(() => Country, (country) => country.region)
countries = new Collection<Country>(this)
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@Index({ name: "IDX_region_deleted_at" })
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "reg")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "reg")
}
}
export default Region

View File

@@ -3,8 +3,9 @@ import {
CreateRegionDTO,
DAL,
FilterableRegionProps,
InternalModuleDeclaration,
IRegionModuleService,
InferEntityType,
InternalModuleDeclaration,
ModuleJoinerConfig,
ModulesSdkTypes,
RegionCountryDTO,
@@ -14,20 +15,18 @@ import {
UpsertRegionDTO,
} from "@medusajs/types"
import {
arrayDifference,
getDuplicates,
InjectManager,
InjectTransactionManager,
isString,
MedusaContext,
MedusaError,
MedusaService,
arrayDifference,
getDuplicates,
isString,
promiseAll,
removeUndefined,
} from "@medusajs/utils"
import { Country, Region } from "@models"
import { RegionCountry as Country, Region } from "@models"
import { UpdateRegionInput } from "@types"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
@@ -49,8 +48,12 @@ export default class RegionModuleService
implements IRegionModuleService
{
protected baseRepository_: DAL.RepositoryService
protected readonly regionService_: ModulesSdkTypes.IMedusaInternalService<Region>
protected readonly countryService_: ModulesSdkTypes.IMedusaInternalService<Country>
protected readonly regionService_: ModulesSdkTypes.IMedusaInternalService<
typeof Region
>
protected readonly countryService_: ModulesSdkTypes.IMedusaInternalService<
typeof Country
>
constructor(
{ baseRepository, regionService, countryService }: InjectedDependencies,
@@ -67,7 +70,7 @@ export default class RegionModuleService
return joinerConfig
}
//@ts-expect-error
// @ts-expect-error
async createRegions(
data: CreateRegionDTO[],
sharedContext?: Context
@@ -95,7 +98,7 @@ export default class RegionModuleService
async createRegions_(
data: CreateRegionDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<Region[]> {
): Promise<InferEntityType<typeof Region>[]> {
let normalizedInput = RegionModuleService.normalizeInput(data)
let normalizedDbRegions = normalizedInput.map((region) =>
@@ -172,7 +175,7 @@ export default class RegionModuleService
(region): region is CreateRegionDTO => !region.id
)
const operations: Promise<Region[]>[] = []
const operations: Promise<InferEntityType<typeof Region>[]>[] = []
if (forCreate.length) {
operations.push(this.createRegions_(forCreate, sharedContext))
@@ -187,7 +190,7 @@ export default class RegionModuleService
)
}
//@ts-expect-error
// @ts-expect-error
async updateRegions(
id: string,
data: UpdateRegionDTO,
@@ -237,7 +240,7 @@ export default class RegionModuleService
protected async updateRegions_(
data: UpdateRegionInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<Region[]> {
): Promise<InferEntityType<typeof Region>[]> {
const normalizedInput = RegionModuleService.normalizeInput(data)
// If countries are being updated for a region, first make previously set countries' region to null to get to a clean slate.
@@ -306,7 +309,7 @@ export default class RegionModuleService
private async validateCountries(
countries: string[] | undefined,
sharedContext: Context
): Promise<Country[]> {
): Promise<InferEntityType<typeof Country>[]> {
if (!countries?.length) {
return []
}
@@ -343,6 +346,7 @@ export default class RegionModuleService
}
// Countries that already have a region already assigned to them
// @ts-ignore
const countriesWithRegion = countriesInDb.filter((c) => !!c.region_id)
if (countriesWithRegion.length) {
throw new MedusaError(