This commit is contained in:
maxinegardenas 2026-08-14 00:28:41 +01:00 committed by GitHub
commit 785fe6dd21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 197 additions and 56 deletions

View file

@ -28,21 +28,15 @@ select
"person".*
from
"person"
inner join "asset_face" on "asset_face"."personId" = "person"."id"
inner join "asset" on "asset_face"."assetId" = "asset"."id"
and "asset"."visibility" = 'timeline'
and "asset"."deletedAt" is null
where
"person"."ownerId" = $1
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" is true
and "person"."isHidden" = $2
group by
"person"."id"
having
(
"person"."name" != $3
or count("asset_face"."assetId") >= COALESCE(
or "person"."assetCount" >= COALESCE(
(
SELECT
value -> 'people' ->> 'minimumFaces'
@ -59,7 +53,7 @@ order by
"person"."isHidden" asc,
"person"."isFavorite" desc,
NULLIF(person.name, '') is null asc,
count("asset_face"."assetId") desc,
"person"."assetCount" desc,
NULLIF(person.name, '') asc nulls last,
"person"."createdAt"
limit
@ -262,24 +256,7 @@ select
from
"person"
where
exists (
select
from
"asset_face"
where
"asset_face"."personId" = "person"."id"
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" = $2
and exists (
select
from
"asset"
where
"asset"."id" = "asset_face"."assetId"
and "asset"."visibility" = 'timeline'
and "asset"."deletedAt" is null
)
)
"person"."assetCount" > $2
and "person"."ownerId" = $3
-- PersonRepository.refreshFaces

View file

@ -152,23 +152,14 @@ export class PersonRepository {
const items = await this.db
.selectFrom('person')
.selectAll('person')
.innerJoin('asset_face', 'asset_face.personId', 'person.id')
.innerJoin('asset', (join) =>
join
.onRef('asset_face.assetId', '=', 'asset.id')
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
.on('asset.deletedAt', 'is', null),
)
.where('person.ownerId', '=', userId)
.where('asset_face.deletedAt', 'is', null)
.where('asset_face.isVisible', 'is', true)
.orderBy('person.isHidden', 'asc')
.orderBy('person.isFavorite', 'desc')
.having((eb) =>
eb.or([
eb('person.name', '!=', ''),
eb(
(innerEb) => innerEb.fn.count('asset_face.assetId'),
'person.assetCount',
'>=',
sql<number>`COALESCE(
(SELECT value -> 'people' ->> 'minimumFaces'
@ -201,7 +192,7 @@ export class PersonRepository {
.$if(!options?.closestFaceAssetId, (qb) =>
qb
.orderBy(sql`NULLIF(person.name, '') is null`, 'asc')
.orderBy((eb) => eb.fn.count('asset_face.assetId'), 'desc')
.orderBy('person.assetCount', 'desc')
.orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast())
.orderBy('person.createdAt'),
)
@ -369,24 +360,7 @@ export class PersonRepository {
const zero = sql.lit(0);
return this.db
.selectFrom('person')
.where((eb) =>
eb.exists((eb) =>
eb
.selectFrom('asset_face')
.whereRef('asset_face.personId', '=', 'person.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.visibility', '=', sql.lit(AssetVisibility.Timeline))
.where('asset.deletedAt', 'is', null),
),
),
),
)
.where('person.assetCount', '>', 0)
.where('person.ownerId', '=', userId)
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>(), zero).as('total'))
.select((eb) => eb.fn.coalesce(eb.fn.countAll<number>().filterWhere('isHidden', '=', true), zero).as('hidden'))

View file

@ -219,6 +219,66 @@ export const person_delete_audit = registerFunction({
END`,
});
export const person_increment_asset_count = registerFunction({
name: 'person_increment_asset_count',
returnType: 'TRIGGER',
language: 'PLPGSQL',
body: `
BEGIN
IF NEW."deletedAt" IS NULL AND NEW."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
END IF;
RETURN NULL;
END`,
});
export const person_update_asset_count = registerFunction({
name: 'person_update_asset_count',
returnType: 'TRIGGER',
language: 'PLPGSQL',
body: `
BEGIN
IF OLD."personId" != NEW."personId" AND OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
END IF;
IF OLD."personId" != NEW."personId" AND NEW."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
END IF;
IF
OLD."personId" = NEW."personId" AND
(OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL) OR
(OLD."isVisible" IS true AND NEW."isVisible" IS false)
THEN
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = NEW."personId";
END IF;
IF
OLD."personId" = NEW."personId" AND
(OLD."deletedAt" IS NOT NULL AND NEW."deletedAt" IS NULL) OR
(OLD."isVisible" IS false AND NEW."isVisible" IS true)
THEN
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
END IF;
RETURN NULL;
END`,
});
export const person_decrement_asset_count = registerFunction({
name: 'person_decrement_asset_count',
returnType: 'TRIGGER',
language: 'PLPGSQL',
body: `
BEGIN
IF OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
END IF;
RETURN NULL;
END`,
});
export const user_metadata_audit = registerFunction({
name: 'user_metadata_audit',
returnType: 'TRIGGER',

View file

@ -0,0 +1,100 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await sql`ALTER TABLE "person" ADD "assetCount" integer NOT NULL DEFAULT 0;`.execute(db);
await sql`UPDATE "person" SET "assetCount" = (
SELECT COUNT("assetId")
FROM "asset_face"
WHERE "personId" = "person".id AND "deletedAt" IS NULL AND "isVisible" IS true
)`.execute(db);
await sql`CREATE INDEX "idx_person_assetCount" ON "person" ("assetCount");`.execute(db);
await sql`CREATE OR REPLACE FUNCTION person_increment_asset_count()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
IF NEW."deletedAt" IS NULL AND NEW."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
END IF;
RETURN NULL;
END
$$;`.execute(db);
await sql`CREATE OR REPLACE FUNCTION person_update_asset_count()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
IF OLD."personId" != NEW."personId" AND OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
END IF;
IF OLD."personId" != NEW."personId" AND NEW."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
END IF;
IF
OLD."personId" = NEW."personId" AND
(OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL) OR
(OLD."isVisible" IS true AND NEW."isVisible" IS false)
THEN
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = NEW."personId";
END IF;
IF
OLD."personId" = NEW."personId" AND
(OLD."deletedAt" IS NOT NULL AND NEW."deletedAt" IS NULL) OR
(OLD."isVisible" IS false AND NEW."isVisible" IS true)
THEN
UPDATE person SET "assetCount" = "assetCount" + 1 WHERE id = NEW."personId";
END IF;
RETURN NULL;
END
$$;`.execute(db);
await sql`CREATE OR REPLACE FUNCTION person_decrement_asset_count()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
IF OLD."deletedAt" IS NULL AND OLD."isVisible" IS true THEN
UPDATE person SET "assetCount" = "assetCount" - 1 WHERE id = OLD."personId";
END IF;
RETURN NULL;
END
$$;`.execute(db);
await sql`CREATE OR REPLACE TRIGGER "asset_face_delete_decrement_person_asset_count_trigger"
AFTER DELETE ON "asset_face"
FOR EACH ROW
EXECUTE FUNCTION person_decrement_asset_count();`.execute(db);
await sql`CREATE OR REPLACE TRIGGER "asset_face_update_increment_person_asset_count_trigger"
AFTER UPDATE ON "asset_face"
FOR EACH ROW
EXECUTE FUNCTION person_update_asset_count();`.execute(db);
await sql`CREATE OR REPLACE TRIGGER "asset_face_insert_increment_person_asset_count_trigger"
AFTER INSERT ON "asset_face"
FOR EACH ROW
EXECUTE FUNCTION person_increment_asset_count();`.execute(db);
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_person_increment_asset_count', '{"type":"function","name":"person_increment_asset_count","sql":"CREATE OR REPLACE FUNCTION person_increment_asset_count()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n IF NEW.\\"deletedAt\\" IS NULL AND NEW.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" + 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_person_update_asset_count', '{"type":"function","name":"person_update_asset_count","sql":"CREATE OR REPLACE FUNCTION person_update_asset_count()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n IF OLD.\\"personId\\" != NEW.\\"personId\\" AND OLD.\\"deletedAt\\" IS NULL AND OLD.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" - 1 WHERE id = OLD.\\"personId\\";\\n END IF;\\n\\n IF OLD.\\"personId\\" != NEW.\\"personId\\" AND NEW.\\"deletedAt\\" IS NULL AND OLD.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" + 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n\\n IF\\n OLD.\\"personId\\" = NEW.\\"personId\\" AND\\n (OLD.\\"deletedAt\\" IS NULL AND NEW.\\"deletedAt\\" IS NOT NULL) OR\\n (OLD.\\"isVisible\\" IS true AND NEW.\\"isVisible\\" IS false)\\n THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" - 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n\\n IF\\n OLD.\\"personId\\" = NEW.\\"personId\\" AND\\n (OLD.\\"deletedAt\\" IS NOT NULL AND NEW.\\"deletedAt\\" IS NULL) OR\\n (OLD.\\"isVisible\\" IS false AND NEW.\\"isVisible\\" IS true)\\n THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" + 1 WHERE id = NEW.\\"personId\\";\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_person_decrement_asset_count', '{"type":"function","name":"person_decrement_asset_count","sql":"CREATE OR REPLACE FUNCTION person_decrement_asset_count()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n IF OLD.\\"deletedAt\\" IS NULL AND OLD.\\"isVisible\\" IS true THEN\\n UPDATE person SET \\"assetCount\\" = \\"assetCount\\" - 1 WHERE id = OLD.\\"personId\\";\\n END IF;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_delete_decrement_person_asset_count_trigger', '{"type":"trigger","name":"asset_face_delete_decrement_person_asset_count_trigger","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_delete_decrement_person_asset_count_trigger\\"\\n AFTER DELETE ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION person_decrement_asset_count();"}'::jsonb);`.execute(db);
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_update_increment_person_asset_count_trigger', '{"type":"trigger","name":"asset_face_update_increment_person_asset_count_trigger","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_update_increment_person_asset_count_trigger\\"\\n AFTER UPDATE ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION person_update_asset_count();"}'::jsonb);`.execute(db);
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_insert_increment_person_asset_count_trigger', '{"type":"trigger","name":"asset_face_insert_increment_person_asset_count_trigger","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_insert_increment_person_asset_count_trigger\\"\\n AFTER INSERT ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION person_increment_asset_count();"}'::jsonb);`.execute(db);
}
export async function down(db: Kysely<any>): Promise<void> {
await sql`DROP TRIGGER "asset_face_insert_increment_person_asset_count_trigger" ON "asset_face";`.execute(db);
await sql`DROP FUNCTION person_increment_asset_count;`.execute(db);
await sql`DROP TRIGGER "asset_face_update_increment_person_asset_count_trigger" ON "asset_face";`.execute(db);
await sql`DROP FUNCTION person_update_asset_count;`.execute(db);
await sql`DROP TRIGGER "asset_face_delete_decrement_person_asset_count_trigger" ON "asset_face";`.execute(db);
await sql`DROP FUNCTION person_decrement_asset_count;`.execute(db);
await sql`DROP INDEX "idx_person_assetCount";`.execute(db);
await sql`ALTER TABLE "person" DROP COLUMN "assetCount";`.execute(db);
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_person_increment_asset_count';`.execute(db);
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_person_update_asset_count';`.execute(db);
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_person_decrement_asset_count';`.execute(db);
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_delete_decrement_person_asset_count_trigger';`.execute(db);
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_update_increment_person_asset_count_trigger';`.execute(db);
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_insert_increment_person_asset_count_trigger';`.execute(db);
}

View file

@ -1,5 +1,7 @@
import {
AfterDeleteTrigger,
AfterInsertTrigger,
AfterUpdateTrigger,
Column,
DeleteDateColumn,
ForeignKeyColumn,
@ -13,7 +15,12 @@ import {
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
import { SourceType } from 'src/enum';
import { asset_face_source_type } from 'src/schema/enums';
import { asset_face_audit } from 'src/schema/functions';
import {
asset_face_audit,
person_decrement_asset_count,
person_increment_asset_count,
person_update_asset_count,
} from 'src/schema/functions';
import { AssetTable } from 'src/schema/tables/asset.table';
import { PersonTable } from 'src/schema/tables/person.table';
@ -25,6 +32,21 @@ import { PersonTable } from 'src/schema/tables/person.table';
referencingOldTableAs: 'old',
when: 'pg_trigger_depth() = 0',
})
@AfterInsertTrigger({
name: 'asset_face_insert_increment_person_asset_count_trigger',
scope: 'row',
function: person_increment_asset_count,
})
@AfterUpdateTrigger({
name: 'asset_face_update_increment_person_asset_count_trigger',
scope: 'row',
function: person_update_asset_count,
})
@AfterDeleteTrigger({
name: 'asset_face_delete_decrement_person_asset_count_trigger',
scope: 'row',
function: person_decrement_asset_count,
})
// schemaFromDatabase does not preserve column order
@Index({ name: 'asset_face_assetId_personId_idx', columns: ['assetId', 'personId'] })
@Index({

View file

@ -22,6 +22,10 @@ import { UserTable } from 'src/schema/tables/user.table';
using: 'gin',
expression: 'f_unaccent("name") gin_trgm_ops',
})
@Index({
name: 'idx_person_assetCount',
columns: ['assetCount'],
})
@UpdatedAtTrigger('person_updatedAt')
@AfterDeleteTrigger({
scope: 'statement',
@ -66,4 +70,7 @@ export class PersonTable {
@UpdateIdColumn({ index: true })
updateId!: Generated<string>;
@Column({ type: 'integer', default: 0 })
assetCount!: Generated<number>;
}

View file

@ -12,6 +12,7 @@ export class PersonFactory {
static from(dto: PersonLike = {}) {
return new PersonFactory({
assetCount: 0,
birthDate: null,
color: null,
createdAt: newDate(),