Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate OpenAPI spec automatically #175

Merged
merged 8 commits into from
Nov 11, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -65,7 +65,6 @@ GRAPHQL_EDITOR=true
#
SWAGGER_ENABLED=true
SWAGGER_ROUTE=/swagger
SWAGGER_FILE=api/swagger.json
SWAGGER_USERNAME=admin
SWAGGER_PASSWORD=1234

1 change: 0 additions & 1 deletion .env.test
Original file line number Diff line number Diff line change
@@ -47,7 +47,6 @@ GRAPHQL_EDITOR=false
#
SWAGGER_ENABLED=true
SWAGGER_ROUTE=/swagger
SWAGGER_FILE=api/swagger.json
SWAGGER_USERNAME=admin
SWAGGER_PASSWORD=1234

3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ Try it!! We are happy to hear your feedback or any kind of new features.
- **Easy Exception Handling** thanks to [routing-controllers](https://github.com/pleerock/routing-controllers).
- **Smart Validation** thanks to [class-validator](https://github.com/pleerock/class-validator) with some nice annotations.
- **Custom Validators** to validate your request even better and stricter. [custom-validation-classes](https://github.com/pleerock/class-validator#custom-validation-classes).
- **API Documentation** thanks to [swagger](http://swagger.io/).
- **API Documentation** thanks to [swagger](http://swagger.io/) and [routing-controllers-openapi](https://github.com/epiphone/routing-controllers-openapi).
- **API Monitoring** thanks to [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor).
- **Integrated Testing Tool** thanks to [Jest](https://facebook.github.io/jest).
- **E2E API Testing** thanks to [supertest](https://github.com/visionmedia/supertest).
@@ -215,7 +215,6 @@ The swagger and the monitor route can be altered in the `.env` file.
| **src/api/resolvers/** | GraphQL resolvers (query, mutation & field-resolver) |
| **src/api/types/** | GraphQL types ,input-types and scalar types |
| **src/api/** schema.gql | Generated GraphQL schema |
| **src/api/** swagger.json | Swagger documentation |
| **src/auth/** | Authentication checkers and services |
| **src/core/** | The core features like logger and env variables |
| **src/database/factories** | Factory the generate fake entities |
8 changes: 0 additions & 8 deletions package-scripts.js
Original file line number Diff line number Diff line change
@@ -106,18 +106,10 @@ module.exports = {
copy: {
default: {
script: series(
`nps copy.swagger`,
`nps copy.public`
),
hiddenFromHelp: true
},
swagger: {
script: copy(
'./src/api/swagger.json',
'./dist'
),
hiddenFromHelp: true
},
public: {
script: copy(
'./src/public/*',
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@
"bcrypt": "3.0.1",
"chalk": "^2.4.1",
"class-validator": "^0.9.1",
"class-validator-jsonschema": "^1.3.0",
"commander": "^2.19.0",
"compression": "^1.7.1",
"copyfiles": "^2.1.0",
@@ -67,6 +68,7 @@
"pg": "^7.4.3",
"reflect-metadata": "^0.1.10",
"routing-controllers": "^0.7.6",
"routing-controllers-openapi": "^1.7.0",
"serve-favicon": "^2.4.5",
"supertest": "^3.0.0",
"swagger-ui-express": "4.0.1",
@@ -77,7 +79,7 @@
"typeorm": "^0.2.5",
"typeorm-seeding": "^1.0.0-beta.6",
"typeorm-typedi-extensions": "^0.2.1",
"typescript": "3.0.3",
"typescript": "^3.6.3",
"uuid": "^3.3.2",
"winston": "3.1.0"
},
@@ -113,6 +115,7 @@
"@types/jest": "23.3.2",
"@types/morgan": "^1.7.35",
"@types/nock": "^9.1.3",
"@types/node": "^12.7.5",
"@types/reflect-metadata": "0.1.0",
"@types/serve-favicon": "^2.2.29",
"@types/supertest": "^2.0.4",
42 changes: 40 additions & 2 deletions src/api/controllers/PetController.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,75 @@
import { IsNotEmpty, IsNumber, IsUUID, ValidateNested } from 'class-validator';
import {
Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put
} from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';

import { PetNotFoundError } from '../errors/PetNotFoundError';
import { Pet } from '../models/Pet';
import { PetService } from '../services/PetService';
import { UserResponse } from './UserController';

class BasePet {
@IsNotEmpty()
public name: string;

@IsNumber()
public age: number;
}

export class PetResponse extends BasePet {
@IsUUID()
public id: string;

@ValidateNested()
public user: UserResponse;
}

class CreatePetBody extends BasePet {
@IsUUID()
public userId: string;
}

@Authorized()
@JsonController('/pets')
@OpenAPI({ security: [{ basicAuth: [] }] })
export class PetController {

constructor(
private petService: PetService
) { }

@Get()
@ResponseSchema(PetResponse, { isArray: true })
public find(): Promise<Pet[]> {
return this.petService.find();
}

@Get('/:id')
@OnUndefined(PetNotFoundError)
@ResponseSchema(PetResponse)
public one(@Param('id') id: string): Promise<Pet | undefined> {
return this.petService.findOne(id);
}

@Post()
public create(@Body() pet: Pet): Promise<Pet> {
@ResponseSchema(PetResponse)
public create(@Body({ required: true }) body: CreatePetBody): Promise<Pet> {
const pet = new Pet();
pet.age = body.age;
pet.name = body.name;
pet.userId = body.userId;

return this.petService.create(pet);
}

@Put('/:id')
public update(@Param('id') id: string, @Body() pet: Pet): Promise<Pet> {
@ResponseSchema(PetResponse)
public update(@Param('id') id: string, @Body() body: BasePet): Promise<Pet> {
const pet = new Pet();
pet.age = body.age;
pet.name = body.name;

return this.petService.update(id, pet);
}

56 changes: 54 additions & 2 deletions src/api/controllers/UserController.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,94 @@
import { Type } from 'class-transformer';
import { IsEmail, IsNotEmpty, IsUUID, ValidateNested } from 'class-validator';
import {
Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, Req
} from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';

import { UserNotFoundError } from '../errors/UserNotFoundError';
import { User } from '../models/User';
import { UserService } from '../services/UserService';
import { PetResponse } from './PetController';

class BaseUser {
@IsNotEmpty()
public firstName: string;

@IsNotEmpty()
public lastName: string;

@IsEmail()
@IsNotEmpty()
public email: string;

@IsNotEmpty()
public username: string;
}

export class UserResponse extends BaseUser {
@IsUUID()
public id: string;

@ValidateNested({ each: true })
@Type(() => PetResponse)
public pets: PetResponse[];
}

class CreateUserBody extends BaseUser {
@IsNotEmpty()
public password: string;
}

@Authorized()
@JsonController('/users')
@OpenAPI({ security: [{ basicAuth: [] }] })
export class UserController {

constructor(
private userService: UserService
) { }

@Get()
@ResponseSchema(UserResponse, { isArray: true })
public find(): Promise<User[]> {
return this.userService.find();
}

@Get('/me')
@ResponseSchema(UserResponse, { isArray: true })
public findMe(@Req() req: any): Promise<User[]> {
return req.user;
}

@Get('/:id')
@OnUndefined(UserNotFoundError)
@ResponseSchema(UserResponse)
public one(@Param('id') id: string): Promise<User | undefined> {
return this.userService.findOne(id);
}

@Post()
public create(@Body() user: User): Promise<User> {
@ResponseSchema(UserResponse)
public create(@Body() body: CreateUserBody): Promise<User> {
const user = new User();
user.email = body.email;
user.firstName = body.firstName;
user.lastName = body.lastName;
user.password = body.password;
user.username = body.username;

return this.userService.create(user);
}

@Put('/:id')
public update(@Param('id') id: string, @Body() user: User): Promise<User> {
@ResponseSchema(UserResponse)
public update(@Param('id') id: string, @Body() body: BaseUser): Promise<User> {
const user = new User();
user.email = body.email;
user.firstName = body.firstName;
user.lastName = body.lastName;
user.username = body.username;

return this.userService.update(id, user);
}

Loading