mirror of
https://github.com/immich-app/immich.git
synced 2025-04-21 15:36:26 +02:00
fix(server): stack info in asset response for mobile (#7346)
* fix(server): stack info in asset response for mobile * fix(server): getAllAssets - do not filter by stack ID * tet(server): GET /assets stack e2e * chore(server): fix checks * stack asset height --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
parent
4c0bb2308c
commit
52dfe5fc92
7 changed files with 37 additions and 24 deletions
mobile
server
e2e/api/specs
src
domain/repositories
immich/api-v1/asset
infra
|
@ -180,4 +180,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.11.3
|
||||
|
|
|
@ -218,18 +218,19 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stackElements.length,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
left: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final assetId = stackElements.elementAt(index).remoteId;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: GestureDetector(
|
||||
onTap: () => stackIndex.value = index,
|
||||
child: Container(
|
||||
width: 40,
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
|
@ -391,7 +392,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
Visibility(
|
||||
visible: stack.isNotEmpty,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
height: 80,
|
||||
child: buildStackedChildren(),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AssetEntity, AssetStackEntity, AssetType, SharedLinkType } from '@app/i
|
|||
import { AssetRepository } from '@app/infra/repositories';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
||||
import { assetApi } from 'e2e/client/asset-api';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import request from 'supertest';
|
||||
import { api } from '../../client';
|
||||
|
@ -532,6 +533,23 @@ describe(`${AssetController.name} (e2e)`, () => {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('should return stack data', async () => {
|
||||
const parentId = asset1.id;
|
||||
const childIds = [asset2.id, asset3.id];
|
||||
await request(server)
|
||||
.put('/asset')
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ stackParentId: parentId, ids: childIds });
|
||||
|
||||
const body = await assetApi.getAllAssets(server, user1.accessToken);
|
||||
// Response includes parent with stack children count
|
||||
const parentDto = body.find((a) => a.id == parentId);
|
||||
expect(parentDto?.stackCount).toEqual(3);
|
||||
|
||||
// Response includes children at the root level
|
||||
expect.arrayContaining([expect.objectContaining({ id: asset1.id }), expect.objectContaining({ id: asset2.id })]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /asset/upload', () => {
|
||||
|
|
|
@ -92,12 +92,12 @@ export interface SearchStatusOptions {
|
|||
export interface SearchOneToOneRelationOptions {
|
||||
withExif?: boolean;
|
||||
withSmartInfo?: boolean;
|
||||
withStacked?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchRelationOptions extends SearchOneToOneRelationOptions {
|
||||
withFaces?: boolean;
|
||||
withPeople?: boolean;
|
||||
withStacked?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchDateOptions {
|
||||
|
|
|
@ -116,9 +116,17 @@ export class AssetService {
|
|||
await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId);
|
||||
const assets = await this.assetRepository.getAllByFileCreationDate(
|
||||
{ take: dto.take ?? 1000, skip: dto.skip },
|
||||
{ ...dto, userIds: [userId], withDeleted: true, orderDirection: 'DESC', withExif: true, isVisible: true },
|
||||
{
|
||||
...dto,
|
||||
userIds: [userId],
|
||||
withDeleted: true,
|
||||
orderDirection: 'DESC',
|
||||
withExif: true,
|
||||
isVisible: true,
|
||||
withStacked: true,
|
||||
},
|
||||
);
|
||||
return assets.items.map((asset) => mapAsset(asset));
|
||||
return assets.items.map((asset) => mapAsset(asset, { withStack: true }));
|
||||
}
|
||||
|
||||
async serveThumbnail(auth: AuthDto, assetId: string, dto: GetAssetThumbnailDto): Promise<ImmichFileResponse> {
|
||||
|
|
|
@ -2,7 +2,6 @@ import { AssetSearchBuilderOptions, Paginated, PaginationOptions } from '@app/do
|
|||
import _ from 'lodash';
|
||||
import {
|
||||
Between,
|
||||
Brackets,
|
||||
FindManyOptions,
|
||||
IsNull,
|
||||
LessThanOrEqual,
|
||||
|
@ -229,12 +228,7 @@ export function searchAssetBuilder(
|
|||
}
|
||||
|
||||
if (withStacked) {
|
||||
builder
|
||||
.leftJoinAndSelect(`${builder.alias}.stack`, 'stack')
|
||||
.leftJoinAndSelect('stack.assets', 'stackedAssets')
|
||||
.andWhere(
|
||||
new Brackets((qb) => qb.where(`stack.primaryAssetId = ${builder.alias}.id`).orWhere('asset.stackId IS NULL')),
|
||||
);
|
||||
builder.leftJoinAndSelect(`${builder.alias}.stack`, 'stack').leftJoinAndSelect('stack.assets', 'stackedAssets');
|
||||
}
|
||||
|
||||
const withDeleted = options.withDeleted ?? (trashedAfter !== undefined || trashedBefore !== undefined);
|
||||
|
|
|
@ -83,10 +83,6 @@ FROM
|
|||
"asset"."isFavorite" = $3
|
||||
AND "asset"."isArchived" = $4
|
||||
)
|
||||
AND (
|
||||
"stack"."primaryAssetId" = "asset"."id"
|
||||
OR "asset"."stackId" IS NULL
|
||||
)
|
||||
)
|
||||
AND ("asset"."deletedAt" IS NULL)
|
||||
) "distinctAlias"
|
||||
|
@ -184,10 +180,6 @@ WHERE
|
|||
"asset"."isFavorite" = $3
|
||||
AND "asset"."isArchived" = $4
|
||||
)
|
||||
AND (
|
||||
"stack"."primaryAssetId" = "asset"."id"
|
||||
OR "asset"."stackId" IS NULL
|
||||
)
|
||||
AND "asset"."ownerId" IN ($5)
|
||||
)
|
||||
AND ("asset"."deletedAt" IS NULL)
|
||||
|
|
Loading…
Add table
Reference in a new issue