@contember/engine-server 1.1.0-alpha.5 → 1.1.0-alpha.8

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 (41) hide show
  1. package/alpine.dockerfile +1 -1
  2. package/debian.dockerfile +1 -1
  3. package/dist/src/bootstrap/index.js +5 -1
  4. package/dist/src/bootstrap/index.js.map +1 -1
  5. package/dist/src/config/config.js.map +1 -1
  6. package/dist/src/config/configSchema.d.ts +10 -8
  7. package/dist/src/config/configSchema.d.ts.map +1 -1
  8. package/dist/src/config/configSchema.js +74 -51
  9. package/dist/src/config/configSchema.js.map +1 -1
  10. package/dist/src/config/projectConfigResolver.d.ts.map +1 -1
  11. package/dist/src/config/projectConfigResolver.js +30 -2
  12. package/dist/src/config/projectConfigResolver.js.map +1 -1
  13. package/dist/src/http/index.js +5 -1
  14. package/dist/src/http/index.js.map +1 -1
  15. package/dist/src/project/index.js +5 -1
  16. package/dist/src/project/index.js.map +1 -1
  17. package/dist/src/start.js +5 -1
  18. package/dist/src/start.js.map +1 -1
  19. package/dist/src/tsconfig.tsbuildinfo +1 -1
  20. package/dist/src/utils/cluster.js.map +1 -1
  21. package/dist/src/utils/index.js +5 -1
  22. package/dist/src/utils/index.js.map +1 -1
  23. package/dist/src/utils/sentry.js +5 -1
  24. package/dist/src/utils/sentry.js.map +1 -1
  25. package/dist/tests/e2e/http.test.d.ts +2 -0
  26. package/dist/tests/e2e/http.test.d.ts.map +1 -0
  27. package/dist/tests/e2e/http.test.js +275 -0
  28. package/dist/tests/e2e/http.test.js.map +1 -0
  29. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +22 -23
  31. package/src/config/configSchema.ts +9 -8
  32. package/src/config/projectConfigResolver.ts +7 -4
  33. package/src/tsconfig.json +3 -0
  34. package/tests/e2e/http.test.ts +303 -0
  35. package/tests/start-server.sh +16 -0
  36. package/vite.config.js +3 -0
  37. package/dist/tests/cases/http.d.ts +0 -2
  38. package/dist/tests/cases/http.d.ts.map +0 -1
  39. package/dist/tests/cases/http.js +0 -298
  40. package/dist/tests/cases/http.js.map +0 -1
  41. package/tests/cases/http.ts +0 -340
package/package.json CHANGED
@@ -1,31 +1,31 @@
1
1
  {
2
2
  "name": "@contember/engine-server",
3
- "version": "1.1.0-alpha.5",
3
+ "version": "1.1.0-alpha.8",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
7
7
  "scripts": {
8
- "test": "uvu dist/tests/cases/ \\.js$"
8
+ "test-e2e": "vitest --no-threads tests/e2e"
9
9
  },
10
10
  "dependencies": {
11
- "@contember/authorization": "^1.1.0-alpha.5",
12
- "@contember/config-loader": "^1.1.0-alpha.5",
13
- "@contember/database": "^1.1.0-alpha.5",
14
- "@contember/database-migrations": "^1.1.0-alpha.5",
15
- "@contember/dic": "^1.1.0-alpha.5",
16
- "@contember/engine-common": "^1.1.0-alpha.5",
17
- "@contember/engine-content-api": "^1.1.0-alpha.5",
18
- "@contember/engine-http": "^1.1.0-alpha.5",
19
- "@contember/engine-plugins": "^1.1.0-alpha.5",
20
- "@contember/engine-s3-plugin": "^1.1.0-alpha.5",
21
- "@contember/engine-system-api": "^1.1.0-alpha.5",
22
- "@contember/engine-tenant-api": "^1.1.0-alpha.5",
23
- "@contember/engine-vimeo-plugin": "^1.1.0-alpha.5",
24
- "@contember/queryable": "^1.1.0-alpha.5",
25
- "@contember/schema": "^1.1.0-alpha.5",
26
- "@contember/schema-definition": "^1.1.0-alpha.5",
27
- "@contember/schema-migrations": "^1.1.0-alpha.5",
28
- "@contember/schema-utils": "^1.1.0-alpha.5",
11
+ "@contember/authorization": "^1.1.0-alpha.8",
12
+ "@contember/config-loader": "^1.1.0-alpha.8",
13
+ "@contember/database": "^1.1.0-alpha.8",
14
+ "@contember/database-migrations": "^1.1.0-alpha.8",
15
+ "@contember/dic": "^1.1.0-alpha.8",
16
+ "@contember/typesafe": "^1.1.0-alpha.8",
17
+ "@contember/engine-content-api": "^1.1.0-alpha.8",
18
+ "@contember/engine-http": "^1.1.0-alpha.8",
19
+ "@contember/engine-plugins": "^1.1.0-alpha.8",
20
+ "@contember/engine-s3-plugin": "^1.1.0-alpha.8",
21
+ "@contember/engine-system-api": "^1.1.0-alpha.8",
22
+ "@contember/engine-tenant-api": "^1.1.0-alpha.8",
23
+ "@contember/engine-vimeo-plugin": "^1.1.0-alpha.8",
24
+ "@contember/queryable": "^1.1.0-alpha.8",
25
+ "@contember/schema": "^1.1.0-alpha.8",
26
+ "@contember/schema-definition": "^1.1.0-alpha.8",
27
+ "@contember/schema-migrations": "^1.1.0-alpha.8",
28
+ "@contember/schema-utils": "^1.1.0-alpha.8",
29
29
  "@koa/cors": "^3.1.0",
30
30
  "@sentry/node": "^6.8.0",
31
31
  "graphql": "^15.4.0",
@@ -37,11 +37,10 @@
37
37
  "uuid": "^8.3.1"
38
38
  },
39
39
  "devDependencies": {
40
- "@contember/engine-api-tester": "^1.1.0-alpha.5",
40
+ "@contember/engine-api-tester": "^1.1.0-alpha.8",
41
41
  "@types/koa-compress": "^4.0.1",
42
42
  "@types/node": "^17.0.5",
43
43
  "@types/supertest": "^2.0.8",
44
- "supertest": "^6.0.1",
45
- "uvu": "^0.5.3"
44
+ "supertest": "^6.0.1"
46
45
  }
47
46
  }
@@ -1,4 +1,4 @@
1
- import { Typesafe } from '@contember/engine-common'
1
+ import * as Typesafe from '@contember/typesafe'
2
2
  import { MailerOptions } from '@contember/engine-tenant-api'
3
3
  import { upperCaseFirst } from '../utils'
4
4
 
@@ -25,9 +25,9 @@ export const tenantConfigSchema = Typesafe.intersection(
25
25
  port: Typesafe.number,
26
26
  user: Typesafe.string,
27
27
  password: Typesafe.string,
28
- }), (value, input): MailerOptions => {
28
+ }), (value, input): MailerOptions & Typesafe.JsonObject => {
29
29
  return {
30
- ...(input as MailerOptions),
30
+ ...(input as any),
31
31
  ...value,
32
32
  ...(value.user ? { auth: { user: value.user, pass: value.password } } : {}),
33
33
  }
@@ -64,7 +64,7 @@ export const serverConfigSchema = Typesafe.intersection(
64
64
  }),
65
65
  Typesafe.partial({
66
66
  workerCount: Typesafe.union(Typesafe.number, Typesafe.string),
67
- projectGroup: (val, path) => Typesafe.valueAt(val, ['domainMapping']) === undefined
67
+ projectGroup: (val: unknown, path: PropertyKey[] = []) => Typesafe.valueAt(val, ['domainMapping']) === undefined
68
68
  ? undefined
69
69
  : Typesafe.intersection(
70
70
  Typesafe.object({
@@ -78,14 +78,15 @@ export const serverConfigSchema = Typesafe.intersection(
78
78
  }),
79
79
  )
80
80
 
81
- export const stageConfig = Typesafe.map(
81
+ export const stageConfig = Typesafe.record(
82
+ Typesafe.string,
82
83
  Typesafe.intersection(
83
84
  Typesafe.object({
84
85
  name: Typesafe.union(
85
86
  Typesafe.string,
86
- (_, path = []) => upperCaseFirst(String(path[path.length - 2])),
87
+ (_: unknown, path: PropertyKey[] = []) => upperCaseFirst(String(path[path.length - 2])),
87
88
  ),
88
- slug: (_, path = []) => String(path[path.length - 2]),
89
+ slug: (_: unknown, path: PropertyKey[] = []) => String(path[path.length - 2]),
89
90
  }),
90
91
  Typesafe.partial({
91
92
  schema: Typesafe.string,
@@ -96,7 +97,7 @@ export const stageConfig = Typesafe.map(
96
97
  export const projectConfigSchema = Typesafe.object({
97
98
  name: Typesafe.union(
98
99
  Typesafe.string,
99
- (_, path = []) => upperCaseFirst(String(path[path.length - 2])).replace(/-/g, ' '),
100
+ (_: unknown, path: PropertyKey[] = []) => upperCaseFirst(String(path[path.length - 2])).replace(/-/g, ' '),
100
101
  ),
101
102
  slug: (_, path = []) => String(path[path.length - 2]),
102
103
  stages: (input, path = []) => Object.values(stageConfig(input, path)),
@@ -1,7 +1,7 @@
1
1
  import { ProjectConfig, ProjectSecrets } from '@contember/engine-http'
2
2
  import { ConfigProcessor } from '@contember/engine-plugins'
3
3
  import { Merger, ParametersResolver, resolveParameters, UndefinedParameterError } from '@contember/config-loader'
4
- import { Typesafe } from '@contember/engine-common'
4
+ import * as Typesafe from '@contember/typesafe'
5
5
  import { projectConfigSchema } from './configSchema'
6
6
  import { TenantConfig } from './config'
7
7
 
@@ -27,9 +27,12 @@ export const createProjectConfigResolver = (env: Env, config: any, configProcess
27
27
  const resolvedConfig = resolveParameters(mergedConfig, createProjectParametersResolver(slug, env, secrets))
28
28
 
29
29
  const projectConfigSchemaWithPlugins = configProcessors.reduce(
30
- (schema, current) => (
31
- current.getProjectConfigSchema ? Typesafe.intersection(schema, current.getProjectConfigSchema(slug)) : schema
32
- ),
30
+ (schema, current) => {
31
+ if (!current.getProjectConfigSchema) {
32
+ return schema
33
+ }
34
+ return Typesafe.intersection(schema, current.getProjectConfigSchema(slug))
35
+ },
33
36
  projectConfigSchema,
34
37
  )
35
38
  const projectConfig = projectConfigSchemaWithPlugins(resolvedConfig, ['project', slug])
package/src/tsconfig.json CHANGED
@@ -54,6 +54,9 @@
54
54
  },
55
55
  {
56
56
  "path": "../../engine-vimeo-plugin/src"
57
+ },
58
+ {
59
+ "path": "../../typesafe/src"
57
60
  }
58
61
  ]
59
62
  }
@@ -0,0 +1,303 @@
1
+ import supertest from 'supertest'
2
+ import * as nodeAssert from 'assert'
3
+ import { assert, beforeAll, test } from 'vitest'
4
+ import { MigrationFilesManager, MigrationsResolver } from '@contember/schema-migrations'
5
+ import { getExampleProjectDirectory } from '@contember/engine-api-tester'
6
+
7
+
8
+ const rootToken = String(process.env.CONTEMBER_ROOT_TOKEN)
9
+ const loginToken = String(process.env.CONTEMBER_LOGIN_TOKEN)
10
+ const apiUrl = String(process.env.CONTEMBER_API_URL)
11
+ let projectSlug = ''
12
+
13
+ // Used to allow prettier formatting of GraphQL queries
14
+ const gql = (strings: TemplateStringsArray) => {
15
+ nodeAssert.strictEqual(strings.length, 1)
16
+ return strings[0]
17
+ }
18
+
19
+
20
+ const executeGraphql = (
21
+ query: string,
22
+ options: { authorizationToken?: string; path?: string; variables?: Record<string, any> } = {},
23
+ ) => {
24
+ return supertest(apiUrl)
25
+ .post(options.path || `/content/${projectSlug}/live`)
26
+ .set('Authorization', 'Bearer ' + (options.authorizationToken || rootToken))
27
+ .send({
28
+ query,
29
+ variables: options.variables || {},
30
+ })
31
+ }
32
+
33
+ const signIn = async (email: string, password: string): Promise<string> => {
34
+ const response2 = await executeGraphql(
35
+ gql`
36
+ mutation ($email: String!, $password: String!) {
37
+ signIn(email: $email, password: $password) {
38
+ ok
39
+ result {
40
+ token
41
+ }
42
+ }
43
+ }
44
+ `,
45
+ {
46
+ variables: { email, password },
47
+ authorizationToken: loginToken,
48
+ path: '/tenant',
49
+ },
50
+ )
51
+
52
+ return response2.body.data.signIn.result.token
53
+ }
54
+
55
+ beforeAll(async () => {
56
+ projectSlug = 'test' + Date.now()
57
+ const result = await executeGraphql(`
58
+ mutation($projectSlug: String!, $config: Json!) {
59
+ createProject(projectSlug: $projectSlug, config: $config) {
60
+ ok
61
+ error { code }
62
+ }
63
+ }
64
+ `,
65
+ {
66
+ path: '/tenant',
67
+ variables: {
68
+ projectSlug: projectSlug,
69
+ config: {},
70
+ },
71
+ })
72
+ .expect(response => {
73
+ assert.isOk(response.body)
74
+ assert.isOk(response.body.data)
75
+ assert.isOk(response.body.data.createProject)
76
+ if (!response.body.data.createProject.ok) {
77
+ assert.equal(response.body.data.createProject.error.code, 'ALREADY_EXISTS')
78
+ }
79
+ })
80
+ .expect(200)
81
+ if (!result.body.data.createProject.ok) {
82
+ return
83
+ }
84
+ const migrationFilesManager = new MigrationsResolver(MigrationFilesManager.createForProject(getExampleProjectDirectory(), 'sample'))
85
+ const migrations = await migrationFilesManager.getMigrations()
86
+ assert.isAtLeast(migrations.length, 2)
87
+ await executeGraphql(`
88
+ mutation($migrations: [Migration!]!) {
89
+ migrate(migrations: $migrations) {
90
+ ok
91
+ error { code }
92
+ }
93
+ }
94
+ `,
95
+ {
96
+ path: '/system/' + projectSlug,
97
+ variables: {
98
+ migrations,
99
+ },
100
+ })
101
+ .expect(response => {
102
+ assert.deepStrictEqual(response.body.data, {
103
+ migrate: {
104
+ error: null,
105
+ ok: true,
106
+ },
107
+ })
108
+ })
109
+ })
110
+
111
+ test('show homepage', async () => {
112
+ await supertest(apiUrl)
113
+ .get('/')
114
+ .expect(200)
115
+ .expect('App is running')
116
+ })
117
+
118
+ test('Content API: create & read tag', async () => {
119
+ await executeGraphql(
120
+ gql`
121
+ mutation {
122
+ createTag(data: { label: "graphql" }) {
123
+ ok
124
+ }
125
+ }
126
+ `,
127
+ )
128
+ .expect(response => {
129
+ assert.deepStrictEqual(response.body.data, {
130
+ createTag: {
131
+ ok: true,
132
+ },
133
+ })
134
+ })
135
+ .expect(200)
136
+
137
+ await executeGraphql(
138
+ gql`
139
+ query {
140
+ listTag(filter: { label: { eq: "graphql" } }) {
141
+ label
142
+ }
143
+ }
144
+ `,
145
+ )
146
+ .expect(response => {
147
+ assert.deepStrictEqual(response.body.data, {
148
+ listTag: [
149
+ {
150
+ label: 'graphql',
151
+ },
152
+ ],
153
+ })
154
+ })
155
+ .expect(200)
156
+ })
157
+
158
+ test('Content API: X-Contember-Ref header', async () => {
159
+ await executeGraphql(
160
+ gql`
161
+ mutation {
162
+ createTag(data: { label: "typescript" }) {
163
+ ok
164
+ }
165
+ }
166
+ `,
167
+ ).expect(200)
168
+
169
+ const response = await executeGraphql(
170
+ gql`
171
+ query {
172
+ listTag(filter: { label: { eq: "typescript" } }) {
173
+ label
174
+ }
175
+ }
176
+ `,
177
+ )
178
+ .set('X-Contember-Ref', 'None')
179
+ .expect(response => {
180
+ assert.deepStrictEqual(response.body.data, {
181
+ listTag: [
182
+ {
183
+ label: 'typescript',
184
+ },
185
+ ],
186
+ })
187
+ })
188
+ .expect(200)
189
+ const eventKey = response.get('X-Contember-Ref')
190
+ await executeGraphql(
191
+ gql`
192
+ query {
193
+ listTag {
194
+ label
195
+ }
196
+ }
197
+ `,
198
+ )
199
+ .set('X-Contember-Ref', eventKey)
200
+ .expect(304)
201
+
202
+ // ignored for mutation
203
+ await executeGraphql(
204
+ gql`
205
+ mutation {
206
+ createTag(data: { label: "typescript" }) {
207
+ ok
208
+ }
209
+ }
210
+ `,
211
+ )
212
+ .set('X-Contember-Ref', eventKey)
213
+ .expect(200)
214
+ })
215
+
216
+ test('Content API: invalid schema error', async () => {
217
+ await executeGraphql(
218
+ gql`
219
+ mutation {
220
+ createFoo(data: { label: "graphql" }) {
221
+ ok
222
+ }
223
+ }
224
+ `,
225
+ )
226
+ .expect(400)
227
+ .expect(response => {
228
+ assert.deepStrictEqual(
229
+ response.body.errors[0].message,
230
+ 'Cannot query field "createFoo" on type "Mutation". Did you mean "createPost" or "createTag"?',
231
+ )
232
+ })
233
+ })
234
+
235
+ test('Tenant API: sign up, add to a project and check project access', async () => {
236
+ const email = `john+${Date.now()}@doe.com`
237
+ const signUpResponse = await executeGraphql(
238
+ gql`
239
+ mutation($email: String!) {
240
+ signUp(email: $email, password: "123456") {
241
+ ok
242
+ result {
243
+ person {
244
+ identity {
245
+ id
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ `,
252
+ {
253
+ path: '/tenant',
254
+ variables: { email },
255
+ },
256
+ ).expect(200)
257
+
258
+ const identityId = signUpResponse.body.data.signUp.result.person.identity.id
259
+
260
+ const authKey = await signIn(email, '123456')
261
+ await executeGraphql(
262
+ gql`
263
+ query {
264
+ listTag {
265
+ id
266
+ }
267
+ }
268
+ `,
269
+ { authorizationToken: authKey },
270
+ )
271
+ .expect(404)
272
+ .expect({ errors: [{ message: `Project ${projectSlug} NOT found`, code: 404 }] })
273
+
274
+ await executeGraphql(
275
+ gql`
276
+ mutation ($identity: String!, $projectSlug: String!) {
277
+ addProjectMember(identityId: $identity, projectSlug: $projectSlug, memberships: [{ role: "admin", variables: [] }]) {
278
+ ok
279
+ }
280
+ }
281
+ `,
282
+ { path: '/tenant', variables: { identity: identityId, projectSlug } },
283
+ )
284
+ .expect(200)
285
+ .expect(response => {
286
+ assert.deepStrictEqual(response.body.data, { addProjectMember: { ok: true } })
287
+ })
288
+
289
+ await executeGraphql(
290
+ gql`
291
+ query {
292
+ listAuthor {
293
+ id
294
+ }
295
+ }
296
+ `,
297
+ { authorizationToken: authKey },
298
+ )
299
+ .expect(response => {
300
+ assert.deepStrictEqual(response.body.data, { listAuthor: [] })
301
+ })
302
+ .expect(200)
303
+ })
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+
3
+ node ./packages/engine-server/dist/src/start.js &
4
+
5
+ max_retry=20
6
+ counter=0
7
+ until curl --silent --fail localhost:${CONTEMBER_PORT:-4000}/;
8
+ do
9
+ sleep 1
10
+ [[ counter -eq $max_retry ]] && echo "Failed!" && exit 1
11
+ echo "Trying again. Try #$counter"
12
+ ((counter++))
13
+ done
14
+
15
+ echo "Ok!"
16
+ exit 0
package/vite.config.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createViteConfig } from '../../scripts/vite/createViteConfig'
2
+
3
+ export default createViteConfig()
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=http.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../tests/cases/http.ts"],"names":[],"mappings":""}