Skip to content

Commit

Permalink
add so many middwares
Browse files Browse the repository at this point in the history
  • Loading branch information
libang committed Dec 25, 2018
1 parent 303db9f commit b8f4175
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ Thumbs.db
*.swp
config.yml
**/node_modules/
.vscode
**/.vscode
**/dist/
1 change: 1 addition & 0 deletions yoga-sample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"class-validator": "^0.9.1",
"graphql": "^14.0.2",
"graphql-query-complexity": "^0.2.2",
"graphql-yoga": "^1.16.9",
"mysql": "^2.16.0",
"reflect-metadata": "^0.1.12",
Expand Down
6 changes: 3 additions & 3 deletions yoga-sample/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ input EmployeeInput {
first_name: String!
last_name: String!
gender: String!
hire_date: DateTime = "2018-12-25T03:58:26.250Z"
hire_date: DateTime = "2018-12-25T11:08:13.492Z"
salaries: [SalaryInput!]!
titles: [TitleInput!]!
}
Expand All @@ -49,7 +49,7 @@ type Salary {
}

input SalaryInput {
from_date: DateTime = "2018-12-25T03:58:26.248Z"
from_date: DateTime = "2018-12-25T11:08:13.489Z"
to_date: DateTime!
salary: Float!
}
Expand All @@ -62,7 +62,7 @@ type Title {
}

input TitleInput {
from_date: DateTime = "2018-12-25T03:58:26.250Z"
from_date: DateTime = "2018-12-25T11:08:13.491Z"
to_date: DateTime!
title: String!
}
24 changes: 20 additions & 4 deletions yoga-sample/src/entity/Employee.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql';
import { Column, Entity, ManyToOne, PrimaryColumn, OneToMany } from 'typeorm';
import {
ObjectType,
Field,
ID,
registerEnumType,
UseMiddleware,
Authorized
} from 'type-graphql';
import { Column, Entity, PrimaryColumn } from 'typeorm';
import { Salary } from './Salary';
import { Title } from './Title';
import { LogAccessMiddleware } from '../middleware';

export enum Gender {
Male = 'M',
Expand All @@ -17,9 +25,11 @@ registerEnumType(Gender, {
@ObjectType()
export class Employee {
@Field(type => ID)
@UseMiddleware(LogAccessMiddleware)
@PrimaryColumn()
readonly emp_no: number;

@Authorized()
@Field(type => String)
@Column({ type: 'date' })
birth_date: Date;
Expand All @@ -32,19 +42,25 @@ export class Employee {
@Column()
last_name: string;

@Authorized('ADMIN')
@Field(type => Gender)
@Column('enum', { enum: Gender })
gender: Gender;

@Authorized('REGULAR')
@Field(type => String)
@Column({ type: 'date' })
hire_date: Date;

@Field(type => [Title])
@Field(type => [Title], {
complexity: 3
})
// @OneToMany(type => Title, title => title.employee)
titles: Title[];

@Field(type => [Salary])
@Field(type => [Salary], {
complexity: 3
})
// @OneToMany(type => Salary, salary => salary.employee)
salaries: Salary[];
}
69 changes: 61 additions & 8 deletions yoga-sample/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import 'reflect-metadata';
import queryComplexity, {
simpleEstimator,
fieldConfigEstimator
} from 'graphql-query-complexity';
import { GraphQLServer } from 'graphql-yoga';
import { Container } from 'typedi';
import * as TypeORM from 'typeorm';
import * as TypeGraphQL from 'type-graphql';

import { EmployeeResolver } from './resolver/employee-resolver';
import { ResolveTime, ErrorInterceptor, DataInterceptor } from './middleware';
import { authChecker } from './utils/auth-checker';

// register 3rd party IOC container
TypeGraphQL.useContainer(Container);
Expand All @@ -17,11 +21,9 @@ async function bootstrap() {
type: 'mysql',
host: 'localhost',
port: 3306,
// synchronize: true,
username: 'mosaice',
password: '',
database: 'employees',
logging: true,
cache: true,
charset: 'utf8mb4',
entities: ['src/entity/**/*.ts'],
Expand All @@ -36,17 +38,68 @@ async function bootstrap() {

// build TypeGraphQL executable schema
const schema = await TypeGraphQL.buildSchema({
resolvers: [EmployeeResolver],
emitSchemaFile: true
resolvers: [`${__dirname}/**/*-resolver.ts`],
emitSchemaFile: true,
authChecker,
globalMiddlewares: [ErrorInterceptor, DataInterceptor, ResolveTime]
});

// create mocked context

// Create GraphQL server
const server = new GraphQLServer({ schema });
const server = new GraphQLServer({
schema,
context: () => {
const ctx = {
// create mocked user in context
// in real app you would be mapping user from `req.user` or sth
user: {
id: 1,
name: 'Sample user',
roles: ['REGULAR']
}
};
return ctx;
}
});

// Start the server
server.start(() => console.log('Server is running on localhost:4000'));
server.start(
{
formatError: TypeGraphQL.formatArgumentValidationError,
cacheControl: true,
debug: true,
formatResponse: res => {
res.code = res.errors ? 500 : 200;
return res;
},
validationRules: req => [
queryComplexity({
// The maximum allowed query complexity, queries above this threshold will be rejected
maximumComplexity: 8,
// The query variables. This is needed because the variables are not available
// in the visitor of the graphql-js library
variables: req.query.variables,
// Optional callback function to retrieve the determined query complexity
// Will be invoked weather the query is rejected or not
// This can be used for logging or to implement rate limiting
onComplete: (complexity: number) => {
console.log('Query Complexity:', complexity);
},
estimators: [
// Using fieldConfigEstimator is mandatory to make it work with type-graphql
fieldConfigEstimator(),
// This will assign each field a complexity of 1 if no other estimator
// returned a value. We can define the default value for field not explicitly annotated
simpleEstimator({
defaultComplexity: 1
})
]
}) as any
]
},
() => console.log('Server is running on localhost:4000')
);
} catch (err) {
console.error(err);
}
Expand Down
16 changes: 16 additions & 0 deletions yoga-sample/src/middleware/error-interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MiddlewareFn } from 'type-graphql';

export const ErrorInterceptor: MiddlewareFn = async (
{ context, info },
next
) => {
try {
return await next();
} catch (err) {
// write error to file log
console.log(err, context, info);

// rethrow the error
throw err;
}
};
4 changes: 4 additions & 0 deletions yoga-sample/src/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { ErrorInterceptor } from './error-interceptor';
export { LogAccessMiddleware } from './log-access';
export { ResolveTime } from './resolve-time';
export { DataInterceptor } from './result-format';
11 changes: 11 additions & 0 deletions yoga-sample/src/middleware/log-access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MiddlewareInterface, NextFn, ResolverData } from 'type-graphql';

export class LogAccessMiddleware implements MiddlewareInterface<any> {
async use({ context, info }: ResolverData<any>, next: NextFn) {
const username: string = context.username || 'guest';
console.log(
`Logging access: ${username} -> ${info.parentType.name}.${info.fieldName}`
);
return next();
}
}
12 changes: 12 additions & 0 deletions yoga-sample/src/middleware/resolve-time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { MiddlewareFn } from 'type-graphql';

export const ResolveTime: MiddlewareFn = async ({ info }, next) => {
const isRoot = info.parentType.name === 'Query';
const start = Date.now();
await next();
const resolveTime = Date.now() - start;
if (isRoot)
console.log(
`${info.parentType.name}.${info.fieldName} [${resolveTime} ms]`
);
};
8 changes: 8 additions & 0 deletions yoga-sample/src/middleware/result-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { MiddlewareFn } from 'type-graphql';

export const DataInterceptor: MiddlewareFn = async ({ info }, next) => {
let result = await next();

if (info.fieldName === 'emp_no') result = 'ID:' + result;
return result;
};
5 changes: 4 additions & 1 deletion yoga-sample/src/resolver/employee-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
Int,
FieldResolver,
Root,
ResolverInterface
ResolverInterface,
UseMiddleware
} from 'type-graphql';
import { Min, Max } from 'class-validator';
import { Repository, getConnection } from 'typeorm';
Expand All @@ -18,6 +19,7 @@ import { Employee } from '../entity/Employee';
import { Title } from '../entity/Title';
import { Salary } from '../entity/Salary';
import { EmployeeInput } from './input/employee-input';
import { LogAccessMiddleware } from '../middleware';

@ArgsType()
class PaginationArgs {
Expand Down Expand Up @@ -54,6 +56,7 @@ export class EmployeeResolver implements ResolverInterface<Employee> {
}

@FieldResolver()
@UseMiddleware(LogAccessMiddleware)
async titles(@Root() emp: Employee) {
return await this.titleRepository.find({
where: {
Expand Down
21 changes: 21 additions & 0 deletions yoga-sample/src/utils/auth-checker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AuthChecker } from 'type-graphql';

export const authChecker: AuthChecker<any> = ({ context: { user } }, roles) => {
if (roles.length === 0) {
// if `@Authorized()`, check only is user exist
return user !== undefined;
}
// there are some roles defined now

if (!user) {
// and if no user, restrict access
return false;
}
if (user.roles.some(role => roles.includes(role))) {
// grant access if the roles overlap
return true;
}

// no roles matched, restrict access
return false;
};

0 comments on commit b8f4175

Please sign in to comment.