diff --git a/backend/src/database/connection.ts b/backend/src/database/connection.ts deleted file mode 100644 index 584b657d2..000000000 --- a/backend/src/database/connection.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createConnection, Connection } from 'mysql2/promise' -import CONFIG from '../config' - -const connection = async (): Promise => { - const con = await createConnection({ - host: CONFIG.DB_HOST, - port: CONFIG.DB_PORT, - user: CONFIG.DB_USER, - password: CONFIG.DB_PASSWORD, - database: CONFIG.DB_DATABASE, - }) - - await con.connect() - - return con -} - -export default connection diff --git a/backend/src/graphql/resolvers/index.ts b/backend/src/graphql/resolvers/index.ts new file mode 100644 index 000000000..f42829645 --- /dev/null +++ b/backend/src/graphql/resolvers/index.ts @@ -0,0 +1,19 @@ +import { UserResolver } from './UserResolver' +import { BalanceResolver } from './BalanceResolver' +import { GdtResolver } from './GdtResolver' +import { TransactionResolver } from './TransactionResolver' +import { KlicktippResolver } from './KlicktippResolver' +import { NonEmptyArray } from 'type-graphql' + +export { UserResolver, BalanceResolver, GdtResolver, TransactionResolver, KlicktippResolver } + +// eslint-disable-next-line @typescript-eslint/ban-types +const resolvers = (): NonEmptyArray => [ + UserResolver, + BalanceResolver, + GdtResolver, + TransactionResolver, + KlicktippResolver, +] + +export default resolvers diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts new file mode 100644 index 000000000..f18a3bea6 --- /dev/null +++ b/backend/src/graphql/schema.ts @@ -0,0 +1,14 @@ +import { GraphQLSchema } from 'graphql' +import { buildSchema } from 'type-graphql' + +import resolvers from './resolvers' +import { isAuthorized } from '../auth/auth' + +const schema = async (): Promise => { + return buildSchema({ + resolvers: resolvers(), + authChecker: isAuthorized, + }) +} + +export default schema diff --git a/backend/src/index.ts b/backend/src/index.ts index bd58f8c86..1c3814096 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -2,98 +2,58 @@ import 'reflect-metadata' import express from 'express' -import cors from 'cors' -import { buildSchema } from 'type-graphql' import { ApolloServer } from 'apollo-server-express' -import { RowDataPacket } from 'mysql2/promise' -import connection from './database/connection' -import typeOrmConnection from './typeorm/connection' +// config import CONFIG from './config' -// TODO move to extern -import { UserResolver } from './graphql/resolvers/UserResolver' -import { BalanceResolver } from './graphql/resolvers/BalanceResolver' -import { GdtResolver } from './graphql/resolvers/GdtResolver' -import { TransactionResolver } from './graphql/resolvers/TransactionResolver' -import { KlicktippResolver } from './graphql/resolvers/KlicktippResolver' +// database +import connection from './typeorm/connection' +import getDBVersion from './typeorm/getDBVersion' -import { isAuthorized } from './auth/auth' +// server +import cors from './server/cors' +import context from './server/context' +import plugins from './server/plugins' + +// graphql +import schema from './graphql/schema' // TODO implement // import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity"; const DB_VERSION = '0001-init_db' -const context = (args: any) => { - const authorization = args.req.headers.authorization - let token = null - if (authorization) { - token = authorization.replace(/^Bearer /, '') - } - const context = { - token, - setHeaders: [], - } - return context -} - async function main() { - // check for correct database version + // open mysql connection const con = await connection() - const [rows] = await con.query(`SELECT * FROM migrations ORDER BY version DESC LIMIT 1;`) - if ( - (rows).length === 0 || - !(rows)[0].fileName || - (rows)[0].fileName.indexOf(DB_VERSION) === -1 - ) { - throw new Error(`Wrong database version - the backend requires '${DB_VERSION}'`) + if (!con || !con.isConnected) { + throw new Error(`Couldn't open connection to database`) } - const toCon = await typeOrmConnection() - if (!toCon.isConnected) { - throw new Error(`Couldn't open typeorm db connection`) - } - - const schema = await buildSchema({ - resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver, KlicktippResolver], - authChecker: isAuthorized, - }) - - // Graphiql interface - let playground = false - if (CONFIG.GRAPHIQL) { - playground = true + // check for correct database version + const dbVersion = await getDBVersion() + if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) { + throw new Error( + `Wrong database version - the backend requires '${DB_VERSION}' but found '${ + dbVersion || 'None' + }'`, + ) } // Express Server const server = express() - const corsOptions = { - origin: '*', - exposedHeaders: ['token'], - } - - server.use(cors(corsOptions)) - - const plugins = [ - { - requestDidStart() { - return { - willSendResponse(requestContext: any) { - const { setHeaders = [] } = requestContext.context - setHeaders.forEach(({ key, value }: { [key: string]: string }) => { - requestContext.response.http.headers.append(key, value) - }) - return requestContext - }, - } - }, - }, - ] + // cors + server.use(cors) // Apollo Server - const apollo = new ApolloServer({ schema, playground, context, plugins }) + const apollo = new ApolloServer({ + schema: await schema(), + playground: CONFIG.GRAPHIQL, + context, + plugins, + }) apollo.applyMiddleware({ app: server }) // Start Server diff --git a/backend/src/server/context.ts b/backend/src/server/context.ts new file mode 100644 index 000000000..2ad4b520d --- /dev/null +++ b/backend/src/server/context.ts @@ -0,0 +1,14 @@ +const context = (args: any) => { + const authorization = args.req.headers.authorization + let token = null + if (authorization) { + token = authorization.replace(/^Bearer /, '') + } + const context = { + token, + setHeaders: [], + } + return context +} + +export default context diff --git a/backend/src/server/cors.ts b/backend/src/server/cors.ts new file mode 100644 index 000000000..e76ed1591 --- /dev/null +++ b/backend/src/server/cors.ts @@ -0,0 +1,8 @@ +import cors from 'cors' + +const corsOptions = { + origin: '*', + exposedHeaders: ['token'], +} + +export default cors(corsOptions) diff --git a/backend/src/server/plugins.ts b/backend/src/server/plugins.ts new file mode 100644 index 000000000..6b27d19ea --- /dev/null +++ b/backend/src/server/plugins.ts @@ -0,0 +1,17 @@ +const plugins = [ + { + requestDidStart() { + return { + willSendResponse(requestContext: any) { + const { setHeaders = [] } = requestContext.context + setHeaders.forEach(({ key, value }: { [key: string]: string }) => { + requestContext.response.http.headers.append(key, value) + }) + return requestContext + }, + } + }, + }, +] + +export default plugins diff --git a/backend/src/typeorm/connection.ts b/backend/src/typeorm/connection.ts index b108c5ef5..db7dbcb29 100644 --- a/backend/src/typeorm/connection.ts +++ b/backend/src/typeorm/connection.ts @@ -2,18 +2,21 @@ import { createConnection, Connection } from 'typeorm' import CONFIG from '../config' import path from 'path' -const connection = async (): Promise => { - const con = await createConnection({ - name: 'default', - type: 'mysql', - host: CONFIG.DB_HOST, - port: CONFIG.DB_PORT, - username: CONFIG.DB_USER, - password: CONFIG.DB_PASSWORD, - database: CONFIG.DB_DATABASE, - entities: [path.join(__dirname, 'entity', '*.ts')], - synchronize: false, - }) +const connection = async (): Promise => { + let con = null + try { + con = await createConnection({ + name: 'default', + type: 'mysql', + host: CONFIG.DB_HOST, + port: CONFIG.DB_PORT, + username: CONFIG.DB_USER, + password: CONFIG.DB_PASSWORD, + database: CONFIG.DB_DATABASE, + entities: [path.join(__dirname, 'entity', '*.ts')], + synchronize: false, + }) + } catch (error) {} return con } diff --git a/backend/src/typeorm/entity/Migration.ts b/backend/src/typeorm/entity/Migration.ts new file mode 100644 index 000000000..f1163cfbc --- /dev/null +++ b/backend/src/typeorm/entity/Migration.ts @@ -0,0 +1,13 @@ +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm' + +@Entity('migrations') +export class Migration extends BaseEntity { + @PrimaryGeneratedColumn() // This is actually not a primary column + version: number + + @Column({ length: 256, nullable: true, default: null }) + fileName: string + + @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) + date: Date +} diff --git a/backend/src/typeorm/getDBVersion.ts b/backend/src/typeorm/getDBVersion.ts new file mode 100644 index 000000000..497a6da5e --- /dev/null +++ b/backend/src/typeorm/getDBVersion.ts @@ -0,0 +1,15 @@ +import { getConnection } from 'typeorm' +import { Migration } from './entity/Migration' + +const getDBVersion = async (): Promise => { + const connection = getConnection() + const migrations = connection.getRepository(Migration) + try { + const dbVersion = await migrations.findOne({ order: { version: 'DESC' } }) + return dbVersion ? dbVersion.fileName : null + } catch (error) { + return null + } +} + +export default getDBVersion