@infuro/cms-core 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +17 -13
  2. package/dist/admin.cjs +8522 -3281
  3. package/dist/admin.cjs.map +1 -1
  4. package/dist/admin.d.cts +13 -2
  5. package/dist/admin.d.ts +13 -2
  6. package/dist/admin.js +8210 -2957
  7. package/dist/admin.js.map +1 -1
  8. package/dist/api.cjs +5099 -1167
  9. package/dist/api.cjs.map +1 -1
  10. package/dist/api.d.cts +2 -2
  11. package/dist/api.d.ts +2 -2
  12. package/dist/api.js +4847 -909
  13. package/dist/api.js.map +1 -1
  14. package/dist/auth.cjs +461 -62
  15. package/dist/auth.cjs.map +1 -1
  16. package/dist/auth.d.cts +73 -47
  17. package/dist/auth.d.ts +73 -47
  18. package/dist/auth.js +425 -38
  19. package/dist/auth.js.map +1 -1
  20. package/dist/cli.cjs +21 -14
  21. package/dist/cli.cjs.map +1 -1
  22. package/dist/cli.js +21 -14
  23. package/dist/cli.js.map +1 -1
  24. package/dist/{index-D9SdaDJ0.d.ts → index-BcMRGNjS.d.cts} +36 -3
  25. package/dist/{index-BPQSXgXF.d.cts → index-Bda8D1Rm.d.ts} +36 -3
  26. package/dist/index.cjs +4753 -740
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +301 -36
  29. package/dist/index.d.ts +301 -36
  30. package/dist/index.js +4750 -768
  31. package/dist/index.js.map +1 -1
  32. package/dist/migrations/1775400000001-AddDiscountAndOrderAddresses.ts +274 -0
  33. package/dist/migrations/1775800000000-VendorsAndVendorUsers.ts +74 -0
  34. package/dist/migrations/1775900000000-StoreVendorIdColumns.ts +124 -0
  35. package/dist/migrations/1775910000000-VendorOwnerRbacSeed.ts +54 -0
  36. package/dist/migrations/1775920000000-VendorOwnerSettingsPermission.ts +25 -0
  37. package/dist/migrations/1775930000000-VendorGroupStorePermissions.ts +54 -0
  38. package/dist/migrations/1775940000000-VendorRls.ts +71 -0
  39. package/dist/migrations/1775950000000-VendorCustomers.ts +87 -0
  40. package/dist/migrations/1775950000001-VendorCustomersPermissions.ts +67 -0
  41. package/dist/migrations/1778652250860-CreateCurrencyTables.ts +16 -0
  42. package/dist/migrations/1778652250861-OrderAddressTables.ts +88 -0
  43. package/dist/migrations/1778652250862-UpdateDiscountandRules.ts +100 -0
  44. package/dist/migrations/1778652250863-MakeCouponCodeNullable.ts +19 -0
  45. package/dist/migrations/1778652250864-RemoveUniqueConstraintFromDiscountCouponCode.ts +22 -0
  46. package/dist/migrations/1778652250865-FixOrdersAddressForeignKeys.ts +71 -0
  47. package/dist/migrations/1778660000000-DiscountVendorId.ts +90 -0
  48. package/dist/migrations/1778660000001-FixOrdersAddressFkToOrderAddresses.ts +79 -0
  49. package/dist/migrations/1778660000002-VendorUserIdInviteStatus.ts +61 -0
  50. package/dist/theme.d.cts +2 -2
  51. package/dist/theme.d.ts +2 -2
  52. package/dist/{types-D34wmivy.d.cts → types-DCVaXlPV.d.cts} +1 -1
  53. package/dist/{types-D34wmivy.d.ts → types-DCVaXlPV.d.ts} +1 -1
  54. package/dist/vendor-scope-DTYhJk_I.d.cts +129 -0
  55. package/dist/vendor-scope-DTYhJk_I.d.ts +129 -0
  56. package/package.json +9 -2
  57. package/dist/helpers-dlrF_49e.d.cts +0 -60
  58. package/dist/helpers-dlrF_49e.d.ts +0 -60
@@ -0,0 +1,274 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ export class AddDiscountAndOrderAddresses1775400000001
4
+ implements MigrationInterface
5
+ {
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+
8
+ // ENUMS
9
+
10
+ await queryRunner.query(`
11
+ CREATE TYPE "discounts_discounttype_enum"
12
+ AS ENUM ('PERCENTAGE', 'FLAT')
13
+ `);
14
+
15
+ await queryRunner.query(`
16
+ CREATE TYPE "discounts_status_enum"
17
+ AS ENUM ('ACTIVE', 'INACTIVE', 'EXPIRED')
18
+ `);
19
+
20
+ await queryRunner.query(`
21
+ CREATE TYPE "order_addresses_addresstype_enum"
22
+ AS ENUM ('shipping_address', 'billing_address')
23
+ `);
24
+
25
+ await queryRunner.query(`
26
+ CREATE TYPE "discount_rules_conditiontype_enum"
27
+ AS ENUM ('group', 'rule')
28
+ `);
29
+
30
+ await queryRunner.query(`
31
+ CREATE TYPE "discount_rules_conditionoperator_enum"
32
+ AS ENUM ('and', 'or')
33
+ `);
34
+
35
+ await queryRunner.query(`
36
+ CREATE TYPE "discount_rules_type_enum"
37
+ AS ENUM (
38
+ 'minAmount',
39
+ 'user',
40
+ 'product',
41
+ 'category',
42
+ 'cart',
43
+ 'paymentMethod',
44
+ 'order',
45
+ 'quantity',
46
+ 'time',
47
+ 'shipping'
48
+ )
49
+ `);
50
+
51
+ await queryRunner.query(`
52
+ CREATE TYPE "discount_rules_subtype_enum"
53
+ AS ENUM (
54
+ 'userType',
55
+ 'userId',
56
+ 'productId',
57
+ 'categoryId',
58
+ 'cartId',
59
+ 'orderId',
60
+ 'firstOrder',
61
+ 'nthOrder',
62
+ 'dateRange',
63
+ 'dayOfWeek',
64
+ 'timeRange'
65
+ )
66
+ `);
67
+
68
+ await queryRunner.query(`
69
+ CREATE TYPE "discount_rules_comparisonoperator_enum"
70
+ AS ENUM (
71
+ 'eq',
72
+ 'neq',
73
+ 'gt',
74
+ 'gte',
75
+ 'lt',
76
+ 'lte',
77
+ 'in',
78
+ 'notIn',
79
+ 'between'
80
+ )
81
+ `);
82
+
83
+ // DISCOUNTS
84
+
85
+ await queryRunner.query(`
86
+ CREATE TABLE "discounts" (
87
+ "id" SERIAL PRIMARY KEY,
88
+ "name" varchar NOT NULL,
89
+ "couponCode" varchar UNIQUE NOT NULL,
90
+ "description" varchar,
91
+ "discountType" "discounts_discounttype_enum" NOT NULL,
92
+ "value" decimal(10,2) NOT NULL,
93
+ "maxDiscountAmount" decimal(10,2),
94
+ "maxTotalUsage" int,
95
+ "maxUsagePerUser" int,
96
+ "usedCount" int DEFAULT 0,
97
+ "validFrom" timestamp NOT NULL,
98
+ "validUntil" timestamp NOT NULL,
99
+ "status" "discounts_status_enum" DEFAULT 'ACTIVE',
100
+ "canCombineWithOtherDiscounts" boolean NOT NULL
101
+ )
102
+ `);
103
+
104
+ // DISCOUNT RULES
105
+
106
+ await queryRunner.query(`
107
+ CREATE TABLE "discount_rules" (
108
+ "id" SERIAL PRIMARY KEY,
109
+ "discountId" int NOT NULL,
110
+ "parentId" int,
111
+ "conditionType" "discount_rules_conditiontype_enum" NOT NULL,
112
+ "conditionOperator" "discount_rules_conditionoperator_enum",
113
+ "type" "discount_rules_type_enum",
114
+ "subType" "discount_rules_subtype_enum",
115
+ "comparisonOperator" "discount_rules_comparisonoperator_enum",
116
+ "value" jsonb NOT NULL
117
+ )
118
+ `);
119
+
120
+ await queryRunner.query(`
121
+ ALTER TABLE "discount_rules"
122
+ ADD CONSTRAINT "FK_discount_rules_discount"
123
+ FOREIGN KEY ("discountId") REFERENCES "discounts"("id")
124
+ ON DELETE CASCADE
125
+ `);
126
+
127
+ await queryRunner.query(`
128
+ ALTER TABLE "discount_rules"
129
+ ADD CONSTRAINT "FK_discount_rules_parent"
130
+ FOREIGN KEY ("parentId") REFERENCES "discount_rules"("id")
131
+ ON DELETE CASCADE
132
+ `);
133
+
134
+ // ORDER DISCOUNTS
135
+
136
+ await queryRunner.query(`
137
+ CREATE TABLE "order_discounts" (
138
+ "id" SERIAL PRIMARY KEY,
139
+ "orderId" int NOT NULL,
140
+ "orderItemId" int NOT NULL,
141
+ "userId" int NOT NULL,
142
+ "discountId" int NOT NULL,
143
+ "discountCode" varchar NOT NULL,
144
+ "discountAmount" decimal(10,2) NOT NULL,
145
+ "metaData" jsonb
146
+ )
147
+ `);
148
+
149
+ await queryRunner.query(`
150
+ ALTER TABLE "order_discounts"
151
+ ADD CONSTRAINT "FK_order_discounts_order"
152
+ FOREIGN KEY ("orderId") REFERENCES "orders"("id")
153
+ ON DELETE CASCADE
154
+ `);
155
+
156
+ await queryRunner.query(`
157
+ ALTER TABLE "order_discounts"
158
+ ADD CONSTRAINT "FK_order_discounts_item"
159
+ FOREIGN KEY ("orderItemId") REFERENCES "order_items"("id")
160
+ ON DELETE CASCADE
161
+ `);
162
+
163
+ await queryRunner.query(`
164
+ ALTER TABLE "order_discounts"
165
+ ADD CONSTRAINT "FK_order_discounts_discount"
166
+ FOREIGN KEY ("discountId") REFERENCES "discounts"("id")
167
+ ON DELETE CASCADE
168
+ `);
169
+
170
+ // ORDER ADDRESSES
171
+
172
+ await queryRunner.query(`
173
+ CREATE TABLE "order_addresses" (
174
+ "id" SERIAL PRIMARY KEY,
175
+ "contactId" int NOT NULL,
176
+ "addressType" "order_addresses_addresstype_enum" NOT NULL,
177
+ "line1" varchar NOT NULL,
178
+ "line2" varchar,
179
+ "city" varchar NOT NULL,
180
+ "state" varchar NOT NULL,
181
+ "postalCode" varchar NOT NULL,
182
+ "country" varchar NOT NULL,
183
+ "createdAt" timestamp DEFAULT CURRENT_TIMESTAMP,
184
+ "updatedAt" timestamp DEFAULT CURRENT_TIMESTAMP
185
+ )
186
+ `);
187
+
188
+ await queryRunner.query(`
189
+ ALTER TABLE "order_addresses"
190
+ ADD CONSTRAINT "FK_order_addresses_contact"
191
+ FOREIGN KEY ("contactId") REFERENCES "contacts"("id")
192
+ ON DELETE CASCADE
193
+ `);
194
+
195
+ // ALTER ORDER ITEMS
196
+
197
+ await queryRunner.query(`
198
+ ALTER TABLE "order_items"
199
+ ADD COLUMN "discountIds" integer[] DEFAULT ARRAY[]::INTEGER[]
200
+ `);
201
+
202
+ await queryRunner.query(`
203
+ ALTER TABLE "order_items"
204
+ ADD COLUMN "subTotal" decimal(10,2)
205
+ `);
206
+
207
+ await queryRunner.query(`
208
+ ALTER TABLE "order_items"
209
+ ADD COLUMN "discount" decimal(10,2)
210
+ `);
211
+
212
+ await queryRunner.query(`
213
+ ALTER TABLE "order_items"
214
+ ADD COLUMN "discountedAmount" decimal(10,2)
215
+ `);
216
+ }
217
+
218
+ public async down(queryRunner: QueryRunner): Promise<void> {
219
+
220
+ // DROP FK FIRST (SAFE)
221
+
222
+ await queryRunner.query(`
223
+ ALTER TABLE "order_addresses" DROP CONSTRAINT IF EXISTS "FK_order_addresses_contact"
224
+ `);
225
+
226
+ await queryRunner.query(`
227
+ ALTER TABLE "order_discounts" DROP CONSTRAINT IF EXISTS "FK_order_discounts_discount"
228
+ `);
229
+
230
+ await queryRunner.query(`
231
+ ALTER TABLE "order_discounts" DROP CONSTRAINT IF EXISTS "FK_order_discounts_item"
232
+ `);
233
+
234
+ await queryRunner.query(`
235
+ ALTER TABLE "order_discounts" DROP CONSTRAINT IF EXISTS "FK_order_discounts_order"
236
+ `);
237
+
238
+ await queryRunner.query(`
239
+ ALTER TABLE "discount_rules" DROP CONSTRAINT IF EXISTS "FK_discount_rules_parent"
240
+ `);
241
+
242
+ await queryRunner.query(`
243
+ ALTER TABLE "discount_rules" DROP CONSTRAINT IF EXISTS "FK_discount_rules_discount"
244
+ `);
245
+
246
+ // DROP TABLES
247
+
248
+ await queryRunner.query(`DROP TABLE IF EXISTS "order_addresses"`);
249
+ await queryRunner.query(`DROP TABLE IF EXISTS "order_discounts"`);
250
+ await queryRunner.query(`DROP TABLE IF EXISTS "discount_rules"`);
251
+ await queryRunner.query(`DROP TABLE IF EXISTS "discounts"`);
252
+
253
+ // ROLLBACK ORDER ITEMS
254
+
255
+ await queryRunner.query(`ALTER TABLE "order_items" DROP COLUMN IF EXISTS "discountIds"`);
256
+ await queryRunner.query(`ALTER TABLE "order_items" DROP COLUMN IF EXISTS "subTotal"`);
257
+ await queryRunner.query(`ALTER TABLE "order_items" DROP COLUMN IF EXISTS "discount"`);
258
+ await queryRunner.query(`ALTER TABLE "order_items" DROP COLUMN IF EXISTS "discountedAmount"`);
259
+ await queryRunner.query(`ALTER TABLE "order_items" DROP COLUMN IF EXISTS "metadata"`);
260
+ await queryRunner.query(`ALTER TABLE "order_items" DROP COLUMN IF EXISTS "createdAt"`);
261
+ await queryRunner.query(`ALTER TABLE "order_items" DROP COLUMN IF EXISTS "updatedAt"`);
262
+
263
+ // DROP ENUMS
264
+
265
+ await queryRunner.query(`DROP TYPE IF EXISTS "discounts_discounttype_enum"`);
266
+ await queryRunner.query(`DROP TYPE IF EXISTS "discounts_status_enum"`);
267
+ await queryRunner.query(`DROP TYPE IF EXISTS "order_addresses_addresstype_enum"`);
268
+ await queryRunner.query(`DROP TYPE IF EXISTS "discount_rules_conditiontype_enum"`);
269
+ await queryRunner.query(`DROP TYPE IF EXISTS "discount_rules_conditionoperator_enum"`);
270
+ await queryRunner.query(`DROP TYPE IF EXISTS "discount_rules_type_enum"`);
271
+ await queryRunner.query(`DROP TYPE IF EXISTS "discount_rules_subtype_enum"`);
272
+ await queryRunner.query(`DROP TYPE IF EXISTS "discount_rules_comparisonoperator_enum"`);
273
+ }
274
+ }
@@ -0,0 +1,74 @@
1
+ import type { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ export class VendorsAndVendorUsers1775800000000 implements MigrationInterface {
4
+ name = 'VendorsAndVendorUsers1775800000000';
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`
8
+ CREATE TABLE IF NOT EXISTS "vendors" (
9
+ "id" SERIAL NOT NULL,
10
+ "name" character varying NOT NULL,
11
+ "slug" character varying NOT NULL,
12
+ "email" character varying,
13
+ "phone" character varying,
14
+ "logo" character varying,
15
+ "description" text,
16
+ "metadata" jsonb,
17
+ "active" boolean NOT NULL DEFAULT true,
18
+ "createdAt" TIMESTAMP NOT NULL DEFAULT now(),
19
+ "updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
20
+ "deletedAt" TIMESTAMP,
21
+ "deleted" boolean NOT NULL DEFAULT false,
22
+ "createdBy" integer,
23
+ "updatedBy" integer,
24
+ "deletedBy" integer,
25
+ CONSTRAINT "UQ_vendors_slug" UNIQUE ("slug"),
26
+ CONSTRAINT "PK_vendors" PRIMARY KEY ("id")
27
+ )
28
+ `);
29
+
30
+ await queryRunner.query(`
31
+ CREATE TABLE IF NOT EXISTS "vendor_users" (
32
+ "id" SERIAL NOT NULL,
33
+ "vendorId" integer NOT NULL,
34
+ "userId" integer NOT NULL,
35
+ "role" character varying NOT NULL DEFAULT 'staff',
36
+ "createdAt" TIMESTAMP NOT NULL DEFAULT now(),
37
+ "updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
38
+ CONSTRAINT "UQ_vendor_users_vendor_user" UNIQUE ("vendorId", "userId"),
39
+ CONSTRAINT "PK_vendor_users" PRIMARY KEY ("id")
40
+ )
41
+ `);
42
+
43
+ await queryRunner.query(`
44
+ CREATE INDEX IF NOT EXISTS "IDX_vendor_users_vendor" ON "vendor_users" ("vendorId")
45
+ `);
46
+ await queryRunner.query(`
47
+ CREATE INDEX IF NOT EXISTS "IDX_vendor_users_user" ON "vendor_users" ("userId")
48
+ `);
49
+
50
+ await queryRunner.query(`
51
+ DO $$ BEGIN
52
+ ALTER TABLE "vendor_users" ADD CONSTRAINT "FK_vendor_users_vendorId"
53
+ FOREIGN KEY ("vendorId") REFERENCES "vendors"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
54
+ EXCEPTION WHEN duplicate_object THEN NULL;
55
+ END $$
56
+ `);
57
+ await queryRunner.query(`
58
+ DO $$ BEGIN
59
+ ALTER TABLE "vendor_users" ADD CONSTRAINT "FK_vendor_users_userId"
60
+ FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
61
+ EXCEPTION WHEN duplicate_object THEN NULL;
62
+ END $$
63
+ `);
64
+ }
65
+
66
+ public async down(queryRunner: QueryRunner): Promise<void> {
67
+ await queryRunner.query(`ALTER TABLE "vendor_users" DROP CONSTRAINT IF EXISTS "FK_vendor_users_userId"`);
68
+ await queryRunner.query(`ALTER TABLE "vendor_users" DROP CONSTRAINT IF EXISTS "FK_vendor_users_vendorId"`);
69
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_vendor_users_user"`);
70
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_vendor_users_vendor"`);
71
+ await queryRunner.query(`DROP TABLE IF EXISTS "vendor_users"`);
72
+ await queryRunner.query(`DROP TABLE IF EXISTS "vendors"`);
73
+ }
74
+ }
@@ -0,0 +1,124 @@
1
+ import type { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ const STORE_TABLES = [
4
+ 'product_categories',
5
+ 'brands',
6
+ 'attributes',
7
+ 'taxes',
8
+ 'collections',
9
+ 'products',
10
+ 'orders',
11
+ 'payments',
12
+ ] as const;
13
+
14
+ /** Global slug/orderNumber uniques replaced by (vendorId, …) composites. */
15
+ const SLUG_UNIQUE_DROPS: Array<{ table: string; constraint: string; composite: string; columns: string }> = [
16
+ { table: 'product_categories', constraint: 'UQ_product_categories_slug', composite: 'UQ_product_categories_vendor_slug', columns: '"vendorId", "slug"' },
17
+ { table: 'brands', constraint: 'UQ_brands_slug', composite: 'UQ_brands_vendor_slug', columns: '"vendorId", "slug"' },
18
+ { table: 'attributes', constraint: 'UQ_attributes_slug', composite: 'UQ_attributes_vendor_slug', columns: '"vendorId", "slug"' },
19
+ { table: 'taxes', constraint: 'UQ_taxes_slug', composite: 'UQ_taxes_vendor_slug', columns: '"vendorId", "slug"' },
20
+ { table: 'collections', constraint: 'UQ_collections_slug', composite: 'UQ_collections_vendor_slug', columns: '"vendorId", "slug"' },
21
+ { table: 'products', constraint: 'UQ_products_slug', composite: 'UQ_products_vendor_slug', columns: '"vendorId", "slug"' },
22
+ ];
23
+
24
+ export class StoreVendorIdColumns1775900000000 implements MigrationInterface {
25
+ name = 'StoreVendorIdColumns1775900000000';
26
+
27
+ public async up(queryRunner: QueryRunner): Promise<void> {
28
+ await queryRunner.query(`
29
+ INSERT INTO "vendors" ("name", "slug", "active")
30
+ VALUES ('Default Store', 'default', true)
31
+ ON CONFLICT ("slug") DO NOTHING
32
+ `);
33
+
34
+ for (const table of STORE_TABLES) {
35
+ await queryRunner.query(`ALTER TABLE "${table}" ADD COLUMN IF NOT EXISTS "vendorId" integer`);
36
+ }
37
+
38
+ await queryRunner.query(`
39
+ UPDATE "product_categories" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
40
+ WHERE "vendorId" IS NULL
41
+ `);
42
+ await queryRunner.query(`
43
+ UPDATE "brands" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
44
+ WHERE "vendorId" IS NULL
45
+ `);
46
+ await queryRunner.query(`
47
+ UPDATE "attributes" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
48
+ WHERE "vendorId" IS NULL
49
+ `);
50
+ await queryRunner.query(`
51
+ UPDATE "taxes" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
52
+ WHERE "vendorId" IS NULL
53
+ `);
54
+ await queryRunner.query(`
55
+ UPDATE "collections" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
56
+ WHERE "vendorId" IS NULL
57
+ `);
58
+ await queryRunner.query(`
59
+ UPDATE "products" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
60
+ WHERE "vendorId" IS NULL
61
+ `);
62
+ await queryRunner.query(`
63
+ UPDATE "orders" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
64
+ WHERE "vendorId" IS NULL
65
+ `);
66
+ await queryRunner.query(`
67
+ UPDATE "payments" p SET "vendorId" = o."vendorId"
68
+ FROM "orders" o
69
+ WHERE p."orderId" = o.id AND p."vendorId" IS NULL
70
+ `);
71
+ await queryRunner.query(`
72
+ UPDATE "payments" SET "vendorId" = (SELECT id FROM "vendors" WHERE "slug" = 'default' LIMIT 1)
73
+ WHERE "vendorId" IS NULL
74
+ `);
75
+
76
+ for (const table of STORE_TABLES) {
77
+ await queryRunner.query(`ALTER TABLE "${table}" ALTER COLUMN "vendorId" SET NOT NULL`);
78
+ }
79
+
80
+ for (const table of STORE_TABLES) {
81
+ await queryRunner.query(`
82
+ DO $$ BEGIN
83
+ ALTER TABLE "${table}" ADD CONSTRAINT "FK_${table}_vendorId"
84
+ FOREIGN KEY ("vendorId") REFERENCES "vendors"("id") ON DELETE RESTRICT ON UPDATE NO ACTION;
85
+ EXCEPTION WHEN duplicate_object THEN NULL;
86
+ END $$
87
+ `);
88
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_${table}_vendorId" ON "${table}" ("vendorId")`);
89
+ }
90
+
91
+ for (const { table, constraint, composite, columns } of SLUG_UNIQUE_DROPS) {
92
+ await queryRunner.query(`ALTER TABLE "${table}" DROP CONSTRAINT IF EXISTS "${constraint}"`);
93
+ await queryRunner.query(`
94
+ CREATE UNIQUE INDEX IF NOT EXISTS "${composite}" ON "${table}" (${columns})
95
+ `);
96
+ }
97
+
98
+ await queryRunner.query(`ALTER TABLE "orders" DROP CONSTRAINT IF EXISTS "UQ_orders_orderNumber"`);
99
+ await queryRunner.query(`
100
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_orders_vendor_orderNumber"
101
+ ON "orders" ("vendorId", "orderNumber")
102
+ `);
103
+ }
104
+
105
+ public async down(queryRunner: QueryRunner): Promise<void> {
106
+ await queryRunner.query(`DROP INDEX IF EXISTS "UQ_orders_vendor_orderNumber"`);
107
+ await queryRunner.query(`
108
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_orders_orderNumber" ON "orders" ("orderNumber")
109
+ `);
110
+
111
+ for (const { table, constraint, composite } of [...SLUG_UNIQUE_DROPS].reverse()) {
112
+ await queryRunner.query(`DROP INDEX IF EXISTS "${composite}"`);
113
+ await queryRunner.query(`
114
+ CREATE UNIQUE INDEX IF NOT EXISTS "${constraint}" ON "${table}" ("slug")
115
+ `);
116
+ }
117
+
118
+ for (const table of [...STORE_TABLES].reverse()) {
119
+ await queryRunner.query(`ALTER TABLE "${table}" DROP CONSTRAINT IF EXISTS "FK_${table}_vendorId"`);
120
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_${table}_vendorId"`);
121
+ await queryRunner.query(`ALTER TABLE "${table}" DROP COLUMN IF EXISTS "vendorId"`);
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,54 @@
1
+ import type { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ const VENDOR_OWNER = 'Vendor Owner';
4
+ const STORE_ENTITIES = [
5
+ 'products',
6
+ 'collections',
7
+ 'brands',
8
+ 'product_categories',
9
+ 'orders',
10
+ 'payments',
11
+ 'taxes',
12
+ 'attributes',
13
+ ];
14
+
15
+ export class VendorOwnerRbacSeed1775910000000 implements MigrationInterface {
16
+ name = 'VendorOwnerRbacSeed1775910000000';
17
+
18
+ public async up(queryRunner: QueryRunner): Promise<void> {
19
+ await queryRunner.query(`
20
+ INSERT INTO "user_groups" ("name") VALUES ('${VENDOR_OWNER}')
21
+ ON CONFLICT ("name") DO NOTHING
22
+ `);
23
+
24
+ const groupRows = (await queryRunner.query(
25
+ `SELECT id FROM "user_groups" WHERE "name" = '${VENDOR_OWNER}' AND "deleted" = false LIMIT 1`
26
+ )) as { id: number }[];
27
+ const groupId = groupRows[0]?.id;
28
+ if (!groupId) return;
29
+
30
+ for (const entity of STORE_ENTITIES) {
31
+ await queryRunner.query(`
32
+ INSERT INTO "permissions" ("groupId", "entity", "canCreate", "canRead", "canUpdate", "canDelete")
33
+ VALUES (${groupId}, '${entity}', true, true, true, true)
34
+ ON CONFLICT ("groupId", "entity") DO UPDATE SET
35
+ "canCreate" = true, "canRead" = true, "canUpdate" = true, "canDelete" = true
36
+ `);
37
+ }
38
+
39
+ await queryRunner.query(`
40
+ INSERT INTO "permissions" ("groupId", "entity", "canCreate", "canRead", "canUpdate", "canDelete")
41
+ VALUES (${groupId}, 'dashboard', false, true, false, false)
42
+ ON CONFLICT ("groupId", "entity") DO UPDATE SET "canRead" = true
43
+ `);
44
+ }
45
+
46
+ public async down(queryRunner: QueryRunner): Promise<void> {
47
+ await queryRunner.query(`
48
+ DELETE FROM "permissions" p
49
+ USING "user_groups" g
50
+ WHERE p."groupId" = g.id AND g.name = '${VENDOR_OWNER}'
51
+ `);
52
+ await queryRunner.query(`DELETE FROM "user_groups" WHERE "name" = '${VENDOR_OWNER}'`);
53
+ }
54
+ }
@@ -0,0 +1,25 @@
1
+ import type { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ /** Vendor Owner group: read/update store settings in admin. */
4
+ export class VendorOwnerSettingsPermission1775920000000 implements MigrationInterface {
5
+ name = 'VendorOwnerSettingsPermission1775920000000';
6
+
7
+ public async up(queryRunner: QueryRunner): Promise<void> {
8
+ await queryRunner.query(`
9
+ INSERT INTO "permissions" ("groupId", "entity", "canCreate", "canRead", "canUpdate", "canDelete")
10
+ SELECT g.id, 'settings', false, true, true, false
11
+ FROM "user_groups" g
12
+ WHERE g.name = 'Vendor Owner' AND g.deleted = false
13
+ ON CONFLICT ("groupId", "entity") DO UPDATE SET
14
+ "canRead" = true, "canUpdate" = true
15
+ `);
16
+ }
17
+
18
+ public async down(queryRunner: QueryRunner): Promise<void> {
19
+ await queryRunner.query(`
20
+ DELETE FROM "permissions" p
21
+ USING "user_groups" g
22
+ WHERE p."groupId" = g.id AND g.name = 'Vendor Owner' AND p.entity = 'settings'
23
+ `);
24
+ }
25
+ }
@@ -0,0 +1,54 @@
1
+ import type { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ const STORE_ENTITIES = [
4
+ 'products',
5
+ 'collections',
6
+ 'brands',
7
+ 'product_categories',
8
+ 'orders',
9
+ 'payments',
10
+ 'taxes',
11
+ 'attributes',
12
+ ];
13
+
14
+ /** Ensure vendor-named groups (e.g. vendor-admin) have store CRUD like Vendor Owner. */
15
+ export class VendorGroupStorePermissions1775930000000 implements MigrationInterface {
16
+ name = 'VendorGroupStorePermissions1775930000000';
17
+
18
+ public async up(queryRunner: QueryRunner): Promise<void> {
19
+ for (const entity of STORE_ENTITIES) {
20
+ await queryRunner.query(`
21
+ INSERT INTO "permissions" ("groupId", "entity", "canCreate", "canRead", "canUpdate", "canDelete")
22
+ SELECT g.id, '${entity}', true, true, true, true
23
+ FROM "user_groups" g
24
+ WHERE g.deleted = false
25
+ AND lower(g.name) LIKE '%vendor%'
26
+ AND g.name <> 'Administrator'
27
+ ON CONFLICT ("groupId", "entity") DO NOTHING
28
+ `);
29
+ }
30
+
31
+ await queryRunner.query(`
32
+ INSERT INTO "permissions" ("groupId", "entity", "canCreate", "canRead", "canUpdate", "canDelete")
33
+ SELECT g.id, 'dashboard', false, true, false, false
34
+ FROM "user_groups" g
35
+ WHERE g.deleted = false
36
+ AND lower(g.name) LIKE '%vendor%'
37
+ AND g.name <> 'Administrator'
38
+ ON CONFLICT ("groupId", "entity") DO NOTHING
39
+ `);
40
+ }
41
+
42
+ public async down(queryRunner: QueryRunner): Promise<void> {
43
+ const entities = [...STORE_ENTITIES, 'dashboard'].map((e) => `'${e}'`).join(', ');
44
+ await queryRunner.query(`
45
+ DELETE FROM "permissions" p
46
+ USING "user_groups" g
47
+ WHERE p."groupId" = g.id
48
+ AND g.deleted = false
49
+ AND lower(g.name) LIKE '%vendor%'
50
+ AND g.name <> 'Administrator'
51
+ AND p.entity IN (${entities})
52
+ `);
53
+ }
54
+ }
@@ -0,0 +1,71 @@
1
+ import type { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ /**
4
+ * Enables PostgreSQL Row Level Security on all vendor-scoped store tables.
5
+ *
6
+ * Policy design
7
+ * ─────────────
8
+ * Before every vendor-scoped query the application sets a session-local GUC:
9
+ *
10
+ * SET LOCAL app.vendor_id = '4'; -- restrict to vendor 4
11
+ * SET LOCAL app.vendor_id = '0'; -- admin / all-access mode
12
+ *
13
+ * The policy allows a row when either:
14
+ * a) the GUC is absent / empty / '0' → admin / no restriction
15
+ * b) the row's vendorId matches the GUC value
16
+ *
17
+ * FORCE ROW LEVEL SECURITY makes the policy apply even to the table owner
18
+ * (it does NOT apply to PostgreSQL superusers; production should use a
19
+ * non-superuser app role so RLS is fully enforced).
20
+ *
21
+ * Use `withVendorRlsContext(dataSource, vendorId, fn)` from
22
+ * `@infuro/cms-core/lib` to set the GUC inside a transaction.
23
+ */
24
+
25
+ const VENDOR_TABLES = [
26
+ 'products',
27
+ 'collections',
28
+ 'brands',
29
+ 'product_categories',
30
+ 'orders',
31
+ 'payments',
32
+ 'taxes',
33
+ 'attributes',
34
+ ] as const;
35
+
36
+ export class VendorRls1775940000000 implements MigrationInterface {
37
+ name = 'VendorRls1775940000000';
38
+
39
+ public async up(queryRunner: QueryRunner): Promise<void> {
40
+ for (const table of VENDOR_TABLES) {
41
+ // Enable RLS — FORCE applies even to the table owner role.
42
+ await queryRunner.query(`ALTER TABLE "${table}" ENABLE ROW LEVEL SECURITY`);
43
+ await queryRunner.query(`ALTER TABLE "${table}" FORCE ROW LEVEL SECURITY`);
44
+
45
+ // Drop previous policy if migration is re-run.
46
+ await queryRunner.query(`DROP POLICY IF EXISTS "vendor_isolation" ON "${table}"`);
47
+
48
+ // Permissive SELECT/INSERT/UPDATE/DELETE policy.
49
+ // Allow when: no vendor context set (admin mode) OR row belongs to the current vendor.
50
+ await queryRunner.query(`
51
+ CREATE POLICY "vendor_isolation" ON "${table}"
52
+ USING (
53
+ COALESCE(NULLIF(current_setting('app.vendor_id', true), ''), '0') = '0'
54
+ OR "vendorId" = NULLIF(current_setting('app.vendor_id', true), '')::integer
55
+ )
56
+ WITH CHECK (
57
+ COALESCE(NULLIF(current_setting('app.vendor_id', true), ''), '0') = '0'
58
+ OR "vendorId" = NULLIF(current_setting('app.vendor_id', true), '')::integer
59
+ )
60
+ `);
61
+ }
62
+ }
63
+
64
+ public async down(queryRunner: QueryRunner): Promise<void> {
65
+ for (const table of [...VENDOR_TABLES].reverse()) {
66
+ await queryRunner.query(`DROP POLICY IF EXISTS "vendor_isolation" ON "${table}"`);
67
+ await queryRunner.query(`ALTER TABLE "${table}" NO FORCE ROW LEVEL SECURITY`);
68
+ await queryRunner.query(`ALTER TABLE "${table}" DISABLE ROW LEVEL SECURITY`);
69
+ }
70
+ }
71
+ }