fix: person without assets (#32117)
fix: person without assets (#32114) (cherry picked from commit c0506fe723995808f4effd8d40d0235fcaf104b5) Co-authored-by: Jason Rasmussen <jason@rasm.me> Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
This commit is contained in:
co-authored by
Jason Rasmussen
Daniel Dietzler
parent
d1c1a65ed3
commit
d806bc002c
@@ -243,15 +243,29 @@ from
|
||||
"person_group"
|
||||
inner join "person" as "owned" on "owned"."personGroupId" = "person_group"."id"
|
||||
and "owned"."ownerId" = $6
|
||||
left join "asset_face" on "asset_face"."personGroupId" = "person_group"."id"
|
||||
inner join "asset_face" on "asset_face"."personGroupId" = "person_group"."id"
|
||||
and "asset_face"."deletedAt" is null
|
||||
and "asset_face"."isVisible" is true
|
||||
left join "asset" on "asset"."id" = "asset_face"."assetId"
|
||||
and "asset"."ownerId" = $7
|
||||
inner join "asset" on "asset"."id" = "asset_face"."assetId"
|
||||
and "asset"."visibility" = 'timeline'
|
||||
and "asset"."deletedAt" is null
|
||||
and (
|
||||
"asset"."ownerId" = any ($7::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" = $8::uuid
|
||||
where
|
||||
"album_asset"."assetId" = "asset"."id"
|
||||
)
|
||||
)
|
||||
where
|
||||
"owned"."isHidden" = $8
|
||||
"owned"."isHidden" = $9
|
||||
and 1 = 1
|
||||
group by
|
||||
"person_group"."id",
|
||||
@@ -259,19 +273,7 @@ group by
|
||||
"owned"."personGroupId"
|
||||
having
|
||||
(
|
||||
exists (
|
||||
select
|
||||
"person_user"."sharedWithId"
|
||||
from
|
||||
"person_user"
|
||||
where
|
||||
"person_user"."personGroupId" = "person_group"."id"
|
||||
and "person_user"."sharedWithId" = $9
|
||||
)
|
||||
or (
|
||||
count("asset"."id") > $10
|
||||
and "owned"."name" != $11
|
||||
)
|
||||
"owned"."name" != $10
|
||||
or count("asset"."id") >= COALESCE(
|
||||
(
|
||||
SELECT
|
||||
@@ -279,7 +281,7 @@ having
|
||||
FROM
|
||||
user_metadata
|
||||
WHERE
|
||||
"userId" = $12
|
||||
"userId" = $11
|
||||
AND key = 'preferences'
|
||||
),
|
||||
'3'
|
||||
@@ -293,9 +295,9 @@ order by
|
||||
NULLIF("owned"."name", '') asc nulls last,
|
||||
"owned"."createdAt"
|
||||
limit
|
||||
$13
|
||||
$12
|
||||
offset
|
||||
$14
|
||||
$13
|
||||
|
||||
-- PersonRepository.getAllWithoutFaces
|
||||
select
|
||||
@@ -900,60 +902,73 @@ where
|
||||
)
|
||||
|
||||
-- PersonRepository.getNumberOfPeople
|
||||
with
|
||||
"similarity_threshold" as (
|
||||
select
|
||||
set_config('pg_trgm.word_similarity_threshold', '0.5', true) as "thresh"
|
||||
)
|
||||
select
|
||||
coalesce(count(*), 0) as "total",
|
||||
coalesce(
|
||||
count(*) filter (
|
||||
where
|
||||
"owned"."isHidden" = $1
|
||||
"people"."isHidden" = $1
|
||||
),
|
||||
0
|
||||
) as "hidden"
|
||||
from
|
||||
"similarity_threshold",
|
||||
"person_group"
|
||||
left join "person" as "owned" on "owned"."personGroupId" = "person_group"."id"
|
||||
and "owned"."ownerId" = $2
|
||||
where
|
||||
(
|
||||
(
|
||||
"owned"."ownerId" is not null
|
||||
and exists (
|
||||
with
|
||||
"similarity_threshold" as (
|
||||
select
|
||||
from
|
||||
"asset_face"
|
||||
where
|
||||
"asset_face"."personGroupId" = "person_group"."id"
|
||||
and "asset_face"."deletedAt" is null
|
||||
and "asset_face"."isVisible" = $3
|
||||
and exists (
|
||||
select
|
||||
from
|
||||
"asset"
|
||||
where
|
||||
"asset"."id" = "asset_face"."assetId"
|
||||
and "asset"."ownerId" = $4
|
||||
and "asset"."visibility" = 'timeline'
|
||||
and "asset"."deletedAt" is null
|
||||
)
|
||||
set_config('pg_trgm.word_similarity_threshold', '0.5', true) as "thresh"
|
||||
)
|
||||
)
|
||||
or exists (
|
||||
select
|
||||
"person_user"."sharedById"
|
||||
from
|
||||
"person_user"
|
||||
where
|
||||
"person_user"."personGroupId" = "person_group"."id"
|
||||
and "person_user"."sharedWithId" = $5
|
||||
)
|
||||
)
|
||||
and 1 = 1
|
||||
select
|
||||
"owned"."isHidden"
|
||||
from
|
||||
"similarity_threshold",
|
||||
"person_group"
|
||||
inner join "person" as "owned" on "owned"."personGroupId" = "person_group"."id"
|
||||
and "owned"."ownerId" = $2
|
||||
inner join "asset_face" on "asset_face"."personGroupId" = "person_group"."id"
|
||||
and "asset_face"."deletedAt" is null
|
||||
and "asset_face"."isVisible" is true
|
||||
inner join "asset" on "asset"."id" = "asset_face"."assetId"
|
||||
and "asset"."visibility" = 'timeline'
|
||||
and "asset"."deletedAt" is null
|
||||
and (
|
||||
"asset"."ownerId" = any ($3::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" = $4::uuid
|
||||
where
|
||||
"album_asset"."assetId" = "asset"."id"
|
||||
)
|
||||
)
|
||||
where
|
||||
1 = 1
|
||||
group by
|
||||
"person_group"."id",
|
||||
"owned"."ownerId",
|
||||
"owned"."personGroupId"
|
||||
having
|
||||
(
|
||||
"owned"."name" != $5
|
||||
or count("asset"."id") >= COALESCE(
|
||||
(
|
||||
SELECT
|
||||
value -> 'people' ->> 'minimumFaces'
|
||||
FROM
|
||||
user_metadata
|
||||
WHERE
|
||||
"userId" = $6
|
||||
AND key = 'preferences'
|
||||
),
|
||||
'3'
|
||||
)::int
|
||||
)
|
||||
) as "people"
|
||||
|
||||
-- PersonRepository.createGroup
|
||||
insert into
|
||||
|
||||
@@ -42,7 +42,11 @@ export interface PersonFilterOptions {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface PersonSearchOptions extends PersonFilterOptions {
|
||||
export interface PersonCountOptions extends PersonFilterOptions {
|
||||
partnerIds: string[];
|
||||
}
|
||||
|
||||
export interface PersonSearchOptions extends PersonCountOptions {
|
||||
withHidden: boolean;
|
||||
closestFaceAssetId?: string;
|
||||
}
|
||||
@@ -436,9 +440,8 @@ export class PersonRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] })
|
||||
async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) {
|
||||
const items = await this.db
|
||||
private getVisiblePeopleQuery(userId: string, partnerIds: string[]) {
|
||||
return this.db
|
||||
.with('similarity_threshold', (db) =>
|
||||
db.selectNoFrom(sql`set_config('pg_trgm.word_similarity_threshold', '0.5', true)`.as('thresh')),
|
||||
)
|
||||
@@ -446,33 +449,23 @@ export class PersonRepository {
|
||||
.innerJoin('person as owned', (join) =>
|
||||
join.onRef('owned.personGroupId', '=', 'person_group.id').on('owned.ownerId', '=', userId),
|
||||
)
|
||||
.leftJoin('asset_face', (join) =>
|
||||
.innerJoin('asset_face', (join) =>
|
||||
join
|
||||
.onRef('asset_face.personGroupId', '=', 'person_group.id')
|
||||
.on('asset_face.deletedAt', 'is', null)
|
||||
.on('asset_face.isVisible', 'is', true),
|
||||
)
|
||||
.leftJoin('asset', (join) =>
|
||||
.innerJoin('asset', (join) =>
|
||||
join
|
||||
.onRef('asset.id', '=', 'asset_face.assetId')
|
||||
.on('asset.ownerId', '=', userId)
|
||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.on('asset.deletedAt', 'is', null),
|
||||
.on('asset.deletedAt', 'is', null)
|
||||
.on((eb) => eb.or([eb('asset.ownerId', '=', anyUuid([userId, ...partnerIds])), inSharedAlbum(eb, userId)])),
|
||||
)
|
||||
.select(withOwnedPerson(userId))
|
||||
.select(withOtherPeople(userId))
|
||||
.groupBy(['person_group.id', 'owned.ownerId', 'owned.personGroupId'])
|
||||
.having((eb) =>
|
||||
eb.or([
|
||||
// shared people are always included
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('person_user')
|
||||
.select('person_user.sharedWithId')
|
||||
.whereRef('person_user.personGroupId', '=', 'person_group.id')
|
||||
.where('person_user.sharedWithId', '=', userId),
|
||||
),
|
||||
eb.and([eb(faceCount, '>', 0), eb('owned.name', '!=', '')]),
|
||||
eb('owned.name', '!=', ''),
|
||||
eb(
|
||||
faceCount,
|
||||
'>=',
|
||||
@@ -485,10 +478,19 @@ export class PersonRepository {
|
||||
)::int `,
|
||||
),
|
||||
]),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@GenerateSql({
|
||||
params: [{ take: 1, skip: 0 }, DummyValue.UUID, { withHidden: false, partnerIds: [DummyValue.UUID] }],
|
||||
})
|
||||
async getAllForUser(pagination: PaginationOptions, userId: string, options: PersonSearchOptions) {
|
||||
const items = await this.getVisiblePeopleQuery(userId, options.partnerIds)
|
||||
.select(withOwnedPerson(userId))
|
||||
.select(withOtherPeople(userId))
|
||||
.orderBy('owned.isHidden', 'asc')
|
||||
.orderBy('owned.isFavorite', 'desc')
|
||||
.$if(!!options?.closestFaceAssetId, (qb) =>
|
||||
.$if(!!options.closestFaceAssetId, (qb) =>
|
||||
qb.orderBy((eb) =>
|
||||
eb(
|
||||
(eb) =>
|
||||
@@ -501,11 +503,11 @@ export class PersonRepository {
|
||||
eb
|
||||
.selectFrom('face_search')
|
||||
.select('face_search.embedding')
|
||||
.where('face_search.faceId', '=', options!.closestFaceAssetId!),
|
||||
.where('face_search.faceId', '=', options.closestFaceAssetId!),
|
||||
),
|
||||
),
|
||||
)
|
||||
.$if(!options?.closestFaceAssetId, (qb) =>
|
||||
.$if(!options.closestFaceAssetId, (qb) =>
|
||||
qb
|
||||
.orderBy(sql`NULLIF("owned"."name", '') is null`, 'asc')
|
||||
.orderBy(faceCount, 'desc')
|
||||
@@ -513,9 +515,9 @@ export class PersonRepository {
|
||||
.orderBy('owned.createdAt'),
|
||||
)
|
||||
// an explicit isHidden filter takes precedence over withHidden
|
||||
.$if(!options?.withHidden && options?.isHidden === undefined, (qb) => qb.where('owned.isHidden', '=', false))
|
||||
.$if(!options.withHidden && options.isHidden === undefined, (qb) => qb.where('owned.isHidden', '=', false))
|
||||
.where(withFilters(userId, options))
|
||||
.$if(!!options?.name, (qb) => qb.orderBy(sql`f_unaccent("owned"."name") <->>> f_unaccent(${options!.name!})`))
|
||||
.$if(!!options.name, (qb) => qb.orderBy(sql`f_unaccent("owned"."name") <->>> f_unaccent(${options.name!})`))
|
||||
.offset(pagination.skip ?? 0)
|
||||
.limit(pagination.take + 1)
|
||||
.execute();
|
||||
@@ -736,52 +738,19 @@ export class PersonRepository {
|
||||
assets: result ? Number(result.count) : 0,
|
||||
};
|
||||
}
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getNumberOfPeople(userId: string, options?: PersonFilterOptions) {
|
||||
@GenerateSql({ params: [DummyValue.UUID, { partnerIds: [DummyValue.UUID] }] })
|
||||
getNumberOfPeople(userId: string, options: PersonCountOptions) {
|
||||
const zero = sql.lit(0);
|
||||
return this.db
|
||||
.with('similarity_threshold', (db) =>
|
||||
db.selectNoFrom(sql`set_config('pg_trgm.word_similarity_threshold', '0.5', true)`.as('thresh')),
|
||||
.selectFrom(
|
||||
this.getVisiblePeopleQuery(userId, options.partnerIds)
|
||||
.select('owned.isHidden')
|
||||
.where(withFilters(userId, options))
|
||||
.as('people'),
|
||||
)
|
||||
.selectFrom(['similarity_threshold', 'person_group'])
|
||||
.leftJoin('person as owned', (join) =>
|
||||
join.onRef('owned.personGroupId', '=', 'person_group.id').on('owned.ownerId', '=', userId),
|
||||
)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb.and([
|
||||
eb('owned.ownerId', 'is not', null),
|
||||
eb.exists((eb) =>
|
||||
eb
|
||||
.selectFrom('asset_face')
|
||||
.whereRef('asset_face.personGroupId', '=', 'person_group.id')
|
||||
.where('asset_face.deletedAt', 'is', null)
|
||||
.where('asset_face.isVisible', '=', true)
|
||||
.where((eb) =>
|
||||
eb.exists((eb) =>
|
||||
eb
|
||||
.selectFrom('asset')
|
||||
.whereRef('asset.id', '=', 'asset_face.assetId')
|
||||
.where('asset.ownerId', '=', userId)
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.where('asset.deletedAt', 'is', null),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
eb.exists((eb) =>
|
||||
eb
|
||||
.selectFrom('person_user')
|
||||
.select('person_user.sharedById')
|
||||
.whereRef('person_user.personGroupId', '=', 'person_group.id')
|
||||
.where('person_user.sharedWithId', '=', userId),
|
||||
),
|
||||
]),
|
||||
)
|
||||
.where(withFilters(userId, options))
|
||||
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>(), zero).as('total'))
|
||||
.select((eb) =>
|
||||
eb.fn.coalesce(eb.fn.countAll<number>().filterWhere('owned.isHidden', '=', true), zero).as('hidden'),
|
||||
eb.fn.coalesce(eb.fn.countAll<number>().filterWhere('people.isHidden', '=', true), zero).as('hidden'),
|
||||
)
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ describe(PersonService.name, () => {
|
||||
hasNextPage: false,
|
||||
});
|
||||
mocks.person.getNumberOfPeople.mockResolvedValue({ total: 2, hidden: 1 });
|
||||
mocks.partner.getAll.mockResolvedValue([]);
|
||||
await expect(sut.getAll(auth, { withHidden: true, page: 1, size: 10 })).resolves.toEqual({
|
||||
hasNextPage: false,
|
||||
total: 2,
|
||||
@@ -65,6 +66,7 @@ describe(PersonService.name, () => {
|
||||
});
|
||||
expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, auth.user.id, {
|
||||
withHidden: true,
|
||||
partnerIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,6 +79,7 @@ describe(PersonService.name, () => {
|
||||
hasNextPage: false,
|
||||
});
|
||||
mocks.person.getNumberOfPeople.mockResolvedValue({ total: 2, hidden: 1 });
|
||||
mocks.partner.getAll.mockResolvedValue([]);
|
||||
await expect(sut.getAll(auth, { withHidden: false, page: 1, size: 10 })).resolves.toEqual({
|
||||
hasNextPage: false,
|
||||
total: 2,
|
||||
@@ -91,6 +94,7 @@ describe(PersonService.name, () => {
|
||||
});
|
||||
expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, auth.user.id, {
|
||||
withHidden: false,
|
||||
partnerIds: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,12 +77,18 @@ export class PersonService extends BaseService {
|
||||
}
|
||||
closestFaceAssetId = person.faceAssetId;
|
||||
}
|
||||
const partnerIds = await getMyPartnerIds({
|
||||
userId: auth.user.id,
|
||||
repository: this.partnerRepository,
|
||||
timelineEnabled: true,
|
||||
});
|
||||
const { items, hasNextPage } = await this.personRepository.getAllForUser(pagination, auth.user.id, {
|
||||
withHidden,
|
||||
partnerIds,
|
||||
closestFaceAssetId,
|
||||
...filters,
|
||||
});
|
||||
const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id, filters);
|
||||
const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id, { partnerIds, ...filters });
|
||||
|
||||
return {
|
||||
people: items.map((person) => mapPerson(person)),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PersonUserRole } from 'src/dtos/person.dto.js';
|
||||
import { AssetFileType } from 'src/enum.js';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository.js';
|
||||
import { PersonUserRepository } from 'src/repositories/person-user.repository.js';
|
||||
import { PersonRepository } from 'src/repositories/person.repository.js';
|
||||
import { PersonRepository, type PersonSearchOptions } from 'src/repositories/person.repository.js';
|
||||
import { DB } from 'src/schema/index.js';
|
||||
import { BaseService } from 'src/services/base.service.js';
|
||||
import { MediumTestContext, newMediumService } from 'test/medium.factory.js';
|
||||
@@ -20,8 +20,12 @@ const setup = (db?: Kysely<DB>) => {
|
||||
return { ctx, sut: ctx.get(PersonRepository) };
|
||||
};
|
||||
|
||||
const listFor = async (sut: PersonRepository, userId: string, options?: { withHidden: boolean }) => {
|
||||
const { items } = await sut.getAllForUser({ take: 100, skip: 0 }, userId, options);
|
||||
const listFor = async (sut: PersonRepository, userId: string, options?: Partial<PersonSearchOptions>) => {
|
||||
const { items } = await sut.getAllForUser({ take: 100, skip: 0 }, userId, {
|
||||
withHidden: false,
|
||||
partnerIds: [],
|
||||
...options,
|
||||
});
|
||||
return items;
|
||||
};
|
||||
|
||||
@@ -33,8 +37,14 @@ const newNamedPerson = async (ctx: MediumTestContext, ownerId: string, name: str
|
||||
return person;
|
||||
};
|
||||
|
||||
const otherPeopleOf = async (sut: PersonRepository, ctx: MediumTestContext, userId: string, groupId: string) => {
|
||||
const listItems = await listFor(sut, userId);
|
||||
const otherPeopleOf = async (
|
||||
sut: PersonRepository,
|
||||
ctx: MediumTestContext,
|
||||
userId: string,
|
||||
groupId: string,
|
||||
partnerIds: string[],
|
||||
) => {
|
||||
const listItems = await listFor(sut, userId, { partnerIds });
|
||||
const listed = listItems.find((item) => item.personGroupId === groupId);
|
||||
const forUser = await sut.getForUser({ userId, personGroupId: groupId });
|
||||
const byGroupId = await sut.getByGroupId({ ownerId: userId, personGroupId: groupId });
|
||||
@@ -171,7 +181,7 @@ describe(PersonRepository.name, () => {
|
||||
]);
|
||||
|
||||
const expected = [{ sharedById: owner.id, role: PersonUserRole.Write, name: 'Alice', birthDate: null }];
|
||||
const results = await otherPeopleOf(sut, ctx, recipient.id, person.personGroupId);
|
||||
const results = await otherPeopleOf(sut, ctx, recipient.id, person.personGroupId, [owner.id]);
|
||||
|
||||
expect(results.getAllForUser).toEqual(expected);
|
||||
expect(results.getForUser).toEqual(expected);
|
||||
@@ -203,7 +213,7 @@ describe(PersonRepository.name, () => {
|
||||
]);
|
||||
|
||||
const expected = [{ sharedById: owner.id, role: PersonUserRole.Read, name: 'Alice', birthDate: null }];
|
||||
const results = await otherPeopleOf(sut, ctx, first.id, person.personGroupId);
|
||||
const results = await otherPeopleOf(sut, ctx, first.id, person.personGroupId, [owner.id]);
|
||||
|
||||
expect(results.getAllForUser).toEqual(expected);
|
||||
expect(results.getForUser).toEqual(expected);
|
||||
@@ -220,7 +230,7 @@ describe(PersonRepository.name, () => {
|
||||
.values({ ownerId: other.id, name: 'Alice', personGroupId: person.personGroupId })
|
||||
.execute();
|
||||
|
||||
const results = await otherPeopleOf(sut, ctx, other.id, person.personGroupId);
|
||||
const results = await otherPeopleOf(sut, ctx, other.id, person.personGroupId, [owner.id]);
|
||||
|
||||
expect(results.getForUser).toEqual([]);
|
||||
expect(results.getByGroupId).toEqual([]);
|
||||
@@ -464,7 +474,7 @@ describe(PersonRepository.name, () => {
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(listFor(sut, sharedWith.id)).resolves.toEqual([
|
||||
await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
ownerId: sharedWith.id,
|
||||
personGroupId: person.personGroupId,
|
||||
@@ -475,10 +485,9 @@ describe(PersonRepository.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return a shared person even when it has no faces of its own', async () => {
|
||||
it('should not return a shared person without any visible faces', async () => {
|
||||
const { ctx, sut } = setup(await getKyselyDB());
|
||||
const [{ user: owner }, { user: sharedWith }] = [await ctx.newUser(), await ctx.newUser()];
|
||||
// no faces at all, so only the "shared people are always included" rule can let it through
|
||||
const { result: person } = await ctx.newPerson({ ownerId: owner.id, name: 'Alice' });
|
||||
|
||||
await ctx.get(PersonUserRepository).createAll([
|
||||
@@ -490,9 +499,72 @@ describe(PersonRepository.name, () => {
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(listFor(sut, sharedWith.id)).resolves.toEqual([
|
||||
expect.objectContaining({ ownerId: sharedWith.id, personGroupId: person.personGroupId }),
|
||||
await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should not return a shared person whose faces are only on assets the user cannot see', async () => {
|
||||
const { ctx, sut } = setup(await getKyselyDB());
|
||||
const [{ user: owner }, { user: sharedWith }] = [await ctx.newUser(), await ctx.newUser()];
|
||||
const person = await newNamedPerson(ctx, owner.id, 'Alice');
|
||||
|
||||
await ctx.get(PersonUserRepository).createAll([
|
||||
{
|
||||
personGroupId: person.personGroupId,
|
||||
sharedById: owner.id,
|
||||
sharedWithId: sharedWith.id,
|
||||
role: PersonUserRole.Read,
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(listFor(sut, sharedWith.id)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should count faces on partner assets', async () => {
|
||||
const { ctx, sut } = setup(await getKyselyDB());
|
||||
const [{ user: owner }, { user: partner }] = [await ctx.newUser(), await ctx.newUser()];
|
||||
const { result: person } = await ctx.newPerson({ ownerId: owner.id, name: '' });
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const { asset } = await ctx.newAsset({ ownerId: partner.id });
|
||||
await ctx.newAssetFace({ assetId: asset.id, personGroupId: person.personGroupId });
|
||||
}
|
||||
|
||||
await expect(listFor(sut, owner.id)).resolves.toEqual([]);
|
||||
await expect(listFor(sut, owner.id, { partnerIds: [partner.id] })).resolves.toEqual([
|
||||
expect.objectContaining({ ownerId: owner.id, personGroupId: person.personGroupId }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should count people with the same rules as the list', async () => {
|
||||
const { ctx, sut } = setup(await getKyselyDB());
|
||||
const [{ user: owner }, { user: partner }] = [await ctx.newUser(), await ctx.newUser()];
|
||||
const named = await newNamedPerson(ctx, owner.id, 'Alice');
|
||||
await ctx.database
|
||||
.updateTable('person')
|
||||
.set({ isHidden: true })
|
||||
.where('personGroupId', '=', named.personGroupId)
|
||||
.execute();
|
||||
await ctx.newPerson({ ownerId: owner.id, name: '' });
|
||||
const { result: onPartnerAssets } = await ctx.newPerson({ ownerId: owner.id, name: '' });
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const { asset } = await ctx.newAsset({ ownerId: partner.id });
|
||||
await ctx.newAssetFace({ assetId: asset.id, personGroupId: onPartnerAssets.personGroupId });
|
||||
}
|
||||
|
||||
await expect(sut.getNumberOfPeople(owner.id, { partnerIds: [] })).resolves.toEqual({ total: 1, hidden: 1 });
|
||||
await expect(sut.getNumberOfPeople(owner.id, { partnerIds: [partner.id] })).resolves.toEqual({
|
||||
total: 2,
|
||||
hidden: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should require unnamed people to meet the minimum face count', async () => {
|
||||
const { ctx, sut } = setup(await getKyselyDB());
|
||||
const { user } = await ctx.newUser();
|
||||
const { result: person } = await ctx.newPerson({ ownerId: user.id, name: '' });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newAssetFace({ assetId: asset.id, personGroupId: person.personGroupId });
|
||||
|
||||
await expect(listFor(sut, user.id)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should stop returning a shared person once the user deletes their copy', async () => {
|
||||
@@ -508,11 +580,11 @@ describe(PersonRepository.name, () => {
|
||||
role: PersonUserRole.Read,
|
||||
},
|
||||
]);
|
||||
await expect(listFor(sut, sharedWith.id)).resolves.toHaveLength(1);
|
||||
await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toHaveLength(1);
|
||||
|
||||
await sut.delete([person.personGroupId], sharedWith.id);
|
||||
|
||||
await expect(listFor(sut, sharedWith.id)).resolves.toEqual([]);
|
||||
await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toEqual([]);
|
||||
// the owner still has theirs
|
||||
await expect(listFor(sut, owner.id)).resolves.toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DatabaseRepository } from 'src/repositories/database.repository.js';
|
||||
import { JobRepository } from 'src/repositories/job.repository.js';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository.js';
|
||||
import { MachineLearningRepository } from 'src/repositories/machine-learning.repository.js';
|
||||
import { PartnerRepository } from 'src/repositories/partner.repository.js';
|
||||
import { PersonUserRepository } from 'src/repositories/person-user.repository.js';
|
||||
import { PersonRepository } from 'src/repositories/person.repository.js';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository.js';
|
||||
@@ -42,6 +43,7 @@ const setup = (db?: Kysely<DB>) => {
|
||||
SystemMetadataRepository,
|
||||
UserRepository,
|
||||
ClusterGroupRepository,
|
||||
PartnerRepository,
|
||||
],
|
||||
mock: [JobRepository, LoggingRepository, StorageRepository, MachineLearningRepository],
|
||||
});
|
||||
@@ -124,6 +126,7 @@ describe(PersonService.name, () => {
|
||||
const { person: sharedWithMe } = await ctx.newPerson({ ownerId: user3.id, name: 'Shared with me' });
|
||||
await ctx.newAssetFace({ assetId: asset.id, personGroupId: sharedByMe.personGroupId });
|
||||
await ctx.newAssetFace({ assetId: asset.id, personGroupId: notShared.personGroupId });
|
||||
await ctx.newAssetFace({ assetId: asset.id, personGroupId: sharedWithMe.personGroupId });
|
||||
await ctx.newPersonUser({
|
||||
personGroupId: sharedByMe.personGroupId,
|
||||
sharedById: user1.id,
|
||||
|
||||
Reference in New Issue
Block a user