@contember/engine-server 1.1.0-rc.0 → 1.2.0-alpha.1
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/MasterContainer.d.ts +12 -25
- package/dist/src/MasterContainer.d.ts.map +1 -1
- package/dist/src/MasterContainer.js +36 -80
- package/dist/src/MasterContainer.js.map +1 -1
- package/dist/src/bootstrap/Initializer.d.ts +3 -3
- package/dist/src/bootstrap/Initializer.d.ts.map +1 -1
- package/dist/src/bootstrap/Initializer.js +15 -6
- package/dist/src/bootstrap/Initializer.js.map +1 -1
- package/dist/src/config/config.d.ts +5 -4
- package/dist/src/config/config.d.ts.map +1 -1
- package/dist/src/config/config.js +2 -2
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/configSchema.d.ts +45 -11
- package/dist/src/config/configSchema.d.ts.map +1 -1
- package/dist/src/config/configSchema.js +20 -18
- package/dist/src/config/configSchema.js.map +1 -1
- package/dist/src/config/configTemplate.d.ts.map +1 -1
- package/dist/src/config/configTemplate.js +23 -0
- package/dist/src/config/configTemplate.js.map +1 -1
- package/dist/src/index.d.ts +9 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +23 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/project/ProjectContainer.js +1 -1
- package/dist/src/project/ProjectContainer.js.map +1 -1
- package/dist/src/project/ProjectContainerResolver.d.ts +6 -3
- package/dist/src/project/ProjectContainerResolver.d.ts.map +1 -1
- package/dist/src/project/ProjectContainerResolver.js +4 -16
- package/dist/src/project/ProjectContainerResolver.js.map +1 -1
- package/dist/src/projectGroup/ProjectGroupContainer.d.ts +2 -1
- package/dist/src/projectGroup/ProjectGroupContainer.d.ts.map +1 -1
- package/dist/src/projectGroup/ProjectGroupContainer.js +8 -4
- package/dist/src/projectGroup/ProjectGroupContainer.js.map +1 -1
- package/dist/src/projectGroup/ProjectGroupResolver.d.ts +7 -0
- package/dist/src/projectGroup/ProjectGroupResolver.d.ts.map +1 -0
- package/dist/src/projectGroup/ProjectGroupResolver.js +13 -0
- package/dist/src/projectGroup/ProjectGroupResolver.js.map +1 -0
- package/dist/src/start.js +15 -112
- package/dist/src/start.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/utils/index.d.ts +0 -4
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +0 -4
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/serverStartup.d.ts +31 -0
- package/dist/src/utils/serverStartup.d.ts.map +1 -0
- package/dist/src/utils/serverStartup.js +42 -0
- package/dist/src/utils/serverStartup.js.map +1 -0
- package/dist/src/utils/serverTermination.d.ts +12 -0
- package/dist/src/utils/serverTermination.d.ts.map +1 -0
- package/dist/src/utils/serverTermination.js +26 -0
- package/dist/src/utils/serverTermination.js.map +1 -0
- package/dist/tests/e2e/http.test.js +74 -157
- package/dist/tests/e2e/http.test.js.map +1 -1
- package/dist/tests/e2e/requests.d.ts +4 -0
- package/dist/tests/e2e/requests.d.ts.map +1 -0
- package/dist/tests/e2e/requests.js +59 -0
- package/dist/tests/e2e/requests.js.map +1 -0
- package/dist/tests/e2e/tester.d.ts +19 -0
- package/dist/tests/e2e/tester.d.ts.map +1 -0
- package/dist/tests/e2e/tester.js +99 -0
- package/dist/tests/e2e/tester.js.map +1 -0
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/esbuild.js +7 -14
- package/package.json +20 -20
- package/src/MasterContainer.ts +58 -110
- package/src/bootstrap/Initializer.ts +16 -6
- package/src/config/config.ts +7 -5
- package/src/config/configSchema.ts +30 -33
- package/src/config/configTemplate.ts +23 -0
- package/src/index.ts +15 -2
- package/src/project/ProjectContainer.ts +1 -1
- package/src/project/ProjectContainerResolver.ts +4 -17
- package/src/projectGroup/ProjectGroupContainer.ts +12 -3
- package/src/projectGroup/ProjectGroupResolver.ts +12 -0
- package/src/start.ts +18 -127
- package/src/utils/index.ts +0 -4
- package/src/utils/serverStartup.ts +42 -0
- package/src/utils/serverTermination.ts +20 -0
- package/tests/e2e/http.test.ts +84 -150
- package/tests/e2e/requests.ts +71 -0
- package/tests/e2e/tester.ts +118 -0
- package/alpine.dockerfile +0 -30
- package/debian.dockerfile +0 -27
- package/dist/src/http/CollectHttpMetricsMiddelware.d.ts +0 -5
- package/dist/src/http/CollectHttpMetricsMiddelware.d.ts.map +0 -1
- package/dist/src/http/CollectHttpMetricsMiddelware.js +0 -36
- package/dist/src/http/CollectHttpMetricsMiddelware.js.map +0 -1
- package/dist/src/http/ShowMetricsMiddleware.d.ts +0 -4
- package/dist/src/http/ShowMetricsMiddleware.d.ts.map +0 -1
- package/dist/src/http/ShowMetricsMiddleware.js +0 -20
- package/dist/src/http/ShowMetricsMiddleware.js.map +0 -1
- package/dist/src/http/index.d.ts +0 -3
- package/dist/src/http/index.d.ts.map +0 -1
- package/dist/src/http/index.js +0 -19
- package/dist/src/http/index.js.map +0 -1
- package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts +0 -12
- package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts.map +0 -1
- package/dist/src/projectGroup/ProjectGroupContainerResolver.js +0 -36
- package/dist/src/projectGroup/ProjectGroupContainerResolver.js.map +0 -1
- package/dist/src/utils/cluster.d.ts +0 -9
- package/dist/src/utils/cluster.d.ts.map +0 -1
- package/dist/src/utils/cluster.js +0 -47
- package/dist/src/utils/cluster.js.map +0 -1
- package/dist/src/utils/dbMetrics.d.ts +0 -16
- package/dist/src/utils/dbMetrics.d.ts.map +0 -1
- package/dist/src/utils/dbMetrics.js +0 -98
- package/dist/src/utils/dbMetrics.js.map +0 -1
- package/dist/src/utils/timeout.d.ts +0 -2
- package/dist/src/utils/timeout.d.ts.map +0 -1
- package/dist/src/utils/timeout.js +0 -8
- package/dist/src/utils/timeout.js.map +0 -1
- package/dist/src/utils/tuple.d.ts +0 -2
- package/dist/src/utils/tuple.d.ts.map +0 -1
- package/dist/src/utils/tuple.js +0 -6
- package/dist/src/utils/tuple.js.map +0 -1
- package/jasmine.json +0 -7
- package/src/http/CollectHttpMetricsMiddelware.ts +0 -32
- package/src/http/ShowMetricsMiddleware.ts +0 -12
- package/src/http/index.ts +0 -2
- package/src/projectGroup/ProjectGroupContainerResolver.ts +0 -47
- package/src/utils/cluster.ts +0 -40
- package/src/utils/dbMetrics.ts +0 -111
- package/src/utils/timeout.ts +0 -4
- package/src/utils/tuple.ts +0 -1
- 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('
|
|
49
|
-
|
|
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,
|
|
4
|
-
import { Server } from 'net'
|
|
3
|
+
import { createContainer, listenOnProcessTermination, serverConfigSchema, TerminationJob } from './index'
|
|
5
4
|
import loadPlugins from './loadPlugins'
|
|
6
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
41
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(`
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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)
|
package/src/utils/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/tests/e2e/http.test.ts
CHANGED
|
@@ -1,113 +1,10 @@
|
|
|
1
1
|
import supertest from 'supertest'
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { assert, test } from 'vitest'
|
|
3
|
+
import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
|
|
4
|
+
import { apiUrl, createTester, gql } from './tester'
|
|
5
|
+
import { addProjectMember, signIn, signUp } from './requests'
|
|
6
6
|
|
|
7
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
8
|
test('show homepage', async () => {
|
|
112
9
|
await supertest(apiUrl)
|
|
113
10
|
.get('/')
|
|
@@ -115,8 +12,16 @@ test('show homepage', async () => {
|
|
|
115
12
|
.expect('App is running')
|
|
116
13
|
})
|
|
117
14
|
|
|
15
|
+
namespace TagModel {
|
|
16
|
+
export class Tag {
|
|
17
|
+
label = def.stringColumn()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
118
21
|
test('Content API: create & read tag', async () => {
|
|
119
|
-
await
|
|
22
|
+
const tester = await createTester(createSchema(TagModel))
|
|
23
|
+
|
|
24
|
+
await tester(
|
|
120
25
|
gql`
|
|
121
26
|
mutation {
|
|
122
27
|
createTag(data: { label: "graphql" }) {
|
|
@@ -134,7 +39,7 @@ test('Content API: create & read tag', async () => {
|
|
|
134
39
|
})
|
|
135
40
|
.expect(200)
|
|
136
41
|
|
|
137
|
-
await
|
|
42
|
+
await tester(
|
|
138
43
|
gql`
|
|
139
44
|
query {
|
|
140
45
|
listTag(filter: { label: { eq: "graphql" } }) {
|
|
@@ -156,7 +61,10 @@ test('Content API: create & read tag', async () => {
|
|
|
156
61
|
})
|
|
157
62
|
|
|
158
63
|
test('Content API: X-Contember-Ref header', async () => {
|
|
159
|
-
|
|
64
|
+
|
|
65
|
+
const tester = await createTester(createSchema(TagModel))
|
|
66
|
+
|
|
67
|
+
await tester(
|
|
160
68
|
gql`
|
|
161
69
|
mutation {
|
|
162
70
|
createTag(data: { label: "typescript" }) {
|
|
@@ -166,7 +74,7 @@ test('Content API: X-Contember-Ref header', async () => {
|
|
|
166
74
|
`,
|
|
167
75
|
).expect(200)
|
|
168
76
|
|
|
169
|
-
const response = await
|
|
77
|
+
const response = await tester(
|
|
170
78
|
gql`
|
|
171
79
|
query {
|
|
172
80
|
listTag(filter: { label: { eq: "typescript" } }) {
|
|
@@ -187,7 +95,7 @@ test('Content API: X-Contember-Ref header', async () => {
|
|
|
187
95
|
})
|
|
188
96
|
.expect(200)
|
|
189
97
|
const eventKey = response.get('X-Contember-Ref')
|
|
190
|
-
await
|
|
98
|
+
await tester(
|
|
191
99
|
gql`
|
|
192
100
|
query {
|
|
193
101
|
listTag {
|
|
@@ -200,7 +108,7 @@ test('Content API: X-Contember-Ref header', async () => {
|
|
|
200
108
|
.expect(304)
|
|
201
109
|
|
|
202
110
|
// ignored for mutation
|
|
203
|
-
await
|
|
111
|
+
await tester(
|
|
204
112
|
gql`
|
|
205
113
|
mutation {
|
|
206
114
|
createTag(data: { label: "typescript" }) {
|
|
@@ -214,7 +122,9 @@ test('Content API: X-Contember-Ref header', async () => {
|
|
|
214
122
|
})
|
|
215
123
|
|
|
216
124
|
test('Content API: invalid schema error', async () => {
|
|
217
|
-
await
|
|
125
|
+
const tester = await createTester(createSchema(TagModel))
|
|
126
|
+
|
|
127
|
+
await tester(
|
|
218
128
|
gql`
|
|
219
129
|
mutation {
|
|
220
130
|
createFoo(data: { label: "graphql" }) {
|
|
@@ -227,38 +137,35 @@ test('Content API: invalid schema error', async () => {
|
|
|
227
137
|
.expect(response => {
|
|
228
138
|
assert.deepStrictEqual(
|
|
229
139
|
response.body.errors[0].message,
|
|
230
|
-
'Cannot query field "createFoo" on type "Mutation". Did you mean "
|
|
140
|
+
'Cannot query field "createFoo" on type "Mutation". Did you mean "createTag"?',
|
|
231
141
|
)
|
|
232
142
|
})
|
|
233
143
|
})
|
|
234
144
|
|
|
235
145
|
test('Tenant API: sign up, add to a project and check project access', async () => {
|
|
146
|
+
const tester = await createTester(createSchema(TagModel))
|
|
147
|
+
|
|
236
148
|
const email = `john+${Date.now()}@doe.com`
|
|
237
|
-
const
|
|
149
|
+
const identityId = await signUp(email)
|
|
150
|
+
const authKey = await signIn(email)
|
|
151
|
+
|
|
152
|
+
await tester(
|
|
238
153
|
gql`
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
result {
|
|
243
|
-
person {
|
|
244
|
-
identity {
|
|
245
|
-
id
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
154
|
+
query {
|
|
155
|
+
listTag {
|
|
156
|
+
id
|
|
249
157
|
}
|
|
250
158
|
}
|
|
251
159
|
`,
|
|
252
|
-
{
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
160
|
+
{ authorizationToken: authKey },
|
|
161
|
+
)
|
|
162
|
+
.expect(404)
|
|
163
|
+
.expect({ errors: [{ message: `Project ${tester.projectSlug} NOT found`, code: 404 }] })
|
|
164
|
+
|
|
257
165
|
|
|
258
|
-
|
|
166
|
+
await addProjectMember(identityId, tester.projectSlug)
|
|
259
167
|
|
|
260
|
-
|
|
261
|
-
await executeGraphql(
|
|
168
|
+
await tester(
|
|
262
169
|
gql`
|
|
263
170
|
query {
|
|
264
171
|
listTag {
|
|
@@ -268,36 +175,63 @@ test('Tenant API: sign up, add to a project and check project access', async ()
|
|
|
268
175
|
`,
|
|
269
176
|
{ authorizationToken: authKey },
|
|
270
177
|
)
|
|
271
|
-
.expect(
|
|
272
|
-
|
|
178
|
+
.expect(response => {
|
|
179
|
+
assert.deepStrictEqual(response.body.data, { listTag: [] })
|
|
180
|
+
})
|
|
181
|
+
.expect(200)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
test('Content API: assume membership', async () => {
|
|
186
|
+
const tester = await createTester(createSchema(TagModel, schema => {
|
|
187
|
+
return {
|
|
188
|
+
...schema,
|
|
189
|
+
acl: {
|
|
190
|
+
roles: {
|
|
191
|
+
test: {
|
|
192
|
+
entities: {},
|
|
193
|
+
variables: {},
|
|
194
|
+
content: {
|
|
195
|
+
assumeMembership: {
|
|
196
|
+
admin: {},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
}))
|
|
204
|
+
|
|
205
|
+
const email = `john+${Date.now()}@doe.com`
|
|
206
|
+
const identityId = await signUp(email)
|
|
207
|
+
const authKey = await signIn(email)
|
|
208
|
+
await addProjectMember(identityId, tester.projectSlug, 'test')
|
|
273
209
|
|
|
274
|
-
await
|
|
210
|
+
await tester(
|
|
275
211
|
gql`
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
212
|
+
query {
|
|
213
|
+
listTag {
|
|
214
|
+
id
|
|
279
215
|
}
|
|
280
216
|
}
|
|
281
217
|
`,
|
|
282
|
-
{
|
|
218
|
+
{ authorizationToken: authKey },
|
|
283
219
|
)
|
|
284
|
-
.expect(
|
|
285
|
-
|
|
286
|
-
assert.deepStrictEqual(response.body.data, { addProjectMember: { ok: true } })
|
|
287
|
-
})
|
|
220
|
+
.expect(400)
|
|
221
|
+
|
|
288
222
|
|
|
289
|
-
|
|
223
|
+
|
|
224
|
+
await tester(
|
|
290
225
|
gql`
|
|
291
226
|
query {
|
|
292
|
-
|
|
227
|
+
listTag {
|
|
293
228
|
id
|
|
294
229
|
}
|
|
295
230
|
}
|
|
296
231
|
`,
|
|
297
232
|
{ authorizationToken: authKey },
|
|
298
233
|
)
|
|
299
|
-
.
|
|
300
|
-
|
|
301
|
-
})
|
|
234
|
+
.set('X-Contember-assume-membership', JSON.stringify({ memberships: [{ role: 'admin', variables: [] }] }))
|
|
235
|
+
.expect({ data: { listTag: [] } })
|
|
302
236
|
.expect(200)
|
|
303
237
|
})
|