@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.
- package/.github/workflows/node.yml +7 -2
- package/.vscode/settings.json +1 -0
- package/README.md +57 -27
- package/package.json +2 -2
- package/src/common/EntityAccessError.ts +10 -0
- package/src/common/IDisposable.ts +25 -0
- package/src/common/ImmutableObject.ts +53 -0
- package/src/common/Logger.ts +59 -0
- package/src/common/TypeInfo.ts +3 -0
- package/src/common/usingAsync.ts +42 -12
- package/src/compiler/QueryCompiler.ts +28 -30
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +23 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +23 -0
- package/src/decorators/ForeignKey.ts +1 -1
- package/src/decorators/IClassOf.ts +2 -1
- package/src/decorators/parser/NameParser.ts +15 -0
- package/src/di/di.ts +224 -0
- package/src/drivers/base/BaseDriver.ts +28 -3
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +48 -9
- package/src/drivers/sql-server/SqlServerDriver.ts +7 -16
- package/src/entity-query/EntityType.ts +1 -1
- package/src/model/EntityContext.ts +167 -22
- package/src/model/EntityQuery.ts +76 -60
- package/src/model/EntitySource.ts +39 -33
- package/src/model/IFilterWithParameter.ts +3 -0
- package/src/model/SourceExpression.ts +21 -25
- package/src/model/{ChangeEntry.ts → changes/ChangeEntry.ts} +33 -3
- package/src/model/{ChangeSet.ts → changes/ChangeSet.ts} +12 -11
- package/src/model/events/ContextEvents.ts +26 -0
- package/src/model/events/EntityEvents.ts +92 -0
- package/src/model/{IdentityService.ts → identity/IdentityService.ts} +1 -1
- package/src/model/symbols.ts +1 -0
- package/src/model/verification/VerificationSession.ts +173 -0
- package/src/query/ast/DebugStringVisitor.ts +128 -0
- package/src/query/ast/ExpressionToSql.ts +289 -119
- package/src/query/ast/Expressions.ts +111 -12
- package/src/query/ast/IStringTransformer.ts +16 -4
- package/src/query/ast/ReplaceParameter.ts +40 -0
- package/src/query/ast/Visitor.ts +20 -5
- package/src/query/parser/ArrowToExpression.ts +116 -16
- package/src/query/parser/BabelVisitor.ts +27 -44
- package/src/query/parser/NotSupportedError.ts +5 -0
- package/src/query/parser/Restructure.ts +66 -0
- package/src/query/parser/TransformVisitor.ts +83 -0
- package/src/sql/ISql.ts +10 -0
- package/src/tests/db-tests/tests/select-items.ts +12 -0
- package/src/tests/expressions/left-joins/child-joins.ts +19 -26
- package/src/tests/expressions/sanitize/sanitize-test.ts +17 -0
- package/src/tests/expressions/select/select.ts +24 -0
- package/src/tests/expressions/simple/parse-arrow.ts +10 -0
- package/src/tests/model/ShoppingContext.ts +7 -3
- package/src/tests/model/createContext.ts +45 -13
- package/src/tests/security/ShoppingContextEvents.ts +20 -0
- package/src/tests/security/events/OrderEvents.ts +66 -0
- package/src/tests/security/events/ProductEvents.ts +92 -0
- package/src/tests/security/events/UserEvents.ts +18 -0
- package/src/tests/security/events/UserInfo.ts +7 -0
- package/src/tests/security/tests/place-order.ts +71 -0
- package/test.js +11 -4
- package/tsconfig.json +2 -0
- package/src/decorators/parser/MemberParser.ts +0 -8
- 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
|
-
|
|
9
|
-
"P1"."name",
|
|
10
|
-
"P1"."ownerID"
|
|
8
|
+
"P1"."productID","P1"."name","P1"."ownerID","P1"."status"
|
|
11
9
|
FROM "Products" AS "P1"
|
|
12
|
-
WHERE
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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"."
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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
|
|
33
|
+
const clothes = addMaleClothes(context);
|
|
34
|
+
|
|
35
|
+
addFemaleClothes(context);
|
|
36
|
+
|
|
37
|
+
await context.saveChanges();
|
|
34
38
|
|
|
35
|
-
const
|
|
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
|
+
}
|