Skip to content

Commit

Permalink
feat: 🔥 more informative swagger
Browse files Browse the repository at this point in the history
more informative swagger
  • Loading branch information
tal-rofe committed Jul 30, 2022
1 parent 63e06c6 commit b09928c
Show file tree
Hide file tree
Showing 33 changed files with 280 additions and 44 deletions.
3 changes: 2 additions & 1 deletion apps/backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ async function bootstrap() {
.setTitle('Exlint Dashboard')
.setDescription('The Exlint Dashboard API Descritpion')
.setVersion('1.0')
.addTag('exlint')
.addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'Token' }, 'access-token')
.addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'Token' }, 'refresh-token')
.build();

const document = SwaggerModule.createDocument(app, swaggerConfig);
Expand Down
23 changes: 21 additions & 2 deletions apps/backend/src/modules/user/modules/auth/auto-auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,48 @@ import {
UseGuards,
} from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import {
ApiBearerAuth,
ApiInternalServerErrorResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { Public } from '@/decorators/public.decorator';
import { CurrentUserEmail } from '@/decorators/current-user-email.decorator';

import { AuthService } from './auth.service';
import type { IAutoLoginResponse } from './interfaces/responses';
import { AutoLoginResponse } from './classes/responses';
import { RefreshTokenGuard } from './guards/refresh-token.guard';
import { JwtTokenType } from './models/jwt-token';
import Routes from './auth.routes';
import { AutoAuthContract } from './queries/contracts/auto-auth.contract';
import type { IAutoAuthLoggedUser } from './interfaces/user';

@ApiTags('Auth')
@Controller(Routes.CONTROLLER)
export class AutoAuthController {
private readonly logger = new Logger(AutoAuthController.name);

constructor(private readonly queryBus: QueryBus, private readonly authService: AuthService) {}

@ApiBearerAuth('refresh-token')
@ApiOperation({ description: 'Auto authentication if providing refresh token' })
@ApiOkResponse({
description: 'If successfully fulfill the auto auth request',
type: AutoLoginResponse,
})
@ApiUnauthorizedResponse({
description: 'If could not find a logged user, or refresh token is either missing or invalid',
})
@ApiInternalServerErrorResponse({ description: 'If failed to retreive logged user' })
@Public()
@UseGuards(RefreshTokenGuard)
@Get(Routes.AUTO_AUTH)
@HttpCode(HttpStatus.OK)
public async autoAuth(@CurrentUserEmail() userEmail: string): Promise<IAutoLoginResponse> {
public async autoAuth(@CurrentUserEmail() userEmail: string): Promise<AutoLoginResponse> {
this.logger.log(`Will try to auto auth with data email: "${userEmail}"`);

const loggedUser = await this.queryBus.execute<AutoAuthContract, IAutoAuthLoggedUser | null>(
Expand Down
27 changes: 27 additions & 0 deletions apps/backend/src/modules/user/modules/auth/classes/responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ApiResponseProperty } from '@nestjs/swagger';

import type { IAutoAuthLoggedUser } from '../interfaces/user';

export class RefreshTokenResponse {
@ApiResponseProperty({
type: String,
example:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
})
public accessToken!: string;
}

export class AutoLoginResponse implements IAutoAuthLoggedUser {
@ApiResponseProperty({
type: String,
example:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
})
public accessToken!: string;

@ApiResponseProperty({ type: String, example: '62e5362119bea07115434f4a' })
public id!: string;

@ApiResponseProperty({ type: String, example: 'Yazif' })
public name!: string;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { Controller, Delete, HttpCode, HttpStatus, Logger } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';
import { DeleteUserContract } from './commands/contracts/delete-user.contract';
import Routes from './auth.routes';

@ApiTags('Auth')
@Controller(Routes.CONTROLLER)
export class DeleteController {
private readonly logger = new Logger(DeleteController.name);

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({
description: 'Deleting a user',
})
@ApiBearerAuth('access-token')
@Delete(Routes.DELETE)
@HttpCode(HttpStatus.OK)
public async delete(@CurrentUserId() userId: string): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { CommandBus, EventBus, QueryBus } from '@nestjs/cqrs';
import { RealIP } from 'nestjs-real-ip';
import { ConfigService } from '@nestjs/config';
import { ApiOperation, ApiTags } from '@nestjs/swagger';

import { Public } from '@/decorators/public.decorator';
import { ExternalAuthUser } from '@/decorators/external-auth-user.decorator';
Expand All @@ -29,6 +30,7 @@ import { LoginMixpanelContract } from './events/contracts/login-mixpanel.contrac
import { JwtTokenType } from './models/jwt-token';
import { ExternalAuthFilter } from './filters/external-auth.filter';

@ApiTags('Auth')
@Controller(Routes.CONTROLLER)
export class GithubController {
private readonly logger = new Logger(GithubController.name);
Expand All @@ -41,13 +43,17 @@ export class GithubController {
private readonly configService: ConfigService<IEnvironment, true>,
) {}

@ApiOperation({ description: 'A redirect URL to enter GitHub OAuth app' })
@Public()
@UseGuards(GithubAuthGuard)
@Get(Routes.GITHUB_AUTH)
public githubAuth() {
return;
}

@ApiOperation({
description: 'A redirect URL used by GitHub to send back the server the user data',
})
@Public()
@UseGuards(GithubAuthGuard)
@UseFilters(ExternalAuthFilter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { CommandBus, EventBus, QueryBus } from '@nestjs/cqrs';
import { RealIP } from 'nestjs-real-ip';
import { ConfigService } from '@nestjs/config';
import { ApiOperation, ApiTags } from '@nestjs/swagger';

import { Public } from '@/decorators/public.decorator';
import { ExternalAuthUser } from '@/decorators/external-auth-user.decorator';
Expand All @@ -31,6 +32,7 @@ import { LoginMixpanelContract } from './events/contracts/login-mixpanel.contrac
import { JwtTokenType } from './models/jwt-token';
import { ExternalAuthFilter } from './filters/external-auth.filter';

@ApiTags('Auth')
@Controller(Routes.CONTROLLER)
export class GoogleController {
private readonly logger = new Logger(GoogleController.name);
Expand All @@ -43,13 +45,17 @@ export class GoogleController {
private readonly configService: ConfigService<IEnvironment, true>,
) {}

@ApiOperation({ description: 'A redirect URL to enter Google OAuth app' })
@Public()
@UseGuards(GoogleAuthGuard)
@Get(Routes.GOOGLE_AUTH)
public googleAuth() {
return;
}

@ApiOperation({
description: 'A redirect URL used by Google to send back the server the user data',
})
@Public()
@UseGuards(GoogleAuthGuard)
@UseFilters(ExternalAuthFilter)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Controller, Get, HttpCode, HttpStatus, Logger, UseGuards } from '@nestjs/common';
import {
ApiBearerAuth,
ApiInternalServerErrorResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';

import { Public } from '@/decorators/public.decorator';
import { CurrentUserId } from '@/decorators/current-user-id.decorator';
Expand All @@ -7,23 +14,31 @@ import { CurrentUserEmail } from '@/decorators/current-user-email.decorator';
import { RefreshTokenGuard } from './guards/refresh-token.guard';
import { AuthService } from './auth.service';
import { JwtTokenType } from './models/jwt-token';
import type { IRefreshTokenResponse } from './interfaces/responses';
import { RefreshTokenResponse } from './classes/responses';
import Routes from './auth.routes';

@ApiTags('Auth')
@Controller(Routes.CONTROLLER)
export class RefreshTokenController {
private readonly logger = new Logger(RefreshTokenController.name);

constructor(private readonly authService: AuthService) {}

@ApiBearerAuth('refresh-token')
@ApiOperation({ description: 'Get a new access token by providing a refresh token' })
@ApiOkResponse({
description: 'If successfully refreshed a new access token',
type: RefreshTokenResponse,
})
@ApiInternalServerErrorResponse({ description: 'If failed to refresh a new access token' })
@Public()
@UseGuards(RefreshTokenGuard)
@Get(Routes.REFRESH_TOKEN)
@HttpCode(HttpStatus.OK)
public async refreshToken(
@CurrentUserId() userId: string,
@CurrentUserEmail() userEmail: string,
): Promise<IRefreshTokenResponse> {
): Promise<RefreshTokenResponse> {
this.logger.log(`Will try to generate access token for a user with an Id: "${userId}"`);

const accessToken = await this.authService.generateJwtToken(userId, userEmail, JwtTokenType.Access);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, MinLength } from 'class-validator';

export class EditLabelDto {
@ApiProperty({ type: String, description: 'The new label for a group' })
@IsString()
@MinLength(1)
readonly label!: string;
Expand Down
63 changes: 63 additions & 0 deletions apps/backend/src/modules/user/modules/groups/classes/responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ApiResponseProperty } from '@nestjs/swagger';
import { PolicyLibrary } from '@prisma/client';

import type { IUserGroupGetAll, IUserGroupInlinePolicy } from '../interfaces/user-group';

class UserGroupInlinePolicyGetAll implements IUserGroupInlinePolicy {
@ApiResponseProperty({
type: String,
example: '62e5362119bea07115434f4a',
})
public id!: string;

@ApiResponseProperty({
type: String,
example: 'Yazif Policy',
})
public label!: string;

@ApiResponseProperty({
enum: PolicyLibrary,
})
public library!: PolicyLibrary;

@ApiResponseProperty({
type: Number,
example: 5,
})
public rulesCount!: number;
}

class UserGroupGetAll implements IUserGroupGetAll {
@ApiResponseProperty({
type: String,
example: '62e5362119bea07115434f4a',
})
public id!: string;

@ApiResponseProperty({
type: String,
example: 'Yazif Group',
})
public label!: string;

@ApiResponseProperty({
type: [UserGroupInlinePolicyGetAll],
})
public inlinePolicies!: IUserGroupInlinePolicy[];
}

export class CreateGroupResponse {
@ApiResponseProperty({
type: String,
example: '62e5362119bea07115434f4a',
})
public groupId!: string;
}

export class GetAllGroupsResponse {
@ApiResponseProperty({
type: [UserGroupGetAll],
})
public groups!: IUserGroupGetAll[];
}
24 changes: 18 additions & 6 deletions apps/backend/src/modules/user/modules/groups/create.controller.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import { Controller, Logger, Post } from '@nestjs/common';
import { Controller, HttpCode, HttpStatus, Logger, Post } from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import { RealIP } from 'nestjs-real-ip';
import {
ApiBearerAuth,
ApiCreatedResponse,
ApiInternalServerErrorResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';

import Routes from './groups.routes';
import type { ICreateGroupResponse } from './interfaces/responses';
import { CreateGroupResponse } from './classes/responses';
import { CreateGroupContract } from './queries/contracts/create-group.contact';

@ApiTags('Groups')
@Controller(Routes.CONTROLLER)
export class CreateController {
private readonly logger = new Logger(CreateController.name);

constructor(private readonly queryBus: QueryBus) {}

@ApiBearerAuth('access-token')
@ApiOperation({ description: 'Creating a group with default label' })
@ApiCreatedResponse({ description: 'If successfully created a group', type: CreateGroupResponse })
@ApiUnauthorizedResponse({ description: 'If access token is either missing or invalid' })
@ApiInternalServerErrorResponse({ description: 'If failed to create the group' })
@Post(Routes.CREATE)
public async create(
@CurrentUserId() userId: string,
@RealIP() ip: string,
): Promise<ICreateGroupResponse> {
@HttpCode(HttpStatus.CREATED)
public async create(@CurrentUserId() userId: string, @RealIP() ip: string): Promise<CreateGroupResponse> {
this.logger.log(`Will try to create a group for a user with an Id: ${userId}`);

const createdGroupId = await this.queryBus.execute<CreateGroupContract, string>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { Controller, Delete, HttpCode, HttpStatus, Logger, Param, UseGuards } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';
import { BelongingGroupGuard } from '@/guards/belonging-group.guard';

import Routes from './groups.routes';
import { DeleteContract } from './commands/contracts/delete.contract';

@ApiTags('Groups')
@Controller(Routes.CONTROLLER)
export class DeleteController {
private readonly logger = new Logger(DeleteController.name);

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: 'Deleting a group with provided identifier' })
@ApiBearerAuth('access-token')
@UseGuards(BelongingGroupGuard)
@Delete(Routes.DELETE)
@HttpCode(HttpStatus.OK)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Body, Controller, HttpCode, HttpStatus, Logger, Param, Patch, UseGuards } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';
import { BelongingGroupGuard } from '@/guards/belonging-group.guard';
Expand All @@ -8,12 +9,15 @@ import Routes from './groups.routes';
import { EditLabelDto } from './classes/edit-label.dto';
import { EditLabelContract } from './commands/contracts/edit-label.contract';

@ApiTags('Groups')
@Controller(Routes.CONTROLLER)
export class EditLabelController {
private readonly logger = new Logger(EditLabelController.name);

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: "Edit a group's label with a new label and its identifier" })
@ApiBearerAuth('access-token')
@UseGuards(BelongingGroupGuard)
@Patch(Routes.EDIT_LABEL)
@HttpCode(HttpStatus.OK)
Expand Down
Loading

0 comments on commit b09928c

Please sign in to comment.