mirror of
https://github.com/immich-app/immich.git
synced 2026-06-12 19:11:52 -07:00
fix(server): hide isFavorite from album asset sync stream (#28923)
* fix(server): hide isFavorite from album asset sync stream * some tests * Revert "some tests" This reverts commit3242e6961c. * alter existing test to clear test's intent * Reapply "some tests" This reverts commitf1d4c47f5f. * drop one * sql
This commit is contained in:
@@ -392,6 +392,27 @@ export const columns = {
|
|||||||
'asset.height',
|
'asset.height',
|
||||||
'asset.isEdited',
|
'asset.isEdited',
|
||||||
],
|
],
|
||||||
|
syncAlbumAsset: [
|
||||||
|
'asset.id',
|
||||||
|
'asset.ownerId',
|
||||||
|
'asset.originalFileName',
|
||||||
|
'asset.thumbhash',
|
||||||
|
'asset.checksum',
|
||||||
|
'asset.fileCreatedAt',
|
||||||
|
'asset.fileModifiedAt',
|
||||||
|
'asset.createdAt',
|
||||||
|
'asset.localDateTime',
|
||||||
|
'asset.type',
|
||||||
|
'asset.deletedAt',
|
||||||
|
'asset.visibility',
|
||||||
|
'asset.duration',
|
||||||
|
'asset.livePhotoVideoId',
|
||||||
|
'asset.stackId',
|
||||||
|
'asset.libraryId',
|
||||||
|
'asset.width',
|
||||||
|
'asset.height',
|
||||||
|
'asset.isEdited',
|
||||||
|
],
|
||||||
syncPartnerAsset: [
|
syncPartnerAsset: [
|
||||||
'asset.id',
|
'asset.id',
|
||||||
'asset.ownerId',
|
'asset.ownerId',
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ select
|
|||||||
"asset"."localDateTime",
|
"asset"."localDateTime",
|
||||||
"asset"."type",
|
"asset"."type",
|
||||||
"asset"."deletedAt",
|
"asset"."deletedAt",
|
||||||
"asset"."isFavorite",
|
|
||||||
"asset"."visibility",
|
"asset"."visibility",
|
||||||
"asset"."duration",
|
"asset"."duration",
|
||||||
"asset"."livePhotoVideoId",
|
"asset"."livePhotoVideoId",
|
||||||
@@ -78,15 +77,19 @@ select
|
|||||||
"asset"."width",
|
"asset"."width",
|
||||||
"asset"."height",
|
"asset"."height",
|
||||||
"asset"."isEdited",
|
"asset"."isEdited",
|
||||||
|
case
|
||||||
|
when "asset"."ownerId" = $1 then "asset"."isFavorite"
|
||||||
|
else $2
|
||||||
|
end as "isFavorite",
|
||||||
"album_asset"."updateId"
|
"album_asset"."updateId"
|
||||||
from
|
from
|
||||||
"album_asset" as "album_asset"
|
"album_asset" as "album_asset"
|
||||||
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
||||||
where
|
where
|
||||||
"album_asset"."updateId" < $1
|
"album_asset"."updateId" < $3
|
||||||
and "album_asset"."updateId" <= $2
|
and "album_asset"."updateId" <= $4
|
||||||
and "album_asset"."updateId" >= $3
|
and "album_asset"."updateId" >= $5
|
||||||
and "album_asset"."albumId" = $4
|
and "album_asset"."albumId" = $6
|
||||||
order by
|
order by
|
||||||
"album_asset"."updateId" asc
|
"album_asset"."updateId" asc
|
||||||
|
|
||||||
@@ -103,7 +106,6 @@ select
|
|||||||
"asset"."localDateTime",
|
"asset"."localDateTime",
|
||||||
"asset"."type",
|
"asset"."type",
|
||||||
"asset"."deletedAt",
|
"asset"."deletedAt",
|
||||||
"asset"."isFavorite",
|
|
||||||
"asset"."visibility",
|
"asset"."visibility",
|
||||||
"asset"."duration",
|
"asset"."duration",
|
||||||
"asset"."livePhotoVideoId",
|
"asset"."livePhotoVideoId",
|
||||||
@@ -112,16 +114,20 @@ select
|
|||||||
"asset"."width",
|
"asset"."width",
|
||||||
"asset"."height",
|
"asset"."height",
|
||||||
"asset"."isEdited",
|
"asset"."isEdited",
|
||||||
|
case
|
||||||
|
when "asset"."ownerId" = $1 then "asset"."isFavorite"
|
||||||
|
else $2
|
||||||
|
end as "isFavorite",
|
||||||
"asset"."updateId"
|
"asset"."updateId"
|
||||||
from
|
from
|
||||||
"asset" as "asset"
|
"asset" as "asset"
|
||||||
inner join "album_asset" on "album_asset"."assetId" = "asset"."id"
|
inner join "album_asset" on "album_asset"."assetId" = "asset"."id"
|
||||||
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
where
|
where
|
||||||
"asset"."updateId" < $1
|
"asset"."updateId" < $3
|
||||||
and "asset"."updateId" > $2
|
and "asset"."updateId" > $4
|
||||||
and "album_asset"."updateId" <= $3
|
and "album_asset"."updateId" <= $5
|
||||||
and "album_user"."userId" = $4
|
and "album_user"."userId" = $6
|
||||||
order by
|
order by
|
||||||
"asset"."updateId" asc
|
"asset"."updateId" asc
|
||||||
|
|
||||||
@@ -139,7 +145,6 @@ select
|
|||||||
"asset"."localDateTime",
|
"asset"."localDateTime",
|
||||||
"asset"."type",
|
"asset"."type",
|
||||||
"asset"."deletedAt",
|
"asset"."deletedAt",
|
||||||
"asset"."isFavorite",
|
|
||||||
"asset"."visibility",
|
"asset"."visibility",
|
||||||
"asset"."duration",
|
"asset"."duration",
|
||||||
"asset"."livePhotoVideoId",
|
"asset"."livePhotoVideoId",
|
||||||
@@ -147,15 +152,19 @@ select
|
|||||||
"asset"."libraryId",
|
"asset"."libraryId",
|
||||||
"asset"."width",
|
"asset"."width",
|
||||||
"asset"."height",
|
"asset"."height",
|
||||||
"asset"."isEdited"
|
"asset"."isEdited",
|
||||||
|
case
|
||||||
|
when "asset"."ownerId" = $1 then "asset"."isFavorite"
|
||||||
|
else $2
|
||||||
|
end as "isFavorite"
|
||||||
from
|
from
|
||||||
"album_asset" as "album_asset"
|
"album_asset" as "album_asset"
|
||||||
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
||||||
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
where
|
where
|
||||||
"album_asset"."updateId" < $1
|
"album_asset"."updateId" < $3
|
||||||
and "album_asset"."updateId" > $2
|
and "album_asset"."updateId" > $4
|
||||||
and "album_user"."userId" = $3
|
and "album_user"."userId" = $5
|
||||||
order by
|
order by
|
||||||
"album_asset"."updateId" asc
|
"album_asset"."updateId" asc
|
||||||
|
|
||||||
|
|||||||
@@ -195,11 +195,20 @@ class AlbumSync extends BaseSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AlbumAssetSync extends BaseSync {
|
class AlbumAssetSync extends BaseSync {
|
||||||
@GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true })
|
@GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID, DummyValue.UUID], stream: true })
|
||||||
getBackfill(options: SyncBackfillOptions, albumId: string) {
|
getBackfill(options: SyncBackfillOptions, albumId: string, userId: string) {
|
||||||
return this.backfillQuery('album_asset', options)
|
return this.backfillQuery('album_asset', options)
|
||||||
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
||||||
.select(columns.syncAsset)
|
.select(columns.syncAlbumAsset)
|
||||||
|
.select((eb) =>
|
||||||
|
eb
|
||||||
|
.case()
|
||||||
|
.when('asset.ownerId', '=', userId)
|
||||||
|
.then(eb.ref('asset.isFavorite'))
|
||||||
|
.else(eb.val(false))
|
||||||
|
.end()
|
||||||
|
.as('isFavorite'),
|
||||||
|
)
|
||||||
.select('album_asset.updateId')
|
.select('album_asset.updateId')
|
||||||
.where('album_asset.albumId', '=', albumId)
|
.where('album_asset.albumId', '=', albumId)
|
||||||
.stream();
|
.stream();
|
||||||
@@ -210,7 +219,16 @@ class AlbumAssetSync extends BaseSync {
|
|||||||
const userId = options.userId;
|
const userId = options.userId;
|
||||||
return this.upsertQuery('asset', options)
|
return this.upsertQuery('asset', options)
|
||||||
.innerJoin('album_asset', 'album_asset.assetId', 'asset.id')
|
.innerJoin('album_asset', 'album_asset.assetId', 'asset.id')
|
||||||
.select(columns.syncAsset)
|
.select(columns.syncAlbumAsset)
|
||||||
|
.select((eb) =>
|
||||||
|
eb
|
||||||
|
.case()
|
||||||
|
.when('asset.ownerId', '=', userId)
|
||||||
|
.then(eb.ref('asset.isFavorite'))
|
||||||
|
.else(eb.val(false))
|
||||||
|
.end()
|
||||||
|
.as('isFavorite'),
|
||||||
|
)
|
||||||
.select('asset.updateId')
|
.select('asset.updateId')
|
||||||
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about
|
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about
|
||||||
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||||
@@ -224,7 +242,16 @@ class AlbumAssetSync extends BaseSync {
|
|||||||
return this.upsertQuery('album_asset', options)
|
return this.upsertQuery('album_asset', options)
|
||||||
.select('album_asset.updateId')
|
.select('album_asset.updateId')
|
||||||
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
||||||
.select(columns.syncAsset)
|
.select(columns.syncAlbumAsset)
|
||||||
|
.select((eb) =>
|
||||||
|
eb
|
||||||
|
.case()
|
||||||
|
.when('asset.ownerId', '=', userId)
|
||||||
|
.then(eb.ref('asset.isFavorite'))
|
||||||
|
.else(eb.val(false))
|
||||||
|
.end()
|
||||||
|
.as('isFavorite'),
|
||||||
|
)
|
||||||
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||||
.where('album_user.userId', '=', userId)
|
.where('album_user.userId', '=', userId)
|
||||||
.stream();
|
.stream();
|
||||||
|
|||||||
@@ -545,6 +545,7 @@ export class SyncService extends BaseService {
|
|||||||
const backfill = this.syncRepository.albumAsset.getBackfill(
|
const backfill = this.syncRepository.albumAsset.getBackfill(
|
||||||
{ ...options, afterUpdateId: startId, beforeUpdateId: endId },
|
{ ...options, afterUpdateId: startId, beforeUpdateId: endId },
|
||||||
album.id,
|
album.id,
|
||||||
|
options.userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
for await (const { updateId, ...data } of backfill) {
|
for await (const { updateId, ...data } of backfill) {
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
|||||||
it('should sync asset updates for an album shared with you', async () => {
|
it('should sync asset updates for an album shared with you', async () => {
|
||||||
const { auth, ctx } = await setup();
|
const { auth, ctx } = await setup();
|
||||||
const { user: user2 } = await ctx.newUser();
|
const { user: user2 } = await ctx.newUser();
|
||||||
const { asset } = await ctx.newAsset({ ownerId: user2.id, isFavorite: false });
|
const { asset } = await ctx.newAsset({ ownerId: user2.id, originalFileName: 'before' });
|
||||||
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||||
await wait(2);
|
await wait(2);
|
||||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||||
@@ -281,9 +281,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
|||||||
updateSyncAck,
|
updateSyncAck,
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({ id: asset.id, originalFileName: 'before' }),
|
||||||
id: asset.id,
|
|
||||||
}),
|
|
||||||
type: SyncEntityType.AlbumAssetCreateV2,
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
@@ -291,24 +289,56 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
|||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
|
|
||||||
// update the asset
|
|
||||||
const assetRepository = ctx.get(AssetRepository);
|
const assetRepository = ctx.get(AssetRepository);
|
||||||
await assetRepository.update({
|
await assetRepository.update({ id: asset.id, originalFileName: 'after' });
|
||||||
id: asset.id,
|
|
||||||
isFavorite: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(updateResponse).toEqual([
|
expect(updateResponse).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({ id: asset.id, originalFileName: 'after' }),
|
||||||
id: asset.id,
|
|
||||||
isFavorite: true,
|
|
||||||
}),
|
|
||||||
type: SyncEntityType.AlbumAssetUpdateV2,
|
type: SyncEntityType.AlbumAssetUpdateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should hide isFavorite for album assets owned by another user', async () => {
|
||||||
|
const { auth, ctx } = await setup();
|
||||||
|
const { user: user2 } = await ctx.newUser();
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user2.id, isFavorite: true });
|
||||||
|
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||||
|
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||||
|
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Viewer });
|
||||||
|
|
||||||
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
|
expect(response).toEqual([
|
||||||
|
updateSyncAck,
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({ id: asset.id, isFavorite: false }),
|
||||||
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sync isFavorite for album assets owned by the requesting user', async () => {
|
||||||
|
const { auth, ctx } = await setup();
|
||||||
|
const { user: user2 } = await ctx.newUser();
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: auth.user.id, isFavorite: true });
|
||||||
|
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||||
|
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||||
|
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Viewer });
|
||||||
|
|
||||||
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
|
expect(response).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({ id: asset.id, isFavorite: true }),
|
||||||
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user