feat: Correctly define all product module models and SQL migration script (#6906)

The only thing remaining is modifying `upsertWithReplace` to handle many-to-many constraints correctly and not cascade updates to them. This is something that we should do separately though.
This commit is contained in:
Stevche Radevski
2024-04-02 14:27:12 +02:00
committed by GitHub
parent 7fe164bc1d
commit 3dee91426e
13 changed files with 257 additions and 294 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/product": patch
---
Correctly define all product module models and SQL migration script

View File

@@ -126,15 +126,6 @@
"primary": false,
"unique": false
},
{
"keyName": "IDX_product_category_handle",
"columnNames": [
"handle"
],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "product_category_pkey",
"columnNames": [
@@ -157,7 +148,7 @@
"id"
],
"referencedTableName": "public.product_category",
"deleteRule": "set null",
"deleteRule": "cascade",
"updateRule": "cascade"
}
}
@@ -245,15 +236,6 @@
"primary": false,
"unique": false
},
{
"keyName": "IDX_product_collection_handle_unique",
"columnNames": [
"handle"
],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "product_collection_pkey",
"columnNames": [
@@ -790,15 +772,6 @@
"primary": false,
"unique": false
},
{
"keyName": "IDX_product_handle_unique",
"columnNames": [
"handle"
],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "product_pkey",
"columnNames": [
@@ -1503,42 +1476,6 @@
"primary": false,
"unique": false
},
{
"keyName": "IDX_product_variant_sku_unique",
"columnNames": [
"sku"
],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "IDX_product_variant_barcode_unique",
"columnNames": [
"barcode"
],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "IDX_product_variant_ean_unique",
"columnNames": [
"ean"
],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "IDX_product_variant_upc_unique",
"columnNames": [
"upc"
],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "product_variant_pkey",
"columnNames": [
@@ -1662,7 +1599,7 @@
"id"
],
"referencedTableName": "public.product_option_value",
"deleteRule": "set null",
"deleteRule": "cascade",
"updateRule": "cascade"
},
"product_variant_option_variant_id_foreign": {
@@ -1675,7 +1612,7 @@
"id"
],
"referencedTableName": "public.product_variant",
"deleteRule": "set null",
"deleteRule": "cascade",
"updateRule": "cascade"
}
}

View File

@@ -2,7 +2,6 @@ import { Migration } from '@mikro-orm/migrations';
export class InitialSetup20240315083440 extends Migration {
async up(): Promise<void> {
// TODO: These migrations that get generated don't even reflect the models, write by hand.
const productTables = await this.execute(
"select * from information_schema.tables where table_name = 'product' and table_schema = 'public'"
)
@@ -15,78 +14,88 @@ export class InitialSetup20240315083440 extends Migration {
this.addSql(
`alter table "product_variant" alter column "inventory_quantity" drop not null;`
)
this.addSql(
`alter table "product_category" add column "deleted_at" timestamptz null;`
)
}
this.addSql('create table if not exists "product_category" ("id" text not null, "name" text not null, "description" text not null default \'\', "handle" text not null, "mpath" text not null, "is_active" boolean not null default false, "is_internal" boolean not null default false, "rank" numeric not null default 0, "parent_category_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), constraint "product_category_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_category_path" on "product_category" ("mpath");');
// TODO: Re-enable when we run the migration from v1
// this.addSql('alter table if exists "product_category" add constraint "IDX_product_category_handle" unique ("handle");');
this.addSql('create table if not exists "product_collection" ("id" text not null, "title" text not null, "handle" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_collection_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_collection_deleted_at" on "product_collection" ("deleted_at");');
this.addSql('alter table if exists "product_collection" add constraint "IDX_product_collection_handle_unique" unique ("handle");');
this.addSql('create table if not exists "image" ("id" text not null, "url" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "image_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_image_url" on "image" ("url");');
this.addSql('create index if not exists "IDX_product_image_deleted_at" on "image" ("deleted_at");');
this.addSql('create table if not exists "product_tag" ("id" text not null, "value" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_tag_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_tag_deleted_at" on "product_tag" ("deleted_at");');
this.addSql('create table if not exists "product_type" ("id" text not null, "value" text not null, "metadata" json null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_type_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_type_deleted_at" on "product_type" ("deleted_at");');
/* --- ENTITY TABLES AND INDICES --- */
this.addSql('create table if not exists "product" ("id" text not null, "title" text not null, "handle" text not null, "subtitle" text null, "description" text null, "is_giftcard" boolean not null default false, "status" text check ("status" in (\'draft\', \'proposed\', \'published\', \'rejected\')) not null, "thumbnail" text null, "weight" text null, "length" text null, "height" text null, "width" text null, "origin_country" text null, "hs_code" text null, "mid_code" text null, "material" text null, "collection_id" text null, "type_id" text null, "discountable" boolean not null default true, "external_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, "metadata" jsonb null, constraint "product_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_type_id" on "product" ("type_id");');
this.addSql('create unique index if not exists "IDX_product_handle_unique" on "product" (handle) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_type_id" on "product" ("type_id") where deleted_at is null;');
this.addSql('create index if not exists "IDX_product_collection_id" on "product" ("collection_id") where deleted_at is null;');
this.addSql('create index if not exists "IDX_product_deleted_at" on "product" ("deleted_at");');
this.addSql('alter table if exists "product" add constraint "IDX_product_handle_unique" unique ("handle");');
this.addSql('create table if not exists "product_variant" ("id" text not null, "title" text not null, "sku" text null, "barcode" text null, "ean" text null, "upc" text null, "inventory_quantity" numeric not null default 100, "allow_backorder" boolean not null default false, "manage_inventory" boolean not null default true, "hs_code" text null, "origin_country" text null, "mid_code" text null, "material" text null, "weight" numeric null, "length" numeric null, "height" numeric null, "width" numeric null, "metadata" jsonb null, "variant_rank" numeric null default 0, "product_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_variant_pkey" primary key ("id"));');
this.addSql('create unique index if not exists "IDX_product_variant_ean_unique" on "product_variant" (ean) where deleted_at is null;')
this.addSql('create unique index if not exists "IDX_product_variant_upc_unique" on "product_variant" (upc) where deleted_at is null;')
this.addSql('create unique index if not exists "IDX_product_variant_sku_unique" on "product_variant" (sku) where deleted_at is null;')
this.addSql('create unique index if not exists "IDX_product_variant_barcode_unique" on "product_variant" (barcode) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_variant_product_id" on "product_variant" ("product_id") where deleted_at is null;');
this.addSql('create index if not exists "IDX_product_variant_deleted_at" on "product_variant" ("deleted_at");');
this.addSql('create table if not exists "product_option" ("id" text not null, "title" text not null, "product_id" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_option_pkey" primary key ("id"));');
this.addSql('create unique index if not exists "IDX_option_product_id_title_unique" on "product_option" (product_id, title) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_option_deleted_at" on "product_option" ("deleted_at");');
this.addSql('create table if not exists "product_option_value" ("id" text not null, "value" text not null, "option_id" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_option_value_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_option_value_option_id" on "product_option_value" ("option_id");');
this.addSql('create unique index if not exists "IDX_option_value_option_id_unique" on "product_option_value" (option_id, value) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_option_value_deleted_at" on "product_option_value" ("deleted_at");');
this.addSql('create table if not exists "product_tags" ("product_id" text not null, "product_tag_id" text not null, constraint "product_tags_pkey" primary key ("product_id", "product_tag_id"));');
this.addSql('create table if not exists "product_images" ("product_id" text not null, "image_id" text not null, constraint "product_images_pkey" primary key ("product_id", "image_id"));');
this.addSql('create table if not exists "product_category_product" ("product_id" text not null, "product_category_id" text not null, constraint "product_category_product_pkey" primary key ("product_id", "product_category_id"));');
this.addSql('create table if not exists "product_variant" ("id" text not null, "title" text not null, "sku" text null, "barcode" text null, "ean" text null, "upc" text null, "inventory_quantity" numeric not null default 100, "allow_backorder" boolean not null default false, "manage_inventory" boolean not null default true, "hs_code" text null, "origin_country" text null, "mid_code" text null, "material" text null, "weight" numeric null, "length" numeric null, "height" numeric null, "width" numeric null, "metadata" jsonb null, "variant_rank" numeric null default 0, "product_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_variant_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_variant_product_id" on "product_variant" ("product_id");');
this.addSql('create index if not exists "IDX_product_variant_deleted_at" on "product_variant" ("deleted_at");');
this.addSql('alter table if exists "product_variant" add constraint "IDX_product_variant_sku_unique" unique ("sku");');
this.addSql('alter table if exists "product_variant" add constraint "IDX_product_variant_barcode_unique" unique ("barcode");');
this.addSql('alter table if exists "product_variant" add constraint "IDX_product_variant_ean_unique" unique ("ean");');
this.addSql('alter table if exists "product_variant" add constraint "IDX_product_variant_upc_unique" unique ("upc");');
this.addSql('create table if not exists "product_variant_option" ("id" text not null, "option_value_id" text null, "variant_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_variant_option_pkey" primary key ("id"));');
this.addSql('create unique index if not exists "IDX_variant_option_option_value_unique" on "product_variant_option" (variant_id, option_value_id) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_variant_option_deleted_at" on "product_variant_option" ("deleted_at");');
this.addSql('alter table if exists "product_category" add constraint "product_category_parent_category_id_foreign" foreign key ("parent_category_id") references "product_category" ("id") on update cascade on delete set null;');
this.addSql('create table if not exists "image" ("id" text not null, "url" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "image_pkey" primary key ("id"));');
this.addSql('create index if not exists "IDX_product_image_url" on "image" ("url") where deleted_at is null;');
this.addSql('create index if not exists "IDX_product_image_deleted_at" on "image" ("deleted_at");');
this.addSql('create table if not exists "product_tag" ("id" text not null, "value" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_tag_pkey" primary key ("id"));');
// TODO: We need to modify upsertWithReplace to handle unique constraints
// this.addSql('create unique index if not exists "IDX_tag_value_unique" on "product_tag" (value) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_tag_deleted_at" on "product_tag" ("deleted_at");');
this.addSql('create table if not exists "product_type" ("id" text not null, "value" text not null, "metadata" json null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_type_pkey" primary key ("id"));');
this.addSql('create unique index if not exists "IDX_type_value_unique" on "product_type" (value) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_type_deleted_at" on "product_type" ("deleted_at");');
this.addSql('create table if not exists "product_collection" ("id" text not null, "title" text not null, "handle" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_collection_pkey" primary key ("id"));');
this.addSql('create unique index if not exists "IDX_collection_handle_unique" on "product_collection" (handle) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_collection_deleted_at" on "product_collection" ("deleted_at");');
this.addSql('create table if not exists "product_category" ("id" text not null, "name" text not null, "description" text not null default \'\', "handle" text not null, "mpath" text not null, "is_active" boolean not null default false, "is_internal" boolean not null default false, "rank" numeric not null default 0, "parent_category_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_category_pkey" primary key ("id"));');
this.addSql('create unique index if not exists "IDX_category_handle_unique" on "product_category" (handle) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_category_path" on "product_category" ("mpath") where deleted_at is null;');
this.addSql('create index if not exists "IDX_product_category_deleted_at" on "product_collection" ("deleted_at");');
/* --- PIVOT TABLES --- */
this.addSql('create table if not exists "product_tags" ("product_id" text not null, "product_tag_id" text not null, constraint "product_tags_pkey" primary key ("product_id", "product_tag_id"));');
this.addSql('create table if not exists "product_images" ("product_id" text not null, "image_id" text not null, constraint "product_images_pkey" primary key ("product_id", "image_id"));');
this.addSql('create table if not exists "product_category_product" ("product_id" text not null, "product_category_id" text not null, constraint "product_category_product_pkey" primary key ("product_id", "product_category_id"));');
/* --- FOREIGN KEYS AND CONSTRAINTS --- */
this.addSql('alter table if exists "product" add constraint "product_collection_id_foreign" foreign key ("collection_id") references "product_collection" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "product" add constraint "product_type_id_foreign" foreign key ("type_id") references "product_type" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "product_variant" add constraint "product_variant_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_option" add constraint "product_option_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_option_value" add constraint "product_option_value_option_id_foreign" foreign key ("option_id") references "product_option" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_tags" add constraint "product_tags_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_tags" add constraint "product_tags_product_tag_id_foreign" foreign key ("product_tag_id") references "product_tag" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_variant_option" add constraint "product_variant_option_option_value_id_foreign" foreign key ("option_value_id") references "product_option_value" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_variant_option" add constraint "product_variant_option_variant_id_foreign" foreign key ("variant_id") references "product_variant" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_images" add constraint "product_images_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_images" add constraint "product_images_image_id_foreign" foreign key ("image_id") references "image" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_tags" add constraint "product_tags_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_tags" add constraint "product_tags_product_tag_id_foreign" foreign key ("product_tag_id") references "product_tag" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_category_product" add constraint "product_category_product_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_category_product" add constraint "product_category_product_product_category_id_foreign" foreign key ("product_category_id") references "product_category" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_variant" add constraint "product_variant_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "product_variant_option" add constraint "product_variant_option_option_value_id_foreign" foreign key ("option_value_id") references "product_option_value" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "product_variant_option" add constraint "product_variant_option_variant_id_foreign" foreign key ("variant_id") references "product_variant" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "product_category" add constraint "product_category_parent_category_id_foreign" foreign key ("parent_category_id") references "product_category" ("id") on update cascade on delete cascade;');
}
}

View File

@@ -1,29 +1,49 @@
import { generateEntityId, kebabCase } from "@medusajs/utils"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
kebabCase,
} from "@medusajs/utils"
import {
BeforeCreate,
Collection,
Entity,
EventArgs,
Filter,
Index,
ManyToMany,
ManyToOne,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { DAL } from "@medusajs/types"
import Product from "./product"
type OptionalFields = DAL.SoftDeletableEntityDateColumns
const categoryHandleIndexName = "IDX_category_handle_unique"
const categoryHandleIndexStatement = createPsqlIndexStatementHelper({
name: categoryHandleIndexName,
tableName: "product_category",
columns: ["handle"],
unique: true,
where: "deleted_at IS NULL",
})
const categoryMpathIndexName = "IDX_product_category_path"
const categoryMpathIndexStatement = createPsqlIndexStatementHelper({
name: categoryMpathIndexName,
tableName: "product_category",
columns: ["mpath"],
unique: false,
where: "deleted_at IS NULL",
})
categoryMpathIndexStatement.MikroORMIndex()
categoryHandleIndexStatement.MikroORMIndex()
@Entity({ tableName: "product_category" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductCategory {
[OptionalProps]?: OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@@ -33,17 +53,9 @@ class ProductCategory {
@Property({ columnType: "text", default: "", nullable: false })
description?: string
@Unique({
name: "IDX_product_category_handle",
properties: ["handle"],
})
@Property({ columnType: "text", nullable: false })
handle?: string
@Index({
name: "IDX_product_category_path",
properties: ["mpath"],
})
@Property({ columnType: "text", nullable: false })
mpath?: string
@@ -61,6 +73,7 @@ class ProductCategory {
fieldName: "parent_category_id",
nullable: true,
mapToPk: true,
onDelete: "cascade",
})
parent_category_id?: string | null
@@ -88,6 +101,10 @@ class ProductCategory {
})
updated_at?: Date
@Index({ name: "IDX_product_category_deleted_at" })
@Property({ columnType: "timestamptz", nullable: true })
deleted_at?: Date
@ManyToMany(() => Product, (product) => product.categories)
products = new Collection<Product>(this)

View File

@@ -6,24 +6,31 @@ import {
Index,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { DAL } from "@medusajs/types"
import { DALUtils, generateEntityId, kebabCase } from "@medusajs/utils"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
kebabCase,
} from "@medusajs/utils"
import Product from "./product"
type OptionalRelations = "products"
type OptionalFields = DAL.SoftDeletableEntityDateColumns
const collectionHandleIndexName = "IDX_collection_handle_unique"
const collectionHandleIndexStatement = createPsqlIndexStatementHelper({
name: collectionHandleIndexName,
tableName: "product_collection",
columns: ["handle"],
unique: true,
where: "deleted_at IS NULL",
})
collectionHandleIndexStatement.MikroORMIndex()
@Entity({ tableName: "product_collection" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductCollection {
[OptionalProps]?: OptionalRelations | OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@@ -31,10 +38,6 @@ class ProductCollection {
title: string
@Property({ columnType: "text" })
@Unique({
name: "IDX_product_collection_handle_unique",
properties: ["handle"],
})
handle?: string
@OneToMany(() => Product, (product) => product.collection)
@@ -63,16 +66,8 @@ class ProductCollection {
deleted_at?: Date
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "pcol")
if (!this.handle && this.title) {
this.handle = kebabCase(this.title)
}
}
@BeforeCreate()
onCreate() {
onInit() {
this.id = generateEntityId(this.id, "pcol")
if (!this.handle && this.title) {

View File

@@ -6,27 +6,33 @@ import {
Index,
ManyToMany,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { DAL } from "@medusajs/types"
import { DALUtils, generateEntityId } from "@medusajs/utils"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import Product from "./product"
type OptionalRelations = "products"
type OptionalFields = DAL.SoftDeletableEntityDateColumns
const imageUrlIndexName = "IDX_product_image_url"
const imageUrlIndexStatement = createPsqlIndexStatementHelper({
name: imageUrlIndexName,
tableName: "image",
columns: ["url"],
unique: false,
where: "deleted_at IS NULL",
})
imageUrlIndexStatement.MikroORMIndex()
@Entity({ tableName: "image" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductImage {
[OptionalProps]?: OptionalRelations | OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@Index({ name: "IDX_product_image_url" })
@Property({ columnType: "text" })
url: string

View File

@@ -1,4 +1,3 @@
import { DAL } from "@medusajs/types"
import {
DALUtils,
createPsqlIndexStatementHelper,
@@ -13,18 +12,10 @@ import {
ManyToOne,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { ProductOption, ProductVariant, ProductVariantOption } from "./index"
type OptionalFields =
| "allow_backorder"
| "manage_inventory"
| "option_id"
| DAL.SoftDeletableEntityDateColumns
type OptionalRelations = "product" | "option" | "variant"
import { ProductOption, ProductVariantOption } from "./index"
const optionValueOptionIdIndexName = "IDX_option_value_option_id_unique"
const optionValueOptionIdIndexStatement = createPsqlIndexStatementHelper({
@@ -39,8 +30,6 @@ optionValueOptionIdIndexStatement.MikroORMIndex()
@Entity({ tableName: "product_option_value" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductOptionValue {
[OptionalProps]?: OptionalFields | OptionalRelations
@PrimaryKey({ columnType: "text" })
id!: string
@@ -52,7 +41,6 @@ class ProductOptionValue {
fieldName: "option_id",
mapToPk: true,
nullable: true,
index: "IDX_product_option_value_option_id",
onDelete: "cascade",
})
option_id: string | null
@@ -89,13 +77,8 @@ class ProductOptionValue {
deleted_at?: Date
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "optval")
this.option_id ??= this.option?.id ?? null
}
@BeforeCreate()
beforeCreate() {
onInit() {
this.id = generateEntityId(this.id, "optval")
this.option_id ??= this.option?.id ?? null
}

View File

@@ -1,4 +1,3 @@
import { DAL } from "@medusajs/types"
import {
DALUtils,
createPsqlIndexStatementHelper,
@@ -14,19 +13,12 @@ import {
ManyToOne,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { Product } from "./index"
import ProductOptionValue from "./product-option-value"
type OptionalRelations =
| "values"
| "product"
| DAL.SoftDeletableEntityDateColumns
type OptionalFields = "product_id"
const optionProductIdTitleIndexName = "IDX_option_product_id_title_unique"
const optionProductIdTitleIndexStatement = createPsqlIndexStatementHelper({
name: optionProductIdTitleIndexName,
@@ -40,8 +32,6 @@ optionProductIdTitleIndexStatement.MikroORMIndex()
@Entity({ tableName: "product_option" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductOption {
[OptionalProps]?: OptionalRelations | OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@@ -64,7 +54,7 @@ class ProductOption {
product: Product | null
@OneToMany(() => ProductOptionValue, (value) => value.option, {
cascade: [Cascade.PERSIST, Cascade.REMOVE, "soft-remove" as any],
cascade: [Cascade.PERSIST, "soft-remove" as any],
})
values = new Collection<ProductOptionValue>(this)
@@ -91,13 +81,8 @@ class ProductOption {
deleted_at?: Date
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "opt")
this.product_id ??= this.product?.id ?? null
}
@BeforeCreate()
beforeCreate() {
onInit() {
this.id = generateEntityId(this.id, "opt")
this.product_id ??= this.product?.id ?? null
}

View File

@@ -6,23 +6,30 @@ import {
Index,
ManyToMany,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { DAL } from "@medusajs/types"
import { DALUtils, generateEntityId } from "@medusajs/utils"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import Product from "./product"
type OptionalRelations = "products"
type OptionalFields = DAL.SoftDeletableEntityDateColumns
const tagValueIndexName = "IDX_tag_value_unique"
const tagValueIndexStatement = createPsqlIndexStatementHelper({
name: tagValueIndexName,
tableName: "product_tag",
columns: ["value"],
unique: true,
where: "deleted_at IS NULL",
})
tagValueIndexStatement.MikroORMIndex()
@Entity({ tableName: "product_tag" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductTag {
[OptionalProps]?: OptionalRelations | OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@@ -55,12 +62,8 @@ class ProductTag {
products = new Collection<Product>(this)
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ptag")
}
@BeforeCreate()
onCreate() {
onInit() {
this.id = generateEntityId(this.id, "ptag")
}
}

View File

@@ -4,21 +4,29 @@ import {
Filter,
Index,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { DAL } from "@medusajs/types"
import { DALUtils, generateEntityId } from "@medusajs/utils"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
type OptionalFields = DAL.SoftDeletableEntityDateColumns
const typeValueIndexName = "IDX_type_value_unique"
const typeValueIndexStatement = createPsqlIndexStatementHelper({
name: typeValueIndexName,
tableName: "product_type",
columns: ["value"],
unique: true,
where: "deleted_at IS NULL",
})
typeValueIndexStatement.MikroORMIndex()
@Entity({ tableName: "product_type" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductType {
[OptionalProps]?: OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@@ -48,12 +56,8 @@ class ProductType {
deleted_at?: Date
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ptyp")
}
@BeforeCreate()
onCreate() {
onInit() {
this.id = generateEntityId(this.id, "ptyp")
}
}

View File

@@ -36,6 +36,7 @@ class ProductVariantOption {
nullable: true,
fieldName: "option_value_id",
mapToPk: true,
onDelete: "cascade",
})
option_value_id!: string
@@ -50,6 +51,7 @@ class ProductVariantOption {
nullable: true,
fieldName: "variant_id",
mapToPk: true,
onDelete: "cascade",
})
variant_id: string | null
@@ -79,14 +81,8 @@ class ProductVariantOption {
deleted_at?: Date
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "varopt")
this.variant_id ??= this.variant?.id ?? null
this.option_value_id ??= this.option_value?.id ?? null
}
@BeforeCreate()
beforeCreate() {
onInit() {
this.id = generateEntityId(this.id, "varopt")
this.variant_id ??= this.variant?.id ?? null
this.option_value_id ??= this.option_value?.id ?? null

View File

@@ -1,6 +1,6 @@
import { DAL } from "@medusajs/types"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
optionalNumericSerializer,
} from "@medusajs/utils"
@@ -14,26 +14,65 @@ import {
ManyToOne,
OnInit,
OneToMany,
OptionalProps,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { Product } from "@models"
import ProductVariantOption from "./product-variant-option"
type OptionalFields =
| "allow_backorder"
| "manage_inventory"
| "product"
| "product_id"
| DAL.SoftDeletableEntityDateColumns
const variantSkuIndexName = "IDX_product_variant_sku_unique"
const variantSkuIndexStatement = createPsqlIndexStatementHelper({
name: variantSkuIndexName,
tableName: "product_variant",
columns: ["sku"],
unique: true,
where: "deleted_at IS NULL",
})
const variantBarcodeIndexName = "IDX_product_variant_barcode_unique"
const variantBarcodeIndexStatement = createPsqlIndexStatementHelper({
name: variantBarcodeIndexName,
tableName: "product_variant",
columns: ["barcode"],
unique: true,
where: "deleted_at IS NULL",
})
const variantEanIndexName = "IDX_product_variant_ean_unique"
const variantEanIndexStatement = createPsqlIndexStatementHelper({
name: variantEanIndexName,
tableName: "product_variant",
columns: ["ean"],
unique: true,
where: "deleted_at IS NULL",
})
const variantUpcIndexName = "IDX_product_variant_upc_unique"
const variantUpcIndexStatement = createPsqlIndexStatementHelper({
name: variantUpcIndexName,
tableName: "product_variant",
columns: ["upc"],
unique: true,
where: "deleted_at IS NULL",
})
const variantProductIdIndexName = "IDX_product_variant_product_id"
const variantProductIdIndexStatement = createPsqlIndexStatementHelper({
name: variantProductIdIndexName,
tableName: "product_variant",
columns: ["product_id"],
unique: false,
where: "deleted_at IS NULL",
})
variantProductIdIndexStatement.MikroORMIndex()
variantSkuIndexStatement.MikroORMIndex()
variantBarcodeIndexStatement.MikroORMIndex()
variantEanIndexStatement.MikroORMIndex()
variantUpcIndexStatement.MikroORMIndex()
@Entity({ tableName: "product_variant" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class ProductVariant {
[OptionalProps]?: OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@@ -41,33 +80,18 @@ class ProductVariant {
title: string
@Property({ columnType: "text", nullable: true })
@Unique({
name: "IDX_product_variant_sku_unique",
properties: ["sku"],
})
sku?: string | null
@Property({ columnType: "text", nullable: true })
@Unique({
name: "IDX_product_variant_barcode_unique",
properties: ["barcode"],
})
barcode?: string | null
@Property({ columnType: "text", nullable: true })
@Unique({
name: "IDX_product_variant_ean_unique",
properties: ["ean"],
})
ean?: string | null
@Property({ columnType: "text", nullable: true })
@Unique({
name: "IDX_product_variant_upc_unique",
properties: ["upc"],
})
upc?: string | null
// TODO: replace with BigNumber
// Note: Upon serialization, this turns to a string. This is on purpose, because you would loose
// precision if you cast numeric to JS number, as JS number is a float.
// Ref: https://github.com/mikro-orm/mikro-orm/issues/2295
@@ -111,6 +135,7 @@ class ProductVariant {
@Property({ columnType: "jsonb", nullable: true })
metadata?: Record<string, unknown> | null
// TODO: replace with BigNumber, or in this case a normal int should work
@Property({
columnType: "numeric",
nullable: true,
@@ -124,7 +149,6 @@ class ProductVariant {
nullable: true,
onDelete: "cascade",
fieldName: "product_id",
index: "IDX_product_variant_product_id",
mapToPk: true,
})
product_id: string | null
@@ -135,6 +159,15 @@ class ProductVariant {
})
product: Product | null
@OneToMany(
() => ProductVariantOption,
(variantOption) => variantOption.variant,
{
cascade: [Cascade.PERSIST, "soft-remove" as any],
}
)
options = new Collection<ProductVariantOption>(this)
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
@@ -154,23 +187,9 @@ class ProductVariant {
@Property({ columnType: "timestamptz", nullable: true })
deleted_at?: Date
@OneToMany(
() => ProductVariantOption,
(variantOption) => variantOption.variant,
{
cascade: [Cascade.PERSIST, Cascade.REMOVE, "soft-remove" as any],
}
)
options = new Collection<ProductVariantOption>(this)
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "variant")
this.product_id ??= this.product?.id ?? null
}
@BeforeCreate()
onCreate() {
onInit() {
this.id = generateEntityId(this.id, "variant")
this.product_id ??= this.product?.id ?? null
}

View File

@@ -9,14 +9,12 @@ import {
ManyToOne,
OneToMany,
OnInit,
OptionalProps,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { DAL } from "@medusajs/types"
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
kebabCase,
@@ -30,19 +28,39 @@ import ProductTag from "./product-tag"
import ProductType from "./product-type"
import ProductVariant from "./product-variant"
type OptionalRelations = "collection" | "type"
type OptionalFields =
| "collection_id"
| "type_id"
| "is_giftcard"
| "discountable"
| DAL.SoftDeletableEntityDateColumns
const productHandleIndexName = "IDX_product_handle_unique"
const productHandleIndexStatement = createPsqlIndexStatementHelper({
name: productHandleIndexName,
tableName: "product",
columns: ["handle"],
unique: true,
where: "deleted_at IS NULL",
})
const productTypeIndexName = "IDX_product_type_id"
const productTypeIndexStatement = createPsqlIndexStatementHelper({
name: productTypeIndexName,
tableName: "product",
columns: ["type_id"],
unique: false,
where: "deleted_at IS NULL",
})
const productCollectionIndexName = "IDX_product_collection_id"
const productCollectionIndexStatement = createPsqlIndexStatementHelper({
name: productCollectionIndexName,
tableName: "product",
columns: ["collection_id"],
unique: false,
where: "deleted_at IS NULL",
})
productTypeIndexStatement.MikroORMIndex()
productCollectionIndexStatement.MikroORMIndex()
productHandleIndexStatement.MikroORMIndex()
@Entity({ tableName: "product" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class Product {
[OptionalProps]?: OptionalRelations | OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@@ -50,11 +68,7 @@ class Product {
title: string
@Property({ columnType: "text" })
@Unique({
name: "IDX_product_handle_unique",
properties: ["handle"],
})
handle?: string | null
handle?: string
@Property({ columnType: "text", nullable: true })
subtitle?: string | null
@@ -111,6 +125,7 @@ class Product {
nullable: true,
fieldName: "collection_id",
mapToPk: true,
onDelete: "set null",
})
collection_id: string | null
@@ -124,8 +139,8 @@ class Product {
columnType: "text",
nullable: true,
fieldName: "type_id",
index: "IDX_product_type_id",
mapToPk: true,
onDelete: "set null",
})
type_id: string | null
@@ -145,7 +160,6 @@ class Product {
@ManyToMany(() => ProductImage, "products", {
owner: true,
pivotTable: "product_images",
index: "IDX_product_image_id",
joinColumn: "product_id",
inverseJoinColumn: "image_id",
})
@@ -186,18 +200,8 @@ class Product {
metadata?: Record<string, unknown> | null
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "prod")
this.type_id ??= this.type?.id ?? null
this.collection_id ??= this.collection?.id ?? null
if (!this.handle && this.title) {
this.handle = kebabCase(this.title)
}
}
@BeforeCreate()
beforeCreate() {
onInit() {
this.id = generateEntityId(this.id, "prod")
this.type_id ??= this.type?.id ?? null
this.collection_id ??= this.collection?.id ?? null