@contember/engine-server 1.1.1 → 1.2.0-alpha.10

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 (173) hide show
  1. package/dist/src/MasterContainer.d.ts +27 -25
  2. package/dist/src/MasterContainer.d.ts.map +1 -1
  3. package/dist/src/MasterContainer.js +54 -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 -8
  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/sentry.d.ts +1 -1
  46. package/dist/src/utils/sentry.d.ts.map +1 -1
  47. package/dist/src/utils/sentry.js +1 -1
  48. package/dist/src/utils/sentry.js.map +1 -1
  49. package/dist/src/utils/serverStartup.d.ts +31 -0
  50. package/dist/src/utils/serverStartup.d.ts.map +1 -0
  51. package/dist/src/utils/serverStartup.js +42 -0
  52. package/dist/src/utils/serverStartup.js.map +1 -0
  53. package/dist/src/utils/serverTermination.d.ts +12 -0
  54. package/dist/src/utils/serverTermination.d.ts.map +1 -0
  55. package/dist/src/utils/serverTermination.js +26 -0
  56. package/dist/src/utils/serverTermination.js.map +1 -0
  57. package/dist/tests/e2e/acl-condition-variable.test.d.ts +2 -0
  58. package/dist/tests/e2e/acl-condition-variable.test.d.ts.map +1 -0
  59. package/dist/tests/e2e/acl-condition-variable.test.js +77 -0
  60. package/dist/tests/e2e/acl-condition-variable.test.js.map +1 -0
  61. package/dist/tests/e2e/assume-identity.test.d.ts +2 -0
  62. package/dist/tests/e2e/assume-identity.test.d.ts.map +1 -0
  63. package/dist/tests/e2e/assume-identity.test.js +109 -0
  64. package/dist/tests/e2e/assume-identity.test.js.map +1 -0
  65. package/dist/tests/e2e/assume-membership.test.d.ts +2 -0
  66. package/dist/tests/e2e/assume-membership.test.d.ts.map +1 -0
  67. package/dist/tests/e2e/assume-membership.test.js +58 -0
  68. package/dist/tests/e2e/assume-membership.test.js.map +1 -0
  69. package/dist/tests/e2e/content-api-basic.test.d.ts +2 -0
  70. package/dist/tests/e2e/content-api-basic.test.d.ts.map +1 -0
  71. package/dist/tests/e2e/content-api-basic.test.js +50 -0
  72. package/dist/tests/e2e/content-api-basic.test.js.map +1 -0
  73. package/dist/tests/e2e/content-invalid-schema.test.d.ts +2 -0
  74. package/dist/tests/e2e/content-invalid-schema.test.d.ts.map +1 -0
  75. package/dist/tests/e2e/content-invalid-schema.test.js +29 -0
  76. package/dist/tests/e2e/content-invalid-schema.test.js.map +1 -0
  77. package/dist/tests/e2e/homepage.test.d.ts +2 -0
  78. package/dist/tests/e2e/homepage.test.d.ts.map +1 -0
  79. package/dist/tests/e2e/homepage.test.js +15 -0
  80. package/dist/tests/e2e/homepage.test.js.map +1 -0
  81. package/dist/tests/e2e/tenant-member-access.test.d.ts +2 -0
  82. package/dist/tests/e2e/tenant-member-access.test.d.ts.map +1 -0
  83. package/dist/tests/e2e/tenant-member-access.test.js +43 -0
  84. package/dist/tests/e2e/tenant-member-access.test.js.map +1 -0
  85. package/dist/tests/e2e/x-contember-ref.test.d.ts +2 -0
  86. package/dist/tests/e2e/x-contember-ref.test.d.ts.map +1 -0
  87. package/dist/tests/e2e/x-contember-ref.test.js +63 -0
  88. package/dist/tests/e2e/x-contember-ref.test.js.map +1 -0
  89. package/dist/tests/src/requests.d.ts +5 -0
  90. package/dist/tests/src/requests.d.ts.map +1 -0
  91. package/dist/tests/src/requests.js +68 -0
  92. package/dist/tests/src/requests.js.map +1 -0
  93. package/dist/tests/src/tester.d.ts +19 -0
  94. package/dist/tests/src/tester.d.ts.map +1 -0
  95. package/dist/tests/src/tester.js +99 -0
  96. package/dist/tests/src/tester.js.map +1 -0
  97. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  98. package/esbuild.js +7 -14
  99. package/package.json +23 -23
  100. package/src/MasterContainer.ts +94 -110
  101. package/src/bootstrap/Initializer.ts +16 -8
  102. package/src/config/config.ts +7 -5
  103. package/src/config/configSchema.ts +30 -33
  104. package/src/config/configTemplate.ts +23 -0
  105. package/src/index.ts +15 -2
  106. package/src/project/ProjectContainer.ts +1 -1
  107. package/src/project/ProjectContainerResolver.ts +4 -17
  108. package/src/projectGroup/ProjectGroupContainer.ts +12 -3
  109. package/src/projectGroup/ProjectGroupResolver.ts +12 -0
  110. package/src/start.ts +18 -127
  111. package/src/utils/index.ts +0 -4
  112. package/src/utils/sentry.ts +1 -1
  113. package/src/utils/serverStartup.ts +42 -0
  114. package/src/utils/serverTermination.ts +20 -0
  115. package/tests/e2e/acl-condition-variable.test.ts +74 -0
  116. package/tests/e2e/assume-identity.test.ts +127 -0
  117. package/tests/e2e/assume-membership.test.ts +64 -0
  118. package/tests/e2e/content-api-basic.test.ts +51 -0
  119. package/tests/e2e/content-invalid-schema.test.ts +31 -0
  120. package/tests/e2e/homepage.test.ts +10 -0
  121. package/tests/e2e/tenant-member-access.test.ts +51 -0
  122. package/tests/e2e/x-contember-ref.test.ts +71 -0
  123. package/tests/src/requests.ts +81 -0
  124. package/tests/src/tester.ts +118 -0
  125. package/alpine.dockerfile +0 -30
  126. package/debian.dockerfile +0 -27
  127. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts +0 -5
  128. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts.map +0 -1
  129. package/dist/src/http/CollectHttpMetricsMiddelware.js +0 -36
  130. package/dist/src/http/CollectHttpMetricsMiddelware.js.map +0 -1
  131. package/dist/src/http/ShowMetricsMiddleware.d.ts +0 -4
  132. package/dist/src/http/ShowMetricsMiddleware.d.ts.map +0 -1
  133. package/dist/src/http/ShowMetricsMiddleware.js +0 -20
  134. package/dist/src/http/ShowMetricsMiddleware.js.map +0 -1
  135. package/dist/src/http/index.d.ts +0 -3
  136. package/dist/src/http/index.d.ts.map +0 -1
  137. package/dist/src/http/index.js +0 -19
  138. package/dist/src/http/index.js.map +0 -1
  139. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts +0 -12
  140. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts.map +0 -1
  141. package/dist/src/projectGroup/ProjectGroupContainerResolver.js +0 -36
  142. package/dist/src/projectGroup/ProjectGroupContainerResolver.js.map +0 -1
  143. package/dist/src/utils/cluster.d.ts +0 -9
  144. package/dist/src/utils/cluster.d.ts.map +0 -1
  145. package/dist/src/utils/cluster.js +0 -47
  146. package/dist/src/utils/cluster.js.map +0 -1
  147. package/dist/src/utils/dbMetrics.d.ts +0 -16
  148. package/dist/src/utils/dbMetrics.d.ts.map +0 -1
  149. package/dist/src/utils/dbMetrics.js +0 -98
  150. package/dist/src/utils/dbMetrics.js.map +0 -1
  151. package/dist/src/utils/timeout.d.ts +0 -2
  152. package/dist/src/utils/timeout.d.ts.map +0 -1
  153. package/dist/src/utils/timeout.js +0 -8
  154. package/dist/src/utils/timeout.js.map +0 -1
  155. package/dist/src/utils/tuple.d.ts +0 -2
  156. package/dist/src/utils/tuple.d.ts.map +0 -1
  157. package/dist/src/utils/tuple.js +0 -6
  158. package/dist/src/utils/tuple.js.map +0 -1
  159. package/dist/tests/e2e/http.test.d.ts +0 -2
  160. package/dist/tests/e2e/http.test.d.ts.map +0 -1
  161. package/dist/tests/e2e/http.test.js +0 -275
  162. package/dist/tests/e2e/http.test.js.map +0 -1
  163. package/jasmine.json +0 -7
  164. package/src/http/CollectHttpMetricsMiddelware.ts +0 -32
  165. package/src/http/ShowMetricsMiddleware.ts +0 -12
  166. package/src/http/index.ts +0 -2
  167. package/src/projectGroup/ProjectGroupContainerResolver.ts +0 -47
  168. package/src/utils/cluster.ts +0 -40
  169. package/src/utils/dbMetrics.ts +0 -111
  170. package/src/utils/timeout.ts +0 -4
  171. package/src/utils/tuple.ts +0 -1
  172. package/tests/e2e/http.test.ts +0 -303
  173. package/tslint.json +0 -14
@@ -21,20 +21,8 @@ export class ProjectContainerResolver {
21
21
  private readonly tenantConfig: TenantConfig,
22
22
  ) {}
23
23
 
24
- public async getAllProjectContainers(): Promise<ProjectContainer[]> {
25
- const projects = await this.projectManager.getProjects(this.tenantDatabase)
26
- return await Promise.all(
27
- projects.map(async it => {
28
- const container = await this.getProjectContainer(it.slug)
29
- if (!container) {
30
- throw new Error('should not happen')
31
- }
32
- return container
33
- }),
34
- )
35
- }
36
24
 
37
- public async getProjectContainer(slug: string, alias: boolean = false): Promise<ProjectContainer | undefined> {
25
+ public async getProjectContainer(slug: string, { alias = false, logger }: {alias?: boolean; logger?: Logger} = {}): Promise<ProjectContainer | undefined> {
38
26
  const realSlug = this.projectContainers.resolveAlias(slug)
39
27
  if (realSlug) {
40
28
  slug = realSlug
@@ -55,14 +43,14 @@ export class ProjectContainerResolver {
55
43
  if (!project) {
56
44
  return undefined
57
45
  }
58
- const container = await this.createProjectContainer(project)
46
+ const container = await this.createProjectContainer(project, logger)
59
47
  if (slug !== project.slug) {
60
48
  this.projectContainers.setAlias(project.slug, slug)
61
49
  }
62
50
  return container
63
51
  }
64
52
 
65
- public async createProjectContainer(project: ProjectWithSecrets): Promise<ProjectContainer> {
53
+ public async createProjectContainer(project: ProjectWithSecrets, logger?: Logger): Promise<ProjectContainer> {
66
54
  const projectConfig = this.projectConfigResolver(project.slug, project.config, project.secrets, this.tenantConfig)
67
55
  return (await this.projectContainers.fetchContainer(project.slug, async slug => {
68
56
  const projectContainer = this.projectContainerFactory.createContainer({
@@ -71,8 +59,7 @@ export class ProjectContainerResolver {
71
59
  await this.systemProjectInitializer.initialize(
72
60
  projectContainer.systemDatabaseContextFactory,
73
61
  projectContainer.project,
74
- // eslint-disable-next-line no-console
75
- new Logger(console.log),
62
+ logger ?? new Logger(() => null),
76
63
  )
77
64
  const cleanups = this.onCreate.map(it => it(projectContainer) || (() => null))
78
65
  return {
@@ -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'
@@ -28,7 +28,7 @@ export const logSentryError = (error: any, context: SentryErrorContext) => {
28
28
  scope.setTag('project', context.project)
29
29
  }
30
30
  scope.setTag('module', context.module)
31
- scope.setLevel(Sentry.Severity.Error)
31
+ scope.setLevel('error')
32
32
  scope.setUser({
33
33
  id: context.user,
34
34
  })
@@ -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,127 @@
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
+ import { AllowAllPermissionFactory } from '@contember/schema-utils'
6
+
7
+ namespace TagModel {
8
+ export class Tag {
9
+ label = def.stringColumn()
10
+ }
11
+ }
12
+
13
+
14
+ test('Content API: assume identity', async () => {
15
+ const tester = await createTester(createSchema(TagModel, schema => {
16
+ return {
17
+ ...schema,
18
+ acl: {
19
+ roles: {
20
+ test: {
21
+ entities: new AllowAllPermissionFactory().create(schema.model),
22
+ variables: {},
23
+ system: {
24
+ history: true,
25
+ assumeIdentity: true,
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
+ const assumedIdentityId = '65e049e7-4b22-487a-a93b-528b3127d967'
39
+ await tester(
40
+ gql`
41
+ mutation {
42
+ createTag(data: {label: "foo bar"}) {
43
+ ok
44
+ }
45
+ }
46
+ `,
47
+ { authorizationToken: authKey },
48
+ )
49
+ .set('X-Contember-assume-identity', assumedIdentityId)
50
+ .expect(200)
51
+
52
+
53
+ await tester(
54
+ gql`
55
+ query {
56
+ events {
57
+ tableName
58
+ identityId
59
+ }
60
+ }
61
+ `,
62
+ { authorizationToken: authKey, path: `/system/${tester.projectSlug}` },
63
+ )
64
+ .expect({ data: { events: [{ tableName: 'tag', identityId: assumedIdentityId }] } })
65
+ .expect(200)
66
+ })
67
+
68
+
69
+ test('Content API: assume identity is declined', async () => {
70
+ const tester = await createTester(createSchema(TagModel, schema => {
71
+ return {
72
+ ...schema,
73
+ acl: {
74
+ roles: {
75
+ foo: {
76
+ entities: {},
77
+ variables: {},
78
+ system: {
79
+ assumeIdentity: true,
80
+ },
81
+ },
82
+ test: {
83
+ entities: new AllowAllPermissionFactory().create(schema.model),
84
+ variables: {},
85
+ system: {
86
+ history: true,
87
+ },
88
+ },
89
+ },
90
+ },
91
+ }
92
+ }))
93
+
94
+ const email = `john+${Date.now()}@doe.com`
95
+ const identityId = await signUp(email)
96
+ const authKey = await signIn(email)
97
+ await addProjectMember(identityId, tester.projectSlug, { role: 'test', variables: [] })
98
+
99
+ const assumedIdentityId = '65e049e7-4b22-487a-a93b-528b3127d967'
100
+ await tester(
101
+ gql`
102
+ mutation {
103
+ createTag(data: {label: "foo bar"}) {
104
+ ok
105
+ }
106
+ }
107
+ `,
108
+ { authorizationToken: authKey },
109
+ )
110
+ .set('X-Contember-assume-identity', assumedIdentityId)
111
+ .expect(200)
112
+
113
+
114
+ await tester(
115
+ gql`
116
+ query {
117
+ events {
118
+ tableName
119
+ identityId
120
+ }
121
+ }
122
+ `,
123
+ { authorizationToken: authKey, path: `/system/${tester.projectSlug}` },
124
+ )
125
+ .expect({ data: { events: [{ tableName: 'tag', identityId }] } })
126
+ .expect(200)
127
+ })
@@ -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
+ })