diff --git a/server/src/repositories/tag.repository.ts b/server/src/repositories/tag.repository.ts index ac38aaa88c..2678f861ea 100644 --- a/server/src/repositories/tag.repository.ts +++ b/server/src/repositories/tag.repository.ts @@ -163,28 +163,26 @@ export class TagRepository { } async deleteEmptyTags() { - await this.db.transaction().execute(async (tx) => { - const result = await tx - .deleteFrom('tag') - .where('id', 'not in', (eb) => eb.selectFrom('tag_asset').select('tagsId')) - .where('id', 'not in', (eb) => - eb - .selectFrom('tag as child') - .select('child.parentId') - .where('child.parentId', 'is not', null) - .where((eb2) => - eb2.or([ - eb2('child.id', 'in', (eb3) => eb3.selectFrom('tag_asset').select('tagsId')), - eb2('child.id', 'not in', (eb3) => eb3.selectFrom('tag_asset').select('tagsId')), - ]), - ), - ) - .executeTakeFirst(); + const result = await this.db + .deleteFrom('tag') + .where('id', 'not in', (eb) => eb.selectFrom('tag_asset').select('tagsId')) + .where('id', 'not in', (eb) => + eb + .selectFrom('tag as child') + .select('child.parentId') + .where('child.parentId', 'is not', null) + .where((eb2) => + eb2.or([ + eb2('child.id', 'in', (eb3) => eb3.selectFrom('tag_asset').select('tagsId')), + eb2('child.id', 'not in', (eb3) => eb3.selectFrom('tag_asset').select('tagsId')), + ]), + ), + ) + .executeTakeFirst(); - const deletedRows = Number(result.numDeletedRows); - if (deletedRows > 0) { - this.logger.log(`Deleted ${deletedRows} empty tags`); - } - }); + const deletedRows = Number(result.numDeletedRows); + if (deletedRows > 0) { + this.logger.log(`Deleted ${deletedRows} empty tags`); + } } } diff --git a/server/test/medium.factory.ts b/server/test/medium.factory.ts index 0cabd41e84..f63e7b6da1 100644 --- a/server/test/medium.factory.ts +++ b/server/test/medium.factory.ts @@ -39,8 +39,8 @@ import { StorageRepository } from 'src/repositories/storage.repository'; import { SyncCheckpointRepository } from 'src/repositories/sync-checkpoint.repository'; import { SyncRepository } from 'src/repositories/sync.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TagRepository } from 'src/repositories/tag.repository'; +import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { DB } from 'src/schema'; diff --git a/server/test/medium/specs/services/tag.service.spec.ts b/server/test/medium/specs/services/tag.service.spec.ts index 6a7e9b7b96..508a10d7f3 100644 --- a/server/test/medium/specs/services/tag.service.spec.ts +++ b/server/test/medium/specs/services/tag.service.spec.ts @@ -1,12 +1,12 @@ -import { Kysely } from "kysely"; -import { JobStatus } from "src/enum"; -import { AccessRepository } from "src/repositories/access.repository"; -import { LoggingRepository } from "src/repositories/logging.repository"; -import { TagRepository } from "src/repositories/tag.repository"; -import { DB } from "src/schema"; -import { TagService } from "src/services/tag.service"; -import { newMediumService } from "test/medium.factory"; -import { getKyselyDB } from "test/utils"; +import { Kysely } from 'kysely'; +import { JobStatus } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { TagRepository } from 'src/repositories/tag.repository'; +import { DB } from 'src/schema'; +import { TagService } from 'src/services/tag.service'; +import { newMediumService } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; let defaultDatabase: Kysely; @@ -29,7 +29,7 @@ describe(TagService.name, () => { const { user } = await ctx.newUser(); const { tag } = await ctx.newTag({ userId: user.id, value: 'tag-1' }); const tagRepo = ctx.get(TagRepository); - + await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id })); await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toBeUndefined(); @@ -41,13 +41,13 @@ describe(TagService.name, () => { const { asset } = await ctx.newAsset({ ownerId: user.id }); const { tag } = await ctx.newTag({ userId: user.id, value: 'tag-1' }); const tagRepo = ctx.get(TagRepository); - + await ctx.newTagAsset({ tagIds: [tag.id], assetIds: [asset.id] }); - + await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id })); await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id })); - }) + }); it('hierarchical tag exists, and the parent is connected to an asset, and the child is deleted', async () => { const { sut, ctx } = setup(); @@ -56,15 +56,21 @@ describe(TagService.name, () => { const { tag: parentTag } = await ctx.newTag({ userId: user.id, value: 'parent' }); const { tag: childrenTag } = await ctx.newTag({ userId: user.id, value: 'child', parentId: parentTag.id }); const tagRepo = ctx.get(TagRepository); - + await ctx.newTagAsset({ tagIds: [parentTag.id], assetIds: [asset.id] }); - - await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(expect.objectContaining({ id: parentTag.id })); - await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual(expect.objectContaining({ id: childrenTag.id })); + + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual( + expect.objectContaining({ id: childrenTag.id }), + ); await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); - await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(expect.objectContaining({ id: parentTag.id })); + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); await expect(tagRepo.getByValue(user.id, 'child')).resolves.toBeUndefined(); - }) + }); it('hierarchical tag exists, and only the child is connected to an asset, and nothing is deleted', async () => { const { sut, ctx } = setup(); @@ -73,15 +79,22 @@ describe(TagService.name, () => { const { tag: parentTag } = await ctx.newTag({ userId: user.id, value: 'parent' }); const { tag: childrenTag } = await ctx.newTag({ userId: user.id, value: 'child', parentId: parentTag.id }); const tagRepo = ctx.get(TagRepository); - - await ctx.newTagAsset({ tagIds: [childrenTag.id], assetIds: [asset.id] }); - - await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(expect.objectContaining({ id: parentTag.id })); - await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual(expect.objectContaining({ id: childrenTag.id })); - await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); - await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual(expect.objectContaining({ id: parentTag.id })); - await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual(expect.objectContaining({ id: childrenTag.id })); - }) - }) -}) \ No newline at end of file + await ctx.newTagAsset({ tagIds: [childrenTag.id], assetIds: [asset.id] }); + + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual( + expect.objectContaining({ id: childrenTag.id }), + ); + await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'child')).resolves.toEqual( + expect.objectContaining({ id: childrenTag.id }), + ); + }); + }); +});