diff --git a/package.json b/package.json index 767823f..07529c6 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@nestjs/core": "^11.0.1", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.2.0", "@prisma/client": "^6.9.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", diff --git a/src/main.ts b/src/main.ts index deef6f2..ca26050 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,5 @@ import { NestFactory } from '@nestjs/core'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { AllExceptionsFilter } from './common/filters/all-exceptions.filter'; @@ -6,7 +7,20 @@ import { ResponseInterceptor } from './common/interceptors/response.interceptor' import { loggerMiddlewareFunc } from './common/middlewares/logger/logger.middleware'; async function bootstrap() { + const config = new DocumentBuilder() + .setTitle('Nest.js Course') + .setDescription('Documentation for nest.js') + .setVersion('0.0.1') + .setContact('Krylov Sergey', 'https://ksv741.tech', 'ksv741@mail.ru') + .setLicense('MIT', 'https://github.com') + .build(); const app = await NestFactory.create(AppModule); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('/docs', app, document, { + jsonDocumentUrl: 'docs/swagger.json', + yamlDocumentUrl: 'docs/swagger.yaml', + customSiteTitle: 'Nest.js base course', + }); app.useGlobalPipes(new ValidationPipe()); app.use(loggerMiddlewareFunc); app.useGlobalInterceptors(new ResponseInterceptor()); diff --git a/src/movie/dto/movie.dto.ts b/src/movie/dto/movie.dto.ts index 259efac..71eb9c0 100644 --- a/src/movie/dto/movie.dto.ts +++ b/src/movie/dto/movie.dto.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsArray, IsInt, @@ -11,20 +12,39 @@ import { import { Actor } from 'generated/prisma'; export class MovieDto { + @ApiProperty({ + description: 'Название фильма', + example: 'Fight Club', + type: String, + }) @IsNotEmpty() @IsString() title: string; + @ApiProperty({ + description: 'Год выпуска', + example: 1995, + type: Number, + }) @IsNotEmpty() @IsInt() @Min(1888) @Max(new Date().getFullYear()) releaseYear: number; + @ApiProperty({ + description: 'Список актеров', + example: ['1234', '5678'], + type: [String], + }) @IsArray() @IsUUID('4', { each: true }) actorIds: Actor['id'][]; + @ApiProperty({ + description: 'URL постера', + example: 'https://storage.example.com/posters/12345.jpg', + }) @IsUrl() imageUrl: string; } diff --git a/src/movie/dto/response.dto.ts b/src/movie/dto/response.dto.ts new file mode 100644 index 0000000..f23202b --- /dev/null +++ b/src/movie/dto/response.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class MovieResponse { + @ApiProperty({ + description: 'Id фильма', + example: '123456', + type: String, + }) + id: string; + + @ApiProperty({ + description: 'Название фильма', + example: 'Fight Club', + type: String, + }) + title: string; + + @ApiProperty({ + description: 'Год выпуска', + example: 1995, + type: Number, + }) + releaseYear: number; +} diff --git a/src/movie/movie.controller.ts b/src/movie/movie.controller.ts index 2c5abe1..3f50897 100644 --- a/src/movie/movie.controller.ts +++ b/src/movie/movie.controller.ts @@ -3,28 +3,66 @@ import { Controller, Delete, Get, + HttpStatus, Param, Post, Put, } from '@nestjs/common'; +import { + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; import { MovieDto } from './dto/movie.dto'; +import { MovieResponse } from './dto/response.dto'; import { MovieService } from './movie.service'; import { Movie } from 'generated/prisma'; +@ApiTags('Movies') @Controller('movies') export class MovieController { constructor(private readonly movieService: MovieService) {} + @ApiOperation({ + summary: 'Список фильмов', + description: 'Получить список всех фильмов', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Фильмы найдены', + }) @Get() public findAll() { return this.movieService.findAll(); } + @ApiOperation({ + summary: 'Поллучить фильм', + description: 'Получить фильм по id', + }) + @ApiParam({ name: 'id', type: 'string', description: 'ID фильма' }) + @ApiOkResponse({ description: 'Фильм найден', type: MovieResponse }) + @ApiNotFoundResponse({ + description: 'Филь не найден', + example: { + status: 404, + message: 'Not found movie with id 22', + timestamp: '2025-06-12T10:46:30.694Z', + path: '', + }, + }) @Get(':id') public findById(@Param('id') id: Movie['id']) { return this.movieService.findById(id); } + @ApiOperation({ + summary: 'Добавить фильм', + description: 'Добавить фильм в коллекцию', + }) @Post() public create(@Body() dto: MovieDto) { return this.movieService.create(dto); diff --git a/yarn.lock b/yarn.lock index ef0266d..8d56466 100644 --- a/yarn.lock +++ b/yarn.lock @@ -858,6 +858,11 @@ resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== +"@microsoft/tsdoc@0.15.1": + version "0.15.1" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz#d4f6937353bc4568292654efb0a0e0532adbcba2" + integrity sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw== + "@napi-rs/nice-android-arm-eabi@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz#9a0cba12706ff56500df127d6f4caf28ddb94936" @@ -1017,7 +1022,7 @@ path-to-regexp "8.2.0" tslib "2.8.1" -"@nestjs/mapped-types@*": +"@nestjs/mapped-types@*", "@nestjs/mapped-types@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19" integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw== @@ -1044,6 +1049,18 @@ jsonc-parser "3.3.1" pluralize "8.0.0" +"@nestjs/swagger@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-11.2.0.tgz#a1b10620a9f90c78edf897a9386dc4f3e014387e" + integrity sha512-5wolt8GmpNcrQv34tIPUtPoV1EeFbCetm40Ij3+M0FNNnf2RJ3FyWfuQvI8SBlcJyfaounYVTKzKHreFXsUyOg== + dependencies: + "@microsoft/tsdoc" "0.15.1" + "@nestjs/mapped-types" "2.1.0" + js-yaml "4.1.0" + lodash "4.17.21" + path-to-regexp "8.2.0" + swagger-ui-dist "5.21.0" + "@nestjs/testing@^11.0.1": version "11.1.2" resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-11.1.2.tgz#af5f03f1b58318db7d5a0b1dbf7006bc8145d288" @@ -1144,6 +1161,11 @@ dependencies: "@prisma/debug" "6.9.0" +"@scarf/scarf@=1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" + integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== + "@sec-ant/readable-stream@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" @@ -4055,6 +4077,13 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -4063,13 +4092,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -5331,6 +5353,13 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swagger-ui-dist@5.21.0: + version "5.21.0" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.21.0.tgz#aed230fe6e294c9470217e67697d601e3bb8eb9d" + integrity sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg== + dependencies: + "@scarf/scarf" "=1.4.0" + symbol-observable@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"