diff --git a/packages/core/utils/src/dal/mikro-orm/utils.ts b/packages/core/utils/src/dal/mikro-orm/utils.ts index a80c5b80d3..4e7637e6db 100644 --- a/packages/core/utils/src/dal/mikro-orm/utils.ts +++ b/packages/core/utils/src/dal/mikro-orm/utils.ts @@ -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( 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) { diff --git a/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts b/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts index d0852b5e6d..8dda65a0e5 100644 --- a/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts +++ b/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts @@ -9,9 +9,9 @@ import { BeforeCreate, ManyToMany, ManyToOne, + OnInit, OneToMany, OneToOne, - OnInit, Property, } from "@mikro-orm/core" import { camelToSnakeCase, pluralize } from "../../../common" diff --git a/packages/modules/region/mikro-orm.config.dev.ts b/packages/modules/region/mikro-orm.config.dev.ts index b5f6f37912..4fa735e4c3 100644 --- a/packages/modules/region/mikro-orm.config.dev.ts +++ b/packages/modules/region/mikro-orm.config.dev.ts @@ -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", +}) \ No newline at end of file diff --git a/packages/modules/region/src/loaders/defaults.ts b/packages/modules/region/src/loaders/defaults.ts index d7b3d60c73..009065364e 100644 --- a/packages/modules/region/src/loaders/defaults.ts +++ b/packages/modules/region/src/loaders/defaults.ts @@ -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 => { // TODO: Add default logger to the container when running tests const logger = container.resolve(ContainerRegistrationKeys.LOGGER) ?? console - const countryService_: ModulesSdkTypes.IMedusaInternalService = - container.resolve("countryService") + const countryService_: ModulesSdkTypes.IMedusaInternalService< + typeof RegionCountry + > = container.resolve("countryService") try { const normalizedCountries = DefaultsUtils.defaultCountries.map((c) => ({ diff --git a/packages/modules/region/src/migrations/.snapshot-medusa-region.json b/packages/modules/region/src/migrations/.snapshot-medusa-region.json index 5b30980831..a2e69d800c 100644 --- a/packages/modules/region/src/migrations/.snapshot-medusa-region.json +++ b/packages/modules/region/src/migrations/.snapshot-medusa-region.json @@ -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" diff --git a/packages/modules/region/src/migrations/Migration20240624200006.ts b/packages/modules/region/src/migrations/Migration20240624200006.ts new file mode 100644 index 0000000000..d2047d2353 --- /dev/null +++ b/packages/modules/region/src/migrations/Migration20240624200006.ts @@ -0,0 +1,39 @@ +import { generatePostgresAlterColummnIfExistStatement } from "@medusajs/utils" +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240624200006 extends Migration { + async up(): Promise { + 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 { + 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` + ) + ) + } +} diff --git a/packages/modules/region/src/models/country.ts b/packages/modules/region/src/models/country.ts index 4150ee0ec9..6178307ee6 100644 --- a/packages/modules/region/src/models/country.ts +++ b/packages/modules/region/src/models/country.ts @@ -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 diff --git a/packages/modules/region/src/models/index.ts b/packages/modules/region/src/models/index.ts index f4e91bedbc..95d96d14bd 100644 --- a/packages/modules/region/src/models/index.ts +++ b/packages/modules/region/src/models/index.ts @@ -1,2 +1,3 @@ -export { default as Country } from "./country" +export { default as RegionCountry } from "./country" export { default as Region } from "./region" + diff --git a/packages/modules/region/src/models/region.ts b/packages/modules/region/src/models/region.ts index bebd60d297..dabef95524 100644 --- a/packages/modules/region/src/models/region.ts +++ b/packages/modules/region/src/models/region.ts @@ -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(this) - - @Property({ columnType: "jsonb", nullable: true }) - metadata: Record | 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 diff --git a/packages/modules/region/src/services/region-module.ts b/packages/modules/region/src/services/region-module.ts index b7fda5cb48..34812b9d39 100644 --- a/packages/modules/region/src/services/region-module.ts +++ b/packages/modules/region/src/services/region-module.ts @@ -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 - protected readonly countryService_: ModulesSdkTypes.IMedusaInternalService + 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 { + ): Promise[]> { 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[] = [] + const operations: Promise[]>[] = [] 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 { + ): Promise[]> { 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 { + ): Promise[]> { 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(