From 6cb9c3d2e6a2ade7e5d8769dfdf8c6163101db98 Mon Sep 17 00:00:00 2001 From: Hendra Lin Date: Fri, 14 Apr 2023 11:07:49 +0700 Subject: [PATCH] Part 1: Building a REST API with NestJS and Prisma --- .env | 7 +++ package.json | 9 ++- .../migrations/20230414015851_init/migration.sql | 13 +++++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 21 +++++++ prisma/seed.ts | 43 ++++++++++++++ src/app.module.ts | 4 +- src/articles/articles.controller.spec.ts | 20 +++++++ src/articles/articles.controller.ts | 48 +++++++++++++++ src/articles/articles.module.ts | 11 ++++ src/articles/articles.service.spec.ts | 18 ++++++ src/articles/articles.service.ts | 36 ++++++++++++ src/articles/dto/create-article.dto.ts | 15 +++++ src/articles/dto/update-article.dto.ts | 4 ++ src/articles/entities/article.entity.ts | 25 ++++++++ src/main.ts | 11 ++++ src/prisma/prisma.module.ts | 8 +++ src/prisma/prisma.service.spec.ts | 18 ++++++ src/prisma/prisma.service.ts | 11 ++++ yarn.lock | 68 +++++++++++++++++++--- 20 files changed, 383 insertions(+), 10 deletions(-) create mode 100644 .env create mode 100644 prisma/migrations/20230414015851_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts create mode 100644 src/articles/articles.controller.spec.ts create mode 100644 src/articles/articles.controller.ts create mode 100644 src/articles/articles.module.ts create mode 100644 src/articles/articles.service.spec.ts create mode 100644 src/articles/articles.service.ts create mode 100644 src/articles/dto/create-article.dto.ts create mode 100644 src/articles/dto/update-article.dto.ts create mode 100644 src/articles/entities/article.entity.ts create mode 100644 src/prisma/prisma.module.ts create mode 100644 src/prisma/prisma.service.spec.ts create mode 100644 src/prisma/prisma.service.ts diff --git a/.env b/.env new file mode 100644 index 0000000..05e8a36 --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema + +# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings + +DATABASE_URL="mysql://root:root@localhost:3306/median" \ No newline at end of file diff --git a/package.json b/package.json index f6cfa84..346960e 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,11 @@ "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", + "@nestjs/swagger": "^6.3.0", + "@prisma/client": "^4.12.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.2.0" + "rxjs": "^7.2.0", + "swagger-ui-express": "^4.6.2" }, "devDependencies": { "@nestjs/cli": "^9.0.0", @@ -41,6 +44,7 @@ "eslint-plugin-prettier": "^4.0.0", "jest": "29.5.0", "prettier": "^2.3.2", + "prisma": "^4.12.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.5", @@ -65,5 +69,8 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" + }, + "prisma": { + "seed": "ts-node prisma/seed.ts" } } diff --git a/prisma/migrations/20230414015851_init/migration.sql b/prisma/migrations/20230414015851_init/migration.sql new file mode 100644 index 0000000..35d61b9 --- /dev/null +++ b/prisma/migrations/20230414015851_init/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE `Article` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `title` VARCHAR(191) NOT NULL, + `description` VARCHAR(191) NULL, + `body` VARCHAR(191) NOT NULL, + `published` BOOLEAN NOT NULL DEFAULT false, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updatedAt` DATETIME(3) NOT NULL, + + UNIQUE INDEX `Article_title_key`(`title`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..e5a788a --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "mysql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..de3e06c --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,21 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +model Article { + id Int @id @default(autoincrement()) + title String @unique + description String? + body String + published Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..f291379 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,43 @@ +// prisma/seed.ts + +import { PrismaClient } from "@prisma/client"; + +// initialize Prisma Client +const prisma = new PrismaClient(); + +async function main() { + // create two dummy articles + const post1 = await prisma.article.upsert({ + where: { title: 'Prisma Adds Support for MongoDB' }, + update: {}, + create: { + title: 'Prisma Adds Support for MongoDB', + body: 'Support for MongoDB has been one of the most requested features since the initial release of...', + description: "We are excited to share that today's Prisma ORM release adds stable support for MongoDB!", + published: false, + } + }) + + const post2 = await prisma.article.upsert({ + where: { title: "What's new in Prisma? (Q1/22)" }, + update: {}, + create: { + title: "What's new in Prisma? (Q1/22)", + body: 'Our engineers have been working hard, issuing new releases with many improvements...', + description: + 'Learn about everything in the Prisma ecosystem and community from January to March 2022.', + published: true, + }, + }); + + console.log({ post1, post2 }); +} + +// execute the main function +main().catch((e)=>{ + console.error(e) + process.exit(1) +}).finally(async () => { + // close Prisma Client at the end + await prisma.$disconnect() +}) \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..a9acbae 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { PrismaModule } from './prisma/prisma.module'; +import { ArticlesModule } from './articles/articles.module'; @Module({ - imports: [], + imports: [PrismaModule, ArticlesModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/articles/articles.controller.spec.ts b/src/articles/articles.controller.spec.ts new file mode 100644 index 0000000..c170385 --- /dev/null +++ b/src/articles/articles.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ArticlesController } from './articles.controller'; +import { ArticlesService } from './articles.service'; + +describe('ArticlesController', () => { + let controller: ArticlesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ArticlesController], + providers: [ArticlesService], + }).compile(); + + controller = module.get(ArticlesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/articles/articles.controller.ts b/src/articles/articles.controller.ts new file mode 100644 index 0000000..8c35405 --- /dev/null +++ b/src/articles/articles.controller.ts @@ -0,0 +1,48 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { ArticlesService } from './articles.service'; +import { CreateArticleDto } from './dto/create-article.dto'; +import { UpdateArticleDto } from './dto/update-article.dto'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { ArticleEntity } from './entities/article.entity'; + +@Controller('articles') +@ApiTags('articles') +export class ArticlesController { + constructor(private readonly articlesService: ArticlesService) { } + + @Post() + @ApiCreatedResponse({ type: ArticleEntity }) + create(@Body() createArticleDto: CreateArticleDto) { + return this.articlesService.create(createArticleDto); + } + + @Get('drafts') + @ApiOkResponse({ type: ArticleEntity, isArray: true }) + findDrafts() { + return this.articlesService.findDrafts(); + } + + @Get() + @ApiOkResponse({ type: ArticleEntity, isArray: true }) + findAll() { + return this.articlesService.findAll(); + } + + @Get(':id') + @ApiOkResponse({ type: ArticleEntity }) + findOne(@Param('id') id: string) { + return this.articlesService.findOne(+id); + } + + @Patch(':id') + @ApiOkResponse({ type: ArticleEntity }) + update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) { + return this.articlesService.update(+id, updateArticleDto); + } + + @Delete(':id') + @ApiOkResponse({ type: ArticleEntity }) + remove(@Param('id') id: string) { + return this.articlesService.remove(+id); + } +} diff --git a/src/articles/articles.module.ts b/src/articles/articles.module.ts new file mode 100644 index 0000000..0cd5810 --- /dev/null +++ b/src/articles/articles.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ArticlesService } from './articles.service'; +import { ArticlesController } from './articles.controller'; +import { PrismaModule } from 'src/prisma/prisma.module'; + +@Module({ + controllers: [ArticlesController], + providers: [ArticlesService], + imports: [PrismaModule] +}) +export class ArticlesModule {} diff --git a/src/articles/articles.service.spec.ts b/src/articles/articles.service.spec.ts new file mode 100644 index 0000000..3fd264d --- /dev/null +++ b/src/articles/articles.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ArticlesService } from './articles.service'; + +describe('ArticlesService', () => { + let service: ArticlesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ArticlesService], + }).compile(); + + service = module.get(ArticlesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/articles/articles.service.ts b/src/articles/articles.service.ts new file mode 100644 index 0000000..f4627d9 --- /dev/null +++ b/src/articles/articles.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { CreateArticleDto } from './dto/create-article.dto'; +import { UpdateArticleDto } from './dto/update-article.dto'; +import { PrismaService } from 'src/prisma/prisma.service'; + +@Injectable() +export class ArticlesService { + constructor(private prisma: PrismaService) { } + + create(createArticleDto: CreateArticleDto) { + return this.prisma.article.create({ data: createArticleDto }) + } + + findDrafts() { + return this.prisma.article.findMany({ where: { published: false } }) + } + + findAll() { + return this.prisma.article.findMany({ where: { published: true } }) + } + + findOne(id: number) { + return this.prisma.article.findUnique({ where: { id } }) + } + + update(id: number, updateArticleDto: UpdateArticleDto) { + return this.prisma.article.update({ + where: { id }, + data: updateArticleDto + }) + } + + remove(id: number) { + return this.prisma.article.delete({ where: { id } }) + } +} diff --git a/src/articles/dto/create-article.dto.ts b/src/articles/dto/create-article.dto.ts new file mode 100644 index 0000000..a27259e --- /dev/null +++ b/src/articles/dto/create-article.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class CreateArticleDto { + @ApiProperty() + title: string + + @ApiProperty({ required: false }) + description?: string + + @ApiProperty() + body: string + + @ApiProperty({ required: false, default: false }) + published?: boolean = false +} diff --git a/src/articles/dto/update-article.dto.ts b/src/articles/dto/update-article.dto.ts new file mode 100644 index 0000000..429306f --- /dev/null +++ b/src/articles/dto/update-article.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateArticleDto } from './create-article.dto'; + +export class UpdateArticleDto extends PartialType(CreateArticleDto) {} diff --git a/src/articles/entities/article.entity.ts b/src/articles/entities/article.entity.ts new file mode 100644 index 0000000..51028cc --- /dev/null +++ b/src/articles/entities/article.entity.ts @@ -0,0 +1,25 @@ +import { Article } from '@prisma/client' +import { ApiProperty } from '@nestjs/swagger' + +export class ArticleEntity implements Article { + @ApiProperty() + id: number + + @ApiProperty() + title: string + + @ApiProperty({ required: false, nullable: true }) + description: string | null + + @ApiProperty() + body: string + + @ApiProperty() + published: boolean + + @ApiProperty() + createdAt: Date + + @ApiProperty() + updatedAt: Date +} diff --git a/src/main.ts b/src/main.ts index 13cad38..a085ef3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,19 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); + + const config = new DocumentBuilder() + .setTitle('Median') + .setDescription('The Median API description') + .setVersion('0.1') + .build() + + const document = SwaggerModule.createDocument(app, config) + SwaggerModule.setup('api', app, document) + await app.listen(3000); } bootstrap(); diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts new file mode 100644 index 0000000..e569e2d --- /dev/null +++ b/src/prisma/prisma.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Module({ + providers: [PrismaService], + exports: [PrismaService] +}) +export class PrismaModule {} diff --git a/src/prisma/prisma.service.spec.ts b/src/prisma/prisma.service.spec.ts new file mode 100644 index 0000000..a68cb9e --- /dev/null +++ b/src/prisma/prisma.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PrismaService } from './prisma.service'; + +describe('PrismaService', () => { + let service: PrismaService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [PrismaService], + }).compile(); + + service = module.get(PrismaService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts new file mode 100644 index 0000000..5ca43ef --- /dev/null +++ b/src/prisma/prisma.service.ts @@ -0,0 +1,11 @@ +import { INestApplication, Injectable } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client' + +@Injectable() +export class PrismaService extends PrismaClient { + async enableShutdownHooks(app: INestApplication) { + this.$on('beforeExit', async () => { + await app.close() + }) + } +} diff --git a/yarn.lock b/yarn.lock index a24f88d..dd2ea3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -716,6 +716,11 @@ path-to-regexp "3.2.0" tslib "2.5.0" +"@nestjs/mapped-types@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-1.2.2.tgz#d9ddb143776e309dbc1a518ac1607fddac1e140e" + integrity sha512-3dHxLXs3M0GPiriAcCFFJQHoDFUuzTD5w6JDhE7TyfT89YKpe6tcCCIqOZWdXmt9AZjjK30RkHRSFF+QEnWFQg== + "@nestjs/platform-express@^9.0.0": version "9.4.0" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-9.4.0.tgz#e1f9e6c60cdd8d7889abbc6a04ab95279976175b" @@ -737,6 +742,17 @@ jsonc-parser "3.2.0" pluralize "8.0.0" +"@nestjs/swagger@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-6.3.0.tgz#2963395a398374c25548a012eb15f03f53ad6e53" + integrity sha512-Gnig189oa1tD+h0BYIfUwhp/wvvmTn6iO3csR2E4rQrDTgCxSxZDlNdfZo3AC+Rmf8u0KX4ZAX1RZN1qXTtC7A== + dependencies: + "@nestjs/mapped-types" "1.2.2" + js-yaml "4.1.0" + lodash "4.17.21" + path-to-regexp "3.2.0" + swagger-ui-dist "4.18.2" + "@nestjs/testing@^9.0.0": version "9.4.0" resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-9.4.0.tgz#1e5d1e799413e996c9c2da02a89dfefa62c3b70e" @@ -774,6 +790,23 @@ consola "^2.15.0" node-fetch "^2.6.1" +"@prisma/client@^4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.12.0.tgz#119b692888b1fe0fd3305c7d0e0ac48520aa6839" + integrity sha512-j9/ighfWwux97J2dS15nqhl60tYoH8V0IuSsgZDb6bCFcQD3fXbXmxjYC8GHhIgOk3lB7Pq+8CwElz2MiDpsSg== + dependencies: + "@prisma/engines-version" "4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7" + +"@prisma/engines-version@4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7": + version "4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7.tgz#51a1cc5c886564b542acde64a873645d0dee2566" + integrity sha512-JIHNj5jlXb9mcaJwakM0vpgRYJIAurxTUqM0iX0tfEQA5XLZ9ONkIckkhuAKdAzocZ+80GYg7QSsfpjg7OxbOA== + +"@prisma/engines@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.12.0.tgz#68d99078b70b2d9c339d0e8cbf2e99f00b72aa8c" + integrity sha512-0alKtnxhNB5hYU+ymESBlGI4b9XrGGSdv7Ud+8TE/fBNOEhIud0XQsAR+TrvUZgS4na5czubiMsODw0TUrgkIA== + "@sinclair/typebox@^0.25.16": version "0.25.24" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" @@ -3201,6 +3234,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" @@ -3209,13 +3249,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@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -3312,7 +3345,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.21: +lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3794,6 +3827,13 @@ pretty-format@^29.0.0, pretty-format@^29.5.0: ansi-styles "^5.0.0" react-is "^18.0.0" +prisma@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.12.0.tgz#1080eda951928cb3b0274ad29da9ae4f93143d68" + integrity sha512-xqVper4mbwl32BWzLpdznHAYvYDWQQWK2tBfXjdUD397XaveRyAP7SkBZ6kFlIg8kKayF4hvuaVtYwXd9BodAg== + dependencies: + "@prisma/engines" "4.12.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -4302,6 +4342,18 @@ 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@4.18.2, swagger-ui-dist@>=4.11.0: + version "4.18.2" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-4.18.2.tgz#323308f1c1d87a7c22ce3e273c31835eb680a71b" + integrity sha512-oVBoBl9Dg+VJw8uRWDxlyUyHoNEDC0c1ysT6+Boy6CTgr2rUcLcfPon4RvxgS2/taNW6O0+US+Z/dlAsWFjOAQ== + +swagger-ui-express@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz#61b2cb9fd7932cdccff99e0efdf700a5459e493c" + integrity sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ== + dependencies: + swagger-ui-dist ">=4.11.0" + symbol-observable@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"