refactor(server): use swagger (#2639)

This commit is contained in:
Jason Rasmussen 2023-06-01 22:12:22 -04:00 committed by GitHub
parent 3ea2fe1c48
commit 422ad20641
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 95 deletions

View file

@ -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' });
}
};

View file

@ -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<NestExpressApplication>(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}`,

View file

@ -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;
}