diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..b45b801c9c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +mobile/openapi/**/*.md -diff -merge +mobile/openapi/**/*.md linguist-generated=true +mobile/openapi/**/*.dart -diff -merge +mobile/openapi/**/*.dart linguist-generated=true + +web/src/api/open-api/**/*.md -diff -merge +web/src/api/open-api/**/*.md linguist-generated=true + +web/src/api/open-api/**/*.ts -diff -merge +web/src/api/open-api/**/*.ts linguist-generated=true + +mobile/openapi/.openapi-generator/FILES -diff -merge +mobile/openapi/.openapi-generator/FILES linguist-generated=true diff --git a/Makefile b/Makefile index 460f01eb35..34c9619c51 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,6 @@ prod-scale: api: cd ./server && npm run api:generate + +attach-server: + docker exec -it docker_immich-server_1 sh \ No newline at end of file diff --git a/NOTES.md b/NOTES.md deleted file mode 100644 index a21726327a..0000000000 --- a/NOTES.md +++ /dev/null @@ -1,9 +0,0 @@ -# TODO - -Server scenario with web - -[ ] 1 user exist without admin right -> make admin on first check - -[ ] 2 users exist without admin right -> ask user to choose which account will be the admin - -[ X ] No users exist -> prompt signup form for Admin \ No newline at end of file diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 3409c3eb15..92f15f288d 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -14,6 +14,7 @@ doc/AssetApi.md doc/AssetCountByTimeBucket.md doc/AssetCountByTimeBucketResponseDto.md doc/AssetCountByUserIdResponseDto.md +doc/AssetEntity.md doc/AssetFileUploadResponseDto.md doc/AssetResponseDto.md doc/AssetTypeEnum.md @@ -25,6 +26,7 @@ doc/CheckExistingAssetsResponseDto.md doc/CreateAlbumDto.md doc/CreateDeviceInfoDto.md doc/CreateProfileImageResponseDto.md +doc/CreateTagDto.md doc/CreateUserDto.md doc/CuratedLocationsResponseDto.md doc/CuratedObjectsResponseDto.md @@ -34,6 +36,7 @@ doc/DeleteAssetStatus.md doc/DeviceInfoApi.md doc/DeviceInfoResponseDto.md doc/DeviceTypeEnum.md +doc/ExifEntity.md doc/ExifResponseDto.md doc/GetAssetByTimeBucketDto.md doc/GetAssetCountByTimeBucketDto.md @@ -58,20 +61,27 @@ doc/ServerPingResponse.md doc/ServerStatsResponseDto.md doc/ServerVersionReponseDto.md doc/SignUpDto.md +doc/SmartInfoEntity.md doc/SmartInfoResponseDto.md doc/SystemConfigApi.md doc/SystemConfigKey.md doc/SystemConfigResponseDto.md doc/SystemConfigResponseItem.md +doc/TagApi.md +doc/TagEntity.md +doc/TagResponseDto.md +doc/TagTypeEnum.md doc/ThumbnailFormat.md doc/TimeGroupEnum.md doc/UpdateAlbumDto.md doc/UpdateAssetDto.md doc/UpdateDeviceInfoDto.md +doc/UpdateTagDto.md doc/UpdateUserDto.md doc/UsageByUserDto.md doc/UserApi.md doc/UserCountResponseDto.md +doc/UserEntity.md doc/UserResponseDto.md doc/ValidateAccessTokenResponseDto.md git_push.sh @@ -84,6 +94,7 @@ lib/api/job_api.dart lib/api/o_auth_api.dart lib/api/server_info_api.dart lib/api/system_config_api.dart +lib/api/tag_api.dart lib/api/user_api.dart lib/api_client.dart lib/api_exception.dart @@ -103,6 +114,7 @@ lib/model/all_job_status_response_dto.dart lib/model/asset_count_by_time_bucket.dart lib/model/asset_count_by_time_bucket_response_dto.dart lib/model/asset_count_by_user_id_response_dto.dart +lib/model/asset_entity.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_response_dto.dart lib/model/asset_type_enum.dart @@ -113,6 +125,7 @@ lib/model/check_existing_assets_response_dto.dart lib/model/create_album_dto.dart lib/model/create_device_info_dto.dart lib/model/create_profile_image_response_dto.dart +lib/model/create_tag_dto.dart lib/model/create_user_dto.dart lib/model/curated_locations_response_dto.dart lib/model/curated_objects_response_dto.dart @@ -121,6 +134,7 @@ lib/model/delete_asset_response_dto.dart lib/model/delete_asset_status.dart lib/model/device_info_response_dto.dart lib/model/device_type_enum.dart +lib/model/exif_entity.dart lib/model/exif_response_dto.dart lib/model/get_asset_by_time_bucket_dto.dart lib/model/get_asset_count_by_time_bucket_dto.dart @@ -142,18 +156,24 @@ lib/model/server_ping_response.dart lib/model/server_stats_response_dto.dart lib/model/server_version_reponse_dto.dart lib/model/sign_up_dto.dart +lib/model/smart_info_entity.dart lib/model/smart_info_response_dto.dart lib/model/system_config_key.dart lib/model/system_config_response_dto.dart lib/model/system_config_response_item.dart +lib/model/tag_entity.dart +lib/model/tag_response_dto.dart +lib/model/tag_type_enum.dart lib/model/thumbnail_format.dart lib/model/time_group_enum.dart lib/model/update_album_dto.dart lib/model/update_asset_dto.dart lib/model/update_device_info_dto.dart +lib/model/update_tag_dto.dart lib/model/update_user_dto.dart lib/model/usage_by_user_dto.dart lib/model/user_count_response_dto.dart +lib/model/user_entity.dart lib/model/user_response_dto.dart lib/model/validate_access_token_response_dto.dart pubspec.yaml diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index a414e09869..2974e839ec 100644 Binary files a/mobile/openapi/README.md and b/mobile/openapi/README.md differ diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 9140b2d5e8..d6139bd58b 100644 Binary files a/mobile/openapi/doc/AssetApi.md and b/mobile/openapi/doc/AssetApi.md differ diff --git a/mobile/openapi/doc/AssetEntity.md b/mobile/openapi/doc/AssetEntity.md new file mode 100644 index 0000000000..8c83a94b03 Binary files /dev/null and b/mobile/openapi/doc/AssetEntity.md differ diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index 3e80e12c3f..aa7d5494cc 100644 Binary files a/mobile/openapi/doc/AssetResponseDto.md and b/mobile/openapi/doc/AssetResponseDto.md differ diff --git a/mobile/openapi/doc/CreateTagDto.md b/mobile/openapi/doc/CreateTagDto.md new file mode 100644 index 0000000000..64222db499 Binary files /dev/null and b/mobile/openapi/doc/CreateTagDto.md differ diff --git a/mobile/openapi/doc/ExifEntity.md b/mobile/openapi/doc/ExifEntity.md new file mode 100644 index 0000000000..4de3fc96ee Binary files /dev/null and b/mobile/openapi/doc/ExifEntity.md differ diff --git a/mobile/openapi/doc/SmartInfoEntity.md b/mobile/openapi/doc/SmartInfoEntity.md new file mode 100644 index 0000000000..6b22c3c004 Binary files /dev/null and b/mobile/openapi/doc/SmartInfoEntity.md differ diff --git a/mobile/openapi/doc/TagApi.md b/mobile/openapi/doc/TagApi.md new file mode 100644 index 0000000000..5137a1dba1 Binary files /dev/null and b/mobile/openapi/doc/TagApi.md differ diff --git a/mobile/openapi/doc/TagEntity.md b/mobile/openapi/doc/TagEntity.md new file mode 100644 index 0000000000..3b5bd36c22 Binary files /dev/null and b/mobile/openapi/doc/TagEntity.md differ diff --git a/mobile/openapi/doc/TagResponseDto.md b/mobile/openapi/doc/TagResponseDto.md new file mode 100644 index 0000000000..6e26854779 Binary files /dev/null and b/mobile/openapi/doc/TagResponseDto.md differ diff --git a/mobile/openapi/doc/TagTypeEnum.md b/mobile/openapi/doc/TagTypeEnum.md new file mode 100644 index 0000000000..cede140906 Binary files /dev/null and b/mobile/openapi/doc/TagTypeEnum.md differ diff --git a/mobile/openapi/doc/UpdateAssetDto.md b/mobile/openapi/doc/UpdateAssetDto.md index dc4f1ff4be..f02b6d5ed4 100644 Binary files a/mobile/openapi/doc/UpdateAssetDto.md and b/mobile/openapi/doc/UpdateAssetDto.md differ diff --git a/mobile/openapi/doc/UpdateTagDto.md b/mobile/openapi/doc/UpdateTagDto.md new file mode 100644 index 0000000000..ad4e551be6 Binary files /dev/null and b/mobile/openapi/doc/UpdateTagDto.md differ diff --git a/mobile/openapi/doc/UserEntity.md b/mobile/openapi/doc/UserEntity.md new file mode 100644 index 0000000000..d342eb4880 Binary files /dev/null and b/mobile/openapi/doc/UserEntity.md differ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 72d4c4fd0f..930cf69df0 100644 Binary files a/mobile/openapi/lib/api.dart and b/mobile/openapi/lib/api.dart differ diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 73811beb63..c48f8b1242 100644 Binary files a/mobile/openapi/lib/api/asset_api.dart and b/mobile/openapi/lib/api/asset_api.dart differ diff --git a/mobile/openapi/lib/api/tag_api.dart b/mobile/openapi/lib/api/tag_api.dart new file mode 100644 index 0000000000..662b6c7723 Binary files /dev/null and b/mobile/openapi/lib/api/tag_api.dart differ diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index e1e6fc5526..cf61c2a85d 100644 Binary files a/mobile/openapi/lib/api_client.dart and b/mobile/openapi/lib/api_client.dart differ diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 94e878289a..828ff84fcb 100644 Binary files a/mobile/openapi/lib/api_helper.dart and b/mobile/openapi/lib/api_helper.dart differ diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index f9901a358f..ac170523d8 100644 Binary files a/mobile/openapi/lib/model/album_response_dto.dart and b/mobile/openapi/lib/model/album_response_dto.dart differ diff --git a/mobile/openapi/lib/model/asset_entity.dart b/mobile/openapi/lib/model/asset_entity.dart new file mode 100644 index 0000000000..b6ca35a1d7 Binary files /dev/null and b/mobile/openapi/lib/model/asset_entity.dart differ diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 44a41036ab..ae15193a30 100644 Binary files a/mobile/openapi/lib/model/asset_response_dto.dart and b/mobile/openapi/lib/model/asset_response_dto.dart differ diff --git a/mobile/openapi/lib/model/create_tag_dto.dart b/mobile/openapi/lib/model/create_tag_dto.dart new file mode 100644 index 0000000000..0d9a79255c Binary files /dev/null and b/mobile/openapi/lib/model/create_tag_dto.dart differ diff --git a/mobile/openapi/lib/model/exif_entity.dart b/mobile/openapi/lib/model/exif_entity.dart new file mode 100644 index 0000000000..208127120b Binary files /dev/null and b/mobile/openapi/lib/model/exif_entity.dart differ diff --git a/mobile/openapi/lib/model/smart_info_entity.dart b/mobile/openapi/lib/model/smart_info_entity.dart new file mode 100644 index 0000000000..cf3e335958 Binary files /dev/null and b/mobile/openapi/lib/model/smart_info_entity.dart differ diff --git a/mobile/openapi/lib/model/tag_entity.dart b/mobile/openapi/lib/model/tag_entity.dart new file mode 100644 index 0000000000..d973c3f881 Binary files /dev/null and b/mobile/openapi/lib/model/tag_entity.dart differ diff --git a/mobile/openapi/lib/model/tag_response_dto.dart b/mobile/openapi/lib/model/tag_response_dto.dart new file mode 100644 index 0000000000..d4ed671a28 Binary files /dev/null and b/mobile/openapi/lib/model/tag_response_dto.dart differ diff --git a/mobile/openapi/lib/model/tag_type_enum.dart b/mobile/openapi/lib/model/tag_type_enum.dart new file mode 100644 index 0000000000..66c44d1303 Binary files /dev/null and b/mobile/openapi/lib/model/tag_type_enum.dart differ diff --git a/mobile/openapi/lib/model/update_asset_dto.dart b/mobile/openapi/lib/model/update_asset_dto.dart index 84db0f2a16..cb7fd1c883 100644 Binary files a/mobile/openapi/lib/model/update_asset_dto.dart and b/mobile/openapi/lib/model/update_asset_dto.dart differ diff --git a/mobile/openapi/lib/model/update_tag_dto.dart b/mobile/openapi/lib/model/update_tag_dto.dart new file mode 100644 index 0000000000..a651405ec4 Binary files /dev/null and b/mobile/openapi/lib/model/update_tag_dto.dart differ diff --git a/mobile/openapi/lib/model/user_entity.dart b/mobile/openapi/lib/model/user_entity.dart new file mode 100644 index 0000000000..7862d296ed Binary files /dev/null and b/mobile/openapi/lib/model/user_entity.dart differ diff --git a/mobile/openapi/test/asset_entity_test.dart b/mobile/openapi/test/asset_entity_test.dart new file mode 100644 index 0000000000..d34d066122 Binary files /dev/null and b/mobile/openapi/test/asset_entity_test.dart differ diff --git a/mobile/openapi/test/create_tag_dto_test.dart b/mobile/openapi/test/create_tag_dto_test.dart new file mode 100644 index 0000000000..8df20a4ef5 Binary files /dev/null and b/mobile/openapi/test/create_tag_dto_test.dart differ diff --git a/mobile/openapi/test/exif_entity_test.dart b/mobile/openapi/test/exif_entity_test.dart new file mode 100644 index 0000000000..2cc95f9665 Binary files /dev/null and b/mobile/openapi/test/exif_entity_test.dart differ diff --git a/mobile/openapi/test/smart_info_entity_test.dart b/mobile/openapi/test/smart_info_entity_test.dart new file mode 100644 index 0000000000..ca4ce46c5f Binary files /dev/null and b/mobile/openapi/test/smart_info_entity_test.dart differ diff --git a/mobile/openapi/test/tag_api_test.dart b/mobile/openapi/test/tag_api_test.dart new file mode 100644 index 0000000000..6554281d54 Binary files /dev/null and b/mobile/openapi/test/tag_api_test.dart differ diff --git a/mobile/openapi/test/tag_entity_test.dart b/mobile/openapi/test/tag_entity_test.dart new file mode 100644 index 0000000000..f19564b38d Binary files /dev/null and b/mobile/openapi/test/tag_entity_test.dart differ diff --git a/mobile/openapi/test/tag_response_dto_test.dart b/mobile/openapi/test/tag_response_dto_test.dart new file mode 100644 index 0000000000..44ed368fbb Binary files /dev/null and b/mobile/openapi/test/tag_response_dto_test.dart differ diff --git a/mobile/openapi/test/tag_type_enum_test.dart b/mobile/openapi/test/tag_type_enum_test.dart new file mode 100644 index 0000000000..07a0389466 Binary files /dev/null and b/mobile/openapi/test/tag_type_enum_test.dart differ diff --git a/mobile/openapi/test/update_tag_dto_test.dart b/mobile/openapi/test/update_tag_dto_test.dart new file mode 100644 index 0000000000..4f4e9498bc Binary files /dev/null and b/mobile/openapi/test/update_tag_dto_test.dart differ diff --git a/mobile/openapi/test/user_entity_test.dart b/mobile/openapi/test/user_entity_test.dart new file mode 100644 index 0000000000..de2efd6e00 Binary files /dev/null and b/mobile/openapi/test/user_entity_test.dart differ diff --git a/server/apps/immich/src/api-v1/album/album.module.ts b/server/apps/immich/src/api-v1/album/album.module.ts index ee589dca25..4df3835ffc 100644 --- a/server/apps/immich/src/api-v1/album/album.module.ts +++ b/server/apps/immich/src/api-v1/album/album.module.ts @@ -1,32 +1,29 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { AlbumService } from './album.service'; import { AlbumController } from './album.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AssetEntity } from '@app/database/entities/asset.entity'; -import { UserEntity } from '@app/database/entities/user.entity'; import { AlbumEntity } from '../../../../../libs/database/src/entities/album.entity'; import { AssetAlbumEntity } from '@app/database/entities/asset-album.entity'; import { UserAlbumEntity } from '@app/database/entities/user-album.entity'; import { AlbumRepository, ALBUM_REPOSITORY } from './album-repository'; -import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository'; import { DownloadModule } from '../../modules/download/download.module'; +import { AssetModule } from '../asset/asset.module'; +import { UserModule } from '../user/user.module'; + +const ALBUM_REPOSITORY_PROVIDER = { + provide: ALBUM_REPOSITORY, + useClass: AlbumRepository, +}; @Module({ imports: [ - TypeOrmModule.forFeature([AssetEntity, UserEntity, AlbumEntity, AssetAlbumEntity, UserAlbumEntity]), + TypeOrmModule.forFeature([AlbumEntity, AssetAlbumEntity, UserAlbumEntity]), DownloadModule, + UserModule, + forwardRef(() => AssetModule), ], controllers: [AlbumController], - providers: [ - AlbumService, - { - provide: ALBUM_REPOSITORY, - useClass: AlbumRepository, - }, - { - provide: ASSET_REPOSITORY, - useClass: AssetRepository, - }, - ], + providers: [AlbumService, ALBUM_REPOSITORY_PROVIDER], + exports: [ALBUM_REPOSITORY_PROVIDER], }) export class AlbumModule {} diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 2aa5fc6418..bd8d1ab6ce 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -1,7 +1,7 @@ import { SearchPropertiesDto } from './dto/search-properties.dto'; import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; -import { BadRequestException, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm/repository/Repository'; import { CreateAssetDto } from './dto/create-asset.dto'; @@ -14,6 +14,7 @@ import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; import { In } from 'typeorm/find-options/operator/In'; import { UpdateAssetDto } from './dto/update-asset.dto'; +import { ITagRepository, TAG_REPOSITORY } from '../tag/tag.repository'; export interface IAssetRepository { create( @@ -25,7 +26,7 @@ export interface IAssetRepository { checksum?: Buffer, livePhotoAssetEntity?: AssetEntity, ): Promise; - update(asset: AssetEntity, dto: UpdateAssetDto): Promise; + update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise; getAllByUserId(userId: string, skip?: number): Promise; getAllByDeviceId(userId: string, deviceId: string): Promise; getById(assetId: string): Promise; @@ -53,6 +54,8 @@ export class AssetRepository implements IAssetRepository { constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, + + @Inject(TAG_REPOSITORY) private _tagRepository: ITagRepository, ) {} async getAssetWithNoSmartInfo(): Promise { @@ -222,7 +225,7 @@ export class AssetRepository implements IAssetRepository { where: { id: assetId, }, - relations: ['exifInfo'], + relations: ['exifInfo', 'tags'], }); } @@ -237,9 +240,9 @@ export class AssetRepository implements IAssetRepository { .andWhere('asset.resizePath is not NULL') .andWhere('asset.isVisible = true') .leftJoinAndSelect('asset.exifInfo', 'exifInfo') + .leftJoinAndSelect('asset.tags', 'tags') .skip(skip || 0) .orderBy('asset.createdAt', 'DESC'); - return await query.getMany(); } @@ -286,9 +289,14 @@ export class AssetRepository implements IAssetRepository { /** * Update asset */ - async update(asset: AssetEntity, dto: UpdateAssetDto): Promise { + async update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise { asset.isFavorite = dto.isFavorite ?? asset.isFavorite; + if (dto.tagIds) { + const tags = await this._tagRepository.getByIds(userId, dto.tagIds); + asset.tags = tags; + } + return await this.assetRepository.save(asset); } @@ -347,10 +355,10 @@ export class AssetRepository implements IAssetRepository { async countByIdAndUser(assetId: string, userId: string): Promise { return await this.assetRepository.count({ - where: { - id: assetId, - userId - } + where: { + id: assetId, + userId, + }, }); } } 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 83befba8b3..7148376bfb 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -216,14 +216,14 @@ export class AssetController { /** * Update an asset */ - @Put('/assetById/:assetId') - async updateAssetById( + @Put('/:assetId') + async updateAsset( @GetAuthUser() authUser: AuthUserDto, @Param('assetId') assetId: string, - @Body() dto: UpdateAssetDto, + @Body(ValidationPipe) dto: UpdateAssetDto, ): Promise { await this.assetService.checkAssetsAccess(authUser, [assetId], true); - return await this.assetService.updateAssetById(assetId, dto); + return await this.assetService.updateAsset(authUser, assetId, dto); } @Delete('/') diff --git a/server/apps/immich/src/api-v1/asset/asset.module.ts b/server/apps/immich/src/api-v1/asset/asset.module.ts index d6d3b98196..c8501426e2 100644 --- a/server/apps/immich/src/api-v1/asset/asset.module.ts +++ b/server/apps/immich/src/api-v1/asset/asset.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { AssetService } from './asset.service'; import { AssetController } from './asset.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; @@ -10,18 +10,25 @@ import { CommunicationModule } from '../communication/communication.module'; import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; import { AssetRepository, ASSET_REPOSITORY } from './asset-repository'; import { DownloadModule } from '../../modules/download/download.module'; -import { ALBUM_REPOSITORY, AlbumRepository } from '../album/album-repository'; -import { AlbumEntity } from '@app/database/entities/album.entity'; -import { UserAlbumEntity } from '@app/database/entities/user-album.entity'; -import { UserEntity } from '@app/database/entities/user.entity'; -import { AssetAlbumEntity } from '@app/database/entities/asset-album.entity'; +import { TagModule } from '../tag/tag.module'; +import { AlbumModule } from '../album/album.module'; +import { UserModule } from '../user/user.module'; + +const ASSET_REPOSITORY_PROVIDER = { + provide: ASSET_REPOSITORY, + useClass: AssetRepository, +}; @Module({ imports: [ + TypeOrmModule.forFeature([AssetEntity]), CommunicationModule, BackgroundTaskModule, DownloadModule, - TypeOrmModule.forFeature([AssetEntity, AlbumEntity, UserAlbumEntity, UserEntity, AssetAlbumEntity]), + UserModule, + AlbumModule, + TagModule, + forwardRef(() => AlbumModule), BullModule.registerQueue({ name: QueueNameEnum.ASSET_UPLOADED, defaultJobOptions: { @@ -40,18 +47,7 @@ import { AssetAlbumEntity } from '@app/database/entities/asset-album.entity'; }), ], controllers: [AssetController], - providers: [ - AssetService, - BackgroundTaskService, - { - provide: ASSET_REPOSITORY, - useClass: AssetRepository, - }, - { - provide: ALBUM_REPOSITORY, - useClass: AlbumRepository, - }, - ], - exports: [AssetService], + providers: [AssetService, BackgroundTaskService, ASSET_REPOSITORY_PROVIDER], + exports: [ASSET_REPOSITORY_PROVIDER], }) export class AssetModule {} 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 6c9d72a0d5..577e08525c 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -231,13 +231,13 @@ export class AssetService { return mapAsset(asset); } - public async updateAssetById(assetId: string, dto: UpdateAssetDto): Promise { + public async updateAsset(authUser: AuthUserDto, assetId: string, dto: UpdateAssetDto): Promise { const asset = await this._assetRepository.getById(assetId); if (!asset) { throw new BadRequestException('Asset not found'); } - const updatedAsset = await this._assetRepository.update(asset, dto); + const updatedAsset = await this._assetRepository.update(authUser.id, asset, dto); return mapAsset(updatedAsset); } diff --git a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts b/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts index 38122e3262..9c18c50a76 100644 --- a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts @@ -1,6 +1,24 @@ -import { IsBoolean } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class UpdateAssetDto { + @IsOptional() @IsBoolean() - isFavorite!: boolean; + isFavorite?: boolean; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + @IsNotEmpty({ each: true }) + @ApiProperty({ + isArray: true, + type: String, + title: 'Array of tag IDs to add to the asset', + example: [ + 'bf973405-3f2a-48d2-a687-2ed4167164be', + 'dd41870b-5d00-46d2-924e-1d8489a0aa0f', + 'fad77c3f-deef-4e7e-9608-14c1aa4e559a', + ], + }) + tagIds?: string[]; } diff --git a/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts index 09d61aae51..840d47c274 100644 --- a/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts +++ b/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts @@ -1,5 +1,6 @@ import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; import { ApiProperty } from '@nestjs/swagger'; +import { mapTag, TagResponseDto } from '../../tag/response-dto/tag-response.dto'; import { ExifResponseDto, mapExif } from './exif-response.dto'; import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto'; @@ -23,6 +24,7 @@ export class AssetResponseDto { exifInfo?: ExifResponseDto; smartInfo?: SmartInfoResponseDto; livePhotoVideoId?: string | null; + tags!: TagResponseDto[]; } export function mapAsset(entity: AssetEntity): AssetResponseDto { @@ -44,5 +46,6 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, livePhotoVideoId: entity.livePhotoVideoId, + tags: entity.tags?.map(mapTag), }; } diff --git a/server/apps/immich/src/api-v1/job/job.module.ts b/server/apps/immich/src/api-v1/job/job.module.ts index 2cb5beb7bf..09ac3885ad 100644 --- a/server/apps/immich/src/api-v1/job/job.module.ts +++ b/server/apps/immich/src/api-v1/job/job.module.ts @@ -5,18 +5,21 @@ import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; import { JwtModule } from '@nestjs/jwt'; import { jwtConfig } from '../../config/jwt.config'; -import { UserEntity } from '@app/database/entities/user.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; import { BullModule } from '@nestjs/bull'; import { QueueNameEnum } from '@app/job'; -import { AssetEntity } from '@app/database/entities/asset.entity'; import { ExifEntity } from '@app/database/entities/exif.entity'; -import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository'; +import { TagModule } from '../tag/tag.module'; +import { AssetModule } from '../asset/asset.module'; +import { UserModule } from '../user/user.module'; @Module({ imports: [ - TypeOrmModule.forFeature([UserEntity, AssetEntity, ExifEntity]), + TypeOrmModule.forFeature([ExifEntity]), ImmichJwtModule, + TagModule, + AssetModule, + UserModule, JwtModule.register(jwtConfig), BullModule.registerQueue( { @@ -70,13 +73,6 @@ import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository'; ), ], controllers: [JobController], - providers: [ - JobService, - ImmichJwtService, - { - provide: ASSET_REPOSITORY, - useClass: AssetRepository, - }, - ], + providers: [JobService, ImmichJwtService], }) export class JobModule {} diff --git a/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts b/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts new file mode 100644 index 0000000000..3c3859291d --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts @@ -0,0 +1,14 @@ +import { TagType } from '@app/database/entities/tag.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; + +export class CreateTagDto { + @IsString() + @IsNotEmpty() + name!: string; + + @IsEnum(TagType) + @IsNotEmpty() + @ApiProperty({ enumName: 'TagTypeEnum', enum: TagType }) + type!: TagType; +} diff --git a/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts b/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts new file mode 100644 index 0000000000..64632f1f6d --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts @@ -0,0 +1,11 @@ +import { IsOptional, IsString } from 'class-validator'; + +export class UpdateTagDto { + @IsString() + @IsOptional() + name?: string; + + @IsString() + @IsOptional() + renameTagId?: string; +} diff --git a/server/apps/immich/src/api-v1/tag/response-dto/tag-response.dto.ts b/server/apps/immich/src/api-v1/tag/response-dto/tag-response.dto.ts new file mode 100644 index 0000000000..973b47db6c --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/response-dto/tag-response.dto.ts @@ -0,0 +1,20 @@ +import { TagEntity, TagType } from '@app/database/entities/tag.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +export class TagResponseDto { + @ApiProperty() + id!: string; + + @ApiProperty({ enumName: 'TagTypeEnum', enum: TagType }) + type!: string; + + name!: string; +} + +export function mapTag(entity: TagEntity): TagResponseDto { + return { + id: entity.id, + type: entity.type, + name: entity.name, + }; +} diff --git a/server/apps/immich/src/api-v1/tag/tag.controller.ts b/server/apps/immich/src/api-v1/tag/tag.controller.ts new file mode 100644 index 0000000000..fb2d79f82d --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.controller.ts @@ -0,0 +1,44 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, ValidationPipe } from '@nestjs/common'; +import { TagService } from './tag.service'; +import { CreateTagDto } from './dto/create-tag.dto'; +import { UpdateTagDto } from './dto/update-tag.dto'; +import { Authenticated } from '../../decorators/authenticated.decorator'; +import { ApiTags } from '@nestjs/swagger'; +import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; +import { TagEntity } from '@app/database/entities/tag.entity'; + +@Authenticated() +@ApiTags('Tag') +@Controller('tag') +export class TagController { + constructor(private readonly tagService: TagService) {} + + @Post() + create(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createTagDto: CreateTagDto): Promise { + return this.tagService.create(authUser, createTagDto); + } + + @Get() + findAll(@GetAuthUser() authUser: AuthUserDto) { + return this.tagService.findAll(authUser); + } + + @Get(':id') + findOne(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string) { + return this.tagService.findOne(authUser, id); + } + + @Patch(':id') + update( + @GetAuthUser() authUser: AuthUserDto, + @Param('id') id: string, + @Body(ValidationPipe) updateTagDto: UpdateTagDto, + ) { + return this.tagService.update(authUser, id, updateTagDto); + } + + @Delete(':id') + delete(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string) { + return this.tagService.remove(authUser, id); + } +} diff --git a/server/apps/immich/src/api-v1/tag/tag.module.ts b/server/apps/immich/src/api-v1/tag/tag.module.ts new file mode 100644 index 0000000000..5a9db11e78 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TagService } from './tag.service'; +import { TagController } from './tag.controller'; +import { TagEntity } from '@app/database/entities/tag.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TagRepository, TAG_REPOSITORY } from './tag.repository'; + +const TAG_REPOSITORY_PROVIDER = { + provide: TAG_REPOSITORY, + useClass: TagRepository, +}; +@Module({ + imports: [TypeOrmModule.forFeature([TagEntity])], + controllers: [TagController], + providers: [TagService, TAG_REPOSITORY_PROVIDER], + exports: [TAG_REPOSITORY_PROVIDER], +}) +export class TagModule {} diff --git a/server/apps/immich/src/api-v1/tag/tag.repository.ts b/server/apps/immich/src/api-v1/tag/tag.repository.ts new file mode 100644 index 0000000000..a1a383aecd --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.repository.ts @@ -0,0 +1,61 @@ +import { TagEntity, TagType } from '@app/database/entities/tag.entity'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; +import { UpdateTagDto } from './dto/update-tag.dto'; + +export interface ITagRepository { + create(userId: string, tagType: TagType, tagName: string): Promise; + getByIds(userId: string, tagIds: string[]): Promise; + getById(tagId: string, userId: string): Promise; + getByUserId(userId: string): Promise; + update(tag: TagEntity, updateTagDto: UpdateTagDto): Promise; + remove(tag: TagEntity): Promise; +} + +export const TAG_REPOSITORY = 'TAG_REPOSITORY'; + +@Injectable() +export class TagRepository implements ITagRepository { + constructor( + @InjectRepository(TagEntity) + private tagRepository: Repository, + ) {} + + async create(userId: string, tagType: TagType, tagName: string): Promise { + const tag = new TagEntity(); + tag.name = tagName; + tag.type = tagType; + tag.userId = userId; + + return this.tagRepository.save(tag); + } + + async getById(tagId: string, userId: string): Promise { + return await this.tagRepository.findOne({ where: { id: tagId, userId }, relations: ['user'] }); + } + + async getByIds(userId: string, tagIds: string[]): Promise { + return await this.tagRepository.find({ + where: { id: In(tagIds), userId }, + relations: { + user: true, + }, + }); + } + + async getByUserId(userId: string): Promise { + return await this.tagRepository.find({ where: { userId } }); + } + + async update(tag: TagEntity, updateTagDto: UpdateTagDto): Promise { + tag.name = updateTagDto.name ?? tag.name; + tag.renameTagId = updateTagDto.renameTagId ?? tag.renameTagId; + + return this.tagRepository.save(tag); + } + + async remove(tag: TagEntity): Promise { + return await this.tagRepository.remove(tag); + } +} diff --git a/server/apps/immich/src/api-v1/tag/tag.service.spec.ts b/server/apps/immich/src/api-v1/tag/tag.service.spec.ts new file mode 100644 index 0000000000..3ab1d3034a --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.service.spec.ts @@ -0,0 +1,91 @@ +import { TagEntity, TagType } from '@app/database/entities/tag.entity'; +import { UserEntity } from '@app/database/entities/user.entity'; +import { AuthUserDto } from '../../decorators/auth-user.decorator'; +import { ITagRepository } from './tag.repository'; +import { TagService } from './tag.service'; + +describe('TagService', () => { + let sut: TagService; + let tagRepositoryMock: jest.Mocked; + + const user1AuthUser: AuthUserDto = Object.freeze({ + id: '1111', + email: 'testuser@email.com', + }); + + const user1: UserEntity = Object.freeze({ + id: '1111', + firstName: 'Alex', + lastName: 'Tran', + isAdmin: true, + email: 'testuser@email.com', + profileImagePath: '', + shouldChangePassword: true, + createdAt: '2022-12-02T19:29:23.603Z', + deletedAt: undefined, + tags: [], + oauthId: 'oauth-id-1', + }); + + // const user2: UserEntity = Object.freeze({ + // id: '2222', + // firstName: 'Alex', + // lastName: 'Tran', + // isAdmin: true, + // email: 'testuser2@email.com', + // profileImagePath: '', + // shouldChangePassword: true, + // createdAt: '2022-12-02T19:29:23.603Z', + // deletedAt: undefined, + // tags: [], + // oauthId: 'oauth-id-2', + // }); + + const user1Tag1: TagEntity = Object.freeze({ + name: 'user 1 tag 1', + type: TagType.CUSTOM, + userId: user1.id, + user: user1, + renameTagId: '', + id: 'user1-tag-1-id', + assets: [], + }); + + // const user1Tag2: TagEntity = Object.freeze({ + // name: 'user 1 tag 2', + // type: TagType.CUSTOM, + // userId: user1.id, + // user: user1, + // renameTagId: '', + // id: 'user1-tag-2-id', + // assets: [], + // }); + + beforeAll(() => { + tagRepositoryMock = { + create: jest.fn(), + getByIds: jest.fn(), + getById: jest.fn(), + getByUserId: jest.fn(), + remove: jest.fn(), + update: jest.fn(), + }; + + sut = new TagService(tagRepositoryMock); + }); + + it('creates tag', async () => { + const createTagDto = { + name: 'user 1 tag 1', + type: TagType.CUSTOM, + }; + + tagRepositoryMock.create.mockResolvedValue(user1Tag1); + + const result = await sut.create(user1AuthUser, createTagDto); + + expect(result.userId).toEqual(user1AuthUser.id); + expect(result.name).toEqual(createTagDto.name); + expect(result.type).toEqual(createTagDto.type); + }); +}); diff --git a/server/apps/immich/src/api-v1/tag/tag.service.ts b/server/apps/immich/src/api-v1/tag/tag.service.ts new file mode 100644 index 0000000000..e14e978801 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.service.ts @@ -0,0 +1,48 @@ +import { TagEntity } from '@app/database/entities/tag.entity'; +import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; +import { AuthUserDto } from '../../decorators/auth-user.decorator'; +import { CreateTagDto } from './dto/create-tag.dto'; +import { UpdateTagDto } from './dto/update-tag.dto'; +import { ITagRepository, TAG_REPOSITORY } from './tag.repository'; + +@Injectable() +export class TagService { + readonly logger = new Logger(TagService.name); + + constructor(@Inject(TAG_REPOSITORY) private _tagRepository: ITagRepository) {} + + async create(authUser: AuthUserDto, createTagDto: CreateTagDto) { + try { + return await this._tagRepository.create(authUser.id, createTagDto.type, createTagDto.name); + } catch (e: any) { + this.logger.error(e, e.stack); + throw new BadRequestException(`Failed to create tag: ${e.detail}`); + } + } + + async findAll(authUser: AuthUserDto) { + return await this._tagRepository.getByUserId(authUser.id); + } + + async findOne(authUser: AuthUserDto, id: string): Promise { + const tag = await this._tagRepository.getById(id, authUser.id); + + if (!tag) { + throw new BadRequestException('Tag not found'); + } + + return tag; + } + + async update(authUser: AuthUserDto, id: string, updateTagDto: UpdateTagDto) { + const tag = await this.findOne(authUser, id); + + return this._tagRepository.update(tag, updateTagDto); + } + + async remove(authUser: AuthUserDto, id: string) { + const tag = await this.findOne(authUser, id); + + return this._tagRepository.remove(tag); + } +} diff --git a/server/apps/immich/src/api-v1/user/user.service.spec.ts b/server/apps/immich/src/api-v1/user/user.service.spec.ts index 49d82938ab..126d925607 100644 --- a/server/apps/immich/src/api-v1/user/user.service.spec.ts +++ b/server/apps/immich/src/api-v1/user/user.service.spec.ts @@ -31,6 +31,7 @@ describe('UserService', () => { shouldChangePassword: false, profileImagePath: '', createdAt: '2021-01-01', + tags: [], }); const immichUser: UserEntity = Object.freeze({ @@ -45,6 +46,7 @@ describe('UserService', () => { shouldChangePassword: false, profileImagePath: '', createdAt: '2021-01-01', + tags: [], }); const updatedImmichUser: UserEntity = Object.freeze({ @@ -59,6 +61,7 @@ describe('UserService', () => { shouldChangePassword: true, profileImagePath: '', createdAt: '2021-01-01', + tags: [], }); beforeAll(() => { diff --git a/server/apps/immich/src/app.module.ts b/server/apps/immich/src/app.module.ts index 487847bbc5..25ed0ab358 100644 --- a/server/apps/immich/src/app.module.ts +++ b/server/apps/immich/src/app.module.ts @@ -18,6 +18,7 @@ import { DatabaseModule } from '@app/database'; import { JobModule } from './api-v1/job/job.module'; import { SystemConfigModule } from './api-v1/system-config/system-config.module'; import { OAuthModule } from './api-v1/oauth/oauth.module'; +import { TagModule } from './api-v1/tag/tag.module'; @Module({ imports: [ @@ -63,6 +64,8 @@ import { OAuthModule } from './api-v1/oauth/oauth.module'; JobModule, SystemConfigModule, + + TagModule, ], controllers: [AppController], providers: [], diff --git a/server/apps/immich/src/main.ts b/server/apps/immich/src/main.ts index cd1cd22d23..3e5e3fb2ae 100644 --- a/server/apps/immich/src/main.ts +++ b/server/apps/immich/src/main.ts @@ -55,7 +55,7 @@ async function bootstrap() { if (process.env.NODE_ENV == 'development') { // Generate API Documentation only in development mode const outputPath = path.resolve(process.cwd(), 'immich-openapi-specs.json'); - writeFileSync(outputPath, JSON.stringify(apiDocument), { encoding: 'utf8' }); + writeFileSync(outputPath, JSON.stringify(apiDocument, null, 2), { encoding: 'utf8' }); Logger.log( `Running Immich Server in DEVELOPMENT environment - version ${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`, 'ImmichServer', diff --git a/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts b/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts index 9936f9de35..7edf313997 100644 --- a/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts +++ b/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts @@ -56,6 +56,7 @@ describe('ImmichJwtService', () => { profileImagePath: '', shouldChangePassword: false, createdAt: 'today', + tags: [], }; const dto: LoginResponseDto = { diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index bee972c82f..7ff2daa56f 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1 +1,3975 @@ -{"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/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/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/{userId}":{"delete":{"operationId":"deleteUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/{userId}/restore":{"post":{"operationId":"restoreUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","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/{assetId}":{"get":{"operationId":"downloadFile","parameters":[{"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"}},{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download-library":{"get":{"operationId":"downloadLibrary","parameters":[{"name":"skip","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file/{assetId}":{"get":{"operationId":"serveFile","parameters":[{"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"}},{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"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"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"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/count-by-time-bucket":{"post":{"operationId":"getAssetCountByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeBucketResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-user-id":{"get":{"operationId":"getAssetCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByUserIdResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[{"name":"if-none-match","in":"header","description":"ETag of data already cached on the client","required":false,"schema":{"type":"string"}}],"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":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"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":[]}]},"put":{"operationId":"updateAssetById","summary":"","description":"Update an asset","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetDto"}}}},"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":[]}]}},"/asset/exist":{"post":{"operationId":"checkExistingAssets","summary":"","description":"Checks if multiple assets exist on the server and returns all existing - used by background backup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsResponseDto"}}}}},"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":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/oauth/config":{"post":{"operationId":"generateConfig","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigResponseDto"}}}}},"tags":["OAuth"]}},"/oauth/callback":{"post":{"operationId":"callback","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthCallbackDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["OAuth"]}},"/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"]}},"/server-info/stats":{"get":{"operationId":"getStats","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerStatsResponseDto"}}}}},"tags":["Server Info"]}},"/album/count-by-user-id":{"get":{"operationId":"getAlbumCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumCountResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/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"}},{"name":"assetId","required":false,"in":"query","description":"Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums","schema":{"type":"string"}}],"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/AddAssetsResponseDto"}}}}},"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":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"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":[]}]}},"/album/{albumId}/download":{"get":{"operationId":"downloadArchive","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"skip","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/jobs":{"get":{"operationId":"getAllJobsStatus","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllJobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/jobs/{jobId}":{"get":{"operationId":"getJobStatus","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]},"put":{"operationId":"sendJobCommand","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCommandDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"number"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/system-config":{"get":{"operationId":"getConfig","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemConfigResponseDto"}}}}},"tags":["System Config"],"security":[{"bearer":[]}]},"put":{"operationId":"updateConfig","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSystemConfigDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemConfigResponseDto"}}}}},"tags":["System Config"],"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"},"deletedAt":{"format":"date-time","type":"string"}},"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":"integer"}},"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"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"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"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"integer","nullable":true,"default":null,"format":"int64"},"fileSizeInByte":{"type":"integer","nullable":true,"default":null,"format":"int64"},"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},"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}}},"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":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"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"},"livePhotoVideoId":{"type":"string","nullable":true}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeBucketDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeBucket":{"type":"object","properties":{"timeBucket":{"type":"string"},"count":{"type":"integer"}},"required":["timeBucket","count"]},"AssetCountByTimeBucketResponseDto":{"type":"object","properties":{"totalCount":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeBucket"}}},"required":["totalCount","buckets"]},"AssetCountByUserIdResponseDto":{"type":"object","properties":{"audio":{"type":"integer","default":0},"photos":{"type":"integer","default":0},"videos":{"type":"integer","default":0},"other":{"type":"integer","default":0},"total":{"type":"integer","default":0}},"required":["audio","photos","videos","other","total"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"UpdateAssetDto":{"type":"object","properties":{"isFavorite":{"type":"boolean"}},"required":["isFavorite"]},"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"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"CheckExistingAssetsDto":{"type":"object","properties":{"deviceAssetIds":{"type":"array","items":{"type":"string"}},"deviceId":{"type":"string"}},"required":["deviceAssetIds","deviceId"]},"CheckExistingAssetsResponseDto":{"type":"object","properties":{"existingIds":{"type":"array","items":{"type":"string"}}},"required":["existingIds"]},"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":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true},"redirectUri":{"type":"string","readOnly":true}},"required":["successful","redirectUri"]},"OAuthConfigDto":{"type":"object","properties":{"redirectUri":{"type":"string"}},"required":["redirectUri"]},"OAuthConfigResponseDto":{"type":"object","properties":{"enabled":{"type":"boolean","readOnly":true},"url":{"type":"string","readOnly":true},"buttonText":{"type":"string","readOnly":true}},"required":["enabled"]},"OAuthCallbackDto":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer","format":"int64"},"diskUseRaw":{"type":"integer","format":"int64"},"diskAvailableRaw":{"type":"integer","format":"int64"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"UsageByUserDto":{"type":"object","properties":{"userId":{"type":"string"},"videos":{"type":"integer"},"photos":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"}},"required":["userId","videos","photos","usageRaw","usage"]},"ServerStatsResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"},"objects":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"},"usageByUser":{"title":"Array of usage for each user","example":[{"photos":1,"videos":1,"diskUsageRaw":1}],"type":"array","items":{"$ref":"#/components/schemas/UsageByUserDto"}}},"required":["photos","videos","objects","usageRaw","usage","usageByUser"]},"AlbumCountResponseDto":{"type":"object","properties":{"owned":{"type":"integer"},"shared":{"type":"integer"},"sharing":{"type":"integer"}},"required":["owned","shared","sharing"]},"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":{"assetCount":{"type":"integer"},"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":["assetCount","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"]},"AddAssetsResponseDto":{"type":"object","properties":{"successfullyAdded":{"type":"integer"},"alreadyInAlbum":{"type":"array","items":{"type":"string"}},"album":{"$ref":"#/components/schemas/AlbumResponseDto"}},"required":["successfullyAdded","alreadyInAlbum"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}},"JobCounts":{"type":"object","properties":{"active":{"type":"integer"},"completed":{"type":"integer"},"failed":{"type":"integer"},"delayed":{"type":"integer"},"waiting":{"type":"integer"}},"required":["active","completed","failed","delayed","waiting"]},"AllJobStatusResponseDto":{"type":"object","properties":{"thumbnailGenerationQueueCount":{"$ref":"#/components/schemas/JobCounts"},"metadataExtractionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"videoConversionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"machineLearningQueueCount":{"$ref":"#/components/schemas/JobCounts"},"isThumbnailGenerationActive":{"type":"boolean"},"isMetadataExtractionActive":{"type":"boolean"},"isVideoConversionActive":{"type":"boolean"},"isMachineLearningActive":{"type":"boolean"}},"required":["thumbnailGenerationQueueCount","metadataExtractionQueueCount","videoConversionQueueCount","machineLearningQueueCount","isThumbnailGenerationActive","isMetadataExtractionActive","isVideoConversionActive","isMachineLearningActive"]},"JobId":{"type":"string","enum":["thumbnail-generation","metadata-extraction","video-conversion","machine-learning"]},"JobStatusResponseDto":{"type":"object","properties":{"isActive":{"type":"boolean"},"queueCount":{"type":"object"}},"required":["isActive","queueCount"]},"JobCommand":{"type":"string","enum":["start","stop"]},"JobCommandDto":{"type":"object","properties":{"command":{"$ref":"#/components/schemas/JobCommand"}},"required":["command"]},"SystemConfigKey":{"type":"string","enum":["ffmpeg_crf","ffmpeg_preset","ffmpeg_target_video_codec","ffmpeg_target_audio_codec","ffmpeg_target_scaling"]},"SystemConfigResponseItem":{"type":"object","properties":{"name":{"type":"string"},"key":{"$ref":"#/components/schemas/SystemConfigKey"},"value":{"type":"string"},"defaultValue":{"type":"string"}},"required":["name","key","value","defaultValue"]},"SystemConfigResponseDto":{"type":"object","properties":{"config":{"type":"array","items":{"$ref":"#/components/schemas/SystemConfigResponseItem"}}},"required":["config"]},"UpdateSystemConfigDto":{"type":"object","properties":{}}}}} \ 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/info/{userId}": { + "get": { + "operationId": "getUserById", + "parameters": [ + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ] + } + }, + "/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/{userId}": { + "delete": { + "operationId": "deleteUser", + "parameters": [ + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/user/{userId}/restore": { + "post": { + "operationId": "restoreUser", + "parameters": [ + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/user/profile-image": { + "post": { + "operationId": "createProfileImage", + "parameters": [], + "requestBody": { + "required": true, + "description": "A new avatar for the user", + "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/{assetId}": { + "get": { + "operationId": "downloadFile", + "parameters": [ + { + "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" + } + }, + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/download-library": { + "get": { + "operationId": "downloadLibrary", + "parameters": [ + { + "name": "skip", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/file/{assetId}": { + "get": { + "operationId": "serveFile", + "parameters": [ + { + "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" + } + }, + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "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" + } + }, + { + "name": "format", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/ThumbnailFormat" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/curated-objects": { + "get": { + "operationId": "getCuratedObjects", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CuratedObjectsResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/curated-locations": { + "get": { + "operationId": "getCuratedLocations", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CuratedLocationsResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/search-terms": { + "get": { + "operationId": "getAssetSearchTerms", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "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/count-by-time-bucket": { + "post": { + "operationId": "getAssetCountByTimeBucket", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAssetCountByTimeBucketDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetCountByTimeBucketResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/count-by-user-id": { + "get": { + "operationId": "getAssetCountByUserId", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetCountByUserIdResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset": { + "get": { + "operationId": "getAllAssets", + "summary": "", + "description": "Get all AssetEntity belong to the user", + "parameters": [ + { + "name": "if-none-match", + "in": "header", + "description": "ETag of data already cached on the client", + "required": false, + "schema": { + "type": "string" + } + } + ], + "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": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeleteAssetResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/time-bucket": { + "post": { + "operationId": "getAssetByTimeBucket", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAssetByTimeBucketDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + } + } + } + }, + "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/{assetId}": { + "put": { + "operationId": "updateAsset", + "summary": "", + "description": "Update an asset", + "parameters": [ + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAssetDto" + } + } + } + }, + "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": [] + } + ] + } + }, + "/asset/exist": { + "post": { + "operationId": "checkExistingAssets", + "summary": "", + "description": "Checks if multiple assets exist on the server and returns all existing - used by background backup", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckExistingAssetsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckExistingAssetsResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album/count-by-user-id": { + "get": { + "operationId": "getAlbumCountByUserId", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumCountResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/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" + } + }, + { + "name": "assetId", + "required": false, + "in": "query", + "description": "Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums", + "schema": { + "type": "string" + } + } + ], + "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/AddAssetsResponseDto" + } + } + } + } + }, + "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": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + } + } + } + }, + "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": [] + } + ] + } + }, + "/album/{albumId}/download": { + "get": { + "operationId": "downloadArchive", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/tag": { + "post": { + "operationId": "create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTagDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + }, + "tags": [ + "Tag" + ] + }, + "get": { + "operationId": "findAll", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + } + }, + "tags": [ + "Tag" + ] + } + }, + "/tag/{id}": { + "get": { + "operationId": "findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + }, + "tags": [ + "Tag" + ] + }, + "patch": { + "operationId": "update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTagDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Tag" + ] + }, + "delete": { + "operationId": "delete", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + }, + "tags": [ + "Tag" + ] + } + }, + "/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": [] + } + ] + } + }, + "/auth/logout": { + "post": { + "operationId": "logout", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LogoutResponseDto" + } + } + } + } + }, + "tags": [ + "Authentication" + ] + } + }, + "/oauth/config": { + "post": { + "operationId": "generateConfig", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthConfigDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthConfigResponseDto" + } + } + } + } + }, + "tags": [ + "OAuth" + ] + } + }, + "/oauth/callback": { + "post": { + "operationId": "callback", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthCallbackDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResponseDto" + } + } + } + } + }, + "tags": [ + "OAuth" + ] + } + }, + "/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" + ] + } + }, + "/server-info/stats": { + "get": { + "operationId": "getStats", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerStatsResponseDto" + } + } + } + } + }, + "tags": [ + "Server Info" + ] + } + }, + "/jobs": { + "get": { + "operationId": "getAllJobsStatus", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllJobStatusResponseDto" + } + } + } + } + }, + "tags": [ + "Job" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/jobs/{jobId}": { + "get": { + "operationId": "getJobStatus", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "$ref": "#/components/schemas/JobId" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatusResponseDto" + } + } + } + } + }, + "tags": [ + "Job" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "operationId": "sendJobCommand", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "$ref": "#/components/schemas/JobId" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobCommandDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + } + }, + "tags": [ + "Job" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/system-config": { + "get": { + "operationId": "getConfig", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigResponseDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "operationId": "updateConfig", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSystemConfigDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigResponseDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "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" + }, + "deletedAt": { + "format": "date-time", + "type": "string" + } + }, + "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": "integer" + } + }, + "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" + ] + }, + "ThumbnailFormat": { + "type": "string", + "enum": [ + "JPEG", + "WEBP" + ] + }, + "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" + ] + }, + "AssetTypeEnum": { + "type": "string", + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ] + }, + "ExifResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "nullable": true, + "default": null, + "format": "int64" + }, + "fileSizeInByte": { + "type": "integer", + "nullable": true, + "default": null, + "format": "int64" + }, + "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 + }, + "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 + } + } + }, + "SmartInfoResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "tags": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + }, + "objects": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "TagTypeEnum": { + "type": "string", + "enum": [ + "OBJECT", + "FACE", + "CUSTOM" + ] + }, + "TagResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/TagTypeEnum" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "type", + "name" + ] + }, + "AssetResponseDto": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/AssetTypeEnum" + }, + "id": { + "type": "string" + }, + "deviceAssetId": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "deviceId": { + "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" + }, + "livePhotoVideoId": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagResponseDto" + } + } + }, + "required": [ + "type", + "id", + "deviceAssetId", + "ownerId", + "deviceId", + "originalPath", + "resizePath", + "createdAt", + "modifiedAt", + "isFavorite", + "mimeType", + "duration", + "webpPath", + "tags" + ] + }, + "TimeGroupEnum": { + "type": "string", + "enum": [ + "day", + "month" + ] + }, + "GetAssetCountByTimeBucketDto": { + "type": "object", + "properties": { + "timeGroup": { + "$ref": "#/components/schemas/TimeGroupEnum" + } + }, + "required": [ + "timeGroup" + ] + }, + "AssetCountByTimeBucket": { + "type": "object", + "properties": { + "timeBucket": { + "type": "string" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "timeBucket", + "count" + ] + }, + "AssetCountByTimeBucketResponseDto": { + "type": "object", + "properties": { + "totalCount": { + "type": "integer" + }, + "buckets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetCountByTimeBucket" + } + } + }, + "required": [ + "totalCount", + "buckets" + ] + }, + "AssetCountByUserIdResponseDto": { + "type": "object", + "properties": { + "audio": { + "type": "integer", + "default": 0 + }, + "photos": { + "type": "integer", + "default": 0 + }, + "videos": { + "type": "integer", + "default": 0 + }, + "other": { + "type": "integer", + "default": 0 + }, + "total": { + "type": "integer", + "default": 0 + } + }, + "required": [ + "audio", + "photos", + "videos", + "other", + "total" + ] + }, + "GetAssetByTimeBucketDto": { + "type": "object", + "properties": { + "timeBucket": { + "title": "Array of date time buckets", + "example": [ + "2015-06-01T00:00:00.000Z", + "2016-02-01T00:00:00.000Z", + "2016-03-01T00:00:00.000Z" + ], + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "timeBucket" + ] + }, + "UpdateAssetDto": { + "type": "object", + "properties": { + "tagIds": { + "title": "Array of tag IDs to add to the asset", + "example": [ + "bf973405-3f2a-48d2-a687-2ed4167164be", + "dd41870b-5d00-46d2-924e-1d8489a0aa0f", + "fad77c3f-deef-4e7e-9608-14c1aa4e559a" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "isFavorite": { + "type": "boolean" + } + } + }, + "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" + ] + }, + "DeleteAssetStatus": { + "type": "string", + "enum": [ + "SUCCESS", + "FAILED" + ] + }, + "DeleteAssetResponseDto": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/DeleteAssetStatus" + }, + "id": { + "type": "string" + } + }, + "required": [ + "status", + "id" + ] + }, + "CheckDuplicateAssetDto": { + "type": "object", + "properties": { + "deviceAssetId": { + "type": "string" + }, + "deviceId": { + "type": "string" + } + }, + "required": [ + "deviceAssetId", + "deviceId" + ] + }, + "CheckDuplicateAssetResponseDto": { + "type": "object", + "properties": { + "isExist": { + "type": "boolean" + }, + "id": { + "type": "string" + } + }, + "required": [ + "isExist" + ] + }, + "CheckExistingAssetsDto": { + "type": "object", + "properties": { + "deviceAssetIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "deviceId": { + "type": "string" + } + }, + "required": [ + "deviceAssetIds", + "deviceId" + ] + }, + "CheckExistingAssetsResponseDto": { + "type": "object", + "properties": { + "existingIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "existingIds" + ] + }, + "AlbumCountResponseDto": { + "type": "object", + "properties": { + "owned": { + "type": "integer" + }, + "shared": { + "type": "integer" + }, + "sharing": { + "type": "integer" + } + }, + "required": [ + "owned", + "shared", + "sharing" + ] + }, + "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": { + "assetCount": { + "type": "integer" + }, + "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": [ + "assetCount", + "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" + ] + }, + "AddAssetsResponseDto": { + "type": "object", + "properties": { + "successfullyAdded": { + "type": "integer" + }, + "alreadyInAlbum": { + "type": "array", + "items": { + "type": "string" + } + }, + "album": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + }, + "required": [ + "successfullyAdded", + "alreadyInAlbum" + ] + }, + "RemoveAssetsDto": { + "type": "object", + "properties": { + "assetIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "assetIds" + ] + }, + "UpdateAlbumDto": { + "type": "object", + "properties": { + "albumName": { + "type": "string" + }, + "albumThumbnailAssetId": { + "type": "string" + } + } + }, + "CreateTagDto": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/TagTypeEnum" + }, + "name": { + "type": "string" + } + }, + "required": [ + "type", + "name" + ] + }, + "ExifEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "assetId": { + "type": "string" + }, + "description": { + "type": "string", + "description": "General info" + }, + "exifImageWidth": { + "type": "number", + "nullable": true + }, + "exifImageHeight": { + "type": "number", + "nullable": true + }, + "fileSizeInByte": { + "type": "number", + "nullable": true + }, + "orientation": { + "type": "string", + "nullable": true + }, + "dateTimeOriginal": { + "format": "date-time", + "type": "string", + "nullable": true + }, + "modifyDate": { + "format": "date-time", + "type": "string", + "nullable": true + }, + "latitude": { + "type": "number", + "nullable": true + }, + "longitude": { + "type": "number", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "make": { + "type": "string", + "nullable": true, + "description": "Image info" + }, + "model": { + "type": "string", + "nullable": true + }, + "imageName": { + "type": "string", + "nullable": true + }, + "lensModel": { + "type": "string", + "nullable": true + }, + "fNumber": { + "type": "number", + "nullable": true + }, + "focalLength": { + "type": "number", + "nullable": true + }, + "iso": { + "type": "number", + "nullable": true + }, + "exposureTime": { + "type": "number", + "nullable": true + }, + "fps": { + "type": "number", + "nullable": true, + "description": "Video info" + }, + "asset": { + "$ref": "#/components/schemas/AssetEntity" + }, + "exifTextSearchableColumn": { + "type": "string" + } + }, + "required": [ + "id", + "assetId", + "description", + "exifImageWidth", + "exifImageHeight", + "fileSizeInByte", + "orientation", + "dateTimeOriginal", + "modifyDate", + "latitude", + "longitude", + "city", + "state", + "country", + "make", + "model", + "imageName", + "lensModel", + "fNumber", + "focalLength", + "iso", + "exposureTime", + "exifTextSearchableColumn" + ] + }, + "SmartInfoEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "assetId": { + "type": "string" + }, + "tags": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + }, + "objects": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + }, + "asset": { + "$ref": "#/components/schemas/AssetEntity" + } + }, + "required": [ + "id", + "assetId", + "tags", + "objects" + ] + }, + "UserEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "isAdmin": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "salt": { + "type": "string" + }, + "oauthId": { + "type": "string" + }, + "profileImagePath": { + "type": "string" + }, + "shouldChangePassword": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "deletedAt": { + "format": "date-time", + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagEntity" + } + } + }, + "required": [ + "id", + "firstName", + "lastName", + "isAdmin", + "email", + "oauthId", + "profileImagePath", + "shouldChangePassword", + "createdAt", + "tags" + ] + }, + "TagEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "OBJECT", + "FACE", + "CUSTOM" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "renameTagId": { + "type": "string" + }, + "assets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetEntity" + } + }, + "user": { + "$ref": "#/components/schemas/UserEntity" + } + }, + "required": [ + "id", + "type", + "name", + "userId", + "renameTagId", + "assets", + "user" + ] + }, + "AssetEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "deviceAssetId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "deviceId": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ] + }, + "originalPath": { + "type": "string" + }, + "resizePath": { + "type": "string", + "nullable": true + }, + "webpPath": { + "type": "string", + "nullable": true + }, + "encodedVideoPath": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "modifiedAt": { + "type": "string" + }, + "isFavorite": { + "type": "boolean" + }, + "mimeType": { + "type": "string", + "nullable": true + }, + "checksum": { + "type": "object", + "nullable": true + }, + "duration": { + "type": "string", + "nullable": true + }, + "isVisible": { + "type": "boolean" + }, + "livePhotoVideoId": { + "type": "string", + "nullable": true + }, + "exifInfo": { + "$ref": "#/components/schemas/ExifEntity" + }, + "smartInfo": { + "$ref": "#/components/schemas/SmartInfoEntity" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagEntity" + } + } + }, + "required": [ + "id", + "deviceAssetId", + "userId", + "deviceId", + "type", + "originalPath", + "resizePath", + "webpPath", + "encodedVideoPath", + "createdAt", + "modifiedAt", + "isFavorite", + "mimeType", + "duration", + "isVisible", + "livePhotoVideoId", + "tags" + ] + }, + "UpdateTagDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "renameTagId": { + "type": "string" + } + } + }, + "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": { + "authStatus": { + "type": "boolean" + } + }, + "required": [ + "authStatus" + ] + }, + "LogoutResponseDto": { + "type": "object", + "properties": { + "successful": { + "type": "boolean", + "readOnly": true + }, + "redirectUri": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "successful", + "redirectUri" + ] + }, + "OAuthConfigDto": { + "type": "object", + "properties": { + "redirectUri": { + "type": "string" + } + }, + "required": [ + "redirectUri" + ] + }, + "OAuthConfigResponseDto": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "readOnly": true + }, + "url": { + "type": "string", + "readOnly": true + }, + "buttonText": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "enabled" + ] + }, + "OAuthCallbackDto": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "DeviceTypeEnum": { + "type": "string", + "enum": [ + "IOS", + "ANDROID", + "WEB" + ] + }, + "CreateDeviceInfoDto": { + "type": "object", + "properties": { + "deviceType": { + "$ref": "#/components/schemas/DeviceTypeEnum" + }, + "deviceId": { + "type": "string" + }, + "isAutoBackup": { + "type": "boolean" + } + }, + "required": [ + "deviceType", + "deviceId" + ] + }, + "DeviceInfoResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "deviceType": { + "$ref": "#/components/schemas/DeviceTypeEnum" + }, + "userId": { + "type": "string" + }, + "deviceId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "isAutoBackup": { + "type": "boolean" + } + }, + "required": [ + "id", + "deviceType", + "userId", + "deviceId", + "createdAt", + "isAutoBackup" + ] + }, + "UpdateDeviceInfoDto": { + "type": "object", + "properties": { + "deviceType": { + "$ref": "#/components/schemas/DeviceTypeEnum" + }, + "deviceId": { + "type": "string" + }, + "isAutoBackup": { + "type": "boolean" + } + }, + "required": [ + "deviceType", + "deviceId" + ] + }, + "ServerInfoResponseDto": { + "type": "object", + "properties": { + "diskSizeRaw": { + "type": "integer", + "format": "int64" + }, + "diskUseRaw": { + "type": "integer", + "format": "int64" + }, + "diskAvailableRaw": { + "type": "integer", + "format": "int64" + }, + "diskUsagePercentage": { + "type": "number", + "format": "float" + }, + "diskSize": { + "type": "string" + }, + "diskUse": { + "type": "string" + }, + "diskAvailable": { + "type": "string" + } + }, + "required": [ + "diskSizeRaw", + "diskUseRaw", + "diskAvailableRaw", + "diskUsagePercentage", + "diskSize", + "diskUse", + "diskAvailable" + ] + }, + "ServerPingResponse": { + "type": "object", + "properties": { + "res": { + "type": "string", + "readOnly": true, + "example": "pong" + } + }, + "required": [ + "res" + ] + }, + "ServerVersionReponseDto": { + "type": "object", + "properties": { + "major": { + "type": "integer" + }, + "minor": { + "type": "integer" + }, + "patch": { + "type": "integer" + }, + "build": { + "type": "integer" + } + }, + "required": [ + "major", + "minor", + "patch", + "build" + ] + }, + "UsageByUserDto": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "videos": { + "type": "integer" + }, + "photos": { + "type": "integer" + }, + "usageRaw": { + "type": "integer", + "format": "int64" + }, + "usage": { + "type": "string" + } + }, + "required": [ + "userId", + "videos", + "photos", + "usageRaw", + "usage" + ] + }, + "ServerStatsResponseDto": { + "type": "object", + "properties": { + "photos": { + "type": "integer" + }, + "videos": { + "type": "integer" + }, + "objects": { + "type": "integer" + }, + "usageRaw": { + "type": "integer", + "format": "int64" + }, + "usage": { + "type": "string" + }, + "usageByUser": { + "title": "Array of usage for each user", + "example": [ + { + "photos": 1, + "videos": 1, + "diskUsageRaw": 1 + } + ], + "type": "array", + "items": { + "$ref": "#/components/schemas/UsageByUserDto" + } + } + }, + "required": [ + "photos", + "videos", + "objects", + "usageRaw", + "usage", + "usageByUser" + ] + }, + "JobCounts": { + "type": "object", + "properties": { + "active": { + "type": "integer" + }, + "completed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "delayed": { + "type": "integer" + }, + "waiting": { + "type": "integer" + } + }, + "required": [ + "active", + "completed", + "failed", + "delayed", + "waiting" + ] + }, + "AllJobStatusResponseDto": { + "type": "object", + "properties": { + "thumbnailGenerationQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "metadataExtractionQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "videoConversionQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "machineLearningQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "isThumbnailGenerationActive": { + "type": "boolean" + }, + "isMetadataExtractionActive": { + "type": "boolean" + }, + "isVideoConversionActive": { + "type": "boolean" + }, + "isMachineLearningActive": { + "type": "boolean" + } + }, + "required": [ + "thumbnailGenerationQueueCount", + "metadataExtractionQueueCount", + "videoConversionQueueCount", + "machineLearningQueueCount", + "isThumbnailGenerationActive", + "isMetadataExtractionActive", + "isVideoConversionActive", + "isMachineLearningActive" + ] + }, + "JobId": { + "type": "string", + "enum": [ + "thumbnail-generation", + "metadata-extraction", + "video-conversion", + "machine-learning" + ] + }, + "JobStatusResponseDto": { + "type": "object", + "properties": { + "isActive": { + "type": "boolean" + }, + "queueCount": { + "type": "object" + } + }, + "required": [ + "isActive", + "queueCount" + ] + }, + "JobCommand": { + "type": "string", + "enum": [ + "start", + "stop" + ] + }, + "JobCommandDto": { + "type": "object", + "properties": { + "command": { + "$ref": "#/components/schemas/JobCommand" + } + }, + "required": [ + "command" + ] + }, + "SystemConfigKey": { + "type": "string", + "enum": [ + "ffmpeg_crf", + "ffmpeg_preset", + "ffmpeg_target_video_codec", + "ffmpeg_target_audio_codec", + "ffmpeg_target_scaling" + ] + }, + "SystemConfigResponseItem": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "key": { + "$ref": "#/components/schemas/SystemConfigKey" + }, + "value": { + "type": "string" + }, + "defaultValue": { + "type": "string" + } + }, + "required": [ + "name", + "key", + "value", + "defaultValue" + ] + }, + "SystemConfigResponseDto": { + "type": "object", + "properties": { + "config": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SystemConfigResponseItem" + } + } + }, + "required": [ + "config" + ] + }, + "UpdateSystemConfigDto": { + "type": "object", + "properties": {} + } + } + } +} \ No newline at end of file diff --git a/server/libs/database/src/entities/asset.entity.ts b/server/libs/database/src/entities/asset.entity.ts index 02c265e677..6ff49747ef 100644 --- a/server/libs/database/src/entities/asset.entity.ts +++ b/server/libs/database/src/entities/asset.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity, Index, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { Column, Entity, Index, JoinTable, ManyToMany, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { ExifEntity } from './exif.entity'; import { SmartInfoEntity } from './smart-info.entity'; +import { TagEntity } from './tag.entity'; @Entity('assets') @Unique('UQ_userid_checksum', ['userId', 'checksum']) @@ -62,6 +63,11 @@ export class AssetEntity { @OneToOne(() => SmartInfoEntity, (smartInfoEntity) => smartInfoEntity.asset) smartInfo?: SmartInfoEntity; + + // https://github.com/typeorm/typeorm/blob/master/docs/many-to-many-relations.md + @ManyToMany(() => TagEntity, (tag) => tag.assets, { cascade: true }) + @JoinTable({ name: 'tag_asset' }) + tags!: TagEntity[]; } export enum AssetType { diff --git a/server/libs/database/src/entities/tag.entity.ts b/server/libs/database/src/entities/tag.entity.ts new file mode 100644 index 0000000000..b3564337a6 --- /dev/null +++ b/server/libs/database/src/entities/tag.entity.ts @@ -0,0 +1,45 @@ +import { Column, Entity, ManyToMany, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { AssetEntity } from './asset.entity'; +import { UserEntity } from './user.entity'; + +@Entity('tags') +@Unique('UQ_tag_name_userId', ['name', 'userId']) +export class TagEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @Column() + type!: TagType; + + @Column() + name!: string; + + @Column() + userId!: string; + + @Column({ type: 'uuid', comment: 'The new renamed tagId', nullable: true }) + renameTagId!: string; + + @ManyToMany(() => AssetEntity, (asset) => asset.tags) + assets!: AssetEntity[]; + + @ManyToOne(() => UserEntity, (user) => user.tags) + user!: UserEntity; +} + +export enum TagType { + /** + * Tag that is detected by the ML model for object detection will use this type + */ + OBJECT = 'OBJECT', + + /** + * Face that is detected by the ML model for facial detection (TBD/NOT YET IMPLEMENTED) will use this type + */ + FACE = 'FACE', + + /** + * Tag that is created by the user will use this type + */ + CUSTOM = 'CUSTOM', +} diff --git a/server/libs/database/src/entities/user.entity.ts b/server/libs/database/src/entities/user.entity.ts index c114101c64..13bfc3d2e0 100644 --- a/server/libs/database/src/entities/user.entity.ts +++ b/server/libs/database/src/entities/user.entity.ts @@ -1,4 +1,5 @@ -import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, CreateDateColumn, DeleteDateColumn, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { TagEntity } from './tag.entity'; @Entity('users') export class UserEntity { @@ -37,4 +38,7 @@ export class UserEntity { @DeleteDateColumn() deletedAt?: Date; + + @OneToMany(() => TagEntity, (tag) => tag.user) + tags!: TagEntity[]; } diff --git a/server/libs/database/src/migrations/1670257571385-CreateTagsTable.ts b/server/libs/database/src/migrations/1670257571385-CreateTagsTable.ts new file mode 100644 index 0000000000..0585aecc8c --- /dev/null +++ b/server/libs/database/src/migrations/1670257571385-CreateTagsTable.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateTagsTable1670257571385 implements MigrationInterface { + name = 'CreateTagsTable1670257571385' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "type" character varying NOT NULL, "name" character varying NOT NULL, "userId" uuid NOT NULL, "renameTagId" uuid, CONSTRAINT "UQ_tag_name_userId" UNIQUE ("name", "userId"), CONSTRAINT "PK_e7dc17249a1148a1970748eda99" PRIMARY KEY ("id")); COMMENT ON COLUMN "tags"."renameTagId" IS 'The new renamed tagId'`); + await queryRunner.query(`CREATE TABLE "tag_asset" ("assetsId" uuid NOT NULL, "tagsId" uuid NOT NULL, CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId"))`); + await queryRunner.query(`CREATE INDEX "IDX_f8e8a9e893cb5c54907f1b798e" ON "tag_asset" ("assetsId") `); + await queryRunner.query(`CREATE INDEX "IDX_e99f31ea4cdf3a2c35c7287eb4" ON "tag_asset" ("tagsId") `); + await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42"`); + await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9"`); + await queryRunner.query(`ALTER TABLE "tags" DROP CONSTRAINT "FK_92e67dc508c705dd66c94615576"`); + await queryRunner.query(`DROP INDEX "public"."IDX_e99f31ea4cdf3a2c35c7287eb4"`); + await queryRunner.query(`DROP INDEX "public"."IDX_f8e8a9e893cb5c54907f1b798e"`); + await queryRunner.query(`DROP TABLE "tag_asset"`); + await queryRunner.query(`DROP TABLE "tags"`); + } + +} diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 233800bdfe..c9874b6811 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -325,6 +325,143 @@ export interface AssetCountByUserIdResponseDto { */ 'total': number; } +/** + * + * @export + * @interface AssetEntity + */ +export interface AssetEntity { + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'deviceAssetId': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'userId': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'deviceId': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'type': AssetEntityTypeEnum; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'originalPath': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'resizePath': string | null; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'webpPath': string | null; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'encodedVideoPath': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'modifiedAt': string; + /** + * + * @type {boolean} + * @memberof AssetEntity + */ + 'isFavorite': boolean; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'mimeType': string | null; + /** + * + * @type {object} + * @memberof AssetEntity + */ + 'checksum'?: object | null; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'duration': string | null; + /** + * + * @type {boolean} + * @memberof AssetEntity + */ + 'isVisible': boolean; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'livePhotoVideoId': string | null; + /** + * + * @type {ExifEntity} + * @memberof AssetEntity + */ + 'exifInfo'?: ExifEntity; + /** + * + * @type {SmartInfoEntity} + * @memberof AssetEntity + */ + 'smartInfo'?: SmartInfoEntity; + /** + * + * @type {Array} + * @memberof AssetEntity + */ + 'tags': Array; +} + +export const AssetEntityTypeEnum = { + Image: 'IMAGE', + Video: 'VIDEO', + Audio: 'AUDIO', + Other: 'OTHER' +} as const; + +export type AssetEntityTypeEnum = typeof AssetEntityTypeEnum[keyof typeof AssetEntityTypeEnum]; + /** * * @export @@ -446,6 +583,12 @@ export interface AssetResponseDto { * @memberof AssetResponseDto */ 'livePhotoVideoId'?: string | null; + /** + * + * @type {Array} + * @memberof AssetResponseDto + */ + 'tags': Array; } /** * @@ -602,6 +745,25 @@ export interface CreateProfileImageResponseDto { */ 'profileImagePath': string; } +/** + * + * @export + * @interface CreateTagDto + */ +export interface CreateTagDto { + /** + * + * @type {TagTypeEnum} + * @memberof CreateTagDto + */ + 'type': TagTypeEnum; + /** + * + * @type {string} + * @memberof CreateTagDto + */ + 'name': string; +} /** * * @export @@ -811,6 +973,163 @@ export const DeviceTypeEnum = { export type DeviceTypeEnum = typeof DeviceTypeEnum[keyof typeof DeviceTypeEnum]; +/** + * + * @export + * @interface ExifEntity + */ +export interface ExifEntity { + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'assetId': string; + /** + * General info + * @type {string} + * @memberof ExifEntity + */ + 'description': string; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'exifImageWidth': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'exifImageHeight': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'fileSizeInByte': number | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'orientation': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'dateTimeOriginal': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'modifyDate': string | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'latitude': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'longitude': number | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'city': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'state': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'country': string | null; + /** + * Image info + * @type {string} + * @memberof ExifEntity + */ + 'make': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'model': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'imageName': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'lensModel': string | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'fNumber': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'focalLength': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'iso': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'exposureTime': number | null; + /** + * Video info + * @type {number} + * @memberof ExifEntity + */ + 'fps'?: number | null; + /** + * + * @type {AssetEntity} + * @memberof ExifEntity + */ + 'asset'?: AssetEntity; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'exifTextSearchableColumn': string; +} /** * * @export @@ -1400,6 +1719,43 @@ export interface SignUpDto { */ 'lastName': string; } +/** + * + * @export + * @interface SmartInfoEntity + */ +export interface SmartInfoEntity { + /** + * + * @type {string} + * @memberof SmartInfoEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof SmartInfoEntity + */ + 'assetId': string; + /** + * + * @type {Array} + * @memberof SmartInfoEntity + */ + 'tags': Array | null; + /** + * + * @type {Array} + * @memberof SmartInfoEntity + */ + 'objects': Array | null; + /** + * + * @type {AssetEntity} + * @memberof SmartInfoEntity + */ + 'asset'?: AssetEntity; +} /** * * @export @@ -1486,6 +1842,104 @@ export interface SystemConfigResponseItem { */ 'defaultValue': string; } +/** + * + * @export + * @interface TagEntity + */ +export interface TagEntity { + /** + * + * @type {string} + * @memberof TagEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'type': TagEntityTypeEnum; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'name': string; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'userId': string; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'renameTagId': string; + /** + * + * @type {Array} + * @memberof TagEntity + */ + 'assets': Array; + /** + * + * @type {UserEntity} + * @memberof TagEntity + */ + 'user': UserEntity; +} + +export const TagEntityTypeEnum = { + Object: 'OBJECT', + Face: 'FACE', + Custom: 'CUSTOM' +} as const; + +export type TagEntityTypeEnum = typeof TagEntityTypeEnum[keyof typeof TagEntityTypeEnum]; + +/** + * + * @export + * @interface TagResponseDto + */ +export interface TagResponseDto { + /** + * + * @type {string} + * @memberof TagResponseDto + */ + 'id': string; + /** + * + * @type {TagTypeEnum} + * @memberof TagResponseDto + */ + 'type': TagTypeEnum; + /** + * + * @type {string} + * @memberof TagResponseDto + */ + 'name': string; +} +/** + * + * @export + * @enum {string} + */ + +export const TagTypeEnum = { + Object: 'OBJECT', + Face: 'FACE', + Custom: 'CUSTOM' +} as const; + +export type TagTypeEnum = typeof TagTypeEnum[keyof typeof TagTypeEnum]; + + /** * * @export @@ -1539,12 +1993,18 @@ export interface UpdateAlbumDto { * @interface UpdateAssetDto */ export interface UpdateAssetDto { + /** + * + * @type {Array} + * @memberof UpdateAssetDto + */ + 'tagIds'?: Array; /** * * @type {boolean} * @memberof UpdateAssetDto */ - 'isFavorite': boolean; + 'isFavorite'?: boolean; } /** * @@ -1571,6 +2031,25 @@ export interface UpdateDeviceInfoDto { */ 'isAutoBackup'?: boolean; } +/** + * + * @export + * @interface UpdateTagDto + */ +export interface UpdateTagDto { + /** + * + * @type {string} + * @memberof UpdateTagDto + */ + 'name'?: string; + /** + * + * @type {string} + * @memberof UpdateTagDto + */ + 'renameTagId'?: string; +} /** * * @export @@ -1670,6 +2149,91 @@ export interface UserCountResponseDto { */ 'userCount': number; } +/** + * + * @export + * @interface UserEntity + */ +export interface UserEntity { + /** + * + * @type {string} + * @memberof UserEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'firstName': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'lastName': string; + /** + * + * @type {boolean} + * @memberof UserEntity + */ + 'isAdmin': boolean; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'email': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'password'?: string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'salt'?: string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'oauthId': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'profileImagePath': string; + /** + * + * @type {boolean} + * @memberof UserEntity + */ + 'shouldChangePassword': boolean; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'deletedAt'?: string; + /** + * + * @type {Array} + * @memberof UserEntity + */ + 'tags': Array; +} /** * * @export @@ -3246,12 +3810,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - updateAssetById: async (assetId: string, updateAssetDto: UpdateAssetDto, options: AxiosRequestConfig = {}): Promise => { + updateAsset: async (assetId: string, updateAssetDto: UpdateAssetDto, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'assetId' is not null or undefined - assertParamExists('updateAssetById', 'assetId', assetId) + assertParamExists('updateAsset', 'assetId', assetId) // verify required parameter 'updateAssetDto' is not null or undefined - assertParamExists('updateAssetById', 'updateAssetDto', updateAssetDto) - const localVarPath = `/asset/assetById/{assetId}` + assertParamExists('updateAsset', 'updateAssetDto', updateAssetDto) + const localVarPath = `/asset/{assetId}` .replace(`{${"assetId"}}`, encodeURIComponent(String(assetId))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -3520,8 +4084,8 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async updateAssetById(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.updateAssetById(assetId, updateAssetDto, options); + async updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(assetId, updateAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -3711,8 +4275,8 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {*} [options] Override http request option. * @throws {RequiredError} */ - updateAssetById(assetId: string, updateAssetDto: UpdateAssetDto, options?: any): AxiosPromise { - return localVarFp.updateAssetById(assetId, updateAssetDto, options).then((request) => request(axios, basePath)); + updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: any): AxiosPromise { + return localVarFp.updateAsset(assetId, updateAssetDto, options).then((request) => request(axios, basePath)); }, /** * @@ -3935,8 +4499,8 @@ export class AssetApi extends BaseAPI { * @throws {RequiredError} * @memberof AssetApi */ - public updateAssetById(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).updateAssetById(assetId, updateAssetDto, options).then((request) => request(this.axios, this.basePath)); + public updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).updateAsset(assetId, updateAssetDto, options).then((request) => request(this.axios, this.basePath)); } /** @@ -5250,6 +5814,363 @@ export class SystemConfigApi extends BaseAPI { } +/** + * TagApi - axios parameter creator + * @export + */ +export const TagApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + _delete: async (id: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('_delete', 'id', id) + const localVarPath = `/tag/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + create: async (createTagDto: CreateTagDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'createTagDto' is not null or undefined + assertParamExists('create', 'createTagDto', createTagDto) + const localVarPath = `/tag`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createTagDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findAll: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/tag`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findOne: async (id: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('findOne', 'id', id) + const localVarPath = `/tag/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + update: async (id: string, updateTagDto: UpdateTagDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('update', 'id', id) + // verify required parameter 'updateTagDto' is not null or undefined + assertParamExists('update', 'updateTagDto', updateTagDto) + const localVarPath = `/tag/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(updateTagDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * TagApi - functional programming interface + * @export + */ +export const TagApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = TagApiAxiosParamCreator(configuration) + return { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async _delete(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator._delete(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async create(createTagDto: CreateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.create(createTagDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async findAll(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.findAll(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async findOne(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.findOne(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async update(id: string, updateTagDto: UpdateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.update(id, updateTagDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * TagApi - factory interface + * @export + */ +export const TagApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = TagApiFp(configuration) + return { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + _delete(id: string, options?: any): AxiosPromise { + return localVarFp._delete(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + create(createTagDto: CreateTagDto, options?: any): AxiosPromise { + return localVarFp.create(createTagDto, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findAll(options?: any): AxiosPromise> { + return localVarFp.findAll(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findOne(id: string, options?: any): AxiosPromise { + return localVarFp.findOne(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + update(id: string, updateTagDto: UpdateTagDto, options?: any): AxiosPromise { + return localVarFp.update(id, updateTagDto, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * TagApi - object-oriented interface + * @export + * @class TagApi + * @extends {BaseAPI} + */ +export class TagApi extends BaseAPI { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public _delete(id: string, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration)._delete(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public create(createTagDto: CreateTagDto, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).create(createTagDto, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public findAll(options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).findAll(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public findOne(id: string, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).findOne(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public update(id: string, updateTagDto: UpdateTagDto, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).update(id, updateTagDto, options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * UserApi - axios parameter creator * @export diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index cd46776e9b..17f58a64f3 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -183,7 +183,7 @@ }; const toggleFavorite = async () => { - const { data } = await api.assetApi.updateAssetById(asset.id, { + const { data } = await api.assetApi.updateAsset(asset.id, { isFavorite: !asset.isFavorite }); diff --git a/web/src/lib/components/shared-components/status-box.svelte b/web/src/lib/components/shared-components/status-box.svelte index b8d5c5feb7..a7271b469d 100644 --- a/web/src/lib/components/shared-components/status-box.svelte +++ b/web/src/lib/components/shared-components/status-box.svelte @@ -62,7 +62,9 @@ style={`width: ${getStorageUsagePercentage()}%`} /> -

{asByteUnitString(serverInfo?.diskUseRaw)} of {asByteUnitString(serverInfo?.diskSizeRaw)} used

+

+ {asByteUnitString(serverInfo?.diskUseRaw)} of {asByteUnitString(serverInfo?.diskSizeRaw)} used +

{:else}
diff --git a/web/src/lib/components/shared-components/upload-panel.svelte b/web/src/lib/components/shared-components/upload-panel.svelte index e393f61dd1..c8eb90246a 100644 --- a/web/src/lib/components/shared-components/upload-panel.svelte +++ b/web/src/lib/components/shared-components/upload-panel.svelte @@ -115,9 +115,7 @@