@memberjunction/server 0.9.4 → 0.9.5
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/.eslintignore +5 -6
- package/.eslintrc +24 -24
- package/README.md +141 -141
- package/config.json +14 -14
- package/dist/apolloServer/TransactionPlugin.js +45 -45
- package/dist/apolloServer/index.js +26 -26
- package/dist/auth/exampleNewUserSubClass.js +67 -67
- package/dist/auth/index.js +87 -87
- package/dist/auth/newUsers.js +66 -66
- package/dist/cache.js +10 -10
- package/dist/config.js +55 -55
- package/dist/context.js +100 -100
- package/dist/directives/Public.js +33 -33
- package/dist/directives/index.js +17 -17
- package/dist/generated/generated.js +19245 -0
- package/dist/generated/generated.js.map +1 -0
- package/dist/generic/PushStatusResolver.js +59 -0
- package/dist/generic/PushStatusResolver.js.map +1 -0
- package/dist/generic/ResolverBase.js +187 -0
- package/dist/generic/ResolverBase.js.map +1 -0
- package/dist/generic/RunViewResolver.js +308 -0
- package/dist/generic/RunViewResolver.js.map +1 -0
- package/dist/index.js +114 -114
- package/dist/orm.js +21 -21
- package/dist/resolvers/AskSkipResolver.js +244 -0
- package/dist/resolvers/AskSkipResolver.js.map +1 -0
- package/dist/resolvers/ColorResolver.js +95 -0
- package/dist/resolvers/ColorResolver.js.map +1 -0
- package/dist/resolvers/DatasetResolver.js +168 -0
- package/dist/resolvers/DatasetResolver.js.map +1 -0
- package/dist/resolvers/EntityRecordNameResolver.js +108 -0
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -0
- package/dist/resolvers/EntityResolver.js +60 -0
- package/dist/resolvers/EntityResolver.js.map +1 -0
- package/dist/resolvers/ReportResolver.js +74 -0
- package/dist/resolvers/ReportResolver.js.map +1 -0
- package/dist/resolvers/UserFavoriteResolver.js +188 -0
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.js +70 -0
- package/dist/resolvers/UserResolver.js.map +1 -0
- package/dist/resolvers/UserViewResolver.js +104 -0
- package/dist/resolvers/UserViewResolver.js.map +1 -0
- package/dist/types.js +2 -2
- package/package.json +74 -74
- package/src/apolloServer/TransactionPlugin.ts +54 -54
- package/src/apolloServer/index.ts +33 -33
- package/src/auth/exampleNewUserSubClass.ts +70 -70
- package/src/auth/index.ts +117 -117
- package/src/auth/newUsers.ts +55 -55
- package/src/cache.ts +10 -10
- package/src/config.ts +67 -67
- package/src/context.ts +105 -105
- package/src/directives/Public.ts +42 -42
- package/src/directives/index.ts +1 -1
- package/src/index.ts +105 -105
- package/src/orm.ts +23 -23
- package/src/types.ts +19 -19
package/src/context.ts
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import { IncomingMessage } from 'http';
|
|
2
|
-
import * as url from 'url';
|
|
3
|
-
import { JwtPayload, VerifyOptions, decode, verify } from 'jsonwebtoken';
|
|
4
|
-
import 'reflect-metadata';
|
|
5
|
-
import { Subject, firstValueFrom } from 'rxjs';
|
|
6
|
-
import { AuthenticationError, AuthorizationError } from 'type-graphql';
|
|
7
|
-
import { DataSource } from 'typeorm';
|
|
8
|
-
import { getSigningKeys, validationOptions, verifyUserRecord } from './auth';
|
|
9
|
-
import { authCache } from './cache';
|
|
10
|
-
import { userEmailMap } from './config';
|
|
11
|
-
import { UserPayload } from './types';
|
|
12
|
-
|
|
13
|
-
const verifyAsync = async (
|
|
14
|
-
issuer: string,
|
|
15
|
-
options: VerifyOptions,
|
|
16
|
-
token: string
|
|
17
|
-
): Promise<JwtPayload> =>
|
|
18
|
-
new Promise((resolve, reject) => {
|
|
19
|
-
verify(token, getSigningKeys(issuer), options, (err, jwt) => {
|
|
20
|
-
if (jwt && typeof jwt !== 'string' && !err) {
|
|
21
|
-
const payload = jwt.payload ?? jwt;
|
|
22
|
-
|
|
23
|
-
console.log(
|
|
24
|
-
`Valid token: ${payload.name} (${
|
|
25
|
-
payload.email ? payload.email : payload.preferred_username
|
|
26
|
-
})`
|
|
27
|
-
); // temporary fix to check preferred_username if email is not present
|
|
28
|
-
resolve(payload);
|
|
29
|
-
} else {
|
|
30
|
-
console.warn('Invalid token');
|
|
31
|
-
reject(err);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export const getUserPayload = async (
|
|
37
|
-
bearerToken: string,
|
|
38
|
-
sessionId = 'default',
|
|
39
|
-
dataSource: DataSource,
|
|
40
|
-
requestDomain?: string
|
|
41
|
-
): Promise<UserPayload> => {
|
|
42
|
-
try {
|
|
43
|
-
const token = bearerToken.replace('Bearer ', '');
|
|
44
|
-
|
|
45
|
-
if (!token) {
|
|
46
|
-
console.warn('No token to validate');
|
|
47
|
-
throw new AuthenticationError('Missing token');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const payload = decode(token);
|
|
51
|
-
if (!payload || typeof payload === 'string') {
|
|
52
|
-
throw new AuthenticationError('Invalid token payload');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!authCache.has(token)) {
|
|
56
|
-
const issuer = payload.iss;
|
|
57
|
-
if (!issuer) {
|
|
58
|
-
console.warn('No issuer claim on token');
|
|
59
|
-
throw new AuthenticationError('Missing issuer claim on token');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
await verifyAsync(issuer, validationOptions[issuer], token);
|
|
63
|
-
authCache.set(token, true);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const email = payload?.email
|
|
67
|
-
? userEmailMap[payload?.email] ?? payload?.email
|
|
68
|
-
: payload?.preferred_username; // temporary fix to check preferred_username if email is not present
|
|
69
|
-
const fullName = payload?.name;
|
|
70
|
-
const firstName = payload?.given_name || fullName?.split(' ')[0];
|
|
71
|
-
const lastName = payload?.family_name || fullName?.split(' ')[1] || fullName?.split(' ')[0];
|
|
72
|
-
const userRecord = await verifyUserRecord(email, firstName, lastName, requestDomain, dataSource);
|
|
73
|
-
|
|
74
|
-
if (!userRecord) {
|
|
75
|
-
console.error(`User ${email} not found`);
|
|
76
|
-
throw new AuthorizationError();
|
|
77
|
-
}
|
|
78
|
-
else if (!userRecord.IsActive) {
|
|
79
|
-
console.error(`User ${email} found but inactive`);
|
|
80
|
-
throw new AuthorizationError();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { userRecord, email, sessionId };
|
|
84
|
-
} catch (e) {
|
|
85
|
-
return {} as UserPayload;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export const contextFunction =
|
|
90
|
-
({ setupComplete$, dataSource }: { setupComplete$: Subject<unknown>; dataSource: DataSource }) =>
|
|
91
|
-
async ({ req }: { req: IncomingMessage }) => {
|
|
92
|
-
await firstValueFrom(setupComplete$); // wait for setup to complete before processing the request
|
|
93
|
-
|
|
94
|
-
const sessionIdRaw = req.headers['x-session-id'];
|
|
95
|
-
const requestDomain = url.parse(req.headers.origin || '')
|
|
96
|
-
const sessionId = sessionIdRaw ? sessionIdRaw.toString() : '';
|
|
97
|
-
const bearerToken = req.headers.authorization ?? '';
|
|
98
|
-
|
|
99
|
-
const userPayload = await getUserPayload(bearerToken, sessionId, dataSource, requestDomain?.hostname ? requestDomain.hostname : undefined);
|
|
100
|
-
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
-
console.log((req as any).body?.operationName);
|
|
103
|
-
|
|
104
|
-
return { dataSource, userPayload };
|
|
105
|
-
};
|
|
1
|
+
import { IncomingMessage } from 'http';
|
|
2
|
+
import * as url from 'url';
|
|
3
|
+
import { JwtPayload, VerifyOptions, decode, verify } from 'jsonwebtoken';
|
|
4
|
+
import 'reflect-metadata';
|
|
5
|
+
import { Subject, firstValueFrom } from 'rxjs';
|
|
6
|
+
import { AuthenticationError, AuthorizationError } from 'type-graphql';
|
|
7
|
+
import { DataSource } from 'typeorm';
|
|
8
|
+
import { getSigningKeys, validationOptions, verifyUserRecord } from './auth';
|
|
9
|
+
import { authCache } from './cache';
|
|
10
|
+
import { userEmailMap } from './config';
|
|
11
|
+
import { UserPayload } from './types';
|
|
12
|
+
|
|
13
|
+
const verifyAsync = async (
|
|
14
|
+
issuer: string,
|
|
15
|
+
options: VerifyOptions,
|
|
16
|
+
token: string
|
|
17
|
+
): Promise<JwtPayload> =>
|
|
18
|
+
new Promise((resolve, reject) => {
|
|
19
|
+
verify(token, getSigningKeys(issuer), options, (err, jwt) => {
|
|
20
|
+
if (jwt && typeof jwt !== 'string' && !err) {
|
|
21
|
+
const payload = jwt.payload ?? jwt;
|
|
22
|
+
|
|
23
|
+
console.log(
|
|
24
|
+
`Valid token: ${payload.name} (${
|
|
25
|
+
payload.email ? payload.email : payload.preferred_username
|
|
26
|
+
})`
|
|
27
|
+
); // temporary fix to check preferred_username if email is not present
|
|
28
|
+
resolve(payload);
|
|
29
|
+
} else {
|
|
30
|
+
console.warn('Invalid token');
|
|
31
|
+
reject(err);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const getUserPayload = async (
|
|
37
|
+
bearerToken: string,
|
|
38
|
+
sessionId = 'default',
|
|
39
|
+
dataSource: DataSource,
|
|
40
|
+
requestDomain?: string
|
|
41
|
+
): Promise<UserPayload> => {
|
|
42
|
+
try {
|
|
43
|
+
const token = bearerToken.replace('Bearer ', '');
|
|
44
|
+
|
|
45
|
+
if (!token) {
|
|
46
|
+
console.warn('No token to validate');
|
|
47
|
+
throw new AuthenticationError('Missing token');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const payload = decode(token);
|
|
51
|
+
if (!payload || typeof payload === 'string') {
|
|
52
|
+
throw new AuthenticationError('Invalid token payload');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!authCache.has(token)) {
|
|
56
|
+
const issuer = payload.iss;
|
|
57
|
+
if (!issuer) {
|
|
58
|
+
console.warn('No issuer claim on token');
|
|
59
|
+
throw new AuthenticationError('Missing issuer claim on token');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await verifyAsync(issuer, validationOptions[issuer], token);
|
|
63
|
+
authCache.set(token, true);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const email = payload?.email
|
|
67
|
+
? userEmailMap[payload?.email] ?? payload?.email
|
|
68
|
+
: payload?.preferred_username; // temporary fix to check preferred_username if email is not present
|
|
69
|
+
const fullName = payload?.name;
|
|
70
|
+
const firstName = payload?.given_name || fullName?.split(' ')[0];
|
|
71
|
+
const lastName = payload?.family_name || fullName?.split(' ')[1] || fullName?.split(' ')[0];
|
|
72
|
+
const userRecord = await verifyUserRecord(email, firstName, lastName, requestDomain, dataSource);
|
|
73
|
+
|
|
74
|
+
if (!userRecord) {
|
|
75
|
+
console.error(`User ${email} not found`);
|
|
76
|
+
throw new AuthorizationError();
|
|
77
|
+
}
|
|
78
|
+
else if (!userRecord.IsActive) {
|
|
79
|
+
console.error(`User ${email} found but inactive`);
|
|
80
|
+
throw new AuthorizationError();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { userRecord, email, sessionId };
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return {} as UserPayload;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const contextFunction =
|
|
90
|
+
({ setupComplete$, dataSource }: { setupComplete$: Subject<unknown>; dataSource: DataSource }) =>
|
|
91
|
+
async ({ req }: { req: IncomingMessage }) => {
|
|
92
|
+
await firstValueFrom(setupComplete$); // wait for setup to complete before processing the request
|
|
93
|
+
|
|
94
|
+
const sessionIdRaw = req.headers['x-session-id'];
|
|
95
|
+
const requestDomain = url.parse(req.headers.origin || '')
|
|
96
|
+
const sessionId = sessionIdRaw ? sessionIdRaw.toString() : '';
|
|
97
|
+
const bearerToken = req.headers.authorization ?? '';
|
|
98
|
+
|
|
99
|
+
const userPayload = await getUserPayload(bearerToken, sessionId, dataSource, requestDomain?.hostname ? requestDomain.hostname : undefined);
|
|
100
|
+
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
console.log((req as any).body?.operationName);
|
|
103
|
+
|
|
104
|
+
return { dataSource, userPayload };
|
|
105
|
+
};
|
package/src/directives/Public.ts
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { FieldMapper, MapperKind, getDirective, mapSchema } from '@graphql-tools/utils';
|
|
2
|
-
import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql';
|
|
3
|
-
import { AuthorizationError, Directive } from 'type-graphql';
|
|
4
|
-
import { AppContext, DirectiveBuilder } from '../types';
|
|
5
|
-
|
|
6
|
-
const DIRECTIVE_NAME = 'Public';
|
|
7
|
-
|
|
8
|
-
export function Public(): PropertyDecorator & MethodDecorator & ClassDecorator;
|
|
9
|
-
export function Public(): PropertyDecorator | MethodDecorator | ClassDecorator {
|
|
10
|
-
return (targetOrPrototype, propertyKey, descriptor) =>
|
|
11
|
-
Directive(`@${DIRECTIVE_NAME}`)(targetOrPrototype, propertyKey, descriptor);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const publicDirective: DirectiveBuilder = {
|
|
15
|
-
typeDefs: `directive @${DIRECTIVE_NAME} on FIELD_DEFINITION`,
|
|
16
|
-
transformer: (schema) => {
|
|
17
|
-
const fieldMapper: FieldMapper = (fieldConfig) => {
|
|
18
|
-
const directive = getDirective(schema, fieldConfig, DIRECTIVE_NAME)?.[0];
|
|
19
|
-
if (directive) {
|
|
20
|
-
return fieldConfig;
|
|
21
|
-
} else {
|
|
22
|
-
// `@Public` directive not present, so will require auth
|
|
23
|
-
const { resolve = defaultFieldResolver } = fieldConfig;
|
|
24
|
-
const directiveResolver: GraphQLFieldResolver<unknown, AppContext> = async (
|
|
25
|
-
source,
|
|
26
|
-
args,
|
|
27
|
-
context,
|
|
28
|
-
info
|
|
29
|
-
) => {
|
|
30
|
-
// eslint-disable-next-line
|
|
31
|
-
if (!context?.userPayload?.userRecord?.IsActive) {
|
|
32
|
-
throw new AuthorizationError();
|
|
33
|
-
}
|
|
34
|
-
return await resolve(source, args, context, info);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
return { ...fieldConfig, resolve: directiveResolver };
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
return mapSchema(schema, { [MapperKind.OBJECT_FIELD]: fieldMapper });
|
|
41
|
-
},
|
|
42
|
-
};
|
|
1
|
+
import { FieldMapper, MapperKind, getDirective, mapSchema } from '@graphql-tools/utils';
|
|
2
|
+
import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql';
|
|
3
|
+
import { AuthorizationError, Directive } from 'type-graphql';
|
|
4
|
+
import { AppContext, DirectiveBuilder } from '../types';
|
|
5
|
+
|
|
6
|
+
const DIRECTIVE_NAME = 'Public';
|
|
7
|
+
|
|
8
|
+
export function Public(): PropertyDecorator & MethodDecorator & ClassDecorator;
|
|
9
|
+
export function Public(): PropertyDecorator | MethodDecorator | ClassDecorator {
|
|
10
|
+
return (targetOrPrototype, propertyKey, descriptor) =>
|
|
11
|
+
Directive(`@${DIRECTIVE_NAME}`)(targetOrPrototype, propertyKey, descriptor);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const publicDirective: DirectiveBuilder = {
|
|
15
|
+
typeDefs: `directive @${DIRECTIVE_NAME} on FIELD_DEFINITION`,
|
|
16
|
+
transformer: (schema) => {
|
|
17
|
+
const fieldMapper: FieldMapper = (fieldConfig) => {
|
|
18
|
+
const directive = getDirective(schema, fieldConfig, DIRECTIVE_NAME)?.[0];
|
|
19
|
+
if (directive) {
|
|
20
|
+
return fieldConfig;
|
|
21
|
+
} else {
|
|
22
|
+
// `@Public` directive not present, so will require auth
|
|
23
|
+
const { resolve = defaultFieldResolver } = fieldConfig;
|
|
24
|
+
const directiveResolver: GraphQLFieldResolver<unknown, AppContext> = async (
|
|
25
|
+
source,
|
|
26
|
+
args,
|
|
27
|
+
context,
|
|
28
|
+
info
|
|
29
|
+
) => {
|
|
30
|
+
// eslint-disable-next-line
|
|
31
|
+
if (!context?.userPayload?.userRecord?.IsActive) {
|
|
32
|
+
throw new AuthorizationError();
|
|
33
|
+
}
|
|
34
|
+
return await resolve(source, args, context, info);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return { ...fieldConfig, resolve: directiveResolver };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return mapSchema(schema, { [MapperKind.OBJECT_FIELD]: fieldMapper });
|
|
41
|
+
},
|
|
42
|
+
};
|
package/src/directives/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './Public';
|
|
1
|
+
export * from './Public';
|
package/src/index.ts
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import dotenv from 'dotenv';
|
|
2
|
-
|
|
3
|
-
dotenv.config();
|
|
4
|
-
|
|
5
|
-
import { expressMiddleware } from '@apollo/server/express4';
|
|
6
|
-
import { mergeSchemas } from '@graphql-tools/schema';
|
|
7
|
-
import { Metadata } from '@memberjunction/core';
|
|
8
|
-
import { setupSQLServerClient, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
|
|
9
|
-
import { json } from 'body-parser';
|
|
10
|
-
import cors from 'cors';
|
|
11
|
-
import express from 'express';
|
|
12
|
-
import { convertPathToPattern, globSync } from 'fast-glob';
|
|
13
|
-
import { useServer } from 'graphql-ws/lib/use/ws';
|
|
14
|
-
import { createServer } from 'node:http';
|
|
15
|
-
import 'reflect-metadata';
|
|
16
|
-
import { ReplaySubject } from 'rxjs';
|
|
17
|
-
import { BuildSchemaOptions, buildSchemaSync, GraphQLTimestamp } from 'type-graphql';
|
|
18
|
-
import { DataSource } from 'typeorm';
|
|
19
|
-
import { WebSocketServer } from 'ws';
|
|
20
|
-
import buildApolloServer from './apolloServer';
|
|
21
|
-
import { configInfo, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage } from './config';
|
|
22
|
-
import { contextFunction, getUserPayload } from './context';
|
|
23
|
-
import { publicDirective } from './directives';
|
|
24
|
-
import orm from './orm';
|
|
25
|
-
|
|
26
|
-
const cacheRefreshInterval = configInfo.databaseSettings.metadataCacheRefreshInterval;
|
|
27
|
-
|
|
28
|
-
export { MaxLength } from 'class-validator';
|
|
29
|
-
export * from 'type-graphql';
|
|
30
|
-
export { NewUserBase } from './auth/newUsers';
|
|
31
|
-
export { configInfo } from './config';
|
|
32
|
-
export * from './directives';
|
|
33
|
-
export * from './types';
|
|
34
|
-
|
|
35
|
-
export const serve = async (resolverPaths: Array<string>) => {
|
|
36
|
-
const paths = resolverPaths.flatMap((path) => globSync(convertPathToPattern(path)));
|
|
37
|
-
console.log({ resolverPaths, paths });
|
|
38
|
-
|
|
39
|
-
const dataSource = new DataSource(orm(paths));
|
|
40
|
-
const setupComplete$ = new ReplaySubject(1);
|
|
41
|
-
dataSource
|
|
42
|
-
.initialize()
|
|
43
|
-
.then(async () => {
|
|
44
|
-
const config = new SQLServerProviderConfigData(dataSource, '', mj_core_schema, cacheRefreshInterval);
|
|
45
|
-
await setupSQLServerClient(config); // datasource is already initialized, so we can setup the client right away
|
|
46
|
-
const md = new Metadata();
|
|
47
|
-
console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
|
|
48
|
-
|
|
49
|
-
setupComplete$.next(true);
|
|
50
|
-
})
|
|
51
|
-
.catch((err) => {
|
|
52
|
-
console.error('Error during Data Source initialization', err);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const dynamicModules = await Promise.all(paths.map((modulePath) => import(modulePath.replace(/\.[jt]s$/, ''))));
|
|
56
|
-
const resolvers = dynamicModules.flatMap((module) =>
|
|
57
|
-
Object.values(module).filter((value) => typeof value === 'function')
|
|
58
|
-
) as BuildSchemaOptions['resolvers'];
|
|
59
|
-
|
|
60
|
-
const schema = publicDirective.transformer(
|
|
61
|
-
mergeSchemas({
|
|
62
|
-
schemas: [
|
|
63
|
-
buildSchemaSync({
|
|
64
|
-
resolvers,
|
|
65
|
-
validate: false,
|
|
66
|
-
scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
|
|
67
|
-
emitSchemaFile: websiteRunFromPackage !== 1,
|
|
68
|
-
}),
|
|
69
|
-
],
|
|
70
|
-
typeDefs: [publicDirective.typeDefs],
|
|
71
|
-
})
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
const app = express();
|
|
75
|
-
const httpServer = createServer(app);
|
|
76
|
-
|
|
77
|
-
const webSocketServer = new WebSocketServer({ server: httpServer, path: graphqlRootPath });
|
|
78
|
-
const serverCleanup = useServer(
|
|
79
|
-
{
|
|
80
|
-
schema,
|
|
81
|
-
context: async ({ connectionParams }) => {
|
|
82
|
-
const userPayload = await getUserPayload(String(connectionParams?.Authorization), undefined, dataSource);
|
|
83
|
-
return { userPayload };
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
webSocketServer
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const apolloServer = buildApolloServer({ schema }, { httpServer, serverCleanup });
|
|
90
|
-
await apolloServer.start();
|
|
91
|
-
|
|
92
|
-
app.use(
|
|
93
|
-
graphqlRootPath,
|
|
94
|
-
cors<cors.CorsRequest>(),
|
|
95
|
-
json(),
|
|
96
|
-
expressMiddleware(apolloServer, {
|
|
97
|
-
context: contextFunction({ setupComplete$, dataSource }),
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
await new Promise<void>((resolve) => httpServer.listen({ port: graphqlPort }, resolve));
|
|
102
|
-
console.log(`🚀 Server ready at http://localhost:${graphqlPort}/`);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export default serve;
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
|
|
3
|
+
dotenv.config();
|
|
4
|
+
|
|
5
|
+
import { expressMiddleware } from '@apollo/server/express4';
|
|
6
|
+
import { mergeSchemas } from '@graphql-tools/schema';
|
|
7
|
+
import { Metadata } from '@memberjunction/core';
|
|
8
|
+
import { setupSQLServerClient, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
|
|
9
|
+
import { json } from 'body-parser';
|
|
10
|
+
import cors from 'cors';
|
|
11
|
+
import express from 'express';
|
|
12
|
+
import { convertPathToPattern, globSync } from 'fast-glob';
|
|
13
|
+
import { useServer } from 'graphql-ws/lib/use/ws';
|
|
14
|
+
import { createServer } from 'node:http';
|
|
15
|
+
import 'reflect-metadata';
|
|
16
|
+
import { ReplaySubject } from 'rxjs';
|
|
17
|
+
import { BuildSchemaOptions, buildSchemaSync, GraphQLTimestamp } from 'type-graphql';
|
|
18
|
+
import { DataSource } from 'typeorm';
|
|
19
|
+
import { WebSocketServer } from 'ws';
|
|
20
|
+
import buildApolloServer from './apolloServer';
|
|
21
|
+
import { configInfo, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage } from './config';
|
|
22
|
+
import { contextFunction, getUserPayload } from './context';
|
|
23
|
+
import { publicDirective } from './directives';
|
|
24
|
+
import orm from './orm';
|
|
25
|
+
|
|
26
|
+
const cacheRefreshInterval = configInfo.databaseSettings.metadataCacheRefreshInterval;
|
|
27
|
+
|
|
28
|
+
export { MaxLength } from 'class-validator';
|
|
29
|
+
export * from 'type-graphql';
|
|
30
|
+
export { NewUserBase } from './auth/newUsers';
|
|
31
|
+
export { configInfo } from './config';
|
|
32
|
+
export * from './directives';
|
|
33
|
+
export * from './types';
|
|
34
|
+
|
|
35
|
+
export const serve = async (resolverPaths: Array<string>) => {
|
|
36
|
+
const paths = resolverPaths.flatMap((path) => globSync(convertPathToPattern(path)));
|
|
37
|
+
console.log({ resolverPaths, paths });
|
|
38
|
+
|
|
39
|
+
const dataSource = new DataSource(orm(paths));
|
|
40
|
+
const setupComplete$ = new ReplaySubject(1);
|
|
41
|
+
dataSource
|
|
42
|
+
.initialize()
|
|
43
|
+
.then(async () => {
|
|
44
|
+
const config = new SQLServerProviderConfigData(dataSource, '', mj_core_schema, cacheRefreshInterval);
|
|
45
|
+
await setupSQLServerClient(config); // datasource is already initialized, so we can setup the client right away
|
|
46
|
+
const md = new Metadata();
|
|
47
|
+
console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
|
|
48
|
+
|
|
49
|
+
setupComplete$.next(true);
|
|
50
|
+
})
|
|
51
|
+
.catch((err) => {
|
|
52
|
+
console.error('Error during Data Source initialization', err);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const dynamicModules = await Promise.all(paths.map((modulePath) => import(modulePath.replace(/\.[jt]s$/, ''))));
|
|
56
|
+
const resolvers = dynamicModules.flatMap((module) =>
|
|
57
|
+
Object.values(module).filter((value) => typeof value === 'function')
|
|
58
|
+
) as BuildSchemaOptions['resolvers'];
|
|
59
|
+
|
|
60
|
+
const schema = publicDirective.transformer(
|
|
61
|
+
mergeSchemas({
|
|
62
|
+
schemas: [
|
|
63
|
+
buildSchemaSync({
|
|
64
|
+
resolvers,
|
|
65
|
+
validate: false,
|
|
66
|
+
scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
|
|
67
|
+
emitSchemaFile: websiteRunFromPackage !== 1,
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
typeDefs: [publicDirective.typeDefs],
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const app = express();
|
|
75
|
+
const httpServer = createServer(app);
|
|
76
|
+
|
|
77
|
+
const webSocketServer = new WebSocketServer({ server: httpServer, path: graphqlRootPath });
|
|
78
|
+
const serverCleanup = useServer(
|
|
79
|
+
{
|
|
80
|
+
schema,
|
|
81
|
+
context: async ({ connectionParams }) => {
|
|
82
|
+
const userPayload = await getUserPayload(String(connectionParams?.Authorization), undefined, dataSource);
|
|
83
|
+
return { userPayload };
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
webSocketServer
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const apolloServer = buildApolloServer({ schema }, { httpServer, serverCleanup });
|
|
90
|
+
await apolloServer.start();
|
|
91
|
+
|
|
92
|
+
app.use(
|
|
93
|
+
graphqlRootPath,
|
|
94
|
+
cors<cors.CorsRequest>(),
|
|
95
|
+
json(),
|
|
96
|
+
expressMiddleware(apolloServer, {
|
|
97
|
+
context: contextFunction({ setupComplete$, dataSource }),
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await new Promise<void>((resolve) => httpServer.listen({ port: graphqlPort }, resolve));
|
|
102
|
+
console.log(`🚀 Server ready at http://localhost:${graphqlPort}/`);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default serve;
|
package/src/orm.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { DataSourceOptions } from 'typeorm';
|
|
2
|
-
import { configInfo, dbDatabase, dbHost, dbPassword, dbPort, dbUsername } from './config';
|
|
3
|
-
|
|
4
|
-
const orm = (entities: Array<string>): DataSourceOptions => {
|
|
5
|
-
const ormConfig = {
|
|
6
|
-
type: 'mssql' as const,
|
|
7
|
-
entities,
|
|
8
|
-
logging: false,
|
|
9
|
-
host: dbHost,
|
|
10
|
-
port: dbPort,
|
|
11
|
-
username: dbUsername,
|
|
12
|
-
password: dbPassword,
|
|
13
|
-
database: dbDatabase,
|
|
14
|
-
synchronize: false,
|
|
15
|
-
requestTimeout: configInfo.databaseSettings.requestTimeout,
|
|
16
|
-
connectionTimeout: configInfo.databaseSettings.connectionTimeout,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
console.log({ ormConfig: { ...ormConfig, password: '***' } });
|
|
20
|
-
return ormConfig;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default orm;
|
|
1
|
+
import { DataSourceOptions } from 'typeorm';
|
|
2
|
+
import { configInfo, dbDatabase, dbHost, dbPassword, dbPort, dbUsername } from './config';
|
|
3
|
+
|
|
4
|
+
const orm = (entities: Array<string>): DataSourceOptions => {
|
|
5
|
+
const ormConfig = {
|
|
6
|
+
type: 'mssql' as const,
|
|
7
|
+
entities,
|
|
8
|
+
logging: false,
|
|
9
|
+
host: dbHost,
|
|
10
|
+
port: dbPort,
|
|
11
|
+
username: dbUsername,
|
|
12
|
+
password: dbPassword,
|
|
13
|
+
database: dbDatabase,
|
|
14
|
+
synchronize: false,
|
|
15
|
+
requestTimeout: configInfo.databaseSettings.requestTimeout,
|
|
16
|
+
connectionTimeout: configInfo.databaseSettings.connectionTimeout,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
console.log({ ormConfig: { ...ormConfig, password: '***' } });
|
|
20
|
+
return ormConfig;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default orm;
|
package/src/types.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { GraphQLSchema } from 'graphql';
|
|
2
|
-
import { DataSource, QueryRunner } from 'typeorm';
|
|
3
|
-
|
|
4
|
-
export type UserPayload = {
|
|
5
|
-
email: string;
|
|
6
|
-
userRecord: any;
|
|
7
|
-
sessionId: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type AppContext = {
|
|
11
|
-
dataSource: DataSource;
|
|
12
|
-
userPayload: UserPayload;
|
|
13
|
-
queryRunner?: QueryRunner;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type DirectiveBuilder = {
|
|
17
|
-
typeDefs: string;
|
|
18
|
-
transformer: (schema: GraphQLSchema) => GraphQLSchema;
|
|
19
|
-
};
|
|
1
|
+
import { GraphQLSchema } from 'graphql';
|
|
2
|
+
import { DataSource, QueryRunner } from 'typeorm';
|
|
3
|
+
|
|
4
|
+
export type UserPayload = {
|
|
5
|
+
email: string;
|
|
6
|
+
userRecord: any;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type AppContext = {
|
|
11
|
+
dataSource: DataSource;
|
|
12
|
+
userPayload: UserPayload;
|
|
13
|
+
queryRunner?: QueryRunner;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type DirectiveBuilder = {
|
|
17
|
+
typeDefs: string;
|
|
18
|
+
transformer: (schema: GraphQLSchema) => GraphQLSchema;
|
|
19
|
+
};
|