diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 0b891e36a..250e641cc 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -8,6 +8,8 @@ doc/AdminSignupResponseDto.md doc/AlbumApi.md doc/AlbumResponseDto.md doc/AssetApi.md +doc/AssetCountByTimeGroupDto.md +doc/AssetCountByTimeGroupResponseDto.md doc/AssetFileUploadResponseDto.md doc/AssetResponseDto.md doc/AssetTypeEnum.md @@ -27,6 +29,7 @@ doc/DeviceInfoApi.md doc/DeviceInfoResponseDto.md doc/DeviceTypeEnum.md doc/ExifResponseDto.md +doc/GetAssetCountByTimeGroupDto.md doc/LoginCredentialDto.md doc/LoginResponseDto.md doc/LogoutResponseDto.md @@ -39,6 +42,7 @@ doc/ServerVersionReponseDto.md doc/SignUpDto.md doc/SmartInfoResponseDto.md doc/ThumbnailFormat.md +doc/TimeGroupEnum.md doc/UpdateAlbumDto.md doc/UpdateDeviceInfoDto.md doc/UpdateUserDto.md @@ -66,6 +70,8 @@ lib/model/add_assets_dto.dart lib/model/add_users_dto.dart lib/model/admin_signup_response_dto.dart lib/model/album_response_dto.dart +lib/model/asset_count_by_time_group_dto.dart +lib/model/asset_count_by_time_group_response_dto.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_response_dto.dart lib/model/asset_type_enum.dart @@ -83,6 +89,7 @@ lib/model/delete_asset_status.dart lib/model/device_info_response_dto.dart lib/model/device_type_enum.dart lib/model/exif_response_dto.dart +lib/model/get_asset_count_by_time_group_dto.dart lib/model/login_credential_dto.dart lib/model/login_response_dto.dart lib/model/logout_response_dto.dart @@ -94,6 +101,7 @@ lib/model/server_version_reponse_dto.dart lib/model/sign_up_dto.dart lib/model/smart_info_response_dto.dart lib/model/thumbnail_format.dart +lib/model/time_group_enum.dart lib/model/update_album_dto.dart lib/model/update_device_info_dto.dart lib/model/update_user_dto.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 03f08f98e..f14d52e6d 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 dc01b3282..91268af32 100644 Binary files a/mobile/openapi/doc/AssetApi.md and b/mobile/openapi/doc/AssetApi.md differ diff --git a/mobile/openapi/doc/AssetCountByTimeGroupDto.md b/mobile/openapi/doc/AssetCountByTimeGroupDto.md new file mode 100644 index 000000000..f75884e16 Binary files /dev/null and b/mobile/openapi/doc/AssetCountByTimeGroupDto.md differ diff --git a/mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md b/mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md new file mode 100644 index 000000000..86990303a Binary files /dev/null and b/mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md differ diff --git a/mobile/openapi/doc/GetAssetCountByTimeGroupDto.md b/mobile/openapi/doc/GetAssetCountByTimeGroupDto.md new file mode 100644 index 000000000..12dccc261 Binary files /dev/null and b/mobile/openapi/doc/GetAssetCountByTimeGroupDto.md differ diff --git a/mobile/openapi/doc/TimeGroupEnum.md b/mobile/openapi/doc/TimeGroupEnum.md new file mode 100644 index 000000000..d037b9f35 Binary files /dev/null and b/mobile/openapi/doc/TimeGroupEnum.md differ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index afb9e664c..8a65cf7f3 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 f83ef32fb..5c0925906 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_client.dart b/mobile/openapi/lib/api_client.dart index 262445371..6e9050da9 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 4e0c1e157..26c90fd0c 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/asset_count_by_time_group_dto.dart b/mobile/openapi/lib/model/asset_count_by_time_group_dto.dart new file mode 100644 index 000000000..c3d805612 Binary files /dev/null and b/mobile/openapi/lib/model/asset_count_by_time_group_dto.dart differ diff --git a/mobile/openapi/lib/model/asset_count_by_time_group_response_dto.dart b/mobile/openapi/lib/model/asset_count_by_time_group_response_dto.dart new file mode 100644 index 000000000..ddd771f51 Binary files /dev/null and b/mobile/openapi/lib/model/asset_count_by_time_group_response_dto.dart differ diff --git a/mobile/openapi/lib/model/get_asset_count_by_time_group_dto.dart b/mobile/openapi/lib/model/get_asset_count_by_time_group_dto.dart new file mode 100644 index 000000000..1e296d377 Binary files /dev/null and b/mobile/openapi/lib/model/get_asset_count_by_time_group_dto.dart differ diff --git a/mobile/openapi/lib/model/time_group_enum.dart b/mobile/openapi/lib/model/time_group_enum.dart new file mode 100644 index 000000000..b6b443a8b Binary files /dev/null and b/mobile/openapi/lib/model/time_group_enum.dart differ diff --git a/mobile/openapi/test/asset_count_by_time_group_dto_test.dart b/mobile/openapi/test/asset_count_by_time_group_dto_test.dart new file mode 100644 index 000000000..3012dde35 Binary files /dev/null and b/mobile/openapi/test/asset_count_by_time_group_dto_test.dart differ diff --git a/mobile/openapi/test/asset_count_by_time_group_response_dto_test.dart b/mobile/openapi/test/asset_count_by_time_group_response_dto_test.dart new file mode 100644 index 000000000..1320fb4bb Binary files /dev/null and b/mobile/openapi/test/asset_count_by_time_group_response_dto_test.dart differ diff --git a/mobile/openapi/test/get_asset_count_by_time_group_dto_test.dart b/mobile/openapi/test/get_asset_count_by_time_group_dto_test.dart new file mode 100644 index 000000000..993c7ce5c Binary files /dev/null and b/mobile/openapi/test/get_asset_count_by_time_group_dto_test.dart differ diff --git a/mobile/openapi/test/time_group_enum_test.dart b/mobile/openapi/test/time_group_enum_test.dart new file mode 100644 index 000000000..3c23d3233 Binary files /dev/null and b/mobile/openapi/test/time_group_enum_test.dart differ diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts new file mode 100644 index 000000000..942475077 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -0,0 +1,187 @@ +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 { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm/repository/Repository'; +import { CreateAssetDto } from './dto/create-asset.dto'; +import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; +import { AssetCountByTimeGroupDto } from './response-dto/asset-count-by-time-group-response.dto'; +import { TimeGroupEnum } from './dto/get-asset-count-by-time-group.dto'; + +export interface IAssetRepository { + create(createAssetDto: CreateAssetDto, ownerId: string, originalPath: string, mimeType: string): Promise; + getAllByUserId(userId: string): Promise; + getAllByDeviceId(userId: string, deviceId: string): Promise; + getById(assetId: string): Promise; + getLocationsByUserId(userId: string): Promise; + getDetectedObjectsByUserId(userId: string): Promise; + getSearchPropertiesByUserId(userId: string): Promise; + getAssetCountByTimeGroup(userId: string, timeGroup: TimeGroupEnum): Promise; +} + +export const ASSET_REPOSITORY = 'ASSET_REPOSITORY'; + +@Injectable() +export class AssetRepository implements IAssetRepository { + constructor( + @InjectRepository(AssetEntity) + private assetRepository: Repository, + ) {} + async getAssetCountByTimeGroup(userId: string, timeGroup: TimeGroupEnum) { + let result: AssetCountByTimeGroupDto[] = []; + + if (timeGroup === TimeGroupEnum.Month) { + result = await this.assetRepository + .createQueryBuilder('asset') + .select(`COUNT(asset.id)::int`, 'count') + .addSelect(`to_char(date_trunc('month', "createdAt"::timestamptz), 'YYYY_MM')`, 'timeGroup') + .where('"userId" = :userId', { userId: userId }) + .groupBy(`date_trunc('month', "createdAt"::timestamptz)`) + .orderBy(`date_trunc('month', "createdAt"::timestamptz)`, 'DESC') + .getRawMany(); + } else if (timeGroup === TimeGroupEnum.Day) { + result = await this.assetRepository + .createQueryBuilder('asset') + .select(`COUNT(asset.id)::int`, 'count') + .addSelect(`to_char(date_trunc('day', "createdAt"::timestamptz), 'YYYY_MM_DD')`, 'timeGroup') + .where('"userId" = :userId', { userId: userId }) + .groupBy(`date_trunc('day', "createdAt"::timestamptz)`) + .orderBy(`date_trunc('day', "createdAt"::timestamptz)`, 'DESC') + .getRawMany(); + } + + return result; + } + + async getSearchPropertiesByUserId(userId: string): Promise { + return await this.assetRepository + .createQueryBuilder('asset') + .where('asset.userId = :userId', { userId: userId }) + .leftJoin('asset.exifInfo', 'ei') + .leftJoin('asset.smartInfo', 'si') + .select('si.tags', 'tags') + .addSelect('si.objects', 'objects') + .addSelect('asset.type', 'assetType') + .addSelect('ei.orientation', 'orientation') + .addSelect('ei."lensModel"', 'lensModel') + .addSelect('ei.make', 'make') + .addSelect('ei.model', 'model') + .addSelect('ei.city', 'city') + .addSelect('ei.state', 'state') + .addSelect('ei.country', 'country') + .distinctOn(['si.tags']) + .getRawMany(); + } + + async getDetectedObjectsByUserId(userId: string): Promise { + return await this.assetRepository.query( + ` + SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId" + FROM assets a + LEFT JOIN smart_info si ON a.id = si."assetId" + WHERE a."userId" = $1 + AND si.objects IS NOT NULL + `, + [userId], + ); + } + + async getLocationsByUserId(userId: string): Promise { + return await this.assetRepository.query( + ` + SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId" + FROM assets a + LEFT JOIN exif e ON a.id = e."assetId" + WHERE a."userId" = $1 + AND e.city IS NOT NULL + AND a.type = 'IMAGE'; + `, + [userId], + ); + } + + /** + * Get a single asset information by its ID + * - include exif info + * @param assetId + */ + async getById(assetId: string): Promise { + return await this.assetRepository.findOneOrFail({ + where: { + id: assetId, + }, + relations: ['exifInfo'], + }); + } + + /** + * Get all assets belong to the user on the database + * @param userId + */ + async getAllByUserId(userId: string): Promise { + const query = this.assetRepository + .createQueryBuilder('asset') + .where('asset.userId = :userId', { userId: userId }) + .andWhere('asset.resizePath is not NULL') + .leftJoinAndSelect('asset.exifInfo', 'exifInfo') + .orderBy('asset.createdAt', 'DESC'); + + return await query.getMany(); + } + + /** + * Create new asset information in database + * @param createAssetDto + * @param ownerId + * @param originalPath + * @param mimeType + * @returns Promise + */ + async create( + createAssetDto: CreateAssetDto, + ownerId: string, + originalPath: string, + mimeType: string, + ): Promise { + const asset = new AssetEntity(); + asset.deviceAssetId = createAssetDto.deviceAssetId; + asset.userId = ownerId; + asset.deviceId = createAssetDto.deviceId; + asset.type = createAssetDto.assetType || AssetType.OTHER; + asset.originalPath = originalPath; + asset.createdAt = createAssetDto.createdAt; + asset.modifiedAt = createAssetDto.modifiedAt; + asset.isFavorite = createAssetDto.isFavorite; + asset.mimeType = mimeType; + asset.duration = createAssetDto.duration || null; + + const createdAsset = await this.assetRepository.save(asset); + + if (!createdAsset) { + throw new BadRequestException('Asset not created'); + } + return createdAsset; + } + + /** + * Get assets by device's Id on the database + * @param userId + * @param deviceId + * + * @returns Promise - Array of assetIds belong to the device + */ + async getAllByDeviceId(userId: string, deviceId: string): Promise { + const rows = await this.assetRepository.find({ + where: { + userId: userId, + deviceId: deviceId, + }, + select: ['deviceAssetId'], + }); + const res: string[] = []; + rows.forEach((v) => res.push(v.deviceAssetId)); + + return res; + } +} 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 18447de8d..7009f1e11 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -2,7 +2,6 @@ import { Controller, Post, UseInterceptors, - UploadedFiles, Body, UseGuards, Get, @@ -44,6 +43,8 @@ import { CreateAssetDto } from './dto/create-asset.dto'; import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto'; import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto'; +import { AssetCountByTimeGroupResponseDto } from './response-dto/asset-count-by-time-group-response.dto'; +import { GetAssetCountByTimeGroupDto } from './dto/get-asset-count-by-time-group.dto'; @UseGuards(JwtAuthGuard) @ApiBearerAuth() @@ -117,17 +118,17 @@ export class AssetController { return this.assetService.getAssetThumbnail(assetId, query); } - @Get('/allObjects') + @Get('/curated-objects') async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getCuratedObject(authUser); } - @Get('/allLocation') + @Get('/curated-locations') async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getCuratedLocation(authUser); } - @Get('/searchTerm') + @Get('/search-terms') async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getAssetSearchTerm(authUser); } @@ -140,6 +141,14 @@ export class AssetController { return this.assetService.searchAsset(authUser, searchAssetDto); } + @Get('/count-by-date') + async getAssetCountByTimeGroup( + @GetAuthUser() authUser: AuthUserDto, + @Body(ValidationPipe) getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, + ): Promise { + return this.assetService.getAssetCountByTimeGroup(authUser, getAssetCountByTimeGroupDto); + } + /** * Get all AssetEntity belong to the user */ 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 a09831391..13df9997f 100644 --- a/server/apps/immich/src/api-v1/asset/asset.module.ts +++ b/server/apps/immich/src/api-v1/asset/asset.module.ts @@ -8,6 +8,7 @@ import { BackgroundTaskModule } from '../../modules/background-task/background-t import { BackgroundTaskService } from '../../modules/background-task/background-task.service'; import { CommunicationModule } from '../communication/communication.module'; import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant'; +import { AssetRepository, ASSET_REPOSITORY } from './asset-repository'; @Module({ imports: [ @@ -24,7 +25,14 @@ import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant'; }), ], controllers: [AssetController], - providers: [AssetService, BackgroundTaskService], + providers: [ + AssetService, + BackgroundTaskService, + { + provide: ASSET_REPOSITORY, + useClass: AssetRepository, + }, + ], exports: [AssetService], }) export class AssetModule {} diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts new file mode 100644 index 000000000..c16050ea8 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -0,0 +1,92 @@ +import { AssetRepository, IAssetRepository } from './asset-repository'; +import { AuthUserDto } from '../../decorators/auth-user.decorator'; +import { AssetService } from './asset.service'; +import { Repository } from 'typeorm'; +import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; +import { CreateAssetDto } from './dto/create-asset.dto'; + +describe('AssetService', () => { + let sui: AssetService; + let a: Repository; // TO BE DELETED AFTER FINISHED REFACTORING + let assetRepositoryMock: jest.Mocked; + + const authUser: AuthUserDto = Object.freeze({ + id: '3ea54709-e168-42b7-90b0-a0dfe8a7ecbd', + email: 'auth@test.com', + }); + + const _getCreateAssetDto = (): CreateAssetDto => { + const createAssetDto = new CreateAssetDto(); + createAssetDto.deviceAssetId = 'deviceAssetId'; + createAssetDto.deviceId = 'deviceId'; + createAssetDto.assetType = AssetType.OTHER; + createAssetDto.createdAt = '2022-06-19T23:41:36.910Z'; + createAssetDto.modifiedAt = '2022-06-19T23:41:36.910Z'; + createAssetDto.isFavorite = false; + createAssetDto.duration = '0:00:00.000000'; + + return createAssetDto; + }; + const _getAsset = () => { + const assetEntity = new AssetEntity(); + + assetEntity.id = 'e8edabfd-7d8a-45d0-9d61-7c7ca60f2c67'; + assetEntity.userId = '3ea54709-e168-42b7-90b0-a0dfe8a7ecbd'; + assetEntity.deviceAssetId = '4967046344801'; + assetEntity.deviceId = '116766fd-2ef2-52dc-a3ef-149988997291'; + assetEntity.type = AssetType.VIDEO; + assetEntity.originalPath = + 'upload/3ea54709-e168-42b7-90b0-a0dfe8a7ecbd/original/116766fd-2ef2-52dc-a3ef-149988997291/51c97f95-244f-462d-bdf0-e1dc19913516.jpg'; + assetEntity.resizePath = ''; + assetEntity.createdAt = '2022-06-19T23:41:36.910Z'; + assetEntity.modifiedAt = '2022-06-19T23:41:36.910Z'; + assetEntity.isFavorite = false; + assetEntity.mimeType = 'image/jpeg'; + assetEntity.webpPath = ''; + assetEntity.encodedVideoPath = ''; + assetEntity.duration = '0:00:00.000000'; + + return assetEntity; + }; + + beforeAll(() => { + assetRepositoryMock = { + create: jest.fn(), + getAllByUserId: jest.fn(), + getAllByDeviceId: jest.fn(), + getAssetCountByTimeGroup: jest.fn(), + getById: jest.fn(), + getDetectedObjectsByUserId: jest.fn(), + getLocationsByUserId: jest.fn(), + getSearchPropertiesByUserId: jest.fn(), + }; + + sui = new AssetService(assetRepositoryMock, a); + }); + + it('create an asset', async () => { + const assetEntity = _getAsset(); + + assetRepositoryMock.create.mockImplementation(() => Promise.resolve(assetEntity)); + + const originalPath = + 'upload/3ea54709-e168-42b7-90b0-a0dfe8a7ecbd/original/116766fd-2ef2-52dc-a3ef-149988997291/51c97f95-244f-462d-bdf0-e1dc19913516.jpg'; + const mimeType = 'image/jpeg'; + const createAssetDto = _getCreateAssetDto(); + const result = await sui.createUserAsset(authUser, createAssetDto, originalPath, mimeType); + + expect(result.userId).toEqual(authUser.id); + expect(result.resizePath).toEqual(''); + expect(result.webpPath).toEqual(''); + }); + + it('get assets by device id', async () => { + assetRepositoryMock.getAllByDeviceId.mockImplementation(() => Promise.resolve(['4967046344801'])); + + const deviceId = '116766fd-2ef2-52dc-a3ef-149988997291'; + const result = await sui.getUserAssetsByDeviceId(authUser, deviceId); + + expect(result.length).toEqual(1); + expect(result[0]).toEqual('4967046344801'); + }); +}); 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 9156950a5..4d3221267 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -1,5 +1,7 @@ +import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; import { BadRequestException, + Inject, Injectable, InternalServerErrorException, Logger, @@ -7,7 +9,7 @@ import { StreamableFile, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { IsNull, Not, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; import { constants, createReadStream, ReadStream, stat } from 'fs'; @@ -25,83 +27,49 @@ import { CreateAssetDto } from './dto/create-asset.dto'; import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto'; import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto'; import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto'; +import { ASSET_REPOSITORY, IAssetRepository } from './asset-repository'; +import { SearchPropertiesDto } from './dto/search-properties.dto'; +import { + AssetCountByTimeGroupResponseDto, + mapAssetCountByTimeGroupResponse, +} from './response-dto/asset-count-by-time-group-response.dto'; +import { GetAssetCountByTimeGroupDto } from './dto/get-asset-count-by-time-group.dto'; const fileInfo = promisify(stat); @Injectable() export class AssetService { constructor( + @Inject(ASSET_REPOSITORY) + private _assetRepository: IAssetRepository, + @InjectRepository(AssetEntity) private assetRepository: Repository, ) {} - public async updateThumbnailInfo(asset: AssetEntity, thumbnailPath: string): Promise { - const updatedAsset = await this.assetRepository - .createQueryBuilder('assets') - .update(AssetEntity, { ...asset, resizePath: thumbnailPath }) - .where('assets.id = :id', { id: asset.id }) - .returning('*') - .updateEntity(true) - .execute(); - - return updatedAsset.raw[0]; - } - public async createUserAsset( authUser: AuthUserDto, - assetInfo: CreateAssetDto, - path: string, + createAssetDto: CreateAssetDto, + originalPath: string, mimeType: string, - ): Promise { - const asset = new AssetEntity(); - asset.deviceAssetId = assetInfo.deviceAssetId; - asset.userId = authUser.id; - asset.deviceId = assetInfo.deviceId; - asset.type = assetInfo.assetType || AssetType.OTHER; - asset.originalPath = path; - asset.createdAt = assetInfo.createdAt; - asset.modifiedAt = assetInfo.modifiedAt; - asset.isFavorite = assetInfo.isFavorite; - asset.mimeType = mimeType; - asset.duration = assetInfo.duration || null; + ): Promise { + const assetEntity = await this._assetRepository.create(createAssetDto, authUser.id, originalPath, mimeType); - const createdAsset = await this.assetRepository.save(asset); - if (!createdAsset) { - throw new Error('Asset not created'); - } - return createdAsset; + return assetEntity; } public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) { - const rows = await this.assetRepository.find({ - where: { - userId: authUser.id, - deviceId: deviceId, - }, - select: ['deviceAssetId'], - }); - - const res: string[] = []; - rows.forEach((v) => res.push(v.deviceAssetId)); - return res; + return this._assetRepository.getAllByDeviceId(authUser.id, deviceId); } public async getAllAssets(authUser: AuthUserDto): Promise { - const assets = await this.assetRepository.find({ - where: { - userId: authUser.id, - resizePath: Not(IsNull()), - }, - relations: ['exifInfo'], - order: { - createdAt: 'DESC', - }, - }); + const assets = await this._assetRepository.getAllByUserId(authUser.id); return assets.map((asset) => mapAsset(asset)); } - public async findAssetOfDevice(deviceId: string, assetId: string): Promise { + // TODO - Refactor this to get asset by its own id + private async findAssetOfDevice(deviceId: string, assetId: string): Promise { const rows = await this.assetRepository.query( 'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2', [assetId, deviceId], @@ -117,16 +85,7 @@ export class AssetService { } public async getAssetById(authUser: AuthUserDto, assetId: string): Promise { - const asset = await this.assetRepository.findOne({ - where: { - id: assetId, - }, - relations: ['exifInfo'], - }); - - if (!asset) { - throw new NotFoundException('Asset not found'); - } + const asset = await this._assetRepository.getById(assetId); return mapAsset(asset); } @@ -394,45 +353,35 @@ export class AssetService { async getAssetSearchTerm(authUser: AuthUserDto): Promise { const possibleSearchTerm = new Set(); - // TODO: should use query builder - const rows = await this.assetRepository.query( - ` - SELECT DISTINCT si.tags, si.objects, e.orientation, e."lensModel", e.make, e.model , a.type, e.city, e.state, e.country - FROM assets a - LEFT JOIN exif e ON a.id = e."assetId" - LEFT JOIN smart_info si ON a.id = si."assetId" - WHERE a."userId" = $1; - `, - [authUser.id], - ); - rows.forEach((row: { [x: string]: any }) => { + const rows = await this._assetRepository.getSearchPropertiesByUserId(authUser.id); + rows.forEach((row: SearchPropertiesDto) => { // tags - row['tags']?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase())); + row.tags?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase())); // objects - row['objects']?.map((object: string) => possibleSearchTerm.add(object?.toLowerCase())); + row.objects?.map((object: string) => possibleSearchTerm.add(object?.toLowerCase())); // asset's tyoe - possibleSearchTerm.add(row['type']?.toLowerCase()); + possibleSearchTerm.add(row.assetType?.toLowerCase() || ''); // image orientation - possibleSearchTerm.add(row['orientation']?.toLowerCase()); + possibleSearchTerm.add(row.orientation?.toLowerCase() || ''); // Lens model - possibleSearchTerm.add(row['lensModel']?.toLowerCase()); + possibleSearchTerm.add(row.lensModel?.toLowerCase() || ''); // Make and model - possibleSearchTerm.add(row['make']?.toLowerCase()); - possibleSearchTerm.add(row['model']?.toLowerCase()); + possibleSearchTerm.add(row.make?.toLowerCase() || ''); + possibleSearchTerm.add(row.model?.toLowerCase() || ''); // Location - possibleSearchTerm.add(row['city']?.toLowerCase()); - possibleSearchTerm.add(row['state']?.toLowerCase()); - possibleSearchTerm.add(row['country']?.toLowerCase()); + possibleSearchTerm.add(row.city?.toLowerCase() || ''); + possibleSearchTerm.add(row.state?.toLowerCase() || ''); + possibleSearchTerm.add(row.country?.toLowerCase() || ''); }); - return Array.from(possibleSearchTerm).filter((x) => x != null); + return Array.from(possibleSearchTerm).filter((x) => x != null && x != ''); } async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto): Promise { @@ -459,33 +408,12 @@ export class AssetService { return searchResults.map((asset) => mapAsset(asset)); } - async getCuratedLocation(authUser: AuthUserDto) { - return await this.assetRepository.query( - ` - SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId" - FROM assets a - LEFT JOIN exif e ON a.id = e."assetId" - WHERE a."userId" = $1 - AND e.city IS NOT NULL - AND a.type = 'IMAGE'; - `, - [authUser.id], - ); + async getCuratedLocation(authUser: AuthUserDto): Promise { + return this._assetRepository.getLocationsByUserId(authUser.id); } async getCuratedObject(authUser: AuthUserDto): Promise { - const curatedObjects: CuratedObjectsResponseDto[] = await this.assetRepository.query( - ` - SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId" - FROM assets a - LEFT JOIN smart_info si ON a.id = si."assetId" - WHERE a."userId" = $1 - AND si.objects IS NOT NULL - `, - [authUser.id], - ); - - return curatedObjects; + return this._assetRepository.getDetectedObjectsByUserId(authUser.id); } async checkDuplicatedAsset( @@ -504,4 +432,16 @@ export class AssetService { return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id); } + + async getAssetCountByTimeGroup( + authUser: AuthUserDto, + getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, + ): Promise { + const result = await this._assetRepository.getAssetCountByTimeGroup( + authUser.id, + getAssetCountByTimeGroupDto.timeGroup, + ); + + return mapAssetCountByTimeGroupResponse(result); + } } diff --git a/server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-group.dto.ts b/server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-group.dto.ts new file mode 100644 index 000000000..2862d3c94 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-group.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty } from 'class-validator'; + +export enum TimeGroupEnum { + Day = 'day', + Month = 'month', +} +export class GetAssetCountByTimeGroupDto { + @IsNotEmpty() + @ApiProperty({ + type: String, + enum: TimeGroupEnum, + enumName: 'TimeGroupEnum', + }) + timeGroup!: TimeGroupEnum; +} diff --git a/server/apps/immich/src/api-v1/asset/dto/search-properties.dto.ts b/server/apps/immich/src/api-v1/asset/dto/search-properties.dto.ts new file mode 100644 index 000000000..669b29b2e --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/search-properties.dto.ts @@ -0,0 +1,12 @@ +export class SearchPropertiesDto { + tags?: string[]; + objects?: string[]; + assetType?: string; + orientation?: string; + lensModel?: string; + make?: string; + model?: string; + city?: string; + state?: string; + country?: string; +} diff --git a/server/apps/immich/src/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts new file mode 100644 index 000000000..2d1d60974 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class AssetCountByTimeGroupDto { + @ApiProperty({ type: 'string' }) + timeGroup!: string; + + @ApiProperty({ type: 'integer' }) + count!: number; +} + +export class AssetCountByTimeGroupResponseDto { + groups!: AssetCountByTimeGroupDto[]; + + @ApiProperty({ type: 'integer' }) + totalAssets!: number; +} + +export function mapAssetCountByTimeGroupResponse(result: AssetCountByTimeGroupDto[]): AssetCountByTimeGroupResponseDto { + return { + groups: result, + totalAssets: result.map((group) => group.count).reduce((a, b) => a + b, 0), + }; +} diff --git a/server/apps/immich/src/api-v1/communication/communication.gateway.ts b/server/apps/immich/src/api-v1/communication/communication.gateway.ts index ba36bbcf7..8cb7928b9 100644 --- a/server/apps/immich/src/api-v1/communication/communication.gateway.ts +++ b/server/apps/immich/src/api-v1/communication/communication.gateway.ts @@ -5,7 +5,7 @@ import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { UserEntity } from '@app/database/entities/user.entity'; import { Repository } from 'typeorm'; - +import cookieParser from 'cookie'; @WebSocketGateway({ cors: true }) export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisconnect { constructor( @@ -26,8 +26,24 @@ export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisco async handleConnection(client: Socket) { try { Logger.log(`New websocket connection: ${client.id}`, 'WebsocketConnectionEvent'); + let accessToken = ''; - const accessToken = client.handshake.headers.authorization?.split(' ')[1]; + if (client.handshake.headers.cookie != undefined) { + const cookies = cookieParser.parse(client.handshake.headers.cookie); + if (cookies.immich_access_token) { + accessToken = cookies.immich_access_token; + } else { + client.emit('error', 'unauthorized'); + client.disconnect(); + return; + } + } else if (client.handshake.headers.authorization != undefined) { + accessToken = client.handshake.headers.authorization.split(' ')[1]; + } else { + client.emit('error', 'unauthorized'); + client.disconnect(); + return; + } const res: JwtValidationResult = accessToken ? await this.immichJwtService.validateToken(accessToken) diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 9cc6f3f46..4d4bf4dad 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1 +1 @@ -{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/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/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":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"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/allObjects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allLocation":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/searchTerm":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"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":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","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":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"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":"string","nullable":true,"default":null},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"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"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"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"]},"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}},"required":["successful"]},"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"},"diskUseRaw":{"type":"integer"},"diskAvailableRaw":{"type":"integer"},"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"]},"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"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}}}}} \ 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/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":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"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-date":{"get":{"operationId":"getAssetCountByTimeGroup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeGroupDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeGroupResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","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":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"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":"string","nullable":true,"default":null},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"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"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeGroupDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeGroupDto":{"type":"object","properties":{"timeGroup":{"type":"string"},"count":{"type":"integer"}},"required":["timeGroup","count"]},"AssetCountByTimeGroupResponseDto":{"type":"object","properties":{"totalAssets":{"type":"integer"},"groups":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeGroupDto"}}},"required":["totalAssets","groups"]},"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"]},"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}},"required":["successful"]},"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"},"diskUseRaw":{"type":"integer"},"diskAvailableRaw":{"type":"integer"},"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"]},"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"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}}}}} \ No newline at end of file diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 79ea7f4ad..2ccc6010c 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -145,6 +145,44 @@ export interface AlbumResponseDto { */ 'assets': Array; } +/** + * + * @export + * @interface AssetCountByTimeGroupDto + */ +export interface AssetCountByTimeGroupDto { + /** + * + * @type {string} + * @memberof AssetCountByTimeGroupDto + */ + 'timeGroup': string; + /** + * + * @type {number} + * @memberof AssetCountByTimeGroupDto + */ + 'count': number; +} +/** + * + * @export + * @interface AssetCountByTimeGroupResponseDto + */ +export interface AssetCountByTimeGroupResponseDto { + /** + * + * @type {number} + * @memberof AssetCountByTimeGroupResponseDto + */ + 'totalAssets': number; + /** + * + * @type {Array} + * @memberof AssetCountByTimeGroupResponseDto + */ + 'groups': Array; +} /** * * @export @@ -720,6 +758,19 @@ export interface ExifResponseDto { */ 'country'?: string | null; } +/** + * + * @export + * @interface GetAssetCountByTimeGroupDto + */ +export interface GetAssetCountByTimeGroupDto { + /** + * + * @type {TimeGroupEnum} + * @memberof GetAssetCountByTimeGroupDto + */ + 'timeGroup': TimeGroupEnum; +} /** * * @export @@ -996,6 +1047,20 @@ export const ThumbnailFormat = { export type ThumbnailFormat = typeof ThumbnailFormat[keyof typeof ThumbnailFormat]; +/** + * + * @export + * @enum {string} + */ + +export const TimeGroupEnum = { + Day: 'day', + Month: 'month' +} as const; + +export type TimeGroupEnum = typeof TimeGroupEnum[keyof typeof TimeGroupEnum]; + + /** * * @export @@ -2072,13 +2137,52 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAssetCountByTimeGroup: async (getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'getAssetCountByTimeGroupDto' is not null or undefined + assertParamExists('getAssetCountByTimeGroup', 'getAssetCountByTimeGroupDto', getAssetCountByTimeGroupDto) + const localVarPath = `/asset/count-by-date`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(getAssetCountByTimeGroupDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {*} [options] Override http request option. * @throws {RequiredError} */ getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/searchTerm`; + const localVarPath = `/asset/search-terms`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -2153,7 +2257,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @throws {RequiredError} */ getCuratedLocations: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/allLocation`; + const localVarPath = `/asset/curated-locations`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -2186,7 +2290,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @throws {RequiredError} */ getCuratedObjects: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/allObjects`; + const localVarPath = `/asset/curated-objects`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -2456,6 +2560,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetById(assetId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -2598,6 +2712,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getAssetById(assetId: string, options?: any): AxiosPromise { return localVarFp.getAssetById(assetId, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: any): AxiosPromise { + return localVarFp.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -2742,6 +2865,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getAssetById(assetId, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts index 569ed5673..db2bb6472 100644 --- a/web/src/lib/stores/websocket.ts +++ b/web/src/lib/stores/websocket.ts @@ -1,5 +1,4 @@ import { Socket, io } from 'socket.io-client'; -import { writable } from 'svelte/store'; let websocket: Socket; diff --git a/web/src/routes/+layout.server.ts b/web/src/routes/+layout.server.ts index ad5168359..d3ae61459 100644 --- a/web/src/routes/+layout.server.ts +++ b/web/src/routes/+layout.server.ts @@ -1,4 +1,4 @@ -import { serverApi } from '@api'; +import { serverApi, TimeGroupEnum } from '@api'; import * as cookieParser from 'cookie'; import type { LayoutServerLoad } from './$types'; @@ -21,6 +21,9 @@ export const load: LayoutServerLoad = async ({ request }) => { user: userInfo }; } catch (e) { - console.log('[ERROR] layout.server.ts [LayoutServerLoad]: ', e); + console.error('[ERROR] layout.server.ts [LayoutServerLoad]: ', e); + return { + user: undefined + }; } }; diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index 38bf463e5..aca0447ed 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -20,11 +20,13 @@ import Close from 'svelte-material-icons/Close.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import type { PageData } from './$types'; - import { onMount } from 'svelte'; + + import { onMount, onDestroy } from 'svelte'; import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; + import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket'; export let data: PageData; @@ -193,6 +195,18 @@ console.error('Error deleteSelectedAssetHandler', e); } }; + + onMount(async () => { + openWebsocketConnection(); + + const { data: assets } = await api.assetApi.getAllAssets(); + + setAssetInfo(assets); + }); + + onDestroy(() => { + closeWebsocketConnection(); + });