diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index dfb4278b10..b36bab8889 100644 Binary files a/mobile/openapi/README.md and b/mobile/openapi/README.md differ diff --git a/mobile/openapi/doc/AlbumApi.md b/mobile/openapi/doc/AlbumApi.md index 2924ce5c02..172cd14162 100644 Binary files a/mobile/openapi/doc/AlbumApi.md and b/mobile/openapi/doc/AlbumApi.md differ diff --git a/mobile/openapi/doc/AlbumCountResponseDto.md b/mobile/openapi/doc/AlbumCountResponseDto.md index 94a2328f00..57b6e156c8 100644 Binary files a/mobile/openapi/doc/AlbumCountResponseDto.md and b/mobile/openapi/doc/AlbumCountResponseDto.md differ diff --git a/mobile/openapi/lib/api/album_api.dart b/mobile/openapi/lib/api/album_api.dart index c1497afc7e..b17c838f5b 100644 Binary files a/mobile/openapi/lib/api/album_api.dart and b/mobile/openapi/lib/api/album_api.dart differ diff --git a/mobile/openapi/lib/model/album_count_response_dto.dart b/mobile/openapi/lib/model/album_count_response_dto.dart index 10a5e21a45..1987175539 100644 Binary files a/mobile/openapi/lib/model/album_count_response_dto.dart and b/mobile/openapi/lib/model/album_count_response_dto.dart differ diff --git a/mobile/openapi/test/album_api_test.dart b/mobile/openapi/test/album_api_test.dart index 6a75449eeb..77f92590ee 100644 Binary files a/mobile/openapi/test/album_api_test.dart and b/mobile/openapi/test/album_api_test.dart differ diff --git a/mobile/openapi/test/album_count_response_dto_test.dart b/mobile/openapi/test/album_count_response_dto_test.dart index 33703a650e..2f294f463e 100644 Binary files a/mobile/openapi/test/album_count_response_dto_test.dart and b/mobile/openapi/test/album_count_response_dto_test.dart differ diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 709b21f5f0..22d0c0d4d7 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -95,9 +95,9 @@ ] } }, - "/album/count-by-user-id": { + "/album/count": { "get": { - "operationId": "getAlbumCountByUserId", + "operationId": "getAlbumCount", "parameters": [], "responses": { "200": { @@ -4530,14 +4530,14 @@ "shared": { "type": "integer" }, - "sharing": { + "notShared": { "type": "integer" } }, "required": [ "owned", "shared", - "sharing" + "notShared" ] }, "AlbumResponseDto": { diff --git a/server/src/domain/album/response-dto/album-response.dto.ts b/server/src/domain/album/album-response.dto.ts similarity index 84% rename from server/src/domain/album/response-dto/album-response.dto.ts rename to server/src/domain/album/album-response.dto.ts index 629bc5e070..2af7810db3 100644 --- a/server/src/domain/album/response-dto/album-response.dto.ts +++ b/server/src/domain/album/album-response.dto.ts @@ -1,7 +1,7 @@ import { AlbumEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; -import { AssetResponseDto, mapAsset } from '../../asset'; -import { mapUser, UserResponseDto } from '../../user'; +import { AssetResponseDto, mapAsset } from '../asset'; +import { mapUser, UserResponseDto } from '../user'; export class AlbumResponseDto { id!: string; @@ -63,3 +63,14 @@ export function mapAlbumExcludeAssetInfo(entity: AlbumEntity): AlbumResponseDto assetCount: entity.assets?.length || 0, }; } + +export class AlbumCountResponseDto { + @ApiProperty({ type: 'integer' }) + owned!: number; + + @ApiProperty({ type: 'integer' }) + shared!: number; + + @ApiProperty({ type: 'integer' }) + notShared!: number; +} diff --git a/server/src/domain/album/album.service.spec.ts b/server/src/domain/album/album.service.spec.ts index 43e5b0fc96..aeb9d0da31 100644 --- a/server/src/domain/album/album.service.spec.ts +++ b/server/src/domain/album/album.service.spec.ts @@ -35,6 +35,23 @@ describe(AlbumService.name, () => { expect(sut).toBeDefined(); }); + describe('getCount', () => { + it('should get the album count', async () => { + albumMock.getOwned.mockResolvedValue([]), + albumMock.getShared.mockResolvedValue([]), + albumMock.getNotShared.mockResolvedValue([]), + await expect(sut.getCount(authStub.admin)).resolves.toEqual({ + owned: 0, + shared: 0, + notShared: 0, + }); + + expect(albumMock.getOwned).toHaveBeenCalledWith(authStub.admin.id); + expect(albumMock.getShared).toHaveBeenCalledWith(authStub.admin.id); + expect(albumMock.getNotShared).toHaveBeenCalledWith(authStub.admin.id); + }); + }); + describe('getAll', () => { it('gets list of albums for auth user', async () => { albumMock.getOwned.mockResolvedValue([albumStub.empty, albumStub.sharedWithUser]); diff --git a/server/src/domain/album/album.service.ts b/server/src/domain/album/album.service.ts index 6bad729531..21461fe271 100644 --- a/server/src/domain/album/album.service.ts +++ b/server/src/domain/album/album.service.ts @@ -4,9 +4,9 @@ import { IAssetRepository, mapAsset } from '../asset'; import { AuthUserDto } from '../auth'; import { IJobRepository, JobName } from '../job'; import { IUserRepository } from '../user'; +import { AlbumCountResponseDto, AlbumResponseDto, mapAlbum } from './album-response.dto'; import { IAlbumRepository } from './album.repository'; import { AddUsersDto, CreateAlbumDto, GetAlbumsDto, UpdateAlbumDto } from './dto'; -import { AlbumResponseDto, mapAlbum } from './response-dto'; @Injectable() export class AlbumService { @@ -17,6 +17,20 @@ export class AlbumService { @Inject(IUserRepository) private userRepository: IUserRepository, ) {} + async getCount(authUser: AuthUserDto): Promise { + const [owned, shared, notShared] = await Promise.all([ + this.albumRepository.getOwned(authUser.id), + this.albumRepository.getShared(authUser.id), + this.albumRepository.getNotShared(authUser.id), + ]); + + return { + owned: owned.length, + shared: shared.length, + notShared: notShared.length, + }; + } + async getAll({ id: ownerId }: AuthUserDto, { assetId, shared }: GetAlbumsDto): Promise { await this.updateInvalidThumbnails(); diff --git a/server/src/domain/album/index.ts b/server/src/domain/album/index.ts index f80717b257..5042b0f446 100644 --- a/server/src/domain/album/index.ts +++ b/server/src/domain/album/index.ts @@ -1,4 +1,4 @@ +export * from './album-response.dto'; export * from './album.repository'; export * from './album.service'; export * from './dto'; -export * from './response-dto'; diff --git a/server/src/domain/album/response-dto/index.ts b/server/src/domain/album/response-dto/index.ts deleted file mode 100644 index d3b278b7dd..0000000000 --- a/server/src/domain/album/response-dto/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './album-response.dto'; diff --git a/server/src/immich/api-v1/album/album-repository.ts b/server/src/immich/api-v1/album/album-repository.ts index b32f83a96e..f73e36f20a 100644 --- a/server/src/immich/api-v1/album/album-repository.ts +++ b/server/src/immich/api-v1/album/album-repository.ts @@ -5,7 +5,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { AddAssetsDto } from './dto/add-assets.dto'; import { RemoveAssetsDto } from './dto/remove-assets.dto'; -import { AlbumCountResponseDto } from './response-dto/album-count-response.dto'; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto'; export interface IAlbumRepository { @@ -13,7 +12,6 @@ export interface IAlbumRepository { removeAssets(album: AlbumEntity, removeAssets: RemoveAssetsDto): Promise; addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise; updateThumbnails(): Promise; - getCountByUserId(userId: string): Promise; getSharedWithUserAlbumCount(userId: string, assetId: string): Promise; } @@ -26,14 +24,6 @@ export class AlbumRepository implements IAlbumRepository { @InjectRepository(AssetEntity) private assetRepository: Repository, ) {} - async getCountByUserId(userId: string): Promise { - const ownedAlbums = await this.albumRepository.find({ where: { ownerId: userId }, relations: ['sharedUsers'] }); - const sharedAlbums = await this.albumRepository.count({ where: { sharedUsers: { id: userId } } }); - const sharedAlbumCount = ownedAlbums.filter((album) => album.sharedUsers?.length > 0).length; - - return new AlbumCountResponseDto(ownedAlbums.length, sharedAlbums, sharedAlbumCount); - } - async get(albumId: string): Promise { return this.albumRepository.findOne({ where: { id: albumId }, diff --git a/server/src/immich/api-v1/album/album.controller.ts b/server/src/immich/api-v1/album/album.controller.ts index 02d06280a6..5d84a018f7 100644 --- a/server/src/immich/api-v1/album/album.controller.ts +++ b/server/src/immich/api-v1/album/album.controller.ts @@ -6,7 +6,6 @@ import { AddAssetsDto } from './dto/add-assets.dto'; import { RemoveAssetsDto } from './dto/remove-assets.dto'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { AlbumResponseDto } from '@app/domain'; -import { AlbumCountResponseDto } from './response-dto/album-count-response.dto'; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto'; import { Response as Res } from 'express'; import { DownloadDto } from '../asset/dto/download-library.dto'; @@ -22,11 +21,6 @@ import { handleDownload } from '../../app.utils'; export class AlbumController { constructor(private readonly service: AlbumService) {} - @Get('count-by-user-id') - getAlbumCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise { - return this.service.getCountByUserId(authUser); - } - @SharedLinkRoute() @Put(':id/assets') addAssetsToAlbum( diff --git a/server/src/immich/api-v1/album/album.service.spec.ts b/server/src/immich/api-v1/album/album.service.spec.ts index c864d767ac..81e66bfd49 100644 --- a/server/src/immich/api-v1/album/album.service.spec.ts +++ b/server/src/immich/api-v1/album/album.service.spec.ts @@ -98,7 +98,6 @@ describe('Album service', () => { get: jest.fn(), removeAssets: jest.fn(), updateThumbnails: jest.fn(), - getCountByUserId: jest.fn(), getSharedWithUserAlbumCount: jest.fn(), }; diff --git a/server/src/immich/api-v1/album/album.service.ts b/server/src/immich/api-v1/album/album.service.ts index 8db380a5ae..bda00573aa 100644 --- a/server/src/immich/api-v1/album/album.service.ts +++ b/server/src/immich/api-v1/album/album.service.ts @@ -4,7 +4,6 @@ import { AlbumEntity, SharedLinkType } from '@app/infra/entities'; import { RemoveAssetsDto } from './dto/remove-assets.dto'; import { AlbumResponseDto, mapAlbum } from '@app/domain'; import { IAlbumRepository } from './album-repository'; -import { AlbumCountResponseDto } from './response-dto/album-count-response.dto'; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto'; import { AddAssetsDto } from './dto/add-assets.dto'; import { DownloadService } from '../../modules/download/download.service'; @@ -90,10 +89,6 @@ export class AlbumService { }; } - async getCountByUserId(authUser: AuthUserDto): Promise { - return this.albumRepository.getCountByUserId(authUser.id); - } - async downloadArchive(authUser: AuthUserDto, albumId: string, dto: DownloadDto) { this.shareCore.checkDownloadAccess(authUser); diff --git a/server/src/immich/api-v1/album/response-dto/album-count-response.dto.ts b/server/src/immich/api-v1/album/response-dto/album-count-response.dto.ts deleted file mode 100644 index 9f5ae515f5..0000000000 --- a/server/src/immich/api-v1/album/response-dto/album-count-response.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class AlbumCountResponseDto { - @ApiProperty({ type: 'integer' }) - owned!: number; - - @ApiProperty({ type: 'integer' }) - shared!: number; - - @ApiProperty({ type: 'integer' }) - sharing!: number; - - constructor(owned: number, shared: number, sharing: number) { - this.owned = owned; - this.shared = shared; - this.sharing = sharing; - } -} diff --git a/server/src/immich/controllers/album.controller.ts b/server/src/immich/controllers/album.controller.ts index b00858a302..416275712a 100644 --- a/server/src/immich/controllers/album.controller.ts +++ b/server/src/immich/controllers/album.controller.ts @@ -1,4 +1,11 @@ -import { AddUsersDto, AlbumService, AuthUserDto, CreateAlbumDto, UpdateAlbumDto } from '@app/domain'; +import { + AddUsersDto, + AlbumCountResponseDto, + AlbumService, + AuthUserDto, + CreateAlbumDto, + UpdateAlbumDto, +} from '@app/domain'; import { GetAlbumsDto } from '@app/domain/album/dto/get-albums.dto'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -15,6 +22,11 @@ import { UUIDParamDto } from './dto/uuid-param.dto'; export class AlbumController { constructor(private service: AlbumService) {} + @Get('count') + getAlbumCount(@GetAuthUser() authUser: AuthUserDto): Promise { + return this.service.getCount(authUser); + } + @Get() getAllAlbums(@GetAuthUser() authUser: AuthUserDto, @Query() query: GetAlbumsDto) { return this.service.getAll(authUser, query); diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index a55acbb527..7876a9e5ed 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -210,7 +210,7 @@ export interface AlbumCountResponseDto { * @type {number} * @memberof AlbumCountResponseDto */ - 'sharing': number; + 'notShared': number; } /** * @@ -3678,8 +3678,8 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAlbumCountByUserId: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/album/count-by-user-id`; + getAlbumCount: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/album/count`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -4029,8 +4029,8 @@ export const AlbumApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAlbumCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAlbumCountByUserId(options); + async getAlbumCount(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAlbumCount(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -4163,8 +4163,8 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAlbumCountByUserId(options?: any): AxiosPromise { - return localVarFp.getAlbumCountByUserId(options).then((request) => request(axios, basePath)); + getAlbumCount(options?: any): AxiosPromise { + return localVarFp.getAlbumCount(options).then((request) => request(axios, basePath)); }, /** * @@ -4529,8 +4529,8 @@ export class AlbumApi extends BaseAPI { * @throws {RequiredError} * @memberof AlbumApi */ - public getAlbumCountByUserId(options?: AxiosRequestConfig) { - return AlbumApiFp(this.configuration).getAlbumCountByUserId(options).then((request) => request(this.axios, this.basePath)); + public getAlbumCount(options?: AxiosRequestConfig) { + return AlbumApiFp(this.configuration).getAlbumCount(options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/lib/components/shared-components/side-bar/side-bar.svelte b/web/src/lib/components/shared-components/side-bar/side-bar.svelte index 92db34f738..12513701f2 100644 --- a/web/src/lib/components/shared-components/side-bar/side-bar.svelte +++ b/web/src/lib/components/shared-components/side-bar/side-bar.svelte @@ -47,18 +47,10 @@ const getAlbumCount = async () => { try { - const { data: albumCount } = await api.albumApi.getAlbumCountByUserId(); - return { - shared: albumCount.shared, - sharing: albumCount.sharing, - owned: albumCount.owned - }; + const { data: albumCount } = await api.albumApi.getAlbumCount(); + return albumCount; } catch { - return { - shared: 0, - sharing: 0, - owned: 0 - }; + return { owned: 0, shared: 0, notShared: 0 }; } }; @@ -133,7 +125,7 @@ {:then data}
-

{(data.shared + data.sharing).toLocaleString($locale)} Albums

+

{data.shared.toLocaleString($locale)} Albums

{/await}