From 9a6dfacf9b382786dca602de8e0e81b9e873d4a1 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 10 Jul 2022 21:41:45 -0500 Subject: [PATCH] Refactor web to use OpenAPI SDK (#326) * Refactor main index page * Refactor admin page * Refactor Auth endpoint * Refactor directory to prep for monorepo * Fixed refactoring path * Resolved file path in vite * Refactor photo index page * Refactor thumbnail * Fixed test * Refactor Video Viewer component * Refactor download file * Refactor navigation bar * Refactor upload file check * Simplify Upload Asset signature * PR feedback --- .../src/api-v1/asset/asset.controller.ts | 78 ++++--- .../immich/src/api-v1/asset/asset.service.ts | 58 +++-- .../api-v1/asset/dto/asset-file-upload.dto.ts | 10 + .../src/api-v1/asset/dto/serve-file.dto.ts | 29 ++- .../asset-file-upload-response.dto.ts | 7 + .../check-duplicate-asset-response.dto.ts | 6 + .../immich/src/api-v1/user/user.controller.ts | 16 +- .../immich/src/api-v1/user/user.service.ts | 16 +- server/apps/immich/src/app.module.ts | 1 + server/apps/immich/test/user.e2e-spec.ts | 2 +- server/immich-openapi-specs.json | 2 +- server/package-lock.json | 2 +- server/package.json | 2 +- .../{lib/immich-api/index.ts => api/api.ts} | 7 +- web/src/api/index.ts | 2 + web/src/{lib => api}/open-api/.gitignore | 0 web/src/{lib => api}/open-api/.npmignore | 0 .../open-api/.openapi-generator-ignore | 0 .../open-api/.openapi-generator/FILES | 0 .../open-api/.openapi-generator/VERSION | 0 web/src/{lib => api}/open-api/api.ts | 208 +++++++----------- web/src/{lib => api}/open-api/base.ts | 47 ++-- web/src/{lib => api}/open-api/common.ts | 0 .../{lib => api}/open-api/configuration.ts | 0 web/src/{lib => api}/open-api/git_push.sh | 0 web/src/{lib => api}/open-api/index.ts | 0 web/src/hooks.ts | 23 +- .../components/admin/user-management.svelte | 6 +- .../asset-viewer/asset-viewer.svelte | 48 ++-- .../asset-viewer/detail-panel.svelte | 9 +- .../asset-viewer/immich-thumbnail.svelte | 37 ++-- .../asset-viewer/photo-viewer.svelte | 32 ++- .../asset-viewer/video-viewer.svelte | 29 ++- .../forms/change-password-form.svelte | 4 +- .../components/shared/navigation-bar.svelte | 19 +- .../lib/components/shared/status-box.svelte | 2 +- web/src/lib/stores/assets.ts | 9 +- web/src/lib/{api.ts => utils/api-helper.ts} | 2 +- web/src/lib/utils/file-uploader.ts | 19 +- web/src/routes/__layout.svelte | 33 +-- web/src/routes/admin/api/create-user.ts | 68 +++--- web/src/routes/admin/index.svelte | 20 +- .../routes/auth/change-password/index.svelte | 20 +- web/src/routes/auth/change-password/index.ts | 31 ++- web/src/routes/auth/login/api/get-users.ts | 11 - web/src/routes/auth/login/api/select-admin.ts | 52 ----- web/src/routes/auth/login/index.ts | 36 +-- web/src/routes/auth/login/update.ts | 63 ------ web/src/routes/auth/logout.ts | 2 +- web/src/routes/auth/register/index.svelte | 12 +- web/src/routes/auth/register/index.ts | 65 +++--- web/src/routes/index.svelte | 33 +-- web/src/routes/photos/index.svelte | 19 +- web/svelte.config.js | 3 +- web/tsconfig.json | 7 +- 55 files changed, 516 insertions(+), 691 deletions(-) create mode 100644 server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts create mode 100644 server/apps/immich/src/api-v1/asset/response-dto/asset-file-upload-response.dto.ts create mode 100644 server/apps/immich/src/api-v1/asset/response-dto/check-duplicate-asset-response.dto.ts rename web/src/{lib/immich-api/index.ts => api/api.ts} (81%) create mode 100644 web/src/api/index.ts rename web/src/{lib => api}/open-api/.gitignore (100%) rename web/src/{lib => api}/open-api/.npmignore (100%) rename web/src/{lib => api}/open-api/.openapi-generator-ignore (100%) rename web/src/{lib => api}/open-api/.openapi-generator/FILES (100%) rename web/src/{lib => api}/open-api/.openapi-generator/VERSION (100%) rename web/src/{lib => api}/open-api/api.ts (96%) rename web/src/{lib => api}/open-api/base.ts (52%) rename web/src/{lib => api}/open-api/common.ts (100%) rename web/src/{lib => api}/open-api/configuration.ts (100%) rename web/src/{lib => api}/open-api/git_push.sh (100%) rename web/src/{lib => api}/open-api/index.ts (100%) rename web/src/lib/{api.ts => utils/api-helper.ts} (96%) delete mode 100644 web/src/routes/auth/login/api/get-users.ts delete mode 100644 web/src/routes/auth/login/api/select-admin.ts delete mode 100644 web/src/routes/auth/login/update.ts diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index 910d3df29a..f3c13faa91 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -8,7 +8,6 @@ import { Get, Param, ValidationPipe, - StreamableFile, Query, Response, Headers, @@ -16,13 +15,13 @@ import { Logger, HttpCode, BadRequestException, + UploadedFile, } from '@nestjs/common'; import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard'; import { AssetService } from './asset.service'; -import { FileFieldsInterceptor } from '@nestjs/platform-express'; +import { FileFieldsInterceptor, FileInterceptor } from '@nestjs/platform-express'; import { assetUploadOption } from '../../config/asset-upload.config'; import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; -import { CreateAssetDto } from './dto/create-asset.dto'; import { ServeFileDto } from './dto/serve-file.dto'; import { AssetEntity } from '@app/database/entities/asset.entity'; import { Response as Res } from 'express'; @@ -36,10 +35,14 @@ import { IAssetUploadedJob } from '@app/job/index'; import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant'; import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant'; import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto'; -import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiBody, ApiConsumes, 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'; +import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto'; +import { AssetFileUploadDto } from './dto/asset-file-upload.dto'; +import { CreateAssetDto } from './dto/create-asset.dto'; +import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; @UseGuards(JwtAuthGuard) @ApiBearerAuth() @@ -56,46 +59,43 @@ export class AssetController { ) {} @Post('upload') - @UseInterceptors( - FileFieldsInterceptor( - [ - { name: 'assetData', maxCount: 1 }, - { name: 'thumbnailData', maxCount: 1 }, - ], - assetUploadOption, - ), - ) + @UseInterceptors(FileInterceptor('assetData', assetUploadOption)) + @ApiConsumes('multipart/form-data') + @ApiBody({ + description: 'Asset Upload Information', + type: AssetFileUploadDto, + }) async uploadFile( @GetAuthUser() authUser: AuthUserDto, - @UploadedFiles() uploadFiles: { assetData: Express.Multer.File[] }, + @UploadedFile() file: Express.Multer.File, @Body(ValidationPipe) assetInfo: CreateAssetDto, - ): Promise<'ok' | undefined> { - for (const file of uploadFiles.assetData) { - try { - const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype); + ): Promise { + try { + const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype); - if (savedAsset) { - await this.assetUploadedQueue.add( - assetUploadedProcessorName, - { asset: savedAsset, fileName: file.originalname, fileSize: file.size }, - { jobId: savedAsset.id }, - ); - } - } catch (e) { - Logger.error(`Error uploading file ${e}`); - throw new BadRequestException(`Error uploading file`, `${e}`); + if (!savedAsset) { + throw new BadRequestException('Asset not created'); } - } - return 'ok'; + await this.assetUploadedQueue.add( + assetUploadedProcessorName, + { asset: savedAsset, fileName: file.originalname, fileSize: file.size }, + { jobId: savedAsset.id }, + ); + + return new AssetFileUploadResponseDto(savedAsset.id); + } catch (e) { + Logger.error(`Error uploading file ${e}`); + throw new BadRequestException(`Error uploading file`, `${e}`); + } } @Get('/download') async downloadFile( @GetAuthUser() authUser: AuthUserDto, @Response({ passthrough: true }) res: Res, - @Query(ValidationPipe) query: ServeFileDto, - ): Promise { + @Query(new ValidationPipe({ transform: true })) query: ServeFileDto, + ): Promise { return this.assetService.downloadFile(query, res); } @@ -104,14 +104,14 @@ export class AssetController { @Headers() headers: Record, @GetAuthUser() authUser: AuthUserDto, @Response({ passthrough: true }) res: Res, - @Query(ValidationPipe) query: ServeFileDto, - ): Promise { + @Query(new ValidationPipe({ transform: true })) query: ServeFileDto, + ): Promise { return this.assetService.serveFile(authUser, query, res, headers); } @Get('/thumbnail/:assetId') - async getAssetThumbnail(@Param('assetId') assetId: string) { - return await this.assetService.getAssetThumbnail(assetId); + async getAssetThumbnail(@Param('assetId') assetId: string): Promise { + return this.assetService.getAssetThumbnail(assetId); } @Get('/allObjects') @@ -195,11 +195,9 @@ export class AssetController { async checkDuplicateAsset( @GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) checkDuplicateAssetDto: CheckDuplicateAssetDto, - ) { + ): Promise { const res = await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto); - return { - isExist: res, - }; + return new CheckDuplicateAssetResponseDto(res); } } diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index c2bb24e69f..647b45a315 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -9,7 +9,6 @@ import { import { InjectRepository } from '@nestjs/typeorm'; import { IsNull, Not, Repository } from 'typeorm'; import { AuthUserDto } from '../../decorators/auth-user.decorator'; -import { CreateAssetDto } from './dto/create-asset.dto'; import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; import { constants, createReadStream, ReadStream, stat } from 'fs'; import { ServeFileDto } from './dto/serve-file.dto'; @@ -21,6 +20,8 @@ import fs from 'fs/promises'; 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'; +import { AssetFileUploadDto } from './dto/asset-file-upload.dto'; +import { CreateAssetDto } from './dto/create-asset.dto'; const fileInfo = promisify(stat); @@ -132,8 +133,10 @@ export class AssetService { let fileReadStream = null; const asset = await this.findAssetOfDevice(query.did, query.aid); - if (query.isThumb === 'false' || !query.isThumb) { + // Download Video + if (asset.type === AssetType.VIDEO) { const { size } = await fileInfo(asset.originalPath); + res.set({ 'Content-Type': asset.mimeType, 'Content-Length': size, @@ -142,22 +145,43 @@ export class AssetService { await fs.access(asset.originalPath, constants.R_OK | constants.W_OK); fileReadStream = createReadStream(asset.originalPath); } else { - if (!asset.resizePath) { - throw new NotFoundException('resizePath not set'); - } - const { size } = await fileInfo(asset.resizePath); - res.set({ - 'Content-Type': 'image/jpeg', - 'Content-Length': size, - }); + // Download Image + if (!query.isThumb) { + /** + * Download Image Original File + */ + const { size } = await fileInfo(asset.originalPath); - await fs.access(asset.resizePath, constants.R_OK | constants.W_OK); - fileReadStream = createReadStream(asset.resizePath); + res.set({ + 'Content-Type': asset.mimeType, + 'Content-Length': size, + }); + + await fs.access(asset.originalPath, constants.R_OK | constants.W_OK); + fileReadStream = createReadStream(asset.originalPath); + } else { + /** + * Download Image Resize File + */ + if (!asset.resizePath) { + throw new NotFoundException('resizePath not set'); + } + + const { size } = await fileInfo(asset.resizePath); + + res.set({ + 'Content-Type': 'image/jpeg', + 'Content-Length': size, + }); + + await fs.access(asset.resizePath, constants.R_OK | constants.W_OK); + fileReadStream = createReadStream(asset.resizePath); + } } return new StreamableFile(fileReadStream); } catch (e) { - Logger.error(`Error download asset`, 'downloadFile'); + Logger.error(`Error download asset ${e}`, 'downloadFile'); throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile'); } } @@ -177,7 +201,7 @@ export class AssetService { fileReadStream = createReadStream(asset.webpPath); } else { if (!asset.resizePath) { - return new NotFoundException('resizePath not set'); + throw new NotFoundException('resizePath not set'); } await fs.access(asset.resizePath, constants.R_OK | constants.W_OK); @@ -203,7 +227,7 @@ export class AssetService { } // Handle Sending Images - if (asset.type == AssetType.IMAGE || query.isThumb == 'true') { + if (asset.type == AssetType.IMAGE) { try { /** * Serve file viewer on the web @@ -225,7 +249,7 @@ export class AssetService { /** * Serve thumbnail image for both web and mobile app */ - if (query.isThumb === 'false' || !query.isThumb) { + if (!query.isThumb) { res.set({ 'Content-Type': asset.mimeType, }); @@ -262,7 +286,7 @@ export class AssetService { `Cannot read thumbnail file for asset ${asset.id} - contact your administrator`, ); } - } else if (asset.type == AssetType.VIDEO) { + } else { try { // Handle Video let videoPath = asset.originalPath; diff --git a/server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts b/server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts new file mode 100644 index 0000000000..ba57ee4df4 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts @@ -0,0 +1,10 @@ +import { AssetType } from '@app/database/entities/asset.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator'; +import { CreateAssetDto } from './create-asset.dto'; + +export class AssetFileUploadDto { + @IsNotEmpty() + @ApiProperty({ type: 'string', format: 'binary' }) + assetData!: any; +} diff --git a/server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts b/server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts index 0e18d2919b..e22732005a 100644 --- a/server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts @@ -1,5 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBooleanString, IsNotEmpty, IsOptional } from 'class-validator'; +import { Transform, Type } from 'class-transformer'; +import { IsBoolean, IsBooleanString, IsNotEmpty, IsOptional } from 'class-validator'; export class ServeFileDto { @IsNotEmpty() @@ -11,10 +12,28 @@ export class ServeFileDto { did!: string; @IsOptional() - @IsBooleanString() - isThumb?: string; + @IsBoolean() + @Transform(({ value }) => { + if (value == 'true') { + return true; + } else if (value == 'false') { + return false; + } + return value; + }) + @ApiProperty({ type: Boolean, title: 'Is serve thumbnail (resize) file' }) + isThumb?: boolean; @IsOptional() - @IsBooleanString() - isWeb?: string; + @IsBoolean() + @Transform(({ value }) => { + if (value == 'true') { + return true; + } else if (value == 'false') { + return false; + } + return value; + }) + @ApiProperty({ type: Boolean, title: 'Is request made from web' }) + isWeb?: boolean; } diff --git a/server/apps/immich/src/api-v1/asset/response-dto/asset-file-upload-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/asset-file-upload-response.dto.ts new file mode 100644 index 0000000000..7d3b6beb3c --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/response-dto/asset-file-upload-response.dto.ts @@ -0,0 +1,7 @@ +export class AssetFileUploadResponseDto { + constructor(id: string) { + this.id = id; + } + + id: string; +} diff --git a/server/apps/immich/src/api-v1/asset/response-dto/check-duplicate-asset-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/check-duplicate-asset-response.dto.ts new file mode 100644 index 0000000000..1a4bbf9113 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/response-dto/check-duplicate-asset-response.dto.ts @@ -0,0 +1,6 @@ +export class CheckDuplicateAssetResponseDto { + constructor(isExist: boolean) { + this.isExist = isExist; + } + isExist: boolean; +} diff --git a/server/apps/immich/src/api-v1/user/user.controller.ts b/server/apps/immich/src/api-v1/user/user.controller.ts index 5855fc18f8..6ec543b1fa 100644 --- a/server/apps/immich/src/api-v1/user/user.controller.ts +++ b/server/apps/immich/src/api-v1/user/user.controller.ts @@ -12,6 +12,7 @@ import { UploadedFile, Response, StreamableFile, + ParseBoolPipe, } from '@nestjs/common'; import { UserService } from './user.service'; import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard'; @@ -24,7 +25,6 @@ import { profileImageUploadOption } from '../../config/profile-image-upload.conf 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'; @@ -37,7 +37,10 @@ export class UserController { @UseGuards(JwtAuthGuard) @ApiBearerAuth() @Get() - async getAllUsers(@GetAuthUser() authUser: AuthUserDto, @Query('isAll') isAll: boolean): Promise { + async getAllUsers( + @GetAuthUser() authUser: AuthUserDto, + @Query('isAll', ParseBoolPipe) isAll: boolean, + ): Promise { return await this.userService.getAllUsers(authUser, isAll); } @@ -57,8 +60,8 @@ export class UserController { } @Get('/count') - async getUserCount(@Query('isAdmin') isAdmin: boolean): Promise { - return await this.userService.getUserCount(isAdmin); + async getUserCount(): Promise { + return await this.userService.getUserCount(); } @UseGuards(JwtAuthGuard) @@ -84,10 +87,7 @@ export class UserController { } @Get('/profile-image/:userId') - async getProfileImage( - @Param('userId') userId: string, - @Response({ passthrough: true }) res: Res, - ): Promise { + async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise { return this.userService.getUserProfileImage(userId, res); } } diff --git a/server/apps/immich/src/api-v1/user/user.service.ts b/server/apps/immich/src/api-v1/user/user.service.ts index a0761d3fa4..ac5c3f295d 100644 --- a/server/apps/immich/src/api-v1/user/user.service.ts +++ b/server/apps/immich/src/api-v1/user/user.service.ts @@ -32,7 +32,6 @@ export class UserService { async getAllUsers(authUser: AuthUserDto, isAll: boolean): Promise { if (isAll) { const allUsers = await this.userRepository.find(); - return allUsers.map(mapUser); } @@ -54,14 +53,8 @@ export class UserService { return mapUser(user); } - async getUserCount(isAdmin: boolean): Promise { - let users; - - if (isAdmin) { - users = await this.userRepository.find({ where: { isAdmin: true } }); - } else { - users = await this.userRepository.find(); - } + async getUserCount(): Promise { + const users = await this.userRepository.find(); return mapUserCountResponse(users.length); } @@ -157,8 +150,7 @@ export class UserService { } if (!user.profileImagePath) { - res.status(404).send('User does not have a profile image'); - return; + throw new NotFoundException('User does not have a profile image'); } res.set({ @@ -167,7 +159,7 @@ export class UserService { const fileStream = createReadStream(user.profileImagePath); return new StreamableFile(fileStream); } catch (e) { - res.status(404).send('User does not have a profile image'); + throw new NotFoundException('User does not have a profile image'); } } } diff --git a/server/apps/immich/src/app.module.ts b/server/apps/immich/src/app.module.ts index a57964a417..9bb6089320 100644 --- a/server/apps/immich/src/app.module.ts +++ b/server/apps/immich/src/app.module.ts @@ -15,6 +15,7 @@ import { AppController } from './app.controller'; import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; import { DatabaseModule } from '@app/database'; +import { AppLoggerMiddleware } from './middlewares/app-logger.middleware'; @Module({ imports: [ diff --git a/server/apps/immich/test/user.e2e-spec.ts b/server/apps/immich/test/user.e2e-spec.ts index 42f6f7f829..22f41e739e 100644 --- a/server/apps/immich/test/user.e2e-spec.ts +++ b/server/apps/immich/test/user.e2e-spec.ts @@ -86,7 +86,7 @@ describe('User', () => { }); it('fetches the user collection excluding the auth user', async () => { - const { status, body } = await request(app.getHttpServer()).get('/user'); + const { status, body } = await request(app.getHttpServer()).get('/user?isAll=false'); expect(status).toEqual(200); expect(body).toHaveLength(2); expect(body).toEqual( diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 84e257f0d0..fadc2b713b 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1 +1 @@ -{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[{"name":"isAdmin","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"string"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"type":"string"}},{"name":"isWeb","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"type":"string"}},{"name":"isWeb","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allObjects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allLocation":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/searchTerm":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":""}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":""}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"number"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"CreateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"},"assetType":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"fileExtension":{"type":"string"},"duration":{"type":"string"}},"required":["deviceAssetId","deviceId","assetType","createdAt","modifiedAt","isFavorite","fileExtension"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"string"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}},"required":["id","make","model","imageName","exifImageWidth","exifImageHeight","fileSizeInByte","orientation","dateTimeOriginal","modifyDate","lensModel","fNumber","focalLength","iso","exposureTime","latitude","longitude","city","state","country"]},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"type":{"enum":["IMAGE","VIDEO","AUDIO","OTHER"],"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["id","deviceAssetId","ownerId","deviceId","type","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{}},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceId":{"type":"string"},"deviceType":{"type":"string","enum":["IOS","ANDROID","WEB"]},"isAutoBackup":{"type":"boolean"}},"required":["deviceId","deviceType"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"number"},"userId":{"type":"string"},"deviceId":{"type":"string"},"deviceType":{"enum":["IOS","ANDROID","WEB"],"type":"string"},"notificationToken":{"type":"string","nullable":true},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","userId","deviceId","deviceType","notificationToken","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{}},"ServerInfoResponseDto":{"type":"object","properties":{"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"},"diskSizeRaw":{"type":"number"},"diskUseRaw":{"type":"number"},"diskAvailableRaw":{"type":"number"},"diskUsagePercentage":{"type":"number"}},"required":["diskSize","diskUse","diskAvailable","diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"number"},"minor":{"type":"number"},"patch":{"type":"number"},"build":{"type":"number"}},"required":["major","minor","patch","build"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"ownerId":{"type":"string"}},"required":["albumName","ownerId"]}}}} \ No newline at end of file +{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allObjects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allLocation":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/searchTerm":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":""}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"number"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"string"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}},"required":["id","make","model","imageName","exifImageWidth","exifImageHeight","fileSizeInByte","orientation","dateTimeOriginal","modifyDate","lensModel","fNumber","focalLength","iso","exposureTime","latitude","longitude","city","state","country"]},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"type":{"enum":["IMAGE","VIDEO","AUDIO","OTHER"],"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["id","deviceAssetId","ownerId","deviceId","type","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"}},"required":["isExist"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{}},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceId":{"type":"string"},"deviceType":{"type":"string","enum":["IOS","ANDROID","WEB"]},"isAutoBackup":{"type":"boolean"}},"required":["deviceId","deviceType"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"number"},"userId":{"type":"string"},"deviceId":{"type":"string"},"deviceType":{"enum":["IOS","ANDROID","WEB"],"type":"string"},"notificationToken":{"type":"string","nullable":true},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","userId","deviceId","deviceType","notificationToken","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{}},"ServerInfoResponseDto":{"type":"object","properties":{"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"},"diskSizeRaw":{"type":"number"},"diskUseRaw":{"type":"number"},"diskAvailableRaw":{"type":"number"},"diskUsagePercentage":{"type":"number"}},"required":["diskSize","diskUse","diskAvailable","diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"number"},"minor":{"type":"number"},"patch":{"type":"number"},"build":{"type":"number"}},"required":["major","minor","patch","build"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"ownerId":{"type":"string"}},"required":["albumName","ownerId"]}}}} \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 3eaccf5162..501c0c3fca 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -53,7 +53,7 @@ "@nestjs/cli": "^8.2.8", "@nestjs/schematics": "^8.0.11", "@nestjs/testing": "^8.4.7", - "@openapitools/openapi-generator-cli": "^2.5.1", + "@openapitools/openapi-generator-cli": "2.5.1", "@types/bcrypt": "^5.0.0", "@types/bull": "^3.15.7", "@types/cron": "^2.0.0", diff --git a/server/package.json b/server/package.json index 434fe0911e..ab0910ac9c 100644 --- a/server/package.json +++ b/server/package.json @@ -23,7 +23,7 @@ "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", "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" + "api:generate-typescript": "rm -rf ../web/src/api/open-api && npx openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/api/open-api" }, "dependencies": { "@mapbox/mapbox-sdk": "^0.13.3", diff --git a/web/src/lib/immich-api/index.ts b/web/src/api/api.ts similarity index 81% rename from web/src/lib/immich-api/index.ts rename to web/src/api/api.ts index 6441bec67f..0af86078af 100644 --- a/web/src/lib/immich-api/index.ts +++ b/web/src/api/api.ts @@ -1,3 +1,4 @@ +import { serverEndpoint } from '$lib/constants'; import { AlbumApi, AssetApi, @@ -6,7 +7,7 @@ import { DeviceInfoApi, ServerInfoApi, UserApi, -} from '../open-api'; +} from './open-api'; class ImmichApi { public userApi: UserApi; @@ -15,7 +16,7 @@ class ImmichApi { public authenticationApi: AuthenticationApi; public deviceInfoApi: DeviceInfoApi; public serverInfoApi: ServerInfoApi; - private config = new Configuration(); + private config = new Configuration({ basePath: serverEndpoint }); constructor() { this.userApi = new UserApi(this.config); @@ -31,4 +32,4 @@ class ImmichApi { } } -export const immichApi = new ImmichApi(); +export const api = new ImmichApi(); diff --git a/web/src/api/index.ts b/web/src/api/index.ts new file mode 100644 index 0000000000..5fb6580e20 --- /dev/null +++ b/web/src/api/index.ts @@ -0,0 +1,2 @@ +export * from './open-api'; +export * from './api'; diff --git a/web/src/lib/open-api/.gitignore b/web/src/api/open-api/.gitignore similarity index 100% rename from web/src/lib/open-api/.gitignore rename to web/src/api/open-api/.gitignore diff --git a/web/src/lib/open-api/.npmignore b/web/src/api/open-api/.npmignore similarity index 100% rename from web/src/lib/open-api/.npmignore rename to web/src/api/open-api/.npmignore diff --git a/web/src/lib/open-api/.openapi-generator-ignore b/web/src/api/open-api/.openapi-generator-ignore similarity index 100% rename from web/src/lib/open-api/.openapi-generator-ignore rename to web/src/api/open-api/.openapi-generator-ignore diff --git a/web/src/lib/open-api/.openapi-generator/FILES b/web/src/api/open-api/.openapi-generator/FILES similarity index 100% rename from web/src/lib/open-api/.openapi-generator/FILES rename to web/src/api/open-api/.openapi-generator/FILES diff --git a/web/src/lib/open-api/.openapi-generator/VERSION b/web/src/api/open-api/.openapi-generator/VERSION similarity index 100% rename from web/src/lib/open-api/.openapi-generator/VERSION rename to web/src/api/open-api/.openapi-generator/VERSION diff --git a/web/src/lib/open-api/api.ts b/web/src/api/open-api/api.ts similarity index 96% rename from web/src/lib/open-api/api.ts rename to web/src/api/open-api/api.ts index 46b0b25834..c94ef1437c 100644 --- a/web/src/lib/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -139,6 +139,19 @@ export interface AlbumResponseDto { */ 'assets': Array; } +/** + * + * @export + * @interface AssetFileUploadResponseDto + */ +export interface AssetFileUploadResponseDto { + /** + * + * @type {string} + * @memberof AssetFileUploadResponseDto + */ + 'id': string; +} /** * * @export @@ -271,6 +284,19 @@ export interface CheckDuplicateAssetDto { */ 'deviceId': string; } +/** + * + * @export + * @interface CheckDuplicateAssetResponseDto + */ +export interface CheckDuplicateAssetResponseDto { + /** + * + * @type {boolean} + * @memberof CheckDuplicateAssetResponseDto + */ + 'isExist': boolean; +} /** * * @export @@ -296,71 +322,6 @@ export interface CreateAlbumDto { */ 'assetIds'?: Array; } -/** - * - * @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 @@ -1879,12 +1840,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - downloadFile: async (aid: string, did: string, isThumb?: string, isWeb?: string, options: AxiosRequestConfig = {}): Promise => { + downloadFile: async (aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'aid' is not null or undefined assertParamExists('downloadFile', 'aid', aid) // verify required parameter 'did' is not null or undefined @@ -2221,12 +2182,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - serveFile: async (aid: string, did: string, isThumb?: string, isWeb?: string, options: AxiosRequestConfig = {}): Promise => { + serveFile: async (aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'aid' is not null or undefined assertParamExists('serveFile', 'aid', aid) // verify required parameter 'did' is not null or undefined @@ -2276,13 +2237,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration }, /** * - * @param {CreateAssetDto} createAssetDto + * @param {any} assetData * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile: async (createAssetDto: CreateAssetDto, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'createAssetDto' is not null or undefined - assertParamExists('uploadFile', 'createAssetDto', createAssetDto) + uploadFile: async (assetData: any, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'assetData' is not null or undefined + assertParamExists('uploadFile', 'assetData', assetData) 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); @@ -2294,19 +2255,24 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration 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 (assetData !== undefined) { + localVarFormParams.append('assetData', assetData as any); + } + + + localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; - 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) + localVarRequestOptions.data = localVarFormParams; return { url: toPathString(localVarUrlObj), @@ -2330,7 +2296,7 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.checkDuplicateAsset(checkDuplicateAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -2348,12 +2314,12 @@ export const AssetApiFp = function(configuration?: Configuration) { * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [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> { + async downloadFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(aid, did, isThumb, isWeb, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -2440,23 +2406,23 @@ export const AssetApiFp = function(configuration?: Configuration) { * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [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> { + async serveFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.serveFile(aid, did, isThumb, isWeb, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** * - * @param {CreateAssetDto} createAssetDto + * @param {any} assetData * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async uploadFile(createAssetDto: CreateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(createAssetDto, options); + async uploadFile(assetData: any, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetData, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -2476,7 +2442,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {*} [options] Override http request option. * @throws {RequiredError} */ - checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: any): AxiosPromise { + checkDuplicateAsset(checkDuplicateAssetDto: CheckDuplicateAssetDto, options?: any): AxiosPromise { return localVarFp.checkDuplicateAsset(checkDuplicateAssetDto, options).then((request) => request(axios, basePath)); }, /** @@ -2492,12 +2458,12 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: any): AxiosPromise { + downloadFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: any): AxiosPromise { return localVarFp.downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath)); }, /** @@ -2575,22 +2541,22 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: any): AxiosPromise { + serveFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: any): AxiosPromise { return localVarFp.serveFile(aid, did, isThumb, isWeb, options).then((request) => request(axios, basePath)); }, /** * - * @param {CreateAssetDto} createAssetDto + * @param {any} assetData * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile(createAssetDto: CreateAssetDto, options?: any): AxiosPromise { - return localVarFp.uploadFile(createAssetDto, options).then((request) => request(axios, basePath)); + uploadFile(assetData: any, options?: any): AxiosPromise { + return localVarFp.uploadFile(assetData, options).then((request) => request(axios, basePath)); }, }; }; @@ -2629,13 +2595,13 @@ export class AssetApi extends BaseAPI { * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public downloadFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig) { + public downloadFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig) { return AssetApiFp(this.configuration).downloadFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath)); } @@ -2730,25 +2696,25 @@ export class AssetApi extends BaseAPI { * * @param {string} aid * @param {string} did - * @param {string} [isThumb] - * @param {string} [isWeb] + * @param {boolean} [isThumb] + * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public serveFile(aid: string, did: string, isThumb?: string, isWeb?: string, options?: AxiosRequestConfig) { + public serveFile(aid: string, did: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig) { return AssetApiFp(this.configuration).serveFile(aid, did, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath)); } /** * - * @param {CreateAssetDto} createAssetDto + * @param {any} assetData * @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)); + public uploadFile(assetData: any, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).uploadFile(assetData, options).then((request) => request(this.axios, this.basePath)); } } @@ -3560,13 +3526,10 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) }, /** * - * @param {boolean} isAdmin * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getUserCount: async (isAdmin: boolean, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'isAdmin' is not null or undefined - assertParamExists('getUserCount', 'isAdmin', isAdmin) + getUserCount: async (options: AxiosRequestConfig = {}): Promise => { 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); @@ -3579,10 +3542,6 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - if (isAdmin !== undefined) { - localVarQueryParameter['isAdmin'] = isAdmin; - } - setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -3688,18 +3647,17 @@ export const UserApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getProfileImage(userId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getProfileImage(userId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { 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> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getUserCount(isAdmin, options); + async getUserCount(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getUserCount(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -3763,17 +3721,16 @@ export const UserApiFactory = function (configuration?: Configuration, basePath? * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getProfileImage(userId: string, options?: any): AxiosPromise { + getProfileImage(userId: string, options?: any): AxiosPromise { 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 { - return localVarFp.getUserCount(isAdmin, options).then((request) => request(axios, basePath)); + getUserCount(options?: any): AxiosPromise { + return localVarFp.getUserCount(options).then((request) => request(axios, basePath)); }, /** * @@ -3850,13 +3807,12 @@ export class UserApi extends BaseAPI { /** * - * @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)); + public getUserCount(options?: AxiosRequestConfig) { + return UserApiFp(this.configuration).getUserCount(options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/lib/open-api/base.ts b/web/src/api/open-api/base.ts similarity index 52% rename from web/src/lib/open-api/base.ts rename to web/src/api/open-api/base.ts index d6c8a7fef9..cccb44fd05 100644 --- a/web/src/lib/open-api/base.ts +++ b/web/src/api/open-api/base.ts @@ -5,29 +5,30 @@ * 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 { 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 const BASE_PATH = "/api".replace(/\/+$/, ""); /** * * @export */ export const COLLECTION_FORMATS = { - csv: ',', - ssv: ' ', - tsv: '\t', - pipes: '|', + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", }; /** @@ -36,8 +37,8 @@ export const COLLECTION_FORMATS = { * @interface RequestArgs */ export interface RequestArgs { - url: string; - options: AxiosRequestConfig; + url: string; + options: AxiosRequestConfig; } /** @@ -46,19 +47,15 @@ export interface RequestArgs { * @class BaseAPI */ export class BaseAPI { - protected configuration: Configuration | undefined; + 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; - } - } -} + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } +}; /** * @@ -67,8 +64,8 @@ export class BaseAPI { * @extends {Error} */ export class RequiredError extends Error { - name: 'RequiredError' = 'RequiredError'; - constructor(public field: string, msg?: string) { - super(msg); - } + name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } } diff --git a/web/src/lib/open-api/common.ts b/web/src/api/open-api/common.ts similarity index 100% rename from web/src/lib/open-api/common.ts rename to web/src/api/open-api/common.ts diff --git a/web/src/lib/open-api/configuration.ts b/web/src/api/open-api/configuration.ts similarity index 100% rename from web/src/lib/open-api/configuration.ts rename to web/src/api/open-api/configuration.ts diff --git a/web/src/lib/open-api/git_push.sh b/web/src/api/open-api/git_push.sh similarity index 100% rename from web/src/lib/open-api/git_push.sh rename to web/src/api/open-api/git_push.sh diff --git a/web/src/lib/open-api/index.ts b/web/src/api/open-api/index.ts similarity index 100% rename from web/src/lib/open-api/index.ts rename to web/src/api/open-api/index.ts diff --git a/web/src/hooks.ts b/web/src/hooks.ts index 400bf275fe..17f4647b75 100644 --- a/web/src/hooks.ts +++ b/web/src/hooks.ts @@ -1,7 +1,7 @@ -import type { ExternalFetch, GetSession, Handle } from '@sveltejs/kit'; +import type { GetSession, Handle } from '@sveltejs/kit'; import * as cookie from 'cookie'; -import { serverEndpoint } from '$lib/constants'; -import { session } from '$app/stores'; +import { api } from '@api'; +import { AxiosError } from 'axios'; export const handle: Handle = async ({ event, resolve }) => { const cookies = cookie.parse(event.request.headers.get('cookie') || ''); @@ -13,14 +13,10 @@ export const handle: Handle = async ({ event, resolve }) => { try { const { email, isAdmin, firstName, lastName, id, accessToken } = JSON.parse(cookies.session); - const res = await fetch(`${serverEndpoint}/auth/validateToken`, { - method: 'POST', - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); + api.setAccessToken(accessToken); + const { status } = await api.authenticationApi.validateAccessToken(); - if (res.status === 201) { + if (status === 201) { event.locals.user = { id, accessToken, @@ -35,7 +31,12 @@ export const handle: Handle = async ({ event, resolve }) => { return response; } catch (error) { - console.log('Error parsing session', error); + if (error instanceof AxiosError) { + console.log('Error validating token'); + return await resolve(event); + } + + console.log('Error parsing session'); return await resolve(event); } }; diff --git a/web/src/lib/components/admin/user-management.svelte b/web/src/lib/components/admin/user-management.svelte index e6825f7554..62d60f8ffc 100644 --- a/web/src/lib/components/admin/user-management.svelte +++ b/web/src/lib/components/admin/user-management.svelte @@ -1,7 +1,9 @@ @@ -18,7 +20,7 @@ - {#each usersOnServer as user, i} + {#each allUsers as user, i} import { createEventDispatcher, onDestroy, onMount } from 'svelte'; - import { fly, slide } from 'svelte/transition'; + import { fly } from 'svelte/transition'; import AsserViewerNavBar from './asser-viewer-nav-bar.svelte'; import { flattenAssetGroupByDate } from '$lib/stores/assets'; import ChevronRight from 'svelte-material-icons/ChevronRight.svelte'; import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte'; - import { AssetType, type ImmichAsset, type ImmichExif } from '../../models/immich-asset'; + import { AssetType } from '../../models/immich-asset'; import PhotoViewer from './photo-viewer.svelte'; import DetailPanel from './detail-panel.svelte'; import { session } from '$app/stores'; - import { serverEndpoint } from '../../constants'; - import axios from 'axios'; import { downloadAssets } from '$lib/stores/download'; import VideoViewer from './video-viewer.svelte'; + import { api, AssetResponseDto } from '@api'; const dispatch = createEventDispatcher(); - export let selectedAsset: ImmichAsset; + export let selectedAsset: AssetResponseDto; export let selectedIndex: number; @@ -99,8 +98,6 @@ const downloadFile = async () => { if ($session.user) { - const url = `${serverEndpoint}/asset/download?aid=${selectedAsset.deviceAssetId}&did=${selectedAsset.deviceId}&isThumb=false`; - try { const imageName = selectedAsset.exifInfo?.imageName ? selectedAsset.exifInfo?.imageName : selectedAsset.id; const imageExtension = selectedAsset.originalPath.split('.')[1]; @@ -112,24 +109,31 @@ } $downloadAssets[imageFileName] = 0; - const res = await axios.get(url, { - responseType: 'blob', - headers: { - Authorization: 'Bearer ' + $session.user.accessToken, - }, - onDownloadProgress: (progressEvent) => { - if (progressEvent.lengthComputable) { - const total = progressEvent.total; - const current = progressEvent.loaded; - let percentCompleted = Math.floor((current / total) * 100); + const { data, status } = await api.assetApi.downloadFile( + selectedAsset.deviceAssetId, + selectedAsset.deviceId, + false, + false, + { + responseType: 'blob', + onDownloadProgress: (progressEvent) => { + if (progressEvent.lengthComputable) { + const total = progressEvent.total; + const current = progressEvent.loaded; + let percentCompleted = Math.floor((current / total) * 100); - $downloadAssets[imageFileName] = percentCompleted; - } + $downloadAssets[imageFileName] = percentCompleted; + } + }, }, - }); + ); - if (res.status === 200) { - const fileUrl = URL.createObjectURL(new Blob([res.data])); + if (!(data instanceof Blob)) { + return; + } + + if (status === 200) { + const fileUrl = URL.createObjectURL(data); const anchor = document.createElement('a'); anchor.href = fileUrl; anchor.download = imageFileName; diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 62f35c7746..0fa493e6eb 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -5,24 +5,23 @@ import CameraIris from 'svelte-material-icons/CameraIris.svelte'; import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte'; import moment from 'moment'; - import type { ImmichAsset } from '../../models/immich-asset'; import { createEventDispatcher, onMount } from 'svelte'; import { browser } from '$app/env'; - import { round } from 'lodash'; + import { AssetResponseDto } from '@api'; // Map Property let map: any; let leaflet: any; let marker: any; - export let asset: ImmichAsset; - $: if (asset.exifInfo) { + export let asset: AssetResponseDto; + $: if (asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null) { drawMap(asset.exifInfo.latitude, asset.exifInfo.longitude); } onMount(async () => { if (browser) { - if (asset.exifInfo) { + if (asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null) { await drawMap(asset.exifInfo.latitude, asset.exifInfo.longitude); } } diff --git a/web/src/lib/components/asset-viewer/immich-thumbnail.svelte b/web/src/lib/components/asset-viewer/immich-thumbnail.svelte index 2c5bd2da07..a07a6faa9d 100644 --- a/web/src/lib/components/asset-viewer/immich-thumbnail.svelte +++ b/web/src/lib/components/asset-viewer/immich-thumbnail.svelte @@ -1,18 +1,18 @@ diff --git a/web/src/lib/components/asset-viewer/video-viewer.svelte b/web/src/lib/components/asset-viewer/video-viewer.svelte index 3697bc9acb..12b417fc94 100644 --- a/web/src/lib/components/asset-viewer/video-viewer.svelte +++ b/web/src/lib/components/asset-viewer/video-viewer.svelte @@ -1,15 +1,14 @@ @@ -24,11 +21,21 @@ import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte'; import AnnouncementBox from '$lib/components/shared/announcement-box.svelte'; import UploadPanel from '$lib/components/shared/upload-panel.svelte'; + import { onMount } from 'svelte'; + import { api } from '@api'; export let url: string; - export let shouldShowAnnouncement: boolean; - export let localVersion: string; - export let remoteVersion: string; + let shouldShowAnnouncement: boolean; + let localVersion: string; + let remoteVersion: string; + + onMount(async () => { + const res = await checkAppVersion(); + + shouldShowAnnouncement = res.shouldShowAnnouncement; + localVersion = res.localVersion ?? 'unknown'; + remoteVersion = res.remoteVersion ?? 'unknown'; + });
diff --git a/web/src/routes/admin/api/create-user.ts b/web/src/routes/admin/api/create-user.ts index 9cc028a85b..0af4872c00 100644 --- a/web/src/routes/admin/api/create-user.ts +++ b/web/src/routes/admin/api/create-user.ts @@ -1,44 +1,34 @@ import type { RequestHandler } from '@sveltejs/kit'; -import { serverEndpoint } from '$lib/constants'; +import { api } from '@api'; -export const post: RequestHandler = async ({ request, locals }) => { - const form = await request.formData(); +export const post: RequestHandler = async ({ request }) => { + const form = await request.formData(); - const email = form.get('email') - const password = form.get('password') - const firstName = form.get('firstName') - const lastName = form.get('lastName') + const email = form.get('email'); + const password = form.get('password'); + const firstName = form.get('firstName'); + const lastName = form.get('lastName'); - const payload = { - email, - password, - firstName, - lastName, - } + const { status } = await api.userApi.createUser({ + email: String(email), + password: String(password), + firstName: String(firstName), + lastName: String(lastName), + }); - const res = await fetch(`${serverEndpoint}/user`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${locals.user?.accessToken}` - }, - body: JSON.stringify(payload), - }) - - if (res.status === 201) { - return { - status: 201, - body: { - success: 'Succesfully create user account' - } - } - } else { - return { - status: 400, - body: { - error: await res.json() - } - } - - } -} \ No newline at end of file + if (status === 201) { + return { + status: 201, + body: { + success: 'Succesfully create user account', + }, + }; + } else { + return { + status: 400, + body: { + error: 'Error create user account', + }, + }; + } +}; diff --git a/web/src/routes/admin/index.svelte b/web/src/routes/admin/index.svelte index b9e0c6bea1..cb577df2e2 100644 --- a/web/src/routes/admin/index.svelte +++ b/web/src/routes/admin/index.svelte @@ -1,8 +1,8 @@ diff --git a/web/src/routes/auth/register/index.ts b/web/src/routes/auth/register/index.ts index 7038606140..8561b30622 100644 --- a/web/src/routes/auth/register/index.ts +++ b/web/src/routes/auth/register/index.ts @@ -1,43 +1,34 @@ import type { RequestHandler } from '@sveltejs/kit'; -import { serverEndpoint } from '$lib/constants'; +import { api } from '@api'; export const post: RequestHandler = async ({ request }) => { - const form = await request.formData(); + const form = await request.formData(); - const email = form.get('email') - const password = form.get('password') - const firstName = form.get('firstName') - const lastName = form.get('lastName') + const email = form.get('email'); + const password = form.get('password'); + const firstName = form.get('firstName'); + const lastName = form.get('lastName'); - const payload = { - email, - password, - firstName, - lastName, - } + const { status } = await api.authenticationApi.adminSignUp({ + email: String(email), + password: String(password), + firstName: String(firstName), + lastName: String(lastName), + }); - const res = await fetch(`${serverEndpoint}/auth/admin-sign-up`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(payload), - }) - - if (res.status === 201) { - return { - status: 201, - body: { - success: 'Succesfully create admin account' - } - } - } else { - return { - status: 400, - body: { - error: await res.json() - } - } - - } -} \ No newline at end of file + if (status === 201) { + return { + status: 201, + body: { + success: 'Succesfully create admin account', + }, + }; + } else { + return { + status: 400, + body: { + error: 'Error create admin account', + }, + }; + } +}; diff --git a/web/src/routes/index.svelte b/web/src/routes/index.svelte index 25fd6b01b3..edfeb06daf 100644 --- a/web/src/routes/index.svelte +++ b/web/src/routes/index.svelte @@ -1,39 +1,28 @@