From 422ad206411d41bedf7817c2b10a73760401d4ae Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 1 Jun 2023 22:12:22 -0400 Subject: [PATCH] refactor(server): use swagger (#2639) --- server/apps/immich/src/app.utils.ts | 91 +++++++++++++++++++ server/apps/immich/src/main.ts | 64 ++----------- .../immich/src/utils/patch-open-api.util.ts | 37 -------- 3 files changed, 97 insertions(+), 95 deletions(-) delete mode 100644 server/apps/immich/src/utils/patch-open-api.util.ts diff --git a/server/apps/immich/src/app.utils.ts b/server/apps/immich/src/app.utils.ts index b836e0aa92..2f43dd027d 100644 --- a/server/apps/immich/src/app.utils.ts +++ b/server/apps/immich/src/app.utils.ts @@ -1,4 +1,16 @@ +import { IMMICH_ACCESS_COOKIE, IMMICH_API_KEY_HEADER, IMMICH_API_KEY_NAME, SERVER_VERSION } from '@app/domain'; +import { INestApplication } from '@nestjs/common'; +import { + DocumentBuilder, + OpenAPIObject, + SwaggerCustomOptions, + SwaggerDocumentOptions, + SwaggerModule, +} from '@nestjs/swagger'; import { Response } from 'express'; +import { writeFileSync } from 'fs'; +import path from 'path'; +import { Metadata } from './decorators/authenticated.decorator'; import { DownloadArchive } from './modules/download/download.service'; export const handleDownload = (download: DownloadArchive, res: Response) => { @@ -8,3 +20,82 @@ export const handleDownload = (download: DownloadArchive, res: Response) => { res.setHeader('X-Immich-Archive-Complete', `${download.complete}`); return download.stream; }; + +const patchOpenAPI = (document: OpenAPIObject) => { + for (const path of Object.values(document.paths)) { + const operations = { + get: path.get, + put: path.put, + post: path.post, + delete: path.delete, + options: path.options, + head: path.head, + patch: path.patch, + trace: path.trace, + }; + + for (const operation of Object.values(operations)) { + if (!operation) { + continue; + } + + if ((operation.security || []).find((item) => !!item[Metadata.PUBLIC_SECURITY])) { + delete operation.security; + } + + if (operation.summary === '') { + delete operation.summary; + } + + if (operation.description === '') { + delete operation.description; + } + } + } + + return document; +}; + +export const useSwagger = (app: INestApplication, isDev: boolean) => { + const config = new DocumentBuilder() + .setTitle('Immich') + .setDescription('Immich API') + .setVersion(SERVER_VERSION) + .addBearerAuth({ + type: 'http', + scheme: 'Bearer', + in: 'header', + }) + .addCookieAuth(IMMICH_ACCESS_COOKIE) + .addApiKey( + { + type: 'apiKey', + in: 'header', + name: IMMICH_API_KEY_HEADER, + }, + IMMICH_API_KEY_NAME, + ) + .addServer('/api') + .build(); + + const options: SwaggerDocumentOptions = { + operationIdFactory: (controllerKey: string, methodKey: string) => methodKey, + }; + + const doc = SwaggerModule.createDocument(app, config, options); + + const customOptions: SwaggerCustomOptions = { + swaggerOptions: { + persistAuthorization: true, + }, + customSiteTitle: 'Immich API Documentation', + }; + + SwaggerModule.setup('doc', app, doc, customOptions); + + if (isDev) { + // Generate API Documentation only in development mode + const outputPath = path.resolve(process.cwd(), 'immich-openapi-specs.json'); + writeFileSync(outputPath, JSON.stringify(patchOpenAPI(doc), null, 2), { encoding: 'utf8' }); + } +}; diff --git a/server/apps/immich/src/main.ts b/server/apps/immich/src/main.ts index 72d8991dcd..5c1779916b 100644 --- a/server/apps/immich/src/main.ts +++ b/server/apps/immich/src/main.ts @@ -1,26 +1,17 @@ -import { - getLogLevels, - IMMICH_ACCESS_COOKIE, - IMMICH_API_KEY_HEADER, - IMMICH_API_KEY_NAME, - MACHINE_LEARNING_ENABLED, - SearchService, - SERVER_VERSION, -} from '@app/domain'; +import { getLogLevels, MACHINE_LEARNING_ENABLED, SearchService, SERVER_VERSION } from '@app/domain'; import { RedisIoAdapter } from '@app/infra'; import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; -import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger'; import { json } from 'body-parser'; import cookieParser from 'cookie-parser'; -import { writeFileSync } from 'fs'; -import path from 'path'; import { AppModule } from './app.module'; import { AppService } from './app.service'; -import { patchOpenAPI } from './utils/patch-open-api.util'; +import { useSwagger } from './app.utils'; const logger = new Logger('ImmichServer'); +const isDev = process.env.NODE_ENV === 'development'; +const serverPort = Number(process.env.SERVER_PORT) || 3001; async function bootstrap() { const app = await NestFactory.create(AppModule, { @@ -31,57 +22,14 @@ async function bootstrap() { app.set('etag', 'strong'); app.use(cookieParser()); app.use(json({ limit: '10mb' })); - if (process.env.NODE_ENV === 'development') { + if (isDev) { app.enableCors(); } - - const serverPort = Number(process.env.SERVER_PORT) || 3001; - app.useWebSocketAdapter(new RedisIoAdapter(app)); - - const config = new DocumentBuilder() - .setTitle('Immich') - .setDescription('Immich API') - .setVersion(SERVER_VERSION) - .addBearerAuth({ - type: 'http', - scheme: 'Bearer', - in: 'header', - }) - .addCookieAuth(IMMICH_ACCESS_COOKIE) - .addApiKey( - { - type: 'apiKey', - in: 'header', - name: IMMICH_API_KEY_HEADER, - }, - IMMICH_API_KEY_NAME, - ) - .addServer('/api') - .build(); - - const apiDocumentOptions: SwaggerDocumentOptions = { - operationIdFactory: (controllerKey: string, methodKey: string) => methodKey, - }; - - const apiDocument = SwaggerModule.createDocument(app, config, apiDocumentOptions); - - SwaggerModule.setup('doc', app, apiDocument, { - swaggerOptions: { - persistAuthorization: true, - }, - customSiteTitle: 'Immich API Documentation', - }); - + useSwagger(app, isDev); await app.get(AppService).init(); await app.listen(serverPort, () => { - if (process.env.NODE_ENV == 'development') { - // Generate API Documentation only in development mode - const outputPath = path.resolve(process.cwd(), 'immich-openapi-specs.json'); - writeFileSync(outputPath, JSON.stringify(patchOpenAPI(apiDocument), null, 2), { encoding: 'utf8' }); - } - const envName = (process.env.NODE_ENV || 'development').toUpperCase(); logger.log( `Running Immich Server in ${envName} environment - version ${SERVER_VERSION} - Listening on port: ${serverPort}`, diff --git a/server/apps/immich/src/utils/patch-open-api.util.ts b/server/apps/immich/src/utils/patch-open-api.util.ts deleted file mode 100644 index 6c56c2b627..0000000000 --- a/server/apps/immich/src/utils/patch-open-api.util.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { OpenAPIObject } from '@nestjs/swagger'; -import { Metadata } from '../decorators/authenticated.decorator'; - -export function patchOpenAPI(document: OpenAPIObject) { - for (const path of Object.values(document.paths)) { - const operations = { - get: path.get, - put: path.put, - post: path.post, - delete: path.delete, - options: path.options, - head: path.head, - patch: path.patch, - trace: path.trace, - }; - - for (const operation of Object.values(operations)) { - if (!operation) { - continue; - } - - if ((operation.security || []).find((item) => !!item[Metadata.PUBLIC_SECURITY])) { - delete operation.security; - } - - if (operation.summary === '') { - delete operation.summary; - } - - if (operation.description === '') { - delete operation.description; - } - } - } - - return document; -}