diff --git a/apps/api/src/auth/controller/auth.controller.ts b/apps/api/src/auth/controller/auth.controller.ts index 3bae4870..448ba5d2 100644 --- a/apps/api/src/auth/controller/auth.controller.ts +++ b/apps/api/src/auth/controller/auth.controller.ts @@ -13,7 +13,13 @@ import { import { AuthService } from '../service/auth.service' import { UserAuthenticatedResponse } from '../auth.types' import { Public } from '../../decorators/public.decorator' -import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger' +import { + ApiOperation, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags +} from '@nestjs/swagger' import { AuthGuard } from '@nestjs/passport' import { GithubOAuthStrategyFactory } from '../../config/factory/github/github-strategy.factory' import { GoogleOAuthStrategyFactory } from '../../config/factory/google/google-strategy.factory' @@ -62,12 +68,12 @@ export class AuthController { description: 'This endpoint validates OTPs. If the OTP is valid, it returns a valid token along with the user details' }) - @ApiParam({ + @ApiQuery({ name: 'email', description: 'Email to send OTP', required: true }) - @ApiParam({ + @ApiQuery({ name: 'otp', description: 'OTP to validate', required: true diff --git a/apps/api/src/common/static.ts b/apps/api/src/common/static.ts new file mode 100644 index 00000000..e43cd059 --- /dev/null +++ b/apps/api/src/common/static.ts @@ -0,0 +1,3 @@ +export const invalidAuthenticationResponse = { + description: 'Invalid authentication header or API key' +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index fe324062..049dedbf 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -97,6 +97,12 @@ async function initializeNestApp() { .setTitle('keyshade') .setDescription('The keyshade API description') .setVersion('1.0') + .addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }) + .addSecurity('api_key', { + type: 'apiKey', + in: 'header', + name: 'x-keyshade-token' + }) .build() const document = SwaggerModule.createDocument(app, swaggerConfig) SwaggerModule.setup('docs', app, document) diff --git a/apps/api/src/user/controller/user.controller.ts b/apps/api/src/user/controller/user.controller.ts index 08d3329a..5b3c9c98 100644 --- a/apps/api/src/user/controller/user.controller.ts +++ b/apps/api/src/user/controller/user.controller.ts @@ -17,22 +17,53 @@ import { UpdateUserDto } from '../dto/update.user/update.user' import { AdminGuard } from '../../auth/guard/admin/admin.guard' import { CreateUserDto } from '../dto/create.user/create.user' import { + ApiBearerAuth, ApiCreatedResponse, + ApiForbiddenResponse, ApiNoContentResponse, + ApiOkResponse, + ApiOperation, + ApiSecurity, ApiTags } from '@nestjs/swagger' import { BypassOnboarding } from '../../decorators/bypass-onboarding.decorator' import { RequiredApiKeyAuthorities } from '../../decorators/required-api-key-authorities.decorator' import { ForbidApiKey } from '../../decorators/forbid-api-key.decorator' +import { invalidAuthenticationResponse } from '../../common/static' + +const userSchema = { + type: 'object', + properties: { + id: { type: 'string' }, + email: { type: 'string' }, + name: { type: 'string' }, + profilePictureUrl: { type: 'string' }, + isAdmin: { type: 'boolean' }, + isActive: { type: 'boolean' }, + isOnboardingFinished: { type: 'boolean' } + } +} @ApiTags('User Controller') @Controller('user') +@ApiBearerAuth() +@ApiSecurity('api_key') export class UserController { constructor(private readonly userService: UserService) {} @Get() @BypassOnboarding() @RequiredApiKeyAuthorities(Authority.READ_SELF) + @ApiOperation({ + summary: 'Get current user', + description: + 'This endpoint returns the details of the currently logged in user' + }) + @ApiOkResponse({ + description: 'User details', + schema: userSchema + }) + @ApiForbiddenResponse(invalidAuthenticationResponse) async getCurrentUser(@CurrentUser() user: User) { return this.userService.getSelf(user) } @@ -40,6 +71,16 @@ export class UserController { @Put() @BypassOnboarding() @RequiredApiKeyAuthorities(Authority.UPDATE_SELF) + @ApiOperation({ + summary: 'Update current user', + description: + 'This endpoint updates the details of the currently logged in user' + }) + @ApiOkResponse({ + description: 'Updated user details', + schema: userSchema + }) + @ApiForbiddenResponse(invalidAuthenticationResponse) async updateSelf(@CurrentUser() user: User, @Body() dto: UpdateUserDto) { return await this.userService.updateSelf(user, dto) } @@ -48,6 +89,15 @@ export class UserController { @ApiNoContentResponse() @HttpCode(204) @ForbidApiKey() + @ApiOperation({ + summary: 'Delete current user', + description: + 'This endpoint deletes the details of the currently logged in user' + }) + @ApiForbiddenResponse(invalidAuthenticationResponse) + @ApiNoContentResponse({ + description: 'User deleted successfully' + }) async deleteSelf(@CurrentUser() user: User) { await this.userService.deleteSelf(user) } diff --git a/apps/api/src/user/dto/create.user/create.user.ts b/apps/api/src/user/dto/create.user/create.user.ts index 04c2811c..2b063b34 100644 --- a/apps/api/src/user/dto/create.user/create.user.ts +++ b/apps/api/src/user/dto/create.user/create.user.ts @@ -1,21 +1,77 @@ -import { IsBoolean, IsOptional, IsString } from 'class-validator' +import { ApiProperty } from '@nestjs/swagger' +import { IsBoolean, IsEmail, IsOptional, IsString } from 'class-validator' export class CreateUserDto { @IsString() @IsOptional() + @ApiProperty({ + name: 'name', + description: 'Full name of the user', + required: false, + type: String, + example: 'John Doe', + default: null + }) name: string + @IsString() + @IsEmail() + @ApiProperty({ + name: 'email', + description: 'Email of the user', + required: true, + type: String, + example: 'johndoe@keyshade.xyz', + format: 'email', + uniqueItems: true + }) email: string + @IsString() @IsOptional() + @ApiProperty({ + name: 'profilePictureUrl', + description: 'URL of the user profile picture', + required: false, + type: String, + example: 'https://example.com/profile.jpg', + default: null + }) profilePictureUrl: string + @IsBoolean() @IsOptional() + @ApiProperty({ + name: 'isActive', + description: 'Is the user active', + required: false, + type: Boolean, + example: true, + default: true + }) isActive: boolean + @IsBoolean() @IsOptional() + @ApiProperty({ + name: 'isOnboardingFinished', + description: 'Is the user onboarding finished', + required: false, + type: Boolean, + example: true, + default: false + }) isOnboardingFinished: boolean + @IsBoolean() @IsOptional() + @ApiProperty({ + name: 'isAdmin', + description: 'Is the user an admin', + required: false, + type: Boolean, + example: false, + default: false + }) isAdmin: boolean }