Merge 0be782afa6 into a02adbb828
commit
31cf7d0e94
|
|
@ -88,6 +88,28 @@ export class TagRepository {
|
|||
await this.db.deleteFrom('tag').where('id', '=', id).execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getAssetIdsForTag(tagId: string): Promise<string[]> {
|
||||
const results = await this.db
|
||||
.selectFrom('tag_asset')
|
||||
.select(['assetId'])
|
||||
.where('tagId', '=', tagId)
|
||||
.execute();
|
||||
|
||||
return results.map(({ assetId }) => assetId);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getDescendantTagIds(tagId: string): Promise<string[]> {
|
||||
const results = await this.db
|
||||
.selectFrom('tag_closure')
|
||||
.select(['id_descendant'])
|
||||
.where('id_ancestor', '=', tagId)
|
||||
.execute();
|
||||
|
||||
return results.map(({ id_descendant }) => id_descendant);
|
||||
}
|
||||
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
async getAssetIds(tagId: string, assetIds: string[]): Promise<Set<string>> {
|
||||
|
|
|
|||
|
|
@ -169,12 +169,53 @@ describe(TagService.name, () => {
|
|||
expect(mocks.tag.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove a tag', async () => {
|
||||
mocks.tag.get.mockResolvedValue(tagStub.tag);
|
||||
it('should remove a tag and emit AssetUntag events for affected assets', async () => {
|
||||
mocks.tag.getDescendantTagIds.mockResolvedValue(['tag-1']);
|
||||
mocks.tag.getAssetIdsForTag.mockResolvedValue(['asset-1', 'asset-2']);
|
||||
mocks.tag.delete.mockResolvedValue();
|
||||
|
||||
await sut.remove(authStub.admin, 'tag-1');
|
||||
|
||||
expect(mocks.tag.getDescendantTagIds).toHaveBeenCalledWith('tag-1');
|
||||
expect(mocks.tag.getAssetIdsForTag).toHaveBeenCalledWith('tag-1');
|
||||
expect(mocks.tag.delete).toHaveBeenCalledWith('tag-1');
|
||||
expect(mocks.event.emit).toHaveBeenNthCalledWith(1, 'AssetUntag', { assetId: 'asset-1' });
|
||||
expect(mocks.event.emit).toHaveBeenNthCalledWith(2, 'AssetUntag', { assetId: 'asset-2' });
|
||||
});
|
||||
|
||||
it('should remove a tag with descendants and emit AssetUntag events for all affected assets', async () => {
|
||||
mocks.tag.getDescendantTagIds.mockResolvedValue(['tag-1', 'tag-1-child', 'tag-1-grandchild']);
|
||||
mocks.tag.getAssetIdsForTag.mockResolvedValueOnce(['asset-1', 'asset-2']);
|
||||
mocks.tag.getAssetIdsForTag.mockResolvedValueOnce(['asset-2', 'asset-3']);
|
||||
mocks.tag.getAssetIdsForTag.mockResolvedValueOnce(['asset-4']);
|
||||
mocks.tag.delete.mockResolvedValue();
|
||||
|
||||
await sut.remove(authStub.admin, 'tag-1');
|
||||
|
||||
expect(mocks.tag.getDescendantTagIds).toHaveBeenCalledWith('tag-1');
|
||||
expect(mocks.tag.getAssetIdsForTag).toHaveBeenNthCalledWith(1, 'tag-1');
|
||||
expect(mocks.tag.getAssetIdsForTag).toHaveBeenNthCalledWith(2, 'tag-1-child');
|
||||
expect(mocks.tag.getAssetIdsForTag).toHaveBeenNthCalledWith(3, 'tag-1-grandchild');
|
||||
expect(mocks.tag.delete).toHaveBeenCalledWith('tag-1');
|
||||
// Should emit events for all affected assets (de-duplicated)
|
||||
expect(mocks.event.emit).toHaveBeenCalledTimes(4);
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AssetUntag', { assetId: 'asset-1' });
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AssetUntag', { assetId: 'asset-2' });
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AssetUntag', { assetId: 'asset-3' });
|
||||
expect(mocks.event.emit).toHaveBeenCalledWith('AssetUntag', { assetId: 'asset-4' });
|
||||
});
|
||||
|
||||
it('should remove a tag with no affected assets', async () => {
|
||||
mocks.tag.getDescendantTagIds.mockResolvedValue(['tag-1']);
|
||||
mocks.tag.getAssetIdsForTag.mockResolvedValue([]);
|
||||
mocks.tag.delete.mockResolvedValue();
|
||||
|
||||
await sut.remove(authStub.admin, 'tag-1');
|
||||
|
||||
expect(mocks.tag.getDescendantTagIds).toHaveBeenCalledWith('tag-1');
|
||||
expect(mocks.tag.getAssetIdsForTag).toHaveBeenCalledWith('tag-1');
|
||||
expect(mocks.tag.delete).toHaveBeenCalledWith('tag-1');
|
||||
expect(mocks.event.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -70,9 +70,19 @@ export class TagService extends BaseService {
|
|||
async remove(auth: AuthDto, id: string): Promise<void> {
|
||||
await this.requireAccess({ auth, permission: Permission.TagDelete, ids: [id] });
|
||||
|
||||
// TODO sync tag changes for affected assets
|
||||
const descendantTagIds = await this.tagRepository.getDescendantTagIds(id);
|
||||
|
||||
const affectedAssetIds = new Set<string>();
|
||||
for (const tagId of descendantTagIds) {
|
||||
const assetIds = await this.tagRepository.getAssetIdsForTag(tagId);
|
||||
assetIds.forEach((assetId) => affectedAssetIds.add(assetId));
|
||||
}
|
||||
|
||||
await this.tagRepository.delete(id);
|
||||
|
||||
for (const assetId of affectedAssetIds) {
|
||||
await this.eventRepository.emit('AssetUntag', { assetId });
|
||||
}
|
||||
}
|
||||
|
||||
async bulkTagAssets(auth: AuthDto, dto: TagBulkAssetsDto): Promise<TagBulkAssetsResponseDto> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue