@contember/engine-server 1.1.0-rc.1 → 1.2.0-alpha.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.
Files changed (163) hide show
  1. package/dist/src/MasterContainer.d.ts +12 -25
  2. package/dist/src/MasterContainer.d.ts.map +1 -1
  3. package/dist/src/MasterContainer.js +36 -80
  4. package/dist/src/MasterContainer.js.map +1 -1
  5. package/dist/src/bootstrap/Initializer.d.ts +3 -3
  6. package/dist/src/bootstrap/Initializer.d.ts.map +1 -1
  7. package/dist/src/bootstrap/Initializer.js +15 -6
  8. package/dist/src/bootstrap/Initializer.js.map +1 -1
  9. package/dist/src/config/config.d.ts +5 -4
  10. package/dist/src/config/config.d.ts.map +1 -1
  11. package/dist/src/config/config.js +2 -2
  12. package/dist/src/config/config.js.map +1 -1
  13. package/dist/src/config/configSchema.d.ts +45 -11
  14. package/dist/src/config/configSchema.d.ts.map +1 -1
  15. package/dist/src/config/configSchema.js +20 -18
  16. package/dist/src/config/configSchema.js.map +1 -1
  17. package/dist/src/config/configTemplate.d.ts.map +1 -1
  18. package/dist/src/config/configTemplate.js +23 -0
  19. package/dist/src/config/configTemplate.js.map +1 -1
  20. package/dist/src/index.d.ts +9 -3
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +23 -4
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/project/ProjectContainer.js +1 -1
  25. package/dist/src/project/ProjectContainer.js.map +1 -1
  26. package/dist/src/project/ProjectContainerResolver.d.ts +6 -3
  27. package/dist/src/project/ProjectContainerResolver.d.ts.map +1 -1
  28. package/dist/src/project/ProjectContainerResolver.js +4 -16
  29. package/dist/src/project/ProjectContainerResolver.js.map +1 -1
  30. package/dist/src/projectGroup/ProjectGroupContainer.d.ts +2 -1
  31. package/dist/src/projectGroup/ProjectGroupContainer.d.ts.map +1 -1
  32. package/dist/src/projectGroup/ProjectGroupContainer.js +8 -4
  33. package/dist/src/projectGroup/ProjectGroupContainer.js.map +1 -1
  34. package/dist/src/projectGroup/ProjectGroupResolver.d.ts +7 -0
  35. package/dist/src/projectGroup/ProjectGroupResolver.d.ts.map +1 -0
  36. package/dist/src/projectGroup/ProjectGroupResolver.js +13 -0
  37. package/dist/src/projectGroup/ProjectGroupResolver.js.map +1 -0
  38. package/dist/src/start.js +15 -112
  39. package/dist/src/start.js.map +1 -1
  40. package/dist/src/tsconfig.tsbuildinfo +1 -1
  41. package/dist/src/utils/index.d.ts +0 -4
  42. package/dist/src/utils/index.d.ts.map +1 -1
  43. package/dist/src/utils/index.js +0 -4
  44. package/dist/src/utils/index.js.map +1 -1
  45. package/dist/src/utils/serverStartup.d.ts +31 -0
  46. package/dist/src/utils/serverStartup.d.ts.map +1 -0
  47. package/dist/src/utils/serverStartup.js +42 -0
  48. package/dist/src/utils/serverStartup.js.map +1 -0
  49. package/dist/src/utils/serverTermination.d.ts +12 -0
  50. package/dist/src/utils/serverTermination.d.ts.map +1 -0
  51. package/dist/src/utils/serverTermination.js +26 -0
  52. package/dist/src/utils/serverTermination.js.map +1 -0
  53. package/dist/tests/e2e/acl-condition-variable.test.d.ts +2 -0
  54. package/dist/tests/e2e/acl-condition-variable.test.d.ts.map +1 -0
  55. package/dist/tests/e2e/acl-condition-variable.test.js +77 -0
  56. package/dist/tests/e2e/acl-condition-variable.test.js.map +1 -0
  57. package/dist/tests/e2e/assume-membership.test.d.ts +2 -0
  58. package/dist/tests/e2e/assume-membership.test.d.ts.map +1 -0
  59. package/dist/tests/e2e/assume-membership.test.js +58 -0
  60. package/dist/tests/e2e/assume-membership.test.js.map +1 -0
  61. package/dist/tests/e2e/content-api-basic.test.d.ts +2 -0
  62. package/dist/tests/e2e/content-api-basic.test.d.ts.map +1 -0
  63. package/dist/tests/e2e/content-api-basic.test.js +50 -0
  64. package/dist/tests/e2e/content-api-basic.test.js.map +1 -0
  65. package/dist/tests/e2e/content-invalid-schema.test.d.ts +2 -0
  66. package/dist/tests/e2e/content-invalid-schema.test.d.ts.map +1 -0
  67. package/dist/tests/e2e/content-invalid-schema.test.js +29 -0
  68. package/dist/tests/e2e/content-invalid-schema.test.js.map +1 -0
  69. package/dist/tests/e2e/homepage.test.d.ts +2 -0
  70. package/dist/tests/e2e/homepage.test.d.ts.map +1 -0
  71. package/dist/tests/e2e/homepage.test.js +15 -0
  72. package/dist/tests/e2e/homepage.test.js.map +1 -0
  73. package/dist/tests/e2e/tenant-member-access.test.d.ts +2 -0
  74. package/dist/tests/e2e/tenant-member-access.test.d.ts.map +1 -0
  75. package/dist/tests/e2e/tenant-member-access.test.js +43 -0
  76. package/dist/tests/e2e/tenant-member-access.test.js.map +1 -0
  77. package/dist/tests/e2e/x-contember-ref.test.d.ts +2 -0
  78. package/dist/tests/e2e/x-contember-ref.test.d.ts.map +1 -0
  79. package/dist/tests/e2e/x-contember-ref.test.js +63 -0
  80. package/dist/tests/e2e/x-contember-ref.test.js.map +1 -0
  81. package/dist/tests/src/requests.d.ts +5 -0
  82. package/dist/tests/src/requests.d.ts.map +1 -0
  83. package/dist/tests/src/requests.js +59 -0
  84. package/dist/tests/src/requests.js.map +1 -0
  85. package/dist/tests/src/tester.d.ts +19 -0
  86. package/dist/tests/src/tester.d.ts.map +1 -0
  87. package/dist/tests/src/tester.js +99 -0
  88. package/dist/tests/src/tester.js.map +1 -0
  89. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  90. package/esbuild.js +7 -14
  91. package/package.json +20 -20
  92. package/src/MasterContainer.ts +58 -110
  93. package/src/bootstrap/Initializer.ts +16 -6
  94. package/src/config/config.ts +7 -5
  95. package/src/config/configSchema.ts +30 -33
  96. package/src/config/configTemplate.ts +23 -0
  97. package/src/index.ts +15 -2
  98. package/src/project/ProjectContainer.ts +1 -1
  99. package/src/project/ProjectContainerResolver.ts +4 -17
  100. package/src/projectGroup/ProjectGroupContainer.ts +12 -3
  101. package/src/projectGroup/ProjectGroupResolver.ts +12 -0
  102. package/src/start.ts +18 -127
  103. package/src/utils/index.ts +0 -4
  104. package/src/utils/serverStartup.ts +42 -0
  105. package/src/utils/serverTermination.ts +20 -0
  106. package/tests/e2e/acl-condition-variable.test.ts +74 -0
  107. package/tests/e2e/assume-membership.test.ts +64 -0
  108. package/tests/e2e/content-api-basic.test.ts +51 -0
  109. package/tests/e2e/content-invalid-schema.test.ts +31 -0
  110. package/tests/e2e/homepage.test.ts +10 -0
  111. package/tests/e2e/tenant-member-access.test.ts +51 -0
  112. package/tests/e2e/x-contember-ref.test.ts +71 -0
  113. package/tests/src/requests.ts +72 -0
  114. package/tests/src/tester.ts +118 -0
  115. package/alpine.dockerfile +0 -30
  116. package/debian.dockerfile +0 -27
  117. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts +0 -5
  118. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts.map +0 -1
  119. package/dist/src/http/CollectHttpMetricsMiddelware.js +0 -36
  120. package/dist/src/http/CollectHttpMetricsMiddelware.js.map +0 -1
  121. package/dist/src/http/ShowMetricsMiddleware.d.ts +0 -4
  122. package/dist/src/http/ShowMetricsMiddleware.d.ts.map +0 -1
  123. package/dist/src/http/ShowMetricsMiddleware.js +0 -20
  124. package/dist/src/http/ShowMetricsMiddleware.js.map +0 -1
  125. package/dist/src/http/index.d.ts +0 -3
  126. package/dist/src/http/index.d.ts.map +0 -1
  127. package/dist/src/http/index.js +0 -19
  128. package/dist/src/http/index.js.map +0 -1
  129. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts +0 -12
  130. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts.map +0 -1
  131. package/dist/src/projectGroup/ProjectGroupContainerResolver.js +0 -36
  132. package/dist/src/projectGroup/ProjectGroupContainerResolver.js.map +0 -1
  133. package/dist/src/utils/cluster.d.ts +0 -9
  134. package/dist/src/utils/cluster.d.ts.map +0 -1
  135. package/dist/src/utils/cluster.js +0 -47
  136. package/dist/src/utils/cluster.js.map +0 -1
  137. package/dist/src/utils/dbMetrics.d.ts +0 -16
  138. package/dist/src/utils/dbMetrics.d.ts.map +0 -1
  139. package/dist/src/utils/dbMetrics.js +0 -98
  140. package/dist/src/utils/dbMetrics.js.map +0 -1
  141. package/dist/src/utils/timeout.d.ts +0 -2
  142. package/dist/src/utils/timeout.d.ts.map +0 -1
  143. package/dist/src/utils/timeout.js +0 -8
  144. package/dist/src/utils/timeout.js.map +0 -1
  145. package/dist/src/utils/tuple.d.ts +0 -2
  146. package/dist/src/utils/tuple.d.ts.map +0 -1
  147. package/dist/src/utils/tuple.js +0 -6
  148. package/dist/src/utils/tuple.js.map +0 -1
  149. package/dist/tests/e2e/http.test.d.ts +0 -2
  150. package/dist/tests/e2e/http.test.d.ts.map +0 -1
  151. package/dist/tests/e2e/http.test.js +0 -275
  152. package/dist/tests/e2e/http.test.js.map +0 -1
  153. package/jasmine.json +0 -7
  154. package/src/http/CollectHttpMetricsMiddelware.ts +0 -32
  155. package/src/http/ShowMetricsMiddleware.ts +0 -12
  156. package/src/http/index.ts +0 -2
  157. package/src/projectGroup/ProjectGroupContainerResolver.ts +0 -47
  158. package/src/utils/cluster.ts +0 -40
  159. package/src/utils/dbMetrics.ts +0 -111
  160. package/src/utils/timeout.ts +0 -4
  161. package/src/utils/tuple.ts +0 -1
  162. package/tests/e2e/http.test.ts +0 -303
  163. package/tslint.json +0 -14
@@ -6,6 +6,8 @@ import {
6
6
  Authenticator,
7
7
  CryptoWrapper,
8
8
  ProjectGroupContainer,
9
+ ProjectMembershipFetcher,
10
+ ProjectMembershipResolver,
9
11
  Providers,
10
12
  SystemGraphQLHandlerFactory,
11
13
  TenantGraphQLHandlerFactory,
@@ -30,6 +32,7 @@ interface ProjectGroupContainerFactoryArgs
30
32
 
31
33
  export class ProjectGroupContainerFactory {
32
34
  constructor(
35
+ private readonly debug: boolean,
33
36
  private readonly providers: Providers,
34
37
  private readonly systemContainerFactory: SystemContainerFactory,
35
38
  private readonly tenantContainerFactory: TenantContainerFactory,
@@ -45,13 +48,15 @@ export class ProjectGroupContainerFactory {
45
48
  slug)
46
49
  .addService('providers', () =>
47
50
  this.providers)
48
- .addService('tenantConnection', (): Connection.ConnectionType =>
49
- new Connection(config.db, {}))
51
+ .addService('tenantDbCredentials', () =>
52
+ config.db)
53
+ .addService('tenantConnection', ({ tenantDbCredentials }): Connection.ConnectionType =>
54
+ Connection.create(tenantDbCredentials, {}))
50
55
  .addService('projectSchemaResolver', () =>
51
56
  new ProjectSchemaResolverProxy())
52
57
  .addService('projectInitializer', () =>
53
58
  new ProjectInitializerProxy())
54
- .addService('tenantContainer', ({ tenantConnection, projectSchemaResolver, projectInitializer }) => {
59
+ .addService('tenantContainer', ({ tenantConnection, tenantDbCredentials, projectSchemaResolver, projectInitializer }) => {
55
60
  const encryptionKey = config.secrets.encryptionKey
56
61
  ? createSecretKey(Buffer.from(config.secrets.encryptionKey, 'hex'))
57
62
  : undefined
@@ -59,6 +64,7 @@ export class ProjectGroupContainerFactory {
59
64
  const cryptoWrapper = new CryptoWrapper(encryptionKey)
60
65
  return this.tenantContainerFactory.create({
61
66
  connection: tenantConnection,
67
+ dbCredentials: tenantDbCredentials,
62
68
  mailOptions: config.mailer,
63
69
  tenantCredentials: config.credentials,
64
70
  projectInitializer,
@@ -103,6 +109,8 @@ export class ProjectGroupContainerFactory {
103
109
  this.systemGraphQLHandlerFactory.create(systemContainer.systemResolversFactory))
104
110
  .addService('authenticator', ({ tenantDatabase, tenantContainer }) =>
105
111
  new Authenticator(tenantDatabase, tenantContainer.apiKeyManager))
112
+ .addService('projectMembershipResolver', ({ tenantContainer }) =>
113
+ new ProjectMembershipResolver(this.debug, new ProjectMembershipFetcher(tenantContainer.projectMemberManager, tenantContainer.databaseContext)))
106
114
  .build()
107
115
  .pick(
108
116
  'projectContainerResolver',
@@ -113,6 +121,7 @@ export class ProjectGroupContainerFactory {
113
121
  'tenantContainer',
114
122
  'tenantDatabase',
115
123
  'authenticator',
124
+ 'projectMembershipResolver',
116
125
  'tenantGraphQLHandler',
117
126
  'slug',
118
127
  )
@@ -0,0 +1,12 @@
1
+ import { ProjectGroupContainer, ProjectGroupResolver as ProjectGroupResolverInterface } from '@contember/engine-http'
2
+
3
+ export class ProjectGroupResolver implements ProjectGroupResolverInterface {
4
+ constructor(
5
+ private readonly projectGroupContainer: ProjectGroupContainer,
6
+ ) {
7
+ }
8
+
9
+ resolveContainer(): Promise<ProjectGroupContainer> {
10
+ return Promise.resolve(this.projectGroupContainer)
11
+ }
12
+ }
package/src/start.ts CHANGED
@@ -1,85 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createContainer, readConfig } from './index'
4
- import { Server } from 'net'
3
+ import { createContainer, listenOnProcessTermination, serverConfigSchema, TerminationJob } from './index'
5
4
  import loadPlugins from './loadPlugins'
6
- import { ConfigProcessor } from '@contember/engine-plugins'
7
- import { join } from 'path'
8
- import os from 'os'
9
- import cluster from 'cluster'
10
- import { getClusterProcessType, initSentry, notifyWorkerStarted, timeout, waitForWorker } from './utils'
11
- import { ConfigSource } from './config/config'
5
+ import { initSentry } from './utils'
12
6
 
13
- const createServerTerminator = (): Server[] => {
14
- const signals = [
15
- ['SIGHUP', 1],
16
- ['SIGINT', 2],
17
- ['SIGTERM', 15],
18
- ] as const
19
7
 
20
- let servers: Server[] = []
21
- for (const [signal, code] of signals) {
22
- process.on(signal, async () => {
23
- // eslint-disable-next-line no-console
24
- console.log(`process received a ${signal} signal`)
25
- for (const server of servers) {
26
- await new Promise(resolve => server.close(() => resolve(null)))
27
- }
28
- process.exit(128 + code)
29
- })
30
- }
31
- return servers
32
- }
8
+ import { getServerVersion, isDebugMode, printStartInfo, resolveServerConfig } from './utils/serverStartup'
33
9
 
34
- ;(async () => {
10
+ (async () => {
35
11
  if (process.env.NODE_HEAPDUMP === 'true') {
36
12
  // eslint-disable-next-line no-console
37
13
  console.log('Initializing heapdump')
38
14
  await import('heapdump')
39
15
  }
40
- const isDebug = process.env.NODE_ENV === 'development'
41
- const packageJsonFile = process.env.CONTEMBER_PACKAGE_JSON || join(__dirname, '../../package.json')
42
- // eslint-disable-next-line @typescript-eslint/no-var-requires
43
- const version = require(packageJsonFile).version
44
-
45
- if (cluster.isMaster) {
46
- // eslint-disable-next-line no-console
47
- console.log(`Starting Contember ${version}`)
48
- // eslint-disable-next-line no-console
49
- console.log(`NODE_ENV is set to ${process.env.NODE_ENV}`)
50
- if (isDebug) {
51
- // eslint-disable-next-line no-console
52
- console.log('Starting Contember in debug mode')
53
- // eslint-disable-next-line no-console
54
- console.log('NEVER USE debug mode in production environment')
55
- }
56
- }
16
+ const terminationJobs: TerminationJob[] = []
17
+ listenOnProcessTermination(terminationJobs)
57
18
 
19
+ const isDebug = isDebugMode()
20
+ const version = getServerVersion()
21
+ printStartInfo({ version, isDebug })
58
22
  const plugins = await loadPlugins()
59
- const configProcessors = plugins
60
- .map(it => (it.getConfigProcessor ? it.getConfigProcessor() : null))
61
- .filter((it): it is ConfigProcessor => it !== null)
62
-
63
- const configSources: ConfigSource[] = []
64
- for (const configType of ['file', 'yaml', 'json'] as const) {
65
- const envValue = process.env['CONTEMBER_CONFIG_' + configType.toUpperCase()]
66
- if (envValue) {
67
- configSources.push({ type: configType, data: envValue })
68
- }
69
- }
70
-
71
- const { serverConfig, projectConfigResolver, tenantConfigResolver } = await readConfig(configSources, configProcessors)
72
-
73
- if (process.argv[2] === 'validate') {
74
- process.exit(0)
75
- }
76
- 'sentry' in serverConfig.logging && initSentry(serverConfig.logging.sentry?.dsn)
77
- const workerConfig = serverConfig.workerCount || 1
23
+ const { serverConfig, projectConfigResolver, tenantConfigResolver } = await resolveServerConfig({ plugins, serverConfigSchema })
78
24
 
79
- const workerCount = workerConfig === 'auto' ? os.cpus().length : Number(workerConfig)
80
- const isClusterMode = workerCount > 1
81
-
82
- const processType = getClusterProcessType(isClusterMode)
25
+ initSentry(serverConfig.logging.sentry?.dsn)
83
26
 
84
27
  const container = createContainer({
85
28
  debugMode: isDebug,
@@ -87,70 +30,18 @@ const createServerTerminator = (): Server[] => {
87
30
  projectConfigResolver,
88
31
  tenantConfigResolver,
89
32
  plugins,
90
- processType,
91
33
  version,
92
34
  })
93
35
 
94
- let initializedProjects: string[] = []
95
- if (cluster.isMaster) {
96
- const monitoringPort = serverConfig.monitoringPort
97
- const terminator = createServerTerminator()
98
- terminator.push(
99
- container.monitoringKoa.listen(monitoringPort, () => {
100
- // eslint-disable-next-line no-console
101
- console.log(`Monitoring running on http://localhost:${monitoringPort}`)
102
- }),
103
- )
104
- if (!serverConfig.projectGroup) {
105
- initializedProjects = await container.initializer.initialize()
106
- }
107
- }
36
+ const initializedProjects: string[] = await container.initializer.initialize()
108
37
 
109
- const port = serverConfig.port
110
- const printStarted = () => {
111
- if (serverConfig.projectGroup) {
112
- // eslint-disable-next-line no-console
113
- console.log('Contember Cloud running')
114
- } else {
115
- // eslint-disable-next-line no-console
116
- console.log(`Contember API running on http://localhost:${port}`)
117
- // eslint-disable-next-line no-console
118
- console.log(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
119
- }
120
- }
121
-
122
- if (isClusterMode && cluster.isMaster) {
38
+ const httpServer = container.koa.listen(serverConfig.port, () => {
123
39
  // eslint-disable-next-line no-console
124
- console.log(`Master ${process.pid} is running`)
125
- for (let i = 0; i < workerCount; i++) {
126
- cluster.fork()
127
- await waitForWorker(15000)
128
- }
129
- printStarted()
130
-
131
- cluster.on('exit', async (worker, code, signal) => {
132
- // eslint-disable-next-line no-console
133
- console.log(`Worker ${worker.process.pid} died with signal ${signal}, restarting`)
134
- await timeout(2000)
135
- cluster.fork()
136
- })
137
- } else {
138
- if (isClusterMode) {
139
- // eslint-disable-next-line no-console
140
- console.log(`Starting worker ${process.pid}`)
141
- }
142
- const serverTerminator = createServerTerminator()
143
-
144
- serverTerminator.push(
145
- container.koa.listen(port, () => {
146
- if (!isClusterMode) {
147
- printStarted()
148
- } else {
149
- notifyWorkerStarted()
150
- }
151
- }),
152
- )
153
- }
40
+ console.log(`Contember API running on http://localhost:${(serverConfig.port)}`)
41
+ // eslint-disable-next-line no-console
42
+ console.log(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
43
+ })
44
+ terminationJobs.push(() => new Promise<any>(resolve => httpServer.close(resolve)))
154
45
  })().catch(e => {
155
46
  // eslint-disable-next-line no-console
156
47
  console.log(e)
@@ -1,6 +1,2 @@
1
- export * from './cluster'
2
- export * from './dbMetrics'
3
1
  export * from './sentry'
4
2
  export * from './strings'
5
- export * from './timeout'
6
- export * from './tuple'
@@ -0,0 +1,42 @@
1
+ import { join } from 'path'
2
+ import { ConfigProcessor, Plugin } from '@contember/engine-plugins'
3
+ import { ConfigSource, readConfig, ServerConfig } from '../config/config'
4
+ import { Type } from '@contember/typesafe'
5
+
6
+ export const getServerVersion = (): string => {
7
+ const packageJsonFile = process.env.CONTEMBER_PACKAGE_JSON || join(__dirname, '../../../package.json')
8
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
9
+ const version = require(packageJsonFile).version
10
+ return version
11
+ }
12
+
13
+ export const isDebugMode = (): boolean => process.env.NODE_ENV === 'development'
14
+
15
+ export const printStartInfo = ({ version, isDebug }: { version: string; isDebug: boolean }) => {
16
+ // eslint-disable-next-line no-console
17
+ console.log(`Starting Contember ${version}`)
18
+ // eslint-disable-next-line no-console
19
+ console.log(`NODE_ENV is set to ${process.env.NODE_ENV}`)
20
+ if (isDebug) {
21
+ // eslint-disable-next-line no-console
22
+ console.log('Starting Contember in debug mode')
23
+ // eslint-disable-next-line no-console
24
+ console.log('NEVER USE debug mode in production environment')
25
+ }
26
+ }
27
+
28
+ export const resolveServerConfig = async <T extends ServerConfig>({ plugins, serverConfigSchema }: { plugins: Plugin[]; serverConfigSchema: Type<T> }) => {
29
+ const configProcessors = plugins
30
+ .map(it => (it.getConfigProcessor ? it.getConfigProcessor() : null))
31
+ .filter((it): it is ConfigProcessor => it !== null)
32
+
33
+ const configSources: ConfigSource[] = []
34
+ for (const configType of ['file', 'yaml', 'json'] as const) {
35
+ const envValue = process.env['CONTEMBER_CONFIG_' + configType.toUpperCase()]
36
+ if (envValue) {
37
+ configSources.push({ type: configType, data: envValue })
38
+ }
39
+ }
40
+
41
+ return await readConfig(configSources, configProcessors, serverConfigSchema)
42
+ }
@@ -0,0 +1,20 @@
1
+ import cluster from 'cluster'
2
+
3
+ const signals = {
4
+ SIGHUP: 1,
5
+ SIGINT: 2,
6
+ SIGTERM: 15,
7
+ }
8
+ export type TerminationJob = (args: { signal: keyof typeof signals; code: number }) => Promise<void>
9
+ export const listenOnProcessTermination = (jobs: TerminationJob[]) => {
10
+ for (const [signal, code] of Object.entries(signals)) {
11
+ process.on(signal, async () => {
12
+ // eslint-disable-next-line no-console
13
+ console.log(`Process ${process.pid} received a ${signal} signal, executing ${jobs.length} termination jobs`)
14
+ await Promise.allSettled(jobs.map(it => it({ signal: signal as keyof typeof signals, code })))
15
+ // eslint-disable-next-line no-console
16
+ console.log(cluster.isMaster ? `All terminated, exiting` : 'All terminated, exiting a worker')
17
+ process.exit(128 + code)
18
+ })
19
+ }
20
+ }
@@ -0,0 +1,74 @@
1
+ import { assert, test } from 'vitest'
2
+ import { createTester, gql } from '../src/tester'
3
+ import { createSchema, SchemaDefinition as def, AclDefinition as acl } from '@contember/schema-definition'
4
+ import { addProjectMember, signIn, signUp } from '../src/requests'
5
+
6
+ namespace ArticleModel {
7
+ export const readerRole = acl.createRole('reader')
8
+ export const subscriptionVariable = acl.createConditionVariable('subscription', readerRole)
9
+
10
+ @acl.allow(readerRole, {
11
+ read: true,
12
+ when: {
13
+ publishedAt: subscriptionVariable,
14
+ },
15
+ })
16
+ export class Article {
17
+ publishedAt = def.dateTimeColumn().notNull()
18
+ }
19
+ }
20
+
21
+ test('ACL: condition variable', async () => {
22
+ const tester = await createTester(createSchema(ArticleModel))
23
+
24
+ await tester(
25
+ gql`
26
+ mutation {
27
+ articleA: createArticle(data: { publishedAt: "2022-02-01" }) {
28
+ ok
29
+ }
30
+ articleB: createArticle(data: { publishedAt: "2021-12-01" }) {
31
+ ok
32
+ }
33
+ }
34
+ `,
35
+ )
36
+ .expect(response => {
37
+ assert.deepStrictEqual(response.body.data, {
38
+ articleA: {
39
+ ok: true,
40
+ },
41
+ articleB: {
42
+ ok: true,
43
+ },
44
+ })
45
+ })
46
+ .expect(200)
47
+
48
+
49
+ const email = `john+${Date.now()}@doe.com`
50
+ const identityId = await signUp(email)
51
+ const authKey = await signIn(email)
52
+ await addProjectMember(identityId, tester.projectSlug, { role: 'reader', variables: [{ name: 'subscription', values: [JSON.stringify({ gte: '2022-01-01' })] }] })
53
+
54
+ await tester(
55
+ gql`
56
+ query {
57
+ listArticle {
58
+ publishedAt
59
+ }
60
+ }
61
+ `,
62
+ { authorizationToken: authKey },
63
+ )
64
+ .expect(response => {
65
+ assert.deepStrictEqual(response.body.data, {
66
+ listArticle: [
67
+ {
68
+ publishedAt: '2022-02-01T00:00:00.000Z',
69
+ },
70
+ ],
71
+ })
72
+ })
73
+ .expect(200)
74
+ })
@@ -0,0 +1,64 @@
1
+ import { test } from 'vitest'
2
+ import { createTester, gql } from '../src/tester'
3
+ import { addProjectMember, signIn, signUp } from '../src/requests'
4
+ import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
5
+
6
+ namespace TagModel {
7
+ export class Tag {
8
+ label = def.stringColumn()
9
+ }
10
+ }
11
+
12
+
13
+ test('Content API: assume membership', async () => {
14
+ const tester = await createTester(createSchema(TagModel, schema => {
15
+ return {
16
+ ...schema,
17
+ acl: {
18
+ roles: {
19
+ test: {
20
+ entities: {},
21
+ variables: {},
22
+ content: {
23
+ assumeMembership: {
24
+ admin: {},
25
+ },
26
+ },
27
+ },
28
+ },
29
+ },
30
+ }
31
+ }))
32
+
33
+ const email = `john+${Date.now()}@doe.com`
34
+ const identityId = await signUp(email)
35
+ const authKey = await signIn(email)
36
+ await addProjectMember(identityId, tester.projectSlug, { role: 'test', variables: [] })
37
+
38
+ await tester(
39
+ gql`
40
+ query {
41
+ listTag {
42
+ id
43
+ }
44
+ }
45
+ `,
46
+ { authorizationToken: authKey },
47
+ )
48
+ .expect(400)
49
+
50
+
51
+ await tester(
52
+ gql`
53
+ query {
54
+ listTag {
55
+ id
56
+ }
57
+ }
58
+ `,
59
+ { authorizationToken: authKey },
60
+ )
61
+ .set('X-Contember-assume-membership', JSON.stringify({ memberships: [{ role: 'admin', variables: [] }] }))
62
+ .expect({ data: { listTag: [] } })
63
+ .expect(200)
64
+ })
@@ -0,0 +1,51 @@
1
+ import { assert, test } from 'vitest'
2
+ import { createTester, gql } from '../src/tester'
3
+ import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
4
+
5
+ namespace TagModel {
6
+ export class Tag {
7
+ label = def.stringColumn()
8
+ }
9
+ }
10
+
11
+ test('Content API: create & read tag', async () => {
12
+ const tester = await createTester(createSchema(TagModel))
13
+
14
+ await tester(
15
+ gql`
16
+ mutation {
17
+ createTag(data: { label: "graphql" }) {
18
+ ok
19
+ }
20
+ }
21
+ `,
22
+ )
23
+ .expect(response => {
24
+ assert.deepStrictEqual(response.body.data, {
25
+ createTag: {
26
+ ok: true,
27
+ },
28
+ })
29
+ })
30
+ .expect(200)
31
+
32
+ await tester(
33
+ gql`
34
+ query {
35
+ listTag(filter: { label: { eq: "graphql" } }) {
36
+ label
37
+ }
38
+ }
39
+ `,
40
+ )
41
+ .expect(response => {
42
+ assert.deepStrictEqual(response.body.data, {
43
+ listTag: [
44
+ {
45
+ label: 'graphql',
46
+ },
47
+ ],
48
+ })
49
+ })
50
+ .expect(200)
51
+ })
@@ -0,0 +1,31 @@
1
+ import { assert, test } from 'vitest'
2
+ import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
3
+ import { createTester, gql } from '../src/tester'
4
+
5
+
6
+ namespace TagModel {
7
+ export class Tag {
8
+ label = def.stringColumn()
9
+ }
10
+ }
11
+
12
+ test('Content API: invalid schema error', async () => {
13
+ const tester = await createTester(createSchema(TagModel))
14
+
15
+ await tester(
16
+ gql`
17
+ mutation {
18
+ createFoo(data: { label: "graphql" }) {
19
+ ok
20
+ }
21
+ }
22
+ `,
23
+ )
24
+ .expect(400)
25
+ .expect(response => {
26
+ assert.deepStrictEqual(
27
+ response.body.errors[0].message,
28
+ 'Cannot query field "createFoo" on type "Mutation". Did you mean "createTag"?',
29
+ )
30
+ })
31
+ })
@@ -0,0 +1,10 @@
1
+ import { test } from 'vitest'
2
+ import supertest from 'supertest'
3
+ import { apiUrl } from '../src/tester'
4
+
5
+ test('show homepage', async () => {
6
+ await supertest(apiUrl)
7
+ .get('/')
8
+ .expect(200)
9
+ .expect('App is running')
10
+ })
@@ -0,0 +1,51 @@
1
+ import { assert, test } from 'vitest'
2
+ import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
3
+ import { createTester, gql } from '../src/tester'
4
+ import { addProjectMember, signIn, signUp } from '../src/requests'
5
+
6
+
7
+ namespace TagModel {
8
+ export class Tag {
9
+ label = def.stringColumn()
10
+ }
11
+ }
12
+
13
+
14
+ test('Tenant API: sign up, add to a project and check project access', async () => {
15
+ const tester = await createTester(createSchema(TagModel))
16
+
17
+ const email = `john+${Date.now()}@doe.com`
18
+ const identityId = await signUp(email)
19
+ const authKey = await signIn(email)
20
+
21
+ await tester(
22
+ gql`
23
+ query {
24
+ listTag {
25
+ id
26
+ }
27
+ }
28
+ `,
29
+ { authorizationToken: authKey },
30
+ )
31
+ .expect(404)
32
+ .expect({ errors: [{ message: `Project ${tester.projectSlug} NOT found`, code: 404 }] })
33
+
34
+
35
+ await addProjectMember(identityId, tester.projectSlug)
36
+
37
+ await tester(
38
+ gql`
39
+ query {
40
+ listTag {
41
+ id
42
+ }
43
+ }
44
+ `,
45
+ { authorizationToken: authKey },
46
+ )
47
+ .expect(response => {
48
+ assert.deepStrictEqual(response.body.data, { listTag: [] })
49
+ })
50
+ .expect(200)
51
+ })
@@ -0,0 +1,71 @@
1
+ import { assert, test } from 'vitest'
2
+ import { createTester, gql } from '../src/tester'
3
+ import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
4
+
5
+ namespace TagModel {
6
+ export class Tag {
7
+ label = def.stringColumn()
8
+ }
9
+ }
10
+
11
+
12
+ test('Content API: X-Contember-Ref header', async () => {
13
+
14
+ const tester = await createTester(createSchema(TagModel))
15
+
16
+ await tester(
17
+ gql`
18
+ mutation {
19
+ createTag(data: { label: "typescript" }) {
20
+ ok
21
+ }
22
+ }
23
+ `,
24
+ ).expect(200)
25
+
26
+ const response = await tester(
27
+ gql`
28
+ query {
29
+ listTag(filter: { label: { eq: "typescript" } }) {
30
+ label
31
+ }
32
+ }
33
+ `,
34
+ )
35
+ .set('X-Contember-Ref', 'None')
36
+ .expect(response => {
37
+ assert.deepStrictEqual(response.body.data, {
38
+ listTag: [
39
+ {
40
+ label: 'typescript',
41
+ },
42
+ ],
43
+ })
44
+ })
45
+ .expect(200)
46
+ const eventKey = response.get('X-Contember-Ref')
47
+ await tester(
48
+ gql`
49
+ query {
50
+ listTag {
51
+ label
52
+ }
53
+ }
54
+ `,
55
+ )
56
+ .set('X-Contember-Ref', eventKey)
57
+ .expect(304)
58
+
59
+ // ignored for mutation
60
+ await tester(
61
+ gql`
62
+ mutation {
63
+ createTag(data: { label: "typescript" }) {
64
+ ok
65
+ }
66
+ }
67
+ `,
68
+ )
69
+ .set('X-Contember-Ref', eventKey)
70
+ .expect(200)
71
+ })