@contember/engine-content-api 1.4.0-beta.2 → 1.4.0-rc.2
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/dist/src/mapper/select/SelectBuilder.d.ts.map +1 -1
- package/dist/src/mapper/select/SelectBuilder.js +4 -3
- package/dist/src/mapper/select/SelectBuilder.js.map +1 -1
- package/dist/src/mapper/select/SelectHydrator.d.ts +1 -1
- package/dist/src/mapper/select/SelectHydrator.d.ts.map +1 -1
- package/dist/src/mapper/select/SelectHydrator.js +5 -5
- package/dist/src/mapper/select/SelectHydrator.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/cases/integration/graphQlRequests/query/acl/relationAcl.test.d.ts +2 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/relationAcl.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/relationAcl.test.js +238 -0
- package/dist/tests/cases/integration/graphQlRequests/query/acl/relationAcl.test.js.map +1 -0
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/mapper/select/SelectBuilder.ts +10 -4
- package/src/mapper/select/SelectHydrator.ts +7 -7
- package/tests/cases/integration/graphQlRequests/query/acl/relationAcl.test.ts +226 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/engine-content-api",
|
|
3
|
-
"version": "1.4.0-
|
|
3
|
+
"version": "1.4.0-rc.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"typings": "dist/src/index.d.ts",
|
|
@@ -13,19 +13,19 @@
|
|
|
13
13
|
"test": "vitest --dir ./tests/cases --no-threads"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@contember/database": "1.4.0-
|
|
17
|
-
"@contember/dic": "1.4.0-
|
|
18
|
-
"@contember/graphql-utils": "1.4.0-
|
|
19
|
-
"@contember/logger": "1.4.0-
|
|
20
|
-
"@contember/schema": "1.4.0-
|
|
21
|
-
"@contember/schema-definition": "1.4.0-
|
|
22
|
-
"@contember/schema-utils": "1.4.0-
|
|
16
|
+
"@contember/database": "1.4.0-rc.2",
|
|
17
|
+
"@contember/dic": "1.4.0-rc.2",
|
|
18
|
+
"@contember/graphql-utils": "1.4.0-rc.2",
|
|
19
|
+
"@contember/logger": "1.4.0-rc.2",
|
|
20
|
+
"@contember/schema": "1.4.0-rc.2",
|
|
21
|
+
"@contember/schema-definition": "1.4.0-rc.2",
|
|
22
|
+
"@contember/schema-utils": "1.4.0-rc.2",
|
|
23
23
|
"fast-deep-equal": "^3.1.3"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@contember/database-tester": "1.4.0-
|
|
27
|
-
"@contember/engine-api-tester": "1.4.0-
|
|
28
|
-
"@contember/schema-definition": "1.4.0-
|
|
26
|
+
"@contember/database-tester": "1.4.0-rc.2",
|
|
27
|
+
"@contember/engine-api-tester": "1.4.0-rc.2",
|
|
28
|
+
"@contember/schema-definition": "1.4.0-rc.2",
|
|
29
29
|
"@graphql-codegen/cli": "^5.0.0",
|
|
30
30
|
"@graphql-codegen/typescript": "^4.0.1",
|
|
31
31
|
"@graphql-codegen/typescript-operations": "^4.0.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Acl, Input, Model } from '@contember/schema'
|
|
1
|
+
import { Acl, Input, Model, Value } from '@contember/schema'
|
|
2
2
|
import { acceptFieldVisitor, getColumnName } from '@contember/schema-utils'
|
|
3
3
|
import { ColumnValueGetter, SelectHydrator, SelectRow } from './SelectHydrator'
|
|
4
4
|
import { Path, PathFactory } from './Path'
|
|
@@ -138,15 +138,21 @@ export class SelectBuilder {
|
|
|
138
138
|
relationPath: this.relationPath,
|
|
139
139
|
addData: async ({ field, dataProvider, defaultValue, predicate }) => {
|
|
140
140
|
if (predicate === false) {
|
|
141
|
-
this.hydrator.addPromise(fieldPath,
|
|
141
|
+
this.hydrator.addPromise(fieldPath, () => null, Promise.resolve({}), defaultValue ?? null)
|
|
142
142
|
return
|
|
143
143
|
}
|
|
144
144
|
const predicateGetter = predicate !== undefined && predicate !== true ? addPredicate(predicate) : null
|
|
145
145
|
const columnName = getColumnName(this.schema, entity, field)
|
|
146
|
-
|
|
146
|
+
const parentValuePath = path.for(field)
|
|
147
|
+
let ids = await this.getColumnValues(parentValuePath, columnName, predicateGetter)
|
|
147
148
|
|
|
148
149
|
const data = ids.length > 0 ? dataProvider(ids) : Promise.resolve({})
|
|
149
|
-
this.hydrator.addPromise(
|
|
150
|
+
this.hydrator.addPromise(
|
|
151
|
+
fieldPath,
|
|
152
|
+
row => predicateGetter === null || predicateGetter(row) ? (row[parentValuePath.alias] as Value.PrimaryValue) : null,
|
|
153
|
+
data,
|
|
154
|
+
defaultValue ?? null,
|
|
155
|
+
)
|
|
150
156
|
},
|
|
151
157
|
addColumn: ({ path = fieldPath, predicate, query, valueGetter }) => {
|
|
152
158
|
if (predicate === false) {
|
|
@@ -5,14 +5,14 @@ import { logger } from '@contember/logger'
|
|
|
5
5
|
|
|
6
6
|
type DataPromises = {
|
|
7
7
|
path: Path
|
|
8
|
-
|
|
8
|
+
getParentValue: ColumnValueGetter<Value.PrimaryValue | null>
|
|
9
9
|
data: Promise<SelectNestedData>
|
|
10
10
|
defaultValue: SelectNestedDefaultValue
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
type ResolvedData = {
|
|
14
14
|
path: Path
|
|
15
|
-
|
|
15
|
+
getParentValue: ColumnValueGetter<Value.PrimaryValue | null>
|
|
16
16
|
data: SelectNestedData
|
|
17
17
|
defaultValue: SelectNestedDefaultValue
|
|
18
18
|
}
|
|
@@ -34,11 +34,11 @@ export class SelectHydrator {
|
|
|
34
34
|
|
|
35
35
|
public addPromise(
|
|
36
36
|
path: Path,
|
|
37
|
-
|
|
37
|
+
getParentValue: ColumnValueGetter<Value.PrimaryValue | null>,
|
|
38
38
|
data: Promise<SelectNestedData>,
|
|
39
39
|
defaultValue: SelectNestedDefaultValue,
|
|
40
40
|
) {
|
|
41
|
-
this.promises.push({ path,
|
|
41
|
+
this.promises.push({ path, getParentValue, data, defaultValue })
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
public async hydrateGroups(rows: SelectRow[], groupBy: string): Promise<SelectGroupedObjects> {
|
|
@@ -87,11 +87,11 @@ export class SelectHydrator {
|
|
|
87
87
|
currentObject[last] = this.formatValue(columnPath.getValue(row))
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
for (let { path,
|
|
90
|
+
for (let { path, getParentValue, data, defaultValue } of resolvedData) {
|
|
91
91
|
const pathTmp = [...path.path]
|
|
92
92
|
const last = pathTmp.pop() as string
|
|
93
93
|
const currentObject = pathTmp.reduce<any>((obj, part) => (obj && obj[part]) || undefined, result)
|
|
94
|
-
const parentValue = row
|
|
94
|
+
const parentValue = getParentValue(row)
|
|
95
95
|
if (currentObject) {
|
|
96
96
|
currentObject[last] = (parentValue ? data[parentValue] : undefined) || defaultValue
|
|
97
97
|
}
|
|
@@ -103,7 +103,7 @@ export class SelectHydrator {
|
|
|
103
103
|
private async resolveDataPromises(): Promise<ResolvedData[]> {
|
|
104
104
|
const results = await Promise.allSettled(this.promises.map(async (it): Promise<ResolvedData> => ({
|
|
105
105
|
defaultValue: it.defaultValue,
|
|
106
|
-
|
|
106
|
+
getParentValue: it.getParentValue,
|
|
107
107
|
path: it.path,
|
|
108
108
|
data: await it.data,
|
|
109
109
|
})))
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { c, createSchema } from '@contember/schema-definition'
|
|
2
|
+
import { test } from 'vitest'
|
|
3
|
+
import { execute } from '../../../../../src/test'
|
|
4
|
+
import { GQL, SQL } from '../../../../../src/tags'
|
|
5
|
+
import { PermissionFactory } from '../../../../../../src'
|
|
6
|
+
import { testUuid } from '@contember/engine-api-tester'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
namespace RelationAclManyHasOne {
|
|
10
|
+
export const readerRole = c.createRole('reader')
|
|
11
|
+
|
|
12
|
+
@c.Allow(readerRole, {
|
|
13
|
+
read: ['title'],
|
|
14
|
+
})
|
|
15
|
+
@c.Allow(readerRole, {
|
|
16
|
+
when: { discloseAuthor: { eq: true } },
|
|
17
|
+
read: ['author'],
|
|
18
|
+
})
|
|
19
|
+
export class Article {
|
|
20
|
+
title = c.stringColumn().notNull()
|
|
21
|
+
author = c.manyHasOne(Author).notNull()
|
|
22
|
+
discloseAuthor = c.boolColumn().notNull()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@c.Allow(readerRole, {
|
|
26
|
+
read: true,
|
|
27
|
+
})
|
|
28
|
+
export class Author {
|
|
29
|
+
name = c.stringColumn().notNull()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
test('can read same entity only over relation of some rows - many has one', async () => {
|
|
35
|
+
const schema = createSchema(RelationAclManyHasOne)
|
|
36
|
+
|
|
37
|
+
const permissions = new PermissionFactory().create(schema, ['reader'])
|
|
38
|
+
|
|
39
|
+
await execute({
|
|
40
|
+
schema: schema.model,
|
|
41
|
+
permissions: permissions,
|
|
42
|
+
variables: {},
|
|
43
|
+
query: GQL`
|
|
44
|
+
query {
|
|
45
|
+
listArticle {
|
|
46
|
+
title
|
|
47
|
+
author {
|
|
48
|
+
name
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}`,
|
|
52
|
+
executes: [
|
|
53
|
+
{
|
|
54
|
+
sql: SQL`select "root_"."title" as "root_title", "root_"."disclose_author" = ? as "root___predicate_discloseAuthor_eq_true", "root_"."author_id" as "root_author", "root_"."id" as "root_id"
|
|
55
|
+
from "public"."article" as "root_"`,
|
|
56
|
+
response: {
|
|
57
|
+
rows: [
|
|
58
|
+
{
|
|
59
|
+
root_id: testUuid(1),
|
|
60
|
+
root_title: 'Article A',
|
|
61
|
+
root___predicate_discloseAuthor_eq_true: true,
|
|
62
|
+
root_author: testUuid(10),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
root_id: testUuid(2),
|
|
66
|
+
root_title: 'Article B',
|
|
67
|
+
root___predicate_discloseAuthor_eq_true: false,
|
|
68
|
+
root_author: testUuid(10),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
sql: SQL`select "root_"."id" as "root_id", "root_"."name" as "root_name", "root_"."id" as "root_id" from "public"."author" as "root_" where "root_"."id" in (?)`,
|
|
75
|
+
parameters: [testUuid(10)],
|
|
76
|
+
response: {
|
|
77
|
+
rows: [
|
|
78
|
+
{
|
|
79
|
+
root_id: testUuid(10),
|
|
80
|
+
root_name: 'John doe',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
return: {
|
|
87
|
+
data: {
|
|
88
|
+
listArticle: [
|
|
89
|
+
{
|
|
90
|
+
title: 'Article A',
|
|
91
|
+
author: {
|
|
92
|
+
name: 'John doe',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
title: 'Article B',
|
|
97
|
+
author: null,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
namespace RelationAclManyHasMany {
|
|
108
|
+
export const readerRole = c.createRole('reader')
|
|
109
|
+
|
|
110
|
+
@c.Allow(readerRole, {
|
|
111
|
+
read: ['title'],
|
|
112
|
+
})
|
|
113
|
+
@c.Allow(readerRole, {
|
|
114
|
+
when: { discloseAuthor: { eq: true } },
|
|
115
|
+
read: ['authors'],
|
|
116
|
+
})
|
|
117
|
+
export class Article {
|
|
118
|
+
title = c.stringColumn().notNull()
|
|
119
|
+
authors = c.manyHasMany(Author)
|
|
120
|
+
discloseAuthor = c.boolColumn().notNull()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@c.Allow(readerRole, {
|
|
124
|
+
read: true,
|
|
125
|
+
})
|
|
126
|
+
export class Author {
|
|
127
|
+
name = c.stringColumn().notNull()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
test('can read same entity only over relation of some rows - many has many', async () => {
|
|
133
|
+
const schema = createSchema(RelationAclManyHasMany)
|
|
134
|
+
|
|
135
|
+
const permissions = new PermissionFactory().create(schema, ['reader'])
|
|
136
|
+
|
|
137
|
+
await execute({
|
|
138
|
+
schema: schema.model,
|
|
139
|
+
permissions: permissions,
|
|
140
|
+
variables: {},
|
|
141
|
+
query: GQL`
|
|
142
|
+
query {
|
|
143
|
+
listArticle {
|
|
144
|
+
title
|
|
145
|
+
authors {
|
|
146
|
+
name
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}`,
|
|
150
|
+
executes: [
|
|
151
|
+
{
|
|
152
|
+
sql: SQL`select "root_"."title" as "root_title", "root_"."disclose_author" = ? as "root___predicate_discloseauthor_eq_true", "root_"."id" as "root_id", "root_"."id" as "root_id"
|
|
153
|
+
from "public"."article" as "root_"`,
|
|
154
|
+
response: {
|
|
155
|
+
rows: [
|
|
156
|
+
{
|
|
157
|
+
root_id: testUuid(1),
|
|
158
|
+
root_title: 'Article A',
|
|
159
|
+
root___predicate_discloseAuthor_eq_true: true,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
root_id: testUuid(2),
|
|
163
|
+
root_title: 'Article B',
|
|
164
|
+
root___predicate_discloseAuthor_eq_true: false,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
sql: SQL`select "junction_"."author_id", "junction_"."article_id" from "public"."article_authors" as "junction_" where "junction_"."article_id" in (?)`,
|
|
171
|
+
parameters: [testUuid(1)],
|
|
172
|
+
response: {
|
|
173
|
+
rows: [
|
|
174
|
+
{
|
|
175
|
+
article_id: testUuid(1),
|
|
176
|
+
author_id: testUuid(10),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
article_id: testUuid(1),
|
|
180
|
+
author_id: testUuid(11),
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
sql: SQL`select "root_"."name" as "root_name", "root_"."id" as "root_id" from "public"."author" as "root_" where "root_"."id" in (?, ?)`,
|
|
187
|
+
parameters: [testUuid(10), testUuid(11)],
|
|
188
|
+
response: {
|
|
189
|
+
rows: [
|
|
190
|
+
{
|
|
191
|
+
root_id: testUuid(10),
|
|
192
|
+
root_name: 'John doe',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
root_id: testUuid(11),
|
|
196
|
+
root_name: 'Jack doe',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
return: {
|
|
203
|
+
data: {
|
|
204
|
+
listArticle: [
|
|
205
|
+
{
|
|
206
|
+
title: 'Article A',
|
|
207
|
+
authors: [
|
|
208
|
+
{
|
|
209
|
+
name: 'John doe',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'Jack doe',
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
title: 'Article B',
|
|
218
|
+
authors: [],
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
|