From 8d6cbb51e26117ba68cddc5abd6d161b0d16f6f4 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 22 Jan 2025 13:13:09 -0500 Subject: [PATCH] fix: get asset by id for stacks (#15522) --- e2e/src/api/specs/stack.e2e-spec.ts | 124 +++++++------------- server/src/repositories/asset.repository.ts | 18 ++- 2 files changed, 61 insertions(+), 81 deletions(-) diff --git a/e2e/src/api/specs/stack.e2e-spec.ts b/e2e/src/api/specs/stack.e2e-spec.ts index bf34369ee3..36b600bec9 100644 --- a/e2e/src/api/specs/stack.e2e-spec.ts +++ b/e2e/src/api/specs/stack.e2e-spec.ts @@ -119,93 +119,57 @@ describe('/stacks', () => { const stacksAfter = await searchStacks({}, { headers: asBearerAuth(user1.accessToken) }); expect(stacksAfter.length).toBe(stacksBefore.length); }); - - // it('should require a valid parent id', async () => { - // const { status, body } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${user1.accessToken}`) - // .send({ stackParentId: uuidDto.invalid, ids: [stackAssets[0].id] }); - - // expect(status).toBe(400); - // expect(body).toEqual(errorDto.badRequest(['stackParentId must be a UUID'])); - // }); }); - // it('should require access to the parent', async () => { - // const { status, body } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${user1.accessToken}`) - // .send({ stackParentId: stackAssets[3].id, ids: [user1Assets[0].id] }); + describe('GET /assets/:id', () => { + it('should include stack details for the primary asset', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); - // expect(status).toBe(400); - // expect(body).toEqual(errorDto.noPermission); - // }); + await utils.createStack(user1.accessToken, [asset1.id, asset2.id]); - // it('should add stack children', async () => { - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ stackParentId: stackAssets[0].id, ids: [stackAssets[3].id] }); + const { status, body } = await request(app) + .get(`/assets/${asset1.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); - // expect(status).toBe(204); + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + id: asset1.id, + stack: { + id: expect.any(String), + assetCount: 2, + primaryAssetId: asset1.id, + }, + }), + ); + }); - // const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).not.toBeUndefined(); - // expect(asset.stack).toEqual(expect.arrayContaining([expect.objectContaining({ id: stackAssets[3].id })])); - // }); + it('should include stack details for a non-primary asset', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); - // it('should remove stack children', async () => { - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ removeParent: true, ids: [stackAssets[1].id] }); + await utils.createStack(user1.accessToken, [asset1.id, asset2.id]); - // expect(status).toBe(204); + const { status, body } = await request(app) + .get(`/assets/${asset2.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); - // const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).not.toBeUndefined(); - // expect(asset.stack).toEqual( - // expect.arrayContaining([ - // expect.objectContaining({ id: stackAssets[2].id }), - // expect.objectContaining({ id: stackAssets[3].id }), - // ]), - // ); - // }); - - // it('should remove all stack children', async () => { - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ removeParent: true, ids: [stackAssets[2].id, stackAssets[3].id] }); - - // expect(status).toBe(204); - - // const asset = await getAssetInfo({ id: stackAssets[0].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).toBeUndefined(); - // }); - - // it('should merge stack children', async () => { - // // create stack after previous test removed stack children - // await updateAssets( - // { assetBulkUpdateDto: { stackParentId: stackAssets[0].id, ids: [stackAssets[1].id, stackAssets[2].id] } }, - // { headers: asBearerAuth(stackUser.accessToken) }, - // ); - - // const { status } = await request(app) - // .put('/assets') - // .set('Authorization', `Bearer ${stackUser.accessToken}`) - // .send({ stackParentId: stackAssets[3].id, ids: [stackAssets[0].id] }); - - // expect(status).toBe(204); - - // const asset = await getAssetInfo({ id: stackAssets[3].id }, { headers: asBearerAuth(stackUser.accessToken) }); - // expect(asset.stack).not.toBeUndefined(); - // expect(asset.stack).toEqual( - // expect.arrayContaining([ - // expect.objectContaining({ id: stackAssets[0].id }), - // expect.objectContaining({ id: stackAssets[1].id }), - // expect.objectContaining({ id: stackAssets[2].id }), - // ]), - // ); - // }); + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + id: asset2.id, + stack: { + id: expect.any(String), + assetCount: 2, + primaryAssetId: asset1.id, + }, + }), + ); + }); + }); }); diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index ef581d4766..ed1ef73a08 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -284,7 +284,23 @@ export class AssetRepository implements IAssetRepository { .$if(!!library, (qb) => qb.select(withLibrary)) .$if(!!owner, (qb) => qb.select(withOwner)) .$if(!!smartSearch, withSmartSearch) - .$if(!!stack, (qb) => withStack(qb, { assets: !!stack!.assets, count: false })) + .$if(!!stack, (qb) => + qb + .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId') + .leftJoinLateral( + (eb) => + eb + .selectFrom('assets as stacked') + .selectAll('asset_stack') + .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets')) + .where('stacked.deletedAt', 'is', null) + .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId') + .whereRef('stacked.stackId', '=', 'asset_stack.id') + .as('stacked_assets'), + (join) => join.on('asset_stack.id', 'is not', null), + ) + .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')), + ) .$if(!!files, (qb) => qb.select(withFiles)) .$if(!!tags, (qb) => qb.select(withTags)) .limit(1)