Merge remote-tracking branch 'origin/master' into

2428-feature-federation-implement-multiple-apollo-graphql-endpoints
This commit is contained in:
Claus-Peter Hübner 2023-01-12 18:02:15 +01:00
parent 4c4979f8eb
commit 9547eff793
11 changed files with 210 additions and 178 deletions

View File

@ -1,6 +1,6 @@
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
import dotenv from 'dotenv'
dotenv.config()
import dotenv from "dotenv";
dotenv.config();
/*
import Decimal from 'decimal.js-light'
@ -11,34 +11,35 @@ Decimal.set({
*/
const constants = {
DB_VERSION: '0058-add_communities_table',
DB_VERSION: "0059-add_hide_amount_to_users",
// DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json',
LOG4JS_CONFIG: "log4js-config.json",
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
LOG_LEVEL: process.env.LOG_LEVEL || "info",
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v1.2023-01-09',
CURRENT: '',
DEFAULT: "DEFAULT",
EXPECTED: "v1.2023-01-09",
CURRENT: "",
},
}
};
const server = {
PORT: process.env.PORT || 5000,
// JWT_SECRET: process.env.JWT_SECRET || 'secret123',
// JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
GRAPHIQL: process.env.GRAPHIQL === "true" || false,
// GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
PRODUCTION: process.env.NODE_ENV === 'production' || false,
}
PRODUCTION: process.env.NODE_ENV === "production" || false,
};
const database = {
DB_HOST: process.env.DB_HOST || 'localhost',
DB_HOST: process.env.DB_HOST || "localhost",
DB_PORT: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306,
DB_USER: process.env.DB_USER || 'root',
DB_PASSWORD: process.env.DB_PASSWORD || '',
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log',
}
DB_USER: process.env.DB_USER || "root",
DB_PASSWORD: process.env.DB_PASSWORD || "",
DB_DATABASE: process.env.DB_DATABASE || "gradido_community",
TYPEORM_LOGGING_RELATIVE_PATH:
process.env.TYPEORM_LOGGING_RELATIVE_PATH || "typeorm.backend.log",
};
/*
const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
@ -52,40 +53,42 @@ const community = {
}
*/
// const eventProtocol = {
// global switch to enable writing of EventProtocol-Entries
// EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
// global switch to enable writing of EventProtocol-Entries
// EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
// }
// This is needed by graphql-directive-auth
// process.env.APP_SECRET = server.JWT_SECRET
// Check config version
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
constants.CONFIG_VERSION.CURRENT =
process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT;
if (
![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes(
constants.CONFIG_VERSION.CURRENT,
)
![
constants.CONFIG_VERSION.EXPECTED,
constants.CONFIG_VERSION.DEFAULT,
].includes(constants.CONFIG_VERSION.CURRENT)
) {
throw new Error(
`Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`,
)
`Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`
);
}
const federation = {
// FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null,
// FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
FEDERATION_PORT: process.env.FEDERATION_PORT || 5000,
FEDERATION_API: process.env.FEDERATION_API || '1_0',
FEDERATION_API: process.env.FEDERATION_API || "1_0",
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null,
}
};
const CONFIG = {
...constants,
...server,
...database,
//...community,
//...eventProtocol,
// ...community,
// ...eventProtocol,
...federation,
}
};
export default CONFIG
export default CONFIG;

View File

@ -1,22 +1,21 @@
import { Field, ObjectType, Query, Resolver } from 'type-graphql'
import { federationLogger as logger } from '@/server/logger'
import { Field, ObjectType, Query, Resolver } from "type-graphql";
import { federationLogger as logger } from "@/server/logger";
@ObjectType()
class GetTestApiResult {
constructor(apiVersion: string) {
this.api = `${apiVersion}`
this.api = `${apiVersion}`;
}
@Field(() => String)
api: string
api: string;
}
@Resolver()
export class TestResolver {
export class TestResolver {
@Query(() => GetTestApiResult)
async test(): Promise<GetTestApiResult> {
logger.info(`test api 1_0`)
return new GetTestApiResult("1_0")
logger.info(`test api 1_0`);
return new GetTestApiResult("1_0");
}
}

View File

@ -1,9 +1,12 @@
import path from 'path'
import path from "path";
// config
import CONFIG from '../../config'
import { federationLogger as logger } from '@/server/logger'
import CONFIG from "../../config";
import { federationLogger as logger } from "@/server/logger";
export const getApiResolvers = () => {
logger.info(`getApiResolvers...${CONFIG.FEDERATION_API}`)
return path.join(__dirname, `./${CONFIG.FEDERATION_API}/resolver/*Resolver.{ts,js}`)
}
export const getApiResolvers = (): string => {
logger.info(`getApiResolvers...${CONFIG.FEDERATION_API}`);
return path.join(
__dirname,
`./${CONFIG.FEDERATION_API}/resolver/*Resolver.{ts,js}`
);
};

View File

@ -1,17 +1,17 @@
import { GraphQLSchema } from 'graphql'
import { buildSchema } from 'type-graphql'
import { GraphQLSchema } from "graphql";
import { buildSchema } from "type-graphql";
// import isAuthorized from './directive/isAuthorized'
import DecimalScalar from './scalar/Decimal'
import Decimal from 'decimal.js-light'
import { getApiResolvers } from './api/schema'
import DecimalScalar from "./scalar/Decimal";
import Decimal from "decimal.js-light";
import { getApiResolvers } from "./api/schema";
const schema = async (): Promise<GraphQLSchema> => {
return await buildSchema({
resolvers: [getApiResolvers()],
// authChecker: isAuthorized,
scalarsMap: [{ type: Decimal, scalar: DecimalScalar }],
})
}
});
};
export default schema
export default schema;

View File

@ -1,28 +1,33 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import createServer from './server/createServer'
import createServer from "./server/createServer";
// config
import CONFIG from './config'
import CONFIG from "./config";
async function main() {
// eslint-disable-next-line no-console
console.log(`FEDERATION_PORT=${CONFIG.FEDERATION_PORT}`)
console.log(`FEDERATION_API=${CONFIG.FEDERATION_API}`)
const { app } = await createServer()
console.log(`FEDERATION_PORT=${CONFIG.FEDERATION_PORT}`);
// eslint-disable-next-line no-console
console.log(`FEDERATION_API=${CONFIG.FEDERATION_API}`);
const { app } = await createServer();
app.listen(CONFIG.FEDERATION_PORT, () => {
// eslint-disable-next-line no-console
console.log(`Server is running at http://localhost:${CONFIG.FEDERATION_PORT}`)
console.log(
`Server is running at http://localhost:${CONFIG.FEDERATION_PORT}`
);
if (CONFIG.GRAPHIQL) {
// eslint-disable-next-line no-console
console.log(`GraphIQL available at http://localhost:${CONFIG.FEDERATION_PORT}`)
console.log(
`GraphIQL available at http://localhost:${CONFIG.FEDERATION_PORT}`
);
}
})
});
}
main().catch((e) => {
// eslint-disable-next-line no-console
console.error(e)
process.exit(1)
})
console.error(e);
process.exit(1);
});

View File

@ -1,8 +1,8 @@
import cors from 'cors'
import cors from "cors";
const corsOptions = {
origin: '*',
exposedHeaders: ['token'],
}
origin: "*",
exposedHeaders: ["token"],
};
export default cors(corsOptions)
export default cors(corsOptions);

View File

@ -1,29 +1,29 @@
import 'reflect-metadata'
import "reflect-metadata";
import { ApolloServer } from 'apollo-server-express'
import express, { Express } from 'express'
import { ApolloServer } from "apollo-server-express";
import express, { Express } from "express";
// database
import connection from '@/typeorm/connection'
import { checkDBVersion } from '@/typeorm/DBVersion'
import connection from "@/typeorm/connection";
import { checkDBVersion } from "@/typeorm/DBVersion";
// server
import cors from './cors'
import cors from "./cors";
// import serverContext from './context'
import plugins from './plugins'
import plugins from "./plugins";
// config
import CONFIG from '@/config'
import CONFIG from "@/config";
// graphql
import schema from '@/graphql/schema'
import schema from "@/graphql/schema";
// webhooks
// import { elopageWebhook } from '@/webhook/elopage'
import { Connection } from '@dbTools/typeorm'
import { Connection } from "@dbTools/typeorm";
import { apolloLogger } from './logger'
import { Logger } from 'log4js'
import { apolloLogger } from "./logger";
import { Logger } from "log4js";
// i18n
// import { i18n } from './localization'
@ -31,41 +31,41 @@ import { Logger } from 'log4js'
// TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
type ServerDef = { apollo: ApolloServer; app: Express; con: Connection }
type ServerDef = { apollo: ApolloServer; app: Express; con: Connection };
const createServer = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// context: any = serverContext,
logger: Logger = apolloLogger,
logger: Logger = apolloLogger
// localization: i18n.I18n = i18n,
): Promise<ServerDef> => {
logger.addContext('user', 'unknown')
logger.debug('createServer...')
logger.addContext("user", "unknown");
logger.debug("createServer...");
// open mysql connection
const con = await connection()
const con = await connection();
if (!con || !con.isConnected) {
logger.fatal(`Couldn't open connection to database!`)
throw new Error(`Fatal: Couldn't open connection to database`)
logger.fatal(`Couldn't open connection to database!`);
throw new Error(`Fatal: Couldn't open connection to database`);
}
// check for correct database version
const dbVersion = await checkDBVersion(CONFIG.DB_VERSION)
const dbVersion = await checkDBVersion(CONFIG.DB_VERSION);
if (!dbVersion) {
logger.fatal('Fatal: Database Version incorrect')
throw new Error('Fatal: Database Version incorrect')
logger.fatal("Fatal: Database Version incorrect");
throw new Error("Fatal: Database Version incorrect");
}
// Express Server
const app = express()
const app = express();
// cors
app.use(cors)
app.use(cors);
// bodyparser json
app.use(express.json())
app.use(express.json());
// bodyparser urlencoded for elopage
app.use(express.urlencoded({ extended: true }))
app.use(express.urlencoded({ extended: true }));
// i18n
// app.use(localization.init)
@ -81,11 +81,11 @@ const createServer = async (
// context,
plugins,
logger,
})
apollo.applyMiddleware({ app, path: '/' })
logger.debug('createServer...successful')
});
apollo.applyMiddleware({ app, path: "/" });
logger.debug("createServer...successful");
return { apollo, app, con }
}
return { apollo, app, con };
};
export default createServer
export default createServer;

View File

@ -1,29 +1,43 @@
import log4js from 'log4js'
import CONFIG from '@/config'
import log4js from "log4js";
import CONFIG from "@/config";
import { readFileSync } from 'fs'
import { readFileSync } from "fs";
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8'))
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, "utf-8"));
options.categories.backend.level = CONFIG.LOG_LEVEL
options.categories.apollo.level = CONFIG.LOG_LEVEL
let filename: string = options.appenders.federation.filename
options.appenders.federation.filename = filename.replace('%v', CONFIG.FEDERATION_API).replace('%p', CONFIG.FEDERATION_PORT.toString())
filename = options.appenders.access.filename
options.appenders.access.filename = filename.replace('%p', CONFIG.FEDERATION_PORT.toString())
filename = options.appenders.apollo.filename
options.appenders.apollo.filename = filename.replace('%p', CONFIG.FEDERATION_PORT.toString())
filename = options.appenders.backend.filename
options.appenders.backend.filename = filename.replace('%p', CONFIG.FEDERATION_PORT.toString())
filename = options.appenders.errorFile.filename
options.appenders.errorFile.filename = filename.replace('%p', CONFIG.FEDERATION_PORT.toString())
options.categories.backend.level = CONFIG.LOG_LEVEL;
options.categories.apollo.level = CONFIG.LOG_LEVEL;
let filename: string = options.appenders.federation.filename;
options.appenders.federation.filename = filename
.replace("%v", CONFIG.FEDERATION_API)
.replace("%p", CONFIG.FEDERATION_PORT.toString());
filename = options.appenders.access.filename;
options.appenders.access.filename = filename.replace(
"%p",
CONFIG.FEDERATION_PORT.toString()
);
filename = options.appenders.apollo.filename;
options.appenders.apollo.filename = filename.replace(
"%p",
CONFIG.FEDERATION_PORT.toString()
);
filename = options.appenders.backend.filename;
options.appenders.backend.filename = filename.replace(
"%p",
CONFIG.FEDERATION_PORT.toString()
);
filename = options.appenders.errorFile.filename;
options.appenders.errorFile.filename = filename.replace(
"%p",
CONFIG.FEDERATION_PORT.toString()
);
log4js.configure(options)
log4js.configure(options);
const apolloLogger = log4js.getLogger('apollo')
const apolloLogger = log4js.getLogger("apollo");
// const backendLogger = log4js.getLogger('backend')
const federationLogger = log4js.getLogger('federation')
const federationLogger = log4js.getLogger("federation");
// backendLogger.addContext('user', 'unknown')
export { apolloLogger, federationLogger }
export { apolloLogger, federationLogger };

View File

@ -1,61 +1,69 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import clonedeep from 'lodash.clonedeep'
import clonedeep from "lodash.clonedeep";
const setHeadersPlugin = {
requestDidStart() {
return {
willSendResponse(requestContext: any) {
const { setHeaders = [] } = requestContext.context
const { setHeaders = [] } = requestContext.context;
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
if (requestContext.response.http.headers.get(key)) {
requestContext.response.http.headers.set(key, value)
requestContext.response.http.headers.set(key, value);
} else {
requestContext.response.http.headers.append(key, value)
requestContext.response.http.headers.append(key, value);
}
})
return requestContext
});
return requestContext;
},
}
};
},
}
};
const filterVariables = (variables: any) => {
const vars = clonedeep(variables)
if (vars.password) vars.password = '***'
if (vars.passwordNew) vars.passwordNew = '***'
return vars
}
const vars = clonedeep(variables);
if (vars.password) vars.password = "***";
if (vars.passwordNew) vars.passwordNew = "***";
return vars;
};
const logPlugin = {
requestDidStart(requestContext: any) {
const { logger } = requestContext
const { query, mutation, variables, operationName } = requestContext.request
if (operationName !== 'IntrospectionQuery') {
const { logger } = requestContext;
const { query, mutation, variables, operationName } =
requestContext.request;
if (operationName !== "IntrospectionQuery") {
logger.info(`Request:
${mutation || query}variables: ${JSON.stringify(filterVariables(variables), null, 2)}`)
${mutation || query}variables: ${JSON.stringify(
filterVariables(variables),
null,
2
)}`);
}
return {
willSendResponse(requestContext: any) {
if (operationName !== 'IntrospectionQuery') {
if (requestContext.context.user) logger.info(`User ID: ${requestContext.context.user.id}`)
if (operationName !== "IntrospectionQuery") {
if (requestContext.context.user)
logger.info(`User ID: ${requestContext.context.user.id}`);
if (requestContext.response.data) {
logger.info('Response Success!')
logger.info("Response Success!");
logger.trace(`Response-Data:
${JSON.stringify(requestContext.response.data, null, 2)}`)
${JSON.stringify(requestContext.response.data, null, 2)}`);
}
if (requestContext.response.errors)
logger.error(`Response-Errors:
${JSON.stringify(requestContext.response.errors, null, 2)}`)
${JSON.stringify(requestContext.response.errors, null, 2)}`);
}
return requestContext
return requestContext;
},
}
};
},
}
};
const plugins =
process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, logPlugin]
process.env.NODE_ENV === "development"
? [setHeadersPlugin]
: [setHeadersPlugin, logPlugin];
export default plugins
export default plugins;

View File

@ -1,27 +1,27 @@
import { Migration } from '@entity/Migration'
import { federationLogger as logger } from '@/server/logger'
import { Migration } from "@entity/Migration";
import { federationLogger as logger } from "@/server/logger";
const getDBVersion = async (): Promise<string | null> => {
try {
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
return dbVersion ? dbVersion.fileName : null
const dbVersion = await Migration.findOne({ order: { version: "DESC" } });
return dbVersion ? dbVersion.fileName : null;
} catch (error) {
logger.error(error)
return null
logger.error(error);
return null;
}
}
};
const checkDBVersion = async (DB_VERSION: string): Promise<boolean> => {
const dbVersion = await getDBVersion()
const dbVersion = await getDBVersion();
if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) {
logger.error(
`Wrong database version detected - the backend requires '${DB_VERSION}' but found '${
dbVersion || 'None'
}`,
)
return false
dbVersion || "None"
}`
);
return false;
}
return true
}
return true;
};
export { checkDBVersion, getDBVersion }
export { checkDBVersion, getDBVersion };

View File

@ -1,14 +1,14 @@
// TODO This is super weird - since the entities are defined in another project they have their own globals.
// We cannot use our connection here, but must use the external typeorm installation
import { Connection, createConnection, FileLogger } from '@dbTools/typeorm'
import CONFIG from '@/config'
import { entities } from '@entity/index'
import { Connection, createConnection, FileLogger } from "@dbTools/typeorm";
import CONFIG from "@/config";
import { entities } from "@entity/index";
const connection = async (): Promise<Connection | null> => {
try {
return createConnection({
name: 'default',
type: 'mysql',
name: "default",
type: "mysql",
host: CONFIG.DB_HOST,
port: CONFIG.DB_PORT,
username: CONFIG.DB_USER,
@ -17,18 +17,18 @@ const connection = async (): Promise<Connection | null> => {
entities,
synchronize: false,
logging: true,
logger: new FileLogger('all', {
logger: new FileLogger("all", {
logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH,
}),
extra: {
charset: 'utf8mb4_unicode_ci',
charset: "utf8mb4_unicode_ci",
},
})
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
return null
console.log(error);
return null;
}
}
};
export default connection
export default connection;