assume 3.0 client

This commit is contained in:
mertalev 2026-04-21 00:40:49 -04:00
parent 9726f31235
commit e9d23493cd
No known key found for this signature in database
GPG Key ID: 0603AE056AA39037
9 changed files with 109 additions and 265 deletions

View File

@ -445,7 +445,6 @@ export type SyncItem = {
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
[SyncEntityType.PartnerV1]: SyncPartnerV1;
[SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1;
[SyncEntityType.AssetV1]: SyncAssetV1;
[SyncEntityType.AssetV2]: SyncAssetV2;
[SyncEntityType.AssetDeleteV1]: SyncAssetDeleteV1;
[SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1;
@ -453,9 +452,7 @@ export type SyncItem = {
[SyncEntityType.AssetExifV1]: SyncAssetExifV1;
[SyncEntityType.AssetEditV1]: SyncAssetEditV1;
[SyncEntityType.AssetEditDeleteV1]: SyncAssetEditDeleteV1;
[SyncEntityType.PartnerAssetV1]: SyncAssetV1;
[SyncEntityType.PartnerAssetV2]: SyncAssetV2;
[SyncEntityType.PartnerAssetBackfillV1]: SyncAssetV1;
[SyncEntityType.PartnerAssetBackfillV2]: SyncAssetV2;
[SyncEntityType.PartnerAssetDeleteV1]: SyncAssetDeleteV1;
[SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1;
@ -466,11 +463,8 @@ export type SyncItem = {
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
[SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1;
[SyncEntityType.AlbumAssetCreateV1]: SyncAssetV1;
[SyncEntityType.AlbumAssetCreateV2]: SyncAssetV2;
[SyncEntityType.AlbumAssetUpdateV1]: SyncAssetV1;
[SyncEntityType.AlbumAssetUpdateV2]: SyncAssetV2;
[SyncEntityType.AlbumAssetBackfillV1]: SyncAssetV1;
[SyncEntityType.AlbumAssetBackfillV2]: SyncAssetV2;
[SyncEntityType.AlbumAssetExifCreateV1]: SyncAssetExifV1;
[SyncEntityType.AlbumAssetExifUpdateV1]: SyncAssetExifV1;

View File

@ -9,7 +9,6 @@ import {
SyncAckSetDto,
syncAlbumV2ToV1,
syncAssetFaceV2ToV1,
SyncAssetV1,
SyncAssetV2,
SyncItem,
SyncStreamDto,
@ -23,8 +22,7 @@ import { hexOrBufferToBase64 } from 'src/utils/bytes';
import { fromAck, serialize, SerializeOptions, toAck } from 'src/utils/sync';
type CheckpointMap = Partial<Record<SyncEntityType, SyncAck>>;
type AssetLike = Omit<SyncAssetV1, 'checksum' | 'thumbhash' | 'duration'> & {
duration: number | null;
type AssetLike = Omit<SyncAssetV2, 'checksum' | 'thumbhash'> & {
checksum: Buffer<ArrayBufferLike>;
thumbhash: Buffer<ArrayBufferLike> | null;
};
@ -33,13 +31,6 @@ const COMPLETE_ID = 'complete';
const MAX_DAYS = 30;
const MAX_DURATION = Duration.fromObject({ days: MAX_DAYS });
const mapSyncAssetV1 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV1 => ({
...data,
duration: Duration.fromMillis(data.duration ?? 0).toFormat('hh:mm:ss.SSS'),
checksum: hexOrBufferToBase64(checksum),
thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null,
});
const mapSyncAssetV2 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV2 => ({
...data,
checksum: hexOrBufferToBase64(checksum),
@ -167,15 +158,13 @@ export class SyncService extends BaseService {
const { nowId } = await this.syncCheckpointRepository.getNow();
const options: SyncQueryOptions = { nowId, userId: auth.user.id };
const handlers: Record<SyncRequestType, () => Promise<void>> = {
const handlers = {
[SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap),
[SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap),
[SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap),
[SyncRequestType.AssetsV1]: () => this.syncAssetsV1(options, response, checkpointMap),
[SyncRequestType.AssetsV2]: () => this.syncAssetsV2(options, response, checkpointMap),
[SyncRequestType.AssetExifsV1]: () => this.syncAssetExifsV1(options, response, checkpointMap),
[SyncRequestType.AssetEditsV1]: () => this.syncAssetEditsV1(options, response, checkpointMap),
[SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(options, response, checkpointMap, session.id),
[SyncRequestType.PartnerAssetsV2]: () => this.syncPartnerAssetsV2(options, response, checkpointMap, session.id),
[SyncRequestType.AssetMetadataV1]: () => this.syncAssetMetadataV1(options, response, checkpointMap, auth),
[SyncRequestType.PartnerAssetExifsV1]: () =>
@ -183,7 +172,6 @@ export class SyncService extends BaseService {
[SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap),
[SyncRequestType.AlbumsV2]: () => this.syncAlbumsV2(options, response, checkpointMap),
[SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id),
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(options, response, checkpointMap, session.id),
[SyncRequestType.AlbumAssetsV2]: () => this.syncAlbumAssetsV2(options, response, checkpointMap, session.id),
[SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id),
[SyncRequestType.AlbumAssetExifsV1]: () =>
@ -196,10 +184,16 @@ export class SyncService extends BaseService {
[SyncRequestType.AssetFacesV1]: async () => this.syncAssetFacesV1(options, response, checkpointMap),
[SyncRequestType.AssetFacesV2]: async () => this.syncAssetFacesV2(options, response, checkpointMap),
[SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap),
};
} as const;
for (const type of dto.types) {
if (!(type in handlers)) {
throw new BadRequestException(`Invalid sync type requested: ${type}`);
}
}
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
const handler = handlers[type];
const handler = handlers[type as keyof typeof handlers];
await handler();
}
@ -275,20 +269,6 @@ export class SyncService extends BaseService {
}
}
private async syncAssetsV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
const deleteType = SyncEntityType.AssetDeleteV1;
const deletes = this.syncRepository.asset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
for await (const { id, ...data } of deletes) {
send(response, { type: deleteType, ids: [id], data });
}
const upsertType = SyncEntityType.AssetV1;
const upserts = this.syncRepository.asset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
for await (const { updateId, ...data } of upserts) {
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) });
}
}
private async syncAssetsV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
const deleteType = SyncEntityType.AssetDeleteV1;
const deletes = this.syncRepository.asset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
@ -303,65 +283,6 @@ export class SyncService extends BaseService {
}
}
private async syncPartnerAssetsV1(
options: SyncQueryOptions,
response: Writable,
checkpointMap: CheckpointMap,
sessionId: string,
) {
const deleteType = SyncEntityType.PartnerAssetDeleteV1;
const deletes = this.syncRepository.partnerAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
for await (const { id, ...data } of deletes) {
send(response, { type: deleteType, ids: [id], data });
}
const backfillType = SyncEntityType.PartnerAssetBackfillV1;
const backfillCheckpoint = checkpointMap[backfillType];
const partners = await this.syncRepository.partner.getCreatedAfter({
...options,
afterCreateId: backfillCheckpoint?.updateId,
});
const upsertType = SyncEntityType.PartnerAssetV1;
const upsertCheckpoint = checkpointMap[upsertType];
if (upsertCheckpoint) {
const endId = upsertCheckpoint.updateId;
for (const partner of partners) {
const createId = partner.createId;
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
continue;
}
const startId = getStartId(createId, backfillCheckpoint);
const backfill = this.syncRepository.partnerAsset.getBackfill(
{ ...options, afterUpdateId: startId, beforeUpdateId: endId },
partner.sharedById,
);
for await (const { updateId, ...data } of backfill) {
send(response, {
type: backfillType,
ids: [createId, updateId],
data: mapSyncAssetV1(data),
});
}
sendEntityBackfillCompleteAck(response, backfillType, createId);
}
} else if (partners.length > 0) {
await this.upsertBackfillCheckpoint({
type: backfillType,
sessionId,
createId: partners.at(-1)!.createId,
});
}
const upserts = this.syncRepository.partnerAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
for await (const { updateId, ...data } of upserts) {
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) });
}
}
private async syncPartnerAssetsV2(
options: SyncQueryOptions,
response: Writable,
@ -578,77 +499,6 @@ export class SyncService extends BaseService {
}
}
private async syncAlbumAssetsV1(
options: SyncQueryOptions,
response: Writable,
checkpointMap: CheckpointMap,
sessionId: string,
) {
const backfillType = SyncEntityType.AlbumAssetBackfillV1;
const backfillCheckpoint = checkpointMap[backfillType];
const albums = await this.syncRepository.album.getCreatedAfter({
...options,
afterCreateId: backfillCheckpoint?.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;
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
continue;
}
const startId = getStartId(createId, backfillCheckpoint);
const backfill = this.syncRepository.albumAsset.getBackfill(
{ ...options, afterUpdateId: startId, beforeUpdateId: endId },
album.id,
);
for await (const { updateId, ...data } of backfill) {
send(response, { type: backfillType, ids: [createId, updateId], data: mapSyncAssetV1(data) });
}
sendEntityBackfillCompleteAck(response, backfillType, createId);
}
} else if (albums.length > 0) {
await this.upsertBackfillCheckpoint({
type: backfillType,
sessionId,
createId: albums.at(-1)!.createId,
});
}
if (createCheckpoint) {
const updates = this.syncRepository.albumAsset.getUpdates(
{ ...options, ack: updateCheckpoint },
createCheckpoint,
);
for await (const { updateId, ...data } of updates) {
send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV1(data) });
}
}
const creates = this.syncRepository.albumAsset.getCreates({ ...options, ack: createCheckpoint });
let first = true;
for await (const { updateId, ...data } of creates) {
if (first) {
send(response, {
type: SyncEntityType.SyncAckV1,
data: {},
ackType: SyncEntityType.AlbumAssetUpdateV1,
ids: [options.nowId],
});
first = false;
}
send(response, { type: createType, ids: [updateId], data: mapSyncAssetV1(data) });
}
}
private async syncAlbumAssetsV2(
options: SyncQueryOptions,
response: Writable,
@ -711,7 +561,7 @@ export class SyncService extends BaseService {
send(response, {
type: SyncEntityType.SyncAckV1,
data: {},
ackType: SyncEntityType.AlbumAssetUpdateV1,
ackType: SyncEntityType.AlbumAssetUpdateV2,
ids: [options.nowId],
});
first = false;

View File

@ -16,7 +16,7 @@ const createAsset = (
type: AssetType.Image,
thumbhash: null,
localDateTime: new Date().toISOString(),
duration: '0:00:00.00000',
duration: 0,
hasMetadata: true,
width: 1920,
height: 1080,

View File

@ -15,13 +15,13 @@ const setup = async (db?: Kysely<DB>) => {
};
const updateSyncAck = {
ack: expect.stringContaining(SyncEntityType.AlbumAssetUpdateV1),
ack: expect.stringContaining(SyncEntityType.AlbumAssetUpdateV2),
data: {},
type: SyncEntityType.SyncAckV1,
};
const backfillSyncAck = {
ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV1),
ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV2),
data: {},
type: SyncEntityType.SyncAckV1,
};
@ -30,7 +30,7 @@ beforeAll(async () => {
defaultDatabase = await getKyselyDB();
});
describe(SyncRequestType.AlbumAssetsV1, () => {
describe(SyncRequestType.AlbumAssetsV2, () => {
it('should detect and sync the first album asset', async () => {
const originalFileName = 'firstAsset';
const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
@ -48,7 +48,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
fileModifiedAt: date,
localDateTime: date,
deletedAt: null,
duration: '0:10:00.00000',
duration: 600_000,
livePhotoVideoId: null,
stackId: null,
libraryId: null,
@ -59,7 +59,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
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]);
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(response).toEqual([
updateSyncAck,
{
@ -85,13 +85,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
height: asset.height,
isEdited: asset.isEdited,
},
type: SyncEntityType.AlbumAssetCreateV1,
type: SyncEntityType.AlbumAssetCreateV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.syncAckAll(auth, response);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
});
it('should sync album asset for own user', async () => {
@ -100,13 +100,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
const { album } = await ctx.newAlbum({ ownerId: auth.user.id });
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1])).resolves.toEqual([
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.SyncAckV1 }),
expect.objectContaining({ type: SyncEntityType.AlbumAssetCreateV1 }),
expect.objectContaining({ type: SyncEntityType.AlbumAssetCreateV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
});
@ -122,11 +122,11 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
const { session } = await ctx.newSession({ userId: user3.id });
const authUser3 = factory.auth({ session, user: user3 });
await expect(ctx.syncStream(authUser3, [SyncRequestType.AssetsV1])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
await expect(ctx.syncStream(authUser3, [SyncRequestType.AssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
});
it('should backfill album assets when a user shares an album with you', async () => {
@ -147,7 +147,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
await wait(2);
await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor });
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(response).toEqual([
updateSyncAck,
{
@ -155,7 +155,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
data: expect.objectContaining({
id: asset2User2.id,
}),
type: SyncEntityType.AlbumAssetCreateV1,
type: SyncEntityType.AlbumAssetCreateV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
@ -166,21 +166,21 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
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]);
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(newResponse).toEqual([
{
ack: expect.any(String),
data: expect.objectContaining({
id: asset1User2.id,
}),
type: SyncEntityType.AlbumAssetBackfillV1,
type: SyncEntityType.AlbumAssetBackfillV2,
},
{
ack: expect.any(String),
data: expect.objectContaining({
id: asset2User2.id,
}),
type: SyncEntityType.AlbumAssetBackfillV1,
type: SyncEntityType.AlbumAssetBackfillV2,
},
backfillSyncAck,
updateSyncAck,
@ -189,13 +189,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
data: expect.objectContaining({
id: asset3User2.id,
}),
type: SyncEntityType.AlbumAssetCreateV1,
type: SyncEntityType.AlbumAssetCreateV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.syncAckAll(auth, newResponse);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
});
it('should sync old assets when a user adds them to an album they share you', async () => {
@ -211,7 +211,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
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]);
const firstAlbumResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(firstAlbumResponse).toEqual([
updateSyncAck,
{
@ -219,7 +219,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
data: expect.objectContaining({
id: album1Asset.id,
}),
type: SyncEntityType.AlbumAssetCreateV1,
type: SyncEntityType.AlbumAssetCreateV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
@ -228,14 +228,14 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor });
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(response).toEqual([
{
ack: expect.any(String),
data: expect.objectContaining({
id: firstAsset.id,
}),
type: SyncEntityType.AlbumAssetBackfillV1,
type: SyncEntityType.AlbumAssetBackfillV2,
},
backfillSyncAck,
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
@ -248,7 +248,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
await wait(2);
// should backfill the new asset even though it's older than the first asset
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(newResponse).toEqual([
updateSyncAck,
{
@ -256,13 +256,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
data: expect.objectContaining({
id: secondAsset.id,
}),
type: SyncEntityType.AlbumAssetCreateV1,
type: SyncEntityType.AlbumAssetCreateV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.syncAckAll(auth, newResponse);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
});
it('should sync asset updates for an album shared with you', async () => {
@ -274,7 +274,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
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]);
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(response).toEqual([
updateSyncAck,
{
@ -282,7 +282,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
data: expect.objectContaining({
id: asset.id,
}),
type: SyncEntityType.AlbumAssetCreateV1,
type: SyncEntityType.AlbumAssetCreateV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
@ -296,7 +296,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
isFavorite: true,
});
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
expect(updateResponse).toEqual([
{
ack: expect.any(String),
@ -304,7 +304,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
id: asset.id,
isFavorite: true,
}),
type: SyncEntityType.AlbumAssetUpdateV1,
type: SyncEntityType.AlbumAssetUpdateV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);

View File

@ -18,7 +18,7 @@ beforeAll(async () => {
defaultDatabase = await getKyselyDB();
});
describe(SyncEntityType.AssetV1, () => {
describe(SyncEntityType.AssetV2, () => {
it('should detect and sync the first asset', async () => {
const originalFileName = 'firstAsset';
const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
@ -35,13 +35,13 @@ describe(SyncEntityType.AssetV1, () => {
fileModifiedAt: date,
localDateTime: date,
deletedAt: null,
duration: '0:10:00.00000',
duration: 600_000,
libraryId: null,
width: 1920,
height: 1080,
});
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
expect(response).toEqual([
{
ack: expect.any(String),
@ -66,13 +66,13 @@ describe(SyncEntityType.AssetV1, () => {
height: asset.height,
isEdited: asset.isEdited,
},
type: 'AssetV1',
type: 'AssetV2',
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.syncAckAll(auth, response);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
});
it('should detect and sync a deleted asset', async () => {
@ -81,7 +81,7 @@ describe(SyncEntityType.AssetV1, () => {
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
await assetRepo.remove(asset);
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
expect(response).toEqual([
{
ack: expect.any(String),
@ -94,7 +94,7 @@ describe(SyncEntityType.AssetV1, () => {
]);
await ctx.syncAckAll(auth, response);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
});
it('should not sync an asset or asset delete for an unrelated user', async () => {
@ -105,17 +105,17 @@ describe(SyncEntityType.AssetV1, () => {
const { asset } = await ctx.newAsset({ ownerId: user2.id });
const auth2 = factory.auth({ session, user: user2 });
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
await assetRepo.remove(asset);
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).toEqual([
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).toEqual([
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
});
});

View File

@ -24,7 +24,7 @@ describe(SyncEntityType.SyncCompleteV1, () => {
it('should work', async () => {
const { auth, ctx } = await setup();
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
});
it('should detect an old checkpoint and send back a reset', async () => {
@ -39,7 +39,7 @@ describe(SyncEntityType.SyncCompleteV1, () => {
},
]);
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]);
});
@ -55,6 +55,6 @@ describe(SyncEntityType.SyncCompleteV1, () => {
},
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
});
});

View File

@ -20,7 +20,7 @@ beforeAll(async () => {
defaultDatabase = await getKyselyDB();
});
describe(SyncRequestType.PartnerAssetsV1, () => {
describe(SyncRequestType.PartnerAssetsV2, () => {
it('should detect and sync the first partner asset', async () => {
const { auth, ctx } = await setup();
@ -39,13 +39,13 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
fileModifiedAt: date,
localDateTime: date,
deletedAt: null,
duration: '0:10:00.00000',
duration: 600_000,
libraryId: null,
});
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
expect(response).toEqual([
{
ack: expect.any(String),
@ -70,13 +70,13 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
width: null,
height: null,
},
type: SyncEntityType.PartnerAssetV1,
type: SyncEntityType.PartnerAssetV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.syncAckAll(auth, response);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
it('should detect and sync a deleted partner asset', async () => {
@ -88,7 +88,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
await assetRepo.remove(asset);
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
expect(response).toEqual([
{
ack: expect.any(String),
@ -101,7 +101,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
]);
await ctx.syncAckAll(auth, response);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
it('should not sync a deleted partner asset due to a user delete', async () => {
@ -112,7 +112,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
await ctx.newAsset({ ownerId: user2.id });
await userRepo.delete({ id: user2.id }, true);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
it('should not sync a deleted partner asset due to a partner delete (unshare)', async () => {
@ -122,12 +122,12 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
const { user: user2 } = await ctx.newUser();
await ctx.newAsset({ ownerId: user2.id });
const { partner } = await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
await expect(ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.PartnerAssetV1 }),
await expect(ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.PartnerAssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await partnerRepo.remove(partner);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
it('should not sync an asset or asset delete for own user', async () => {
@ -138,19 +138,19 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
await assetRepo.remove(asset);
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
it('should not sync an asset or asset delete for unrelated user', async () => {
@ -162,19 +162,19 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
const { asset } = await ctx.newAsset({ ownerId: user2.id });
const auth2 = factory.auth({ session, user: user2 });
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
await assetRepo.remove(asset);
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).resolves.toEqual([
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
it('should backfill partner assets when a partner shared their library with you', async () => {
@ -187,14 +187,14 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
const { asset: assetUser2 } = await ctx.newAsset({ ownerId: user2.id });
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
expect(response).toEqual([
{
ack: expect.any(String),
data: expect.objectContaining({
id: assetUser2.id,
}),
type: SyncEntityType.PartnerAssetV1,
type: SyncEntityType.PartnerAssetV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
@ -202,17 +202,17 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
await ctx.syncAckAll(auth, response);
await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id });
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
expect(newResponse).toEqual([
{
ack: expect.any(String),
data: expect.objectContaining({
id: assetUser3.id,
}),
type: SyncEntityType.PartnerAssetBackfillV1,
type: SyncEntityType.PartnerAssetBackfillV2,
},
{
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV1),
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV2),
data: {},
type: SyncEntityType.SyncAckV1,
},
@ -220,7 +220,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
]);
await ctx.syncAckAll(auth, newResponse);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
it('should only backfill partner assets created prior to the current partner asset checkpoint', async () => {
@ -235,31 +235,31 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
const { asset: asset2User3 } = await ctx.newAsset({ ownerId: user3.id });
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
expect(response).toEqual([
{
ack: expect.any(String),
data: expect.objectContaining({
id: assetUser2.id,
}),
type: SyncEntityType.PartnerAssetV1,
type: SyncEntityType.PartnerAssetV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.syncAckAll(auth, response);
await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id });
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
expect(newResponse).toEqual([
{
ack: expect.any(String),
data: expect.objectContaining({
id: assetUser3.id,
}),
type: SyncEntityType.PartnerAssetBackfillV1,
type: SyncEntityType.PartnerAssetBackfillV2,
},
{
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV1),
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV2),
data: {},
type: SyncEntityType.SyncAckV1,
},
@ -268,12 +268,12 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
data: expect.objectContaining({
id: asset2User3.id,
}),
type: SyncEntityType.PartnerAssetV1,
type: SyncEntityType.PartnerAssetV2,
},
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
await ctx.syncAckAll(auth, newResponse);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
});
});

View File

@ -21,7 +21,7 @@ describe(SyncEntityType.SyncResetV1, () => {
it('should work', async () => {
const { auth, ctx } = await setup();
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
});
it('should detect a pending sync reset', async () => {
@ -31,7 +31,7 @@ describe(SyncEntityType.SyncResetV1, () => {
isPendingSyncReset: true,
});
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]);
});
@ -40,8 +40,8 @@ describe(SyncEntityType.SyncResetV1, () => {
await ctx.newAsset({ ownerId: user.id });
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
@ -49,7 +49,7 @@ describe(SyncEntityType.SyncResetV1, () => {
isPendingSyncReset: true,
});
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' },
]);
});
@ -63,8 +63,8 @@ describe(SyncEntityType.SyncResetV1, () => {
isPendingSyncReset: true,
});
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1], true)).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2], true)).resolves.toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
});
@ -74,20 +74,20 @@ describe(SyncEntityType.SyncResetV1, () => {
await ctx.newAsset({ ownerId: user.id });
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
await ctx.syncAckAll(auth, response);
await ctx.get(SessionRepository).update(auth.session!.id, {
isPendingSyncReset: true,
});
const resetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
const resetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
await ctx.syncAckAll(auth, resetResponse);
const postResetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
const postResetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
expect(postResetResponse).toEqual([
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
]);
});

View File

@ -50,7 +50,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const url = getAssetUrl({ asset });
@ -65,7 +65,7 @@ describe('utils', () => {
originalPath: 'image.webp',
originalMimeType: 'image/webp',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const url = getAssetUrl({ asset });
@ -119,7 +119,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] });
@ -134,7 +134,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const sharedLink = sharedLinkFactory.build({ allowDownload: false, assets: [asset] });
@ -150,7 +150,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const sharedLink = sharedLinkFactory.build({ showMetadata: false, assets: [asset] });