mirror of
https://github.com/immich-app/immich.git
synced 2026-10-26 04:19:42 +08:00
fix(web): partner sharing timeline (#31241)
* fix(web): prevent websocket assets from leaking into scoped timelines * test: add 1 more test case to make sure the changes didnt break the logic
This commit is contained in:
@@ -13,10 +13,10 @@ export class WebsocketSupport {
|
||||
#processPendingChanges = throttle(() => {
|
||||
const { add, update, remove } = this.#getPendingChangeBatches();
|
||||
if (add.length > 0) {
|
||||
this.#timelineManager.upsertAssets(add);
|
||||
this.#timelineManager.upsertAssetsFromLiveEvent(add);
|
||||
}
|
||||
if (update.length > 0) {
|
||||
this.#timelineManager.upsertAssets(update);
|
||||
this.#timelineManager.upsertAssetsFromLiveEvent(update);
|
||||
}
|
||||
if (remove.length > 0) {
|
||||
this.#timelineManager.removeAssets(remove);
|
||||
|
||||
@@ -485,6 +485,85 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('live event asset insertion', () => {
|
||||
let timelineManager: TimelineManager;
|
||||
|
||||
beforeEach(async () => {
|
||||
timelineManager = new TimelineManager();
|
||||
sdkMock.getTimeBuckets.mockResolvedValue([]);
|
||||
|
||||
await timelineManager.updateViewport({ width: 1588, height: 1000 });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
timelineManager.destroy();
|
||||
});
|
||||
|
||||
it('does not insert live event assets with a different owner into a user-scoped timeline', async () => {
|
||||
await timelineManager.updateOptions({ userId: 'partner-id', visibility: AssetVisibility.Timeline });
|
||||
|
||||
const asset = deriveLocalDateTimeFromFileCreatedAt(
|
||||
timelineAssetFactory.build({
|
||||
ownerId: 'current-user-id',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
}),
|
||||
);
|
||||
|
||||
timelineManager.upsertAssetsFromLiveEvent([asset]);
|
||||
|
||||
expect(timelineManager.assetCount).toEqual(0);
|
||||
});
|
||||
|
||||
it('inserts live event assets for the matching owner into a user-scoped timeline', async () => {
|
||||
await timelineManager.updateOptions({ userId: 'partner-id', visibility: AssetVisibility.Timeline });
|
||||
|
||||
const asset = deriveLocalDateTimeFromFileCreatedAt(
|
||||
timelineAssetFactory.build({
|
||||
ownerId: 'partner-id',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
}),
|
||||
);
|
||||
|
||||
timelineManager.upsertAssetsFromLiveEvent([asset]);
|
||||
|
||||
expect(timelineManager.assetCount).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not insert unknown live event assets into album timelines', async () => {
|
||||
await timelineManager.updateOptions({ albumId: 'album-id' });
|
||||
|
||||
const asset = deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build());
|
||||
|
||||
timelineManager.upsertAssetsFromLiveEvent([asset]);
|
||||
|
||||
expect(timelineManager.assetCount).toEqual(0);
|
||||
});
|
||||
|
||||
it('updates existing live event assets in scoped timelines', async () => {
|
||||
await timelineManager.updateOptions({ albumId: 'album-id' });
|
||||
|
||||
const asset = deriveLocalDateTimeFromFileCreatedAt(
|
||||
timelineAssetFactory.build({
|
||||
isFavorite: false,
|
||||
}),
|
||||
);
|
||||
|
||||
timelineManager.upsertAssets([asset]);
|
||||
expect(timelineManager.assetCount).toEqual(1);
|
||||
expect(timelineManager.months[0].getFirstAsset().isFavorite).toEqual(false);
|
||||
|
||||
timelineManager.upsertAssetsFromLiveEvent([
|
||||
{
|
||||
...asset,
|
||||
isFavorite: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(timelineManager.assetCount).toEqual(1);
|
||||
expect(timelineManager.months[0].getFirstAsset().isFavorite).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAssets', () => {
|
||||
let timelineManager: TimelineManager;
|
||||
|
||||
|
||||
@@ -381,6 +381,12 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
this.addAssetsUpsertSegments([...notExcluded]);
|
||||
}
|
||||
|
||||
upsertAssetsFromLiveEvent(assets: TimelineAsset[]) {
|
||||
const notUpdated = this.#updateAssets(assets);
|
||||
const insertable = notUpdated.filter((asset) => this.canInsertAssetFromLiveEvent(asset));
|
||||
this.addAssetsUpsertSegments(insertable);
|
||||
}
|
||||
|
||||
async findTimelineMonthForAsset(asset: AssetDescriptor | AssetResponseDto) {
|
||||
if (!this.isInitialized) {
|
||||
await this.initTask.waitUntilExecution();
|
||||
@@ -620,6 +626,19 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
);
|
||||
}
|
||||
|
||||
canInsertAssetFromLiveEvent(asset: TimelineAsset) {
|
||||
if (this.isExcluded(asset)) {
|
||||
return false;
|
||||
}
|
||||
if (this.#options.albumId || this.#options.personId || this.#options.timelineAlbumId) {
|
||||
return false;
|
||||
}
|
||||
if (this.#options.userId && !this.#options.withPartners && asset.ownerId !== this.#options.userId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getAssetOrder() {
|
||||
return this.#options.order ?? AssetOrder.Desc;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user