@entity-access/entity-access 1.0.2 → 1.0.5

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 (62) hide show
  1. package/.github/workflows/node.yml +7 -2
  2. package/.vscode/settings.json +1 -0
  3. package/README.md +57 -27
  4. package/package.json +2 -2
  5. package/src/common/EntityAccessError.ts +10 -0
  6. package/src/common/IDisposable.ts +25 -0
  7. package/src/common/ImmutableObject.ts +53 -0
  8. package/src/common/Logger.ts +59 -0
  9. package/src/common/TypeInfo.ts +3 -0
  10. package/src/common/usingAsync.ts +42 -12
  11. package/src/compiler/QueryCompiler.ts +28 -30
  12. package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +23 -0
  13. package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +23 -0
  14. package/src/decorators/ForeignKey.ts +1 -1
  15. package/src/decorators/IClassOf.ts +2 -1
  16. package/src/decorators/parser/NameParser.ts +15 -0
  17. package/src/di/di.ts +224 -0
  18. package/src/drivers/base/BaseDriver.ts +28 -3
  19. package/src/drivers/sql-server/ExpressionToSqlServer.ts +48 -9
  20. package/src/drivers/sql-server/SqlServerDriver.ts +7 -16
  21. package/src/entity-query/EntityType.ts +1 -1
  22. package/src/model/EntityContext.ts +167 -22
  23. package/src/model/EntityQuery.ts +76 -60
  24. package/src/model/EntitySource.ts +39 -33
  25. package/src/model/IFilterWithParameter.ts +3 -0
  26. package/src/model/SourceExpression.ts +21 -25
  27. package/src/model/{ChangeEntry.ts → changes/ChangeEntry.ts} +33 -3
  28. package/src/model/{ChangeSet.ts → changes/ChangeSet.ts} +12 -11
  29. package/src/model/events/ContextEvents.ts +26 -0
  30. package/src/model/events/EntityEvents.ts +92 -0
  31. package/src/model/{IdentityService.ts → identity/IdentityService.ts} +1 -1
  32. package/src/model/symbols.ts +1 -0
  33. package/src/model/verification/VerificationSession.ts +173 -0
  34. package/src/query/ast/DebugStringVisitor.ts +128 -0
  35. package/src/query/ast/ExpressionToSql.ts +289 -119
  36. package/src/query/ast/Expressions.ts +111 -12
  37. package/src/query/ast/IStringTransformer.ts +16 -4
  38. package/src/query/ast/ReplaceParameter.ts +40 -0
  39. package/src/query/ast/Visitor.ts +20 -5
  40. package/src/query/parser/ArrowToExpression.ts +116 -16
  41. package/src/query/parser/BabelVisitor.ts +27 -44
  42. package/src/query/parser/NotSupportedError.ts +5 -0
  43. package/src/query/parser/Restructure.ts +66 -0
  44. package/src/query/parser/TransformVisitor.ts +83 -0
  45. package/src/sql/ISql.ts +10 -0
  46. package/src/tests/db-tests/tests/select-items.ts +12 -0
  47. package/src/tests/expressions/left-joins/child-joins.ts +19 -26
  48. package/src/tests/expressions/sanitize/sanitize-test.ts +17 -0
  49. package/src/tests/expressions/select/select.ts +24 -0
  50. package/src/tests/expressions/simple/parse-arrow.ts +10 -0
  51. package/src/tests/model/ShoppingContext.ts +7 -3
  52. package/src/tests/model/createContext.ts +45 -13
  53. package/src/tests/security/ShoppingContextEvents.ts +20 -0
  54. package/src/tests/security/events/OrderEvents.ts +66 -0
  55. package/src/tests/security/events/ProductEvents.ts +92 -0
  56. package/src/tests/security/events/UserEvents.ts +18 -0
  57. package/src/tests/security/events/UserInfo.ts +7 -0
  58. package/src/tests/security/tests/place-order.ts +71 -0
  59. package/test.js +11 -4
  60. package/tsconfig.json +2 -0
  61. package/src/decorators/parser/MemberParser.ts +0 -8
  62. package/src/model/EntitySchema.ts +0 -21
@@ -0,0 +1,66 @@
1
+ import * as bpe from "@babel/types";
2
+ import { BabelVisitor } from "./BabelVisitor.js";
3
+ import { NotSupportedError } from "./NotSupportedError.js";
4
+ import TransformVisitor from "./TransformVisitor.js";
5
+
6
+ export default class Restructure extends TransformVisitor {
7
+
8
+ private map: Map<string, bpe.Node> = new Map();
9
+
10
+ visitTemplateElement(node: bpe.TemplateElement): bpe.Node {
11
+ return node;
12
+ }
13
+
14
+ visitArrowFunctionExpression(node: bpe.ArrowFunctionExpression): bpe.Node {
15
+
16
+ // we need to restructure identifiers from destructure
17
+
18
+ node.params = node.params.map((x) => this.toIdentifier(x));
19
+
20
+ node.body = this.visit(node.body) as bpe.Expression;
21
+
22
+ return node;
23
+ }
24
+
25
+ visitIdentifier(node: bpe.Identifier): bpe.Node {
26
+ return this.map.get(node.name) ?? node;
27
+ }
28
+
29
+ toIdentifier(x: bpe.Identifier | bpe.Pattern | bpe.RestElement): bpe.Identifier {
30
+ switch(x.type) {
31
+ case "Identifier":
32
+ return x;
33
+ case "ObjectPattern":
34
+ const id = `x${this.map.size + 1}`;
35
+ const idExp = bpe.identifier(id);
36
+ this.convertPattern(idExp, x);
37
+ return idExp;
38
+ case "ArrayPattern":
39
+ case "AssignmentPattern":
40
+ case "RestElement":
41
+ throw new NotSupportedError();
42
+ }
43
+ }
44
+
45
+ convertPattern(parentExp: bpe.Expression, x: bpe.ObjectPattern) {
46
+ for (const iterator of x.properties) {
47
+ switch(iterator.type) {
48
+ case "RestElement":
49
+ throw new NotSupportedError();
50
+ case "ObjectProperty":
51
+ switch(iterator.key.type){
52
+ case "Identifier":
53
+ const childExp = bpe.memberExpression(parentExp, iterator.key);
54
+ this.map.set(iterator.key.name, childExp);
55
+ switch(iterator.value.type) {
56
+ case "ObjectPattern":
57
+ this.convertPattern(childExp, iterator.value);
58
+ break;
59
+ }
60
+ break;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ }
@@ -0,0 +1,83 @@
1
+ import * as bpe from "@babel/types";
2
+ import { BabelVisitor } from "./BabelVisitor.js";
3
+
4
+ export default class TransformVisitor extends BabelVisitor<bpe.Node> {
5
+
6
+ transform<T>(n:T) {
7
+ if (!n) {
8
+ return n;
9
+ }
10
+ if (Array.isArray(n)) {
11
+ return n.map((x) => this.transform(x));
12
+ }
13
+ return this.visit(n as any);
14
+ }
15
+
16
+ visitTemplateElement(node: bpe.TemplateElement): bpe.Node {
17
+ return node;
18
+ }
19
+
20
+ visitObjectExpression(node: bpe.ObjectExpression): bpe.Node {
21
+ return bpe.objectExpression(
22
+ this.transform(node.properties)
23
+ );
24
+ }
25
+ visitMemberExpression(node: bpe.MemberExpression): bpe.Node {
26
+ return bpe.memberExpression(this.transform(node.object), this.transform(node.property), node.computed, node.optional);
27
+ }
28
+ visitLogicalExpression(node: bpe.LogicalExpression): bpe.Node {
29
+ return bpe.logicalExpression(node.operator, this.transform(node.left), this.transform(node.right));
30
+ }
31
+ visitIdentifier(node: bpe.Identifier): bpe.Node {
32
+ return node;
33
+ }
34
+ visitTemplateLiteral(node: bpe.TemplateLiteral): bpe.Node {
35
+ return bpe.templateLiteral(this.transform(node.quasis), this.transform(node.expressions));
36
+ }
37
+ visitNumericLiteral(node: bpe.NumericLiteral): bpe.Node {
38
+ return node;
39
+ }
40
+ visitDecimalLiteral(node: bpe.DecimalLiteral): bpe.Node {
41
+ return node;
42
+ }
43
+ visitBooleanLiteral(node: bpe.BooleanLiteral): bpe.Node {
44
+ return node;
45
+ }
46
+ visitBigIntLiteral(node: bpe.BigIntLiteral): bpe.Node {
47
+ return node;
48
+ }
49
+ visitStringLiteral(node: bpe.StringLiteral): bpe.Node {
50
+ return node;
51
+ }
52
+ visitNullLiteral(node: bpe.NullLiteral): bpe.Node {
53
+ return node;
54
+ }
55
+ visitConditionalExpression(node: bpe.ConditionalExpression): bpe.Node {
56
+ return bpe.conditionalExpression(
57
+ this.transform(node.test),
58
+ this.transform(node.consequent),
59
+ this.transform(node.alternate));
60
+ }
61
+ visitArrowFunctionExpression(node: bpe.ArrowFunctionExpression): bpe.Node {
62
+ return bpe.arrowFunctionExpression(this.transform(node.params), this.transform(node.body), node.async);
63
+ }
64
+ visitCallExpression(node: bpe.CallExpression): bpe.Node {
65
+ return bpe.callExpression(this.transform(node.callee), this.transform(node.arguments));
66
+ }
67
+ visitBinaryExpression(node: bpe.BinaryExpression): bpe.Node {
68
+ return bpe.binaryExpression(node.operator, this.transform(node.left), this.transform(node.right));
69
+ }
70
+
71
+ visitObjectProperty(node: bpe.ObjectProperty): bpe.Node {
72
+
73
+ let key = node.key;
74
+ if (node.key.type !== "Identifier") {
75
+ key = this.transform(key);
76
+ }
77
+
78
+ const value = this.transform(node.value);
79
+
80
+ return bpe.objectProperty(key, value, node.computed, node.shorthand, node.decorators);
81
+ }
82
+
83
+ }
package/src/sql/ISql.ts CHANGED
@@ -2,6 +2,16 @@ export interface ISql {
2
2
 
3
3
  in<T>(a: T, array: T[]): boolean;
4
4
 
5
+ cast: {
6
+ asNumber(a: any): number;
7
+ asInteger(a: any): number;
8
+ asBigInt(a: any): number;
9
+ asText(a: any): string;
10
+ asDate(a: any): Date;
11
+ asDateTime(a: any): Date;
12
+ asDecimal(a: any): number;
13
+ }
14
+
5
15
  text: {
6
16
  concat(... fragments: string[]): string;
7
17
  like(text: string, test: string): boolean;
@@ -34,4 +34,16 @@ export default async function(this: TestConfig) {
34
34
  allHeadphones = await context.products.where({ headPhoneCategory }, (p) => (x) => x.categories.some((pc) => pc.categoryID === p.headPhoneCategory)).count();
35
35
 
36
36
  assert.equal(6, allHeadphones);
37
+
38
+ const first = await context.productCategories.where({ headPhoneCategory }, (p) => (x) => x.category.categoryID === p.headPhoneCategory).first();
39
+
40
+ // delete first one...
41
+ context.products.delete(first);
42
+
43
+ await context.saveChanges();
44
+
45
+ allHeadphones = await context.products.where({ headPhoneCategory }, (p) => (x) => x.categories.some((pc) => pc.categoryID === p.headPhoneCategory)).count();
46
+
47
+ assert.equal(5, allHeadphones);
48
+
37
49
  }
@@ -5,45 +5,38 @@ import { assertSqlMatch, trimInternal } from "../trimInternal.js";
5
5
  import PostgreSqlDriver from "../../../drivers/postgres/PostgreSqlDriver.js";
6
6
 
7
7
  const sql1 = `SELECT
8
- "P1"."productID",
9
- "P1"."name",
10
- "P1"."ownerID"
8
+ "P1"."productID","P1"."name","P1"."ownerID","P1"."status"
11
9
  FROM "Products" AS "P1"
12
- WHERE
13
- EXISTS (SELECT
14
- $1 AS "o1"
15
- FROM "OrderItems" AS "O0"
16
- WHERE
17
- ("P1"."productID" = "O0"."productID")
18
- AND ("O0"."productID" = $2)
19
- )
20
- `;
10
+ WHERE EXISTS (SELECT
11
+ 1
12
+ FROM "OrderItems" AS "O0"
13
+ WHERE ("P1"."productID" = "O0"."productID") AND ("O0"."productID" = $1))`;
21
14
 
22
15
  const sql2 = `SELECT
23
- "P1"."productID","P1"."name","P1"."ownerID"
16
+ "P1"."productID","P1"."name","P1"."ownerID","P1"."status"
24
17
  FROM "Products" AS "P1"
25
18
  WHERE EXISTS (SELECT
26
- $1 AS "o1"
27
- FROM "OrderItems" AS "O0"
28
- WHERE ("P1"."productID" = "O0"."productID") AND ("O0"."productID" = $2)) AND EXISTS (SELECT
29
- $3 AS "o1"
19
+ 1
30
20
  FROM "OrderItems" AS "O1"
31
- WHERE ("P1"."productID" = "O1"."productID") AND ("O1"."amount" > $4))`;
21
+ WHERE ("P1"."productID" = "O1"."productID") AND ("O1"."productID" = $1)) AND EXISTS (SELECT
22
+ 1
23
+ FROM "OrderItems" AS "O2"
24
+ WHERE ("P1"."productID" = "O2"."productID") AND ("O2"."amount" > $2))`;
32
25
 
33
26
  const sql3 = `SELECT
34
- "P1"."productID","P1"."name","P1"."ownerID"
27
+ "P1"."productID","P1"."name","P1"."ownerID","P1"."status"
35
28
  FROM "Products" AS "P1"
36
29
  WHERE EXISTS (SELECT
37
- $1 AS "o1"
38
- FROM "OrderItems" AS "O0"
39
- WHERE ("P1"."productID" = "O0"."productID") AND ("O0"."productID" = $2)) AND EXISTS (SELECT
40
- $3 AS "o1"
30
+ 1
41
31
  FROM "OrderItems" AS "O1"
42
- INNER JOIN "Orders" AS "O2" ON "O1"."orderID" = "O2"."orderID"
43
- WHERE ("P1"."productID" = "O1"."productID") AND ("O2"."orderDate" > $4))`;
32
+ WHERE ("P1"."productID" = "O1"."productID") AND ("O1"."productID" = $1)) AND EXISTS (SELECT
33
+ 1
34
+ FROM "OrderItems" AS "O2"
35
+ INNER JOIN "Orders" AS "O0" ON "O2"."orderID" = "O0"."orderID"
36
+ WHERE ("P1"."productID" = "O2"."productID") AND ("O0"."orderDate" > $2))`;
44
37
 
45
38
  const productJoin = `SELECT
46
- "P1"."productID","P1"."name","P1"."ownerID"
39
+ "P1"."productID","P1"."name","P1"."ownerID","P1"."status"
47
40
  FROM "Products" AS "P1"
48
41
  LEFT JOIN "Users" AS "U0" ON "P1"."ownerID" = "U0"."userID"
49
42
  WHERE "U0"."dateCreated" > $1`;
@@ -0,0 +1,17 @@
1
+ import assert from "assert";
2
+ import QueryCompiler from "../../../compiler/QueryCompiler.js";
3
+
4
+ declare let pg_kill: any;
5
+
6
+ export default function () {
7
+
8
+ const compiler = QueryCompiler.instance;
9
+
10
+ const name = "Akash";
11
+
12
+ assert.throws(()=>
13
+ compiler.execute({ name }, (p) => (x) => pg_kill(9))
14
+ );
15
+
16
+
17
+ }
@@ -0,0 +1,24 @@
1
+ import assert from "assert";
2
+ import QueryCompiler from "../../../compiler/QueryCompiler.js";
3
+ import ArrowToExpression from "../../../query/parser/ArrowToExpression.js";
4
+ import { ExpressionAs, Identifier, MemberExpression, NewObjectExpression, QuotedLiteral } from "../../../query/ast/Expressions.js";
5
+ import ExpressionToSql from "../../../query/ast/ExpressionToSql.js";
6
+
7
+ type ICustomer = { firstName: string; lastName: string; emailAddress: string; birthDate: Date };
8
+
9
+ export default function() {
10
+
11
+
12
+ const compiler = QueryCompiler.instance;
13
+
14
+ const name = "Akash";
15
+
16
+ let r = compiler.execute({ name }, (p) => ({ firstName, lastName, emailAddress }: ICustomer) => ({ emailAddress, name: `${firstName} ${lastName}` }));
17
+
18
+ assert.strictEqual(`FROM ("x1"."emailAddress" AS "emailAddress",CONCAT("x1"."firstName",$1,"x1"."lastName") AS "name")`, r.text);
19
+
20
+ r = compiler.execute({ name }, (p) => ({ id }) => ({ error: `${id > 0 ? "Error" : ""}` }));
21
+
22
+ assert.strictEqual(`FROM (CONCAT((CASE WHEN "x1"."id" > $1 THEN $2 ELSE $3 END)) AS "error")`, r.text);
23
+
24
+ }
@@ -43,4 +43,14 @@ export default function () {
43
43
  assert.equal(`starts_with("x"."firstName", $1)`, r.text);
44
44
  assert.equal("Akash", r.values[0]);
45
45
 
46
+ const code = "1235";
47
+ const key = 13434;
48
+ r = compiler.execute({name, code, key},
49
+ (p) => (x: KeyCode) =>
50
+ x.code === Sql.cast.asNumber(p.code) && x.key === Sql.cast.asText(p.key) );
51
+
52
+ assert.equal(`("x"."code" = ($1 ::double)) AND ("x"."key" = ($2 ::text))`, r.text);
53
+
46
54
  }
55
+
56
+ type KeyCode = { name: string, code: number, key: string };
@@ -2,9 +2,8 @@ import EntityContext from "../../model/EntityContext.js";
2
2
  import Column from "../../decorators/Column.js";
3
3
  import ForeignKey from "../../decorators/ForeignKey.js";
4
4
  import Table from "../../decorators/Table.js";
5
- import PostgreSqlDriver from "../../drivers/postgres/PostgreSqlDriver.js";
6
- import { BaseDriver } from "../../drivers/base/BaseDriver.js";
7
- import { Console } from "console";
5
+
6
+ export const statusPublished = "published";
8
7
 
9
8
  export class ShoppingContext extends EntityContext {
10
9
 
@@ -65,6 +64,9 @@ export class Product {
65
64
  @Column({ nullable: true })
66
65
  public ownerID: number;
67
66
 
67
+ @Column({ dataType: "Char", length: 20})
68
+ public status: string;
69
+
68
70
  public orderItems: OrderItem[];
69
71
 
70
72
  public prices: ProductPrice[];
@@ -123,8 +125,10 @@ export class ProductPrice {
123
125
  @Column({ nullable: true})
124
126
  public endDate?: Date;
125
127
 
128
+ @Column({ })
126
129
  public amount: number;
127
130
 
131
+ @Column({})
128
132
  public productID: number;
129
133
 
130
134
  @ForeignKey({
@@ -1,13 +1,13 @@
1
1
  import { IClassOf } from "../../decorators/IClassOf.js";
2
2
  import { BaseDriver } from "../../drivers/base/BaseDriver.js";
3
- import { ShoppingContext } from "./ShoppingContext.js";
3
+ import { ShoppingContext, statusPublished } from "./ShoppingContext.js";
4
+
5
+ const status = statusPublished;
4
6
 
5
7
  export async function createContext(driver: BaseDriver) {
6
8
 
7
- const rn = "d" + Date.now();
8
9
  const copy = { ... driver } as BaseDriver;
9
10
  (copy as any).connectionString = { ... driver.connectionString };
10
- copy.connectionString.database = rn;
11
11
  Object.setPrototypeOf(copy, Object.getPrototypeOf(driver));
12
12
  const context = new ShoppingContext(copy);
13
13
 
@@ -30,9 +30,30 @@ async function seed(context: ShoppingContext) {
30
30
 
31
31
  addHeadPhones(context, now);
32
32
 
33
- const maleClothes = addMaleClothes(context);
33
+ const clothes = addMaleClothes(context);
34
+
35
+ addFemaleClothes(context);
36
+
37
+ await context.saveChanges();
34
38
 
35
- const femaleClothes = addFemaleClothes(context);
39
+ const product = clothes[0];
40
+ const productPrice = product.prices[0];
41
+
42
+ const user = context.users.add({
43
+ dateCreated: new Date(),
44
+ orders: [
45
+ context.orders.add({
46
+ orderDate: new Date(),
47
+ orderItems:[
48
+ context.orderItems.add({
49
+ product,
50
+ productPrice,
51
+ amount: productPrice.amount
52
+ })
53
+ ]
54
+ })
55
+ ]
56
+ });
36
57
 
37
58
  await context.saveChanges();
38
59
  }
@@ -48,6 +69,7 @@ function addFemaleClothes(context: ShoppingContext) {
48
69
 
49
70
  context.products.add({
50
71
  name: "White T-Shirt",
72
+ status,
51
73
  categories: [
52
74
  context.productCategories.add({
53
75
  category
@@ -64,6 +86,7 @@ function addFemaleClothes(context: ShoppingContext) {
64
86
 
65
87
  context.products.add({
66
88
  name: "Red T-Shirt",
89
+ status,
67
90
  categories: [
68
91
  context.productCategories.add({
69
92
  category
@@ -80,6 +103,7 @@ function addFemaleClothes(context: ShoppingContext) {
80
103
 
81
104
  context.products.add({
82
105
  name: "Blue T-Shirt",
106
+ status,
83
107
  categories: [
84
108
  context.productCategories.add({
85
109
  category
@@ -96,6 +120,7 @@ function addFemaleClothes(context: ShoppingContext) {
96
120
 
97
121
  context.products.add({
98
122
  name: "Pink T-Shirt",
123
+ status,
99
124
  categories: [
100
125
  context.productCategories.add({
101
126
  category
@@ -120,8 +145,9 @@ function addMaleClothes(context: ShoppingContext) {
120
145
  const startDate = new Date();
121
146
  const active = true;
122
147
 
123
- context.products.add({
148
+ return [context.products.add({
124
149
  name: "White T-Shirt",
150
+ status,
125
151
  categories: [
126
152
  context.productCategories.add({
127
153
  category
@@ -134,10 +160,10 @@ function addMaleClothes(context: ShoppingContext) {
134
160
  startDate
135
161
  })
136
162
  ]
137
- });
138
-
163
+ }),
139
164
  context.products.add({
140
165
  name: "Red T-Shirt",
166
+ status,
141
167
  categories: [
142
168
  context.productCategories.add({
143
169
  category
@@ -150,10 +176,10 @@ function addMaleClothes(context: ShoppingContext) {
150
176
  startDate
151
177
  })
152
178
  ]
153
- });
154
-
179
+ }),
155
180
  context.products.add({
156
181
  name: "Blue T-Shirt",
182
+ status,
157
183
  categories: [
158
184
  context.productCategories.add({
159
185
  category
@@ -166,10 +192,10 @@ function addMaleClothes(context: ShoppingContext) {
166
192
  startDate
167
193
  })
168
194
  ]
169
- });
170
-
195
+ }),
171
196
  context.products.add({
172
197
  name: "Pink T-Shirt",
198
+ status,
173
199
  categories: [
174
200
  context.productCategories.add({
175
201
  category
@@ -182,7 +208,7 @@ function addMaleClothes(context: ShoppingContext) {
182
208
  startDate
183
209
  })
184
210
  ]
185
- });
211
+ })];
186
212
  }
187
213
 
188
214
  export const headPhoneCategory = "head-phones";
@@ -197,6 +223,7 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
197
223
 
198
224
  context.products.add({
199
225
  name: "Jabber Head Phones",
226
+ status,
200
227
  categories: [
201
228
  context.productCategories.add({
202
229
  category
@@ -213,6 +240,7 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
213
240
 
214
241
  context.products.add({
215
242
  name: "Sony Head Phones",
243
+ status,
216
244
  categories: [
217
245
  context.productCategories.add({
218
246
  category
@@ -229,6 +257,7 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
229
257
 
230
258
  context.products.add({
231
259
  name: "Sony Head Phones Black",
260
+ status,
232
261
  categories: [
233
262
  context.productCategories.add({
234
263
  category
@@ -245,6 +274,7 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
245
274
 
246
275
  context.products.add({
247
276
  name: "Sony Head Phones Blue",
277
+ status,
248
278
  categories: [
249
279
  context.productCategories.add({
250
280
  category
@@ -261,6 +291,7 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
261
291
 
262
292
  context.products.add({
263
293
  name: "Jabber Head Phones Black",
294
+ status,
264
295
  categories: [
265
296
  context.productCategories.add({
266
297
  category
@@ -277,6 +308,7 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
277
308
 
278
309
  context.products.add({
279
310
  name: "Jabber Head Phones Blue",
311
+ status,
280
312
  categories: [
281
313
  context.productCategories.add({
282
314
  category
@@ -0,0 +1,20 @@
1
+ import ContextEvents from "../../model/events/ContextEvents.js";
2
+ import { Order, OrderItem, Product, ProductCategory, ProductPrice, User } from "../model/ShoppingContext.js";
3
+ import { OrderEvents, OrderItemEvents } from "./events/OrderEvents.js";
4
+ import { ProductEvents, ProductCategoryEvents, ProductPriceEvents } from "./events/ProductEvents.js";
5
+ import { UserEvents } from "./events/UserEvents.js";
6
+
7
+ export class ShoppingContextEvents extends ContextEvents {
8
+
9
+ constructor() {
10
+ super();
11
+
12
+ this.register(User, UserEvents);
13
+ this.register(Product, ProductEvents);
14
+ this.register(ProductCategory, ProductCategoryEvents);
15
+ this.register(ProductPrice, ProductPriceEvents);
16
+ this.register(Order, OrderEvents);
17
+ this.register(OrderItem, OrderItemEvents);
18
+ }
19
+
20
+ }
@@ -0,0 +1,66 @@
1
+ import Inject from "../../../di/di.js";
2
+ import { IEntityQuery } from "../../../model/IFilterWithParameter.js";
3
+ import EntityEvents, { ForeignKeyFilter } from "../../../model/events/EntityEvents.js";
4
+ import { Order, OrderItem, User } from "../../model/ShoppingContext.js";
5
+ import { UserInfo } from "./UserInfo.js";
6
+
7
+ export class OrderEvents extends EntityEvents<Order> {
8
+
9
+ @Inject
10
+ user: UserInfo;
11
+
12
+ filter(query: IEntityQuery<Order>): IEntityQuery<Order> {
13
+ if (this.user.admin) {
14
+ return null;
15
+ }
16
+ const { userID } = this.user;
17
+
18
+ // user can access orders placed by the user or orders with products owned by user
19
+
20
+ return query.where({ userID }, (p) => (x) => x.customerID === p.userID || x.orderItems.some((item) => item.product.ownerID === p.userID));
21
+ }
22
+
23
+ modify(query: IEntityQuery<Order>): IEntityQuery<Order> {
24
+ if (this.user.admin) {
25
+ return null;
26
+ }
27
+ const { userID } = this.user;
28
+ // user can only modify placed orders
29
+ return query.where({ userID }, (p) => (x) => x.customerID === p.userID);
30
+ }
31
+ }
32
+
33
+ export class OrderItemEvents extends EntityEvents<OrderItem> {
34
+
35
+ @Inject
36
+ user: UserInfo;
37
+
38
+ filter(query: IEntityQuery<OrderItem>): IEntityQuery<OrderItem> {
39
+ if (this.user.admin) {
40
+ return null;
41
+ }
42
+ const { userID } = this.user;
43
+
44
+ // user can access orders placed by the user or orders with products owned by user
45
+
46
+ return query.where({ userID }, (p) => (x) => x.order.customerID === p.userID || x.product.ownerID === p.userID);
47
+ }
48
+
49
+ modify(query: IEntityQuery<OrderItem>): IEntityQuery<OrderItem> {
50
+ if (this.user.admin) {
51
+ return null;
52
+ }
53
+ const { userID } = this.user;
54
+ // user can only modify placed orders
55
+ return query.where({ userID }, (p) => (x) => x.order.customerID === p.userID);
56
+ }
57
+
58
+ onForeignKeyFilter(filter: ForeignKeyFilter<OrderItem>): IEntityQuery<any> {
59
+ if (filter.is((x) => x.product)) {
60
+ return filter.read();
61
+ }
62
+ if (filter.is((x) => x.productPrice)) {
63
+ return filter.read();
64
+ }
65
+ }
66
+ }