mirror of
https://github.com/immich-app/immich.git
synced 2025-08-11 09:16:31 -04:00
fix: album asset sync new assets in shared album
This commit is contained in:
parent
48a1ac83cd
commit
220ee82c18
12
mobile/openapi/lib/model/sync_entity_type.dart
generated
12
mobile/openapi/lib/model/sync_entity_type.dart
generated
@ -44,8 +44,12 @@ class SyncEntityType {
|
||||
static const albumUserV1 = SyncEntityType._(r'AlbumUserV1');
|
||||
static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1');
|
||||
static const albumUserDeleteV1 = SyncEntityType._(r'AlbumUserDeleteV1');
|
||||
static const albumAssetCreateV1 = SyncEntityType._(r'AlbumAssetCreateV1');
|
||||
static const albumAssetUpdateV1 = SyncEntityType._(r'AlbumAssetUpdateV1');
|
||||
static const albumAssetV1 = SyncEntityType._(r'AlbumAssetV1');
|
||||
static const albumAssetBackfillV1 = SyncEntityType._(r'AlbumAssetBackfillV1');
|
||||
static const albumAssetExifCreateV1 = SyncEntityType._(r'AlbumAssetExifCreateV1');
|
||||
static const albumAssetExifUpdateV1 = SyncEntityType._(r'AlbumAssetExifUpdateV1');
|
||||
static const albumAssetExifV1 = SyncEntityType._(r'AlbumAssetExifV1');
|
||||
static const albumAssetExifBackfillV1 = SyncEntityType._(r'AlbumAssetExifBackfillV1');
|
||||
static const albumToAssetV1 = SyncEntityType._(r'AlbumToAssetV1');
|
||||
@ -89,8 +93,12 @@ class SyncEntityType {
|
||||
albumUserV1,
|
||||
albumUserBackfillV1,
|
||||
albumUserDeleteV1,
|
||||
albumAssetCreateV1,
|
||||
albumAssetUpdateV1,
|
||||
albumAssetV1,
|
||||
albumAssetBackfillV1,
|
||||
albumAssetExifCreateV1,
|
||||
albumAssetExifUpdateV1,
|
||||
albumAssetExifV1,
|
||||
albumAssetExifBackfillV1,
|
||||
albumToAssetV1,
|
||||
@ -169,8 +177,12 @@ class SyncEntityTypeTypeTransformer {
|
||||
case r'AlbumUserV1': return SyncEntityType.albumUserV1;
|
||||
case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1;
|
||||
case r'AlbumUserDeleteV1': return SyncEntityType.albumUserDeleteV1;
|
||||
case r'AlbumAssetCreateV1': return SyncEntityType.albumAssetCreateV1;
|
||||
case r'AlbumAssetUpdateV1': return SyncEntityType.albumAssetUpdateV1;
|
||||
case r'AlbumAssetV1': return SyncEntityType.albumAssetV1;
|
||||
case r'AlbumAssetBackfillV1': return SyncEntityType.albumAssetBackfillV1;
|
||||
case r'AlbumAssetExifCreateV1': return SyncEntityType.albumAssetExifCreateV1;
|
||||
case r'AlbumAssetExifUpdateV1': return SyncEntityType.albumAssetExifUpdateV1;
|
||||
case r'AlbumAssetExifV1': return SyncEntityType.albumAssetExifV1;
|
||||
case r'AlbumAssetExifBackfillV1': return SyncEntityType.albumAssetExifBackfillV1;
|
||||
case r'AlbumToAssetV1': return SyncEntityType.albumToAssetV1;
|
||||
|
@ -14944,8 +14944,12 @@
|
||||
"AlbumUserV1",
|
||||
"AlbumUserBackfillV1",
|
||||
"AlbumUserDeleteV1",
|
||||
"AlbumAssetCreateV1",
|
||||
"AlbumAssetUpdateV1",
|
||||
"AlbumAssetV1",
|
||||
"AlbumAssetBackfillV1",
|
||||
"AlbumAssetExifCreateV1",
|
||||
"AlbumAssetExifUpdateV1",
|
||||
"AlbumAssetExifV1",
|
||||
"AlbumAssetExifBackfillV1",
|
||||
"AlbumToAssetV1",
|
||||
|
@ -4770,8 +4770,12 @@ export enum SyncEntityType {
|
||||
AlbumUserV1 = "AlbumUserV1",
|
||||
AlbumUserBackfillV1 = "AlbumUserBackfillV1",
|
||||
AlbumUserDeleteV1 = "AlbumUserDeleteV1",
|
||||
AlbumAssetCreateV1 = "AlbumAssetCreateV1",
|
||||
AlbumAssetUpdateV1 = "AlbumAssetUpdateV1",
|
||||
AlbumAssetV1 = "AlbumAssetV1",
|
||||
AlbumAssetBackfillV1 = "AlbumAssetBackfillV1",
|
||||
AlbumAssetExifCreateV1 = "AlbumAssetExifCreateV1",
|
||||
AlbumAssetExifUpdateV1 = "AlbumAssetExifUpdateV1",
|
||||
AlbumAssetExifV1 = "AlbumAssetExifV1",
|
||||
AlbumAssetExifBackfillV1 = "AlbumAssetExifBackfillV1",
|
||||
AlbumToAssetV1 = "AlbumToAssetV1",
|
||||
|
48
server/package-lock.json
generated
48
server/package-lock.json
generated
@ -85,6 +85,7 @@
|
||||
"thumbhash": "^0.1.1",
|
||||
"typeorm": "^0.3.17",
|
||||
"ua-parser-js": "^2.0.0",
|
||||
"uuid": "^11.1.0",
|
||||
"validator": "^13.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -8745,6 +8746,19 @@
|
||||
"uuid": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bullmq/node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@ -11515,6 +11529,19 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/gaxios/node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/gcp-metadata": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
|
||||
@ -18021,19 +18048,6 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/typeorm/node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
@ -18338,16 +18352,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/validator": {
|
||||
|
@ -110,6 +110,7 @@
|
||||
"thumbhash": "^0.1.1",
|
||||
"typeorm": "^0.3.17",
|
||||
"ua-parser-js": "^2.0.0",
|
||||
"uuid": "^11.1.0",
|
||||
"validator": "^13.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -339,8 +339,12 @@ export type SyncItem = {
|
||||
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
|
||||
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
|
||||
[SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1;
|
||||
[SyncEntityType.AlbumAssetCreateV1]: SyncAssetV1;
|
||||
[SyncEntityType.AlbumAssetUpdateV1]: SyncAssetV1;
|
||||
[SyncEntityType.AlbumAssetV1]: SyncAssetV1;
|
||||
[SyncEntityType.AlbumAssetBackfillV1]: SyncAssetV1;
|
||||
[SyncEntityType.AlbumAssetExifCreateV1]: SyncAssetExifV1;
|
||||
[SyncEntityType.AlbumAssetExifUpdateV1]: SyncAssetExifV1;
|
||||
[SyncEntityType.AlbumAssetExifV1]: SyncAssetExifV1;
|
||||
[SyncEntityType.AlbumAssetExifBackfillV1]: SyncAssetExifV1;
|
||||
[SyncEntityType.AlbumToAssetV1]: SyncAlbumToAssetV1;
|
||||
|
@ -668,8 +668,14 @@ export enum SyncEntityType {
|
||||
AlbumUserBackfillV1 = 'AlbumUserBackfillV1',
|
||||
AlbumUserDeleteV1 = 'AlbumUserDeleteV1',
|
||||
|
||||
AlbumAssetCreateV1 = 'AlbumAssetCreateV1', // album-to-asset table joined to asset table, updateId from album-to-asset, filter out assets owned by you
|
||||
AlbumAssetUpdateV1 = 'AlbumAssetUpdateV1', // asset table, updateId from asset table, don't send assets where createdAt == updatedAt, filter out assets owned by you
|
||||
AlbumAssetV1 = 'AlbumAssetV1',
|
||||
AlbumAssetBackfillV1 = 'AlbumAssetBackfillV1',
|
||||
AlbumAssetExifCreateV1 = 'AlbumAssetExifCreateV1', // album-to-asset table joined to asset-exif table, updateId from album-to-asset, filter out assets owned by you
|
||||
// If exif isn't created straight away that might cause a problem with the exif createV1, maybe we just send an empty object if it doesn't exist yet?
|
||||
// We would ack this on album-to-asset table so that would work ok for future syncs
|
||||
AlbumAssetExifUpdateV1 = 'AlbumAssetExifUpdateV1', // asset-exif table, updateId from asset-exif table, filter out assets owned by you
|
||||
AlbumAssetExifV1 = 'AlbumAssetExifV1',
|
||||
AlbumAssetExifBackfillV1 = 'AlbumAssetExifBackfillV1',
|
||||
|
||||
|
@ -155,26 +155,27 @@ class AlbumAssetSync extends BaseSync {
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
|
||||
getBackfill(albumId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.innerJoin('album_asset', 'album_asset.assetsId', 'asset.id')
|
||||
.selectFrom('album_asset')
|
||||
.innerJoin('asset', 'asset.id', 'album_asset.assetsId')
|
||||
.select(columns.syncAsset)
|
||||
.select('asset.updateId')
|
||||
.select('album_asset.updateId')
|
||||
.where('album_asset.albumsId', '=', albumId)
|
||||
.where('asset.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.where('asset.updateId', '<=', beforeUpdateId)
|
||||
.$if(!!afterUpdateId, (eb) => eb.where('asset.updateId', '>=', afterUpdateId!))
|
||||
.orderBy('asset.updateId', 'asc')
|
||||
.where('album_asset.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.where('album_asset.updateId', '<=', beforeUpdateId)
|
||||
.$if(!!afterUpdateId, (eb) => eb.where('album_asset.updateId', '>=', afterUpdateId!))
|
||||
.orderBy('album_asset.updateId', 'asc')
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getUpserts(userId: string, ack?: SyncAck) {
|
||||
getUpdates(userId: string, albumToAssetAck: SyncAck, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.innerJoin('album_asset', 'album_asset.assetsId', 'asset.id')
|
||||
.select(columns.syncAsset)
|
||||
.select('asset.updateId')
|
||||
.where('asset.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about
|
||||
.$if(!!ack, (qb) => qb.where('asset.updateId', '>', ack!.updateId))
|
||||
.orderBy('asset.updateId', 'asc')
|
||||
.innerJoin('album', 'album.id', 'album_asset.albumsId')
|
||||
@ -182,6 +183,22 @@ class AlbumAssetSync extends BaseSync {
|
||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)]))
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getCreates(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('album_asset')
|
||||
.select('album_asset.updateId')
|
||||
.innerJoin('asset', 'asset.id', 'album_asset.assetsId')
|
||||
.select(columns.syncAsset)
|
||||
.innerJoin('album', 'album.id', 'album_asset.albumsId')
|
||||
.leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId')
|
||||
.where('album_asset.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)]))
|
||||
.$if(!!ack, (qb) => qb.where('album_asset.updateId', '>', ack!.updateId))
|
||||
.orderBy('album_asset.updateId', 'asc')
|
||||
.stream();
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumAssetExifSync extends BaseSync {
|
||||
|
@ -23,6 +23,7 @@ import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||
import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
||||
import { setIsEqual } from 'src/utils/set';
|
||||
import { fromAck, mapJsonLine, serialize, SerializeOptions, toAck } from 'src/utils/sync';
|
||||
import { v7 } from 'uuid';
|
||||
|
||||
type CheckpointMap = Partial<Record<SyncEntityType, SyncAck>>;
|
||||
type AssetLike = Omit<SyncAssetV1, 'checksum' | 'thumbhash'> & {
|
||||
@ -388,10 +389,12 @@ export class SyncService extends BaseService {
|
||||
const backfillType = SyncEntityType.AlbumAssetBackfillV1;
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const albums = await this.syncRepository.album.getCreatedAfter(auth.user.id, backfillCheckpoint?.updateId);
|
||||
const upsertType = SyncEntityType.AlbumAssetV1;
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
if (upsertCheckpoint) {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
const updateType = SyncEntityType.AlbumAssetUpdateV1;
|
||||
const createType = SyncEntityType.AlbumAssetCreateV1;
|
||||
const updateCheckpoint = checkpointMap[updateType];
|
||||
const createCheckpoint = checkpointMap[createType];
|
||||
if (createCheckpoint) {
|
||||
const endId = createCheckpoint.updateId;
|
||||
|
||||
for (const album of albums) {
|
||||
const createId = album.createId;
|
||||
@ -416,9 +419,26 @@ export class SyncService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
const upserts = this.syncRepository.albumAsset.getUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) });
|
||||
if (createCheckpoint) {
|
||||
const updates = this.syncRepository.albumAsset.getUpdates(auth.user.id, createCheckpoint, updateCheckpoint);
|
||||
for await (const { updateId, ...data } of updates) {
|
||||
send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV1(data) });
|
||||
}
|
||||
}
|
||||
|
||||
const creates = this.syncRepository.albumAsset.getCreates(auth.user.id, createCheckpoint);
|
||||
let first = true;
|
||||
for await (const { updateId, ...data } of creates) {
|
||||
if (first) {
|
||||
send(response, {
|
||||
type: SyncEntityType.SyncAckV1,
|
||||
data: {},
|
||||
ackType: SyncEntityType.AlbumAssetUpdateV1,
|
||||
ids: [v7()],
|
||||
});
|
||||
first = false;
|
||||
}
|
||||
send(response, { type: createType, ids: [updateId], data: mapSyncAssetV1(data) });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,15 @@ import { createHash, randomBytes } from 'node:crypto';
|
||||
import { Writable } from 'node:stream';
|
||||
import { AssetFace } from 'src/database';
|
||||
import { AuthDto, LoginResponseDto } from 'src/dtos/auth.dto';
|
||||
import { AlbumUserRole, AssetType, AssetVisibility, MemoryType, SourceType, SyncRequestType } from 'src/enum';
|
||||
import {
|
||||
AlbumUserRole,
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
MemoryType,
|
||||
SourceType,
|
||||
SyncEntityType,
|
||||
SyncRequestType,
|
||||
} from 'src/enum';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
@ -251,11 +259,16 @@ export class SyncTestContext extends MediumTestContext<SyncService> {
|
||||
|
||||
async syncAckAll(auth: AuthDto, response: Array<{ type: string; ack: string }>) {
|
||||
const acks: Record<string, string> = {};
|
||||
const syncAcks: string[] = [];
|
||||
for (const { type, ack } of response) {
|
||||
if (type === SyncEntityType.SyncAckV1) {
|
||||
syncAcks.push(ack);
|
||||
continue;
|
||||
}
|
||||
acks[type] = ack;
|
||||
}
|
||||
|
||||
await this.sut.setAcks(auth, { acks: Object.values(acks) });
|
||||
await this.sut.setAcks(auth, { acks: [...Object.values(acks), ...syncAcks] });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { SyncTestContext } from 'test/medium.factory';
|
||||
import { factory } from 'test/small.factory';
|
||||
@ -13,6 +14,18 @@ const setup = async (db?: Kysely<DB>) => {
|
||||
return { auth, user, session, ctx };
|
||||
};
|
||||
|
||||
const updateSyncAck = {
|
||||
ack: expect.stringContaining(SyncEntityType.AlbumAssetUpdateV1),
|
||||
data: {},
|
||||
type: SyncEntityType.SyncAckV1,
|
||||
};
|
||||
|
||||
const backfillSyncAck = {
|
||||
ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV1),
|
||||
data: {},
|
||||
type: SyncEntityType.SyncAckV1,
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
@ -45,8 +58,9 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response).toHaveLength(2);
|
||||
expect(response).toEqual([
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: {
|
||||
@ -67,7 +81,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
stackId: asset.stackId,
|
||||
libraryId: asset.libraryId,
|
||||
},
|
||||
type: SyncEntityType.AlbumAssetV1,
|
||||
type: SyncEntityType.AlbumAssetCreateV1,
|
||||
},
|
||||
]);
|
||||
|
||||
@ -82,7 +96,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toHaveLength(1);
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1])).resolves.toHaveLength(1);
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1])).resolves.toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should not sync album asset for unrelated user', async () => {
|
||||
@ -103,27 +117,31 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
it('should backfill album assets when a user shares an album with you', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { asset: asset1Owner } = await ctx.newAsset({ ownerId: auth.user.id });
|
||||
await wait(2);
|
||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
const { asset: asset1User2 } = await ctx.newAsset({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album2.id, assetId: asset1User2.id });
|
||||
await wait(2);
|
||||
const { asset: asset2User2 } = await ctx.newAsset({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album2.id, assetId: asset2User2.id });
|
||||
await wait(2);
|
||||
await ctx.newAlbumAsset({ albumId: album1.id, assetId: asset2User2.id });
|
||||
await wait(2);
|
||||
const { asset: asset3User2 } = await ctx.newAsset({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album2.id, assetId: asset3User2.id });
|
||||
await wait(2);
|
||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album1.id, assetId: asset2User2.id });
|
||||
await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response).toHaveLength(2);
|
||||
expect(response).toEqual([
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: asset2User2.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetV1,
|
||||
type: SyncEntityType.AlbumAssetCreateV1,
|
||||
},
|
||||
]);
|
||||
|
||||
@ -131,24 +149,12 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
await ctx.syncAckAll(auth, response);
|
||||
|
||||
// create a second album
|
||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await Promise.all(
|
||||
[asset1User2.id, asset2User2.id, asset3User2.id, asset1Owner.id].map((assetId) =>
|
||||
ctx.newAlbumAsset({ albumId: album2.id, assetId }),
|
||||
),
|
||||
);
|
||||
|
||||
await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||
|
||||
// should backfill the album user
|
||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(newResponse).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: asset1Owner.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetBackfillV1,
|
||||
},
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
@ -163,17 +169,14 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetBackfillV1,
|
||||
},
|
||||
{
|
||||
ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV1),
|
||||
data: {},
|
||||
type: SyncEntityType.SyncAckV1,
|
||||
},
|
||||
backfillSyncAck,
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: asset3User2.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetV1,
|
||||
type: SyncEntityType.AlbumAssetCreateV1,
|
||||
},
|
||||
]);
|
||||
|
||||
@ -181,39 +184,38 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1])).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should backfill old album assets when a user adds assets to an album they share you', async () => {
|
||||
it('should sync old assets when a user adds them to an album they share you', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { asset: firstAsset } = await ctx.newAsset({ ownerId: user2.id });
|
||||
await wait(2);
|
||||
const { asset: secondAsset } = await ctx.newAsset({ ownerId: user2.id });
|
||||
await wait(2);
|
||||
const { asset: album1Asset } = await ctx.newAsset({ ownerId: user2.id });
|
||||
await wait(2);
|
||||
const { asset: firstAsset } = await ctx.newAsset({ ownerId: user2.id, originalFileName: 'firstAsset' });
|
||||
const { asset: secondAsset } = await ctx.newAsset({ ownerId: user2.id, originalFileName: 'secondAsset' });
|
||||
const { asset: album1Asset } = await ctx.newAsset({ ownerId: user2.id, originalFileName: 'album1Asset' });
|
||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album2.id, assetId: firstAsset.id });
|
||||
await wait(2);
|
||||
await ctx.newAlbumAsset({ albumId: album1.id, assetId: album1Asset.id });
|
||||
await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||
|
||||
const firstAlbumResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(firstAlbumResponse).toHaveLength(1);
|
||||
expect(firstAlbumResponse).toHaveLength(2);
|
||||
expect(firstAlbumResponse).toEqual([
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: album1Asset.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetV1,
|
||||
type: SyncEntityType.AlbumAssetCreateV1,
|
||||
},
|
||||
]);
|
||||
|
||||
await ctx.syncAckAll(auth, firstAlbumResponse);
|
||||
|
||||
await ctx.newAlbumAsset({ albumId: album2.id, assetId: firstAsset.id });
|
||||
await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(response).toHaveLength(1);
|
||||
// expect(response).toHaveLength(2);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
@ -222,11 +224,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetBackfillV1,
|
||||
},
|
||||
{
|
||||
ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV1),
|
||||
data: {},
|
||||
type: SyncEntityType.SyncAckV1,
|
||||
},
|
||||
backfillSyncAck,
|
||||
]);
|
||||
|
||||
// ack initial album asset sync
|
||||
@ -238,16 +236,62 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
// should backfill the new asset even though it's older than the first asset
|
||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(newResponse).toEqual([
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: secondAsset.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetV1,
|
||||
type: SyncEntityType.AlbumAssetCreateV1,
|
||||
},
|
||||
]);
|
||||
|
||||
await ctx.syncAckAll(auth, newResponse);
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1])).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should sync asset updates for an album shared with you', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user2.id, isFavorite: false });
|
||||
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await wait(2);
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(response).toHaveLength(2);
|
||||
expect(response).toEqual([
|
||||
updateSyncAck,
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: asset.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetCreateV1,
|
||||
},
|
||||
]);
|
||||
|
||||
await ctx.syncAckAll(auth, response);
|
||||
|
||||
// update the asset
|
||||
const assetRepository = ctx.get(AssetRepository);
|
||||
await assetRepository.update({
|
||||
id: asset.id,
|
||||
isFavorite: true,
|
||||
});
|
||||
|
||||
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
||||
expect(updateResponse).toHaveLength(1);
|
||||
expect(updateResponse).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
id: asset.id,
|
||||
isFavorite: true,
|
||||
}),
|
||||
type: SyncEntityType.AlbumAssetUpdateV1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user