diff --git a/e2e/src/specs/server/api/album.e2e-spec.ts b/e2e/src/specs/server/api/album.e2e-spec.ts index 3725de8d26..e1e5178476 100644 --- a/e2e/src/specs/server/api/album.e2e-spec.ts +++ b/e2e/src/specs/server/api/album.e2e-spec.ts @@ -154,23 +154,31 @@ describe('/albums', () => { expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedLink, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedEditorUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedViewerUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user2.userId, albumName: user2SharedUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, + ]), shared: true, }), ]), @@ -184,23 +192,31 @@ describe('/albums', () => { expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedEditorUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedViewerUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedLink, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user1.userId, albumName: user1NotShared, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: false, }), ]), @@ -216,23 +232,31 @@ describe('/albums', () => { expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedEditorUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedViewerUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user1.userId, albumName: user1SharedLink, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: true, }), expect.objectContaining({ - ownerId: user2.userId, albumName: user2SharedUser, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, + ]), shared: true, }), ]), @@ -248,8 +272,10 @@ describe('/albums', () => { expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ - ownerId: user1.userId, albumName: user1NotShared, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, + ]), shared: false, }), ]), @@ -286,13 +312,17 @@ describe('/albums', () => { expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ - ownerId: user4.userId, albumName: user4DeletedAsset, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) }, + ]), shared: false, }), expect.objectContaining({ - ownerId: user4.userId, albumName: user4Empty, + albumUsers: expect.arrayContaining([ + { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) }, + ]), shared: false, }), ]), @@ -362,16 +392,17 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); - expect(body).toEqual({ - ...user2Albums[0], - contributorCounts: [{ userId: user1.userId, assetCount: 1 }], - assetCount: 1, - lastModifiedAssetTimestamp: expect.any(String), - endDate: expect.any(String), - startDate: expect.any(String), - albumUsers: expect.any(Array), - shared: true, - }); + expect(body).toEqual( + expect.objectContaining({ + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], + assetCount: 1, + lastModifiedAssetTimestamp: expect.any(String), + endDate: expect.any(String), + startDate: expect.any(String), + albumUsers: expect.any(Array), + shared: true, + }), + ); }); }); @@ -397,15 +428,13 @@ describe('/albums', () => { id: expect.any(String), createdAt: expect.any(String), updatedAt: expect.any(String), - ownerId: user1.userId, albumName: 'New album', description: '', albumThumbnailAssetId: null, shared: false, - albumUsers: [], + albumUsers: [{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }], hasSharedLink: false, assetCount: 0, - owner: expect.objectContaining({ email: user1.userEmail }), isActivityEnabled: true, order: AssetOrder.Desc, }); @@ -621,11 +650,11 @@ describe('/albums', () => { expect(status).toBe(200); expect(body).toEqual( expect.objectContaining({ - albumUsers: [ + albumUsers: expect.arrayContaining([ expect.objectContaining({ user: expect.objectContaining({ id: user2.userId }), }), - ], + ]), }), ); }); @@ -637,7 +666,7 @@ describe('/albums', () => { .send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] }); expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner')); + expect(body).toEqual(errorDto.badRequest('User already added')); }); it('should not be able to add existing user to shared album', async () => { @@ -663,7 +692,7 @@ describe('/albums', () => { albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], }); - expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer); + expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer); const { status } = await request(app) .put(`/albums/${album.id}/user/${user2.userId}`) @@ -678,7 +707,10 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`); expect(body).toEqual( expect.objectContaining({ - albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })], + albumUsers: [ + expect.objectContaining({ role: AlbumUserRole.Owner }), + expect.objectContaining({ role: AlbumUserRole.Editor }), + ], }), ); }); @@ -689,7 +721,7 @@ describe('/albums', () => { albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], }); - expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer); + expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer); const { status, body } = await request(app) .put(`/albums/${album.id}/user/${user2.userId}`) diff --git a/e2e/src/ui/generators/timeline/rest-response.ts b/e2e/src/ui/generators/timeline/rest-response.ts index 8fc9ce331d..83a60556be 100644 --- a/e2e/src/ui/generators/timeline/rest-response.ts +++ b/e2e/src/ui/generators/timeline/rest-response.ts @@ -3,6 +3,7 @@ */ import { + AlbumUserRole, AssetTypeEnum, AssetVisibility, UserAvatarColor, @@ -420,9 +421,7 @@ export function getAlbum( albumThumbnailAssetId: album.thumbnailAssetId, createdAt: album.createdAt, updatedAt: album.updatedAt, - ownerId: albumOwner.id, - owner: albumOwner, - albumUsers: [], // Empty array for non-shared album + albumUsers: [{ user: albumOwner, role: AlbumUserRole.Owner }], shared: false, hasSharedLink: false, isActivityEnabled: true, diff --git a/mobile/drift_schemas/main/drift_schema_v24.json b/mobile/drift_schemas/main/drift_schema_v24.json new file mode 100644 index 0000000000..94c65bd09a --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v24.json @@ -0,0 +1,3301 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": true + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 1, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "remote_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "local_date_time", + "getter_name": "localDateTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumb_hash", + "getter_name": "thumbHash", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "live_photo_video_id", + "getter_name": "livePhotoVideoId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "visibility", + "getter_name": "visibility", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", + "dart_type_name": "AssetVisibility" + } + }, + { + "name": "stack_id", + "getter_name": "stackId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "library_id", + "getter_name": "libraryId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_edited", + "getter_name": "isEdited", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_edited\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 2, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "stack_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "primary_asset_id", + "getter_name": "primaryAssetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "i_cloud_id", + "getter_name": "iCloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 4, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('\\'\\'')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "thumbnail_asset_id", + "getter_name": "thumbnailAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "is_activity_enabled", + "getter_name": "isActivityEnabled", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "order", + "getter_name": "order", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", + "dart_type_name": "AlbumAssetOrder" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 5, + "references": [ + 4 + ], + "type": "table", + "data": { + "name": "local_album_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "backup_selection", + "getter_name": "backupSelection", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(BackupSelection.values)", + "dart_type_name": "BackupSelection" + } + }, + { + "name": "is_ios_shared_album", + "getter_name": "isIosSharedAlbum", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "linked_remote_album_id", + "getter_name": "linkedRemoteAlbumId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 6, + "references": [ + 3, + 5 + ], + "type": "table", + "data": { + "name": "local_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "local_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "marker", + "getter_name": "marker_", + "moor_type": "bool", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"marker\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 7, + "references": [ + 6 + ], + "type": "index", + "data": { + "on": 6, + "name": "idx_local_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 8, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 9, + "references": [ + 3 + ], + "type": "index", + "data": { + "on": 3, + "name": "idx_local_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 10, + "references": [ + 2 + ], + "type": "index", + "data": { + "on": 2, + "name": "idx_stack_primary_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 11, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_owner_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 12, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 13, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "UQ_remote_assets_owner_library_checksum", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", + "unique": true, + "columns": [] + } + }, + { + "id": 14, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 15, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_stack_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 16, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_local_date_time_day", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))", + "unique": false, + "columns": [] + } + }, + { + "id": 17, + "references": [ + 1 + ], + "type": "index", + "data": { + "on": 1, + "name": "idx_remote_asset_local_date_time_month", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))", + "unique": false, + "columns": [] + } + }, + { + "id": 18, + "references": [], + "type": "table", + "data": { + "name": "auth_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "email", + "getter_name": "email", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_admin", + "getter_name": "isAdmin", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_admin\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "has_profile_image", + "getter_name": "hasProfileImage", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "profile_changed_at", + "getter_name": "profileChangedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "avatar_color", + "getter_name": "avatarColor", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AvatarColor.values)", + "dart_type_name": "AvatarColor" + } + }, + { + "name": "quota_size_in_bytes", + "getter_name": "quotaSizeInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "quota_usage_in_bytes", + "getter_name": "quotaUsageInBytes", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "pin_code", + "getter_name": "pinCode", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 19, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "user_metadata_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "key", + "getter_name": "key", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", + "dart_type_name": "UserMetadataKey" + } + }, + { + "name": "value", + "getter_name": "value", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "userMetadataConverter", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "user_id", + "key" + ] + } + }, + { + "id": 20, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "partner_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "shared_by_id", + "getter_name": "sharedById", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "shared_with_id", + "getter_name": "sharedWithId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "in_timeline", + "getter_name": "inTimeline", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "shared_by_id", + "shared_with_id" + ] + } + }, + { + "id": 21, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_exif_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "city", + "getter_name": "city", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "state", + "getter_name": "state", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "country", + "getter_name": "country", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "date_time_original", + "getter_name": "dateTimeOriginal", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "exposure_time", + "getter_name": "exposureTime", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "f_number", + "getter_name": "fNumber", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "file_size", + "getter_name": "fileSize", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "focal_length", + "getter_name": "focalLength", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "iso", + "getter_name": "iso", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "make", + "getter_name": "make", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "model", + "getter_name": "model", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "lens", + "getter_name": "lens", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "time_zone", + "getter_name": "timeZone", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "rating", + "getter_name": "rating", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "projection_type", + "getter_name": "projectionType", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 22, + "references": [ + 1, + 4 + ], + "type": "table", + "data": { + "name": "remote_album_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "album_id" + ] + } + }, + { + "id": 23, + "references": [ + 4, + 0 + ], + "type": "table", + "data": { + "name": "remote_album_user_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_album_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "user_id", + "getter_name": "userId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "role", + "getter_name": "role", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", + "dart_type_name": "AlbumUserRole" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "album_id", + "user_id" + ] + } + }, + { + "id": 24, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "remote_asset_cloud_id_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "cloud_id", + "getter_name": "cloudId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "adjustment_time", + "getter_name": "adjustmentTime", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "latitude", + "getter_name": "latitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "longitude", + "getter_name": "longitude", + "moor_type": "double", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id" + ] + } + }, + { + "id": 25, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "memory_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", + "dart_type_name": "MemoryTypeEnum" + } + }, + { + "name": "data", + "getter_name": "data", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_saved", + "getter_name": "isSaved", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_saved\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "memory_at", + "getter_name": "memoryAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "seen_at", + "getter_name": "seenAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_at", + "getter_name": "showAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "hide_at", + "getter_name": "hideAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 26, + "references": [ + 1, + 25 + ], + "type": "table", + "data": { + "name": "memory_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "memory_id", + "getter_name": "memoryId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "memory_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "asset_id", + "memory_id" + ] + } + }, + { + "id": 27, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "person_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "owner_id", + "getter_name": "ownerId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "user_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "face_asset_id", + "getter_name": "faceAssetId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_hidden", + "getter_name": "isHidden", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "color", + "getter_name": "color", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "birth_date", + "getter_name": "birthDate", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 28, + "references": [ + 1, + 27 + ], + "type": "table", + "data": { + "name": "asset_face_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "person_id", + "getter_name": "personId", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "person_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "setNull" + } + } + ] + }, + { + "name": "image_width", + "getter_name": "imageWidth", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "image_height", + "getter_name": "imageHeight", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x1", + "getter_name": "boundingBoxX1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y1", + "getter_name": "boundingBoxY1", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_x2", + "getter_name": "boundingBoxX2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "bounding_box_y2", + "getter_name": "boundingBoxY2", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source_type", + "getter_name": "sourceType", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_visible", + "getter_name": "isVisible", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_visible\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('1')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "deleted_at", + "getter_name": "deletedAt", + "moor_type": "dateTime", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 29, + "references": [], + "type": "table", + "data": { + "name": "store_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "string_value", + "getter_name": "stringValue", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "int_value", + "getter_name": "intValue", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 30, + "references": [], + "type": "table", + "data": { + "name": "trashed_local_asset_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetType.values)", + "dart_type_name": "AssetType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "updated_at", + "getter_name": "updatedAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "width", + "getter_name": "width", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "height", + "getter_name": "height", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "duration_ms", + "getter_name": "durationMs", + "moor_type": "int", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "album_id", + "getter_name": "albumId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "checksum", + "getter_name": "checksum", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_favorite", + "getter_name": "isFavorite", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" + }, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "orientation", + "getter_name": "orientation", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source", + "getter_name": "source", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", + "dart_type_name": "TrashOrigin" + } + }, + { + "name": "playback_style", + "getter_name": "playbackStyle", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('0')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", + "dart_type_name": "AssetPlaybackStyle" + } + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id", + "album_id" + ] + } + }, + { + "id": 31, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "asset_edit_entity", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "asset_id", + "getter_name": "assetId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", + "dialectAwareDefaultConstraints": { + "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "remote_asset_entity", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "action", + "getter_name": "action", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", + "dart_type_name": "AssetEditAction" + } + }, + { + "name": "parameters", + "getter_name": "parameters", + "moor_type": "blob", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "editParameterConverter", + "dart_type_name": "Map" + } + }, + { + "name": "sequence", + "getter_name": "sequence", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": true, + "constraints": [], + "strict": true, + "explicit_pk": [ + "id" + ] + } + }, + { + "id": 32, + "references": [ + 20 + ], + "type": "index", + "data": { + "on": 20, + "name": "idx_partner_shared_with_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 33, + "references": [ + 21 + ], + "type": "index", + "data": { + "on": 21, + "name": "idx_lat_lng", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", + "unique": false, + "columns": [] + } + }, + { + "id": 34, + "references": [ + 22 + ], + "type": "index", + "data": { + "on": 22, + "name": "idx_remote_album_asset_album_asset", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 35, + "references": [ + 24 + ], + "type": "index", + "data": { + "on": 24, + "name": "idx_remote_asset_cloud_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 36, + "references": [ + 27 + ], + "type": "index", + "data": { + "on": 27, + "name": "idx_person_owner_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 37, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, + "name": "idx_asset_face_person_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 38, + "references": [ + 28 + ], + "type": "index", + "data": { + "on": 28, + "name": "idx_asset_face_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 39, + "references": [ + 30 + ], + "type": "index", + "data": { + "on": 30, + "name": "idx_trashed_local_asset_checksum", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", + "unique": false, + "columns": [] + } + }, + { + "id": 40, + "references": [ + 30 + ], + "type": "index", + "data": { + "on": 30, + "name": "idx_trashed_local_asset_album", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", + "unique": false, + "columns": [] + } + }, + { + "id": 41, + "references": [ + 31 + ], + "type": "index", + "data": { + "on": 31, + "name": "idx_asset_edit_asset_id", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", + "unique": false, + "columns": [] + } + } + ], + "fixed_sql": [ + { + "name": "user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "stack_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "local_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_local_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_local_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" + } + ] + }, + { + "name": "idx_stack_primary_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" + } + ] + }, + { + "name": "idx_remote_asset_owner_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" + } + ] + }, + { + "name": "UQ_remote_assets_owner_library_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" + } + ] + }, + { + "name": "idx_remote_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_remote_asset_stack_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" + } + ] + }, + { + "name": "idx_remote_asset_local_date_time_day", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))" + } + ] + }, + { + "name": "idx_remote_asset_local_date_time_month", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))" + } + ] + }, + { + "name": "auth_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "user_metadata_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "partner_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_exif_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_album_user_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "remote_asset_cloud_id_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "memory_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "person_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_face_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "store_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "trashed_local_asset_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "asset_edit_entity", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" + } + ] + }, + { + "name": "idx_partner_shared_with_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" + } + ] + }, + { + "name": "idx_lat_lng", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" + } + ] + }, + { + "name": "idx_remote_album_asset_album_asset", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" + } + ] + }, + { + "name": "idx_remote_asset_cloud_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" + } + ] + }, + { + "name": "idx_person_owner_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" + } + ] + }, + { + "name": "idx_asset_face_person_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" + } + ] + }, + { + "name": "idx_asset_face_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" + } + ] + }, + { + "name": "idx_trashed_local_asset_checksum", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" + } + ] + }, + { + "name": "idx_trashed_local_asset_album", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" + } + ] + }, + { + "name": "idx_asset_edit_asset_id", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" + } + ] + } + ] +} \ No newline at end of file diff --git a/mobile/lib/domain/models/album/album.model.dart b/mobile/lib/domain/models/album/album.model.dart index 4558e1d5be..ef67d729ec 100644 --- a/mobile/lib/domain/models/album/album.model.dart +++ b/mobile/lib/domain/models/album/album.model.dart @@ -8,6 +8,7 @@ enum AlbumUserRole { // do not change this order! editor, viewer, + owner, } // Model for an album stored in the server diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index 945ba8eb3f..f060ba9290 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -7,8 +7,8 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; -import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; class RemoteAlbumService { final DriftRemoteAlbumRepository _repository; @@ -28,10 +28,6 @@ class RemoteAlbumService { return _repository.get(albumId); } - Future getByName(String albumName, String ownerId) { - return _repository.getByName(albumName, ownerId); - } - Future> sortAlbums( List albums, AlbumSortMode sortMode, { @@ -86,8 +82,18 @@ class RemoteAlbumService { return filtered; } - Future createAlbum({required String title, required List assetIds, String? description}) async { - final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds); + Future createAlbum({ + required String title, + required UserDto owner, + required List assetIds, + String? description, + }) async { + final album = await _albumApiRepository.createDriftAlbum( + title, + owner, + description: description, + assetIds: assetIds, + ); await _repository.create(album, assetIds); return album; @@ -101,8 +107,10 @@ class RemoteAlbumService { bool? isActivityEnabled, AlbumAssetOrder? order, }) async { + final owner = await _repository.getOwner(albumId); final updatedAlbum = await _albumApiRepository.updateAlbum( albumId, + owner, name: name, description: description, thumbnailAssetId: thumbnailAssetId, diff --git a/mobile/lib/domain/services/sync_linked_album.service.dart b/mobile/lib/domain/services/sync_linked_album.service.dart index b61ca1c965..3bc76083b8 100644 --- a/mobile/lib/domain/services/sync_linked_album.service.dart +++ b/mobile/lib/domain/services/sync_linked_album.service.dart @@ -1,8 +1,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; @@ -12,6 +15,7 @@ final syncLinkedAlbumServiceProvider = Provider( ref.watch(localAlbumRepository), ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider), + ref.watch(storeServiceProvider), ), ); @@ -19,8 +23,14 @@ class SyncLinkedAlbumService { final DriftLocalAlbumRepository _localAlbumRepository; final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftAlbumApiRepository _albumApiRepository; + final StoreService _storeService; - SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository); + SyncLinkedAlbumService( + this._localAlbumRepository, + this._remoteAlbumRepository, + this._albumApiRepository, + this._storeService, + ); final _log = Logger("SyncLinkedAlbumService"); @@ -103,7 +113,11 @@ class SyncLinkedAlbumService { /// Creates a new remote album and links it to the local album Future _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async { dPrint(() => "Creating new remote album for local album: ${localAlbum.name}"); - final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, assetIds: []); + final newRemoteAlbum = await _albumApiRepository.createDriftAlbum( + localAlbum.name, + _storeService.get(StoreKey.currentUser), + assetIds: [], + ); await _remoteAlbumRepository.create(newRemoteAlbum, []); return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id); } diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 9769c2eeec..b98ba24407 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -225,6 +225,8 @@ class SyncStreamService { return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill'); case SyncEntityType.albumV1: return _syncStreamRepository.updateAlbumsV1(data.cast()); + case SyncEntityType.albumV2: + return _syncStreamRepository.updateAlbumsV2(data.cast()); case SyncEntityType.albumDeleteV1: return _syncStreamRepository.deleteAlbumsV1(data.cast()); case SyncEntityType.albumUserV1: diff --git a/mobile/lib/infrastructure/entities/remote_album.entity.dart b/mobile/lib/infrastructure/entities/remote_album.entity.dart index 30e13853d8..d12d511148 100644 --- a/mobile/lib/infrastructure/entities/remote_album.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_album.entity.dart @@ -1,10 +1,8 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; -@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)') class RemoteAlbumEntity extends Table with DriftDefaultsMixin { const RemoteAlbumEntity(); @@ -18,8 +16,6 @@ class RemoteAlbumEntity extends Table with DriftDefaultsMixin { DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); - TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)(); - TextColumn get thumbnailAssetId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()(); diff --git a/mobile/lib/infrastructure/entities/remote_album.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_album.entity.drift.dart index 7dc864b978..8fdabe0b15 100644 --- a/mobile/lib/infrastructure/entities/remote_album.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/remote_album.entity.drift.dart @@ -7,11 +7,9 @@ import 'package:immich_mobile/domain/models/album/album.model.dart' as i2; import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart' as i3; import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; -import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' as i5; import 'package:drift/internal/modular.dart' as i6; -import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' - as i7; typedef $$RemoteAlbumEntityTableCreateCompanionBuilder = i1.RemoteAlbumEntityCompanion Function({ @@ -20,7 +18,6 @@ typedef $$RemoteAlbumEntityTableCreateCompanionBuilder = i0.Value description, i0.Value createdAt, i0.Value updatedAt, - required String ownerId, i0.Value thumbnailAssetId, i0.Value isActivityEnabled, required i2.AlbumAssetOrder order, @@ -32,7 +29,6 @@ typedef $$RemoteAlbumEntityTableUpdateCompanionBuilder = i0.Value description, i0.Value createdAt, i0.Value updatedAt, - i0.Value ownerId, i0.Value thumbnailAssetId, i0.Value isActivityEnabled, i0.Value order, @@ -51,42 +47,10 @@ final class $$RemoteAlbumEntityTableReferences super.$_typedResult, ); - static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) => - i6.ReadDatabaseContainer(db) - .resultSet('user_entity') - .createAlias( - i0.$_aliasNameGenerator( - i6.ReadDatabaseContainer(db) - .resultSet('remote_album_entity') - .ownerId, - i6.ReadDatabaseContainer( - db, - ).resultSet('user_entity').id, - ), - ); - - i5.$$UserEntityTableProcessedTableManager get ownerId { - final $_column = $_itemColumn('owner_id')!; - - final manager = i5 - .$$UserEntityTableTableManager( - $_db, - i6.ReadDatabaseContainer( - $_db, - ).resultSet('user_entity'), - ) - .filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_ownerIdTable($_db)); - if (item == null) return manager; - return i0.ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } - - static i7.$RemoteAssetEntityTable _thumbnailAssetIdTable( + static i5.$RemoteAssetEntityTable _thumbnailAssetIdTable( i0.GeneratedDatabase db, ) => i6.ReadDatabaseContainer(db) - .resultSet('remote_asset_entity') + .resultSet('remote_asset_entity') .createAlias( i0.$_aliasNameGenerator( i6.ReadDatabaseContainer(db) @@ -94,19 +58,19 @@ final class $$RemoteAlbumEntityTableReferences .thumbnailAssetId, i6.ReadDatabaseContainer( db, - ).resultSet('remote_asset_entity').id, + ).resultSet('remote_asset_entity').id, ), ); - i7.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId { + i5.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId { final $_column = $_itemColumn('thumbnail_asset_id'); if ($_column == null) return null; - final manager = i7 + final manager = i5 .$$RemoteAssetEntityTableTableManager( $_db, i6.ReadDatabaseContainer( $_db, - ).resultSet('remote_asset_entity'), + ).resultSet('remote_asset_entity'), ) .filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db)); @@ -162,51 +126,24 @@ class $$RemoteAlbumEntityTableFilterComposer builder: (column) => i0.ColumnWithTypeConverterFilters(column), ); - i5.$$UserEntityTableFilterComposer get ownerId { - final i5.$$UserEntityTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.ownerId, - referencedTable: i6.ReadDatabaseContainer( - $db, - ).resultSet('user_entity'), - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => i5.$$UserEntityTableFilterComposer( - $db: $db, - $table: i6.ReadDatabaseContainer( - $db, - ).resultSet('user_entity'), - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - i7.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId { - final i7.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + i5.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId { + final i5.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.thumbnailAssetId, referencedTable: i6.ReadDatabaseContainer( $db, - ).resultSet('remote_asset_entity'), + ).resultSet('remote_asset_entity'), getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => i7.$$RemoteAssetEntityTableFilterComposer( + }) => i5.$$RemoteAssetEntityTableFilterComposer( $db: $db, $table: i6.ReadDatabaseContainer( $db, - ).resultSet('remote_asset_entity'), + ).resultSet('remote_asset_entity'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -261,52 +198,25 @@ class $$RemoteAlbumEntityTableOrderingComposer builder: (column) => i0.ColumnOrderings(column), ); - i5.$$UserEntityTableOrderingComposer get ownerId { - final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.ownerId, - referencedTable: i6.ReadDatabaseContainer( - $db, - ).resultSet('user_entity'), - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => i5.$$UserEntityTableOrderingComposer( - $db: $db, - $table: i6.ReadDatabaseContainer( - $db, - ).resultSet('user_entity'), - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - i7.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId { - final i7.$$RemoteAssetEntityTableOrderingComposer composer = + i5.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId { + final i5.$$RemoteAssetEntityTableOrderingComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.thumbnailAssetId, referencedTable: i6.ReadDatabaseContainer( $db, - ).resultSet('remote_asset_entity'), + ).resultSet('remote_asset_entity'), getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => i7.$$RemoteAssetEntityTableOrderingComposer( + }) => i5.$$RemoteAssetEntityTableOrderingComposer( $db: $db, $table: i6.ReadDatabaseContainer( $db, - ).resultSet('remote_asset_entity'), + ).resultSet('remote_asset_entity'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -351,52 +261,25 @@ class $$RemoteAlbumEntityTableAnnotationComposer i0.GeneratedColumnWithTypeConverter get order => $composableBuilder(column: $table.order, builder: (column) => column); - i5.$$UserEntityTableAnnotationComposer get ownerId { - final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.ownerId, - referencedTable: i6.ReadDatabaseContainer( - $db, - ).resultSet('user_entity'), - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => i5.$$UserEntityTableAnnotationComposer( - $db: $db, - $table: i6.ReadDatabaseContainer( - $db, - ).resultSet('user_entity'), - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - i7.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId { - final i7.$$RemoteAssetEntityTableAnnotationComposer composer = + i5.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId { + final i5.$$RemoteAssetEntityTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.thumbnailAssetId, referencedTable: i6.ReadDatabaseContainer( $db, - ).resultSet('remote_asset_entity'), + ).resultSet('remote_asset_entity'), getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => i7.$$RemoteAssetEntityTableAnnotationComposer( + }) => i5.$$RemoteAssetEntityTableAnnotationComposer( $db: $db, $table: i6.ReadDatabaseContainer( $db, - ).resultSet('remote_asset_entity'), + ).resultSet('remote_asset_entity'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -420,7 +303,7 @@ class $$RemoteAlbumEntityTableTableManager $$RemoteAlbumEntityTableUpdateCompanionBuilder, (i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences), i1.RemoteAlbumEntityData, - i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId}) + i0.PrefetchHooks Function({bool thumbnailAssetId}) > { $$RemoteAlbumEntityTableTableManager( i0.GeneratedDatabase db, @@ -445,7 +328,6 @@ class $$RemoteAlbumEntityTableTableManager i0.Value description = const i0.Value.absent(), i0.Value createdAt = const i0.Value.absent(), i0.Value updatedAt = const i0.Value.absent(), - i0.Value ownerId = const i0.Value.absent(), i0.Value thumbnailAssetId = const i0.Value.absent(), i0.Value isActivityEnabled = const i0.Value.absent(), i0.Value order = const i0.Value.absent(), @@ -455,7 +337,6 @@ class $$RemoteAlbumEntityTableTableManager description: description, createdAt: createdAt, updatedAt: updatedAt, - ownerId: ownerId, thumbnailAssetId: thumbnailAssetId, isActivityEnabled: isActivityEnabled, order: order, @@ -467,7 +348,6 @@ class $$RemoteAlbumEntityTableTableManager i0.Value description = const i0.Value.absent(), i0.Value createdAt = const i0.Value.absent(), i0.Value updatedAt = const i0.Value.absent(), - required String ownerId, i0.Value thumbnailAssetId = const i0.Value.absent(), i0.Value isActivityEnabled = const i0.Value.absent(), required i2.AlbumAssetOrder order, @@ -477,7 +357,6 @@ class $$RemoteAlbumEntityTableTableManager description: description, createdAt: createdAt, updatedAt: updatedAt, - ownerId: ownerId, thumbnailAssetId: thumbnailAssetId, isActivityEnabled: isActivityEnabled, order: order, @@ -490,7 +369,7 @@ class $$RemoteAlbumEntityTableTableManager ), ) .toList(), - prefetchHooksCallback: ({ownerId = false, thumbnailAssetId = false}) { + prefetchHooksCallback: ({thumbnailAssetId = false}) { return i0.PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -510,21 +389,6 @@ class $$RemoteAlbumEntityTableTableManager dynamic > >(state) { - if (ownerId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.ownerId, - referencedTable: i1 - .$$RemoteAlbumEntityTableReferences - ._ownerIdTable(db), - referencedColumn: i1 - .$$RemoteAlbumEntityTableReferences - ._ownerIdTable(db) - .id, - ) - as T; - } if (thumbnailAssetId) { state = state.withJoin( @@ -564,12 +428,8 @@ typedef $$RemoteAlbumEntityTableProcessedTableManager = $$RemoteAlbumEntityTableUpdateCompanionBuilder, (i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences), i1.RemoteAlbumEntityData, - i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId}) + i0.PrefetchHooks Function({bool thumbnailAssetId}) >; -i0.Index get idxRemoteAlbumOwnerId => i0.Index( - 'idx_remote_album_owner_id', - 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', -); class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> { @@ -636,20 +496,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity requiredDuringInsert: false, defaultValue: i4.currentDateAndTime, ); - static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta( - 'ownerId', - ); - @override - late final i0.GeneratedColumn ownerId = i0.GeneratedColumn( - 'owner_id', - aliasedName, - false, - type: i0.DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: i0.GeneratedColumn.constraintIsAlways( - 'REFERENCES user_entity (id) ON DELETE CASCADE', - ), - ); static const i0.VerificationMeta _thumbnailAssetIdMeta = const i0.VerificationMeta('thumbnailAssetId'); @override @@ -698,7 +544,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity description, createdAt, updatedAt, - ownerId, thumbnailAssetId, isActivityEnabled, order, @@ -749,14 +594,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), ); } - if (data.containsKey('owner_id')) { - context.handle( - _ownerIdMeta, - ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta), - ); - } else if (isInserting) { - context.missing(_ownerIdMeta); - } if (data.containsKey('thumbnail_asset_id')) { context.handle( _thumbnailAssetIdMeta, @@ -807,10 +644,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'], )!, - ownerId: attachedDatabase.typeMapping.read( - i0.DriftSqlType.string, - data['${effectivePrefix}owner_id'], - )!, thumbnailAssetId: attachedDatabase.typeMapping.read( i0.DriftSqlType.string, data['${effectivePrefix}thumbnail_asset_id'], @@ -850,7 +683,6 @@ class RemoteAlbumEntityData extends i0.DataClass final String description; final DateTime createdAt; final DateTime updatedAt; - final String ownerId; final String? thumbnailAssetId; final bool isActivityEnabled; final i2.AlbumAssetOrder order; @@ -860,7 +692,6 @@ class RemoteAlbumEntityData extends i0.DataClass required this.description, required this.createdAt, required this.updatedAt, - required this.ownerId, this.thumbnailAssetId, required this.isActivityEnabled, required this.order, @@ -873,7 +704,6 @@ class RemoteAlbumEntityData extends i0.DataClass map['description'] = i0.Variable(description); map['created_at'] = i0.Variable(createdAt); map['updated_at'] = i0.Variable(updatedAt); - map['owner_id'] = i0.Variable(ownerId); if (!nullToAbsent || thumbnailAssetId != null) { map['thumbnail_asset_id'] = i0.Variable(thumbnailAssetId); } @@ -897,7 +727,6 @@ class RemoteAlbumEntityData extends i0.DataClass description: serializer.fromJson(json['description']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), - ownerId: serializer.fromJson(json['ownerId']), thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson( @@ -914,7 +743,6 @@ class RemoteAlbumEntityData extends i0.DataClass 'description': serializer.toJson(description), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), - 'ownerId': serializer.toJson(ownerId), 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), 'isActivityEnabled': serializer.toJson(isActivityEnabled), 'order': serializer.toJson( @@ -929,7 +757,6 @@ class RemoteAlbumEntityData extends i0.DataClass String? description, DateTime? createdAt, DateTime? updatedAt, - String? ownerId, i0.Value thumbnailAssetId = const i0.Value.absent(), bool? isActivityEnabled, i2.AlbumAssetOrder? order, @@ -939,7 +766,6 @@ class RemoteAlbumEntityData extends i0.DataClass description: description ?? this.description, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, - ownerId: ownerId ?? this.ownerId, thumbnailAssetId: thumbnailAssetId.present ? thumbnailAssetId.value : this.thumbnailAssetId, @@ -955,7 +781,6 @@ class RemoteAlbumEntityData extends i0.DataClass : this.description, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, - ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, thumbnailAssetId: data.thumbnailAssetId.present ? data.thumbnailAssetId.value : this.thumbnailAssetId, @@ -974,7 +799,6 @@ class RemoteAlbumEntityData extends i0.DataClass ..write('description: $description, ') ..write('createdAt: $createdAt, ') ..write('updatedAt: $updatedAt, ') - ..write('ownerId: $ownerId, ') ..write('thumbnailAssetId: $thumbnailAssetId, ') ..write('isActivityEnabled: $isActivityEnabled, ') ..write('order: $order') @@ -989,7 +813,6 @@ class RemoteAlbumEntityData extends i0.DataClass description, createdAt, updatedAt, - ownerId, thumbnailAssetId, isActivityEnabled, order, @@ -1003,7 +826,6 @@ class RemoteAlbumEntityData extends i0.DataClass other.description == this.description && other.createdAt == this.createdAt && other.updatedAt == this.updatedAt && - other.ownerId == this.ownerId && other.thumbnailAssetId == this.thumbnailAssetId && other.isActivityEnabled == this.isActivityEnabled && other.order == this.order); @@ -1016,7 +838,6 @@ class RemoteAlbumEntityCompanion final i0.Value description; final i0.Value createdAt; final i0.Value updatedAt; - final i0.Value ownerId; final i0.Value thumbnailAssetId; final i0.Value isActivityEnabled; final i0.Value order; @@ -1026,7 +847,6 @@ class RemoteAlbumEntityCompanion this.description = const i0.Value.absent(), this.createdAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(), - this.ownerId = const i0.Value.absent(), this.thumbnailAssetId = const i0.Value.absent(), this.isActivityEnabled = const i0.Value.absent(), this.order = const i0.Value.absent(), @@ -1037,13 +857,11 @@ class RemoteAlbumEntityCompanion this.description = const i0.Value.absent(), this.createdAt = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(), - required String ownerId, this.thumbnailAssetId = const i0.Value.absent(), this.isActivityEnabled = const i0.Value.absent(), required i2.AlbumAssetOrder order, }) : id = i0.Value(id), name = i0.Value(name), - ownerId = i0.Value(ownerId), order = i0.Value(order); static i0.Insertable custom({ i0.Expression? id, @@ -1051,7 +869,6 @@ class RemoteAlbumEntityCompanion i0.Expression? description, i0.Expression? createdAt, i0.Expression? updatedAt, - i0.Expression? ownerId, i0.Expression? thumbnailAssetId, i0.Expression? isActivityEnabled, i0.Expression? order, @@ -1062,7 +879,6 @@ class RemoteAlbumEntityCompanion if (description != null) 'description': description, if (createdAt != null) 'created_at': createdAt, if (updatedAt != null) 'updated_at': updatedAt, - if (ownerId != null) 'owner_id': ownerId, if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, if (order != null) 'order': order, @@ -1075,7 +891,6 @@ class RemoteAlbumEntityCompanion i0.Value? description, i0.Value? createdAt, i0.Value? updatedAt, - i0.Value? ownerId, i0.Value? thumbnailAssetId, i0.Value? isActivityEnabled, i0.Value? order, @@ -1086,7 +901,6 @@ class RemoteAlbumEntityCompanion description: description ?? this.description, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, - ownerId: ownerId ?? this.ownerId, thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, order: order ?? this.order, @@ -1111,9 +925,6 @@ class RemoteAlbumEntityCompanion if (updatedAt.present) { map['updated_at'] = i0.Variable(updatedAt.value); } - if (ownerId.present) { - map['owner_id'] = i0.Variable(ownerId.value); - } if (thumbnailAssetId.present) { map['thumbnail_asset_id'] = i0.Variable(thumbnailAssetId.value); } @@ -1136,7 +947,6 @@ class RemoteAlbumEntityCompanion ..write('description: $description, ') ..write('createdAt: $createdAt, ') ..write('updatedAt: $updatedAt, ') - ..write('ownerId: $ownerId, ') ..write('thumbnailAssetId: $thumbnailAssetId, ') ..write('isActivityEnabled: $isActivityEnabled, ') ..write('order: $order') diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 371a7bace5..bed62e1b1b 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -84,7 +84,7 @@ class Drift extends $Drift { } @override - int get schemaVersion => 23; + int get schemaVersion => 24; @override MigrationStrategy get migration => MigrationStrategy( @@ -246,6 +246,10 @@ class Drift extends $Drift { ), ); }, + from23To24: (m, v24) async { + await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id'); + await m.alterTable(TableMigration(v24.remoteAlbumEntity)); + }, ), ); diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index c898b7ce65..4e199c51c1 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -105,7 +105,6 @@ abstract class $Drift extends i0.GeneratedDatabase { localAlbumEntity, localAlbumAssetEntity, i7.idxLocalAlbumAssetAlbumAsset, - i5.idxRemoteAlbumOwnerId, i4.idxLocalAssetChecksum, i4.idxLocalAssetCloudId, i3.idxStackPrimaryAssetId, @@ -160,15 +159,6 @@ abstract class $Drift extends i0.GeneratedDatabase { ), result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)], ), - i0.WritePropagation( - on: i0.TableUpdateQuery.onTableName( - 'user_entity', - limitUpdateKind: i0.UpdateKind.delete, - ), - result: [ - i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.delete), - ], - ), i0.WritePropagation( on: i0.TableUpdateQuery.onTableName( 'remote_asset_entity', diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index 795a45b21b..82bb81628c 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.steps.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart @@ -11824,6 +11824,557 @@ i1.GeneratedColumn _column_209(String aliasedName) => type: i1.DriftSqlType.int, $customConstraints: 'NOT NULL', ); + +final class Schema24 extends i0.VersionedSchema { + Schema24({required super.database}) : super(version: 24); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + late final Shape33 userEntity = Shape33( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape34 remoteAssetEntity = Shape34( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_119, + _column_120, + _column_121, + _column_122, + _column_123, + _column_124, + _column_125, + _column_126, + _column_127, + _column_128, + _column_129, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape35 stackEntity = Shape35( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_130, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape36 localAssetEntity = Shape36( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_131, + _column_120, + _column_132, + _column_133, + _column_134, + _column_135, + _column_136, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape48 remoteAlbumEntity = Shape48( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_138, + _column_114, + _column_115, + _column_139, + _column_140, + _column_141, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape38 localAlbumEntity = Shape38( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_115, + _column_142, + _column_143, + _column_144, + _column_145, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape39 localAlbumAssetEntity = Shape39( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_146, _column_147, _column_145], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final Shape40 authUserEntity = Shape40( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_148, + _column_110, + _column_111, + _column_149, + _column_150, + _column_151, + _column_152, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_153, _column_154, _column_155], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape41 partnerEntity = Shape41( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_156, _column_157, _column_158], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape42 remoteExifEntity = Shape42( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_160, + _column_161, + _column_162, + _column_163, + _column_164, + _column_117, + _column_116, + _column_165, + _column_166, + _column_167, + _column_168, + _column_135, + _column_136, + _column_169, + _column_170, + _column_171, + _column_172, + _column_173, + _column_174, + _column_175, + _column_176, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_159, _column_177], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_177, _column_153, _column_178], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape43 remoteAssetCloudIdEntity = Shape43( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_179, + _column_180, + _column_134, + _column_135, + _column_136, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape44 memoryEntity = Shape44( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_124, + _column_121, + _column_113, + _column_181, + _column_182, + _column_183, + _column_184, + _column_185, + _column_186, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_159, _column_187], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape45 personEntity = Shape45( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_108, + _column_188, + _column_189, + _column_190, + _column_191, + _column_192, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape46 assetFaceEntity = Shape46( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_193, + _column_194, + _column_195, + _column_196, + _column_197, + _column_198, + _column_199, + _column_200, + _column_201, + _column_124, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_202, _column_203, _column_204], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape47 trashedLocalAssetEntity = Shape47( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_205, + _column_131, + _column_120, + _column_132, + _column_206, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape32 assetEditEntity = Shape32( + source: i0.VersionedTable( + entityName: 'asset_edit_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_207, + _column_208, + _column_209, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + final i1.Index idxAssetEditAssetId = i1.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); +} + +class Shape48 extends i0.VersionedTable { + Shape48({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get description => + columnsByName['description']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get thumbnailAssetId => + columnsByName['thumbnail_asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get isActivityEnabled => + columnsByName['is_activity_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get order => + columnsByName['order']! as i1.GeneratedColumn; +} + i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -11847,6 +12398,7 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema21 schema) from20To21, required Future Function(i1.Migrator m, Schema22 schema) from21To22, required Future Function(i1.Migrator m, Schema23 schema) from22To23, + required Future Function(i1.Migrator m, Schema24 schema) from23To24, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -11960,6 +12512,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from22To23(migrator, schema); return 23; + case 23: + final schema = Schema24(database: database); + final migrator = i1.Migrator(database, schema); + await from23To24(migrator, schema); + return 24; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -11989,6 +12546,7 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema21 schema) from20To21, required Future Function(i1.Migrator m, Schema22 schema) from21To22, required Future Function(i1.Migrator m, Schema23 schema) from22To23, + required Future Function(i1.Migrator m, Schema24 schema) from23To24, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -12013,5 +12571,6 @@ i1.OnUpgrade stepByStep({ from20To21: from20To21, from21To22: from21To22, from22To23: from22To23, + from23To24: from23To24, ), ); diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index a594647f19..19ebcaac45 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -32,17 +32,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), useColumns: false, ), - leftOuterJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false), leftOuterJoin( _db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), + innerJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & + _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & + _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), + useColumns: false, + ), ]); query ..where(_db.remoteAssetEntity.deletedAt.isNull()) ..addColumns([assetCount]) - ..addColumns([_db.userEntity.name]) + ..addColumns([_db.userEntity.name, _db.userEntity.id]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..groupBy([_db.remoteAlbumEntity.id]); @@ -63,6 +69,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .readTable(_db.remoteAlbumEntity) .toDto( assetCount: row.read(assetCount) ?? 0, + ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name)!, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ), @@ -85,20 +92,22 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), useColumns: false, ), - leftOuterJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), - useColumns: false, - ), leftOuterJoin( _db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), + innerJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & + _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & + _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), + useColumns: false, + ), ]) ..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull()) ..addColumns([assetCount]) - ..addColumns([_db.userEntity.name]) + ..addColumns([_db.userEntity.name, _db.userEntity.id]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..groupBy([_db.remoteAlbumEntity.id]); @@ -108,6 +117,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .readTable(_db.remoteAlbumEntity) .toDto( assetCount: row.read(assetCount) ?? 0, + ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name)!, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ), @@ -116,12 +126,29 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { } Future getByName(String albumName, String ownerId) { - final query = _db.remoteAlbumEntity.select() - ..where((row) => row.name.equals(albumName) & row.ownerId.equals(ownerId)) - ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) + final query = _db.remoteAlbumEntity.select().join([ + innerJoin( + _db.remoteAlbumUserEntity, + _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & + _db.remoteAlbumUserEntity.userId.equals(ownerId) & + _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), + useColumns: false, + ), + ]); + + query + ..addColumns([_db.remoteAlbumUserEntity.userId]) + ..where(_db.remoteAlbumEntity.name.equals(albumName)) + ..orderBy([OrderingTerm.desc(_db.remoteAlbumEntity.createdAt)]) ..limit(1); - return query.map((row) => row.toDto(ownerName: '', isShared: false)).getSingleOrNull(); + return query + .map( + (row) => row + .readTable(_db.remoteAlbumEntity) + .toDto(ownerId: row.read(_db.remoteAlbumUserEntity.userId)!, ownerName: '', isShared: false), + ) + .getSingleOrNull(); } Future create(RemoteAlbum album, List assetIds) async { @@ -129,7 +156,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { final entity = RemoteAlbumEntityCompanion( id: Value(album.id), name: Value(album.name), - ownerId: Value(album.ownerId), createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), description: Value(album.description), @@ -140,6 +166,14 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { await _db.remoteAlbumEntity.insertOne(entity); + await _db.remoteAlbumUserEntity.insertOne( + RemoteAlbumUserEntityCompanion( + albumId: Value(album.id), + userId: Value(album.ownerId), + role: const Value(AlbumUserRole.owner), + ), + ); + if (assetIds.isNotEmpty) { final albumAssets = assetIds.map( (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)), @@ -157,7 +191,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { RemoteAlbumEntityCompanion( id: Value(album.id), name: Value(album.name), - ownerId: Value(album.ownerId), createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), description: Value(album.description), @@ -197,7 +230,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { Future> getSharedUsers(String albumId) async { final albumUserRows = await (_db.select( _db.remoteAlbumUserEntity, - )..where((row) => row.albumId.equals(albumId))).get(); + )..where((row) => row.albumId.equals(albumId) & row.role.isNotValue(AlbumUserRole.owner.index))).get(); if (albumUserRows.isEmpty) { return []; @@ -295,19 +328,21 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), useColumns: false, ), - leftOuterJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), - useColumns: false, - ), leftOuterJoin( _db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), + innerJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & + _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & + _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), + useColumns: false, + ), ]) ..where(_db.remoteAlbumEntity.id.equals(albumId)) - ..addColumns([_db.userEntity.name]) + ..addColumns([_db.userEntity.name, _db.userEntity.id]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..groupBy([_db.remoteAlbumEntity.id]); @@ -315,6 +350,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { final album = row .readTable(_db.remoteAlbumEntity) .toDto( + ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name)!, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ); @@ -355,6 +391,37 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { return _db.managers.remoteAlbumEntity.count(); } + Future getOwner(String albumId) { + final query = + _db.userEntity.select().join([ + innerJoin( + _db.remoteAlbumUserEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId), + useColumns: false, + ), + ])..where( + _db.remoteAlbumUserEntity.albumId.equals(albumId) & + _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), + ); + + return query + .map( + (row) => UserDto( + id: row.read(_db.userEntity.id)!, + email: row.read(_db.userEntity.email)!, + name: row.read(_db.userEntity.name)!, + memoryEnabled: true, + inTimeline: false, + isPartnerSharedBy: false, + isPartnerSharedWith: false, + profileChangedAt: row.read(_db.userEntity.profileChangedAt)!, + hasProfileImage: row.read(_db.userEntity.hasProfileImage)!, + avatarColor: AvatarColor.values[row.read(_db.userEntity.avatarColor)!], + ), + ) + .getSingle(); + } + Future> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async { // Find remote asset ids that: // 1. Belong to the provided local album (via local_album_asset_entity) @@ -416,21 +483,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), useColumns: false, ), - leftOuterJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), - useColumns: false, - ), leftOuterJoin( _db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), + innerJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & + _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & + _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), + useColumns: false, + ), ]) ..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull()) ..addColumns([assetCount]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) - ..addColumns([_db.userEntity.name]) + ..addColumns([_db.userEntity.name, _db.userEntity.id]) ..groupBy([_db.remoteAlbumEntity.id]); return query @@ -438,6 +507,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { (row) => row .readTable(_db.remoteAlbumEntity) .toDto( + ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name) ?? '', isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, assetCount: row.read(assetCount) ?? 0, @@ -448,7 +518,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { } extension on RemoteAlbumEntityData { - RemoteAlbum toDto({int assetCount = 0, required String ownerName, required bool isShared}) { + RemoteAlbum toDto({int assetCount = 0, required String ownerName, required String ownerId, required bool isShared}) { return RemoteAlbum( id: id, name: name, diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 624759b2e6..83a0d6d38f 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -53,7 +53,10 @@ class SyncApiRepository { SyncRequestType.partnersV1, SyncRequestType.partnerAssetsV1, SyncRequestType.partnerAssetExifsV1, - SyncRequestType.albumsV1, + if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0)) + SyncRequestType.albumsV1 + else + SyncRequestType.albumsV2, SyncRequestType.albumUsersV1, SyncRequestType.albumAssetsV1, SyncRequestType.albumAssetExifsV1, @@ -162,6 +165,7 @@ const _kResponseMap = { SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson, SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson, SyncEntityType.albumV1: SyncAlbumV1.fromJson, + SyncEntityType.albumV2: SyncAlbumV2.fromJson, SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson, SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson, SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson, diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index fd8c843433..8079b00503 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -30,7 +30,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction; -import 'package:openapi/api.dart' hide AlbumUserRole, UserMetadataKey, AssetEditAction, AssetVisibility; +import 'package:openapi/api.dart' hide UserMetadataKey, AssetEditAction, AssetVisibility, AlbumUserRole; class SyncStreamRepository extends DriftDatabaseRepository { final Logger _logger = Logger('DriftSyncStreamRepository'); @@ -397,6 +397,47 @@ class SyncStreamRepository extends DriftDatabaseRepository { } Future updateAlbumsV1(Iterable data) async { + try { + await _db.transaction(() async { + await _db.batch((batch) { + for (final album in data) { + final companion = RemoteAlbumEntityCompanion( + name: Value(album.name), + description: Value(album.description), + isActivityEnabled: Value(album.isActivityEnabled), + order: Value(album.order.toAlbumAssetOrder()), + thumbnailAssetId: Value(album.thumbnailAssetId), + createdAt: Value(album.createdAt), + updatedAt: Value(album.updatedAt), + ); + + batch.insert( + _db.remoteAlbumEntity, + companion.copyWith(id: Value(album.id)), + onConflict: DoUpdate((_) => companion), + ); + } + }); + + await _db.batch((batch) { + for (final album in data) { + final companion = RemoteAlbumUserEntityCompanion( + albumId: Value(album.id), + userId: Value(album.ownerId), + role: const Value(AlbumUserRole.owner), + ); + + batch.insert(_db.remoteAlbumUserEntity, companion, onConflict: DoUpdate((_) => companion)); + } + }); + }); + } catch (error, stack) { + _logger.severe('Error: updateAlbumsV1', error, stack); + rethrow; + } + } + + Future updateAlbumsV2(Iterable data) async { try { await _db.batch((batch) { for (final album in data) { @@ -406,7 +447,6 @@ class SyncStreamRepository extends DriftDatabaseRepository { isActivityEnabled: Value(album.isActivityEnabled), order: Value(album.order.toAlbumAssetOrder()), thumbnailAssetId: Value(album.thumbnailAssetId), - ownerId: Value(album.ownerId), createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), ); @@ -419,7 +459,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { } }); } catch (error, stack) { - _logger.severe('Error: updateAlbumsV1', error, stack); + _logger.severe('Error: updateAlbumsV2', error, stack); rethrow; } } @@ -821,6 +861,7 @@ extension on api.AlbumUserRole { AlbumUserRole toAlbumUserRole() => switch (this) { api.AlbumUserRole.editor => AlbumUserRole.editor, api.AlbumUserRole.viewer => AlbumUserRole.viewer, + api.AlbumUserRole.owner => AlbumUserRole.owner, _ => throw Exception('Unknown AlbumUserRole value: $this'), }; } diff --git a/mobile/lib/providers/infrastructure/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index 3c00e2732c..949e6d747e 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:logging/logging.dart'; class RemoteAlbumState { @@ -81,7 +82,17 @@ class RemoteAlbumNotifier extends Notifier { List assetIds = const [], }) async { try { - final album = await _remoteAlbumService.createAlbum(title: title, description: description, assetIds: assetIds); + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + throw Exception('User not logged in'); + } + + final album = await _remoteAlbumService.createAlbum( + title: title, + owner: currentUser, + description: description, + assetIds: assetIds, + ); state = state.copyWith(albums: [...state.albums, album]); diff --git a/mobile/lib/repositories/drift_album_api_repository.dart b/mobile/lib/repositories/drift_album_api_repository.dart index 557050323a..a0c7a3732a 100644 --- a/mobile/lib/repositories/drift_album_api_repository.dart +++ b/mobile/lib/repositories/drift_album_api_repository.dart @@ -1,9 +1,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/repositories/api.repository.dart'; // ignore: import_rule_openapi -import 'package:openapi/api.dart'; +import 'package:openapi/api.dart' hide AlbumUserRole; final driftAlbumApiRepositoryProvider = Provider( (ref) => DriftAlbumApiRepository(ref.watch(apiServiceProvider).albumsApi), @@ -14,12 +15,17 @@ class DriftAlbumApiRepository extends ApiRepository { DriftAlbumApiRepository(this._api); - Future createDriftAlbum(String name, {required Iterable assetIds, String? description}) async { + Future createDriftAlbum( + String name, + UserDto owner, { + required Iterable assetIds, + String? description, + }) async { final responseDto = await checkNull( _api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())), ); - return responseDto.toRemoteAlbum(); + return responseDto.toRemoteAlbum(owner); } Future<({List removed, List failed})> removeAssets(String albumId, Iterable assetIds) async { @@ -50,7 +56,8 @@ class DriftAlbumApiRepository extends ApiRepository { } Future updateAlbum( - String albumId, { + String albumId, + UserDto owner, { String? name, String? description, String? thumbnailAssetId, @@ -75,17 +82,16 @@ class DriftAlbumApiRepository extends ApiRepository { ), ); - return responseDto.toRemoteAlbum(); + return responseDto.toRemoteAlbum(owner); } Future deleteAlbum(String albumId) { return _api.deleteAlbum(albumId); } - Future addUsers(String albumId, Iterable userIds) async { + Future addUsers(String albumId, Iterable userIds) async { final albumUsers = userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList(); - final response = await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers))); - return response.toRemoteAlbum(); + await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers))); } Future removeUser(String albumId, {required String userId}) async { @@ -99,11 +105,12 @@ class DriftAlbumApiRepository extends ApiRepository { } extension on AlbumResponseDto { - RemoteAlbum toRemoteAlbum() { + RemoteAlbum toRemoteAlbum(final UserDto user) { return RemoteAlbum( id: id, name: albumName, - ownerId: owner.id, + ownerId: user.id, + ownerName: user.name, description: description, createdAt: createdAt, updatedAt: updatedAt, @@ -111,7 +118,6 @@ extension on AlbumResponseDto { isActivityEnabled: isActivityEnabled, order: order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, assetCount: assetCount, - ownerName: owner.name, isShared: albumUsers.length > 2, ); } diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 4a195017d3..44b070e954 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -241,7 +241,8 @@ class ActionService { } Future setAlbumCover(String albumId, String assetId) async { - final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, thumbnailAssetId: assetId); + final owner = await _remoteAlbumRepository.getOwner(albumId); + final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, owner, thumbnailAssetId: assetId); await _remoteAlbumRepository.update(updatedAlbum); return true; } diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 50bbff2bae..cf702f62ff 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -568,6 +568,7 @@ Class | Method | HTTP request | Description - [SyncAlbumUserDeleteV1](doc//SyncAlbumUserDeleteV1.md) - [SyncAlbumUserV1](doc//SyncAlbumUserV1.md) - [SyncAlbumV1](doc//SyncAlbumV1.md) + - [SyncAlbumV2](doc//SyncAlbumV2.md) - [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md) - [SyncAssetEditDeleteV1](doc//SyncAssetEditDeleteV1.md) - [SyncAssetEditV1](doc//SyncAssetEditV1.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 9eca7a2ab7..1e819595fc 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -316,6 +316,7 @@ part 'model/sync_album_to_asset_v1.dart'; part 'model/sync_album_user_delete_v1.dart'; part 'model/sync_album_user_v1.dart'; part 'model/sync_album_v1.dart'; +part 'model/sync_album_v2.dart'; part 'model/sync_asset_delete_v1.dart'; part 'model/sync_asset_edit_delete_v1.dart'; part 'model/sync_asset_edit_v1.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index b8799a7be5..5a4b7b75c7 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -678,6 +678,8 @@ class ApiClient { return SyncAlbumUserV1.fromJson(value); case 'SyncAlbumV1': return SyncAlbumV1.fromJson(value); + case 'SyncAlbumV2': + return SyncAlbumV2.fromJson(value); case 'SyncAssetDeleteV1': return SyncAssetDeleteV1.fromJson(value); case 'SyncAssetEditDeleteV1': diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index 348e25ddaf..fd90f23e3a 100644 --- a/mobile/openapi/lib/model/album_response_dto.dart +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -26,8 +26,6 @@ class AlbumResponseDto { required this.isActivityEnabled, this.lastModifiedAssetTimestamp, this.order, - required this.owner, - required this.ownerId, required this.shared, this.startDate, required this.updatedAt, @@ -39,6 +37,7 @@ class AlbumResponseDto { /// Thumbnail asset ID String? albumThumbnailAssetId; + /// First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically. List albumUsers; /// Number of assets @@ -90,11 +89,6 @@ class AlbumResponseDto { /// AssetOrder? order; - UserResponseDto owner; - - /// Owner user ID - String ownerId; - /// Is shared album bool shared; @@ -125,8 +119,6 @@ class AlbumResponseDto { other.isActivityEnabled == isActivityEnabled && other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp && other.order == order && - other.owner == owner && - other.ownerId == ownerId && other.shared == shared && other.startDate == startDate && other.updatedAt == updatedAt; @@ -147,14 +139,12 @@ class AlbumResponseDto { (isActivityEnabled.hashCode) + (lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) + (order == null ? 0 : order!.hashCode) + - (owner.hashCode) + - (ownerId.hashCode) + (shared.hashCode) + (startDate == null ? 0 : startDate!.hashCode) + (updatedAt.hashCode); @override - String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]'; + String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -187,8 +177,6 @@ class AlbumResponseDto { } else { // json[r'order'] = null; } - json[r'owner'] = this.owner; - json[r'ownerId'] = this.ownerId; json[r'shared'] = this.shared; if (this.startDate != null) { json[r'startDate'] = this.startDate!.toUtc().toIso8601String(); @@ -221,8 +209,6 @@ class AlbumResponseDto { isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''), order: AssetOrder.fromJson(json[r'order']), - owner: UserResponseDto.fromJson(json[r'owner'])!, - ownerId: mapValueOfType(json, r'ownerId')!, shared: mapValueOfType(json, r'shared')!, startDate: mapDateTime(json, r'startDate', r''), updatedAt: mapDateTime(json, r'updatedAt', r'')!, @@ -282,8 +268,6 @@ class AlbumResponseDto { 'hasSharedLink', 'id', 'isActivityEnabled', - 'owner', - 'ownerId', 'shared', 'updatedAt', }; diff --git a/mobile/openapi/lib/model/album_user_role.dart b/mobile/openapi/lib/model/album_user_role.dart index d797fdc2e8..6fb7830115 100644 --- a/mobile/openapi/lib/model/album_user_role.dart +++ b/mobile/openapi/lib/model/album_user_role.dart @@ -24,11 +24,13 @@ class AlbumUserRole { String toJson() => value; static const editor = AlbumUserRole._(r'editor'); + static const owner = AlbumUserRole._(r'owner'); static const viewer = AlbumUserRole._(r'viewer'); /// List of all possible values in this [enum][AlbumUserRole]. static const values = [ editor, + owner, viewer, ]; @@ -69,6 +71,7 @@ class AlbumUserRoleTypeTransformer { if (data != null) { switch (data) { case r'editor': return AlbumUserRole.editor; + case r'owner': return AlbumUserRole.owner; case r'viewer': return AlbumUserRole.viewer; default: if (!allowNull) { diff --git a/mobile/openapi/lib/model/sync_album_v2.dart b/mobile/openapi/lib/model/sync_album_v2.dart new file mode 100644 index 0000000000..67c65a190b --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_v2.dart @@ -0,0 +1,170 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAlbumV2 { + /// Returns a new [SyncAlbumV2] instance. + SyncAlbumV2({ + required this.createdAt, + required this.description, + required this.id, + required this.isActivityEnabled, + required this.name, + required this.order, + required this.thumbnailAssetId, + required this.updatedAt, + }); + + /// Created at + DateTime createdAt; + + /// Album description + String description; + + /// Album ID + String id; + + /// Is activity enabled + bool isActivityEnabled; + + /// Album name + String name; + + AssetOrder order; + + /// Thumbnail asset ID + String? thumbnailAssetId; + + /// Updated at + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumV2 && + other.createdAt == createdAt && + other.description == description && + other.id == id && + other.isActivityEnabled == isActivityEnabled && + other.name == name && + other.order == order && + other.thumbnailAssetId == thumbnailAssetId && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (description.hashCode) + + (id.hashCode) + + (isActivityEnabled.hashCode) + + (name.hashCode) + + (order.hashCode) + + (thumbnailAssetId == null ? 0 : thumbnailAssetId!.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'SyncAlbumV2[createdAt=$createdAt, description=$description, id=$id, isActivityEnabled=$isActivityEnabled, name=$name, order=$order, thumbnailAssetId=$thumbnailAssetId, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.createdAt.millisecondsSinceEpoch + : this.createdAt.toUtc().toIso8601String(); + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'isActivityEnabled'] = this.isActivityEnabled; + json[r'name'] = this.name; + json[r'order'] = this.order; + if (this.thumbnailAssetId != null) { + json[r'thumbnailAssetId'] = this.thumbnailAssetId; + } else { + // json[r'thumbnailAssetId'] = null; + } + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? this.updatedAt.millisecondsSinceEpoch + : this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [SyncAlbumV2] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumV2? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumV2"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumV2( + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, + name: mapValueOfType(json, r'name')!, + order: AssetOrder.fromJson(json[r'order'])!, + thumbnailAssetId: mapValueOfType(json, r'thumbnailAssetId'), + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumV2.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumV2.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumV2-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumV2.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'description', + 'id', + 'isActivityEnabled', + 'name', + 'order', + 'thumbnailAssetId', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/sync_entity_type.dart b/mobile/openapi/lib/model/sync_entity_type.dart index e8db2dc4d3..a8cf011ee0 100644 --- a/mobile/openapi/lib/model/sync_entity_type.dart +++ b/mobile/openapi/lib/model/sync_entity_type.dart @@ -44,6 +44,7 @@ class SyncEntityType { static const partnerStackDeleteV1 = SyncEntityType._(r'PartnerStackDeleteV1'); static const partnerStackV1 = SyncEntityType._(r'PartnerStackV1'); static const albumV1 = SyncEntityType._(r'AlbumV1'); + static const albumV2 = SyncEntityType._(r'AlbumV2'); static const albumDeleteV1 = SyncEntityType._(r'AlbumDeleteV1'); static const albumUserV1 = SyncEntityType._(r'AlbumUserV1'); static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1'); @@ -97,6 +98,7 @@ class SyncEntityType { partnerStackDeleteV1, partnerStackV1, albumV1, + albumV2, albumDeleteV1, albumUserV1, albumUserBackfillV1, @@ -185,6 +187,7 @@ class SyncEntityTypeTypeTransformer { case r'PartnerStackDeleteV1': return SyncEntityType.partnerStackDeleteV1; case r'PartnerStackV1': return SyncEntityType.partnerStackV1; case r'AlbumV1': return SyncEntityType.albumV1; + case r'AlbumV2': return SyncEntityType.albumV2; case r'AlbumDeleteV1': return SyncEntityType.albumDeleteV1; case r'AlbumUserV1': return SyncEntityType.albumUserV1; case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1; diff --git a/mobile/openapi/lib/model/sync_request_type.dart b/mobile/openapi/lib/model/sync_request_type.dart index f51cc8bde9..c50d5bb906 100644 --- a/mobile/openapi/lib/model/sync_request_type.dart +++ b/mobile/openapi/lib/model/sync_request_type.dart @@ -24,6 +24,7 @@ class SyncRequestType { String toJson() => value; static const albumsV1 = SyncRequestType._(r'AlbumsV1'); + static const albumsV2 = SyncRequestType._(r'AlbumsV2'); static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1'); static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1'); static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1'); @@ -49,6 +50,7 @@ class SyncRequestType { /// List of all possible values in this [enum][SyncRequestType]. static const values = [ albumsV1, + albumsV2, albumUsersV1, albumToAssetsV1, albumAssetsV1, @@ -109,6 +111,7 @@ class SyncRequestTypeTypeTransformer { if (data != null) { switch (data) { case r'AlbumsV1': return SyncRequestType.albumsV1; + case r'AlbumsV2': return SyncRequestType.albumsV2; case r'AlbumUsersV1': return SyncRequestType.albumUsersV1; case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1; case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1; diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index 0b68a8fe6e..904ebc1348 100644 --- a/mobile/test/drift/main/generated/schema.dart +++ b/mobile/test/drift/main/generated/schema.dart @@ -27,6 +27,7 @@ import 'schema_v20.dart' as v20; import 'schema_v21.dart' as v21; import 'schema_v22.dart' as v22; import 'schema_v23.dart' as v23; +import 'schema_v24.dart' as v24; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -78,6 +79,8 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v22.DatabaseAtV22(db); case 23: return v23.DatabaseAtV23(db); + case 24: + return v24.DatabaseAtV24(db); default: throw MissingSchemaException(version, versions); } @@ -107,5 +110,6 @@ class GeneratedHelper implements SchemaInstantiationHelper { 21, 22, 23, + 24, ]; } diff --git a/mobile/test/drift/main/generated/schema_v24.dart b/mobile/test/drift/main/generated/schema_v24.dart new file mode 100644 index 0000000000..872731c31f --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v24.dart @@ -0,0 +1,9131 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn localDateTime = GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_edited IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String checksum; + final int isFavorite; + final String ownerId; + final String? localDateTime; + final String? thumbHash; + final String? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final int isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? checksum, + int? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + int? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String? checksum; + final int isFavorite; + final int orientation; + final String? iCloudId; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'\'', + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_asset_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 1 CHECK (is_activity_enabled IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final String createdAt; + final String updatedAt; + final String? thumbnailAssetId; + final int isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + String? createdAt, + String? updatedAt, + Value thumbnailAssetId = const Value.absent(), + int? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (is_ios_shared_album IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_album_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String updatedAt; + final int backupSelection; + final int isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final int? marker; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + String? updatedAt, + int? backupSelection, + int? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker == this.marker); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL CHECK (marker IN (0, 1))', + ); + @override + List get $columns => [assetId, albumId, marker]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final int? marker; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker != null) { + map['marker'] = Variable(marker); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker: serializer.fromJson(json['marker']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker': serializer.toJson(marker), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker.present ? marker.value : this.marker, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker: data.marker.present ? data.marker.value : this.marker, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker == this.marker); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker != null) 'marker': marker, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker: marker ?? this.marker, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker.present) { + map['marker'] = Variable(marker.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker: $marker') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_admin IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final int isAdmin; + final int hasProfileImage; + final String profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + int? isAdmin, + int? hasProfileImage, + String? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = + GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(user_id, "key")']; + @override + bool get dontWriteConstraints => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final i2.Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + i2.Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required i2.Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (in_timeline IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(shared_by_id, shared_with_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final int inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + int? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn dateTimeOriginal = GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final String? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson(json['dateTimeOriginal']), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(album_id, user_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final String? createdAt; + final String? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_saved IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String? deletedAt; + final String ownerId; + final int type; + final String data; + final int isSaved; + final String memoryAt; + final String? seenAt; + final String? showAt; + final String? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + int? isSaved, + String? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required String memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES memory_entity(id)ON DELETE CASCADE', + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, memory_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_favorite IN (0, 1))', + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_hidden IN (0, 1))', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final int isFavorite; + final int isHidden; + final String? color; + final String? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + String? createdAt, + String? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + int? isFavorite, + int? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required int isFavorite, + required int isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL REFERENCES person_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final int isVisible; + final String? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + int? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id, album_id)']; + @override + bool get dontWriteConstraints => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String albumId; + final String? checksum; + final int isFavorite; + final int orientation; + final int source; + final int playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationMs: serializer.fromJson(json['durationMs']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationMs': serializer.toJson(durationMs), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + int? isFavorite, + int? orientation, + int? source, + int? playbackStyle, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationMs == this.durationMs && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationMs; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + final Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationMs = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationMs, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationMs != null) 'duration_ms': durationMs, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationMs, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + Value? playbackStyle, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationMs: durationMs ?? this.durationMs, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationMs.present) { + map['duration_ms'] = Variable(durationMs.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class AssetEditEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetEditEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parameters = + GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + parameters: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + sequence: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + AssetEditEntity createAlias(String alias) { + return AssetEditEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final i2.Uint8List parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['action'] = Variable(action); + map['parameters'] = Variable(parameters); + map['sequence'] = Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: serializer.fromJson(json['action']), + parameters: serializer.fromJson(json['parameters']), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson(action), + 'parameters': serializer.toJson(parameters), + 'sequence': serializer.toJson(sequence), + }; + } + + AssetEditEntityData copyWith({ + String? id, + String? assetId, + int? action, + i2.Uint8List? parameters, + int? sequence, + }) => AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + action, + $driftBlobEquality.hash(parameters), + sequence, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + $driftBlobEquality.equals(other.parameters, this.parameters) && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value action; + final Value parameters; + final Value sequence; + const AssetEditEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.action = const Value.absent(), + this.parameters = const Value.absent(), + this.sequence = const Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required int action, + required i2.Uint8List parameters, + required int sequence, + }) : id = Value(id), + assetId = Value(assetId), + action = Value(action), + parameters = Value(parameters), + sequence = Value(sequence); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? action, + Expression? parameters, + Expression? sequence, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + AssetEditEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? action, + Value? parameters, + Value? sequence, + }) { + return AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (action.present) { + map['action'] = Variable(action.value); + } + if (parameters.present) { + map['parameters'] = Variable(parameters.value); + } + if (sequence.present) { + map['sequence'] = Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV24 extends GeneratedDatabase { + DatabaseAtV24(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetLocalDateTimeDay = Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + late final Index idxRemoteAssetLocalDateTimeMonth = Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final AssetEditEntity assetEditEntity = AssetEditEntity(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + late final Index idxAssetEditAssetId = Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('stack_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('local_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('user_metadata_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_exif_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_asset_cloud_id_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('person_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], + ), + ]); + @override + int get schemaVersion => 24; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/medium/repository_context.dart b/mobile/test/medium/repository_context.dart index da19ded9f7..6e00f268fa 100644 --- a/mobile/test/medium/repository_context.dart +++ b/mobile/test/medium/repository_context.dart @@ -11,6 +11,7 @@ import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.d import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; @@ -151,13 +152,13 @@ class MediumRepositoryContext { String? thumbnailAssetId, }) async { id = TestUtils.uuid(id); - return db + + final album = await db .into(db.remoteAlbumEntity) .insertReturning( RemoteAlbumEntityCompanion( id: Value(id), name: Value(name ?? 'remote_album_$id'), - ownerId: Value(TestUtils.uuid(ownerId)), createdAt: Value(TestUtils.date(createdAt)), updatedAt: Value(TestUtils.date(updatedAt)), description: Value(description ?? 'Description for album $id'), @@ -166,6 +167,18 @@ class MediumRepositoryContext { thumbnailAssetId: Value(thumbnailAssetId), ), ); + + await db + .into(db.remoteAlbumUserEntity) + .insert( + RemoteAlbumUserEntityCompanion.insert( + albumId: id, + userId: ownerId ?? const Uuid().v4(), + role: AlbumUserRole.owner, + ), + ); + + return album; } Future insertRemoteAlbumAsset({required String albumId, required String assetId}) { diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 48dca7f6db..ac1de35252 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -15279,9 +15279,11 @@ "type": "string" }, "albumUsers": { + "description": "First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically.", "items": { "$ref": "#/components/schemas/AlbumUserResponseDto" }, + "minItems": 1, "type": "array" }, "assetCount": { @@ -15330,13 +15332,6 @@ "order": { "$ref": "#/components/schemas/AssetOrder" }, - "owner": { - "$ref": "#/components/schemas/UserResponseDto" - }, - "ownerId": { - "description": "Owner user ID", - "type": "string" - }, "shared": { "description": "Is shared album", "type": "boolean" @@ -15362,8 +15357,6 @@ "hasSharedLink", "id", "isActivityEnabled", - "owner", - "ownerId", "shared", "updatedAt" ], @@ -15453,6 +15446,7 @@ "description": "Album user role", "enum": [ "editor", + "owner", "viewer" ], "type": "string" @@ -22531,6 +22525,59 @@ ], "type": "object" }, + "SyncAlbumV2": { + "properties": { + "createdAt": { + "description": "Created at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "type": "string" + }, + "description": { + "description": "Album description", + "type": "string" + }, + "id": { + "description": "Album ID", + "type": "string" + }, + "isActivityEnabled": { + "description": "Is activity enabled", + "type": "boolean" + }, + "name": { + "description": "Album name", + "type": "string" + }, + "order": { + "$ref": "#/components/schemas/AssetOrder" + }, + "thumbnailAssetId": { + "description": "Thumbnail asset ID", + "nullable": true, + "type": "string" + }, + "updatedAt": { + "description": "Updated at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "type": "string" + } + }, + "required": [ + "createdAt", + "description", + "id", + "isActivityEnabled", + "name", + "order", + "thumbnailAssetId", + "updatedAt" + ], + "type": "object" + }, "SyncAssetDeleteV1": { "properties": { "assetId": { @@ -23216,6 +23263,7 @@ "PartnerStackDeleteV1", "PartnerStackV1", "AlbumV1", + "AlbumV2", "AlbumDeleteV1", "AlbumUserV1", "AlbumUserBackfillV1", @@ -23510,6 +23558,7 @@ "description": "Sync request type", "enum": [ "AlbumsV1", + "AlbumsV2", "AlbumUsersV1", "AlbumToAssetsV1", "AlbumAssetsV1", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index bb269024f6..40c4bec235 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -457,6 +457,7 @@ export type AlbumResponseDto = { albumName: string; /** Thumbnail asset ID */ albumThumbnailAssetId: string | null; + /** First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically. */ albumUsers: AlbumUserResponseDto[]; /** Number of assets */ assetCount: number; @@ -476,9 +477,6 @@ export type AlbumResponseDto = { /** Last modified asset timestamp */ lastModifiedAssetTimestamp?: string; order?: AssetOrder; - owner: UserResponseDto; - /** Owner user ID */ - ownerId: string; /** Is shared album */ shared: boolean; /** Start date (earliest asset) */ @@ -2881,6 +2879,23 @@ export type SyncAlbumV1 = { /** Updated at */ updatedAt: string; }; +export type SyncAlbumV2 = { + /** Created at */ + createdAt: string; + /** Album description */ + description: string; + /** Album ID */ + id: string; + /** Is activity enabled */ + isActivityEnabled: boolean; + /** Album name */ + name: string; + order: AssetOrder; + /** Thumbnail asset ID */ + thumbnailAssetId: string | null; + /** Updated at */ + updatedAt: string; +}; export type SyncAssetDeleteV1 = { /** Asset ID */ assetId: string; @@ -6731,6 +6746,7 @@ export enum AssetVisibility { } export enum AlbumUserRole { Editor = "editor", + Owner = "owner", Viewer = "viewer" } export enum BulkIdErrorReason { @@ -7110,6 +7126,7 @@ export enum SyncEntityType { PartnerStackDeleteV1 = "PartnerStackDeleteV1", PartnerStackV1 = "PartnerStackV1", AlbumV1 = "AlbumV1", + AlbumV2 = "AlbumV2", AlbumDeleteV1 = "AlbumDeleteV1", AlbumUserV1 = "AlbumUserV1", AlbumUserBackfillV1 = "AlbumUserBackfillV1", @@ -7142,6 +7159,7 @@ export enum SyncEntityType { } export enum SyncRequestType { AlbumsV1 = "AlbumsV1", + AlbumsV2 = "AlbumsV2", AlbumUsersV1 = "AlbumUsersV1", AlbumToAssetsV1 = "AlbumToAssetsV1", AlbumAssetsV1 = "AlbumAssetsV1", diff --git a/server/src/database.ts b/server/src/database.ts index 29d0353891..c001388e79 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -195,7 +195,6 @@ export type SharedLink = { }; export type Album = Selectable & { - owner: ShallowDehydrateObject; assets: ShallowDehydrateObject>[]; }; diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 519094cd94..33870cd6fc 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -1,7 +1,6 @@ import { ShallowDehydrateObject } from 'kysely'; -import _ from 'lodash'; import { createZodDto } from 'nestjs-zod'; -import { AlbumUser, AuthSharedLink, User } from 'src/database'; +import { AlbumUser, AuthSharedLink } from 'src/database'; import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto'; import { MapAsset } from 'src/dtos/asset-response.dto'; import { UserResponseSchema, mapUser } from 'src/dtos/user.dto'; @@ -104,7 +103,6 @@ const ContributorCountResponseSchema = z export const AlbumResponseSchema = z .object({ id: z.string().describe('Album ID'), - ownerId: z.string().describe('Owner user ID'), albumName: z.string().describe('Album name'), description: z.string().describe('Album description'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. @@ -113,9 +111,13 @@ export const AlbumResponseSchema = z updatedAt: z.string().meta({ format: 'date-time' }).describe('Last update date'), albumThumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'), shared: z.boolean().describe('Is shared album'), - albumUsers: z.array(AlbumUserResponseSchema), + albumUsers: z + .array(AlbumUserResponseSchema) + .min(1) + .describe( + 'First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically.', + ), hasSharedLink: z.boolean().describe('Has shared link'), - owner: UserResponseSchema, assetCount: z.int().min(0).describe('Number of assets'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. lastModifiedAssetTimestamp: z @@ -155,8 +157,6 @@ export type MapAlbumDto = { createdAt: Date; updatedAt: Date; id: string; - ownerId: string; - owner: ShallowDehydrateObject; isActivityEnabled: boolean; order: AssetOrder; }; @@ -174,12 +174,10 @@ export const mapAlbum = (entity: MaybeDehydrated): AlbumResponseDto } } - const albumUsersSorted = _.orderBy(albumUsers, ['role', 'user.name']); - const assets = entity.assets || []; const hasSharedLink = !!entity.sharedLinks && entity.sharedLinks.length > 0; - const hasSharedUser = albumUsers.length > 0; + const hasSharedUser = albumUsers.length > 1; let startDate = assets.at(0)?.localDateTime; let endDate = assets.at(-1)?.localDateTime; @@ -195,9 +193,7 @@ export const mapAlbum = (entity: MaybeDehydrated): AlbumResponseDto createdAt: asDateString(entity.createdAt), updatedAt: asDateString(entity.updatedAt), id: entity.id, - ownerId: entity.ownerId, - owner: mapUser(entity.owner), - albumUsers: albumUsersSorted, + albumUsers, shared: hasSharedUser || hasSharedLink, hasSharedLink, startDate: asDateString(startDate), diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index f6383d0a5d..0df617813d 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -2,6 +2,7 @@ import { createZodDto } from 'nestjs-zod'; import { AssetEditActionSchema } from 'src/dtos/editing.dto'; import { + AlbumUserRole, AlbumUserRoleSchema, AssetOrderSchema, AssetTypeSchema, @@ -211,6 +212,19 @@ const SyncAlbumV1Schema = z }) .meta({ id: 'SyncAlbumV1' }); +const SyncAlbumV2Schema = z + .object({ + id: z.string().describe('Album ID'), + name: z.string().describe('Album name'), + description: z.string().describe('Album description'), + createdAt: isoDatetimeToDate.describe('Created at'), + updatedAt: isoDatetimeToDate.describe('Updated at'), + thumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'), + isActivityEnabled: z.boolean().describe('Is activity enabled'), + order: AssetOrderSchema, + }) + .meta({ id: 'SyncAlbumV2' }); + const SyncAlbumToAssetV1Schema = z .object({ albumId: z.string().describe('Album ID'), @@ -234,10 +248,21 @@ class SyncAlbumUserV1 extends createZodDto(SyncAlbumUserV1Schema) {} @ExtraModel() class SyncAlbumV1 extends createZodDto(SyncAlbumV1Schema) {} @ExtraModel() +class SyncAlbumV2 extends createZodDto(SyncAlbumV2Schema) {} +@ExtraModel() class SyncAlbumToAssetV1 extends createZodDto(SyncAlbumToAssetV1Schema) {} @ExtraModel() class SyncAlbumToAssetDeleteV1 extends createZodDto(SyncAlbumToAssetDeleteV1Schema) {} +export function syncAlbumV2ToV1( + albumV2: SyncAlbumV2, + albumUsers: { userId: string; role: AlbumUserRole }[], +): SyncAlbumV1 { + const owner = albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + + return { ...albumV2, ownerId: owner.userId }; +} + const SyncMemoryV1Schema = z .object({ id: z.string().describe('Memory ID'), @@ -407,6 +432,7 @@ export type SyncItem = { [SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1; [SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1; [SyncEntityType.AlbumV1]: SyncAlbumV1; + [SyncEntityType.AlbumV2]: SyncAlbumV2; [SyncEntityType.AlbumDeleteV1]: SyncAlbumDeleteV1; [SyncEntityType.AlbumUserV1]: SyncAlbumUserV1; [SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1; diff --git a/server/src/enum.ts b/server/src/enum.ts index 76e1841a89..8a1993b48f 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -61,6 +61,7 @@ export enum AssetFileType { export enum AlbumUserRole { Editor = 'editor', + Owner = 'owner', Viewer = 'viewer', } @@ -797,6 +798,7 @@ export enum ExitCode { export enum SyncRequestType { AlbumsV1 = 'AlbumsV1', + AlbumsV2 = 'AlbumsV2', AlbumUsersV1 = 'AlbumUsersV1', AlbumToAssetsV1 = 'AlbumToAssetsV1', AlbumAssetsV1 = 'AlbumAssetsV1', @@ -852,6 +854,7 @@ export enum SyncEntityType { PartnerStackV1 = 'PartnerStackV1', AlbumV1 = 'AlbumV1', + AlbumV2 = 'AlbumV2', AlbumDeleteV1 = 'AlbumDeleteV1', AlbumUserV1 = 'AlbumUserV1', diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql index 810229093b..94d3b4d003 100644 --- a/server/src/queries/access.repository.sql +++ b/server/src/queries/access.repository.sql @@ -14,27 +14,26 @@ select "activity"."id" from "activity" - left join "album" on "activity"."albumId" = "album"."id" + inner join "album" on "activity"."albumId" = "album"."id" and "album"."deletedAt" is null + inner join "album_user" on "album"."id" = "album_user"."albumId" + and "album_user"."role" = 'owner' + and "album_user"."userId" = $1::uuid where - "activity"."id" in ($1) - and "album"."ownerId" = $2::uuid + "activity"."id" in ($2) -- AccessRepository.activity.checkCreateAccess select "album"."id" from "album" - left join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id" - left join "user" on "user"."id" = "albumUsers"."userId" + inner join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id" + inner join "user" on "user"."id" = "albumUsers"."userId" and "user"."deletedAt" is null where "album"."id" in ($1) and "album"."isActivityEnabled" = $2 - and ( - "album"."ownerId" = $3 - or "user"."id" = $4 - ) + and "user"."id" = $3 and "album"."deletedAt" is null -- AccessRepository.album.checkOwnerAccess @@ -42,9 +41,11 @@ select "album"."id" from "album" + inner join "album_user" on "album"."id" = "album_user"."albumId" + and "album_user"."role" = 'owner' + and "album_user"."userId" = $1 where - "album"."id" in ($1) - and "album"."ownerId" = $2 + "album"."id" in ($2) and "album"."deletedAt" is null -- AccessRepository.album.checkSharedAlbumAccess @@ -52,8 +53,8 @@ select "album"."id" from "album" - left join "album_user" on "album_user"."albumId" = "album"."id" - left join "user" on "user"."id" = "album_user"."userId" + inner join "album_user" on "album_user"."albumId" = "album"."id" + inner join "user" on "user"."id" = "album_user"."userId" and "user"."deletedAt" is null where "album"."id" in ($1) @@ -93,10 +94,7 @@ where "asset"."id" = any (target.ids) or "asset"."livePhotoVideoId" = any (target.ids) ) - and ( - "album"."ownerId" = $2 - or "user"."id" = $3 - ) + and "user"."id" = $2 and "album"."deletedAt" is null -- AccessRepository.asset.checkOwnerAccess diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql index cc15260bdb..f5bf79ac9e 100644 --- a/server/src/queries/album.repository.sql +++ b/server/src/queries/album.repository.sql @@ -1,26 +1,17 @@ -- NOTE: This file is auto generated by ./sql-generator -- AlbumRepository.getById +with + "album_user" as ( + select + * + from + "album_user" + where + "album_user"."albumId" = $1 + ) select "album".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", ( select coalesce(json_agg(agg), '[]') @@ -41,15 +32,21 @@ select "profileImagePath", "profileChangedAt" from - "user" - where - "user"."id" = "album_user"."userId" + ( + select + 1 + ) as "dummy" ) as obj ) as "user" from "album_user" + inner join "user" on "user"."id" = "album_user"."userId" where "album_user"."albumId" = "album"."id" + order by + "album_user"."role", + "album_user"."userId" = $2 desc, + "user"."name" asc ) as agg ) as "albumUsers", ( @@ -88,30 +85,12 @@ select from "album" where - "album"."id" = $1 + "album"."id" = $3 and "album"."deletedAt" is null -- AlbumRepository.getByAssetId select "album".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", ( select coalesce(json_agg(agg), '[]') @@ -132,36 +111,38 @@ select "profileImagePath", "profileChangedAt" from - "user" - where - "user"."id" = "album_user"."userId" + ( + select + 1 + ) as "dummy" ) as obj ) as "user" from "album_user" + inner join "user" on "user"."id" = "album_user"."userId" where "album_user"."albumId" = "album"."id" + order by + "album_user"."role", + "album_user"."userId" = $1 desc, + "user"."name" asc ) as agg ) as "albumUsers" from "album" inner join "album_asset" on "album_asset"."albumId" = "album"."id" where - ( - "album"."ownerId" = $1 - or exists ( - select - from - "album_user" - where - "album_user"."albumId" = "album"."id" - and "album_user"."userId" = $2 - ) + exists ( + select + from + "album_user" + where + "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $2 ) and "album_asset"."assetId" = $3 and "album"."deletedAt" is null order by - "album"."createdAt" desc, "album"."createdAt" desc -- AlbumRepository.getByAssetIds @@ -172,18 +153,15 @@ from "album" inner join "album_asset" on "album_asset"."albumId" = "album"."id" where - ( - "album"."ownerId" = $1 - or exists ( - select - from - "album_user" - where - "album_user"."albumId" = "album"."id" - and "album_user"."userId" = $2 - ) + exists ( + select + from + "album_user" + where + "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $1 ) - and "album_asset"."assetId" in ($3) + and "album_asset"."assetId" in ($2) and "album"."deletedAt" is null -- AlbumRepository.getMetadataForIds @@ -210,24 +188,6 @@ group by -- AlbumRepository.getOwned select "album".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", ( select coalesce(json_agg(agg), '[]') @@ -248,15 +208,21 @@ select "profileImagePath", "profileChangedAt" from - "user" - where - "user"."id" = "album_user"."userId" + ( + select + 1 + ) as "dummy" ) as obj ) as "user" from "album_user" + inner join "user" on "user"."id" = "album_user"."userId" where "album_user"."albumId" = "album"."id" + order by + "album_user"."role", + "album_user"."userId" = $1 desc, + "user"."name" asc ) as agg ) as "albumUsers", ( @@ -274,9 +240,11 @@ select ) as "sharedLinks" from "album" + inner join "album_user" on "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $2 + and "album_user"."role" = 'owner' where - "album"."ownerId" = $1 - and "album"."deletedAt" is null + "album"."deletedAt" is null order by "album"."createdAt" desc @@ -303,35 +271,23 @@ select "profileImagePath", "profileChangedAt" from - "user" - where - "user"."id" = "album_user"."userId" + ( + select + 1 + ) as "dummy" ) as obj ) as "user" from "album_user" + inner join "user" on "user"."id" = "album_user"."userId" where "album_user"."albumId" = "album"."id" + order by + "album_user"."role", + "album_user"."userId" = $1 desc, + "user"."name" asc ) as agg ) as "albumUsers", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", ( select coalesce(json_agg(agg), '[]') @@ -347,29 +303,34 @@ select ) as "sharedLinks" from "album" + inner join ( + select + "album_user"."albumId" as "id" + from + "album_user" + where + "album_user"."userId" = $2 + and "album_user"."albumId" in ( + select + "album_user"."albumId" + from + "album_user" + where + "album_user"."role" != 'owner' + ) + union + select + "shared_link"."albumId" as "id" + from + "shared_link" + where + "shared_link"."userId" = $3 + and "shared_link"."albumId" is not null + ) as "matching" on "matching"."id" = "album"."id" + inner join "album_user" on "album_user"."albumId" = "album"."id" + and "album_user"."role" = 'owner' where - ( - exists ( - select - from - "album_user" - where - "album_user"."albumId" = "album"."id" - and ( - "album"."ownerId" = $1 - or "album_user"."userId" = $2 - ) - ) - or exists ( - select - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - and "shared_link"."userId" = $3 - ) - ) - and "album"."deletedAt" is null + "album"."deletedAt" is null order by "album"."createdAt" desc @@ -378,33 +339,68 @@ select "album".*, ( select - to_json(obj) + coalesce(json_agg(agg), '[]') from ( select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" + "shared_link".* from - "user" + "shared_link" where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner" + "shared_link"."albumId" = "album"."id" + ) as agg + ) as "sharedLinks", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "album_user"."role", + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "avatarColor", + "profileImagePath", + "profileChangedAt" + from + ( + select + 1 + ) as "dummy" + ) as obj + ) as "user" + from + "album_user" + inner join "user" on "user"."id" = "album_user"."userId" + where + "album_user"."albumId" = "album"."id" + order by + "album_user"."role", + "album_user"."userId" = $1 desc, + "user"."name" asc + ) as agg + ) as "albumUsers" from "album" + inner join "album_user" on "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $2 + and "album_user"."role" = 'owner' where - "album"."ownerId" = $1 - and "album"."deletedAt" is null + "album"."deletedAt" is null and not exists ( select from - "album_user" + "album_user" as "au" where - "album_user"."albumId" = "album"."id" + "au"."albumId" = "album"."id" + and "au"."role" != 'owner' ) and not exists ( select @@ -430,6 +426,117 @@ where "album_asset"."albumId" = $1 and "album_asset"."assetId" in ($2) +-- AlbumRepository.addAssetIds +insert into + "album_asset" +select + $1::uuid as "albumId", + unnest($2::uuid[]) as "assetId" +from + ( + select + 1 + ) as "dummy" +on conflict do nothing + +-- AlbumRepository.create +with + "album" as ( + insert into + "album" ("albumName") + values + ($1) + returning + * + ), + "album_user" as ( + insert into + "album_user" + select + "album"."id" as "albumId", + unnest($2::uuid[]) as "userId", + unnest($3::album_user_role_enum[]) as "role" + from + "album" + returning + "album_user"."albumId", + "album_user"."userId", + "album_user"."role" + ), + "album_asset" as ( + insert into + "album_asset" + select + "album"."id" as "albumId", + unnest($4::uuid[]) as "assetId" + from + "album" + on conflict do nothing + returning + "album_asset"."albumId", + "album_asset"."assetId" + ) +select + "album".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "album_user"."role", + ( + select + to_json(obj) + from + ( + select + "id", + "name", + "email", + "avatarColor", + "profileImagePath", + "profileChangedAt" + from + ( + select + 1 + ) as "dummy" + ) as obj + ) as "user" + from + "album_user" + inner join "user" on "user"."id" = "album_user"."userId" + where + "album_user"."albumId" = "album"."id" + order by + "album_user"."role", + "user"."name" asc + ) as agg + ) as "albumUsers", + ( + select + json_agg("asset") as "assets" + from + ( + select + "asset".*, + "asset_exif" as "exifInfo" + from + "asset" + left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" + inner join "album_asset" on "album_asset"."assetId" = "asset"."id" + where + "album_asset"."albumId" = "album"."id" + and "asset"."deletedAt" is null + and "asset"."visibility" in ('archive', 'timeline') + order by + "asset"."fileCreatedAt" desc + ) as "asset" + ) as "assets" +from + "album" + -- AlbumRepository.getContributorCounts select "asset"."ownerId" as "userId", diff --git a/server/src/queries/shared.link.repository.sql b/server/src/queries/shared.link.repository.sql index e1177bba28..33bb9d0e04 100644 --- a/server/src/queries/shared.link.repository.sql +++ b/server/src/queries/shared.link.repository.sql @@ -139,7 +139,15 @@ from from "user" where - "user"."id" = "album"."ownerId" + exists ( + select + from + "album_user" + where + "album_user"."role" = 'owner' + and "album_user"."albumId" = "album"."id" + and "album_user"."userId" = "user"."id" + ) and "user"."deletedAt" is null ) as "owner" on true where @@ -201,7 +209,15 @@ from from "user" where - "user"."id" = "album"."ownerId" + exists ( + select + from + "album_user" + where + "album_user"."role" = 'owner' + and "album_user"."albumId" = "album"."id" + and "album_user"."userId" = "user"."id" + ) and "user"."deletedAt" is null ) as "owner" on true where diff --git a/server/src/queries/sync.repository.sql b/server/src/queries/sync.repository.sql index a4acc95e29..ed8db709e6 100644 --- a/server/src/queries/sync.repository.sql +++ b/server/src/queries/sync.repository.sql @@ -29,7 +29,6 @@ order by -- SyncRepository.album.getUpserts select distinct on ("album"."id", "album"."updateId") "album"."id", - "album"."ownerId", "album"."albumName" as "name", "album"."description", "album"."createdAt", @@ -44,13 +43,19 @@ from where "album"."updateId" < $1 and "album"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_users"."userId" = $4 - ) + and "album_users"."userId" = $3 order by "album"."updateId" asc +-- SyncRepository.album.getAlbumUsers +select + "userId", + "role" +from + "album_user" +where + "albumId" = $1 + -- SyncRepository.albumAsset.getBackfill select "asset"."id", @@ -109,16 +114,12 @@ select from "asset" as "asset" inner join "album_asset" on "album_asset"."assetId" = "asset"."id" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" + inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "asset"."updateId" < $1 and "asset"."updateId" > $2 and "album_asset"."updateId" <= $3 - and ( - "album"."ownerId" = $4 - or "album_user"."userId" = $5 - ) + and "album_user"."userId" = $4 order by "asset"."updateId" asc @@ -147,15 +148,11 @@ select from "album_asset" as "album_asset" inner join "asset" on "asset"."id" = "album_asset"."assetId" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" + inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "album_asset"."updateId" < $1 and "album_asset"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_user"."userId" = $4 - ) + and "album_user"."userId" = $3 order by "album_asset"."updateId" asc @@ -229,16 +226,12 @@ select from "asset_exif" as "asset_exif" inner join "album_asset" on "album_asset"."assetId" = "asset_exif"."assetId" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" + inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "asset_exif"."updateId" < $1 and "asset_exif"."updateId" > $2 and "album_asset"."updateId" <= $3 - and ( - "album"."ownerId" = $4 - or "album_user"."userId" = $5 - ) + and "album_user"."userId" = $4 order by "asset_exif"."updateId" asc @@ -278,10 +271,7 @@ from where "album_asset"."updateId" < $1 and "album_asset"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_user"."userId" = $4 - ) + and "album_user"."userId" = $3 order by "album_asset"."updateId" asc @@ -312,20 +302,11 @@ where and "album_asset_audit"."id" > $2 and "albumId" in ( select - "id" + "album_user"."albumId" as "id" from - "album" + "album_user" where - "ownerId" = $3 - union - ( - select - "album_user"."albumId" as "id" - from - "album_user" - where - "album_user"."userId" = $4 - ) + "album_user"."userId" = $3 ) order by "album_asset_audit"."id" asc @@ -337,15 +318,11 @@ select "album_asset"."updateId" from "album_asset" as "album_asset" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" + inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "album_asset"."updateId" < $1 and "album_asset"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_user"."userId" = $4 - ) + and "album_user"."userId" = $3 order by "album_asset"."updateId" asc @@ -377,20 +354,11 @@ where and "album_user_audit"."id" > $2 and "albumId" in ( select - "id" + "album_user"."albumId" as "id" from - "album" + "album_user" where - "ownerId" = $3 - union - ( - select - "album_user"."albumId" as "id" - from - "album_user" - where - "album_user"."userId" = $4 - ) + "album_user"."userId" = $3 ) order by "album_user_audit"."id" asc @@ -408,20 +376,11 @@ where and "album_user"."updateId" > $2 and "album_user"."albumId" in ( select - "id" + "albumUsers"."albumId" as "id" from - "album" + "album_user" as "albumUsers" where - "ownerId" = $3 - union - ( - select - "albumUsers"."albumId" as "id" - from - "album_user" as "albumUsers" - where - "albumUsers"."userId" = $4 - ) + "albumUsers"."userId" = $3 ) order by "album_user"."updateId" asc diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index 1661e42c14..5752b70863 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -35,9 +35,14 @@ class ActivityAccess { return this.db .selectFrom('activity') .select('activity.id') - .leftJoin('album', (join) => join.onRef('activity.albumId', '=', 'album.id').on('album.deletedAt', 'is', null)) + .innerJoin('album', (join) => join.onRef('activity.albumId', '=', 'album.id').on('album.deletedAt', 'is', null)) + .innerJoin('album_user', (join) => + join + .onRef('album.id', '=', 'album_user.albumId') + .on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)) + .on('album_user.userId', '=', asUuid(userId)), + ) .where('activity.id', 'in', [...activityIds]) - .whereRef('album.ownerId', '=', asUuid(userId)) .execute() .then((activities) => new Set(activities.map((activity) => activity.id))); } @@ -52,11 +57,11 @@ class ActivityAccess { return this.db .selectFrom('album') .select('album.id') - .leftJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null)) + .innerJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id') + .innerJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null)) .where('album.id', 'in', [...albumIds]) .where('album.isActivityEnabled', '=', true) - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)])) + .where((eb) => eb('user.id', '=', userId)) .where('album.deletedAt', 'is', null) .execute() .then((albums) => new Set(albums.map((album) => album.id))); @@ -77,7 +82,12 @@ class AlbumAccess { .selectFrom('album') .select('album.id') .where('album.id', 'in', [...albumIds]) - .where('album.ownerId', '=', userId) + .innerJoin('album_user', (join) => + join + .onRef('album.id', '=', 'album_user.albumId') + .on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)) + .on('album_user.userId', '=', userId), + ) .where('album.deletedAt', 'is', null) .execute() .then((albums) => new Set(albums.map((album) => album.id))); @@ -96,8 +106,8 @@ class AlbumAccess { return this.db .selectFrom('album') .select('album.id') - .leftJoin('album_user', 'album_user.albumId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'album_user.userId').on('user.deletedAt', 'is', null)) + .innerJoin('album_user', 'album_user.albumId', 'album.id') + .innerJoin('user', (join) => join.onRef('user.id', '=', 'album_user.userId').on('user.deletedAt', 'is', null)) .where('album.id', 'in', [...albumIds]) .where('album.deletedAt', 'is', null) .where('user.id', '=', userId) @@ -152,7 +162,7 @@ class AssetAccess { eb('asset.livePhotoVideoId', '=', sql`any(target.ids)`), ]), ) - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)])) + .where('user.id', '=', userId) .where('album.deletedAt', 'is', null) .execute() .then((assets) => { diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts index 1a1104b118..69fa02f59d 100644 --- a/server/src/repositories/activity.repository.ts +++ b/server/src/repositories/activity.repository.ts @@ -7,7 +7,7 @@ import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetVisibility } from 'src/enum'; import { DB } from 'src/schema'; import { ActivityTable } from 'src/schema/tables/activity.table'; -import { asUuid } from 'src/utils/database'; +import { asUuid, dummy } from 'src/utils/database'; export interface ActivitySearch { albumId?: string; @@ -31,11 +31,7 @@ export class ActivityRepository { join.onRef('user2.id', '=', 'activity.userId').on('user2.deletedAt', 'is', null), ) .innerJoinLateral( - (eb) => - eb - .selectFrom(sql`(select 1)`.as('dummy')) - .select(columns.userWithPrefix) - .as('user'), + (eb) => eb.selectFrom(dummy).select(columns.userWithPrefix).as('user'), (join) => join.onTrue(), ) .select((eb) => eb.fn.toJson('user').as('user')) diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index e4d802b93c..a910673c62 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -14,10 +14,11 @@ import { InjectKysely } from 'nestjs-kysely'; import { columns } from 'src/database'; import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { AlbumUserCreateDto } from 'src/dtos/album.dto'; +import { AlbumUserRole } from 'src/enum'; import { DB } from 'src/schema'; import { AlbumTable } from 'src/schema/tables/album.table'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; -import { withDefaultVisibility } from 'src/utils/database'; +import { asUuid, dummy, withDefaultVisibility } from 'src/utils/database'; export interface AlbumAssetCount { albumId: string; @@ -31,33 +32,25 @@ export interface AlbumInfoOptions { withAssets: boolean; } -const withOwner = (eb: ExpressionBuilder) => { - return jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album.ownerId')) - .$notNull() - .as('owner'); -}; - -const withAlbumUsers = (eb: ExpressionBuilder) => { - return jsonArrayFrom( +const withAlbumUsers = (authUserId?: string) => (eb: ExpressionBuilder) => + jsonArrayFrom( eb .selectFrom('album_user') + .innerJoin('user', 'user.id', 'album_user.userId') + .whereRef('album_user.albumId', '=', 'album.id') .select('album_user.role') - .select((eb) => - jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album_user.userId')) - .$notNull() - .as('user'), - ) - .whereRef('album_user.albumId', '=', 'album.id'), + .select((eb) => jsonObjectFrom(eb.selectFrom(dummy).select(columns.user)).$notNull().as('user')) + .orderBy('album_user.role') + .$if(!!authUserId, (qb) => qb.orderBy((eb) => eb('album_user.userId', '=', authUserId!), 'desc')) + .orderBy('user.name', 'asc'), ) .$notNull() .as('albumUsers'); -}; -const withSharedLink = (eb: ExpressionBuilder) => { - return jsonArrayFrom( +const withSharedLink = (eb: ExpressionBuilder) => + jsonArrayFrom( eb.selectFrom('shared_link').selectAll('shared_link').whereRef('shared_link.albumId', '=', 'album.id'), ).as('sharedLinks'); -}; const withAssets = (eb: ExpressionBuilder) => { return eb @@ -80,19 +73,28 @@ const withAssets = (eb: ExpressionBuilder) => { .as('assets'); }; +const isAlbumOwned = (ownerId: string) => (eb: ExpressionBuilder) => + eb.exists( + eb + .selectFrom('album_user') + .whereRef('album_user.albumId', '=', 'album.id') + .where('album_user.role', '=', AlbumUserRole.Owner) + .where('album_user.userId', '=', ownerId), + ); + @Injectable() export class AlbumRepository { constructor(@InjectKysely() private db: Kysely) {} - @GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] }) - async getById(id: string, options: AlbumInfoOptions) { + @GenerateSql({ params: [DummyValue.UUID, { withAssets: true }, DummyValue.UUID] }) + getById(id: string, options: AlbumInfoOptions, authUserId?: string) { return this.db + .with('album_user', (qb) => qb.selectFrom('album_user').selectAll().where('album_user.albumId', '=', id)) .selectFrom('album') .selectAll('album') .where('album.id', '=', id) .where('album.deletedAt', 'is', null) - .select(withOwner) - .select(withAlbumUsers) + .select(withAlbumUsers(authUserId)) .select(withSharedLink) .$if(options.withAssets, (eb) => eb.select(withAssets)) .$narrowType<{ assets: NotNull }>() @@ -100,27 +102,22 @@ export class AlbumRepository { } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - async getByAssetId(ownerId: string, assetId: string) { + getByAssetId(ownerId: string, assetId: string) { return this.db .selectFrom('album') .selectAll('album') .innerJoin('album_asset', 'album_asset.albumId', 'album.id') .where((eb) => - eb.or([ - eb('album.ownerId', '=', ownerId), - eb.exists( - eb - .selectFrom('album_user') - .whereRef('album_user.albumId', '=', 'album.id') - .where('album_user.userId', '=', ownerId), - ), - ]), + eb.exists( + eb + .selectFrom('album_user') + .whereRef('album_user.albumId', '=', 'album.id') + .where('album_user.userId', '=', ownerId), + ), ) .where('album_asset.assetId', '=', assetId) .where('album.deletedAt', 'is', null) - .orderBy('album.createdAt', 'desc') - .select(withOwner) - .select(withAlbumUsers) + .select(withAlbumUsers(ownerId)) .orderBy('album.createdAt', 'desc') .execute(); } @@ -137,15 +134,12 @@ export class AlbumRepository { .select('album.id') .innerJoin('album_asset', 'album_asset.albumId', 'album.id') .where((eb) => - eb.or([ - eb('album.ownerId', '=', ownerId), - eb.exists( - eb - .selectFrom('album_user') - .whereRef('album_user.albumId', '=', 'album.id') - .where('album_user.userId', '=', ownerId), - ), - ]), + eb.exists( + eb + .selectFrom('album_user') + .whereRef('album_user.albumId', '=', 'album.id') + .where('album_user.userId', '=', ownerId), + ), ) .where('album_asset.assetId', 'in', assetIds) .where('album.deletedAt', 'is', null) @@ -190,15 +184,19 @@ export class AlbumRepository { } @GenerateSql({ params: [DummyValue.UUID] }) - async getOwned(ownerId: string) { + getOwned(ownerId: string) { return this.db .selectFrom('album') .selectAll('album') - .select(withOwner) - .select(withAlbumUsers) - .select(withSharedLink) - .where('album.ownerId', '=', ownerId) + .innerJoin('album_user', (join) => + join + .onRef('album_user.albumId', '=', 'album.id') + .on('album_user.userId', '=', ownerId) + .on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)), + ) .where('album.deletedAt', 'is', null) + .select(withAlbumUsers(ownerId)) + .select(withSharedLink) .orderBy('album.createdAt', 'desc') .execute(); } @@ -207,29 +205,40 @@ export class AlbumRepository { * Get albums shared with and shared by owner. */ @GenerateSql({ params: [DummyValue.UUID] }) - async getShared(ownerId: string) { + getShared(ownerId: string) { return this.db .selectFrom('album') .selectAll('album') - .where((eb) => - eb.or([ - eb.exists( - eb - .selectFrom('album_user') - .whereRef('album_user.albumId', '=', 'album.id') - .where((eb) => eb.or([eb('album.ownerId', '=', ownerId), eb('album_user.userId', '=', ownerId)])), - ), - eb.exists( - eb - .selectFrom('shared_link') - .whereRef('shared_link.albumId', '=', 'album.id') - .where('shared_link.userId', '=', ownerId), - ), - ]), + .innerJoin( + (eb) => + eb + .selectFrom('album_user') + .select('album_user.albumId as id') + .where('album_user.userId', '=', ownerId) + .where( + 'album_user.albumId', + 'in', + eb + .selectFrom('album_user') + .select('album_user.albumId') + .where('album_user.role', '!=', sql.lit(AlbumUserRole.Owner)), + ) + .union( + eb + .selectFrom('shared_link') + .where('shared_link.userId', '=', ownerId) + .where('shared_link.albumId', 'is not', null) + .select('shared_link.albumId as id') + .$narrowType<{ id: NotNull }>(), + ) + .as('matching'), + (join) => join.onRef('matching.id', '=', 'album.id'), + ) + .innerJoin('album_user', (join) => + join.onRef('album_user.albumId', '=', 'album.id').on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)), ) .where('album.deletedAt', 'is', null) - .select(withAlbumUsers) - .select(withOwner) + .select(withAlbumUsers(ownerId)) .select(withSharedLink) .orderBy('album.createdAt', 'desc') .execute(); @@ -239,29 +248,45 @@ export class AlbumRepository { * Get albums of owner that are _not_ shared */ @GenerateSql({ params: [DummyValue.UUID] }) - async getNotShared(ownerId: string) { + getNotShared(ownerId: string) { return this.db .selectFrom('album') .selectAll('album') - .where('album.ownerId', '=', ownerId) + .innerJoin('album_user', (join) => + join + .onRef('album_user.albumId', '=', 'album.id') + .on('album_user.userId', '=', ownerId) + .on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)), + ) .where('album.deletedAt', 'is', null) - .where((eb) => eb.not(eb.exists(eb.selectFrom('album_user').whereRef('album_user.albumId', '=', 'album.id')))) - .where((eb) => eb.not(eb.exists(eb.selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id')))) - .select(withOwner) + .where(({ not, exists, selectFrom }) => + not( + exists( + selectFrom('album_user as au') + .whereRef('au.albumId', '=', 'album.id') + .where('au.role', '!=', sql.lit(AlbumUserRole.Owner)), + ), + ), + ) + .where(({ not, exists, selectFrom }) => + not(exists(selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id'))), + ) + .select(withSharedLink) + .select(withAlbumUsers(ownerId)) .orderBy('album.createdAt', 'desc') .execute(); } async restoreAll(userId: string): Promise { - await this.db.updateTable('album').set({ deletedAt: null }).where('ownerId', '=', userId).execute(); + await this.db.updateTable('album').set({ deletedAt: null }).where(isAlbumOwned(userId)).execute(); } async softDeleteAll(userId: string): Promise { - await this.db.updateTable('album').set({ deletedAt: new Date() }).where('ownerId', '=', userId).execute(); + await this.db.updateTable('album').set({ deletedAt: new Date() }).where(isAlbumOwned(userId)).execute(); } async deleteAll(userId: string): Promise { - await this.db.deleteFrom('album').where('ownerId', '=', userId).execute(); + await this.db.deleteFrom('album').where(isAlbumOwned(userId)).execute(); } @GenerateSql({ params: [[DummyValue.UUID]] }) @@ -306,52 +331,86 @@ export class AlbumRepository { .then((results) => new Set(results.map(({ assetId }) => assetId))); } + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) async addAssetIds(albumId: string, assetIds: string[]): Promise { - await this.addAssets(this.db, albumId, assetIds); + if (assetIds.length === 0) { + return; + } + + await this.db + .insertInto('album_asset') + .expression((eb) => + eb.selectFrom(dummy).select([asUuid(albumId).as('albumId'), sql`unnest(${assetIds}::uuid[])`.as('assetId')]), + ) + .onConflict((oc) => oc.doNothing()) + .execute(); } - create(album: Insertable, assetIds: string[], albumUsers: AlbumUserCreateDto[]) { - return this.db.transaction().execute(async (tx) => { - const newAlbum = await tx.insertInto('album').values(album).returning('album.id').executeTakeFirst(); + @GenerateSql({ + params: [ + { albumName: DummyValue.STRING }, + [], + [{ userId: DummyValue.UUID, role: AlbumUserRole.Owner }, DummyValue.UUID], + ], + }) + async create( + album: Insertable, + assetIds: string[], + albumUsers: AlbumUserCreateDto[], + authUserId: string, + ) { + if (!albumUsers.some((u) => u.role === AlbumUserRole.Owner)) { + throw new Error('Album must have an owner'); + } - if (!newAlbum) { - throw new Error('Failed to create album'); - } + const userIds = albumUsers.map((u) => u.userId); + const roles = albumUsers.map((u) => u.role); - if (assetIds.length > 0) { - await this.addAssets(tx, newAlbum.id, assetIds); - } - - if (albumUsers.length > 0) { - await tx + const result = await this.db + .with('album', (db) => db.insertInto('album').values(album).returningAll()) + .with('album_user', (db) => + db .insertInto('album_user') - .values( - albumUsers.map((albumUser) => ({ albumId: newAlbum.id, userId: albumUser.userId, role: albumUser.role })), + .expression((eb) => + eb + .selectFrom('album') + .select(({ ref }) => [ + ref('album.id').as('albumId'), + sql`unnest(${userIds}::uuid[])`.as('userId'), + sql`unnest(${roles}::album_user_role_enum[])`.as('role'), + ]), ) - .execute(); - } + .returning(['album_user.albumId', 'album_user.userId', 'album_user.role']), + ) + .with('album_asset', (db) => + db + .insertInto('album_asset') + .expression((eb) => + eb + .selectFrom('album') + .select(({ ref }) => [ref('album.id').as('albumId'), sql`unnest(${assetIds}::uuid[])`.as('assetId')]), + ) + .onConflict((oc) => oc.doNothing()) + .returning(['album_asset.albumId', 'album_asset.assetId']), + ) + .selectFrom('album') + .selectAll('album') + .select(withAlbumUsers(authUserId)) + .select(withAssets) + .$narrowType<{ assets: NotNull }>() + .executeTakeFirstOrThrow(); - return tx - .selectFrom('album') - .selectAll('album') - .where('id', '=', newAlbum.id) - .select(withOwner) - .select(withAssets) - .select(withAlbumUsers) - .$narrowType<{ assets: NotNull }>() - .executeTakeFirstOrThrow(); - }); + return result; } - update(id: string, album: Updateable) { + update(id: string, album: Updateable, authUserId: string) { return this.db .updateTable('album') .set(album) - .where('id', '=', id) + .where('album.id', '=', id) .returningAll('album') - .returning(withOwner) .returning(withSharedLink) - .returning(withAlbumUsers) + .returning(withAlbumUsers(authUserId)) .executeTakeFirstOrThrow(); } @@ -359,19 +418,6 @@ export class AlbumRepository { await this.db.deleteFrom('album').where('id', '=', id).execute(); } - @Chunked({ paramIndex: 2, chunkSize: 30_000 }) - private async addAssets(db: Kysely, albumId: string, assetIds: string[]): Promise { - if (assetIds.length === 0) { - return; - } - - await db - .insertInto('album_asset') - .values(assetIds.map((assetId) => ({ albumId, assetId }))) - .onConflict((oc) => oc.doNothing()) - .execute(); - } - @Chunked({ chunkSize: 30_000 }) async addAssetIdsToAlbums(values: { albumId: string; assetId: string }[]): Promise { if (values.length === 0) { @@ -402,7 +448,7 @@ export class AlbumRepository { albumThumbnailAssetId: this.updateThumbnailBuilder(eb) .select('album_asset.assetId') .orderBy('asset.fileCreatedAt', 'desc') - .limit(1), + .limit(sql.lit(1)), })) .where((eb) => eb.or([ diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index c505dd3fb3..713828cd95 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -39,7 +39,7 @@ type EventMap = { // album events AlbumUpdate: [{ id: string; recipientId: string }]; - AlbumInvite: [{ id: string; userId: string }]; + AlbumInvite: [{ id: string; userId: string; senderName: string }]; // asset events AssetCreate: [{ asset: Asset }]; diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index ab5fad40d1..2a9f822e94 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -9,7 +9,7 @@ import { DB } from 'src/schema'; import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; import { FaceSearchTable } from 'src/schema/tables/face-search.table'; import { PersonTable } from 'src/schema/tables/person.table'; -import { removeUndefinedKeys, withFilePath } from 'src/utils/database'; +import { dummy, removeUndefinedKeys, withFilePath } from 'src/utils/database'; import { paginationHelper, PaginationOptions } from 'src/utils/pagination'; export interface PersonSearchOptions { @@ -418,7 +418,7 @@ export class PersonRepository { (query as any) = query.with('added_embeddings', (db) => db.insertInto('face_search').values(embeddingsToAdd)); } - await query.selectFrom(sql`(select 1)`.as('dummy')).execute(); + await query.selectFrom(dummy).execute(); } async update(person: Updateable & { id: string }) { diff --git a/server/src/repositories/shared-link.repository.ts b/server/src/repositories/shared-link.repository.ts index ddfe37ef35..22839205a3 100644 --- a/server/src/repositories/shared-link.repository.ts +++ b/server/src/repositories/shared-link.repository.ts @@ -5,7 +5,7 @@ import _ from 'lodash'; import { InjectKysely } from 'nestjs-kysely'; import { Album, columns } from 'src/database'; import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; -import { SharedLinkType } from 'src/enum'; +import { AlbumUserRole, SharedLinkType } from 'src/enum'; import { DB } from 'src/schema'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; import { AssetTable } from 'src/schema/tables/asset.table'; @@ -39,7 +39,15 @@ const withAlbumOwner = (eb: ExpressionBuilder) => { return eb .selectFrom('user') .select(columns.user) - .whereRef('user.id', '=', 'album.ownerId') + .where((eb) => + eb.exists( + eb + .selectFrom('album_user') + .where('album_user.role', '=', sql.lit(AlbumUserRole.Owner)) + .whereRef('album_user.albumId', '=', 'album.id') + .whereRef('album_user.userId', '=', 'user.id'), + ), + ) .where('user.deletedAt', 'is', null) .as('owner'); }; diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts index 486bf15163..320b6e6094 100644 --- a/server/src/repositories/sync.repository.ts +++ b/server/src/repositories/sync.repository.ts @@ -171,10 +171,9 @@ class AlbumSync extends BaseSync { return this.upsertQuery('album', options) .distinctOn(['album.id', 'album.updateId']) .leftJoin('album_user as album_users', 'album.id', 'album_users.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_users.userId', '=', userId)])) + .where('album_users.userId', '=', userId) .select([ 'album.id', - 'album.ownerId', 'album.albumName as name', 'album.description', 'album.createdAt', @@ -186,6 +185,11 @@ class AlbumSync extends BaseSync { ]) .stream(); } + + @GenerateSql({ params: [DummyValue.UUID] }) + async getAlbumUsers(albumId: string) { + return this.db.selectFrom('album_user').select(['userId', 'role']).where('albumId', '=', albumId).execute(); + } } class AlbumAssetSync extends BaseSync { @@ -207,9 +211,8 @@ class AlbumAssetSync extends BaseSync { .select(columns.syncAsset) .select('asset.updateId') .where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) + .innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where('album_user.userId', '=', userId) .stream(); } @@ -220,9 +223,8 @@ class AlbumAssetSync extends BaseSync { .select('album_asset.updateId') .innerJoin('asset', 'asset.id', 'album_asset.assetId') .select(columns.syncAsset) - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) + .innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where('album_user.userId', '=', userId) .stream(); } } @@ -246,9 +248,8 @@ class AlbumAssetExifSync extends BaseSync { .select(columns.syncAssetExif) .select('asset_exif.updateId') .where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send exif updates for assets that the client already knows about - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) + .innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where('album_user.userId', '=', userId) .stream(); } @@ -261,7 +262,7 @@ class AlbumAssetExifSync extends BaseSync { .select(columns.syncAssetExif) .innerJoin('album', 'album.id', 'album_asset.albumId') .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) + .where('album_user.userId', '=', userId) .stream(); } } @@ -284,18 +285,7 @@ class AlbumToAssetSync extends BaseSync { eb( 'albumId', 'in', - eb - .selectFrom('album') - .select(['id']) - .where('ownerId', '=', userId) - .union((eb) => - eb.parens( - eb - .selectFrom('album_user') - .select(['album_user.albumId as id']) - .where('album_user.userId', '=', userId), - ), - ), + eb.selectFrom('album_user').select(['album_user.albumId as id']).where('album_user.userId', '=', userId), ), ) .stream(); @@ -310,9 +300,8 @@ class AlbumToAssetSync extends BaseSync { const userId = options.userId; return this.upsertQuery('album_asset', options) .select(['album_asset.assetId as assetId', 'album_asset.albumId as albumId', 'album_asset.updateId']) - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) + .innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where('album_user.userId', '=', userId) .stream(); } } @@ -336,18 +325,7 @@ class AlbumUserSync extends BaseSync { eb( 'albumId', 'in', - eb - .selectFrom('album') - .select(['id']) - .where('ownerId', '=', userId) - .union((eb) => - eb.parens( - eb - .selectFrom('album_user') - .select(['album_user.albumId as id']) - .where('album_user.userId', '=', userId), - ), - ), + eb.selectFrom('album_user').select(['album_user.albumId as id']).where('album_user.userId', '=', userId), ), ) .stream(); @@ -368,17 +346,9 @@ class AlbumUserSync extends BaseSync { 'album_user.albumId', 'in', eb - .selectFrom('album') - .select(['id']) - .where('ownerId', '=', userId) - .union((eb) => - eb.parens( - eb - .selectFrom('album_user as albumUsers') - .select(['albumUsers.albumId as id']) - .where('albumUsers.userId', '=', userId), - ), - ), + .selectFrom('album_user as albumUsers') + .select(['albumUsers.albumId as id']) + .where('albumUsers.userId', '=', userId), ), ) .stream(); diff --git a/server/src/schema/enums.ts b/server/src/schema/enums.ts index f63a09c462..2bfa4a3340 100644 --- a/server/src/schema/enums.ts +++ b/server/src/schema/enums.ts @@ -1,5 +1,10 @@ import { registerEnum } from '@immich/sql-tools'; -import { AssetStatus, AssetVisibility, ChecksumAlgorithm, SourceType } from 'src/enum'; +import { AlbumUserRole, AssetStatus, AssetVisibility, ChecksumAlgorithm, SourceType } from 'src/enum'; + +export const album_user_role_enum = registerEnum({ + name: 'album_user_role_enum', + values: [AlbumUserRole.Owner, AlbumUserRole.Editor, AlbumUserRole.Viewer], +}); export const assets_status_enum = registerEnum({ name: 'assets_status_enum', diff --git a/server/src/schema/functions.ts b/server/src/schema/functions.ts index 6dbbd28b1a..23080ae0a8 100644 --- a/server/src/schema/functions.ts +++ b/server/src/schema/functions.ts @@ -29,7 +29,8 @@ export const album_user_after_insert = registerFunction({ body: ` BEGIN UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) - WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows); + WHERE "id" IN (SELECT "albumId" FROM inserted_rows) + AND NOT EXISTS (SELECT FROM inserted_rows WHERE role = 'owner'); RETURN NULL; END`, }); @@ -119,19 +120,6 @@ export const asset_delete_audit = registerFunction({ END`, }); -export const album_delete_audit = registerFunction({ - name: 'album_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO album_audit ("albumId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END`, -}); - export const album_asset_delete_audit = registerFunction({ name: 'album_asset_delete_audit', returnType: 'TRIGGER', diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index e3db3d01c7..618df795a2 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -1,7 +1,11 @@ import { Database, Extensions, Generated, Int8 } from '@immich/sql-tools'; -import { asset_face_source_type, asset_visibility_enum, assets_status_enum } from 'src/schema/enums'; import { - album_delete_audit, + album_user_role_enum, + asset_face_source_type, + asset_visibility_enum, + assets_status_enum, +} from 'src/schema/enums'; +import { album_user_after_insert, album_user_delete_audit, asset_delete_audit, @@ -146,7 +150,6 @@ export class ImmichDatabase { user_delete_audit, partner_delete_audit, asset_delete_audit, - album_delete_audit, album_user_after_insert, album_user_delete_audit, memory_delete_audit, @@ -158,7 +161,7 @@ export class ImmichDatabase { asset_face_audit, ]; - enum = [assets_status_enum, asset_face_source_type, asset_visibility_enum]; + enum = [album_user_role_enum, assets_status_enum, asset_face_source_type, asset_visibility_enum]; } export interface Migrations { diff --git a/server/src/schema/migrations/1776848612954-MigrateAlbumOwnerIdToAlbumUser.ts b/server/src/schema/migrations/1776848612954-MigrateAlbumOwnerIdToAlbumUser.ts new file mode 100644 index 0000000000..bb9d68a4ec --- /dev/null +++ b/server/src/schema/migrations/1776848612954-MigrateAlbumOwnerIdToAlbumUser.ts @@ -0,0 +1,92 @@ +import { Kysely, sql } from 'kysely'; +import { AlbumUserRole } from 'src/enum'; + +export async function up(db: Kysely): Promise { + await sql`CREATE OR REPLACE FUNCTION album_user_after_insert() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + BEGIN + UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) + WHERE "id" IN (SELECT "albumId" FROM inserted_rows) + AND NOT EXISTS (SELECT FROM inserted_rows WHERE role = 'owner'); + RETURN NULL; + END + $$;`.execute(db); + await sql`DROP TRIGGER "album_delete_audit" ON "album";`.execute(db); + await sql`DROP FUNCTION album_delete_audit;`.execute(db); + await sql`CREATE TYPE "album_user_role_enum" AS ENUM ('owner','editor','viewer');`.execute(db); + await sql`ALTER TABLE "album_user" ALTER COLUMN "role" DROP DEFAULT;`.execute(db); + await sql`ALTER TABLE "album_user" ALTER COLUMN "role" TYPE album_user_role_enum USING "role"::album_user_role_enum;`.execute(db); + await sql`ALTER TABLE "album_user" ALTER COLUMN "role" SET DEFAULT 'editor'::album_user_role_enum;`.execute(db); + await db + .insertInto('album_user') + .expression((eb) => + eb + .selectFrom('album') + .select(['album.id as albumId', 'album.ownerId as userId', eb.val(AlbumUserRole.Owner).as('role')]), + ) + .execute(); + await sql`ALTER TABLE "album" DROP CONSTRAINT "album_ownerId_fkey";`.execute(db); + await sql`ALTER TABLE "album" DROP COLUMN "ownerId";`.execute(db); + await sql`CREATE UNIQUE INDEX "album_user_unique_owner" ON "album_user" ("albumId") WHERE (role = 'owner');`.execute(db); + await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_user_after_insert","sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE album SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT \\"albumId\\" FROM inserted_rows)\\n AND NOT EXISTS (SELECT FROM inserted_rows WHERE role = ''owner'');\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_album_user_unique_owner', '{"type":"index","name":"album_user_unique_owner","sql":"CREATE UNIQUE INDEX \\"album_user_unique_owner\\" ON \\"album_user\\" (\\"albumId\\") WHERE (role = ''owner'');"}'::jsonb);`.execute(db); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_delete_audit';`.execute(db); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_delete_audit';`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`CREATE OR REPLACE FUNCTION public.album_user_after_insert() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ + BEGIN + UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) + WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows); + RETURN NULL; + END + $function$ +`.execute(db); + await sql`CREATE OR REPLACE FUNCTION public.album_delete_audit() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ + BEGIN + INSERT INTO album_audit ("albumId", "userId") + SELECT "id", "ownerId" + FROM OLD; + RETURN NULL; + END + $function$ +`.execute(db); + await sql`ALTER TABLE "album" ADD "ownerId" uuid NOT NULL;`.execute(db); + await db + .updateTable('album') + .set((eb) => + ({ + id: eb.ref('album_user.albumId'), + ownerId: eb.ref('album_user.userId') + }) + ) + .from('album_user') + .where('album_user.role', '=', AlbumUserRole.Owner) + .execute(); + await sql`DROP INDEX "album_user_unique_owner";`.execute(db); + await sql`ALTER TABLE "album_user" ALTER COLUMN "role" DROP DEFAULT;`.execute(db); + await sql`ALTER TABLE "album_user" ALTER COLUMN "role" TYPE character varying USING "role"::text;`.execute(db); + await sql`ALTER TABLE "album_user" ALTER COLUMN "role" SET DEFAULT 'editor';`.execute(db); + await sql`DROP TYPE "album_user_role_enum";`.execute(db); + await sql`CREATE INDEX "album_ownerId_idx" ON "album" ("ownerId");`.execute(db); + await sql`ALTER TABLE "album" ADD CONSTRAINT "album_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); + await sql`CREATE OR REPLACE TRIGGER "album_delete_audit" + AFTER DELETE ON "album" + REFERENCING OLD TABLE AS "old" + FOR EACH STATEMENT + WHEN ((pg_trigger_depth() = 0)) + EXECUTE FUNCTION album_delete_audit();`.execute(db); + await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE album SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT DISTINCT \\"albumId\\" FROM inserted_rows);\\n RETURN NULL;\\n END\\n $$;","name":"album_user_after_insert","type":"function"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION album_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"album_delete_audit","type":"function"}'::jsonb);`.execute(db); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"album_delete_audit\\"\\n AFTER DELETE ON \\"album\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION album_delete_audit();","name":"album_delete_audit","type":"trigger"}'::jsonb);`.execute(db); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_album_user_unique_owner';`.execute(db); +} diff --git a/server/src/schema/tables/album-user.table.ts b/server/src/schema/tables/album-user.table.ts index 2e38041daf..677d6ca2f2 100644 --- a/server/src/schema/tables/album-user.table.ts +++ b/server/src/schema/tables/album-user.table.ts @@ -5,17 +5,25 @@ import { CreateDateColumn, ForeignKeyColumn, Generated, + Index, Table, Timestamp, UpdateDateColumn, } from '@immich/sql-tools'; import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; import { AlbumUserRole } from 'src/enum'; +import { album_user_role_enum } from 'src/schema/enums'; import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions'; import { AlbumTable } from 'src/schema/tables/album.table'; import { UserTable } from 'src/schema/tables/user.table'; @Table({ name: 'album_user' }) +@Index({ + name: 'album_user_unique_owner', + columns: ['albumId'], + unique: true, + where: `role = 'owner'`, +}) // Pre-existing indices from original album <--> user ManyToMany mapping @UpdatedAtTrigger('album_user_updatedAt') @AfterInsertTrigger({ @@ -47,7 +55,7 @@ export class AlbumUserTable { }) userId!: string; - @Column({ type: 'character varying', default: AlbumUserRole.Editor }) + @Column({ enum: album_user_role_enum, default: AlbumUserRole.Editor }) role!: Generated; @CreateIdColumn({ index: true }) diff --git a/server/src/schema/tables/album.table.ts b/server/src/schema/tables/album.table.ts index 81b846c0f4..f54658be65 100644 --- a/server/src/schema/tables/album.table.ts +++ b/server/src/schema/tables/album.table.ts @@ -1,5 +1,4 @@ import { - AfterDeleteTrigger, Column, CreateDateColumn, DeleteDateColumn, @@ -12,25 +11,14 @@ import { } from '@immich/sql-tools'; import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; import { AssetOrder } from 'src/enum'; -import { album_delete_audit } from 'src/schema/functions'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { UserTable } from 'src/schema/tables/user.table'; @Table({ name: 'album' }) @UpdatedAtTrigger('album_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: album_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) export class AlbumTable { @PrimaryGeneratedColumn() id!: Generated; - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - ownerId!: string; - @Column({ default: 'Untitled Album' }) albumName!: Generated; diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 4f5d4edd00..1d61272d5c 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -44,7 +44,8 @@ describe(AlbumService.name, () => { describe('getAll', () => { it('gets list of albums for auth user', async () => { const album = AlbumFactory.from().albumUser().build(); - const sharedWithUserAlbum = AlbumFactory.from().owner(album.owner).albumUser().build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + const sharedWithUserAlbum = AlbumFactory.from().owner(owner).albumUser().build(); mocks.album.getOwned.mockResolvedValue([getForAlbum(album), getForAlbum(sharedWithUserAlbum)]); mocks.album.getMetadataForIds.mockResolvedValue([ { @@ -63,7 +64,7 @@ describe(AlbumService.name, () => { }, ]); - const result = await sut.getAll(AuthFactory.create(album.owner), {}); + const result = await sut.getAll(AuthFactory.create(owner), {}); expect(result).toHaveLength(2); expect(result[0].id).toEqual(album.id); expect(result[1].id).toEqual(sharedWithUserAlbum.id); @@ -76,6 +77,7 @@ describe(AlbumService.name, () => { .asset({}, (builder) => builder.exif()) .asset({}, (builder) => builder.exif()) .build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getByAssetId.mockResolvedValue([getForAlbum(album)]); mocks.album.getMetadataForIds.mockResolvedValue([ { @@ -87,7 +89,7 @@ describe(AlbumService.name, () => { }, ]); - const result = await sut.getAll(AuthFactory.create(album.owner), { assetId: album.assets[0].id }); + const result = await sut.getAll(AuthFactory.create(owner), { assetId: album.assets[0].id }); expect(result).toHaveLength(1); expect(result[0].id).toEqual(album.id); expect(mocks.album.getByAssetId).toHaveBeenCalledTimes(1); @@ -95,6 +97,7 @@ describe(AlbumService.name, () => { it('gets list of albums that are shared', async () => { const album = AlbumFactory.from().albumUser().build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getShared.mockResolvedValue([getForAlbum(album)]); mocks.album.getMetadataForIds.mockResolvedValue([ { @@ -106,7 +109,7 @@ describe(AlbumService.name, () => { }, ]); - const result = await sut.getAll(AuthFactory.create(album.owner), { shared: true }); + const result = await sut.getAll(AuthFactory.create(owner), { shared: true }); expect(result).toHaveLength(1); expect(result[0].id).toEqual(album.id); expect(mocks.album.getShared).toHaveBeenCalledTimes(1); @@ -114,6 +117,7 @@ describe(AlbumService.name, () => { it('gets list of albums that are NOT shared', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getNotShared.mockResolvedValue([getForAlbum(album)]); mocks.album.getMetadataForIds.mockResolvedValue([ { @@ -125,7 +129,7 @@ describe(AlbumService.name, () => { }, ]); - const result = await sut.getAll(AuthFactory.create(album.owner), { shared: false }); + const result = await sut.getAll(AuthFactory.create(owner), { shared: false }); expect(result).toHaveLength(1); expect(result[0].id).toEqual(album.id); expect(mocks.album.getNotShared).toHaveBeenCalledTimes(1); @@ -134,6 +138,7 @@ describe(AlbumService.name, () => { it('counts assets correctly', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getOwned.mockResolvedValue([getForAlbum(album)]); mocks.album.getMetadataForIds.mockResolvedValue([ { @@ -145,7 +150,7 @@ describe(AlbumService.name, () => { }, ]); - const result = await sut.getAll(AuthFactory.create(album.owner), {}); + const result = await sut.getAll(AuthFactory.create(owner), {}); expect(result).toHaveLength(1); expect(result[0].assetCount).toEqual(1); expect(mocks.album.getOwned).toHaveBeenCalledTimes(1); @@ -159,13 +164,14 @@ describe(AlbumService.name, () => { .asset({ id: assetId }, (asset) => asset.exif()) .albumUser(albumUser) .build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.create.mockResolvedValue(getForAlbum(album)); mocks.user.get.mockResolvedValue(UserFactory.create(album.albumUsers[0].user)); mocks.user.getMetadata.mockResolvedValue([]); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); - await sut.create(AuthFactory.create(album.owner), { + await sut.create(AuthFactory.create(owner), { albumName: 'test', albumUsers: [albumUser], description: 'description', @@ -174,20 +180,27 @@ describe(AlbumService.name, () => { expect(mocks.album.create).toHaveBeenCalledWith( { - ownerId: album.owner.id, albumName: 'test', description: 'description', order: album.order, albumThumbnailAssetId: assetId, }, [assetId], - [{ userId: albumUser.userId, role: AlbumUserRole.Editor }], + [ + { userId: owner.id, role: AlbumUserRole.Owner }, + { userId: albumUser.userId, role: AlbumUserRole.Editor }, + ], + owner.id, ); expect(mocks.user.get).toHaveBeenCalledWith(albumUser.userId, {}); - expect(mocks.user.getMetadata).toHaveBeenCalledWith(album.owner.id); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([assetId]), false); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { id: album.id, userId: albumUser.userId }); + expect(mocks.user.getMetadata).toHaveBeenCalledWith(owner.id); + expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId]), false); + expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { + id: album.id, + userId: albumUser.userId, + senderName: owner.name, + }); }); it('creates album with assetOrder from user preferences', async () => { @@ -197,8 +210,10 @@ describe(AlbumService.name, () => { .asset({ id: assetId }, (asset) => asset.exif()) .albumUser(albumUser) .build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.create.mockResolvedValue(getForAlbum(album)); - mocks.user.get.mockResolvedValue(album.albumUsers[0].user); + mocks.albumUser.create.mockResolvedValue(album.albumUsers[0]); + mocks.user.get.mockResolvedValue(UserFactory.create(album.albumUsers[1].user)); mocks.user.getMetadata.mockResolvedValue([ { key: UserMetadataKey.Preferences, @@ -211,7 +226,7 @@ describe(AlbumService.name, () => { ]); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); - await sut.create(AuthFactory.create(album.owner), { + await sut.create(AuthFactory.create(owner), { albumName: album.albumName, albumUsers: [albumUser], description: album.description, @@ -220,20 +235,24 @@ describe(AlbumService.name, () => { expect(mocks.album.create).toHaveBeenCalledWith( { - ownerId: album.owner.id, albumName: album.albumName, description: album.description, order: 'asc', albumThumbnailAssetId: assetId, }, [assetId], - [albumUser], + [{ userId: owner.id, role: AlbumUserRole.Owner }, albumUser], + owner.id, ); expect(mocks.user.get).toHaveBeenCalledWith(albumUser.userId, {}); - expect(mocks.user.getMetadata).toHaveBeenCalledWith(album.owner.id); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([assetId]), false); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { id: album.id, userId: albumUser.userId }); + expect(mocks.user.getMetadata).toHaveBeenCalledWith(owner.id); + expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId]), false); + expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { + id: album.id, + userId: albumUser.userId, + senderName: owner.name, + }); }); it('should require valid userIds', async () => { @@ -254,12 +273,13 @@ describe(AlbumService.name, () => { .asset({ id: assetId }, (asset) => asset.exif()) .albumUser() .build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.user.get.mockResolvedValue(album.albumUsers[0].user); mocks.album.create.mockResolvedValue(getForAlbum(album)); mocks.user.getMetadata.mockResolvedValue([]); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); - await sut.create(AuthFactory.create(album.owner), { + await sut.create(AuthFactory.create(owner), { albumName: album.albumName, description: album.description, assetIds: [assetId, 'asset-2'], @@ -267,29 +287,26 @@ describe(AlbumService.name, () => { expect(mocks.album.create).toHaveBeenCalledWith( { - ownerId: album.owner.id, albumName: album.albumName, description: album.description, order: 'desc', albumThumbnailAssetId: assetId, }, [assetId], - [], - ); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - album.owner.id, - new Set([assetId, 'asset-2']), - false, + [{ userId: owner.id, role: AlbumUserRole.Owner }], + owner.id, ); + expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId, 'asset-2']), false); }); it('should throw an error if the userId is the ownerId', async () => { const album = AlbumFactory.create(); - mocks.user.get.mockResolvedValue(album.owner); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + mocks.user.get.mockResolvedValue(owner); await expect( - sut.create(AuthFactory.create(album.owner), { + sut.create(AuthFactory.create(owner), { albumName: 'Empty album', - albumUsers: [{ userId: album.owner.id, role: AlbumUserRole.Editor }], + albumUsers: [{ userId: owner.id, role: AlbumUserRole.Editor }], }), ).rejects.toBeInstanceOf(BadRequestException); expect(mocks.album.create).not.toHaveBeenCalled(); @@ -312,20 +329,22 @@ describe(AlbumService.name, () => { it('should prevent updating a not owned album (shared with auth user)', async () => { const album = AlbumFactory.from().albumUser().build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set()); await expect( - sut.update(AuthFactory.create(album.owner), album.id, { albumName: 'new album name' }), + sut.update(AuthFactory.create(owner), album.id, { albumName: 'new album name' }), ).rejects.toBeInstanceOf(BadRequestException); }); it('should require a valid thumbnail asset id', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValue(new Set()); await expect( - sut.update(AuthFactory.create(album.owner), album.id, { albumThumbnailAssetId: 'not-in-album' }), + sut.update(AuthFactory.create(owner), album.id, { albumThumbnailAssetId: 'not-in-album' }), ).rejects.toBeInstanceOf(BadRequestException); expect(mocks.album.getAssetIds).toHaveBeenCalledWith(album.id, ['not-in-album']); @@ -334,43 +353,51 @@ describe(AlbumService.name, () => { it('should allow the owner to update the album', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.update.mockResolvedValue(getForAlbum(album)); - await sut.update(AuthFactory.create(album.owner), album.id, { albumName: 'new album name' }); + await sut.update(AuthFactory.create(owner), album.id, { albumName: 'new album name' }); expect(mocks.album.update).toHaveBeenCalledTimes(1); - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { id: album.id, albumName: 'new album name' }); + expect(mocks.album.update).toHaveBeenCalledWith( + album.id, + { id: album.id, albumName: 'new album name' }, + owner.id, + ); }); }); describe('delete', () => { it('should require permissions', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set()); - await expect(sut.delete(AuthFactory.create(album.owner), album.id)).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.delete(AuthFactory.create(owner), album.id)).rejects.toBeInstanceOf(BadRequestException); expect(mocks.album.delete).not.toHaveBeenCalled(); }); it('should not let a shared user delete the album', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set()); - await expect(sut.delete(AuthFactory.create(album.owner), album.id)).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.delete(AuthFactory.create(owner), album.id)).rejects.toBeInstanceOf(BadRequestException); expect(mocks.album.delete).not.toHaveBeenCalled(); }); it('should let the owner delete an album', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); - await sut.delete(AuthFactory.create(album.owner), album.id); + await sut.delete(AuthFactory.create(owner), album.id); expect(mocks.album.delete).toHaveBeenCalledTimes(1); expect(mocks.album.delete).toHaveBeenCalledWith(album.id); @@ -391,10 +418,11 @@ describe(AlbumService.name, () => { it('should throw an error if the userId is already added', async () => { const userId = newUuid(); const album = AlbumFactory.from().albumUser({ userId }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); await expect( - sut.addUsers(AuthFactory.create(album.owner), album.id, { albumUsers: [{ userId }] }), + sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] }), ).rejects.toBeInstanceOf(BadRequestException); expect(mocks.album.update).not.toHaveBeenCalled(); expect(mocks.user.get).not.toHaveBeenCalled(); @@ -402,11 +430,12 @@ describe(AlbumService.name, () => { it('should throw an error if the userId does not exist', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.user.get.mockResolvedValue(void 0); await expect( - sut.addUsers(AuthFactory.create(album.owner), album.id, { albumUsers: [{ userId: 'unknown-user' }] }), + sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId: 'unknown-user' }] }), ).rejects.toBeInstanceOf(BadRequestException); expect(mocks.album.update).not.toHaveBeenCalled(); expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {}); @@ -414,11 +443,12 @@ describe(AlbumService.name, () => { it('should throw an error if the userId is the ownerId', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); await expect( - sut.addUsers(AuthFactory.create(album.owner), album.id, { - albumUsers: [{ userId: album.owner.id }], + sut.addUsers(AuthFactory.create(owner), album.id, { + albumUsers: [{ userId: owner.id }], }), ).rejects.toBeInstanceOf(BadRequestException); expect(mocks.album.update).not.toHaveBeenCalled(); @@ -427,6 +457,7 @@ describe(AlbumService.name, () => { it('should add valid shared users', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const user = UserFactory.create(); mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); @@ -434,7 +465,7 @@ describe(AlbumService.name, () => { mocks.user.get.mockResolvedValue(user); mocks.albumUser.create.mockResolvedValue(AlbumUserFactory.from().album(album).user(user).build()); - await sut.addUsers(AuthFactory.create(album.owner), album.id, { albumUsers: [{ userId: user.id }] }); + await sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId: user.id }] }); expect(mocks.albumUser.create).toHaveBeenCalledWith({ userId: user.id, @@ -443,6 +474,7 @@ describe(AlbumService.name, () => { expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { id: album.id, userId: user.id, + senderName: owner.name, }); }); }); @@ -460,15 +492,16 @@ describe(AlbumService.name, () => { it('should remove a shared user from an owned album', async () => { const userId = newUuid(); const album = AlbumFactory.from().albumUser({ userId }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.albumUser.delete.mockResolvedValue(); - await expect(sut.removeUser(AuthFactory.create(album.owner), album.id, userId)).resolves.toBeUndefined(); + await expect(sut.removeUser(AuthFactory.create(owner), album.id, userId)).resolves.toBeUndefined(); expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1); expect(mocks.albumUser.delete).toHaveBeenCalledWith({ albumId: album.id, userId }); - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }); + expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, owner.id); }); it('should prevent removing a shared user from a not-owned album (shared with auth user)', async () => { @@ -511,9 +544,10 @@ describe(AlbumService.name, () => { it('should not allow the owner to be removed', async () => { const album = AlbumFactory.from().albumUser().build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getById.mockResolvedValue(getForAlbum(album)); - await expect(sut.removeUser(AuthFactory.create(album.owner), album.id, album.owner.id)).rejects.toBeInstanceOf( + await expect(sut.removeUser(AuthFactory.create(owner), album.id, owner.id)).rejects.toBeInstanceOf( BadRequestException, ); @@ -522,9 +556,10 @@ describe(AlbumService.name, () => { it('should throw an error for a user not in the album', async () => { const album = AlbumFactory.from().albumUser().build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getById.mockResolvedValue(getForAlbum(album)); - await expect(sut.removeUser(AuthFactory.create(album.owner), album.id, 'user-3')).rejects.toBeInstanceOf( + await expect(sut.removeUser(AuthFactory.create(owner), album.id, 'user-3')).rejects.toBeInstanceOf( BadRequestException, ); @@ -536,10 +571,11 @@ describe(AlbumService.name, () => { it('should update user role', async () => { const user = UserFactory.create(); const album = AlbumFactory.from().albumUser({ userId: user.id }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.albumUser.update.mockResolvedValue(); - await sut.updateUser(AuthFactory.create(album.owner), album.id, user.id, { role: AlbumUserRole.Viewer }); + await sut.updateUser(AuthFactory.create(owner), album.id, user.id, { role: AlbumUserRole.Viewer }); expect(mocks.albumUser.update).toHaveBeenCalledWith( { albumId: album.id, userId: user.id }, @@ -551,6 +587,7 @@ describe(AlbumService.name, () => { describe('getAlbumInfo', () => { it('should get a shared album', async () => { const album = AlbumFactory.from().albumUser().build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getMetadataForIds.mockResolvedValue([ @@ -563,10 +600,10 @@ describe(AlbumService.name, () => { }, ]); - await sut.get(AuthFactory.create(album.owner), album.id); + await sut.get(AuthFactory.create(owner), album.id); - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }); - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([album.id])); + expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, owner.id); + expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([album.id])); }); it('should get a shared album via a shared link', async () => { @@ -586,7 +623,7 @@ describe(AlbumService.name, () => { const auth = AuthFactory.from().sharedLink().build(); await sut.get(auth, album.id); - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }); + expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, auth.user.id); expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink!.id, new Set([album.id])); }); @@ -607,7 +644,7 @@ describe(AlbumService.name, () => { await sut.get(AuthFactory.create(user), album.id); - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }); + expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, user.id); expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith( user.id, new Set([album.id]), @@ -631,7 +668,7 @@ describe(AlbumService.name, () => { describe('addAssets', () => { it('should allow the owner to add assets', async () => { const owner = UserFactory.create({ isAdmin: true }); - const album = AlbumFactory.from({ ownerId: owner.id }).owner(owner).build(); + const album = AlbumFactory.from().owner(owner).build(); const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()]; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); @@ -646,37 +683,47 @@ describe(AlbumService.name, () => { { success: true, id: asset3.id }, ]); - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); + expect(mocks.album.update).toHaveBeenCalledWith( + album.id, + { + id: album.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + owner.id, + ); expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]); }); it('should not set the thumbnail if the album has one already', async () => { const [asset1, asset2] = [AssetFactory.create(), AssetFactory.create()]; const album = AlbumFactory.from({ albumThumbnailAssetId: asset1.id }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset2.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset2.id] })).resolves.toEqual([ + await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset2.id] })).resolves.toEqual([ { success: true, id: asset2.id }, ]); - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); + expect(mocks.album.update).toHaveBeenCalledWith( + album.id, + { + id: album.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + owner.id, + ); expect(mocks.album.addAssetIds).toHaveBeenCalled(); }); it('should allow a shared user to add assets', async () => { const user = UserFactory.create(); const album = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()]; mocks.access.album.checkSharedAlbumAccess.mockResolvedValue(new Set([album.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); @@ -691,15 +738,19 @@ describe(AlbumService.name, () => { { success: true, id: asset3.id }, ]); - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); + expect(mocks.album.update).toHaveBeenCalledWith( + album.id, + { + id: album.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + user.id, + ); expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]); expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { id: album.id, - recipientId: album.ownerId, + recipientId: owner.id, }); }); @@ -719,33 +770,39 @@ describe(AlbumService.name, () => { it('should allow adding assets shared via partner sharing', async () => { const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const asset = AssetFactory.create(); mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([asset.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([ + await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([ { success: true, id: asset.id }, ]); - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset.id, - }); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(album.ownerId, new Set([asset.id])); + expect(mocks.album.update).toHaveBeenCalledWith( + album.id, + { + id: album.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset.id, + }, + owner.id, + ); + expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id])); }); it('should skip duplicate assets', async () => { const asset = AssetFactory.create(); const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValueOnce(new Set([asset.id])); - await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([ + await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([ { success: false, id: asset.id, error: BulkIdErrorReason.DUPLICATE }, ]); @@ -755,16 +812,17 @@ describe(AlbumService.name, () => { it('should skip assets not shared with user', async () => { const asset = AssetFactory.create(); const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([ + await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([ { success: false, id: asset.id, error: BulkIdErrorReason.NO_PERMISSION }, ]); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(album.ownerId, new Set([asset.id]), false); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(album.ownerId, new Set([asset.id])); + expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id]), false); + expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id])); }); it('should not allow unauthorized access to the album', async () => { @@ -797,6 +855,7 @@ describe(AlbumService.name, () => { describe('addAssetsToAlbums', () => { it('should allow the owner to add assets', async () => { const album1 = AlbumFactory.create(); + const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const album2 = AlbumFactory.create(); const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()]; mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); @@ -805,23 +864,33 @@ describe(AlbumService.name, () => { mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); await expect( - sut.addAssetsToAlbums(AuthFactory.create(album1.owner), { + sut.addAssetsToAlbums(AuthFactory.create(owner), { albumIds: [album1.id, album2.id], assetIds: [asset1.id, asset2.id, asset3.id], }), ).resolves.toEqual({ success: true, error: undefined }); expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 1, + album1.id, + { + id: album1.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + owner.id, + ); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 2, + album2.id, + { + id: album2.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + owner.id, + ); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ { albumId: album1.id, assetId: asset1.id }, { albumId: album1.id, assetId: asset2.id }, @@ -835,6 +904,7 @@ describe(AlbumService.name, () => { it('should not set the thumbnail if the album has one already', async () => { const asset = AssetFactory.create(); const album1 = AlbumFactory.from({ albumThumbnailAssetId: asset.id }).build(); + const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const album2 = AlbumFactory.from({ albumThumbnailAssetId: asset.id }).build(); const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()]; mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); @@ -843,23 +913,33 @@ describe(AlbumService.name, () => { mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); await expect( - sut.addAssetsToAlbums(AuthFactory.create(album1.owner), { + sut.addAssetsToAlbums(AuthFactory.create(owner), { albumIds: [album1.id, album2.id], assetIds: [asset1.id, asset2.id, asset3.id], }), ).resolves.toEqual({ success: true, error: undefined }); expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset.id, - }); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 1, + album1.id, + { + id: album1.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset.id, + }, + owner.id, + ); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 2, + album2.id, + { + id: album2.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset.id, + }, + owner.id, + ); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ { albumId: album1.id, assetId: asset1.id }, { albumId: album1.id, assetId: asset2.id }, @@ -873,7 +953,9 @@ describe(AlbumService.name, () => { it('should allow a shared user to add assets', async () => { const user = UserFactory.create(); const album1 = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build(); + const { user: owner1 } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const album2 = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build(); + const { user: owner2 } = album2.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()]; mocks.access.album.checkSharedAlbumAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); @@ -888,16 +970,26 @@ describe(AlbumService.name, () => { ).resolves.toEqual({ success: true, error: undefined }); expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 1, + album1.id, + { + id: album1.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + user.id, + ); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 2, + album2.id, + { + id: album2.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + user.id, + ); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ { albumId: album1.id, assetId: asset1.id }, { albumId: album1.id, assetId: asset2.id }, @@ -908,11 +1000,11 @@ describe(AlbumService.name, () => { ]); expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { id: album1.id, - recipientId: album1.ownerId, + recipientId: owner1.id, }); expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { id: album2.id, - recipientId: album2.ownerId, + recipientId: owner2.id, }); }); @@ -942,6 +1034,7 @@ describe(AlbumService.name, () => { it('should allow adding assets shared via partner sharing', async () => { const user = UserFactory.create(); const album1 = AlbumFactory.create(); + const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const album2 = AlbumFactory.create(); const [asset1, asset2, asset3] = [ AssetFactory.create({ ownerId: user.id }), @@ -954,23 +1047,33 @@ describe(AlbumService.name, () => { mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); await expect( - sut.addAssetsToAlbums(AuthFactory.create(album1.owner), { + sut.addAssetsToAlbums(AuthFactory.create(owner), { albumIds: [album1.id, album2.id], assetIds: [asset1.id, asset2.id, asset3.id], }), ).resolves.toEqual({ success: true, error: undefined }); expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 1, + album1.id, + { + id: album1.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + owner.id, + ); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 2, + album2.id, + { + id: album2.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + owner.id, + ); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ { albumId: album1.id, assetId: asset1.id }, { albumId: album1.id, assetId: asset2.id }, @@ -980,7 +1083,7 @@ describe(AlbumService.name, () => { { albumId: album2.id, assetId: asset3.id }, ]); expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith( - album1.ownerId, + owner.id, new Set([asset1.id, asset2.id, asset3.id]), ); }); @@ -988,7 +1091,9 @@ describe(AlbumService.name, () => { it('should skip some duplicate assets', async () => { const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()]; const album1 = AlbumFactory.create(); + const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const album2 = AlbumFactory.create(); + mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); mocks.album.getAssetIds @@ -997,18 +1102,23 @@ describe(AlbumService.name, () => { mocks.album.getById.mockResolvedValueOnce(getForAlbum(album1)).mockResolvedValueOnce(getForAlbum(album2)); await expect( - sut.addAssetsToAlbums(AuthFactory.create(album1.owner), { + sut.addAssetsToAlbums(AuthFactory.create(owner), { albumIds: [album1.id, album2.id], assetIds: [asset1.id, asset2.id, asset3.id], }), ).resolves.toEqual({ success: true, error: undefined }); expect(mocks.album.update).toHaveBeenCalledTimes(1); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); + expect(mocks.album.update).toHaveBeenNthCalledWith( + 1, + album2.id, + { + id: album2.id, + updatedAt: expect.any(Date), + albumThumbnailAssetId: asset1.id, + }, + owner.id, + ); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ { albumId: album2.id, assetId: asset1.id }, { albumId: album2.id, assetId: asset2.id }, @@ -1019,6 +1129,7 @@ describe(AlbumService.name, () => { it('should skip all duplicate assets', async () => { const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()]; const album1 = AlbumFactory.create(); + const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const album2 = AlbumFactory.create(); mocks.access.album.checkOwnerAccess .mockResolvedValueOnce(new Set([album1.id])) @@ -1028,7 +1139,7 @@ describe(AlbumService.name, () => { mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); await expect( - sut.addAssetsToAlbums(AuthFactory.create(album1.owner), { + sut.addAssetsToAlbums(AuthFactory.create(owner), { albumIds: [album1.id, album2.id], assetIds: [asset1.id, asset2.id, asset3.id], }), @@ -1044,6 +1155,7 @@ describe(AlbumService.name, () => { it('should skip assets not shared with user', async () => { const user = UserFactory.create(); const album1 = AlbumFactory.create(); + const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; const album2 = AlbumFactory.create(); const [asset1, asset2, asset3] = [ AssetFactory.create({ ownerId: user.id }), @@ -1057,7 +1169,7 @@ describe(AlbumService.name, () => { mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); await expect( - sut.addAssetsToAlbums(AuthFactory.create(album1.owner), { + sut.addAssetsToAlbums(AuthFactory.create(owner), { albumIds: [album1.id, album2.id], assetIds: [asset1.id, asset2.id, asset3.id], }), @@ -1069,12 +1181,12 @@ describe(AlbumService.name, () => { expect(mocks.album.update).not.toHaveBeenCalled(); expect(mocks.album.addAssetIds).not.toHaveBeenCalled(); expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - album1.ownerId, + owner.id, new Set([asset1.id, asset2.id, asset3.id]), false, ); expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith( - album1.ownerId, + owner.id, new Set([asset1.id, asset2.id, asset3.id]), ); }); @@ -1126,12 +1238,13 @@ describe(AlbumService.name, () => { it('should allow the owner to remove assets', async () => { const asset = AssetFactory.create(); const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id])); - await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([ + await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([ { success: true, id: asset.id }, ]); @@ -1141,11 +1254,12 @@ describe(AlbumService.name, () => { it('should skip assets not in the album', async () => { const asset = AssetFactory.create(); const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValue(new Set()); - await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([ + await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([ { success: false, id: asset.id, error: BulkIdErrorReason.NOT_FOUND }, ]); @@ -1155,11 +1269,12 @@ describe(AlbumService.name, () => { it('should allow owner to remove all assets from the album', async () => { const asset = AssetFactory.create(); const album = AlbumFactory.create(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id])); - await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([ + await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([ { success: true, id: asset.id }, ]); }); @@ -1168,12 +1283,13 @@ describe(AlbumService.name, () => { const asset1 = AssetFactory.create(); const asset2 = AssetFactory.create(); const album = AlbumFactory.from({ albumThumbnailAssetId: asset1.id }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id])); - await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset1.id] })).resolves.toEqual([ + await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset1.id] })).resolves.toEqual([ { success: true, id: asset1.id }, ]); diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index 8142bfeff5..ef8a31dcb5 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -15,7 +15,7 @@ import { import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { MapMarkerResponseDto } from 'src/dtos/map.dto'; -import { Permission } from 'src/enum'; +import { AlbumUserRole, Permission } from 'src/enum'; import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; @@ -74,10 +74,10 @@ export class AlbumService extends BaseService { async get(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] }); await this.albumRepository.updateThumbnails(); - const album = await this.findOrFail(id, { withAssets: false }); + const album = await this.findOrFail(id, auth.user.id, { withAssets: false }); const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]); - const hasSharedUsers = album.albumUsers && album.albumUsers.length > 0; + const hasSharedUsers = album.albumUsers && album.albumUsers.length > 1; const hasSharedLink = album.sharedLinks && album.sharedLinks.length > 0; const isShared = hasSharedUsers || hasSharedLink; @@ -114,6 +114,7 @@ export class AlbumService extends BaseService { throw new BadRequestException('Cannot share album with owner'); } } + albumUsers.unshift({ userId: auth.user.id, role: AlbumUserRole.Owner }); const allowedAssetIdsSet = await this.checkAccess({ auth, @@ -126,7 +127,6 @@ export class AlbumService extends BaseService { const album = await this.albumRepository.create( { - ownerId: auth.user.id, albumName: dto.albumName, description: dto.description, albumThumbnailAssetId: assetIds[0] || null, @@ -134,10 +134,11 @@ export class AlbumService extends BaseService { }, assetIds, albumUsers, + auth.user.id, ); for (const { userId } of albumUsers) { - await this.eventRepository.emit('AlbumInvite', { id: album.id, userId }); + await this.eventRepository.emit('AlbumInvite', { id: album.id, userId, senderName: auth.user.name }); } return mapAlbum(album); @@ -146,7 +147,7 @@ export class AlbumService extends BaseService { async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise { await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] }); - const album = await this.findOrFail(id, { withAssets: true }); + const album = await this.findOrFail(id, auth.user.id, { withAssets: true }); if (dto.albumThumbnailAssetId) { const results = await this.albumRepository.getAssetIds(id, [dto.albumThumbnailAssetId]); @@ -154,14 +155,18 @@ export class AlbumService extends BaseService { throw new BadRequestException('Invalid album thumbnail'); } } - const updatedAlbum = await this.albumRepository.update(album.id, { - id: album.id, - albumName: dto.albumName, - description: dto.description, - albumThumbnailAssetId: dto.albumThumbnailAssetId, - isActivityEnabled: dto.isActivityEnabled, - order: dto.order, - }); + const updatedAlbum = await this.albumRepository.update( + album.id, + { + id: album.id, + albumName: dto.albumName, + description: dto.description, + albumThumbnailAssetId: dto.albumThumbnailAssetId, + isActivityEnabled: dto.isActivityEnabled, + order: dto.order, + }, + auth.user.id, + ); return mapAlbum({ ...updatedAlbum, assets: album.assets }); } @@ -172,7 +177,7 @@ export class AlbumService extends BaseService { } async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { - const album = await this.findOrFail(id, { withAssets: false }); + const album = await this.findOrFail(id, auth.user.id, { withAssets: false }); await this.requireAccess({ auth, permission: Permission.AlbumAssetCreate, ids: [id] }); const results = await addAssets( @@ -183,16 +188,18 @@ export class AlbumService extends BaseService { const { id: firstNewAssetId } = results.find(({ success }) => success) || {}; if (firstNewAssetId) { - await this.albumRepository.update(id, { + await this.albumRepository.update( id, - updatedAt: new Date(), - albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId, - }); - - const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter( - (userId) => userId !== auth.user.id, + { + id, + updatedAt: new Date(), + albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId, + }, + auth.user.id, ); + const allUsersExceptUs = album.albumUsers.map(({ user }) => user.id).filter((userId) => userId !== auth.user.id); + for (const recipientId of allUsersExceptUs) { await this.eventRepository.emit('AlbumUpdate', { id, recipientId }); } @@ -231,21 +238,23 @@ export class AlbumService extends BaseService { if (notPresentAssetIds.length === 0) { continue; } - const album = await this.findOrFail(albumId, { withAssets: false }); + const album = await this.findOrFail(albumId, auth.user.id, { withAssets: false }); results.error = undefined; results.success = true; for (const assetId of notPresentAssetIds) { albumAssetValues.push({ albumId, assetId }); } - await this.albumRepository.update(albumId, { - id: albumId, - updatedAt: new Date(), - albumThumbnailAssetId: album.albumThumbnailAssetId ?? notPresentAssetIds[0], - }); - const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter( - (userId) => userId !== auth.user.id, + await this.albumRepository.update( + albumId, + { + id: albumId, + updatedAt: new Date(), + albumThumbnailAssetId: album.albumThumbnailAssetId ?? notPresentAssetIds[0], + }, + auth.user.id, ); + const allUsersExceptUs = album.albumUsers.map(({ user }) => user.id).filter((userId) => userId !== auth.user.id); events.push({ id: albumId, recipients: allUsersExceptUs }); } @@ -262,7 +271,7 @@ export class AlbumService extends BaseService { async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { await this.requireAccess({ auth, permission: Permission.AlbumAssetDelete, ids: [id] }); - const album = await this.findOrFail(id, { withAssets: false }); + const album = await this.findOrFail(id, auth.user.id, { withAssets: false }); const results = await removeAssets( auth, { access: this.accessRepository, bulk: this.albumRepository }, @@ -280,11 +289,11 @@ export class AlbumService extends BaseService { async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise { await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); - const album = await this.findOrFail(id, { withAssets: false }); + const album = await this.findOrFail(id, auth.user.id, { withAssets: false }); for (const { userId, role } of albumUsers) { - if (album.ownerId === userId) { - throw new BadRequestException('Cannot be shared with owner'); + if (role === AlbumUserRole.Owner) { + throw new BadRequestException('Cannot add another owner'); } const exists = album.albumUsers.find(({ user: { id } }) => id === userId); @@ -298,10 +307,10 @@ export class AlbumService extends BaseService { } await this.albumUserRepository.create({ userId, albumId: id, role }); - await this.eventRepository.emit('AlbumInvite', { id, userId }); + await this.eventRepository.emit('AlbumInvite', { id, userId, senderName: auth.user.name }); } - return this.findOrFail(id, { withAssets: true }).then(mapAlbum); + return this.findOrFail(id, auth.user.id, { withAssets: true }).then(mapAlbum); } async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise { @@ -309,17 +318,20 @@ export class AlbumService extends BaseService { userId = auth.user.id; } - const album = await this.findOrFail(id, { withAssets: false }); - - if (album.ownerId === userId) { - throw new BadRequestException('Cannot remove album owner'); - } + const album = await this.findOrFail(id, auth.user.id, { withAssets: false }); const exists = album.albumUsers.find(({ user: { id } }) => id === userId); if (!exists) { throw new BadRequestException('Album not shared with user'); } + if ( + exists.role === AlbumUserRole.Owner && + album.albumUsers.filter(({ role }) => role === AlbumUserRole.Owner).length === 1 + ) { + throw new BadRequestException('Cannot remove the last album owner'); + } + // non-admin can remove themselves if (auth.user.id !== userId) { await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); @@ -333,8 +345,8 @@ export class AlbumService extends BaseService { await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role }); } - private async findOrFail(id: string, options: AlbumInfoOptions) { - const album = await this.albumRepository.getById(id, options); + private async findOrFail(id: string, authUserId: string, options: AlbumInfoOptions) { + const album = await this.albumRepository.getById(id, options, authUserId); if (!album) { throw new BadRequestException('Album not found'); } diff --git a/server/src/services/notification.service.spec.ts b/server/src/services/notification.service.spec.ts index 1eaa4f9a2c..0d51db7ac8 100644 --- a/server/src/services/notification.service.spec.ts +++ b/server/src/services/notification.service.spec.ts @@ -168,10 +168,10 @@ describe(NotificationService.name, () => { describe('onAlbumInviteEvent', () => { it('should queue notify album invite event', async () => { - await sut.onAlbumInvite({ id: '', userId: '42' }); + await sut.onAlbumInvite({ id: '', userId: '42', senderName: 'foo' }); expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.NotifyAlbumInvite, - data: { id: '', recipientId: '42' }, + data: { id: '', recipientId: '42', senderName: 'foo' }, }); }); }); @@ -264,14 +264,18 @@ describe(NotificationService.name, () => { describe('handleAlbumInvite', () => { it('should skip if album could not be found', async () => { - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Skipped, + ); expect(mocks.user.get).not.toHaveBeenCalled(); }); it('should skip if recipient could not be found', async () => { mocks.album.getById.mockResolvedValue(getForAlbum(AlbumFactory.create())); - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Skipped, + ); expect(mocks.job.queue).not.toHaveBeenCalled(); }); @@ -288,7 +292,9 @@ describe(NotificationService.name, () => { }); mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Skipped, + ); }); it('should skip if the recipient has email notifications for album invite disabled', async () => { @@ -304,7 +310,9 @@ describe(NotificationService.name, () => { }); mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Skipped, + ); }); it('should send invite email', async () => { @@ -322,7 +330,9 @@ describe(NotificationService.name, () => { mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Success, + ); expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SendMail, data: expect.objectContaining({ subject: expect.stringContaining('You have been added to a shared album') }), @@ -346,7 +356,9 @@ describe(NotificationService.name, () => { mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Success, + ); expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith( album.albumThumbnailAssetId, AssetFileType.Thumbnail, @@ -378,7 +390,9 @@ describe(NotificationService.name, () => { mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([assetFile]); - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Success, + ); expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith( album.albumThumbnailAssetId, AssetFileType.Thumbnail, @@ -412,7 +426,9 @@ describe(NotificationService.name, () => { mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([asset.files[0]]); - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); + await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe( + JobStatus.Success, + ); expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith( album.albumThumbnailAssetId, AssetFileType.Thumbnail, @@ -434,7 +450,7 @@ describe(NotificationService.name, () => { }); it('should skip if owner could not be found', async () => { - mocks.album.getById.mockResolvedValue(getForAlbum(AlbumFactory.create({ ownerId: 'non-existent' }))); + mocks.album.getById.mockResolvedValue(getForAlbum(AlbumFactory.from().owner({ id: 'non-existent' }).build())); await expect(sut.handleAlbumUpdate({ id: '', recipientId: '1' })).resolves.toBe(JobStatus.Skipped); expect(mocks.systemMetadata.get).not.toHaveBeenCalled(); @@ -443,7 +459,6 @@ describe(NotificationService.name, () => { it('should skip recipient that could not be looked up', async () => { const album = AlbumFactory.from().albumUser({ userId: 'non-existent' }).build(); mocks.album.getById.mockResolvedValue(getForAlbum(album)); - mocks.user.get.mockResolvedValueOnce(album.owner); mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); diff --git a/server/src/services/notification.service.ts b/server/src/services/notification.service.ts index 9f11d19af7..e688f7dc7f 100644 --- a/server/src/services/notification.service.ts +++ b/server/src/services/notification.service.ts @@ -226,8 +226,8 @@ export class NotificationService extends BaseService { } @OnEvent({ name: 'AlbumInvite' }) - async onAlbumInvite({ id, userId }: ArgOf<'AlbumInvite'>) { - await this.jobRepository.queue({ name: JobName.NotifyAlbumInvite, data: { id, recipientId: userId } }); + async onAlbumInvite({ id, userId, senderName }: ArgOf<'AlbumInvite'>) { + await this.jobRepository.queue({ name: JobName.NotifyAlbumInvite, data: { id, recipientId: userId, senderName } }); } @OnEvent({ name: 'SessionDelete' }) @@ -303,7 +303,7 @@ export class NotificationService extends BaseService { } @OnJob({ name: JobName.NotifyAlbumInvite, queue: QueueName.Notification }) - async handleAlbumInvite({ id, recipientId }: JobOf) { + async handleAlbumInvite({ id, recipientId, senderName }: JobOf) { const album = await this.albumRepository.getById(id, { withAssets: false }); if (!album) { return JobStatus.Skipped; @@ -314,7 +314,7 @@ export class NotificationService extends BaseService { return JobStatus.Skipped; } - await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumInvite, album.owner.name); + await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumInvite, senderName); const { emailNotifications } = getPreferences(recipient.metadata); @@ -331,7 +331,7 @@ export class NotificationService extends BaseService { baseUrl: getExternalDomain(server), albumId: album.id, albumName: album.albumName, - senderName: album.owner.name, + senderName, recipientName: recipient.name, cid: attachment ? attachment.cid : undefined, }, @@ -360,8 +360,8 @@ export class NotificationService extends BaseService { return JobStatus.Skipped; } - const owner = await this.userRepository.get(album.ownerId, { withDeleted: false }); - if (!owner) { + const recipient = await this.userRepository.get(recipientId, { withDeleted: false }); + if (!recipient) { return JobStatus.Skipped; } diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts index 50bf9368c8..d4d1a46b4a 100644 --- a/server/src/services/sync.service.ts +++ b/server/src/services/sync.service.ts @@ -7,6 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { SyncAckDeleteDto, SyncAckSetDto, + syncAlbumV2ToV1, syncAssetFaceV2ToV1, SyncAssetV1, SyncItem, @@ -60,6 +61,7 @@ export const SYNC_TYPES_ORDER = [ SyncRequestType.PartnerStacksV1, SyncRequestType.AlbumAssetsV1, SyncRequestType.AlbumsV1, + SyncRequestType.AlbumsV2, SyncRequestType.AlbumUsersV1, SyncRequestType.AlbumToAssetsV1, SyncRequestType.AssetExifsV1, @@ -165,6 +167,7 @@ export class SyncService extends BaseService { [SyncRequestType.PartnerAssetExifsV1]: () => this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id), [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.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id), @@ -412,6 +415,21 @@ export class SyncService extends BaseService { const upsertType = SyncEntityType.AlbumV1; const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] }); + for await (const { updateId, ...data } of upserts) { + const albumUsers = await this.syncRepository.album.getAlbumUsers(data.id); + send(response, { type: upsertType, ids: [updateId], data: syncAlbumV2ToV1(data, albumUsers) }); + } + } + + private async syncAlbumsV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { + const deleteType = SyncEntityType.AlbumDeleteV1; + const deletes = this.syncRepository.album.getDeletes({ ...options, ack: checkpointMap[deleteType] }); + for await (const { id, ...data } of deletes) { + send(response, { type: deleteType, ids: [id], data }); + } + + const upsertType = SyncEntityType.AlbumV2; + const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] }); for await (const { updateId, ...data } of upserts) { send(response, { type: upsertType, ids: [updateId], data }); } diff --git a/server/src/types.ts b/server/src/types.ts index a69bd79dff..179f9d1b61 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -249,6 +249,7 @@ export interface INotifySignupJob extends IEntityJob { export interface INotifyAlbumInviteJob extends IEntityJob { recipientId: string; + senderName: string; } export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob { diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 44a4b37ad7..05b1e0b199 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -455,3 +455,5 @@ export const updateLockedColumns = & { locked exif.lockedProperties = lockableProperties.filter((property) => property in exif); return exif; }; + +export const dummy = sql`(select 1)`.as('dummy'); diff --git a/server/test/factories/album.factory.ts b/server/test/factories/album.factory.ts index f401cd343d..336a9747cd 100644 --- a/server/test/factories/album.factory.ts +++ b/server/test/factories/album.factory.ts @@ -1,5 +1,5 @@ import { Selectable } from 'kysely'; -import { AssetOrder } from 'src/enum'; +import { AlbumUserRole, AssetOrder } from 'src/enum'; import { AlbumTable } from 'src/schema/tables/album.table'; import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; import { AlbumUserFactory } from 'test/factories/album-user.factory'; @@ -10,15 +10,12 @@ import { UserFactory } from 'test/factories/user.factory'; import { newDate, newUuid, newUuidV7 } from 'test/small.factory'; export class AlbumFactory { - #owner: UserFactory; + #owner!: UserFactory; #sharedLinks: Selectable[] = []; #albumUsers: AlbumUserFactory[] = []; #assets: AssetFactory[] = []; - private constructor(private readonly value: Selectable) { - value.ownerId ??= newUuid(); - this.#owner = UserFactory.from({ id: value.ownerId }); - } + private constructor(private readonly value: Selectable) {} static create(dto: AlbumLike = {}) { return AlbumFactory.from(dto).build(); @@ -27,7 +24,6 @@ export class AlbumFactory { static from(dto: AlbumLike = {}) { return new AlbumFactory({ id: newUuid(), - ownerId: newUuid(), albumName: 'My Album', albumThumbnailAssetId: null, createdAt: newDate(), @@ -43,7 +39,7 @@ export class AlbumFactory { owner(dto: UserLike = {}, builder?: FactoryBuilder) { this.#owner = build(UserFactory.from(dto), builder); - this.value.ownerId = this.#owner.build().id; + this.albumUser({ userId: this.#owner.build().id, role: AlbumUserRole.Owner }); return this; } @@ -53,7 +49,7 @@ export class AlbumFactory { } albumUser(dto: AlbumUserLike = {}, builder?: FactoryBuilder) { - const albumUser = build(AlbumUserFactory.from(dto).album(this.value), builder); + const albumUser = build(AlbumUserFactory.from(dto), builder); this.#albumUsers.push(albumUser); return this; } @@ -78,7 +74,6 @@ export class AlbumFactory { build() { return { ...this.value, - owner: this.#owner.build(), assets: this.#assets.map((asset) => asset.build()), albumUsers: this.#albumUsers.map((albumUser) => albumUser.build()), sharedLinks: this.#sharedLinks, diff --git a/server/test/mappers.ts b/server/test/mappers.ts index edd85a386d..8cac6ce8fd 100644 --- a/server/test/mappers.ts +++ b/server/test/mappers.ts @@ -84,7 +84,6 @@ export const getForAlbum = (album: ReturnType) => ({ createdAt: albumUser.createdAt.toISOString(), user: getDehydrated(albumUser.user), })), - owner: getDehydrated(album.owner), sharedLinks: album.sharedLinks.map((sharedLink) => getDehydrated(sharedLink)), }); @@ -219,7 +218,6 @@ export const getForSharedLink = (sharedLink: ReturnType getDehydrated(asset)), } : null, diff --git a/server/test/medium.factory.ts b/server/test/medium.factory.ts index b874c11b84..c2019029da 100644 --- a/server/test/medium.factory.ts +++ b/server/test/medium.factory.ts @@ -222,9 +222,14 @@ export class MediumTestContext { return { result }; } - async newAlbum(dto: Insertable, assetIds?: string[]) { + async newAlbum({ ownerId, ...dto }: Insertable & { ownerId: string }, assetIds?: string[]) { const album = mediumFactory.albumInsert(dto); - const result = await this.get(AlbumRepository).create(album, assetIds ?? [], []); + const result = await this.get(AlbumRepository).create( + album, + assetIds ?? [], + [{ userId: ownerId, role: AlbumUserRole.Owner }], + ownerId, + ); return { album, result }; } @@ -570,9 +575,9 @@ const assetInsert = (asset: Partial> = {}) => { }; }; -const albumInsert = (album: Partial> & { ownerId: string }) => { +const albumInsert = (album: Partial>) => { const id = album.id || newUuid(); - const defaults: Omit, 'ownerId'> = { + const defaults: Insertable = { albumName: 'Album', }; diff --git a/server/test/medium/specs/sync/sync-album-user.spec.ts b/server/test/medium/specs/sync/sync-album-user.spec.ts index 4970995d28..acd693f9fb 100644 --- a/server/test/medium/specs/sync/sync-album-user.spec.ts +++ b/server/test/medium/specs/sync/sync-album-user.spec.ts @@ -25,6 +25,14 @@ describe(SyncRequestType.AlbumUsersV1, () => { const { albumUser } = await ctx.newAlbumUser({ albumId: album.id, userId: user.id, role: AlbumUserRole.Editor }); await expect(ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1])).resolves.toEqual([ + { + ack: expect.any(String), + data: expect.objectContaining({ + albumId: album.id, + role: AlbumUserRole.Owner, + }), + type: SyncEntityType.AlbumUserV1, + }, { ack: expect.any(String), data: expect.objectContaining({ @@ -47,6 +55,14 @@ describe(SyncRequestType.AlbumUsersV1, () => { const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(response).toEqual([ + { + ack: expect.any(String), + data: expect.objectContaining({ + albumId: album.id, + role: AlbumUserRole.Owner, + }), + type: SyncEntityType.AlbumUserV1, + }, { ack: expect.any(String), data: expect.objectContaining({ @@ -136,6 +152,14 @@ describe(SyncRequestType.AlbumUsersV1, () => { const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(response).toEqual([ + { + ack: expect.any(String), + data: expect.objectContaining({ + albumId: album.id, + role: AlbumUserRole.Owner, + }), + type: SyncEntityType.AlbumUserV1, + }, { ack: expect.any(String), data: expect.objectContaining({ @@ -163,6 +187,7 @@ describe(SyncRequestType.AlbumUsersV1, () => { const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(response).toEqual([ + expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }), expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }), expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), @@ -201,6 +226,7 @@ describe(SyncRequestType.AlbumUsersV1, () => { const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(response).toEqual([ + expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }), expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }), expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }), expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), @@ -229,10 +255,11 @@ describe(SyncRequestType.AlbumUsersV1, () => { it('should backfill album users when a user shares an album with you', async () => { const { auth, ctx } = await setup(); + const { user } = await ctx.newUser(); const { user: user1 } = await ctx.newUser(); const { user: user2 } = await ctx.newUser(); - const { album: album1 } = await ctx.newAlbum({ ownerId: user1.id }); - const { album: album2 } = await ctx.newAlbum({ ownerId: user1.id }); + const { album: album1 } = await ctx.newAlbum({ ownerId: user.id }); + const { album: album2 } = await ctx.newAlbum({ ownerId: user.id }); // backfill album user await ctx.newAlbumUser({ albumId: album1.id, userId: user1.id, role: AlbumUserRole.Editor }); await wait(2); @@ -244,6 +271,15 @@ describe(SyncRequestType.AlbumUsersV1, () => { const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(response).toEqual([ + { + ack: expect.any(String), + data: expect.objectContaining({ + albumId: album2.id, + role: AlbumUserRole.Owner, + userId: user.id, + }), + type: SyncEntityType.AlbumUserV1, + }, { ack: expect.any(String), data: expect.objectContaining({ @@ -264,6 +300,15 @@ describe(SyncRequestType.AlbumUsersV1, () => { // should backfill the album user const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(newResponse).toEqual([ + { + ack: expect.any(String), + data: expect.objectContaining({ + albumId: album1.id, + role: AlbumUserRole.Owner, + userId: user.id, + }), + type: SyncEntityType.AlbumUserBackfillV1, + }, { ack: expect.any(String), data: expect.objectContaining({ diff --git a/server/test/medium/specs/sync/sync-album.spec.ts b/server/test/medium/specs/sync/sync-album.spec.ts index 02536e3a8d..8fff7691b8 100644 --- a/server/test/medium/specs/sync/sync-album.spec.ts +++ b/server/test/medium/specs/sync/sync-album.spec.ts @@ -30,7 +30,6 @@ describe(SyncRequestType.AlbumsV1, () => { data: expect.objectContaining({ id: album.id, name: album.albumName, - ownerId: album.ownerId, }), type: SyncEntityType.AlbumV1, }, diff --git a/web/src/lib/components/album-page/AlbumCard.svelte b/web/src/lib/components/album-page/AlbumCard.svelte index 38b13fa150..621de983eb 100644 --- a/web/src/lib/components/album-page/AlbumCard.svelte +++ b/web/src/lib/components/album-page/AlbumCard.svelte @@ -3,7 +3,7 @@ import { authManager } from '$lib/managers/auth-manager.svelte'; import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu'; import { getShortDateRange } from '$lib/utils/date-time'; - import type { AlbumResponseDto } from '@immich/sdk'; + import { type AlbumResponseDto } from '@immich/sdk'; import { IconButton } from '@immich/ui'; import { mdiDotsVertical } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -85,12 +85,13 @@ {/if} {#if showOwner} - {#if authManager.user.id === album.ownerId} + {@const owner = album.albumUsers[0].user} + {#if owner.id === authManager.user.id}

{$t('owned')}

- {:else if album.owner} -

{$t('shared_by_user', { values: { user: album.owner.name } })}

{:else} -

{$t('shared')}

+

+ {$t('shared_by_user', { values: { user: owner.name } })} +

{/if} {:else if album.shared}

{$t('shared')}

diff --git a/web/src/lib/components/album-page/AlbumsList.svelte b/web/src/lib/components/album-page/AlbumsList.svelte index d62e6c1d3b..846e2b97a7 100644 --- a/web/src/lib/components/album-page/AlbumsList.svelte +++ b/web/src/lib/components/album-page/AlbumsList.svelte @@ -20,7 +20,7 @@ import { getSelectedAlbumGroupOption, sortAlbums, stringToSortOrder, type AlbumGroup } from '$lib/utils/album-utils'; import type { ContextMenuPosition } from '$lib/utils/context-menu'; import { normalizeSearchString } from '$lib/utils/string-utils'; - import { type AlbumResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; + import { AlbumUserRole, type AlbumResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; import { modalManager } from '@immich/ui'; import { mdiDeleteOutline, mdiDownload, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js'; import { groupBy } from 'lodash-es'; @@ -98,24 +98,26 @@ /** Group by owner */ [AlbumGroupBy.Owner]: (order, albums): AlbumGroup[] => { const currentUserId = authManager.user.id; - const groupedByOwnerIds = groupBy(albums, 'ownerId'); + const groupedByOwnerIds = groupBy(albums, (album) => album.albumUsers[0].user.id); const sortSign = order === SortOrder.Desc ? -1 : 1; - const sortedByOwnerNames = Object.entries(groupedByOwnerIds).sort(([ownerA, albumsA], [ownerB, albumsB]) => { + const sortedByOwnerNames = Object.entries(groupedByOwnerIds).sort(([ownerIdA, albumsA], [ownerIdB, albumsB]) => { // We make sure owned albums stay either at the beginning or the end // of the list - if (ownerA === currentUserId) { + if (ownerIdA === currentUserId) { return -sortSign; - } else if (ownerB === currentUserId) { + } else if (ownerIdB === currentUserId) { return sortSign; } else { - return albumsA[0].owner.name.localeCompare(albumsB[0].owner.name, $locale) * sortSign; + const ownerA = albumsA[0].albumUsers[0].user; + const ownerB = albumsB[0].albumUsers[0].user; + return ownerA.name.localeCompare(ownerB.name, $locale) * sortSign; } }); return sortedByOwnerNames.map(([ownerId, albums]) => ({ id: ownerId, - name: ownerId === currentUserId ? $t('my_albums') : albums[0].owner.name, + name: ownerId === currentUserId ? $t('my_albums') : albums[0].albumUsers[0].user.name, albums, })); }, @@ -130,7 +132,10 @@ return sharedAlbums; } default: { - const nonOwnedAlbums = sharedAlbums.filter((album) => album.ownerId !== authManager.user.id); + const nonOwnedAlbums = sharedAlbums.filter( + (album) => + album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role !== AlbumUserRole.Owner, + ); return nonOwnedAlbums.length > 0 ? ownedAlbums.concat(nonOwnedAlbums) : ownedAlbums; } } @@ -167,7 +172,9 @@ albumGroupIds = groupedAlbums.map(({ id }) => id); }); - let showFullContextMenu = $derived(allowEdit && selectedAlbum && selectedAlbum.ownerId === authManager.user.id); + let showFullContextMenu = $derived( + allowEdit && selectedAlbum && selectedAlbum.albumUsers[0].user.id === authManager.user.id, + ); onMount(async () => { if (allowEdit) { diff --git a/web/src/lib/components/album-page/AlbumsTableRow.svelte b/web/src/lib/components/album-page/AlbumsTableRow.svelte index f743251f56..0dde44511b 100644 --- a/web/src/lib/components/album-page/AlbumsTableRow.svelte +++ b/web/src/lib/components/album-page/AlbumsTableRow.svelte @@ -5,7 +5,7 @@ import { Route } from '$lib/route'; import { locale } from '$lib/stores/preferences.store'; import type { ContextMenuPosition } from '$lib/utils/context-menu'; - import type { AlbumResponseDto } from '@immich/sdk'; + import { AlbumUserRole, type AlbumResponseDto } from '@immich/sdk'; import { Icon } from '@immich/ui'; import { mdiShareVariantOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -43,9 +43,11 @@ icon={mdiShareVariantOutline} size="16" class="inline ms-1 opacity-70" - title={album.ownerId === authManager.user.id + title={album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role === AlbumUserRole.Owner ? $t('shared_by_you') - : $t('shared_by_user', { values: { user: album.owner.name } })} + : $t('shared_by_user', { + values: { user: album.albumUsers[0].user.name }, + })} /> {/if} diff --git a/web/src/lib/components/asset-viewer/ActivityViewer.svelte b/web/src/lib/components/asset-viewer/ActivityViewer.svelte index 2b5d5f9345..b59bdafad8 100644 --- a/web/src/lib/components/asset-viewer/ActivityViewer.svelte +++ b/web/src/lib/components/asset-viewer/ActivityViewer.svelte @@ -12,7 +12,7 @@ import { getAssetType } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; import { isTenMinutesApart } from '$lib/utils/timesince'; - import { ReactionType, type ActivityResponseDto, type AssetTypeEnum } from '@immich/sdk'; + import { ReactionType, type ActivityResponseDto, type AlbumUserResponseDto, type AssetTypeEnum } from '@immich/sdk'; import { Icon, IconButton, LoadingSpinner, Textarea, toastManager } from '@immich/ui'; import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiSend, mdiThumbUp } from '@mdi/js'; import * as luxon from 'luxon'; @@ -43,11 +43,11 @@ assetId?: string | undefined; albumId: string; assetType?: AssetTypeEnum | undefined; - albumOwnerId: string; + albumUsers: AlbumUserResponseDto[]; disabled: boolean; } - let { assetId = undefined, albumId, assetType = undefined, albumOwnerId, disabled }: Props = $props(); + let { assetId = undefined, albumId, assetType = undefined, albumUsers, disabled }: Props = $props(); let innerHeight: number = $state(0); let activityHeight: number = $state(0); @@ -56,6 +56,7 @@ let previousAssetId: string | undefined = $state(assetId); let message = $state(''); let isSendingMessage = $state(false); + const isAlbumOwner = $derived(albumUsers[0].user.id === authManager.user.id); const timeOptions: Intl.DateTimeFormatOptions = { year: 'numeric', @@ -147,7 +148,7 @@ /> {/if} - {#if reaction.user.id === authManager.user.id || albumOwnerId === authManager.user.id} + {#if reaction.user.id === authManager.user.id || isAlbumOwner}
{/if} - {#if reaction.user.id === authManager.user.id || albumOwnerId === authManager.user.id} + {#if reaction.user.id === authManager.user.id || isAlbumOwner}
diff --git a/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte b/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte index 4aa72bb368..5098a78619 100644 --- a/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte @@ -82,7 +82,7 @@ }: Props = $props(); const isOwner = $derived(authManager.authenticated && asset.ownerId === authManager.user.id); - const isAlbumOwner = $derived(authManager.authenticated && album?.ownerId === authManager.user.id); + const isAlbumOwner = $derived(authManager.authenticated && album?.albumUsers[0].user.id === authManager.user.id); const isLocked = $derived(asset.visibility === AssetVisibility.Locked); const smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch); diff --git a/web/src/lib/modals/AlbumAddUsersModal.svelte b/web/src/lib/modals/AlbumAddUsersModal.svelte index dbfda82da9..54bc0d0b4e 100644 --- a/web/src/lib/modals/AlbumAddUsersModal.svelte +++ b/web/src/lib/modals/AlbumAddUsersModal.svelte @@ -20,7 +20,7 @@ const { album, onClose }: Props = $props(); let users: UserResponseDto[] = $state([]); - const excludedUserIds = $derived([album.ownerId, ...album.albumUsers.map(({ user: { id } }) => id)]); + const excludedUserIds = $derived(album.albumUsers.map(({ user: { id } }) => id)); const filteredUsers = $derived( sortBy( users.filter( diff --git a/web/src/lib/modals/AlbumOptionsModal.svelte b/web/src/lib/modals/AlbumOptionsModal.svelte index b80097a6f3..8d7088266c 100644 --- a/web/src/lib/modals/AlbumOptionsModal.svelte +++ b/web/src/lib/modals/AlbumOptionsModal.svelte @@ -105,16 +105,6 @@
-
-
- -
- {album.owner.name} - - []} onChange={(value) => handleRoleSelect(user, value)} diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts index c168ad3e19..8cd46bd13d 100644 --- a/web/src/lib/services/album.service.ts +++ b/web/src/lib/services/album.service.ts @@ -42,7 +42,7 @@ export const getAlbumsActions = ($t: MessageFormatter) => { }; export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto) => { - const isOwned = authManager.user.id === album.ownerId; + const isOwned = album.albumUsers[0].user.id === authManager.user.id; const Share: ActionItem = { title: $t('share'), diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 35078b06e4..9a72417b74 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -210,9 +210,7 @@ let albumId = $derived(album.id); const containsEditors = $derived(album?.shared && album.albumUsers.some(({ role }) => role === AlbumUserRole.Editor)); - const albumUsers = $derived( - showAlbumUsers && containsEditors ? [album.owner, ...album.albumUsers.map(({ user }) => user)] : [], - ); + const albumUsers = $derived(showAlbumUsers && containsEditors ? album.albumUsers.map(({ user }) => user) : []); $effect(() => { if (!album.isActivityEnabled && activityManager.commentCount === 0) { @@ -231,7 +229,7 @@ return { albumId, order: album.order }; }); - const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0); + const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 1); $effect(() => { if (assetViewerManager.isViewing || !isShared) { @@ -243,16 +241,15 @@ onDestroy(() => activityManager.reset()); - let isOwned = $derived(authManager.user.id == album.ownerId); + const isOwned = $derived(album.albumUsers[0].user.id === authManager.user.id); let showActivityStatus = $derived( - album.albumUsers.length > 0 && + album.albumUsers.length > 1 && !assetViewerManager.isViewing && (album.isActivityEnabled || activityManager.commentCount > 0), ); - let isEditor = $derived( - album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role === AlbumUserRole.Editor || - album.ownerId === authManager.user.id, + const isEditor = $derived( + album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role === AlbumUserRole.Editor || isOwned, ); let albumHasViewers = $derived(album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer)); @@ -374,7 +371,7 @@ {/if} - {#if album.albumUsers.length > 0 || (album.hasSharedLink && isOwned)} + {#if album.albumUsers.length > 1 || (album.hasSharedLink && isOwned)}
{#if album.hasSharedLink && isOwned} @@ -388,13 +385,8 @@ /> {/if} - - - - {#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor) as { user } (user.id)} + {#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor || role === AlbumUserRole.Owner) as { user } (user.id)} @@ -620,7 +612,7 @@ {/if} {/if}
- {#if album.albumUsers.length > 0 && album && assetViewerManager.isShowActivityPanel && authManager.authenticated && !assetViewerManager.isViewing} + {#if album.albumUsers.length > 1 && album && assetViewerManager.isShowActivityPanel && authManager.authenticated && !assetViewerManager.isViewing}
- +
{/if} diff --git a/web/src/test-data/factories/album-factory.ts b/web/src/test-data/factories/album-factory.ts index 7ad44b7ac5..7ef8a964a9 100644 --- a/web/src/test-data/factories/album-factory.ts +++ b/web/src/test-data/factories/album-factory.ts @@ -1,7 +1,6 @@ import { faker } from '@faker-js/faker'; import { AssetOrder, type AlbumResponseDto } from '@immich/sdk'; import { Sync } from 'factory.ts'; -import { userFactory } from './user-factory'; export const albumFactory = Sync.makeFactory({ albumName: Sync.each(() => faker.commerce.product()), @@ -11,8 +10,6 @@ export const albumFactory = Sync.makeFactory({ createdAt: Sync.each(() => faker.date.past().toISOString()), updatedAt: Sync.each(() => faker.date.past().toISOString()), id: Sync.each(() => faker.string.uuid()), - ownerId: Sync.each(() => faker.string.uuid()), - owner: userFactory.build(), shared: false, albumUsers: [], hasSharedLink: false,