@contember/engine-server 1.1.0-rc.3 → 1.2.0-alpha.3
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 +27 -25
- package/dist/src/MasterContainer.d.ts.map +1 -1
- package/dist/src/MasterContainer.js +54 -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/sentry.d.ts +1 -1
- package/dist/src/utils/sentry.d.ts.map +1 -1
- package/dist/src/utils/sentry.js +1 -1
- package/dist/src/utils/sentry.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/acl-condition-variable.test.d.ts +2 -0
- package/dist/tests/e2e/acl-condition-variable.test.d.ts.map +1 -0
- package/dist/tests/e2e/acl-condition-variable.test.js +77 -0
- package/dist/tests/e2e/acl-condition-variable.test.js.map +1 -0
- package/dist/tests/e2e/assume-membership.test.d.ts +2 -0
- package/dist/tests/e2e/assume-membership.test.d.ts.map +1 -0
- package/dist/tests/e2e/assume-membership.test.js +58 -0
- package/dist/tests/e2e/assume-membership.test.js.map +1 -0
- package/dist/tests/e2e/content-api-basic.test.d.ts +2 -0
- package/dist/tests/e2e/content-api-basic.test.d.ts.map +1 -0
- package/dist/tests/e2e/content-api-basic.test.js +50 -0
- package/dist/tests/e2e/content-api-basic.test.js.map +1 -0
- package/dist/tests/e2e/content-invalid-schema.test.d.ts +2 -0
- package/dist/tests/e2e/content-invalid-schema.test.d.ts.map +1 -0
- package/dist/tests/e2e/content-invalid-schema.test.js +29 -0
- package/dist/tests/e2e/content-invalid-schema.test.js.map +1 -0
- package/dist/tests/e2e/homepage.test.d.ts +2 -0
- package/dist/tests/e2e/homepage.test.d.ts.map +1 -0
- package/dist/tests/e2e/homepage.test.js +15 -0
- package/dist/tests/e2e/homepage.test.js.map +1 -0
- package/dist/tests/e2e/tenant-member-access.test.d.ts +2 -0
- package/dist/tests/e2e/tenant-member-access.test.d.ts.map +1 -0
- package/dist/tests/e2e/tenant-member-access.test.js +43 -0
- package/dist/tests/e2e/tenant-member-access.test.js.map +1 -0
- package/dist/tests/e2e/x-contember-ref.test.d.ts +2 -0
- package/dist/tests/e2e/x-contember-ref.test.d.ts.map +1 -0
- package/dist/tests/e2e/x-contember-ref.test.js +63 -0
- package/dist/tests/e2e/x-contember-ref.test.js.map +1 -0
- package/dist/tests/src/requests.d.ts +5 -0
- package/dist/tests/src/requests.d.ts.map +1 -0
- package/dist/tests/src/requests.js +59 -0
- package/dist/tests/src/requests.js.map +1 -0
- package/dist/tests/src/tester.d.ts +19 -0
- package/dist/tests/src/tester.d.ts.map +1 -0
- package/dist/tests/src/tester.js +99 -0
- package/dist/tests/src/tester.js.map +1 -0
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/esbuild.js +7 -14
- package/package.json +23 -23
- package/src/MasterContainer.ts +94 -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/sentry.ts +1 -1
- package/src/utils/serverStartup.ts +42 -0
- package/src/utils/serverTermination.ts +20 -0
- package/tests/e2e/acl-condition-variable.test.ts +74 -0
- package/tests/e2e/assume-membership.test.ts +64 -0
- package/tests/e2e/content-api-basic.test.ts +51 -0
- package/tests/e2e/content-invalid-schema.test.ts +31 -0
- package/tests/e2e/homepage.test.ts +10 -0
- package/tests/e2e/tenant-member-access.test.ts +51 -0
- package/tests/e2e/x-contember-ref.test.ts +71 -0
- package/tests/src/requests.ts +72 -0
- package/tests/src/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/dist/tests/e2e/http.test.d.ts +0 -2
- package/dist/tests/e2e/http.test.d.ts.map +0 -1
- package/dist/tests/e2e/http.test.js +0 -275
- package/dist/tests/e2e/http.test.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/tests/e2e/http.test.ts +0 -303
- 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 =
|
|
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
|
-
|
|
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('
|
|
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
package/src/utils/sentry.ts
CHANGED
|
@@ -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(
|
|
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,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,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
|
+
})
|