@infuro/cms-core 1.0.30 → 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.
- package/README.md +17 -13
- package/dist/admin.cjs +12886 -8223
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +13 -2
- package/dist/admin.d.ts +13 -2
- package/dist/admin.js +12668 -8004
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +4873 -1096
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +2 -2
- package/dist/api.d.ts +2 -2
- package/dist/api.js +4621 -838
- package/dist/api.js.map +1 -1
- package/dist/auth.cjs +461 -62
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +73 -47
- package/dist/auth.d.ts +73 -47
- package/dist/auth.js +425 -38
- package/dist/auth.js.map +1 -1
- package/dist/cli.cjs +16 -13
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +16 -13
- package/dist/cli.js.map +1 -1
- package/dist/{index-D9SdaDJ0.d.ts → index-BcMRGNjS.d.cts} +36 -3
- package/dist/{index-BPQSXgXF.d.cts → index-Bda8D1Rm.d.ts} +36 -3
- package/dist/index.cjs +4622 -765
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +300 -36
- package/dist/index.d.ts +300 -36
- package/dist/index.js +4549 -723
- package/dist/index.js.map +1 -1
- package/dist/migrations/1775400000001-AddDiscountAndOrderAddresses.ts +274 -0
- package/dist/migrations/1775800000000-VendorsAndVendorUsers.ts +74 -0
- package/dist/migrations/1775900000000-StoreVendorIdColumns.ts +124 -0
- package/dist/migrations/1775910000000-VendorOwnerRbacSeed.ts +54 -0
- package/dist/migrations/1775920000000-VendorOwnerSettingsPermission.ts +25 -0
- package/dist/migrations/1775930000000-VendorGroupStorePermissions.ts +54 -0
- package/dist/migrations/1775940000000-VendorRls.ts +71 -0
- package/dist/migrations/1775950000000-VendorCustomers.ts +87 -0
- package/dist/migrations/1775950000001-VendorCustomersPermissions.ts +67 -0
- package/dist/migrations/1778652250860-CreateCurrencyTables.ts +16 -0
- package/dist/migrations/1778652250861-OrderAddressTables.ts +88 -0
- package/dist/migrations/1778652250862-UpdateDiscountandRules.ts +100 -0
- package/dist/migrations/1778652250863-MakeCouponCodeNullable.ts +19 -0
- package/dist/migrations/1778652250864-RemoveUniqueConstraintFromDiscountCouponCode.ts +22 -0
- package/dist/migrations/1778652250865-FixOrdersAddressForeignKeys.ts +71 -0
- package/dist/migrations/1778660000000-DiscountVendorId.ts +90 -0
- package/dist/migrations/1778660000001-FixOrdersAddressFkToOrderAddresses.ts +79 -0
- package/dist/migrations/1778660000002-VendorUserIdInviteStatus.ts +61 -0
- package/dist/theme.d.cts +2 -2
- package/dist/theme.d.ts +2 -2
- package/dist/{types-D34wmivy.d.cts → types-DCVaXlPV.d.cts} +1 -1
- package/dist/{types-D34wmivy.d.ts → types-DCVaXlPV.d.ts} +1 -1
- package/dist/vendor-scope-DTYhJk_I.d.cts +129 -0
- package/dist/vendor-scope-DTYhJk_I.d.ts +129 -0
- package/package.json +2 -2
- package/dist/helpers-dlrF_49e.d.cts +0 -60
- 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
|
+
}
|