mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
feat: shared asset count
This commit is contained in:
parent
19d0f5b7aa
commit
0398a30edb
9 changed files with 75 additions and 12 deletions
|
|
@ -1954,6 +1954,7 @@
|
|||
"shared_album_activity_remove_title": "Delete Activity",
|
||||
"shared_album_section_people_action_error": "Error leaving/removing from album",
|
||||
"shared_album_section_people_title": "PEOPLE",
|
||||
"shared_assets_count": "{count, plural, one {# shared asset} other {# shared assets}}",
|
||||
"shared_by": "Shared by",
|
||||
"shared_by_user": "Shared by {user}",
|
||||
"shared_by_you": "Shared by you",
|
||||
|
|
|
|||
|
|
@ -21591,10 +21591,24 @@
|
|||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"ownedAssets": {
|
||||
"description": "Number of assets owned by the current user",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
},
|
||||
"sharedAssets": {
|
||||
"description": "Number of assets owned by other users, visible through shared albums",
|
||||
"maximum": 9007199254740991,
|
||||
"minimum": -9007199254740991,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assets"
|
||||
"assets",
|
||||
"ownedAssets",
|
||||
"sharedAssets"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1537,6 +1537,10 @@ export type AssetFaceUpdateDto = {
|
|||
export type PersonStatisticsResponseDto = {
|
||||
/** Number of assets */
|
||||
assets: number;
|
||||
/** Number of assets owned by the current user */
|
||||
ownedAssets: number;
|
||||
/** Number of assets owned by other users, visible through shared albums */
|
||||
sharedAssets: number;
|
||||
};
|
||||
export type PluginMethodResponseDto = {
|
||||
/** Description */
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ const AssetFaceDeleteSchema = z
|
|||
const PersonStatisticsResponseSchema = z
|
||||
.object({
|
||||
assets: z.int().describe('Number of assets'),
|
||||
ownedAssets: z.int().describe('Number of assets owned by the current user'),
|
||||
sharedAssets: z.int().describe('Number of assets owned by other users, visible through shared albums'),
|
||||
})
|
||||
.meta({ id: 'PersonStatisticsResponseDto' });
|
||||
|
||||
|
|
|
|||
|
|
@ -288,16 +288,35 @@ where
|
|||
|
||||
-- PersonRepository.getStatistics
|
||||
select
|
||||
count(distinct ("asset"."id")) as "count"
|
||||
count(distinct ("asset"."id")) as "count",
|
||||
count(distinct ("asset"."id")) filter (
|
||||
where
|
||||
"asset"."ownerId" = $1::uuid
|
||||
) as "ownedCount"
|
||||
from
|
||||
"asset_face"
|
||||
left join "asset" on "asset"."id" = "asset_face"."assetId"
|
||||
and "asset"."visibility" = 'timeline'
|
||||
and "asset"."deletedAt" is null
|
||||
and (
|
||||
"asset"."ownerId" = $2::uuid
|
||||
or exists (
|
||||
select
|
||||
1 as "exists"
|
||||
from
|
||||
"album_asset"
|
||||
inner join "album" on "album"."id" = "album_asset"."albumId"
|
||||
and "album"."deletedAt" is null
|
||||
inner join "album_user" on "album_user"."albumId" = "album"."id"
|
||||
and "album_user"."userId" = $3::uuid
|
||||
where
|
||||
"album_asset"."assetId" = "asset"."id"
|
||||
)
|
||||
)
|
||||
where
|
||||
"asset_face"."deletedAt" is null
|
||||
and "asset_face"."isVisible" is true
|
||||
and "asset_face"."personGroupId" = $1
|
||||
and "asset_face"."personGroupId" = $4
|
||||
|
||||
-- PersonRepository.getNumberOfPeople
|
||||
select
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
|||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||
import { PersonGroupTable } from 'src/schema/tables/person-group.table';
|
||||
import { PersonTable } from 'src/schema/tables/person.table';
|
||||
import { dummy, removeUndefinedKeys, withFilePath } from 'src/utils/database';
|
||||
import { asUuid, dummy, inSharedAlbum, removeUndefinedKeys, withFilePath } from 'src/utils/database';
|
||||
import { paginationHelper, PaginationOptions } from 'src/utils/pagination';
|
||||
|
||||
export interface PersonSearchOptions {
|
||||
|
|
@ -40,6 +40,8 @@ export interface UpdateFacesData {
|
|||
|
||||
export interface PersonStatistics {
|
||||
assets: number;
|
||||
ownedAssets: number;
|
||||
sharedAssets: number;
|
||||
}
|
||||
|
||||
export interface DeleteFacesOptions {
|
||||
|
|
@ -415,24 +417,36 @@ export class PersonRepository {
|
|||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getStatistics(personGroupId: string): Promise<PersonStatistics> {
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||
async getStatistics(personGroupId: string, userId: string): Promise<PersonStatistics> {
|
||||
const result = await this.db
|
||||
.selectFrom('asset_face')
|
||||
.leftJoin('asset', (join) =>
|
||||
join
|
||||
.onRef('asset.id', '=', 'asset_face.assetId')
|
||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.on('asset.deletedAt', 'is', null),
|
||||
.on('asset.deletedAt', 'is', null)
|
||||
.on((eb) => eb.or([eb('asset.ownerId', '=', asUuid(userId)), inSharedAlbum(eb, userId)])),
|
||||
)
|
||||
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
|
||||
.select((eb) => [
|
||||
eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'),
|
||||
eb.fn
|
||||
.count(eb.fn('distinct', ['asset.id']))
|
||||
.filterWhere('asset.ownerId', '=', asUuid(userId))
|
||||
.as('ownedCount'),
|
||||
])
|
||||
.where('asset_face.deletedAt', 'is', null)
|
||||
.where('asset_face.isVisible', 'is', true)
|
||||
.where('asset_face.personGroupId', '=', personGroupId)
|
||||
.executeTakeFirst();
|
||||
|
||||
const assets = result ? Number(result.count) : 0;
|
||||
const ownedAssets = result ? Number(result.ownedCount) : 0;
|
||||
|
||||
return {
|
||||
assets: result ? Number(result.count) : 0,
|
||||
assets,
|
||||
ownedAssets,
|
||||
sharedAssets: assets - ownedAssets,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1407,9 +1407,14 @@ describe(PersonService.name, () => {
|
|||
const person = PersonFactory.create();
|
||||
|
||||
mocks.person.getByGroupId.mockResolvedValue(person);
|
||||
mocks.person.getStatistics.mockResolvedValue({ assets: 3 });
|
||||
mocks.person.getStatistics.mockResolvedValue({ assets: 3, ownedAssets: 2, sharedAssets: 1 });
|
||||
mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set([person.personGroupId]));
|
||||
await expect(sut.getStatistics(auth, person.personGroupId)).resolves.toEqual({ assets: 3 });
|
||||
await expect(sut.getStatistics(auth, person.personGroupId)).resolves.toEqual({
|
||||
assets: 3,
|
||||
ownedAssets: 2,
|
||||
sharedAssets: 1,
|
||||
});
|
||||
expect(mocks.person.getStatistics).toHaveBeenCalledWith(person.personGroupId, auth.user.id);
|
||||
expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(auth.user.id, new Set([person.personGroupId]));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export class PersonService extends BaseService {
|
|||
|
||||
async getStatistics(auth: AuthDto, personGroupId: string): Promise<PersonStatisticsResponseDto> {
|
||||
await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [personGroupId] });
|
||||
return this.personRepository.getStatistics(personGroupId);
|
||||
return this.personRepository.getStatistics(personGroupId, auth.user.id);
|
||||
}
|
||||
|
||||
async getThumbnail(auth: AuthDto, personGroupId: string): Promise<ImmichFileResponse> {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
let { data }: Props = $props();
|
||||
|
||||
let numberOfAssets = $derived(data.statistics.assets);
|
||||
let numberOfSharedAssets = $derived(data.statistics.sharedAssets);
|
||||
let person = $derived(data.person);
|
||||
let thumbnailData = $derived(getPeopleThumbnailUrl(person));
|
||||
|
||||
|
|
@ -395,6 +396,9 @@
|
|||
<p class="w-40 truncate font-medium sm:w-72">{person.name || $t('add_a_name')}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{$t('assets_count', { values: { count: numberOfAssets } })}
|
||||
{#if numberOfSharedAssets > 0}
|
||||
· {$t('shared_assets_count', { values: { count: numberOfSharedAssets } })}
|
||||
{/if}
|
||||
</p>
|
||||
{#if person.birthDate}
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue