Browse Source

Add OpenAPI Specs and Response DTOs (#320)

* Added swagger bearer auth method authentication accordingly

* Update Auth endpoint

* Added additional api information for authentication

* Added Swagger CLI pluggin

* Added DTO for /user endpoint

* Added /device-info reponse DTOs

* Implement server version

* Added DTOs for /server-info

* Added DTOs for /assets

* Added album to Swagger group

* Added generated specs file

* Add Client API generator for web

* Remove incorrectly placed node_modules

* Created class to handle access token

* Remove password and hash when getting all user

* PR feedback

* Fixed video from CLI doesn't get metadata extracted

* Fixed issue with TSConfig to work with generated openAPI

* PR feedback

* Remove console.log
Alex 3 years ago
parent
commit
7f236c5b18
59 changed files with 5476 additions and 226 deletions
  1. 6 3
      server/apps/immich/src/api-v1/album/album.controller.ts
  2. 9 9
      server/apps/immich/src/api-v1/album/response-dto/album-response.dto.ts
  3. 30 9
      server/apps/immich/src/api-v1/asset/asset.controller.ts
  4. 44 28
      server/apps/immich/src/api-v1/asset/asset.service.ts
  5. 11 0
      server/apps/immich/src/api-v1/asset/dto/delete-asset.dto.ts
  6. 3 2
      server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts
  7. 17 13
      server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts
  8. 7 0
      server/apps/immich/src/api-v1/asset/response-dto/curated-locations-response.dto.ts
  9. 7 0
      server/apps/immich/src/api-v1/asset/response-dto/curated-objects-response.dto.ts
  10. 21 21
      server/apps/immich/src/api-v1/asset/response-dto/exif-response.dto.ts
  11. 4 4
      server/apps/immich/src/api-v1/asset/response-dto/smart-info-response.dto.ts
  12. 19 7
      server/apps/immich/src/api-v1/auth/auth.controller.ts
  13. 7 14
      server/apps/immich/src/api-v1/auth/auth.service.ts
  14. 3 0
      server/apps/immich/src/api-v1/auth/dto/login-credential.dto.ts
  15. 5 0
      server/apps/immich/src/api-v1/auth/dto/sign-up.dto.ts
  16. 19 0
      server/apps/immich/src/api-v1/auth/response-dto/admin-signup-response.dto.ts
  17. 41 0
      server/apps/immich/src/api-v1/auth/response-dto/login-response.dto.ts
  18. 7 0
      server/apps/immich/src/api-v1/auth/response-dto/validate-asset-token-response.dto,.ts
  19. 13 3
      server/apps/immich/src/api-v1/device-info/device-info.controller.ts
  20. 16 11
      server/apps/immich/src/api-v1/device-info/device-info.service.ts
  21. 23 0
      server/apps/immich/src/api-v1/device-info/response-dto/create-device-info-response.dto.ts
  22. 1 2
      server/apps/immich/src/api-v1/server-info/response-dto/server-info-response.dto.ts
  23. 10 0
      server/apps/immich/src/api-v1/server-info/response-dto/server-ping-response.dto.ts
  24. 8 0
      server/apps/immich/src/api-v1/server-info/response-dto/server-version-response.dto.ts
  25. 9 15
      server/apps/immich/src/api-v1/server-info/server-info.controller.ts
  26. 2 2
      server/apps/immich/src/api-v1/server-info/server-info.service.ts
  27. 6 0
      server/apps/immich/src/api-v1/user/dto/create-profile-image.dto.ts
  28. 5 12
      server/apps/immich/src/api-v1/user/dto/create-user.dto.ts
  29. 23 3
      server/apps/immich/src/api-v1/user/dto/update-user.dto.ts
  30. 11 0
      server/apps/immich/src/api-v1/user/response-dto/create-profile-image-response.dto.ts
  31. 10 0
      server/apps/immich/src/api-v1/user/response-dto/user-count-response.dto.ts
  32. 13 7
      server/apps/immich/src/api-v1/user/response-dto/user-response.dto.ts
  33. 32 9
      server/apps/immich/src/api-v1/user/user.controller.ts
  34. 27 24
      server/apps/immich/src/api-v1/user/user.service.ts
  35. 8 1
      server/apps/immich/src/constants/server_version.constant.ts
  36. 35 0
      server/apps/immich/src/main.ts
  37. 2 1
      server/apps/immich/src/modules/background-task/background-task.processor.ts
  38. 2 1
      server/apps/immich/src/modules/background-task/background-task.service.ts
  39. 1 1
      server/apps/microservices/src/processors/asset-uploaded.processor.ts
  40. 0 0
      server/immich-openapi-specs.json
  41. 9 2
      server/nest-cli.json
  42. 7 0
      server/openapitools.json
  43. 588 6
      server/package-lock.json
  44. 6 2
      server/package.json
  45. 4 4
      web/.eslintrc.cjs
  46. 34 0
      web/src/lib/immich-api/index.ts
  47. 4 0
      web/src/lib/open-api/.gitignore
  48. 1 0
      web/src/lib/open-api/.npmignore
  49. 23 0
      web/src/lib/open-api/.openapi-generator-ignore
  50. 9 0
      web/src/lib/open-api/.openapi-generator/FILES
  51. 1 0
      web/src/lib/open-api/.openapi-generator/VERSION
  52. 3874 0
      web/src/lib/open-api/api.ts
  53. 74 0
      web/src/lib/open-api/base.ts
  54. 138 0
      web/src/lib/open-api/common.ts
  55. 101 0
      web/src/lib/open-api/configuration.ts
  56. 57 0
      web/src/lib/open-api/git_push.sh
  57. 18 0
      web/src/lib/open-api/index.ts
  58. 8 9
      web/src/routes/photos/index.svelte
  59. 3 1
      web/tsconfig.json

+ 6 - 3
server/apps/immich/src/api-v1/album/album.controller.ts

@@ -22,20 +22,23 @@ import { AddUsersDto } from './dto/add-users.dto';
 import { RemoveAssetsDto } from './dto/remove-assets.dto';
 import { RemoveAssetsDto } from './dto/remove-assets.dto';
 import { UpdateAlbumDto } from './dto/update-album.dto';
 import { UpdateAlbumDto } from './dto/update-album.dto';
 import { GetAlbumsDto } from './dto/get-albums.dto';
 import { GetAlbumsDto } from './dto/get-albums.dto';
+import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
 
 
 // TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
 // TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
 @UseGuards(JwtAuthGuard)
 @UseGuards(JwtAuthGuard)
+@ApiBearerAuth()
+@ApiTags('Album')
 @Controller('album')
 @Controller('album')
 export class AlbumController {
 export class AlbumController {
   constructor(private readonly albumService: AlbumService) {}
   constructor(private readonly albumService: AlbumService) {}
 
 
   @Post()
   @Post()
-  async create(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createAlbumDto: CreateAlbumDto) {
+  async createAlbum(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createAlbumDto: CreateAlbumDto) {
     return this.albumService.create(authUser, createAlbumDto);
     return this.albumService.create(authUser, createAlbumDto);
   }
   }
 
 
   @Put('/:albumId/users')
   @Put('/:albumId/users')
-  async addUsers(
+  async addUsersToAlbum(
     @GetAuthUser() authUser: AuthUserDto,
     @GetAuthUser() authUser: AuthUserDto,
     @Body(ValidationPipe) addUsersDto: AddUsersDto,
     @Body(ValidationPipe) addUsersDto: AddUsersDto,
     @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
     @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@@ -44,7 +47,7 @@ export class AlbumController {
   }
   }
 
 
   @Put('/:albumId/assets')
   @Put('/:albumId/assets')
-  async addAssets(
+  async addAssetsToAlbum(
     @GetAuthUser() authUser: AuthUserDto,
     @GetAuthUser() authUser: AuthUserDto,
     @Body(ValidationPipe) addAssetsDto: AddAssetsDto,
     @Body(ValidationPipe) addAssetsDto: AddAssetsDto,
     @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
     @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,

+ 9 - 9
server/apps/immich/src/api-v1/album/response-dto/album-response.dto.ts

@@ -2,15 +2,15 @@ import { AlbumEntity } from '../../../../../../libs/database/src/entities/album.
 import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
 import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
 import { AssetResponseDto, mapAsset } from '../../asset/response-dto/asset-response.dto';
 import { AssetResponseDto, mapAsset } from '../../asset/response-dto/asset-response.dto';
 
 
-export interface AlbumResponseDto {
-  id: string;
-  ownerId: string;
-  albumName: string;
-  createdAt: string;
-  albumThumbnailAssetId: string | null;
-  shared: boolean;
-  sharedUsers: UserResponseDto[];
-  assets: AssetResponseDto[];
+export class AlbumResponseDto {
+  id!: string;
+  ownerId!: string;
+  albumName!: string;
+  createdAt!: string;
+  albumThumbnailAssetId!: string | null;
+  shared!: boolean;
+  sharedUsers!: UserResponseDto[];
+  assets!: AssetResponseDto[];
 }
 }
 
 
 export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
 export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {

+ 30 - 9
server/apps/immich/src/api-v1/asset/asset.controller.ts

@@ -36,8 +36,14 @@ import { IAssetUploadedJob } from '@app/job/index';
 import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
 import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
 import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant';
 import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant';
 import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
 import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
+import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger';
+import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
+import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
+import { AssetResponseDto } from './response-dto/asset-response.dto';
 
 
 @UseGuards(JwtAuthGuard)
 @UseGuards(JwtAuthGuard)
+@ApiBearerAuth()
+@ApiTags('Asset')
 @Controller('asset')
 @Controller('asset')
 export class AssetController {
 export class AssetController {
   constructor(
   constructor(
@@ -89,7 +95,7 @@ export class AssetController {
     @GetAuthUser() authUser: AuthUserDto,
     @GetAuthUser() authUser: AuthUserDto,
     @Response({ passthrough: true }) res: Res,
     @Response({ passthrough: true }) res: Res,
     @Query(ValidationPipe) query: ServeFileDto,
     @Query(ValidationPipe) query: ServeFileDto,
-  ) {
+  ): Promise<StreamableFile> {
     return this.assetService.downloadFile(query, res);
     return this.assetService.downloadFile(query, res);
   }
   }
 
 
@@ -109,43 +115,58 @@ export class AssetController {
   }
   }
 
 
   @Get('/allObjects')
   @Get('/allObjects')
-  async getCuratedObject(@GetAuthUser() authUser: AuthUserDto) {
+  async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
     return this.assetService.getCuratedObject(authUser);
     return this.assetService.getCuratedObject(authUser);
   }
   }
 
 
   @Get('/allLocation')
   @Get('/allLocation')
-  async getCuratedLocation(@GetAuthUser() authUser: AuthUserDto) {
+  async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
     return this.assetService.getCuratedLocation(authUser);
     return this.assetService.getCuratedLocation(authUser);
   }
   }
 
 
   @Get('/searchTerm')
   @Get('/searchTerm')
-  async getAssetSearchTerm(@GetAuthUser() authUser: AuthUserDto) {
+  async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<String[]> {
     return this.assetService.getAssetSearchTerm(authUser);
     return this.assetService.getAssetSearchTerm(authUser);
   }
   }
 
 
   @Post('/search')
   @Post('/search')
-  async searchAsset(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) searchAssetDto: SearchAssetDto) {
+  async searchAsset(
+    @GetAuthUser() authUser: AuthUserDto,
+    @Body(ValidationPipe) searchAssetDto: SearchAssetDto,
+  ): Promise<AssetResponseDto[]> {
     return this.assetService.searchAsset(authUser, searchAssetDto);
     return this.assetService.searchAsset(authUser, searchAssetDto);
   }
   }
 
 
+  /**
+   * Get all AssetEntity belong to the user
+   */
   @Get('/')
   @Get('/')
-  async getAllAssets(@GetAuthUser() authUser: AuthUserDto) {
+  async getAllAssets(@GetAuthUser() authUser: AuthUserDto): Promise<AssetResponseDto[]> {
     return await this.assetService.getAllAssets(authUser);
     return await this.assetService.getAllAssets(authUser);
   }
   }
 
 
+  /**
+   * Get all asset of a device that are in the database, ID only.
+   */
   @Get('/:deviceId')
   @Get('/:deviceId')
   async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param('deviceId') deviceId: string) {
   async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param('deviceId') deviceId: string) {
     return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
     return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
   }
   }
 
 
+  /**
+   * Get a single asset's information
+   */
   @Get('/assetById/:assetId')
   @Get('/assetById/:assetId')
-  async getAssetById(@GetAuthUser() authUser: AuthUserDto, @Param('assetId') assetId: string) {
+  async getAssetById(
+    @GetAuthUser() authUser: AuthUserDto,
+    @Param('assetId') assetId: string,
+  ): Promise<AssetResponseDto> {
     return await this.assetService.getAssetById(authUser, assetId);
     return await this.assetService.getAssetById(authUser, assetId);
   }
   }
 
 
   @Delete('/')
   @Delete('/')
-  async deleteAssetById(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) assetIds: DeleteAssetDto) {
-    const deleteAssetList: AssetEntity[] = [];
+  async deleteAsset(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) assetIds: DeleteAssetDto) {
+    const deleteAssetList: AssetResponseDto[] = [];
 
 
     for (const id of assetIds.ids) {
     for (const id of assetIds.ids) {
       const assets = await this.assetService.getAssetById(authUser, id);
       const assets = await this.assetService.getAssetById(authUser, id);

+ 44 - 28
server/apps/immich/src/api-v1/asset/asset.service.ts

@@ -19,6 +19,8 @@ import { DeleteAssetDto } from './dto/delete-asset.dto';
 import { SearchAssetDto } from './dto/search-asset.dto';
 import { SearchAssetDto } from './dto/search-asset.dto';
 import fs from 'fs/promises';
 import fs from 'fs/promises';
 import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
 import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
+import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
+import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
 
 
 const fileInfo = promisify(stat);
 const fileInfo = promisify(stat);
 
 
@@ -80,49 +82,55 @@ export class AssetService {
     return res;
     return res;
   }
   }
 
 
-  public async getAllAssets(authUser: AuthUserDto) {
-    try {
-      return await this.assetRepository.find({
-        where: {
-          userId: authUser.id,
-          resizePath: Not(IsNull()),
-        },
-        relations: ['exifInfo'],
-        order: {
-          createdAt: 'DESC',
-        },
-      });
-    } catch (e) {
-      Logger.error(e, 'getAllAssets');
-    }
+  public async getAllAssets(authUser: AuthUserDto): Promise<AssetResponseDto[]> {
+    const assets = await this.assetRepository.find({
+      where: {
+        userId: authUser.id,
+        resizePath: Not(IsNull()),
+      },
+      relations: ['exifInfo'],
+      order: {
+        createdAt: 'DESC',
+      },
+    });
+
+    return assets.map((asset) => mapAsset(asset));
   }
   }
 
 
-  public async findOne(deviceId: string, assetId: string): Promise<AssetEntity> {
+  public async findAssetOfDevice(deviceId: string, assetId: string): Promise<AssetResponseDto> {
     const rows = await this.assetRepository.query(
     const rows = await this.assetRepository.query(
       'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
       'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
       [assetId, deviceId],
       [assetId, deviceId],
     );
     );
 
 
     if (rows.lengh == 0) {
     if (rows.lengh == 0) {
-      throw new BadRequestException('Not Found');
+      throw new NotFoundException('Not Found');
     }
     }
 
 
-    return rows[0] as AssetEntity;
+    const assetOnDevice = rows[0] as AssetEntity;
+
+    return mapAsset(assetOnDevice);
   }
   }
 
 
-  public async getAssetById(authUser: AuthUserDto, assetId: string) {
-    return await this.assetRepository.findOne({
+  public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
+    const asset = await this.assetRepository.findOne({
       where: {
       where: {
         id: assetId,
         id: assetId,
       },
       },
       relations: ['exifInfo'],
       relations: ['exifInfo'],
     });
     });
+
+    if (!asset) {
+      throw new NotFoundException('Asset not found');
+    }
+
+    return mapAsset(asset);
   }
   }
 
 
   public async downloadFile(query: ServeFileDto, res: Res) {
   public async downloadFile(query: ServeFileDto, res: Res) {
     try {
     try {
       let fileReadStream = null;
       let fileReadStream = null;
-      const asset = await this.findOne(query.did, query.aid);
+      const asset = await this.findAssetOfDevice(query.did, query.aid);
 
 
       if (query.isThumb === 'false' || !query.isThumb) {
       if (query.isThumb === 'false' || !query.isThumb) {
         const { size } = await fileInfo(asset.originalPath);
         const { size } = await fileInfo(asset.originalPath);
@@ -188,7 +196,7 @@ export class AssetService {
 
 
   public async serveFile(authUser: AuthUserDto, query: ServeFileDto, res: Res, headers: any) {
   public async serveFile(authUser: AuthUserDto, query: ServeFileDto, res: Res, headers: any) {
     let fileReadStream: ReadStream;
     let fileReadStream: ReadStream;
-    const asset = await this.findOne(query.did, query.aid);
+    const asset = await this.findAssetOfDevice(query.did, query.aid);
 
 
     if (!asset) {
     if (!asset) {
       throw new NotFoundException('Asset does not exist');
       throw new NotFoundException('Asset does not exist');
@@ -258,12 +266,13 @@ export class AssetService {
       try {
       try {
         // Handle Video
         // Handle Video
         let videoPath = asset.originalPath;
         let videoPath = asset.originalPath;
+
         let mimeType = asset.mimeType;
         let mimeType = asset.mimeType;
 
 
         await fs.access(videoPath, constants.R_OK | constants.W_OK);
         await fs.access(videoPath, constants.R_OK | constants.W_OK);
 
 
         if (query.isWeb && asset.mimeType == 'video/quicktime') {
         if (query.isWeb && asset.mimeType == 'video/quicktime') {
-          videoPath = asset.encodedVideoPath == '' ? asset.originalPath : asset.encodedVideoPath;
+          videoPath = asset.encodedVideoPath == '' ? String(asset.originalPath) : String(asset.encodedVideoPath);
           mimeType = asset.encodedVideoPath == '' ? asset.mimeType : 'video/mp4';
           mimeType = asset.encodedVideoPath == '' ? asset.mimeType : 'video/mp4';
         }
         }
 
 
@@ -390,7 +399,7 @@ export class AssetService {
     return Array.from(possibleSearchTerm).filter((x) => x != null);
     return Array.from(possibleSearchTerm).filter((x) => x != null);
   }
   }
 
 
-  async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto) {
+  async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto): Promise<AssetResponseDto[]> {
     const query = `
     const query = `
     SELECT a.*
     SELECT a.*
     FROM assets a
     FROM assets a
@@ -406,7 +415,12 @@ export class AssetService {
         );
         );
     `;
     `;
 
 
-    return await this.assetRepository.query(query, [authUser.id, searchAssetDto.searchTerm]);
+    const searchResults: AssetEntity[] = await this.assetRepository.query(query, [
+      authUser.id,
+      searchAssetDto.searchTerm,
+    ]);
+
+    return searchResults.map((asset) => mapAsset(asset));
   }
   }
 
 
   async getCuratedLocation(authUser: AuthUserDto) {
   async getCuratedLocation(authUser: AuthUserDto) {
@@ -423,8 +437,8 @@ export class AssetService {
     );
     );
   }
   }
 
 
-  async getCuratedObject(authUser: AuthUserDto) {
-    return await this.assetRepository.query(
+  async getCuratedObject(authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
+    const curatedObjects: CuratedObjectsResponseDto[] = await this.assetRepository.query(
       `
       `
         SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
         SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
         FROM assets a
         FROM assets a
@@ -434,9 +448,11 @@ export class AssetService {
       `,
       `,
       [authUser.id],
       [authUser.id],
     );
     );
+
+    return curatedObjects;
   }
   }
 
 
-  async checkDuplicatedAsset(authUser: AuthUserDto, checkDuplicateAssetDto: CheckDuplicateAssetDto) {
+  async checkDuplicatedAsset(authUser: AuthUserDto, checkDuplicateAssetDto: CheckDuplicateAssetDto): Promise<boolean> {
     const res = await this.assetRepository.findOne({
     const res = await this.assetRepository.findOne({
       where: {
       where: {
         deviceAssetId: checkDuplicateAssetDto.deviceAssetId,
         deviceAssetId: checkDuplicateAssetDto.deviceAssetId,

+ 11 - 0
server/apps/immich/src/api-v1/asset/dto/delete-asset.dto.ts

@@ -1,6 +1,17 @@
+import { ApiProperty } from '@nestjs/swagger';
 import { IsNotEmpty } from 'class-validator';
 import { IsNotEmpty } from 'class-validator';
 
 
 export class DeleteAssetDto {
 export class DeleteAssetDto {
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({
+    isArray: true,
+    type: String,
+    title: 'Array of asset IDs to delete',
+    example: [
+      'bf973405-3f2a-48d2-a687-2ed4167164be',
+      'dd41870b-5d00-46d2-924e-1d8489a0aa0f',
+      'fad77c3f-deef-4e7e-9608-14c1aa4e559a',
+    ],
+  })
   ids!: string[];
   ids!: string[];
 }
 }

+ 3 - 2
server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts

@@ -1,12 +1,13 @@
+import { ApiProperty } from '@nestjs/swagger';
 import { IsBooleanString, IsNotEmpty, IsOptional } from 'class-validator';
 import { IsBooleanString, IsNotEmpty, IsOptional } from 'class-validator';
 
 
 export class ServeFileDto {
 export class ServeFileDto {
-  //assetId
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ type: String, title: 'Device Asset ID' })
   aid!: string;
   aid!: string;
 
 
-  //deviceId
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ type: String, title: 'Device ID' })
   did!: string;
   did!: string;
 
 
   @IsOptional()
   @IsOptional()

+ 17 - 13
server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts

@@ -2,19 +2,21 @@ import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
 import { ExifResponseDto, mapExif } from './exif-response.dto';
 import { ExifResponseDto, mapExif } from './exif-response.dto';
 import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto';
 import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto';
 
 
-export interface AssetResponseDto {
-  id: string;
-  deviceAssetId: string;
-  ownerId: string;
-  deviceId: string;
-  type: AssetType;
-  originalPath: string;
-  resizePath: string | null;
-  createdAt: string;
-  modifiedAt: string;
-  isFavorite: boolean;
-  mimeType: string | null;
-  duration: string;
+export class AssetResponseDto {
+  id!: string;
+  deviceAssetId!: string;
+  ownerId!: string;
+  deviceId!: string;
+  type!: AssetType;
+  originalPath!: string;
+  resizePath!: string | null;
+  createdAt!: string;
+  modifiedAt!: string;
+  isFavorite!: boolean;
+  mimeType!: string | null;
+  duration!: string;
+  webpPath!: string | null;
+  encodedVideoPath!: string | null;
   exifInfo?: ExifResponseDto;
   exifInfo?: ExifResponseDto;
   smartInfo?: SmartInfoResponseDto;
   smartInfo?: SmartInfoResponseDto;
 }
 }
@@ -32,6 +34,8 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto {
     modifiedAt: entity.modifiedAt,
     modifiedAt: entity.modifiedAt,
     isFavorite: entity.isFavorite,
     isFavorite: entity.isFavorite,
     mimeType: entity.mimeType,
     mimeType: entity.mimeType,
+    webpPath: entity.webpPath,
+    encodedVideoPath: entity.encodedVideoPath,
     duration: entity.duration ?? '0:00:00.00000',
     duration: entity.duration ?? '0:00:00.00000',
     exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
     exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
     smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
     smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,

+ 7 - 0
server/apps/immich/src/api-v1/asset/response-dto/curated-locations-response.dto.ts

@@ -0,0 +1,7 @@
+export class CuratedLocationsResponseDto {
+  id!: string;
+  city!: string;
+  resizePath!: string;
+  deviceAssetId!: string;
+  deviceId!: string;
+}

+ 7 - 0
server/apps/immich/src/api-v1/asset/response-dto/curated-objects-response.dto.ts

@@ -0,0 +1,7 @@
+export class CuratedObjectsResponseDto {
+  id!: string;
+  object!: string;
+  resizePath!: string;
+  deviceAssetId!: string;
+  deviceId!: string;
+}

+ 21 - 21
server/apps/immich/src/api-v1/asset/response-dto/exif-response.dto.ts

@@ -1,26 +1,26 @@
 import { ExifEntity } from '@app/database/entities/exif.entity';
 import { ExifEntity } from '@app/database/entities/exif.entity';
 
 
-export interface ExifResponseDto {
-  id: string;
-  make: string | null;
-  model: string | null;
-  imageName: string | null;
-  exifImageWidth: number | null;
-  exifImageHeight: number | null;
-  fileSizeInByte: number | null;
-  orientation: string | null;
-  dateTimeOriginal: Date | null;
-  modifyDate: Date | null;
-  lensModel: string | null;
-  fNumber: number | null;
-  focalLength: number | null;
-  iso: number | null;
-  exposureTime: number | null;
-  latitude: number | null;
-  longitude: number | null;
-  city: string | null;
-  state: string | null;
-  country: string | null;
+export class ExifResponseDto {
+  id!: string;
+  make: string | null = null;
+  model: string | null = null;
+  imageName: string | null = null;
+  exifImageWidth: number | null = null;
+  exifImageHeight: number | null = null;
+  fileSizeInByte: number | null = null;
+  orientation: string | null = null;
+  dateTimeOriginal: Date | null = null;
+  modifyDate: Date | null = null;
+  lensModel: string | null = null;
+  fNumber: number | null = null;
+  focalLength: number | null = null;
+  iso: number | null = null;
+  exposureTime: number | null = null;
+  latitude: number | null = null;
+  longitude: number | null = null;
+  city: string | null = null;
+  state: string | null = null;
+  country: string | null = null;
 }
 }
 
 
 export function mapExif(entity: ExifEntity): ExifResponseDto {
 export function mapExif(entity: ExifEntity): ExifResponseDto {

+ 4 - 4
server/apps/immich/src/api-v1/asset/response-dto/smart-info-response.dto.ts

@@ -1,9 +1,9 @@
 import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
 import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
 
 
-export interface SmartInfoResponseDto {
-  id: string;
-  tags: string[] | null;
-  objects: string[] | null;
+export class SmartInfoResponseDto {
+  id?: string;
+  tags?: string[] | null;
+  objects?: string[] | null;
 }
 }
 
 
 export function mapSmartInfo(entity: SmartInfoEntity): SmartInfoResponseDto {
 export function mapSmartInfo(entity: SmartInfoEntity): SmartInfoResponseDto {

+ 19 - 7
server/apps/immich/src/api-v1/auth/auth.controller.ts

@@ -1,30 +1,42 @@
 import { Body, Controller, Post, UseGuards, ValidationPipe } from '@nestjs/common';
 import { Body, Controller, Post, UseGuards, ValidationPipe } from '@nestjs/common';
+import {
+  ApiBadRequestResponse,
+  ApiBearerAuth,
+  ApiBody,
+  ApiCreatedResponse,
+  ApiResponse,
+  ApiTags,
+} from '@nestjs/swagger';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { AuthService } from './auth.service';
 import { AuthService } from './auth.service';
 import { LoginCredentialDto } from './dto/login-credential.dto';
 import { LoginCredentialDto } from './dto/login-credential.dto';
+import { LoginResponseDto } from './response-dto/login-response.dto';
 import { SignUpDto } from './dto/sign-up.dto';
 import { SignUpDto } from './dto/sign-up.dto';
+import { AdminSignupResponseDto } from './response-dto/admin-signup-response.dto';
+import { ValidateAccessTokenResponseDto } from './response-dto/validate-asset-token-response.dto,';
 
 
+@ApiTags('Authentication')
 @Controller('auth')
 @Controller('auth')
 export class AuthController {
 export class AuthController {
   constructor(private readonly authService: AuthService) {}
   constructor(private readonly authService: AuthService) {}
 
 
   @Post('/login')
   @Post('/login')
-  async login(@Body(ValidationPipe) loginCredential: LoginCredentialDto) {
+  async login(@Body(ValidationPipe) loginCredential: LoginCredentialDto): Promise<LoginResponseDto> {
     return await this.authService.login(loginCredential);
     return await this.authService.login(loginCredential);
   }
   }
 
 
   @Post('/admin-sign-up')
   @Post('/admin-sign-up')
-  async adminSignUp(@Body(ValidationPipe) signUpCrendential: SignUpDto) {
-    return await this.authService.adminSignUp(signUpCrendential);
+  @ApiBadRequestResponse({ description: 'The server already has an admin' })
+  async adminSignUp(@Body(ValidationPipe) signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
+    return await this.authService.adminSignUp(signUpCredential);
   }
   }
 
 
   @UseGuards(JwtAuthGuard)
   @UseGuards(JwtAuthGuard)
+  @ApiBearerAuth()
   @Post('/validateToken')
   @Post('/validateToken')
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  async validateToken(@GetAuthUser() authUser: AuthUserDto) {
-    return {
-      authStatus: true,
-    };
+  async validateAccessToken(@GetAuthUser() authUser: AuthUserDto): Promise<ValidateAccessTokenResponseDto> {
+    return new ValidateAccessTokenResponseDto(true);
   }
   }
 }
 }

+ 7 - 14
server/apps/immich/src/api-v1/auth/auth.service.ts

@@ -7,7 +7,8 @@ import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
 import { JwtPayloadDto } from './dto/jwt-payload.dto';
 import { JwtPayloadDto } from './dto/jwt-payload.dto';
 import { SignUpDto } from './dto/sign-up.dto';
 import { SignUpDto } from './dto/sign-up.dto';
 import * as bcrypt from 'bcrypt';
 import * as bcrypt from 'bcrypt';
-import { mapUser, UserResponseDto } from '../user/response-dto/user-response.dto';
+import { LoginResponseDto, mapLoginResponse } from './response-dto/login-response.dto';
+import { AdminSignupResponseDto, mapAdminSignupResponse } from './response-dto/admin-signup-response.dto';
 
 
 @Injectable()
 @Injectable()
 export class AuthService {
 export class AuthService {
@@ -49,7 +50,7 @@ export class AuthService {
     return null;
     return null;
   }
   }
 
 
-  public async login(loginCredential: LoginCredentialDto) {
+  public async login(loginCredential: LoginCredentialDto): Promise<LoginResponseDto> {
     const validatedUser = await this.validateUser(loginCredential);
     const validatedUser = await this.validateUser(loginCredential);
 
 
     if (!validatedUser) {
     if (!validatedUser) {
@@ -57,20 +58,12 @@ export class AuthService {
     }
     }
 
 
     const payload = new JwtPayloadDto(validatedUser.id, validatedUser.email);
     const payload = new JwtPayloadDto(validatedUser.id, validatedUser.email);
+    const accessToken = await this.immichJwtService.generateToken(payload);
 
 
-    return {
-      accessToken: await this.immichJwtService.generateToken(payload),
-      userId: validatedUser.id,
-      userEmail: validatedUser.email,
-      firstName: validatedUser.firstName,
-      lastName: validatedUser.lastName,
-      isAdmin: validatedUser.isAdmin,
-      profileImagePath: validatedUser.profileImagePath,
-      shouldChangePassword: validatedUser.shouldChangePassword,
-    };
+    return mapLoginResponse(validatedUser, accessToken);
   }
   }
 
 
-  public async adminSignUp(signUpCredential: SignUpDto): Promise<UserResponseDto> {
+  public async adminSignUp(signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
     const adminUser = await this.userRepository.findOne({ where: { isAdmin: true } });
     const adminUser = await this.userRepository.findOne({ where: { isAdmin: true } });
 
 
     if (adminUser) {
     if (adminUser) {
@@ -88,7 +81,7 @@ export class AuthService {
     try {
     try {
       const savedNewAdminUserUser = await this.userRepository.save(newAdminUser);
       const savedNewAdminUserUser = await this.userRepository.save(newAdminUser);
 
 
-      return mapUser(savedNewAdminUserUser);
+      return mapAdminSignupResponse(savedNewAdminUserUser);
     } catch (e) {
     } catch (e) {
       Logger.error('e', 'signUp');
       Logger.error('e', 'signUp');
       throw new InternalServerErrorException('Failed to register new admin user');
       throw new InternalServerErrorException('Failed to register new admin user');

+ 3 - 0
server/apps/immich/src/api-v1/auth/dto/login-credential.dto.ts

@@ -1,9 +1,12 @@
+import { ApiProperty } from '@nestjs/swagger';
 import { IsNotEmpty } from 'class-validator';
 import { IsNotEmpty } from 'class-validator';
 
 
 export class LoginCredentialDto {
 export class LoginCredentialDto {
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'testuser@email.com' })
   email!: string;
   email!: string;
 
 
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'password' })
   password!: string;
   password!: string;
 }
 }

+ 5 - 0
server/apps/immich/src/api-v1/auth/dto/sign-up.dto.ts

@@ -1,15 +1,20 @@
+import { ApiProperty } from '@nestjs/swagger';
 import { IsNotEmpty } from 'class-validator';
 import { IsNotEmpty } from 'class-validator';
 
 
 export class SignUpDto {
 export class SignUpDto {
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'testuser@email.com' })
   email!: string;
   email!: string;
 
 
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'password' })
   password!: string;
   password!: string;
 
 
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'Admin' })
   firstName!: string;
   firstName!: string;
 
 
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'Doe' })
   lastName!: string;
   lastName!: string;
 }
 }

+ 19 - 0
server/apps/immich/src/api-v1/auth/response-dto/admin-signup-response.dto.ts

@@ -0,0 +1,19 @@
+import { UserEntity } from '@app/database/entities/user.entity';
+
+export class AdminSignupResponseDto {
+  id!: string;
+  email!: string;
+  firstName!: string;
+  lastName!: string;
+  createdAt!: string;
+}
+
+export function mapAdminSignupResponse(entity: UserEntity): AdminSignupResponseDto {
+  return {
+    id: entity.id,
+    email: entity.email,
+    firstName: entity.firstName,
+    lastName: entity.lastName,
+    createdAt: entity.createdAt,
+  };
+}

+ 41 - 0
server/apps/immich/src/api-v1/auth/response-dto/login-response.dto.ts

@@ -0,0 +1,41 @@
+import { UserEntity } from '@app/database/entities/user.entity';
+import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger';
+
+export class LoginResponseDto {
+  @ApiResponseProperty()
+  accessToken!: string;
+
+  @ApiResponseProperty()
+  userId!: string;
+
+  @ApiResponseProperty()
+  userEmail!: string;
+
+  @ApiResponseProperty()
+  firstName!: string;
+
+  @ApiResponseProperty()
+  lastName!: string;
+
+  @ApiResponseProperty()
+  profileImagePath!: string;
+
+  @ApiResponseProperty()
+  isAdmin!: boolean;
+
+  @ApiResponseProperty()
+  shouldChangePassword!: boolean;
+}
+
+export function mapLoginResponse(entity: UserEntity, accessToken: string): LoginResponseDto {
+  return {
+    accessToken: accessToken,
+    userId: entity.id,
+    userEmail: entity.email,
+    firstName: entity.firstName,
+    lastName: entity.lastName,
+    isAdmin: entity.isAdmin,
+    profileImagePath: entity.profileImagePath,
+    shouldChangePassword: entity.shouldChangePassword,
+  };
+}

+ 7 - 0
server/apps/immich/src/api-v1/auth/response-dto/validate-asset-token-response.dto,.ts

@@ -0,0 +1,7 @@
+export class ValidateAccessTokenResponseDto {
+  constructor(authStatus: boolean) {
+    this.authStatus = authStatus;
+  }
+
+  authStatus: boolean;
+}

+ 13 - 3
server/apps/immich/src/api-v1/device-info/device-info.controller.ts

@@ -1,22 +1,32 @@
 import { Controller, Post, Body, Patch, UseGuards, ValidationPipe } from '@nestjs/common';
 import { Controller, Post, Body, Patch, UseGuards, ValidationPipe } from '@nestjs/common';
+import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { DeviceInfoService } from './device-info.service';
 import { DeviceInfoService } from './device-info.service';
 import { CreateDeviceInfoDto } from './dto/create-device-info.dto';
 import { CreateDeviceInfoDto } from './dto/create-device-info.dto';
 import { UpdateDeviceInfoDto } from './dto/update-device-info.dto';
 import { UpdateDeviceInfoDto } from './dto/update-device-info.dto';
+import { DeviceInfoResponseDto } from './response-dto/create-device-info-response.dto';
 
 
 @UseGuards(JwtAuthGuard)
 @UseGuards(JwtAuthGuard)
+@ApiBearerAuth()
+@ApiTags('Device Info')
 @Controller('device-info')
 @Controller('device-info')
 export class DeviceInfoController {
 export class DeviceInfoController {
   constructor(private readonly deviceInfoService: DeviceInfoService) {}
   constructor(private readonly deviceInfoService: DeviceInfoService) {}
 
 
   @Post()
   @Post()
-  async create(@Body(ValidationPipe) createDeviceInfoDto: CreateDeviceInfoDto, @GetAuthUser() authUser: AuthUserDto) {
-    return await this.deviceInfoService.create(createDeviceInfoDto, authUser);
+  async createDeviceInfo(
+    @Body(ValidationPipe) createDeviceInfoDto: CreateDeviceInfoDto,
+    @GetAuthUser() authUser: AuthUserDto,
+  ): Promise<DeviceInfoResponseDto> {
+    return this.deviceInfoService.create(createDeviceInfoDto, authUser);
   }
   }
 
 
   @Patch()
   @Patch()
-  async update(@Body(ValidationPipe) updateDeviceInfoDto: UpdateDeviceInfoDto, @GetAuthUser() authUser: AuthUserDto) {
+  async updateDeviceInfo(
+    @Body(ValidationPipe) updateDeviceInfoDto: UpdateDeviceInfoDto,
+    @GetAuthUser() authUser: AuthUserDto,
+  ): Promise<DeviceInfoResponseDto> {
     return this.deviceInfoService.update(authUser.id, updateDeviceInfoDto);
     return this.deviceInfoService.update(authUser.id, updateDeviceInfoDto);
   }
   }
 }
 }

+ 16 - 11
server/apps/immich/src/api-v1/device-info/device-info.service.ts

@@ -1,10 +1,11 @@
-import { BadRequestException, Injectable, Logger } from '@nestjs/common';
+import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { InjectRepository } from '@nestjs/typeorm';
 import { Repository } from 'typeorm';
 import { Repository } from 'typeorm';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { CreateDeviceInfoDto } from './dto/create-device-info.dto';
 import { CreateDeviceInfoDto } from './dto/create-device-info.dto';
 import { UpdateDeviceInfoDto } from './dto/update-device-info.dto';
 import { UpdateDeviceInfoDto } from './dto/update-device-info.dto';
 import { DeviceInfoEntity } from '@app/database/entities/device-info.entity';
 import { DeviceInfoEntity } from '@app/database/entities/device-info.entity';
+import { DeviceInfoResponseDto, mapDeviceInfoResponse } from './response-dto/create-device-info-response.dto';
 
 
 @Injectable()
 @Injectable()
 export class DeviceInfoService {
 export class DeviceInfoService {
@@ -13,7 +14,7 @@ export class DeviceInfoService {
     private deviceRepository: Repository<DeviceInfoEntity>,
     private deviceRepository: Repository<DeviceInfoEntity>,
   ) {}
   ) {}
 
 
-  async create(createDeviceInfoDto: CreateDeviceInfoDto, authUser: AuthUserDto) {
+  async create(createDeviceInfoDto: CreateDeviceInfoDto, authUser: AuthUserDto): Promise<DeviceInfoResponseDto> {
     const res = await this.deviceRepository.findOne({
     const res = await this.deviceRepository.findOne({
       where: {
       where: {
         deviceId: createDeviceInfoDto.deviceId,
         deviceId: createDeviceInfoDto.deviceId,
@@ -23,7 +24,7 @@ export class DeviceInfoService {
 
 
     if (res) {
     if (res) {
       Logger.log('Device Info Exist', 'createDeviceInfo');
       Logger.log('Device Info Exist', 'createDeviceInfo');
-      return res;
+      return mapDeviceInfoResponse(res);
     }
     }
 
 
     const deviceInfo = new DeviceInfoEntity();
     const deviceInfo = new DeviceInfoEntity();
@@ -31,20 +32,18 @@ export class DeviceInfoService {
     deviceInfo.deviceType = createDeviceInfoDto.deviceType;
     deviceInfo.deviceType = createDeviceInfoDto.deviceType;
     deviceInfo.userId = authUser.id;
     deviceInfo.userId = authUser.id;
 
 
-    try {
-      return await this.deviceRepository.save(deviceInfo);
-    } catch (e) {
-      Logger.error('Error creating new device info', 'createDeviceInfo');
-    }
+    const newDeviceInfo = await this.deviceRepository.save(deviceInfo);
+
+    return mapDeviceInfoResponse(newDeviceInfo);
   }
   }
 
 
-  async update(userId: string, updateDeviceInfoDto: UpdateDeviceInfoDto) {
+  async update(userId: string, updateDeviceInfoDto: UpdateDeviceInfoDto): Promise<DeviceInfoResponseDto> {
     const deviceInfo = await this.deviceRepository.findOne({
     const deviceInfo = await this.deviceRepository.findOne({
       where: { deviceId: updateDeviceInfoDto.deviceId, userId: userId },
       where: { deviceId: updateDeviceInfoDto.deviceId, userId: userId },
     });
     });
 
 
     if (!deviceInfo) {
     if (!deviceInfo) {
-      throw new BadRequestException('Device Not Found');
+      throw new NotFoundException('Device Not Found');
     }
     }
 
 
     const res = await this.deviceRepository.update(
     const res = await this.deviceRepository.update(
@@ -55,9 +54,15 @@ export class DeviceInfoService {
     );
     );
 
 
     if (res.affected == 1) {
     if (res.affected == 1) {
-      return await this.deviceRepository.findOne({
+      const updatedDeviceInfo = await this.deviceRepository.findOne({
         where: { deviceId: updateDeviceInfoDto.deviceId, userId: userId },
         where: { deviceId: updateDeviceInfoDto.deviceId, userId: userId },
       });
       });
+
+      if (!updatedDeviceInfo) {
+        throw new NotFoundException('Device Not Found');
+      }
+
+      return mapDeviceInfoResponse(updatedDeviceInfo);
     } else {
     } else {
       throw new BadRequestException('Bad Request');
       throw new BadRequestException('Bad Request');
     }
     }

+ 23 - 0
server/apps/immich/src/api-v1/device-info/response-dto/create-device-info-response.dto.ts

@@ -0,0 +1,23 @@
+import { DeviceInfoEntity, DeviceType } from '@app/database/entities/device-info.entity';
+
+export class DeviceInfoResponseDto {
+  id!: number;
+  userId!: string;
+  deviceId!: string;
+  deviceType!: DeviceType;
+  notificationToken!: string | null;
+  createdAt!: string;
+  isAutoBackup!: boolean;
+}
+
+export function mapDeviceInfoResponse(entity: DeviceInfoEntity): DeviceInfoResponseDto {
+  return {
+    id: entity.id,
+    userId: entity.userId,
+    deviceId: entity.deviceId,
+    deviceType: entity.deviceType,
+    notificationToken: entity.notificationToken,
+    createdAt: entity.createdAt,
+    isAutoBackup: entity.isAutoBackup,
+  };
+}

+ 1 - 2
server/apps/immich/src/api-v1/server-info/dto/server-info.dto.ts → server/apps/immich/src/api-v1/server-info/response-dto/server-info-response.dto.ts

@@ -1,5 +1,4 @@
-// TODO: this is being used as a response DTO. Should be changed to interface
-export class ServerInfoDto {
+export class ServerInfoResponseDto {
   diskSize!: string;
   diskSize!: string;
   diskUse!: string;
   diskUse!: string;
   diskAvailable!: string;
   diskAvailable!: string;

+ 10 - 0
server/apps/immich/src/api-v1/server-info/response-dto/server-ping-response.dto.ts

@@ -0,0 +1,10 @@
+import { ApiResponseProperty } from '@nestjs/swagger';
+
+export class ServerPingResponse {
+  constructor(res: string) {
+    this.res = res;
+  }
+
+  @ApiResponseProperty({ type: String, example: 'pong' })
+  res!: string;
+}

+ 8 - 0
server/apps/immich/src/api-v1/server-info/response-dto/server-version-response.dto.ts

@@ -0,0 +1,8 @@
+import { IServerVersion } from 'apps/immich/src/constants/server_version.constant';
+
+export class ServerVersionReponseDto implements IServerVersion {
+  major!: number;
+  minor!: number;
+  patch!: number;
+  build!: number;
+}

+ 9 - 15
server/apps/immich/src/api-v1/server-info/server-info.controller.ts

@@ -3,34 +3,28 @@ import { ConfigService } from '@nestjs/config';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { ServerInfoService } from './server-info.service';
 import { ServerInfoService } from './server-info.service';
 import { serverVersion } from '../../constants/server_version.constant';
 import { serverVersion } from '../../constants/server_version.constant';
+import { ApiTags } from '@nestjs/swagger';
+import { ServerPingResponse } from './response-dto/server-ping-response.dto';
+import { ServerVersionReponseDto } from './response-dto/server-version-response.dto';
+import { ServerInfoResponseDto } from './response-dto/server-info-response.dto';
 
 
+@ApiTags('Server Info')
 @Controller('server-info')
 @Controller('server-info')
 export class ServerInfoController {
 export class ServerInfoController {
   constructor(private readonly serverInfoService: ServerInfoService, private readonly configService: ConfigService) {}
   constructor(private readonly serverInfoService: ServerInfoService, private readonly configService: ConfigService) {}
 
 
   @Get()
   @Get()
-  async getServerInfo() {
+  async getServerInfo(): Promise<ServerInfoResponseDto> {
     return await this.serverInfoService.getServerInfo();
     return await this.serverInfoService.getServerInfo();
   }
   }
 
 
   @Get('/ping')
   @Get('/ping')
-  async getServerPulse() {
-    return {
-      res: 'pong',
-    };
-  }
-
-  @UseGuards(JwtAuthGuard)
-  @Get('/mapbox')
-  async getMapboxInfo() {
-    return {
-      isEnable: this.configService.get('ENABLE_MAPBOX'),
-      mapboxSecret: this.configService.get('MAPBOX_KEY'),
-    };
+  async pingServer(): Promise<ServerPingResponse> {
+    return new ServerPingResponse('pong');
   }
   }
 
 
   @Get('/version')
   @Get('/version')
-  async getServerVersion() {
+  async getServerVersion(): Promise<ServerVersionReponseDto> {
     return serverVersion;
     return serverVersion;
   }
   }
 }
 }

+ 2 - 2
server/apps/immich/src/api-v1/server-info/server-info.service.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@nestjs/common';
 import { Injectable } from '@nestjs/common';
-import { ServerInfoDto } from './dto/server-info.dto';
+import { ServerInfoResponseDto } from './response-dto/server-info-response.dto';
 import diskusage from 'diskusage';
 import diskusage from 'diskusage';
 import { APP_UPLOAD_LOCATION } from '../../constants/upload_location.constant';
 import { APP_UPLOAD_LOCATION } from '../../constants/upload_location.constant';
 
 
@@ -10,7 +10,7 @@ export class ServerInfoService {
 
 
     const usagePercentage = (((diskInfo.total - diskInfo.free) / diskInfo.total) * 100).toFixed(2);
     const usagePercentage = (((diskInfo.total - diskInfo.free) / diskInfo.total) * 100).toFixed(2);
 
 
-    const serverInfo = new ServerInfoDto();
+    const serverInfo = new ServerInfoResponseDto();
     serverInfo.diskAvailable = ServerInfoService.getHumanReadableString(diskInfo.available);
     serverInfo.diskAvailable = ServerInfoService.getHumanReadableString(diskInfo.available);
     serverInfo.diskSize = ServerInfoService.getHumanReadableString(diskInfo.total);
     serverInfo.diskSize = ServerInfoService.getHumanReadableString(diskInfo.total);
     serverInfo.diskUse = ServerInfoService.getHumanReadableString(diskInfo.total - diskInfo.free);
     serverInfo.diskUse = ServerInfoService.getHumanReadableString(diskInfo.total - diskInfo.free);

+ 6 - 0
server/apps/immich/src/api-v1/user/dto/create-profile-image.dto.ts

@@ -0,0 +1,6 @@
+import { ApiProperty } from '@nestjs/swagger';
+
+export class CreateProfileImageDto {
+  @ApiProperty({ type: 'string', format: 'binary' })
+  file: any;
+}

+ 5 - 12
server/apps/immich/src/api-v1/user/dto/create-user.dto.ts

@@ -1,27 +1,20 @@
+import { ApiProperty } from '@nestjs/swagger';
 import { IsNotEmpty, IsOptional } from 'class-validator';
 import { IsNotEmpty, IsOptional } from 'class-validator';
 
 
 export class CreateUserDto {
 export class CreateUserDto {
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'testuser@email.com' })
   email!: string;
   email!: string;
 
 
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'password' })
   password!: string;
   password!: string;
 
 
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'John' })
   firstName!: string;
   firstName!: string;
 
 
   @IsNotEmpty()
   @IsNotEmpty()
+  @ApiProperty({ example: 'Doe' })
   lastName!: string;
   lastName!: string;
-
-  @IsOptional()
-  profileImagePath?: string;
-
-  @IsOptional()
-  isAdmin?: boolean;
-
-  @IsOptional()
-  shouldChangePassword?: boolean;
-
-  @IsOptional()
-  id?: string;
 }
 }

+ 23 - 3
server/apps/immich/src/api-v1/user/dto/update-user.dto.ts

@@ -1,4 +1,24 @@
-import { PartialType } from '@nestjs/mapped-types';
-import { CreateUserDto } from './create-user.dto';
+import { IsNotEmpty, IsOptional } from 'class-validator';
 
 
-export class UpdateUserDto extends PartialType(CreateUserDto) {}
+export class UpdateUserDto {
+  @IsNotEmpty()
+  id!: string;
+
+  @IsOptional()
+  password?: string;
+
+  @IsOptional()
+  firstName?: string;
+
+  @IsOptional()
+  lastName?: string;
+
+  @IsOptional()
+  isAdmin?: boolean;
+
+  @IsOptional()
+  shouldChangePassword?: boolean;
+
+  @IsOptional()
+  profileImagePath?: string;
+}

+ 11 - 0
server/apps/immich/src/api-v1/user/response-dto/create-profile-image-response.dto.ts

@@ -0,0 +1,11 @@
+export class CreateProfileImageResponseDto {
+  userId!: string;
+  profileImagePath!: string;
+}
+
+export function mapCreateProfileImageResponse(userId: string, profileImagePath: string): CreateProfileImageResponseDto {
+  return {
+    userId: userId,
+    profileImagePath: profileImagePath,
+  };
+}

+ 10 - 0
server/apps/immich/src/api-v1/user/response-dto/user-count-response.dto.ts

@@ -0,0 +1,10 @@
+
+export class UserCountResponseDto {
+  userCount!: number;
+}
+
+export function mapUserCountResponse(count: number): UserCountResponseDto {
+  return {
+    userCount: count,
+  };
+}

+ 13 - 7
server/apps/immich/src/api-v1/user/response-dto/user-response.dto.ts

@@ -1,11 +1,14 @@
-import { UserEntity } from '../../../../../../libs/database/src/entities/user.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 
 
-export interface UserResponseDto {
-  id: string;
-  email: string;
-  firstName: string;
-  lastName: string;
-  createdAt: string;
+export class UserResponseDto {
+  id!: string;
+  email!: string;
+  firstName!: string;
+  lastName!: string;
+  createdAt!: string;
+  profileImagePath!: string;
+  shouldChangePassword!: boolean;
+  isAdmin!: boolean;
 }
 }
 
 
 export function mapUser(entity: UserEntity): UserResponseDto {
 export function mapUser(entity: UserEntity): UserResponseDto {
@@ -15,5 +18,8 @@ export function mapUser(entity: UserEntity): UserResponseDto {
     firstName: entity.firstName,
     firstName: entity.firstName,
     lastName: entity.lastName,
     lastName: entity.lastName,
     createdAt: entity.createdAt,
     createdAt: entity.createdAt,
+    profileImagePath: entity.profileImagePath,
+    shouldChangePassword: entity.shouldChangePassword,
+    isAdmin: entity.isAdmin,
   };
   };
 }
 }

+ 32 - 9
server/apps/immich/src/api-v1/user/user.controller.ts

@@ -11,6 +11,7 @@ import {
   UseInterceptors,
   UseInterceptors,
   UploadedFile,
   UploadedFile,
   Response,
   Response,
+  StreamableFile,
 } from '@nestjs/common';
 } from '@nestjs/common';
 import { UserService } from './user.service';
 import { UserService } from './user.service';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
@@ -21,50 +22,72 @@ import { UpdateUserDto } from './dto/update-user.dto';
 import { FileInterceptor } from '@nestjs/platform-express';
 import { FileInterceptor } from '@nestjs/platform-express';
 import { profileImageUploadOption } from '../../config/profile-image-upload.config';
 import { profileImageUploadOption } from '../../config/profile-image-upload.config';
 import { Response as Res } from 'express';
 import { Response as Res } from 'express';
+import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
+import { UserResponseDto } from './response-dto/user-response.dto';
+import { UserEntity } from '@app/database/entities/user.entity';
+import { UserCountResponseDto } from './response-dto/user-count-response.dto';
+import { CreateProfileImageDto } from './dto/create-profile-image.dto';
+import { CreateProfileImageResponseDto } from './response-dto/create-profile-image-response.dto';
 
 
+@ApiTags('User')
 @Controller('user')
 @Controller('user')
 export class UserController {
 export class UserController {
   constructor(private readonly userService: UserService) {}
   constructor(private readonly userService: UserService) {}
 
 
   @UseGuards(JwtAuthGuard)
   @UseGuards(JwtAuthGuard)
+  @ApiBearerAuth()
   @Get()
   @Get()
-  async getAllUsers(@GetAuthUser() authUser: AuthUserDto, @Query('isAll') isAll: boolean) {
+  async getAllUsers(@GetAuthUser() authUser: AuthUserDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
     return await this.userService.getAllUsers(authUser, isAll);
     return await this.userService.getAllUsers(authUser, isAll);
   }
   }
 
 
   @UseGuards(JwtAuthGuard)
   @UseGuards(JwtAuthGuard)
+  @ApiBearerAuth()
   @Get('me')
   @Get('me')
-  async getUserInfo(@GetAuthUser() authUser: AuthUserDto) {
+  async getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
     return await this.userService.getUserInfo(authUser);
     return await this.userService.getUserInfo(authUser);
   }
   }
 
 
   @UseGuards(JwtAuthGuard)
   @UseGuards(JwtAuthGuard)
+  @ApiBearerAuth()
   @UseGuards(AdminRolesGuard)
   @UseGuards(AdminRolesGuard)
   @Post()
   @Post()
-  async createNewUser(@Body(ValidationPipe) createUserDto: CreateUserDto) {
+  async createUser(@Body(ValidationPipe) createUserDto: CreateUserDto): Promise<UserResponseDto> {
     return await this.userService.createUser(createUserDto);
     return await this.userService.createUser(createUserDto);
   }
   }
 
 
   @Get('/count')
   @Get('/count')
-  async getUserCount(@Query('isAdmin') isAdmin: boolean) {
+  async getUserCount(@Query('isAdmin') isAdmin: boolean): Promise<UserCountResponseDto> {
     return await this.userService.getUserCount(isAdmin);
     return await this.userService.getUserCount(isAdmin);
   }
   }
 
 
   @UseGuards(JwtAuthGuard)
   @UseGuards(JwtAuthGuard)
+  @ApiBearerAuth()
   @Put()
   @Put()
-  async updateUser(@Body(ValidationPipe) updateUserDto: UpdateUserDto) {
+  async updateUser(@Body(ValidationPipe) updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
     return await this.userService.updateUser(updateUserDto);
     return await this.userService.updateUser(updateUserDto);
   }
   }
 
 
-  @UseGuards(JwtAuthGuard)
   @UseInterceptors(FileInterceptor('file', profileImageUploadOption))
   @UseInterceptors(FileInterceptor('file', profileImageUploadOption))
+  @UseGuards(JwtAuthGuard)
+  @ApiBearerAuth()
+  @ApiConsumes('multipart/form-data')
+  @ApiBody({
+    type: CreateProfileImageDto,
+  })
   @Post('/profile-image')
   @Post('/profile-image')
-  async createProfileImage(@GetAuthUser() authUser: AuthUserDto, @UploadedFile() fileInfo: Express.Multer.File) {
+  async createProfileImage(
+    @GetAuthUser() authUser: AuthUserDto,
+    @UploadedFile() fileInfo: Express.Multer.File,
+  ): Promise<CreateProfileImageResponseDto> {
     return await this.userService.createProfileImage(authUser, fileInfo);
     return await this.userService.createProfileImage(authUser, fileInfo);
   }
   }
 
 
   @Get('/profile-image/:userId')
   @Get('/profile-image/:userId')
-  async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res) {
-    return await this.userService.getUserProfileImage(userId, res);
+  async getProfileImage(
+    @Param('userId') userId: string,
+    @Response({ passthrough: true }) res: Res,
+  ): Promise<StreamableFile | undefined> {
+    return this.userService.getUserProfileImage(userId, res);
   }
   }
 }
 }

+ 27 - 24
server/apps/immich/src/api-v1/user/user.service.ts

@@ -16,6 +16,11 @@ import * as bcrypt from 'bcrypt';
 import { createReadStream } from 'fs';
 import { createReadStream } from 'fs';
 import { Response as Res } from 'express';
 import { Response as Res } from 'express';
 import { mapUser, UserResponseDto } from './response-dto/user-response.dto';
 import { mapUser, UserResponseDto } from './response-dto/user-response.dto';
+import { mapUserCountResponse, UserCountResponseDto } from './response-dto/user-count-response.dto';
+import {
+  CreateProfileImageResponseDto,
+  mapCreateProfileImageResponse,
+} from './response-dto/create-profile-image-response.dto';
 
 
 @Injectable()
 @Injectable()
 export class UserService {
 export class UserService {
@@ -24,24 +29,32 @@ export class UserService {
     private userRepository: Repository<UserEntity>,
     private userRepository: Repository<UserEntity>,
   ) {}
   ) {}
 
 
-  async getAllUsers(authUser: AuthUserDto, isAll: boolean) {
+  async getAllUsers(authUser: AuthUserDto, isAll: boolean): Promise<UserResponseDto[]> {
     if (isAll) {
     if (isAll) {
-      return await this.userRepository.find();
+      const allUsers = await this.userRepository.find();
+
+      return allUsers.map(mapUser);
     }
     }
 
 
-    return await this.userRepository.find({
+    const allUserExceptRequestedUser = await this.userRepository.find({
       where: { id: Not(authUser.id) },
       where: { id: Not(authUser.id) },
       order: {
       order: {
         createdAt: 'DESC',
         createdAt: 'DESC',
       },
       },
     });
     });
+
+    return allUserExceptRequestedUser.map(mapUser);
   }
   }
 
 
-  async getUserInfo(authUser: AuthUserDto) {
-    return this.userRepository.findOne({ where: { id: authUser.id } });
+  async getUserInfo(authUser: AuthUserDto): Promise<UserResponseDto> {
+    const user = await this.userRepository.findOne({ where: { id: authUser.id } });
+    if (!user) {
+      throw new BadRequestException('User not found');
+    }
+    return mapUser(user);
   }
   }
 
 
-  async getUserCount(isAdmin: boolean) {
+  async getUserCount(isAdmin: boolean): Promise<UserCountResponseDto> {
     let users;
     let users;
 
 
     if (isAdmin) {
     if (isAdmin) {
@@ -50,9 +63,7 @@ export class UserService {
       users = await this.userRepository.find();
       users = await this.userRepository.find();
     }
     }
 
 
-    return {
-      userCount: users.length,
-    };
+    return mapUserCountResponse(users.length);
   }
   }
 
 
   async createUser(createUserDto: CreateUserDto): Promise<UserResponseDto> {
   async createUser(createUserDto: CreateUserDto): Promise<UserResponseDto> {
@@ -84,7 +95,7 @@ export class UserService {
     return bcrypt.hash(password, salt);
     return bcrypt.hash(password, salt);
   }
   }
 
 
-  async updateUser(updateUserDto: UpdateUserDto) {
+  async updateUser(updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
     const user = await this.userRepository.findOne({ where: { id: updateUserDto.id } });
     const user = await this.userRepository.findOne({ where: { id: updateUserDto.id } });
     if (!user) {
     if (!user) {
       throw new NotFoundException('User not found');
       throw new NotFoundException('User not found');
@@ -115,31 +126,23 @@ export class UserService {
     try {
     try {
       const updatedUser = await this.userRepository.save(user);
       const updatedUser = await this.userRepository.save(user);
 
 
-      // TODO: this should probably retrun UserResponseDto
-      return {
-        id: updatedUser.id,
-        email: updatedUser.email,
-        firstName: updatedUser.firstName,
-        lastName: updatedUser.lastName,
-        isAdmin: updatedUser.isAdmin,
-        profileImagePath: updatedUser.profileImagePath,
-      };
+      return mapUser(updatedUser);
     } catch (e) {
     } catch (e) {
       Logger.error(e, 'Create new user');
       Logger.error(e, 'Create new user');
       throw new InternalServerErrorException('Failed to register new user');
       throw new InternalServerErrorException('Failed to register new user');
     }
     }
   }
   }
 
 
-  async createProfileImage(authUser: AuthUserDto, fileInfo: Express.Multer.File) {
+  async createProfileImage(
+    authUser: AuthUserDto,
+    fileInfo: Express.Multer.File,
+  ): Promise<CreateProfileImageResponseDto> {
     try {
     try {
       await this.userRepository.update(authUser.id, {
       await this.userRepository.update(authUser.id, {
         profileImagePath: fileInfo.path,
         profileImagePath: fileInfo.path,
       });
       });
 
 
-      return {
-        userId: authUser.id,
-        profileImagePath: fileInfo.path,
-      };
+      return mapCreateProfileImageResponse(authUser.id, fileInfo.path);
     } catch (e) {
     } catch (e) {
       Logger.error(e, 'Create User Profile Image');
       Logger.error(e, 'Create User Profile Image');
       throw new InternalServerErrorException('Failed to create new user profile image');
       throw new InternalServerErrorException('Failed to create new user profile image');

+ 8 - 1
server/apps/immich/src/constants/server_version.constant.ts

@@ -1,7 +1,14 @@
 // major.minor.patch+build
 // major.minor.patch+build
 // check mobile/pubspec.yml for current release version
 // check mobile/pubspec.yml for current release version
 
 
-export const serverVersion = {
+export interface IServerVersion {
+  major: number;
+  minor: number;
+  patch: number;
+  build: number;
+}
+
+export const serverVersion: IServerVersion = {
   major: 1,
   major: 1,
   minor: 17,
   minor: 17,
   patch: 0,
   patch: 0,

+ 35 - 0
server/apps/immich/src/main.ts

@@ -1,6 +1,9 @@
 import { Logger } from '@nestjs/common';
 import { Logger } from '@nestjs/common';
 import { NestFactory } from '@nestjs/core';
 import { NestFactory } from '@nestjs/core';
 import { NestExpressApplication } from '@nestjs/platform-express';
 import { NestExpressApplication } from '@nestjs/platform-express';
+import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
+import { writeFileSync } from 'fs';
+import path from 'path';
 import { AppModule } from './app.module';
 import { AppModule } from './app.module';
 import { RedisIoAdapter } from './middlewares/redis-io.adapter.middleware';
 import { RedisIoAdapter } from './middlewares/redis-io.adapter.middleware';
 
 
@@ -15,6 +18,38 @@ async function bootstrap() {
 
 
   app.useWebSocketAdapter(new RedisIoAdapter(app));
   app.useWebSocketAdapter(new RedisIoAdapter(app));
 
 
+  const config = new DocumentBuilder()
+    .setTitle('Immich')
+    .setDescription('Immich API')
+    .setVersion('1.17.0')
+    .addBearerAuth({
+      type: 'http',
+      scheme: 'bearer',
+      bearerFormat: 'JWT',
+      name: 'JWT',
+      description: 'Enter JWT token',
+      in: 'header',
+    })
+    .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',
+  });
+
+  // Generate API Documentation
+  const outputPath = path.resolve(process.cwd(), 'immich-openapi-specs.json');
+  writeFileSync(outputPath, JSON.stringify(apiDocument), { encoding: 'utf8' });
+
   await app.listen(3001, () => {
   await app.listen(3001, () => {
     if (process.env.NODE_ENV == 'development') {
     if (process.env.NODE_ENV == 'development') {
       Logger.log('Running Immich Server in DEVELOPMENT environment', 'ImmichServer');
       Logger.log('Running Immich Server in DEVELOPMENT environment', 'ImmichServer');

+ 2 - 1
server/apps/immich/src/modules/background-task/background-task.processor.ts

@@ -5,6 +5,7 @@ import { AssetEntity } from '@app/database/entities/asset.entity';
 import fs from 'fs';
 import fs from 'fs';
 import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
 import { SmartInfoEntity } from '@app/database/entities/smart-info.entity';
 import { Job } from 'bull';
 import { Job } from 'bull';
+import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
 
 
 @Processor('background-task')
 @Processor('background-task')
 export class BackgroundTaskProcessor {
 export class BackgroundTaskProcessor {
@@ -18,7 +19,7 @@ export class BackgroundTaskProcessor {
 
 
   // TODO: Should probably use constants / Interfaces for Queue names / data
   // TODO: Should probably use constants / Interfaces for Queue names / data
   @Process('delete-file-on-disk')
   @Process('delete-file-on-disk')
-  async deleteFileOnDisk(job: Job<{ assets: AssetEntity[] }>) {
+  async deleteFileOnDisk(job: Job<{ assets: AssetResponseDto[] }>) {
     const { assets } = job.data;
     const { assets } = job.data;
 
 
     for (const asset of assets) {
     for (const asset of assets) {

+ 2 - 1
server/apps/immich/src/modules/background-task/background-task.service.ts

@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
 import { Queue } from 'bull';
 import { Queue } from 'bull';
 import { randomUUID } from 'node:crypto';
 import { randomUUID } from 'node:crypto';
 import { AssetEntity } from '@app/database/entities/asset.entity';
 import { AssetEntity } from '@app/database/entities/asset.entity';
+import { AssetResponseDto } from '../../api-v1/asset/response-dto/asset-response.dto';
 
 
 @Injectable()
 @Injectable()
 export class BackgroundTaskService {
 export class BackgroundTaskService {
@@ -11,7 +12,7 @@ export class BackgroundTaskService {
     private backgroundTaskQueue: Queue,
     private backgroundTaskQueue: Queue,
   ) {}
   ) {}
 
 
-  async deleteFileOnDisk(assets: AssetEntity[]) {
+  async deleteFileOnDisk(assets: AssetResponseDto[]) {
     await this.backgroundTaskQueue.add(
     await this.backgroundTaskQueue.add(
       'delete-file-on-disk',
       'delete-file-on-disk',
       {
       {

+ 1 - 1
server/apps/microservices/src/processors/asset-uploaded.processor.ts

@@ -63,7 +63,7 @@ export class AssetUploadedProcessor {
     }
     }
 
 
     // Extract video duration if uploaded from the web & CLI
     // Extract video duration if uploaded from the web & CLI
-    if (asset.type == AssetType.VIDEO && asset.duration == '0:00:00.000000') {
+    if (asset.type == AssetType.VIDEO) {
       await this.metadataExtractionQueue.add(videoMetadataExtractionProcessorName, { asset }, { jobId: randomUUID() });
       await this.metadataExtractionQueue.add(videoMetadataExtractionProcessorName, { asset }, { jobId: randomUUID() });
     }
     }
   }
   }

File diff suppressed because it is too large
+ 0 - 0
server/immich-openapi-specs.json


+ 9 - 2
server/nest-cli.json

@@ -5,7 +5,14 @@
   "root": "apps/immich",
   "root": "apps/immich",
   "compilerOptions": {
   "compilerOptions": {
     "webpack": false,
     "webpack": false,
-    "tsConfigPath": "apps/immich/tsconfig.app.json"
+    "tsConfigPath": "apps/immich/tsconfig.app.json",
+    "plugins": [  {
+      "name": "@nestjs/swagger",
+      "options": {
+        "classValidatorShim": false,
+        "introspectComments": true
+      }
+    }]
   },
   },
   "projects": {
   "projects": {
     "immich": {
     "immich": {
@@ -45,4 +52,4 @@
       }
       }
     }
     }
   }
   }
-}
+}

+ 7 - 0
server/openapitools.json

@@ -0,0 +1,7 @@
+{
+  "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
+  "spaces": 2,
+  "generator-cli": {
+    "version": "6.0.1"
+  }
+}

+ 588 - 6
server/package-lock.json

@@ -21,6 +21,7 @@
         "@nestjs/platform-fastify": "^8.4.7",
         "@nestjs/platform-fastify": "^8.4.7",
         "@nestjs/platform-socket.io": "^8.4.7",
         "@nestjs/platform-socket.io": "^8.4.7",
         "@nestjs/schedule": "^2.0.1",
         "@nestjs/schedule": "^2.0.1",
+        "@nestjs/swagger": "^5.2.1",
         "@nestjs/typeorm": "^8.1.4",
         "@nestjs/typeorm": "^8.1.4",
         "@nestjs/websockets": "^8.4.7",
         "@nestjs/websockets": "^8.4.7",
         "@socket.io/redis-adapter": "^7.1.0",
         "@socket.io/redis-adapter": "^7.1.0",
@@ -44,6 +45,7 @@
         "rxjs": "^7.2.0",
         "rxjs": "^7.2.0",
         "sharp": "^0.28.0",
         "sharp": "^0.28.0",
         "socket.io-redis": "^6.1.1",
         "socket.io-redis": "^6.1.1",
+        "swagger-ui-express": "^4.4.0",
         "systeminformation": "^5.11.0",
         "systeminformation": "^5.11.0",
         "typeorm": "^0.3.6"
         "typeorm": "^0.3.6"
       },
       },
@@ -51,6 +53,7 @@
         "@nestjs/cli": "^8.2.8",
         "@nestjs/cli": "^8.2.8",
         "@nestjs/schematics": "^8.0.11",
         "@nestjs/schematics": "^8.0.11",
         "@nestjs/testing": "^8.4.7",
         "@nestjs/testing": "^8.4.7",
+        "@openapitools/openapi-generator-cli": "^2.5.1",
         "@types/bcrypt": "^5.0.0",
         "@types/bcrypt": "^5.0.0",
         "@types/bull": "^3.15.7",
         "@types/bull": "^3.15.7",
         "@types/cron": "^2.0.0",
         "@types/cron": "^2.0.0",
@@ -1839,6 +1842,31 @@
       "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
       "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
       "dev": true
       "dev": true
     },
     },
+    "node_modules/@nestjs/swagger": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-5.2.1.tgz",
+      "integrity": "sha512-7dNa08WCnTsW/oAk3Ujde+z64JMfNm19DhpXasFR8oJp/9pggYAbYU927HpA+GJsSFJX6adjIRZsCKUqaGWznw==",
+      "dependencies": {
+        "@nestjs/mapped-types": "1.0.1",
+        "lodash": "4.17.21",
+        "path-to-regexp": "3.2.0"
+      },
+      "peerDependencies": {
+        "@nestjs/common": "^8.0.0",
+        "@nestjs/core": "^8.0.0",
+        "fastify-swagger": "*",
+        "reflect-metadata": "^0.1.12",
+        "swagger-ui-express": "*"
+      },
+      "peerDependenciesMeta": {
+        "fastify-swagger": {
+          "optional": true
+        },
+        "swagger-ui-express": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@nestjs/testing": {
     "node_modules/@nestjs/testing": {
       "version": "8.4.7",
       "version": "8.4.7",
       "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.4.7.tgz",
       "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.4.7.tgz",
@@ -1966,6 +1994,214 @@
         "npm": ">=5.0.0"
         "npm": ">=5.0.0"
       }
       }
     },
     },
+    "node_modules/@openapitools/openapi-generator-cli": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.5.1.tgz",
+      "integrity": "sha512-WSRQBU0dCSVD+0Qv8iCsv0C4iMaZe/NpJ/CT4SmrEYLH3txoKTE8wEfbdj/kqShS8Or0YEGDPUzhSIKY151L0w==",
+      "dev": true,
+      "hasInstallScript": true,
+      "dependencies": {
+        "@nestjs/common": "8.4.4",
+        "@nestjs/core": "8.4.4",
+        "@nuxtjs/opencollective": "0.3.2",
+        "chalk": "4.1.2",
+        "commander": "8.3.0",
+        "compare-versions": "4.1.3",
+        "concurrently": "6.5.1",
+        "console.table": "0.10.0",
+        "fs-extra": "10.0.1",
+        "glob": "7.1.6",
+        "inquirer": "8.2.2",
+        "lodash": "4.17.21",
+        "reflect-metadata": "0.1.13",
+        "rxjs": "7.5.5",
+        "tslib": "2.0.3"
+      },
+      "bin": {
+        "openapi-generator-cli": "main.js"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/openapi_generator"
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/common": {
+      "version": "8.4.4",
+      "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-8.4.4.tgz",
+      "integrity": "sha512-QHi7QcgH/5Jinz+SCfIZJkFHc6Cch1YsAEGFEhi6wSp6MILb0sJMQ1CX06e9tCOAjSlBwaJj4PH0eFCVau5v9Q==",
+      "dev": true,
+      "dependencies": {
+        "axios": "0.26.1",
+        "iterare": "1.2.1",
+        "tslib": "2.3.1",
+        "uuid": "8.3.2"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nest"
+      },
+      "peerDependencies": {
+        "cache-manager": "*",
+        "class-transformer": "*",
+        "class-validator": "*",
+        "reflect-metadata": "^0.1.12",
+        "rxjs": "^7.1.0"
+      },
+      "peerDependenciesMeta": {
+        "cache-manager": {
+          "optional": true
+        },
+        "class-transformer": {
+          "optional": true
+        },
+        "class-validator": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/common/node_modules/tslib": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+      "dev": true
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/core": {
+      "version": "8.4.4",
+      "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-8.4.4.tgz",
+      "integrity": "sha512-Ef3yJPuzAttpNfehnGqIV5kHIL9SHptB5F4ERxoU7pT61H3xiYpZw6hSjx68cJO7cc6rm7/N+b4zeuJvFHtvBg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "dependencies": {
+        "@nuxtjs/opencollective": "0.3.2",
+        "fast-safe-stringify": "2.1.1",
+        "iterare": "1.2.1",
+        "object-hash": "3.0.0",
+        "path-to-regexp": "3.2.0",
+        "tslib": "2.3.1",
+        "uuid": "8.3.2"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nest"
+      },
+      "peerDependencies": {
+        "@nestjs/common": "^8.0.0",
+        "@nestjs/microservices": "^8.0.0",
+        "@nestjs/platform-express": "^8.0.0",
+        "@nestjs/websockets": "^8.0.0",
+        "reflect-metadata": "^0.1.12",
+        "rxjs": "^7.1.0"
+      },
+      "peerDependenciesMeta": {
+        "@nestjs/microservices": {
+          "optional": true
+        },
+        "@nestjs/platform-express": {
+          "optional": true
+        },
+        "@nestjs/websockets": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/core/node_modules/tslib": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+      "dev": true
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/commander": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+      "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+      "dev": true,
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/fs-extra": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz",
+      "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==",
+      "dev": true,
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "dev": true,
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/inquirer": {
+      "version": "8.2.2",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.2.tgz",
+      "integrity": "sha512-pG7I/si6K/0X7p1qU+rfWnpTE1UIkTONN1wxtzh0d+dHXtT/JG6qBgLxoyHVsQa8cFABxAPh0pD6uUUHiAoaow==",
+      "dev": true,
+      "dependencies": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^4.1.1",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^3.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.21",
+        "mute-stream": "0.0.8",
+        "ora": "^5.4.1",
+        "run-async": "^2.4.0",
+        "rxjs": "^7.5.5",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "through": "^2.3.6"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/rxjs": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
+      "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/rxjs/node_modules/tslib": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+      "dev": true
+    },
+    "node_modules/@openapitools/openapi-generator-cli/node_modules/tslib": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+      "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==",
+      "dev": true
+    },
     "node_modules/@sideway/address": {
     "node_modules/@sideway/address": {
       "version": "4.1.3",
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz",
       "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz",
@@ -3247,9 +3483,9 @@
       }
       }
     },
     },
     "node_modules/axios": {
     "node_modules/axios": {
-      "version": "0.26.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
-      "integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
+      "version": "0.26.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+      "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
       "dependencies": {
       "dependencies": {
         "follow-redirects": "^1.14.8"
         "follow-redirects": "^1.14.8"
       }
       }
@@ -3989,6 +4225,12 @@
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
       "dev": true
     },
     },
+    "node_modules/compare-versions": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-4.1.3.tgz",
+      "integrity": "sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg==",
+      "dev": true
+    },
     "node_modules/component-emitter": {
     "node_modules/component-emitter": {
       "version": "1.3.0",
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@@ -4040,6 +4282,61 @@
         "safe-buffer": "~5.1.0"
         "safe-buffer": "~5.1.0"
       }
       }
     },
     },
+    "node_modules/concurrently": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.5.1.tgz",
+      "integrity": "sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "^4.1.0",
+        "date-fns": "^2.16.1",
+        "lodash": "^4.17.21",
+        "rxjs": "^6.6.3",
+        "spawn-command": "^0.0.2-1",
+        "supports-color": "^8.1.0",
+        "tree-kill": "^1.2.2",
+        "yargs": "^16.2.0"
+      },
+      "bin": {
+        "concurrently": "bin/concurrently.js"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/concurrently/node_modules/rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^1.9.0"
+      },
+      "engines": {
+        "npm": ">=2.0.0"
+      }
+    },
+    "node_modules/concurrently/node_modules/supports-color": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/concurrently/node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
     "node_modules/consola": {
     "node_modules/consola": {
       "version": "2.15.3",
       "version": "2.15.3",
       "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
       "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
@@ -4050,6 +4347,18 @@
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
     },
     },
+    "node_modules/console.table": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz",
+      "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==",
+      "dev": true,
+      "dependencies": {
+        "easy-table": "1.1.0"
+      },
+      "engines": {
+        "node": "> 0.10"
+      }
+    },
     "node_modules/content-disposition": {
     "node_modules/content-disposition": {
       "version": "0.5.4",
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -4521,6 +4830,15 @@
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
       "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
       "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
     },
     },
+    "node_modules/easy-table": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz",
+      "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==",
+      "dev": true,
+      "optionalDependencies": {
+        "wcwidth": ">=1.0.1"
+      }
+    },
     "node_modules/ecdsa-sig-formatter": {
     "node_modules/ecdsa-sig-formatter": {
       "version": "1.0.11",
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
       "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -9737,6 +10055,12 @@
       "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
       "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
       "dev": true
       "dev": true
     },
     },
+    "node_modules/spawn-command": {
+      "version": "0.0.2-1",
+      "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
+      "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==",
+      "dev": true
+    },
     "node_modules/spdx-correct": {
     "node_modules/spdx-correct": {
       "version": "3.1.1",
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -9996,6 +10320,25 @@
         "url": "https://github.com/sponsors/ljharb"
         "url": "https://github.com/sponsors/ljharb"
       }
       }
     },
     },
+    "node_modules/swagger-ui-dist": {
+      "version": "4.12.0",
+      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.12.0.tgz",
+      "integrity": "sha512-B0Iy2ueXtbByE6OOyHTi3lFQkpPi/L7kFOKFeKTr44za7dJIELa9kzaca6GkndCgpK1QTjArnoXG+aUy0XQp1w=="
+    },
+    "node_modules/swagger-ui-express": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.4.0.tgz",
+      "integrity": "sha512-1CzRkHG386VQMVZK406jcpgnW2a9A5A/NiAjKhsFTQqUBWRF+uGbXTU/mA7WSV3mTzyOQDvjBdWP/c2qd5lqKw==",
+      "dependencies": {
+        "swagger-ui-dist": ">=4.11.0"
+      },
+      "engines": {
+        "node": ">= v0.10.32"
+      },
+      "peerDependencies": {
+        "express": ">=4.0.0"
+      }
+    },
     "node_modules/symbol-observable": {
     "node_modules/symbol-observable": {
       "version": "4.0.0",
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
       "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@@ -12510,6 +12853,16 @@
         }
         }
       }
       }
     },
     },
+    "@nestjs/swagger": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-5.2.1.tgz",
+      "integrity": "sha512-7dNa08WCnTsW/oAk3Ujde+z64JMfNm19DhpXasFR8oJp/9pggYAbYU927HpA+GJsSFJX6adjIRZsCKUqaGWznw==",
+      "requires": {
+        "@nestjs/mapped-types": "1.0.1",
+        "lodash": "4.17.21",
+        "path-to-regexp": "3.2.0"
+      }
+    },
     "@nestjs/testing": {
     "@nestjs/testing": {
       "version": "8.4.7",
       "version": "8.4.7",
       "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.4.7.tgz",
       "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-8.4.7.tgz",
@@ -12588,6 +12941,150 @@
         "node-fetch": "^2.6.1"
         "node-fetch": "^2.6.1"
       }
       }
     },
     },
+    "@openapitools/openapi-generator-cli": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.5.1.tgz",
+      "integrity": "sha512-WSRQBU0dCSVD+0Qv8iCsv0C4iMaZe/NpJ/CT4SmrEYLH3txoKTE8wEfbdj/kqShS8Or0YEGDPUzhSIKY151L0w==",
+      "dev": true,
+      "requires": {
+        "@nestjs/common": "8.4.4",
+        "@nestjs/core": "8.4.4",
+        "@nuxtjs/opencollective": "0.3.2",
+        "chalk": "4.1.2",
+        "commander": "8.3.0",
+        "compare-versions": "4.1.3",
+        "concurrently": "6.5.1",
+        "console.table": "0.10.0",
+        "fs-extra": "10.0.1",
+        "glob": "7.1.6",
+        "inquirer": "8.2.2",
+        "lodash": "4.17.21",
+        "reflect-metadata": "0.1.13",
+        "rxjs": "7.5.5",
+        "tslib": "2.0.3"
+      },
+      "dependencies": {
+        "@nestjs/common": {
+          "version": "8.4.4",
+          "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-8.4.4.tgz",
+          "integrity": "sha512-QHi7QcgH/5Jinz+SCfIZJkFHc6Cch1YsAEGFEhi6wSp6MILb0sJMQ1CX06e9tCOAjSlBwaJj4PH0eFCVau5v9Q==",
+          "dev": true,
+          "requires": {
+            "axios": "0.26.1",
+            "iterare": "1.2.1",
+            "tslib": "2.3.1",
+            "uuid": "8.3.2"
+          },
+          "dependencies": {
+            "tslib": {
+              "version": "2.3.1",
+              "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+              "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+              "dev": true
+            }
+          }
+        },
+        "@nestjs/core": {
+          "version": "8.4.4",
+          "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-8.4.4.tgz",
+          "integrity": "sha512-Ef3yJPuzAttpNfehnGqIV5kHIL9SHptB5F4ERxoU7pT61H3xiYpZw6hSjx68cJO7cc6rm7/N+b4zeuJvFHtvBg==",
+          "dev": true,
+          "requires": {
+            "@nuxtjs/opencollective": "0.3.2",
+            "fast-safe-stringify": "2.1.1",
+            "iterare": "1.2.1",
+            "object-hash": "3.0.0",
+            "path-to-regexp": "3.2.0",
+            "tslib": "2.3.1",
+            "uuid": "8.3.2"
+          },
+          "dependencies": {
+            "tslib": {
+              "version": "2.3.1",
+              "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+              "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+              "dev": true
+            }
+          }
+        },
+        "commander": {
+          "version": "8.3.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+          "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+          "dev": true
+        },
+        "fs-extra": {
+          "version": "10.0.1",
+          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz",
+          "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.2.0",
+            "jsonfile": "^6.0.1",
+            "universalify": "^2.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+          "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "inquirer": {
+          "version": "8.2.2",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.2.tgz",
+          "integrity": "sha512-pG7I/si6K/0X7p1qU+rfWnpTE1UIkTONN1wxtzh0d+dHXtT/JG6qBgLxoyHVsQa8cFABxAPh0pD6uUUHiAoaow==",
+          "dev": true,
+          "requires": {
+            "ansi-escapes": "^4.2.1",
+            "chalk": "^4.1.1",
+            "cli-cursor": "^3.1.0",
+            "cli-width": "^3.0.0",
+            "external-editor": "^3.0.3",
+            "figures": "^3.0.0",
+            "lodash": "^4.17.21",
+            "mute-stream": "0.0.8",
+            "ora": "^5.4.1",
+            "run-async": "^2.4.0",
+            "rxjs": "^7.5.5",
+            "string-width": "^4.1.0",
+            "strip-ansi": "^6.0.0",
+            "through": "^2.3.6"
+          }
+        },
+        "rxjs": {
+          "version": "7.5.5",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
+          "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^2.1.0"
+          },
+          "dependencies": {
+            "tslib": {
+              "version": "2.4.0",
+              "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+              "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+              "dev": true
+            }
+          }
+        },
+        "tslib": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+          "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==",
+          "dev": true
+        }
+      }
+    },
     "@sideway/address": {
     "@sideway/address": {
       "version": "4.1.3",
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz",
       "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz",
@@ -13697,9 +14194,9 @@
       }
       }
     },
     },
     "axios": {
     "axios": {
-      "version": "0.26.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
-      "integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
+      "version": "0.26.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+      "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
       "requires": {
       "requires": {
         "follow-redirects": "^1.14.8"
         "follow-redirects": "^1.14.8"
       }
       }
@@ -14259,6 +14756,12 @@
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
       "dev": true
     },
     },
+    "compare-versions": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-4.1.3.tgz",
+      "integrity": "sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg==",
+      "dev": true
+    },
     "component-emitter": {
     "component-emitter": {
       "version": "1.3.0",
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@@ -14309,6 +14812,48 @@
         }
         }
       }
       }
     },
     },
+    "concurrently": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.5.1.tgz",
+      "integrity": "sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag==",
+      "dev": true,
+      "requires": {
+        "chalk": "^4.1.0",
+        "date-fns": "^2.16.1",
+        "lodash": "^4.17.21",
+        "rxjs": "^6.6.3",
+        "spawn-command": "^0.0.2-1",
+        "supports-color": "^8.1.0",
+        "tree-kill": "^1.2.2",
+        "yargs": "^16.2.0"
+      },
+      "dependencies": {
+        "rxjs": {
+          "version": "6.6.7",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+          "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        },
+        "supports-color": {
+          "version": "8.1.1",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+          "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
+        "tslib": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+          "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+          "dev": true
+        }
+      }
+    },
     "consola": {
     "consola": {
       "version": "2.15.3",
       "version": "2.15.3",
       "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
       "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
@@ -14319,6 +14864,15 @@
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
     },
     },
+    "console.table": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz",
+      "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==",
+      "dev": true,
+      "requires": {
+        "easy-table": "1.1.0"
+      }
+    },
     "content-disposition": {
     "content-disposition": {
       "version": "0.5.4",
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -14683,6 +15237,15 @@
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
       "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
       "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
     },
     },
+    "easy-table": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz",
+      "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==",
+      "dev": true,
+      "requires": {
+        "wcwidth": ">=1.0.1"
+      }
+    },
     "ecdsa-sig-formatter": {
     "ecdsa-sig-formatter": {
       "version": "1.0.11",
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
       "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -18655,6 +19218,12 @@
       "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
       "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
       "dev": true
       "dev": true
     },
     },
+    "spawn-command": {
+      "version": "0.0.2-1",
+      "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
+      "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==",
+      "dev": true
+    },
     "spdx-correct": {
     "spdx-correct": {
       "version": "3.1.1",
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -18858,6 +19427,19 @@
       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
     },
     },
+    "swagger-ui-dist": {
+      "version": "4.12.0",
+      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.12.0.tgz",
+      "integrity": "sha512-B0Iy2ueXtbByE6OOyHTi3lFQkpPi/L7kFOKFeKTr44za7dJIELa9kzaca6GkndCgpK1QTjArnoXG+aUy0XQp1w=="
+    },
+    "swagger-ui-express": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.4.0.tgz",
+      "integrity": "sha512-1CzRkHG386VQMVZK406jcpgnW2a9A5A/NiAjKhsFTQqUBWRF+uGbXTU/mA7WSV3mTzyOQDvjBdWP/c2qd5lqKw==",
+      "requires": {
+        "swagger-ui-dist": ">=4.11.0"
+      }
+    },
     "symbol-observable": {
     "symbol-observable": {
       "version": "4.0.0",
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
       "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",

+ 6 - 2
server/package.json

@@ -22,7 +22,8 @@
     "test:cov": "jest --coverage",
     "test:cov": "jest --coverage",
     "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
     "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
     "test:e2e": "jest --config ./apps/immich/test/jest-e2e.json",
     "test:e2e": "jest --config ./apps/immich/test/jest-e2e.json",
-    "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js"
+    "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
+    "api:generate-typescript": "rm -rf ../web/src/lib/open-api && npx openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/lib/open-api"
   },
   },
   "dependencies": {
   "dependencies": {
     "@mapbox/mapbox-sdk": "^0.13.3",
     "@mapbox/mapbox-sdk": "^0.13.3",
@@ -37,6 +38,7 @@
     "@nestjs/platform-fastify": "^8.4.7",
     "@nestjs/platform-fastify": "^8.4.7",
     "@nestjs/platform-socket.io": "^8.4.7",
     "@nestjs/platform-socket.io": "^8.4.7",
     "@nestjs/schedule": "^2.0.1",
     "@nestjs/schedule": "^2.0.1",
+    "@nestjs/swagger": "^5.2.1",
     "@nestjs/typeorm": "^8.1.4",
     "@nestjs/typeorm": "^8.1.4",
     "@nestjs/websockets": "^8.4.7",
     "@nestjs/websockets": "^8.4.7",
     "@socket.io/redis-adapter": "^7.1.0",
     "@socket.io/redis-adapter": "^7.1.0",
@@ -60,6 +62,7 @@
     "rxjs": "^7.2.0",
     "rxjs": "^7.2.0",
     "sharp": "^0.28.0",
     "sharp": "^0.28.0",
     "socket.io-redis": "^6.1.1",
     "socket.io-redis": "^6.1.1",
+    "swagger-ui-express": "^4.4.0",
     "systeminformation": "^5.11.0",
     "systeminformation": "^5.11.0",
     "typeorm": "^0.3.6"
     "typeorm": "^0.3.6"
   },
   },
@@ -83,6 +86,7 @@
     "@types/supertest": "^2.0.11",
     "@types/supertest": "^2.0.11",
     "@typescript-eslint/eslint-plugin": "^5.0.0",
     "@typescript-eslint/eslint-plugin": "^5.0.0",
     "@typescript-eslint/parser": "^5.0.0",
     "@typescript-eslint/parser": "^5.0.0",
+    "@openapitools/openapi-generator-cli": "2.5.1",
     "eslint": "^8.0.1",
     "eslint": "^8.0.1",
     "eslint-config-prettier": "^8.3.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-plugin-prettier": "^4.0.0",
     "eslint-plugin-prettier": "^4.0.0",
@@ -124,4 +128,4 @@
       "^@app/job(|/.*)$": "<rootDir>/libs/job/src/$1"
       "^@app/job(|/.*)$": "<rootDir>/libs/job/src/$1"
     }
     }
   }
   }
-}
+}

+ 4 - 4
web/.eslintrc.cjs

@@ -6,15 +6,15 @@ module.exports = {
 	ignorePatterns: ['*.cjs'],
 	ignorePatterns: ['*.cjs'],
 	overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
 	overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
 	settings: {
 	settings: {
-		'svelte3/typescript': () => require('typescript')
+		'svelte3/typescript': () => require('typescript'),
 	},
 	},
 	parserOptions: {
 	parserOptions: {
 		sourceType: 'module',
 		sourceType: 'module',
-		ecmaVersion: 2020
+		ecmaVersion: 2020,
 	},
 	},
 	env: {
 	env: {
 		browser: true,
 		browser: true,
 		es2017: true,
 		es2017: true,
-		node: true
-	}
+		node: true,
+	},
 };
 };

+ 34 - 0
web/src/lib/immich-api/index.ts

@@ -0,0 +1,34 @@
+import {
+	AlbumApi,
+	AssetApi,
+	AuthenticationApi,
+	Configuration,
+	DeviceInfoApi,
+	ServerInfoApi,
+	UserApi,
+} from '../open-api';
+
+class ImmichApi {
+	public userApi: UserApi;
+	public albumApi: AlbumApi;
+	public assetApi: AssetApi;
+	public authenticationApi: AuthenticationApi;
+	public deviceInfoApi: DeviceInfoApi;
+	public serverInfoApi: ServerInfoApi;
+	private config = new Configuration();
+
+	constructor() {
+		this.userApi = new UserApi(this.config);
+		this.albumApi = new AlbumApi(this.config);
+		this.assetApi = new AssetApi(this.config);
+		this.authenticationApi = new AuthenticationApi(this.config);
+		this.deviceInfoApi = new DeviceInfoApi(this.config);
+		this.serverInfoApi = new ServerInfoApi(this.config);
+	}
+
+	public setAccessToken(accessToken: string) {
+		this.config.accessToken = accessToken;
+	}
+}
+
+export const immichApi = new ImmichApi();

+ 4 - 0
web/src/lib/open-api/.gitignore

@@ -0,0 +1,4 @@
+wwwroot/*.js
+node_modules
+typings
+dist

+ 1 - 0
web/src/lib/open-api/.npmignore

@@ -0,0 +1 @@
+# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm

+ 23 - 0
web/src/lib/open-api/.openapi-generator-ignore

@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md

+ 9 - 0
web/src/lib/open-api/.openapi-generator/FILES

@@ -0,0 +1,9 @@
+.gitignore
+.npmignore
+.openapi-generator-ignore
+api.ts
+base.ts
+common.ts
+configuration.ts
+git_push.sh
+index.ts

+ 1 - 0
web/src/lib/open-api/.openapi-generator/VERSION

@@ -0,0 +1 @@
+6.0.1

+ 3874 - 0
web/src/lib/open-api/api.ts

@@ -0,0 +1,3874 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Immich
+ * Immich API
+ *
+ * The version of the OpenAPI document: 1.17.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from './configuration';
+import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
+// Some imports not used depending on template conditions
+// @ts-ignore
+import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
+// @ts-ignore
+import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';
+
+/**
+ * 
+ * @export
+ * @interface AddAssetsDto
+ */
+export interface AddAssetsDto {
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof AddAssetsDto
+     */
+    'assetIds': Array<string>;
+}
+/**
+ * 
+ * @export
+ * @interface AddUsersDto
+ */
+export interface AddUsersDto {
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof AddUsersDto
+     */
+    'sharedUserIds': Array<string>;
+}
+/**
+ * 
+ * @export
+ * @interface AdminSignupResponseDto
+ */
+export interface AdminSignupResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof AdminSignupResponseDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AdminSignupResponseDto
+     */
+    'email': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AdminSignupResponseDto
+     */
+    'firstName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AdminSignupResponseDto
+     */
+    'lastName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AdminSignupResponseDto
+     */
+    'createdAt': string;
+}
+/**
+ * 
+ * @export
+ * @interface AlbumResponseDto
+ */
+export interface AlbumResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof AlbumResponseDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AlbumResponseDto
+     */
+    'ownerId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AlbumResponseDto
+     */
+    'albumName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AlbumResponseDto
+     */
+    'createdAt': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AlbumResponseDto
+     */
+    'albumThumbnailAssetId': string | null;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof AlbumResponseDto
+     */
+    'shared': boolean;
+    /**
+     * 
+     * @type {Array<UserResponseDto>}
+     * @memberof AlbumResponseDto
+     */
+    'sharedUsers': Array<UserResponseDto>;
+    /**
+     * 
+     * @type {Array<AssetResponseDto>}
+     * @memberof AlbumResponseDto
+     */
+    'assets': Array<AssetResponseDto>;
+}
+/**
+ * 
+ * @export
+ * @interface AssetResponseDto
+ */
+export interface AssetResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'deviceAssetId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'ownerId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'deviceId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'type': AssetResponseDtoTypeEnum;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'originalPath': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'resizePath': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'createdAt': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'modifiedAt': string;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof AssetResponseDto
+     */
+    'isFavorite': boolean;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'mimeType': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'duration': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'webpPath': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof AssetResponseDto
+     */
+    'encodedVideoPath': string | null;
+    /**
+     * 
+     * @type {ExifResponseDto}
+     * @memberof AssetResponseDto
+     */
+    'exifInfo'?: ExifResponseDto;
+    /**
+     * 
+     * @type {SmartInfoResponseDto}
+     * @memberof AssetResponseDto
+     */
+    'smartInfo'?: SmartInfoResponseDto;
+}
+
+export const AssetResponseDtoTypeEnum = {
+    Image: 'IMAGE',
+    Video: 'VIDEO',
+    Audio: 'AUDIO',
+    Other: 'OTHER'
+} as const;
+
+export type AssetResponseDtoTypeEnum = typeof AssetResponseDtoTypeEnum[keyof typeof AssetResponseDtoTypeEnum];
+
+/**
+ * 
+ * @export
+ * @interface CheckDuplicateAssetDto
+ */
+export interface CheckDuplicateAssetDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CheckDuplicateAssetDto
+     */
+    'deviceAssetId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CheckDuplicateAssetDto
+     */
+    'deviceId': string;
+}
+/**
+ * 
+ * @export
+ * @interface CreateAlbumDto
+ */
+export interface CreateAlbumDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAlbumDto
+     */
+    'albumName': string;
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof CreateAlbumDto
+     */
+    'sharedWithUserIds'?: Array<string>;
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof CreateAlbumDto
+     */
+    'assetIds'?: Array<string>;
+}
+/**
+ * 
+ * @export
+ * @interface CreateAssetDto
+ */
+export interface CreateAssetDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAssetDto
+     */
+    'deviceAssetId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAssetDto
+     */
+    'deviceId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAssetDto
+     */
+    'assetType': CreateAssetDtoAssetTypeEnum;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAssetDto
+     */
+    'createdAt': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAssetDto
+     */
+    'modifiedAt': string;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof CreateAssetDto
+     */
+    'isFavorite': boolean;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAssetDto
+     */
+    'fileExtension': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateAssetDto
+     */
+    'duration'?: string;
+}
+
+export const CreateAssetDtoAssetTypeEnum = {
+    Image: 'IMAGE',
+    Video: 'VIDEO',
+    Audio: 'AUDIO',
+    Other: 'OTHER'
+} as const;
+
+export type CreateAssetDtoAssetTypeEnum = typeof CreateAssetDtoAssetTypeEnum[keyof typeof CreateAssetDtoAssetTypeEnum];
+
+/**
+ * 
+ * @export
+ * @interface CreateDeviceInfoDto
+ */
+export interface CreateDeviceInfoDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateDeviceInfoDto
+     */
+    'deviceId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateDeviceInfoDto
+     */
+    'deviceType': CreateDeviceInfoDtoDeviceTypeEnum;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof CreateDeviceInfoDto
+     */
+    'isAutoBackup'?: boolean;
+}
+
+export const CreateDeviceInfoDtoDeviceTypeEnum = {
+    Ios: 'IOS',
+    Android: 'ANDROID',
+    Web: 'WEB'
+} as const;
+
+export type CreateDeviceInfoDtoDeviceTypeEnum = typeof CreateDeviceInfoDtoDeviceTypeEnum[keyof typeof CreateDeviceInfoDtoDeviceTypeEnum];
+
+/**
+ * 
+ * @export
+ * @interface CreateProfileImageResponseDto
+ */
+export interface CreateProfileImageResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateProfileImageResponseDto
+     */
+    'userId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateProfileImageResponseDto
+     */
+    'profileImagePath': string;
+}
+/**
+ * 
+ * @export
+ * @interface CreateUserDto
+ */
+export interface CreateUserDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateUserDto
+     */
+    'email': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateUserDto
+     */
+    'password': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateUserDto
+     */
+    'firstName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CreateUserDto
+     */
+    'lastName': string;
+}
+/**
+ * 
+ * @export
+ * @interface CuratedLocationsResponseDto
+ */
+export interface CuratedLocationsResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedLocationsResponseDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedLocationsResponseDto
+     */
+    'city': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedLocationsResponseDto
+     */
+    'resizePath': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedLocationsResponseDto
+     */
+    'deviceAssetId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedLocationsResponseDto
+     */
+    'deviceId': string;
+}
+/**
+ * 
+ * @export
+ * @interface CuratedObjectsResponseDto
+ */
+export interface CuratedObjectsResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedObjectsResponseDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedObjectsResponseDto
+     */
+    'object': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedObjectsResponseDto
+     */
+    'resizePath': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedObjectsResponseDto
+     */
+    'deviceAssetId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof CuratedObjectsResponseDto
+     */
+    'deviceId': string;
+}
+/**
+ * 
+ * @export
+ * @interface DeleteAssetDto
+ */
+export interface DeleteAssetDto {
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof DeleteAssetDto
+     */
+    'ids': Array<string>;
+}
+/**
+ * 
+ * @export
+ * @interface DeviceInfoResponseDto
+ */
+export interface DeviceInfoResponseDto {
+    /**
+     * 
+     * @type {number}
+     * @memberof DeviceInfoResponseDto
+     */
+    'id': number;
+    /**
+     * 
+     * @type {string}
+     * @memberof DeviceInfoResponseDto
+     */
+    'userId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof DeviceInfoResponseDto
+     */
+    'deviceId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof DeviceInfoResponseDto
+     */
+    'deviceType': DeviceInfoResponseDtoDeviceTypeEnum;
+    /**
+     * 
+     * @type {string}
+     * @memberof DeviceInfoResponseDto
+     */
+    'notificationToken': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof DeviceInfoResponseDto
+     */
+    'createdAt': string;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof DeviceInfoResponseDto
+     */
+    'isAutoBackup': boolean;
+}
+
+export const DeviceInfoResponseDtoDeviceTypeEnum = {
+    Ios: 'IOS',
+    Android: 'ANDROID',
+    Web: 'WEB'
+} as const;
+
+export type DeviceInfoResponseDtoDeviceTypeEnum = typeof DeviceInfoResponseDtoDeviceTypeEnum[keyof typeof DeviceInfoResponseDtoDeviceTypeEnum];
+
+/**
+ * 
+ * @export
+ * @interface ExifResponseDto
+ */
+export interface ExifResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'make': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'model': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'imageName': string | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'exifImageWidth': number | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'exifImageHeight': number | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'fileSizeInByte': number | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'orientation': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'dateTimeOriginal': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'modifyDate': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'lensModel': string | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'fNumber': number | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'focalLength': number | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'iso': number | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'exposureTime': number | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'latitude': number | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof ExifResponseDto
+     */
+    'longitude': number | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'city': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'state': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof ExifResponseDto
+     */
+    'country': string | null;
+}
+/**
+ * 
+ * @export
+ * @interface LoginCredentialDto
+ */
+export interface LoginCredentialDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginCredentialDto
+     */
+    'email': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginCredentialDto
+     */
+    'password': string;
+}
+/**
+ * 
+ * @export
+ * @interface LoginResponseDto
+ */
+export interface LoginResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginResponseDto
+     */
+    'accessToken': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginResponseDto
+     */
+    'userId': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginResponseDto
+     */
+    'userEmail': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginResponseDto
+     */
+    'firstName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginResponseDto
+     */
+    'lastName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof LoginResponseDto
+     */
+    'profileImagePath': string;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof LoginResponseDto
+     */
+    'isAdmin': boolean;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof LoginResponseDto
+     */
+    'shouldChangePassword': boolean;
+}
+/**
+ * 
+ * @export
+ * @interface RemoveAssetsDto
+ */
+export interface RemoveAssetsDto {
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof RemoveAssetsDto
+     */
+    'assetIds': Array<string>;
+}
+/**
+ * 
+ * @export
+ * @interface SearchAssetDto
+ */
+export interface SearchAssetDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof SearchAssetDto
+     */
+    'searchTerm': string;
+}
+/**
+ * 
+ * @export
+ * @interface ServerInfoResponseDto
+ */
+export interface ServerInfoResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskSize': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskUse': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskAvailable': string;
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskSizeRaw': number;
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskUseRaw': number;
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskAvailableRaw': number;
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskUsagePercentage': number;
+}
+/**
+ * 
+ * @export
+ * @interface ServerPingResponse
+ */
+export interface ServerPingResponse {
+    /**
+     * 
+     * @type {string}
+     * @memberof ServerPingResponse
+     */
+    'res': string;
+}
+/**
+ * 
+ * @export
+ * @interface ServerVersionReponseDto
+ */
+export interface ServerVersionReponseDto {
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerVersionReponseDto
+     */
+    'major': number;
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerVersionReponseDto
+     */
+    'minor': number;
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerVersionReponseDto
+     */
+    'patch': number;
+    /**
+     * 
+     * @type {number}
+     * @memberof ServerVersionReponseDto
+     */
+    'build': number;
+}
+/**
+ * 
+ * @export
+ * @interface SignUpDto
+ */
+export interface SignUpDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof SignUpDto
+     */
+    'email': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof SignUpDto
+     */
+    'password': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof SignUpDto
+     */
+    'firstName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof SignUpDto
+     */
+    'lastName': string;
+}
+/**
+ * 
+ * @export
+ * @interface SmartInfoResponseDto
+ */
+export interface SmartInfoResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof SmartInfoResponseDto
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof SmartInfoResponseDto
+     */
+    'tags'?: Array<string> | null;
+    /**
+     * 
+     * @type {Array<string>}
+     * @memberof SmartInfoResponseDto
+     */
+    'objects'?: Array<string> | null;
+}
+/**
+ * 
+ * @export
+ * @interface UpdateAlbumDto
+ */
+export interface UpdateAlbumDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateAlbumDto
+     */
+    'albumName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateAlbumDto
+     */
+    'ownerId': string;
+}
+/**
+ * 
+ * @export
+ * @interface UpdateUserDto
+ */
+export interface UpdateUserDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateUserDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateUserDto
+     */
+    'password'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateUserDto
+     */
+    'firstName'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateUserDto
+     */
+    'lastName'?: string;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof UpdateUserDto
+     */
+    'isAdmin'?: boolean;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof UpdateUserDto
+     */
+    'shouldChangePassword'?: boolean;
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateUserDto
+     */
+    'profileImagePath'?: string;
+}
+/**
+ * 
+ * @export
+ * @interface UserCountResponseDto
+ */
+export interface UserCountResponseDto {
+    /**
+     * 
+     * @type {number}
+     * @memberof UserCountResponseDto
+     */
+    'userCount': number;
+}
+/**
+ * 
+ * @export
+ * @interface UserResponseDto
+ */
+export interface UserResponseDto {
+    /**
+     * 
+     * @type {string}
+     * @memberof UserResponseDto
+     */
+    'id': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UserResponseDto
+     */
+    'email': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UserResponseDto
+     */
+    'firstName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UserResponseDto
+     */
+    'lastName': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UserResponseDto
+     */
+    'createdAt': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof UserResponseDto
+     */
+    'profileImagePath': string;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof UserResponseDto
+     */
+    'shouldChangePassword': boolean;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof UserResponseDto
+     */
+    'isAdmin': boolean;
+}
+
+/**
+ * AlbumApi - axios parameter creator
+ * @export
+ */
+export const AlbumApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {AddAssetsDto} addAssetsDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        addAssetsToAlbum: async (albumId: string, addAssetsDto: AddAssetsDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'albumId' is not null or undefined
+            assertParamExists('addAssetsToAlbum', 'albumId', albumId)
+            // verify required parameter 'addAssetsDto' is not null or undefined
+            assertParamExists('addAssetsToAlbum', 'addAssetsDto', addAssetsDto)
+            const localVarPath = `/album/{albumId}/assets`
+                .replace(`{${"albumId"}}`, encodeURIComponent(String(albumId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(addAssetsDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {AddUsersDto} addUsersDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        addUsersToAlbum: async (albumId: string, addUsersDto: AddUsersDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'albumId' is not null or undefined
+            assertParamExists('addUsersToAlbum', 'albumId', albumId)
+            // verify required parameter 'addUsersDto' is not null or undefined
+            assertParamExists('addUsersToAlbum', 'addUsersDto', addUsersDto)
+            const localVarPath = `/album/{albumId}/users`
+                .replace(`{${"albumId"}}`, encodeURIComponent(String(albumId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(addUsersDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {CreateAlbumDto} createAlbumDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createAlbum: async (createAlbumDto: CreateAlbumDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'createAlbumDto' is not null or undefined
+            assertParamExists('createAlbum', 'createAlbumDto', createAlbumDto)
+            const localVarPath = `/album`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(createAlbumDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        deleteAlbum: async (albumId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'albumId' is not null or undefined
+            assertParamExists('deleteAlbum', 'albumId', albumId)
+            const localVarPath = `/album/{albumId}`
+                .replace(`{${"albumId"}}`, encodeURIComponent(String(albumId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAlbumInfo: async (albumId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'albumId' is not null or undefined
+            assertParamExists('getAlbumInfo', 'albumId', albumId)
+            const localVarPath = `/album/{albumId}`
+                .replace(`{${"albumId"}}`, encodeURIComponent(String(albumId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {boolean} [shared] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAllAlbums: async (shared?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/album`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+            if (shared !== undefined) {
+                localVarQueryParameter['shared'] = shared;
+            }
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {RemoveAssetsDto} removeAssetsDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        removeAssetFromAlbum: async (albumId: string, removeAssetsDto: RemoveAssetsDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'albumId' is not null or undefined
+            assertParamExists('removeAssetFromAlbum', 'albumId', albumId)
+            // verify required parameter 'removeAssetsDto' is not null or undefined
+            assertParamExists('removeAssetFromAlbum', 'removeAssetsDto', removeAssetsDto)
+            const localVarPath = `/album/{albumId}/assets`
+                .replace(`{${"albumId"}}`, encodeURIComponent(String(albumId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(removeAssetsDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {string} userId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        removeUserFromAlbum: async (albumId: string, userId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'albumId' is not null or undefined
+            assertParamExists('removeUserFromAlbum', 'albumId', albumId)
+            // verify required parameter 'userId' is not null or undefined
+            assertParamExists('removeUserFromAlbum', 'userId', userId)
+            const localVarPath = `/album/{albumId}/user/{userId}`
+                .replace(`{${"albumId"}}`, encodeURIComponent(String(albumId)))
+                .replace(`{${"userId"}}`, encodeURIComponent(String(userId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {UpdateAlbumDto} updateAlbumDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        updateAlbumInfo: async (albumId: string, updateAlbumDto: UpdateAlbumDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'albumId' is not null or undefined
+            assertParamExists('updateAlbumInfo', 'albumId', albumId)
+            // verify required parameter 'updateAlbumDto' is not null or undefined
+            assertParamExists('updateAlbumInfo', 'updateAlbumDto', updateAlbumDto)
+            const localVarPath = `/album/{albumId}`
+                .replace(`{${"albumId"}}`, encodeURIComponent(String(albumId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(updateAlbumDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * AlbumApi - functional programming interface
+ * @export
+ */
+export const AlbumApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = AlbumApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {AddAssetsDto} addAssetsDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async addAssetsToAlbum(albumId: string, addAssetsDto: AddAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.addAssetsToAlbum(albumId, addAssetsDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {AddUsersDto} addUsersDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async addUsersToAlbum(albumId: string, addUsersDto: AddUsersDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.addUsersToAlbum(albumId, addUsersDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {CreateAlbumDto} createAlbumDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async createAlbum(createAlbumDto: CreateAlbumDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.createAlbum(createAlbumDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async deleteAlbum(albumId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAlbum(albumId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getAlbumInfo(albumId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAlbumInfo(albumId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {boolean} [shared] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getAllAlbums(shared?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AlbumResponseDto>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAlbums(shared, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {RemoveAssetsDto} removeAssetsDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async removeAssetFromAlbum(albumId: string, removeAssetsDto: RemoveAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.removeAssetFromAlbum(albumId, removeAssetsDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {string} userId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async removeUserFromAlbum(albumId: string, userId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.removeUserFromAlbum(albumId, userId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {UpdateAlbumDto} updateAlbumDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async updateAlbumInfo(albumId: string, updateAlbumDto: UpdateAlbumDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.updateAlbumInfo(albumId, updateAlbumDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * AlbumApi - factory interface
+ * @export
+ */
+export const AlbumApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = AlbumApiFp(configuration)
+    return {
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {AddAssetsDto} addAssetsDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        addAssetsToAlbum(albumId: string, addAssetsDto: AddAssetsDto, options?: any): AxiosPromise<AlbumResponseDto> {
+            return localVarFp.addAssetsToAlbum(albumId, addAssetsDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {AddUsersDto} addUsersDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        addUsersToAlbum(albumId: string, addUsersDto: AddUsersDto, options?: any): AxiosPromise<AlbumResponseDto> {
+            return localVarFp.addUsersToAlbum(albumId, addUsersDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {CreateAlbumDto} createAlbumDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createAlbum(createAlbumDto: CreateAlbumDto, options?: any): AxiosPromise<AlbumResponseDto> {
+            return localVarFp.createAlbum(createAlbumDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        deleteAlbum(albumId: string, options?: any): AxiosPromise<void> {
+            return localVarFp.deleteAlbum(albumId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAlbumInfo(albumId: string, options?: any): AxiosPromise<AlbumResponseDto> {
+            return localVarFp.getAlbumInfo(albumId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {boolean} [shared] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAllAlbums(shared?: boolean, options?: any): AxiosPromise<Array<AlbumResponseDto>> {
+            return localVarFp.getAllAlbums(shared, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {RemoveAssetsDto} removeAssetsDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        removeAssetFromAlbum(albumId: string, removeAssetsDto: RemoveAssetsDto, options?: any): AxiosPromise<void> {
+            return localVarFp.removeAssetFromAlbum(albumId, removeAssetsDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {string} userId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        removeUserFromAlbum(albumId: string, userId: string, options?: any): AxiosPromise<void> {
+            return localVarFp.removeUserFromAlbum(albumId, userId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} albumId 
+         * @param {UpdateAlbumDto} updateAlbumDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        updateAlbumInfo(albumId: string, updateAlbumDto: UpdateAlbumDto, options?: any): AxiosPromise<AlbumResponseDto> {
+            return localVarFp.updateAlbumInfo(albumId, updateAlbumDto, options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * AlbumApi - object-oriented interface
+ * @export
+ * @class AlbumApi
+ * @extends {BaseAPI}
+ */
+export class AlbumApi extends BaseAPI {
+    /**
+     * 
+     * @param {string} albumId 
+     * @param {AddAssetsDto} addAssetsDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public addAssetsToAlbum(albumId: string, addAssetsDto: AddAssetsDto, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).addAssetsToAlbum(albumId, addAssetsDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} albumId 
+     * @param {AddUsersDto} addUsersDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public addUsersToAlbum(albumId: string, addUsersDto: AddUsersDto, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).addUsersToAlbum(albumId, addUsersDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {CreateAlbumDto} createAlbumDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public createAlbum(createAlbumDto: CreateAlbumDto, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).createAlbum(createAlbumDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} albumId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public deleteAlbum(albumId: string, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).deleteAlbum(albumId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} albumId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public getAlbumInfo(albumId: string, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).getAlbumInfo(albumId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {boolean} [shared] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public getAllAlbums(shared?: boolean, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).getAllAlbums(shared, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} albumId 
+     * @param {RemoveAssetsDto} removeAssetsDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public removeAssetFromAlbum(albumId: string, removeAssetsDto: RemoveAssetsDto, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).removeAssetFromAlbum(albumId, removeAssetsDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} albumId 
+     * @param {string} userId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public removeUserFromAlbum(albumId: string, userId: string, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).removeUserFromAlbum(albumId, userId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} albumId 
+     * @param {UpdateAlbumDto} updateAlbumDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AlbumApi
+     */
+    public updateAlbumInfo(albumId: string, updateAlbumDto: UpdateAlbumDto, options?: AxiosRequestConfig) {
+        return AlbumApiFp(this.configuration).updateAlbumInfo(albumId, updateAlbumDto, options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+
+/**
+ * AssetApi - axios parameter creator
+ * @export
+ */
+export const AssetApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * Check duplicated asset before uploading - for Web upload used
+         * @summary 
+         * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        checkDuplicateAsset: async (checkDuplicateAssetDto: CheckDuplicateAssetDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'checkDuplicateAssetDto' is not null or undefined
+            assertParamExists('checkDuplicateAsset', 'checkDuplicateAssetDto', checkDuplicateAssetDto)
+            const localVarPath = `/asset/check`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(checkDuplicateAssetDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {DeleteAssetDto} deleteAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        deleteAsset: async (deleteAssetDto: DeleteAssetDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'deleteAssetDto' is not null or undefined
+            assertParamExists('deleteAsset', 'deleteAssetDto', deleteAssetDto)
+            const localVarPath = `/asset`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(deleteAssetDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} aid 
+         * @param {string} did 
+         * @param {string} [isThumb] 
+         * @param {string} [isWeb] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        downloadFile: async (aid: string, did: string, isThumb?: string, isWeb?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'aid' is not null or undefined
+            assertParamExists('downloadFile', 'aid', aid)
+            // verify required parameter 'did' is not null or undefined
+            assertParamExists('downloadFile', 'did', did)
+            const localVarPath = `/asset/download`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+            if (aid !== undefined) {
+                localVarQueryParameter['aid'] = aid;
+            }
+
+            if (did !== undefined) {
+                localVarQueryParameter['did'] = did;
+            }
+
+            if (isThumb !== undefined) {
+                localVarQueryParameter['isThumb'] = isThumb;
+            }
+
+            if (isWeb !== undefined) {
+                localVarQueryParameter['isWeb'] = isWeb;
+            }
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Get all AssetEntity belong to the user
+         * @summary 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAllAssets: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/asset`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Get a single asset\'s information
+         * @summary 
+         * @param {string} assetId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAssetById: async (assetId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'assetId' is not null or undefined
+            assertParamExists('getAssetById', 'assetId', assetId)
+            const localVarPath = `/asset/assetById/{assetId}`
+                .replace(`{${"assetId"}}`, encodeURIComponent(String(assetId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/asset/searchTerm`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} assetId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAssetThumbnail: async (assetId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'assetId' is not null or undefined
+            assertParamExists('getAssetThumbnail', 'assetId', assetId)
+            const localVarPath = `/asset/thumbnail/{assetId}`
+                .replace(`{${"assetId"}}`, encodeURIComponent(String(assetId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getCuratedLocations: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/asset/allLocation`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getCuratedObjects: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/asset/allObjects`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Get all asset of a device that are in the database, ID only.
+         * @summary 
+         * @param {string} deviceId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getUserAssetsByDeviceId: async (deviceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'deviceId' is not null or undefined
+            assertParamExists('getUserAssetsByDeviceId', 'deviceId', deviceId)
+            const localVarPath = `/asset/{deviceId}`
+                .replace(`{${"deviceId"}}`, encodeURIComponent(String(deviceId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {SearchAssetDto} searchAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        searchAsset: async (searchAssetDto: SearchAssetDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'searchAssetDto' is not null or undefined
+            assertParamExists('searchAsset', 'searchAssetDto', searchAssetDto)
+            const localVarPath = `/asset/search`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(searchAssetDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} aid 
+         * @param {string} did 
+         * @param {string} [isThumb] 
+         * @param {string} [isWeb] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        serveFile: async (aid: string, did: string, isThumb?: string, isWeb?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'aid' is not null or undefined
+            assertParamExists('serveFile', 'aid', aid)
+            // verify required parameter 'did' is not null or undefined
+            assertParamExists('serveFile', 'did', did)
+            const localVarPath = `/asset/file`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+            if (aid !== undefined) {
+                localVarQueryParameter['aid'] = aid;
+            }
+
+            if (did !== undefined) {
+                localVarQueryParameter['did'] = did;
+            }
+
+            if (isThumb !== undefined) {
+                localVarQueryParameter['isThumb'] = isThumb;
+            }
+
+            if (isWeb !== undefined) {
+                localVarQueryParameter['isWeb'] = isWeb;
+            }
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {CreateAssetDto} createAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        uploadFile: async (createAssetDto: CreateAssetDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'createAssetDto' is not null or undefined
+            assertParamExists('uploadFile', 'createAssetDto', createAssetDto)
+            const localVarPath = `/asset/upload`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(createAssetDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * AssetApi - functional programming interface
+ * @export
+ */
+export const AssetApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = AssetApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * Check duplicated asset before uploading - for Web upload used
+         * @summary 
+         * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.checkDuplicateAsset(checkDuplicateAssetDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {DeleteAssetDto} deleteAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async deleteAsset(deleteAssetDto: DeleteAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAsset(deleteAssetDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} aid 
+         * @param {string} did 
+         * @param {string} [isThumb] 
+         * @param {string} [isWeb] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(aid, did, isThumb, isWeb, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Get all AssetEntity belong to the user
+         * @summary 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getAllAssets(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Get a single asset\'s information
+         * @summary 
+         * @param {string} assetId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getAssetById(assetId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetById(assetId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getAssetSearchTerms(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<object>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetSearchTerms(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} assetId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getAssetThumbnail(assetId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetThumbnail(assetId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getCuratedLocations(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<CuratedLocationsResponseDto>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getCuratedLocations(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getCuratedObjects(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<CuratedObjectsResponseDto>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getCuratedObjects(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Get all asset of a device that are in the database, ID only.
+         * @summary 
+         * @param {string} deviceId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getUserAssetsByDeviceId(deviceId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {SearchAssetDto} searchAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async searchAsset(searchAssetDto: SearchAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.searchAsset(searchAssetDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} aid 
+         * @param {string} did 
+         * @param {string} [isThumb] 
+         * @param {string} [isWeb] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.serveFile(aid, did, isThumb, isWeb, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {CreateAssetDto} createAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async uploadFile(createAssetDto: CreateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(createAssetDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * AssetApi - factory interface
+ * @export
+ */
+export const AssetApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = AssetApiFp(configuration)
+    return {
+        /**
+         * Check duplicated asset before uploading - for Web upload used
+         * @summary 
+         * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: any): AxiosPromise<void> {
+            return localVarFp.checkDuplicateAsset(checkDuplicateAssetDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {DeleteAssetDto} deleteAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        deleteAsset(deleteAssetDto: DeleteAssetDto, options?: any): AxiosPromise<void> {
+            return localVarFp.deleteAsset(deleteAssetDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} aid 
+         * @param {string} did 
+         * @param {string} [isThumb] 
+         * @param {string} [isWeb] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: any): AxiosPromise<void> {
+            return localVarFp.downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Get all AssetEntity belong to the user
+         * @summary 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAllAssets(options?: any): AxiosPromise<Array<AssetResponseDto>> {
+            return localVarFp.getAllAssets(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Get a single asset\'s information
+         * @summary 
+         * @param {string} assetId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAssetById(assetId: string, options?: any): AxiosPromise<AssetResponseDto> {
+            return localVarFp.getAssetById(assetId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAssetSearchTerms(options?: any): AxiosPromise<Array<object>> {
+            return localVarFp.getAssetSearchTerms(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} assetId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAssetThumbnail(assetId: string, options?: any): AxiosPromise<object> {
+            return localVarFp.getAssetThumbnail(assetId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getCuratedLocations(options?: any): AxiosPromise<Array<CuratedLocationsResponseDto>> {
+            return localVarFp.getCuratedLocations(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getCuratedObjects(options?: any): AxiosPromise<Array<CuratedObjectsResponseDto>> {
+            return localVarFp.getCuratedObjects(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Get all asset of a device that are in the database, ID only.
+         * @summary 
+         * @param {string} deviceId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getUserAssetsByDeviceId(deviceId: string, options?: any): AxiosPromise<Array<string>> {
+            return localVarFp.getUserAssetsByDeviceId(deviceId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {SearchAssetDto} searchAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        searchAsset(searchAssetDto: SearchAssetDto, options?: any): AxiosPromise<Array<AssetResponseDto>> {
+            return localVarFp.searchAsset(searchAssetDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} aid 
+         * @param {string} did 
+         * @param {string} [isThumb] 
+         * @param {string} [isWeb] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: any): AxiosPromise<void> {
+            return localVarFp.serveFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {CreateAssetDto} createAssetDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        uploadFile(createAssetDto: CreateAssetDto, options?: any): AxiosPromise<string> {
+            return localVarFp.uploadFile(createAssetDto, options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * AssetApi - object-oriented interface
+ * @export
+ * @class AssetApi
+ * @extends {BaseAPI}
+ */
+export class AssetApi extends BaseAPI {
+    /**
+     * Check duplicated asset before uploading - for Web upload used
+     * @summary 
+     * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).checkDuplicateAsset(checkDuplicateAssetDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {DeleteAssetDto} deleteAssetDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public deleteAsset(deleteAssetDto: DeleteAssetDto, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).deleteAsset(deleteAssetDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} aid 
+     * @param {string} did 
+     * @param {string} [isThumb] 
+     * @param {string} [isWeb] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Get all AssetEntity belong to the user
+     * @summary 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public getAllAssets(options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).getAllAssets(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Get a single asset\'s information
+     * @summary 
+     * @param {string} assetId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public getAssetById(assetId: string, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).getAssetById(assetId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public getAssetSearchTerms(options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).getAssetSearchTerms(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} assetId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public getAssetThumbnail(assetId: string, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).getAssetThumbnail(assetId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public getCuratedLocations(options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).getCuratedLocations(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public getCuratedObjects(options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).getCuratedObjects(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Get all asset of a device that are in the database, ID only.
+     * @summary 
+     * @param {string} deviceId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public getUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).getUserAssetsByDeviceId(deviceId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {SearchAssetDto} searchAssetDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public searchAsset(searchAssetDto: SearchAssetDto, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).searchAsset(searchAssetDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} aid 
+     * @param {string} did 
+     * @param {string} [isThumb] 
+     * @param {string} [isWeb] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).serveFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {CreateAssetDto} createAssetDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AssetApi
+     */
+    public uploadFile(createAssetDto: CreateAssetDto, options?: AxiosRequestConfig) {
+        return AssetApiFp(this.configuration).uploadFile(createAssetDto, options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+
+/**
+ * AuthenticationApi - axios parameter creator
+ * @export
+ */
+export const AuthenticationApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * 
+         * @param {SignUpDto} signUpDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        adminSignUp: async (signUpDto: SignUpDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'signUpDto' is not null or undefined
+            assertParamExists('adminSignUp', 'signUpDto', signUpDto)
+            const localVarPath = `/auth/admin-sign-up`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(signUpDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {LoginCredentialDto} loginCredentialDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        login: async (loginCredentialDto: LoginCredentialDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'loginCredentialDto' is not null or undefined
+            assertParamExists('login', 'loginCredentialDto', loginCredentialDto)
+            const localVarPath = `/auth/login`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(loginCredentialDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        validateAccessToken: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/auth/validateToken`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * AuthenticationApi - functional programming interface
+ * @export
+ */
+export const AuthenticationApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = AuthenticationApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * 
+         * @param {SignUpDto} signUpDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async adminSignUp(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AdminSignupResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.adminSignUp(signUpDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {LoginCredentialDto} loginCredentialDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async login(loginCredentialDto: LoginCredentialDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<LoginResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.login(loginCredentialDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async validateAccessToken(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.validateAccessToken(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * AuthenticationApi - factory interface
+ * @export
+ */
+export const AuthenticationApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = AuthenticationApiFp(configuration)
+    return {
+        /**
+         * 
+         * @param {SignUpDto} signUpDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        adminSignUp(signUpDto: SignUpDto, options?: any): AxiosPromise<AdminSignupResponseDto> {
+            return localVarFp.adminSignUp(signUpDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {LoginCredentialDto} loginCredentialDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        login(loginCredentialDto: LoginCredentialDto, options?: any): AxiosPromise<LoginResponseDto> {
+            return localVarFp.login(loginCredentialDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        validateAccessToken(options?: any): AxiosPromise<object> {
+            return localVarFp.validateAccessToken(options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * AuthenticationApi - object-oriented interface
+ * @export
+ * @class AuthenticationApi
+ * @extends {BaseAPI}
+ */
+export class AuthenticationApi extends BaseAPI {
+    /**
+     * 
+     * @param {SignUpDto} signUpDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AuthenticationApi
+     */
+    public adminSignUp(signUpDto: SignUpDto, options?: AxiosRequestConfig) {
+        return AuthenticationApiFp(this.configuration).adminSignUp(signUpDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {LoginCredentialDto} loginCredentialDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AuthenticationApi
+     */
+    public login(loginCredentialDto: LoginCredentialDto, options?: AxiosRequestConfig) {
+        return AuthenticationApiFp(this.configuration).login(loginCredentialDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AuthenticationApi
+     */
+    public validateAccessToken(options?: AxiosRequestConfig) {
+        return AuthenticationApiFp(this.configuration).validateAccessToken(options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+
+/**
+ * DeviceInfoApi - axios parameter creator
+ * @export
+ */
+export const DeviceInfoApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * 
+         * @param {CreateDeviceInfoDto} createDeviceInfoDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createDeviceInfo: async (createDeviceInfoDto: CreateDeviceInfoDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'createDeviceInfoDto' is not null or undefined
+            assertParamExists('createDeviceInfo', 'createDeviceInfoDto', createDeviceInfoDto)
+            const localVarPath = `/device-info`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(createDeviceInfoDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {object} body 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        updateDeviceInfo: async (body: object, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'body' is not null or undefined
+            assertParamExists('updateDeviceInfo', 'body', body)
+            const localVarPath = `/device-info`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * DeviceInfoApi - functional programming interface
+ * @export
+ */
+export const DeviceInfoApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = DeviceInfoApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * 
+         * @param {CreateDeviceInfoDto} createDeviceInfoDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async createDeviceInfo(createDeviceInfoDto: CreateDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.createDeviceInfo(createDeviceInfoDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {object} body 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async updateDeviceInfo(body: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.updateDeviceInfo(body, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * DeviceInfoApi - factory interface
+ * @export
+ */
+export const DeviceInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = DeviceInfoApiFp(configuration)
+    return {
+        /**
+         * 
+         * @param {CreateDeviceInfoDto} createDeviceInfoDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createDeviceInfo(createDeviceInfoDto: CreateDeviceInfoDto, options?: any): AxiosPromise<DeviceInfoResponseDto> {
+            return localVarFp.createDeviceInfo(createDeviceInfoDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {object} body 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        updateDeviceInfo(body: object, options?: any): AxiosPromise<DeviceInfoResponseDto> {
+            return localVarFp.updateDeviceInfo(body, options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * DeviceInfoApi - object-oriented interface
+ * @export
+ * @class DeviceInfoApi
+ * @extends {BaseAPI}
+ */
+export class DeviceInfoApi extends BaseAPI {
+    /**
+     * 
+     * @param {CreateDeviceInfoDto} createDeviceInfoDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof DeviceInfoApi
+     */
+    public createDeviceInfo(createDeviceInfoDto: CreateDeviceInfoDto, options?: AxiosRequestConfig) {
+        return DeviceInfoApiFp(this.configuration).createDeviceInfo(createDeviceInfoDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {object} body 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof DeviceInfoApi
+     */
+    public updateDeviceInfo(body: object, options?: AxiosRequestConfig) {
+        return DeviceInfoApiFp(this.configuration).updateDeviceInfo(body, options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+
+/**
+ * ServerInfoApi - axios parameter creator
+ * @export
+ */
+export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getServerInfo: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/server-info`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getServerVersion: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/server-info/version`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        pingServer: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/server-info/ping`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * ServerInfoApi - functional programming interface
+ * @export
+ */
+export const ServerInfoApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getServerInfo(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerInfoResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getServerInfo(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerVersionReponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getServerVersion(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async pingServer(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerPingResponse>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.pingServer(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * ServerInfoApi - factory interface
+ * @export
+ */
+export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = ServerInfoApiFp(configuration)
+    return {
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getServerInfo(options?: any): AxiosPromise<ServerInfoResponseDto> {
+            return localVarFp.getServerInfo(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getServerVersion(options?: any): AxiosPromise<ServerVersionReponseDto> {
+            return localVarFp.getServerVersion(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        pingServer(options?: any): AxiosPromise<ServerPingResponse> {
+            return localVarFp.pingServer(options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * ServerInfoApi - object-oriented interface
+ * @export
+ * @class ServerInfoApi
+ * @extends {BaseAPI}
+ */
+export class ServerInfoApi extends BaseAPI {
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof ServerInfoApi
+     */
+    public getServerInfo(options?: AxiosRequestConfig) {
+        return ServerInfoApiFp(this.configuration).getServerInfo(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof ServerInfoApi
+     */
+    public getServerVersion(options?: AxiosRequestConfig) {
+        return ServerInfoApiFp(this.configuration).getServerVersion(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof ServerInfoApi
+     */
+    public pingServer(options?: AxiosRequestConfig) {
+        return ServerInfoApiFp(this.configuration).pingServer(options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+
+/**
+ * UserApi - axios parameter creator
+ * @export
+ */
+export const UserApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * 
+         * @param {any} file 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createProfileImage: async (file: any, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'file' is not null or undefined
+            assertParamExists('createProfileImage', 'file', file)
+            const localVarPath = `/user/profile-image`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+            const localVarFormParams = new ((configuration && configuration.formDataCtor) || FormData)();
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+            if (file !== undefined) { 
+                localVarFormParams.append('file', file as any);
+            }
+    
+    
+            localVarHeaderParameter['Content-Type'] = 'multipart/form-data';
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = localVarFormParams;
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {CreateUserDto} createUserDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createUser: async (createUserDto: CreateUserDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'createUserDto' is not null or undefined
+            assertParamExists('createUser', 'createUserDto', createUserDto)
+            const localVarPath = `/user`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(createUserDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {boolean} isAll 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAllUsers: async (isAll: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'isAll' is not null or undefined
+            assertParamExists('getAllUsers', 'isAll', isAll)
+            const localVarPath = `/user`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+            if (isAll !== undefined) {
+                localVarQueryParameter['isAll'] = isAll;
+            }
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getMyUserInfo: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/user/me`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} userId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getProfileImage: async (userId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'userId' is not null or undefined
+            assertParamExists('getProfileImage', 'userId', userId)
+            const localVarPath = `/user/profile-image/{userId}`
+                .replace(`{${"userId"}}`, encodeURIComponent(String(userId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {boolean} isAdmin 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getUserCount: async (isAdmin: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'isAdmin' is not null or undefined
+            assertParamExists('getUserCount', 'isAdmin', isAdmin)
+            const localVarPath = `/user/count`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            if (isAdmin !== undefined) {
+                localVarQueryParameter['isAdmin'] = isAdmin;
+            }
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {UpdateUserDto} updateUserDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        updateUser: async (updateUserDto: UpdateUserDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'updateUserDto' is not null or undefined
+            assertParamExists('updateUser', 'updateUserDto', updateUserDto)
+            const localVarPath = `/user`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication bearer required
+            // http bearer authentication required
+            await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(updateUserDto, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * UserApi - functional programming interface
+ * @export
+ */
+export const UserApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = UserApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * 
+         * @param {any} file 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async createProfileImage(file: any, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<CreateProfileImageResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.createProfileImage(file, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {CreateUserDto} createUserDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async createUser(createUserDto: CreateUserDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.createUser(createUserDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {boolean} isAll 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getAllUsers(isAll: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<UserResponseDto>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getAllUsers(isAll, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getMyUserInfo(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getMyUserInfo(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {string} userId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getProfileImage(userId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getProfileImage(userId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {boolean} isAdmin 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async getUserCount(isAdmin: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserCountResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.getUserCount(isAdmin, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * 
+         * @param {UpdateUserDto} updateUserDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async updateUser(updateUserDto: UpdateUserDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.updateUser(updateUserDto, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * UserApi - factory interface
+ * @export
+ */
+export const UserApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = UserApiFp(configuration)
+    return {
+        /**
+         * 
+         * @param {any} file 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createProfileImage(file: any, options?: any): AxiosPromise<CreateProfileImageResponseDto> {
+            return localVarFp.createProfileImage(file, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {CreateUserDto} createUserDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        createUser(createUserDto: CreateUserDto, options?: any): AxiosPromise<UserResponseDto> {
+            return localVarFp.createUser(createUserDto, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {boolean} isAll 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getAllUsers(isAll: boolean, options?: any): AxiosPromise<Array<UserResponseDto>> {
+            return localVarFp.getAllUsers(isAll, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getMyUserInfo(options?: any): AxiosPromise<UserResponseDto> {
+            return localVarFp.getMyUserInfo(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {string} userId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getProfileImage(userId: string, options?: any): AxiosPromise<void> {
+            return localVarFp.getProfileImage(userId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {boolean} isAdmin 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        getUserCount(isAdmin: boolean, options?: any): AxiosPromise<UserCountResponseDto> {
+            return localVarFp.getUserCount(isAdmin, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @param {UpdateUserDto} updateUserDto 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        updateUser(updateUserDto: UpdateUserDto, options?: any): AxiosPromise<UserResponseDto> {
+            return localVarFp.updateUser(updateUserDto, options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * UserApi - object-oriented interface
+ * @export
+ * @class UserApi
+ * @extends {BaseAPI}
+ */
+export class UserApi extends BaseAPI {
+    /**
+     * 
+     * @param {any} file 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public createProfileImage(file: any, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration).createProfileImage(file, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {CreateUserDto} createUserDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public createUser(createUserDto: CreateUserDto, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration).createUser(createUserDto, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {boolean} isAll 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public getAllUsers(isAll: boolean, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration).getAllUsers(isAll, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public getMyUserInfo(options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration).getMyUserInfo(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {string} userId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public getProfileImage(userId: string, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration).getProfileImage(userId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {boolean} isAdmin 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public getUserCount(isAdmin: boolean, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration).getUserCount(isAdmin, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * 
+     * @param {UpdateUserDto} updateUserDto 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public updateUser(updateUserDto: UpdateUserDto, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration).updateUser(updateUserDto, options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+

+ 74 - 0
web/src/lib/open-api/base.ts

@@ -0,0 +1,74 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Immich
+ * Immich API
+ *
+ * The version of the OpenAPI document: 1.17.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { Configuration } from './configuration';
+// Some imports not used depending on template conditions
+// @ts-ignore
+import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
+
+export const BASE_PATH = '/api'.replace(/\/+$/, '');
+
+/**
+ *
+ * @export
+ */
+export const COLLECTION_FORMATS = {
+	csv: ',',
+	ssv: ' ',
+	tsv: '\t',
+	pipes: '|',
+};
+
+/**
+ *
+ * @export
+ * @interface RequestArgs
+ */
+export interface RequestArgs {
+	url: string;
+	options: AxiosRequestConfig;
+}
+
+/**
+ *
+ * @export
+ * @class BaseAPI
+ */
+export class BaseAPI {
+	protected configuration: Configuration | undefined;
+
+	constructor(
+		configuration?: Configuration,
+		protected basePath: string = BASE_PATH,
+		protected axios: AxiosInstance = globalAxios,
+	) {
+		if (configuration) {
+			this.configuration = configuration;
+			this.basePath = configuration.basePath || this.basePath;
+		}
+	}
+}
+
+/**
+ *
+ * @export
+ * @class RequiredError
+ * @extends {Error}
+ */
+export class RequiredError extends Error {
+	name: 'RequiredError' = 'RequiredError';
+	constructor(public field: string, msg?: string) {
+		super(msg);
+	}
+}

+ 138 - 0
web/src/lib/open-api/common.ts

@@ -0,0 +1,138 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Immich
+ * Immich API
+ *
+ * The version of the OpenAPI document: 1.17.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from "./configuration";
+import { RequiredError, RequestArgs } from "./base";
+import { AxiosInstance, AxiosResponse } from 'axios';
+
+/**
+ *
+ * @export
+ */
+export const DUMMY_BASE_URL = 'https://example.com'
+
+/**
+ *
+ * @throws {RequiredError}
+ * @export
+ */
+export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
+    if (paramValue === null || paramValue === undefined) {
+        throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
+    if (configuration && configuration.apiKey) {
+        const localVarApiKeyValue = typeof configuration.apiKey === 'function'
+            ? await configuration.apiKey(keyParamName)
+            : await configuration.apiKey;
+        object[keyParamName] = localVarApiKeyValue;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
+    if (configuration && (configuration.username || configuration.password)) {
+        object["auth"] = { username: configuration.username, password: configuration.password };
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
+    if (configuration && configuration.accessToken) {
+        const accessToken = typeof configuration.accessToken === 'function'
+            ? await configuration.accessToken()
+            : await configuration.accessToken;
+        object["Authorization"] = "Bearer " + accessToken;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
+    if (configuration && configuration.accessToken) {
+        const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
+            ? await configuration.accessToken(name, scopes)
+            : await configuration.accessToken;
+        object["Authorization"] = "Bearer " + localVarAccessTokenValue;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setSearchParams = function (url: URL, ...objects: any[]) {
+    const searchParams = new URLSearchParams(url.search);
+    for (const object of objects) {
+        for (const key in object) {
+            if (Array.isArray(object[key])) {
+                searchParams.delete(key);
+                for (const item of object[key]) {
+                    searchParams.append(key, item);
+                }
+            } else {
+                searchParams.set(key, object[key]);
+            }
+        }
+    }
+    url.search = searchParams.toString();
+}
+
+/**
+ *
+ * @export
+ */
+export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
+    const nonString = typeof value !== 'string';
+    const needsSerialization = nonString && configuration && configuration.isJsonMime
+        ? configuration.isJsonMime(requestOptions.headers['Content-Type'])
+        : nonString;
+    return needsSerialization
+        ? JSON.stringify(value !== undefined ? value : {})
+        : (value || "");
+}
+
+/**
+ *
+ * @export
+ */
+export const toPathString = function (url: URL) {
+    return url.pathname + url.search + url.hash
+}
+
+/**
+ *
+ * @export
+ */
+export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
+    return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+        const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
+        return axios.request<T, R>(axiosRequestArgs);
+    };
+}

+ 101 - 0
web/src/lib/open-api/configuration.ts

@@ -0,0 +1,101 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Immich
+ * Immich API
+ *
+ * The version of the OpenAPI document: 1.17.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface ConfigurationParameters {
+    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
+    username?: string;
+    password?: string;
+    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
+    basePath?: string;
+    baseOptions?: any;
+    formDataCtor?: new () => any;
+}
+
+export class Configuration {
+    /**
+     * parameter for apiKey security
+     * @param name security name
+     * @memberof Configuration
+     */
+    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
+    /**
+     * parameter for basic security
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    username?: string;
+    /**
+     * parameter for basic security
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    password?: string;
+    /**
+     * parameter for oauth2 security
+     * @param name security name
+     * @param scopes oauth2 scope
+     * @memberof Configuration
+     */
+    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
+    /**
+     * override base path
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    basePath?: string;
+    /**
+     * base options for axios calls
+     *
+     * @type {any}
+     * @memberof Configuration
+     */
+    baseOptions?: any;
+    /**
+     * The FormData constructor that will be used to create multipart form data
+     * requests. You can inject this here so that execution environments that
+     * do not support the FormData class can still run the generated client.
+     *
+     * @type {new () => FormData}
+     */
+    formDataCtor?: new () => any;
+
+    constructor(param: ConfigurationParameters = {}) {
+        this.apiKey = param.apiKey;
+        this.username = param.username;
+        this.password = param.password;
+        this.accessToken = param.accessToken;
+        this.basePath = param.basePath;
+        this.baseOptions = param.baseOptions;
+        this.formDataCtor = param.formDataCtor;
+    }
+
+    /**
+     * Check if the given MIME is a JSON MIME.
+     * JSON MIME examples:
+     *   application/json
+     *   application/json; charset=UTF8
+     *   APPLICATION/JSON
+     *   application/vnd.company+json
+     * @param mime - MIME (Multipurpose Internet Mail Extensions)
+     * @return True if the given MIME is JSON, false otherwise.
+     */
+    public isJsonMime(mime: string): boolean {
+        const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
+        return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
+    }
+}

+ 57 - 0
web/src/lib/open-api/git_push.sh

@@ -0,0 +1,57 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+    git_host="github.com"
+    echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+    git_user_id="GIT_USER_ID"
+    echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+    git_repo_id="GIT_REPO_ID"
+    echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+    release_note="Minor update"
+    echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=$(git remote)
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+    if [ "$GIT_TOKEN" = "" ]; then
+        echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+        git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+    else
+        git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
+    fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'

+ 18 - 0
web/src/lib/open-api/index.ts

@@ -0,0 +1,18 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Immich
+ * Immich API
+ *
+ * The version of the OpenAPI document: 1.17.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export * from "./api";
+export * from "./configuration";
+

+ 8 - 9
web/src/routes/photos/index.svelte

@@ -3,7 +3,6 @@
 
 
 	import type { Load } from '@sveltejs/kit';
 	import type { Load } from '@sveltejs/kit';
 	import { getAssetsInfo } from '$lib/stores/assets';
 	import { getAssetsInfo } from '$lib/stores/assets';
-	import { checkAppVersion } from '$lib/utils/check-app-version';
 
 
 	export const load: Load = async ({ session }) => {
 	export const load: Load = async ({ session }) => {
 		if (!session.user) {
 		if (!session.user) {
@@ -25,7 +24,7 @@
 <script lang="ts">
 <script lang="ts">
 	import type { ImmichUser } from '$lib/models/immich-user';
 	import type { ImmichUser } from '$lib/models/immich-user';
 
 
-	import NavigationBar from '../../lib/components/shared/navigation-bar.svelte';
+	import NavigationBar from '$lib/components/shared/navigation-bar.svelte';
 	import SideBarButton from '$lib/components/shared/side-bar-button.svelte';
 	import SideBarButton from '$lib/components/shared/side-bar-button.svelte';
 	import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
 	import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
 
 
@@ -35,16 +34,16 @@
 	import { fly } from 'svelte/transition';
 	import { fly } from 'svelte/transition';
 	import { session } from '$app/stores';
 	import { session } from '$app/stores';
 	import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
 	import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
-	import ImmichThumbnail from '../../lib/components/asset-viewer/immich-thumbnail.svelte';
+	import ImmichThumbnail from '$lib/components/asset-viewer/immich-thumbnail.svelte';
 	import moment from 'moment';
 	import moment from 'moment';
-	import type { ImmichAsset } from '../../lib/models/immich-asset';
-	import AssetViewer from '../../lib/components/asset-viewer/asset-viewer.svelte';
-	import DownloadPanel from '../../lib/components/asset-viewer/download-panel.svelte';
-	import StatusBox from '../../lib/components/shared/status-box.svelte';
-	import { fileUploader } from '../../lib/utils/file-uploader';
-	import { openWebsocketConnection, closeWebsocketConnection } from '../../lib/stores/websocket';
+	import type { ImmichAsset } from '$lib/models/immich-asset';
+	import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
+	import StatusBox from '$lib/components/shared/status-box.svelte';
+	import { fileUploader } from '$lib/utils/file-uploader';
+	import { openWebsocketConnection, closeWebsocketConnection } from '$lib/stores/websocket';
 
 
 	export let user: ImmichUser;
 	export let user: ImmichUser;
+
 	let selectedAction: AppSideBarSelection;
 	let selectedAction: AppSideBarSelection;
 
 
 	let selectedGroupThumbnail: number | null;
 	let selectedGroupThumbnail: number | null;

+ 3 - 1
web/tsconfig.json

@@ -16,5 +16,7 @@
     "sourceMap": true,
     "sourceMap": true,
     "strict": true,
     "strict": true,
     "target": "es2020",
     "target": "es2020",
-  }
+    "importsNotUsedAsValues": "preserve",
+    "preserveValueImports": false
+  },
 }
 }

Some files were not shown because too many files changed in this diff