mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
feat: mobile trusted groups
This commit is contained in:
parent
5fc7233e39
commit
436f866583
15 changed files with 4625 additions and 55 deletions
3668
mobile/drift_schemas/main/drift_schema_v32.json
generated
Normal file
3668
mobile/drift_schemas/main/drift_schema_v32.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -104,12 +104,9 @@ abstract class DriftPerson with _$DriftPerson {
|
|||
required String id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required String ownerId,
|
||||
required String name,
|
||||
String? faceAssetId,
|
||||
required bool isFavorite,
|
||||
required bool isHidden,
|
||||
required String? color,
|
||||
DateTime? birthDate,
|
||||
}) = _DriftPerson;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,8 +316,14 @@ class SyncStreamService {
|
|||
return _syncStreamRepository.deleteUserMetadatasV1(data.cast());
|
||||
case SyncEntityType.personV1:
|
||||
return _syncStreamRepository.updatePeopleV1(data.cast());
|
||||
case SyncEntityType.personV2:
|
||||
return _syncStreamRepository.updatePeopleV2(data.cast());
|
||||
case SyncEntityType.personDeleteV1:
|
||||
return _syncStreamRepository.deletePeopleV1(data.cast());
|
||||
case SyncEntityType.personUserV1:
|
||||
return _syncStreamRepository.updatePersonUsersV1(data.cast());
|
||||
case SyncEntityType.personUserDeleteV1:
|
||||
return _syncStreamRepository.deletePersonUsersV1(data.cast());
|
||||
case SyncEntityType.assetFaceV1:
|
||||
return _syncStreamRepository.updateAssetFacesV1(data.cast());
|
||||
case SyncEntityType.assetFaceV2:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import 'package:drift/drift.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_person_owner_id ON person_entity (owner_id)')
|
||||
class PersonEntity extends Table with DriftDefaultsMixin {
|
||||
const PersonEntity();
|
||||
|
||||
|
|
@ -12,16 +10,8 @@ class PersonEntity extends Table with DriftDefaultsMixin {
|
|||
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
TextColumn get name => text()();
|
||||
|
||||
TextColumn get faceAssetId => text().nullable()();
|
||||
|
||||
BoolColumn get isFavorite => boolean()();
|
||||
|
||||
BoolColumn get isHidden => boolean()();
|
||||
|
||||
TextColumn get color => text().nullable()();
|
||||
|
||||
DateTimeColumn get birthDate => dateTime().nullable()();
|
||||
|
|
|
|||
23
mobile/lib/infrastructure/entities/person_user.entity.dart
Normal file
23
mobile/lib/infrastructure/entities/person_user.entity.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||
|
||||
class PersonUserEntity extends Table with DriftDefaultsMixin {
|
||||
const PersonUserEntity();
|
||||
|
||||
TextColumn get personId => text().references(PersonEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
|
||||
|
||||
BoolColumn get isHidden => boolean().withDefault(const Constant(false))();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {personId, ownerId};
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person_user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
|
||||
User mapToUser(UserEntityData data) => User(
|
||||
|
|
@ -13,3 +16,17 @@ User mapToUser(UserEntityData data) => User(
|
|||
|
||||
Partner mapToPartner(UserEntityData user, PartnerEntityData partner) =>
|
||||
Partner.fromUser(mapToUser(user), inTimeline: partner.inTimeline);
|
||||
|
||||
DriftPerson mapToPerson(PersonEntityData person, PersonUserEntityData personUser) {
|
||||
final updatedAt = personUser.updatedAt.isAfter(person.updatedAt) ? personUser.updatedAt : person.updatedAt;
|
||||
|
||||
return .new(
|
||||
id: person.id,
|
||||
createdAt: person.createdAt,
|
||||
updatedAt: updatedAt,
|
||||
name: person.name,
|
||||
isFavorite: personUser.isFavorite,
|
||||
isHidden: personUser.isHidden,
|
||||
birthDate: person.birthDate,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
|||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person_user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
|
||||
|
|
@ -61,6 +62,7 @@ import 'package:sqlite_async/sqlite_async.dart';
|
|||
MemoryAssetEntity,
|
||||
StackEntity,
|
||||
PersonEntity,
|
||||
PersonUserEntity,
|
||||
AssetFaceEntity,
|
||||
StoreEntity,
|
||||
TrashedLocalAssetEntity,
|
||||
|
|
@ -120,7 +122,7 @@ class Drift extends $Drift {
|
|||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 31;
|
||||
int get schemaVersion => 32;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
|
|
@ -318,6 +320,17 @@ class Drift extends $Drift {
|
|||
from30To31: (m, v31) async {
|
||||
await m.createIndex(v31.idxRemoteAssetUploaded);
|
||||
},
|
||||
from31To32: (m, v32) async {
|
||||
await m.createTable(v32.personUserEntity);
|
||||
await customStatement('''
|
||||
INSERT INTO person_user_entity
|
||||
(person_id, owner_id, is_favorite, is_hidden, created_at, updated_at)
|
||||
SELECT id, owner_id, is_favorite, is_hidden, created_at, updated_at
|
||||
FROM person_entity
|
||||
''');
|
||||
await customStatement('DROP INDEX IF EXISTS idx_person_owner_id');
|
||||
await m.alterTable(TableMigration(v32.personEntity));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16506,6 +16506,651 @@ final class Schema31 extends i0.VersionedSchema {
|
|||
);
|
||||
}
|
||||
|
||||
final class Schema32 extends i0.VersionedSchema {
|
||||
Schema32({required super.database}) : super(version: 32);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
userEntity,
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
idxLocalAlbumAssetAlbumAsset,
|
||||
idxLocalAssetChecksum,
|
||||
idxLocalAssetCloudId,
|
||||
idxLocalAssetCreatedAt,
|
||||
idxStackPrimaryAssetId,
|
||||
uQRemoteAssetsOwnerChecksum,
|
||||
uQRemoteAssetsOwnerLibraryChecksum,
|
||||
idxRemoteAssetChecksum,
|
||||
idxRemoteAssetStackId,
|
||||
idxRemoteAssetOwnerVisibilityDeletedCreated,
|
||||
idxRemoteAssetUploaded,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
remoteAssetCloudIdEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity,
|
||||
personEntity,
|
||||
personUserEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
trashedLocalAssetEntity,
|
||||
assetEditEntity,
|
||||
settings,
|
||||
assetOcrEntity,
|
||||
idxPartnerSharedWithId,
|
||||
idxLatLng,
|
||||
idxRemoteExifCity,
|
||||
idxRemoteAlbumAssetAlbumAsset,
|
||||
idxRemoteAssetCloudId,
|
||||
idxAssetFacePersonId,
|
||||
idxAssetFaceAssetId,
|
||||
idxAssetFaceVisiblePerson,
|
||||
idxTrashedLocalAssetChecksum,
|
||||
idxTrashedLocalAssetAlbum,
|
||||
idxAssetEditAssetId,
|
||||
idxAssetOcrAssetId,
|
||||
];
|
||||
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 Shape50 remoteAssetEntity = Shape50(
|
||||
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_212,
|
||||
_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 idxLocalAssetCreatedAt = i1.Index(
|
||||
'idx_local_asset_created_at',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)',
|
||||
);
|
||||
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 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 idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index(
|
||||
'idx_remote_asset_owner_visibility_deleted_created',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetUploaded = i1.Index(
|
||||
'idx_remote_asset_uploaded',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)',
|
||||
);
|
||||
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 Shape52 personEntity = Shape52(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'person_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_108,
|
||||
_column_191,
|
||||
_column_192,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape53 personUserEntity = Shape53(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'person_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(person_id, owner_id)'],
|
||||
columns: [
|
||||
_column_225,
|
||||
_column_121,
|
||||
_column_120,
|
||||
_column_226,
|
||||
_column_114,
|
||||
_column_115,
|
||||
],
|
||||
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,
|
||||
);
|
||||
late final Shape49 settings = Shape49(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'settings',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY("key")'],
|
||||
columns: [_column_210, _column_224, _column_115],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape51 assetOcrEntity = Shape51(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_ocr_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_159,
|
||||
_column_213,
|
||||
_column_214,
|
||||
_column_215,
|
||||
_column_216,
|
||||
_column_217,
|
||||
_column_218,
|
||||
_column_219,
|
||||
_column_220,
|
||||
_column_221,
|
||||
_column_222,
|
||||
_column_223,
|
||||
_column_201,
|
||||
],
|
||||
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 idxRemoteExifCity = i1.Index(
|
||||
'idx_remote_exif_city',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL',
|
||||
);
|
||||
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 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 idxAssetFaceVisiblePerson = i1.Index(
|
||||
'idx_asset_face_visible_person',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL',
|
||||
);
|
||||
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)',
|
||||
);
|
||||
final i1.Index idxAssetOcrAssetId = i1.Index(
|
||||
'idx_asset_ocr_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)',
|
||||
);
|
||||
}
|
||||
|
||||
class Shape52 extends i0.VersionedTable {
|
||||
Shape52({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get id =>
|
||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get createdAt =>
|
||||
columnsByName['created_at']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get updatedAt =>
|
||||
columnsByName['updated_at']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get name =>
|
||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get color =>
|
||||
columnsByName['color']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get birthDate =>
|
||||
columnsByName['birth_date']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
class Shape53 extends i0.VersionedTable {
|
||||
Shape53({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get personId =>
|
||||
columnsByName['person_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get ownerId =>
|
||||
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get isFavorite =>
|
||||
columnsByName['is_favorite']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get isHidden =>
|
||||
columnsByName['is_hidden']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get createdAt =>
|
||||
columnsByName['created_at']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get updatedAt =>
|
||||
columnsByName['updated_at']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_225(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'person_id',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.string,
|
||||
$customConstraints:
|
||||
'NOT NULL REFERENCES person_entity(id)ON DELETE CASCADE',
|
||||
);
|
||||
i1.GeneratedColumn<int> _column_226(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'is_hidden',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_hidden IN (0, 1))',
|
||||
defaultValue: const i1.CustomExpression('0'),
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
|
|
@ -16537,6 +17182,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||
required Future<void> Function(i1.Migrator m, Schema30 schema) from29To30,
|
||||
required Future<void> Function(i1.Migrator m, Schema31 schema) from30To31,
|
||||
required Future<void> Function(i1.Migrator m, Schema32 schema) from31To32,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
|
|
@ -16690,6 +17336,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
final migrator = i1.Migrator(database, schema);
|
||||
await from30To31(migrator, schema);
|
||||
return 31;
|
||||
case 31:
|
||||
final schema = Schema32(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from31To32(migrator, schema);
|
||||
return 32;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
|
|
@ -16727,6 +17378,7 @@ i1.OnUpgrade stepByStep({
|
|||
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||
required Future<void> Function(i1.Migrator m, Schema30 schema) from29To30,
|
||||
required Future<void> Function(i1.Migrator m, Schema31 schema) from30To31,
|
||||
required Future<void> Function(i1.Migrator m, Schema32 schema) from31To32,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
|
|
@ -16759,5 +17411,6 @@ i1.OnUpgrade stepByStep({
|
|||
from28To29: from28To29,
|
||||
from29To30: from29To30,
|
||||
from30To31: from30To31,
|
||||
from31To32: from31To32,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:drift/drift.dart';
|
|||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/mapper.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class DriftPeopleRepository extends DriftDatabaseRepository {
|
||||
|
|
@ -9,13 +10,21 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
|
|||
const DriftPeopleRepository(this._db) : super(_db);
|
||||
|
||||
Future<DriftPerson?> get(String personId) async {
|
||||
final query = _db.select(_db.personEntity)..where((row) => row.id.equals(personId));
|
||||
final query = _db.personEntity.select().join([
|
||||
innerJoin(_db.personUserEntity, _db.personUserEntity.personId.equalsExp(_db.personEntity.id)),
|
||||
])..where(_db.personEntity.id.equals(personId));
|
||||
|
||||
final result = await query.getSingleOrNull();
|
||||
return result?.toDto();
|
||||
final person = result?.readTable(_db.personEntity);
|
||||
final personUser = result?.readTable(_db.personUserEntity);
|
||||
if (person == null || personUser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mapToPerson(person, personUser);
|
||||
}
|
||||
|
||||
Future<List<DriftPerson>> getAssetPeople(String assetId) async {
|
||||
Future<List<DriftPerson>> getAssetPeople(String assetId) {
|
||||
// An asset can have multiple face records for the same person (e.g., metadata
|
||||
// imports alongside ML detections). Use a subquery instead of a join so each
|
||||
// person is returned once, regardless of how many of their faces are on the asset
|
||||
|
|
@ -27,24 +36,27 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
|
|||
_db.assetFaceEntity.deletedAt.isNull(),
|
||||
);
|
||||
|
||||
final query = _db.select(_db.personEntity)
|
||||
..where((row) => row.id.isInQuery(faceQuery) & row.isHidden.equals(false));
|
||||
final query = _db.personEntity.select().join([
|
||||
innerJoin(_db.personUserEntity, _db.personUserEntity.personId.equalsExp(_db.personEntity.id)),
|
||||
])..where(_db.personEntity.id.isInQuery(faceQuery) & _db.personUserEntity.isHidden.equals(false));
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
return query.map((row) => mapToPerson(row.readTable(_db.personEntity), row.readTable(_db.personUserEntity))).get();
|
||||
}
|
||||
|
||||
Future<List<DriftPerson>> getAllPeople({int minFaces = 3}) async {
|
||||
Future<List<DriftPerson>> getAllPeople({int minFaces = 3}) {
|
||||
final people = _db.personEntity;
|
||||
final personUsers = _db.personUserEntity;
|
||||
final faces = _db.assetFaceEntity;
|
||||
final assets = _db.remoteAssetEntity;
|
||||
|
||||
final query =
|
||||
_db.select(people).join([
|
||||
innerJoin(personUsers, personUsers.personId.equalsExp(people.id)),
|
||||
innerJoin(faces, faces.personId.equalsExp(people.id)),
|
||||
innerJoin(assets, assets.id.equalsExp(faces.assetId)),
|
||||
])
|
||||
..where(
|
||||
people.isHidden.equals(false) &
|
||||
personUsers.isHidden.equals(false) &
|
||||
assets.deletedAt.isNull() &
|
||||
assets.visibility.equalsValue(AssetVisibility.timeline) &
|
||||
faces.isVisible.equals(true) &
|
||||
|
|
@ -56,10 +68,7 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
|
|||
OrderingTerm(expression: faces.id.count(), mode: OrderingMode.desc),
|
||||
]);
|
||||
|
||||
return query.map((row) {
|
||||
final person = row.readTable(people);
|
||||
return person.toDto();
|
||||
}).get();
|
||||
return query.map((row) => mapToPerson(row.readTable(_db.personEntity), row.readTable(_db.personUserEntity))).get();
|
||||
}
|
||||
|
||||
Future<int> updateName(String personId, String name) {
|
||||
|
|
@ -74,20 +83,3 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
|
|||
return query.write(PersonEntityCompanion(birthDate: Value(birthday), updatedAt: Value(DateTime.now())));
|
||||
}
|
||||
}
|
||||
|
||||
extension on PersonEntityData {
|
||||
DriftPerson toDto() {
|
||||
return DriftPerson(
|
||||
id: id,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
ownerId: ownerId,
|
||||
name: name,
|
||||
faceAssetId: faceAssetId,
|
||||
isFavorite: isFavorite,
|
||||
isHidden: isHidden,
|
||||
color: color,
|
||||
birthDate: birthDate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,10 @@ class SyncApiRepository {
|
|||
SyncRequestType.stacksV1,
|
||||
SyncRequestType.partnerStacksV1,
|
||||
SyncRequestType.userMetadataV1,
|
||||
SyncRequestType.peopleV1,
|
||||
serverVersion >= const SemVer(major: 3, minor: 2, patch: 0)
|
||||
? SyncRequestType.peopleV2
|
||||
: SyncRequestType.peopleV1,
|
||||
if (serverVersion >= const SemVer(major: 3, minor: 2, patch: 0)) SyncRequestType.personUsersV1,
|
||||
serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)
|
||||
? SyncRequestType.assetFacesV2
|
||||
: SyncRequestType.assetFacesV1,
|
||||
|
|
@ -201,7 +204,10 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||
SyncEntityType.userMetadataV1: SyncUserMetadataV1.fromJson,
|
||||
SyncEntityType.userMetadataDeleteV1: SyncUserMetadataDeleteV1.fromJson,
|
||||
SyncEntityType.personV1: SyncPersonV1.fromJson,
|
||||
SyncEntityType.personV2: SyncPersonV2.fromJson,
|
||||
SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson,
|
||||
SyncEntityType.personUserV1: SyncPersonUserV1.fromJson,
|
||||
SyncEntityType.personUserDeleteV1: SyncPersonUserDeleteV1.fromJson,
|
||||
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
|
||||
SyncEntityType.assetFaceV2: SyncAssetFaceV2.fromJson,
|
||||
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
|
|||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person_user.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';
|
||||
|
|
@ -59,6 +60,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||
await _db.memoryEntity.deleteAll();
|
||||
await _db.partnerEntity.deleteAll();
|
||||
await _db.personEntity.deleteAll();
|
||||
await _db.personUserEntity.deleteAll();
|
||||
await _db.remoteAlbumAssetEntity.deleteAll();
|
||||
await _db.remoteAlbumEntity.deleteAll();
|
||||
await _db.remoteAlbumUserEntity.deleteAll();
|
||||
|
|
@ -736,17 +738,55 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||
}
|
||||
|
||||
Future<void> updatePeopleV1(Iterable<SyncPersonV1> data) async {
|
||||
try {
|
||||
await _db.transaction(() async {
|
||||
await _db.batch((batch) {
|
||||
for (final person in data) {
|
||||
final companion = PersonEntityCompanion(
|
||||
createdAt: Value(person.createdAt),
|
||||
updatedAt: Value(person.updatedAt),
|
||||
name: Value(person.name),
|
||||
color: Value(person.color),
|
||||
birthDate: Value(person.birthDate),
|
||||
);
|
||||
|
||||
batch.insert(
|
||||
_db.personEntity,
|
||||
companion.copyWith(id: Value(person.id)),
|
||||
onConflict: DoUpdate((_) => companion),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await _db.batch((batch) {
|
||||
for (final person in data) {
|
||||
final companion = PersonUserEntityCompanion(
|
||||
personId: Value(person.id),
|
||||
ownerId: Value(person.ownerId),
|
||||
isFavorite: Value(person.isFavorite),
|
||||
isHidden: Value(person.isHidden),
|
||||
createdAt: Value(person.createdAt),
|
||||
updatedAt: Value(person.updatedAt),
|
||||
);
|
||||
|
||||
batch.insert(_db.personUserEntity, companion, onConflict: DoUpdate((_) => companion));
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updatePeopleV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updatePeopleV2(Iterable<SyncPersonV2> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
for (final person in data) {
|
||||
final companion = PersonEntityCompanion(
|
||||
createdAt: Value(person.createdAt),
|
||||
updatedAt: Value(person.updatedAt),
|
||||
ownerId: Value(person.ownerId),
|
||||
name: Value(person.name),
|
||||
faceAssetId: Value(person.faceAssetId),
|
||||
isFavorite: Value(person.isFavorite),
|
||||
isHidden: Value(person.isHidden),
|
||||
color: Value(person.color),
|
||||
birthDate: Value(person.birthDate),
|
||||
);
|
||||
|
|
@ -759,7 +799,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updatePeopleV1', error, stack);
|
||||
_logger.severe('Error: updatePeopleV2', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
@ -777,6 +817,44 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> updatePersonUsersV1(Iterable<SyncPersonUserV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
for (final personUser in data) {
|
||||
final companion = PersonUserEntityCompanion(
|
||||
personId: Value(personUser.personId),
|
||||
ownerId: Value(personUser.ownerId),
|
||||
isFavorite: Value(personUser.isFavorite),
|
||||
isHidden: Value(personUser.isHidden),
|
||||
createdAt: Value(personUser.createdAt),
|
||||
updatedAt: Value(personUser.updatedAt),
|
||||
);
|
||||
|
||||
batch.insert(_db.personUserEntity, companion, onConflict: DoUpdate((_) => companion));
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: updatePersonUsersV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deletePersonUsersV1(Iterable<SyncPersonUserDeleteV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
for (final personUser in data) {
|
||||
batch.deleteWhere(
|
||||
_db.personUserEntity,
|
||||
(row) => row.personId.equals(personUser.personId) & row.ownerId.equals(personUser.ownerId),
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deletePersonUsersV1', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateAssetFacesV1(Iterable<SyncAssetFaceV1> data) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,15 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_dev/api/migrations_native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person_user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
import '../../unit/factories/person_factory.dart';
|
||||
import '../../unit/factories/user_factory.dart';
|
||||
import 'generated/schema.dart';
|
||||
import 'generated/schema_v1.dart' as v1;
|
||||
import 'generated/schema_v2.dart' as v2;
|
||||
import 'generated/schema_v31.dart' as v31;
|
||||
|
||||
void main() {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
|
|
@ -35,4 +39,62 @@ void main() {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
group('data migrations', () {
|
||||
test(
|
||||
'v31 to v32 rebuilds person_user from the dropped person columns',
|
||||
() async {
|
||||
final schema = await verifier.schemaAt(31);
|
||||
final oldDb = v31.DatabaseAtV31(schema.newConnection());
|
||||
final user = UserFactory.create();
|
||||
final person = PersonFactory.create();
|
||||
|
||||
await oldDb
|
||||
.into(oldDb.userEntity)
|
||||
.insert(
|
||||
v31.UserEntityCompanion.insert(
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
profileChangedAt: Value(
|
||||
user.profileChangedAt.toIso8601String(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await oldDb
|
||||
.into(oldDb.personEntity)
|
||||
.insert(
|
||||
v31.PersonEntityCompanion.insert(
|
||||
id: person.id,
|
||||
ownerId: user.id,
|
||||
name: person.name,
|
||||
isFavorite: person.isFavorite ? 1 : 0,
|
||||
isHidden: person.isHidden ? 1 : 0,
|
||||
createdAt: Value(person.createdAt.toIso8601String()),
|
||||
updatedAt: Value(person.updatedAt.toIso8601String()),
|
||||
),
|
||||
);
|
||||
await oldDb.close();
|
||||
|
||||
final db = Drift(schema.newConnection());
|
||||
await verifier.migrateAndValidate(db, 32);
|
||||
|
||||
final personUsers = await db.select(db.personUserEntity).get();
|
||||
expect(personUsers, hasLength(1));
|
||||
expect(
|
||||
personUsers.single,
|
||||
isA<PersonUserEntityData>()
|
||||
.having((row) => row.personId, 'personId', person.id)
|
||||
.having((row) => row.ownerId, 'ownerId', user.id)
|
||||
.having((row) => row.isFavorite, 'isFavorite', person.isFavorite)
|
||||
.having((row) => row.isHidden, 'isHidden', person.isHidden),
|
||||
);
|
||||
|
||||
final people = await db.select(db.personEntity).get();
|
||||
expect(people.single.name, person.name);
|
||||
|
||||
await db.close();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
// drift also exports isNotNull, which collides with the matcher of the same name
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person_user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/people.repository.dart';
|
||||
|
||||
import '../repository_context.dart';
|
||||
|
|
@ -74,4 +77,36 @@ void main() {
|
|||
expect(people, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('updatedAt', () {
|
||||
test('follows person_user when the thumbnail changed more recently', () async {
|
||||
final user = await ctx.newUser();
|
||||
final person = await ctx.newPerson(ownerId: user.id);
|
||||
final thumbnailChangedAt = DateTime.now().add(const Duration(days: 1));
|
||||
|
||||
await (ctx.db.update(ctx.db.personUserEntity)..where((row) => row.personId.equals(person.id))).write(
|
||||
PersonUserEntityCompanion(updatedAt: Value(thumbnailChangedAt)),
|
||||
);
|
||||
|
||||
final result = await sut.get(person.id);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.updatedAt, thumbnailChangedAt);
|
||||
});
|
||||
});
|
||||
|
||||
group('person user', () {
|
||||
test('reads the user flags from person_user', () async {
|
||||
final user = await ctx.newUser();
|
||||
final asset = await ctx.newRemoteAsset(ownerId: user.id);
|
||||
final person = await ctx.newPerson(ownerId: user.id, isFavorite: true);
|
||||
await ctx.newFace(assetId: asset.id, personId: person.id);
|
||||
|
||||
final result = await sut.get(person.id);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.isFavorite, isTrue);
|
||||
expect(result.isHidden, isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
|
|||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/person_user.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';
|
||||
|
|
@ -220,19 +221,29 @@ class MediumRepositoryContext {
|
|||
.insert(RemoteAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId)));
|
||||
}
|
||||
|
||||
Future<PersonEntityData> newPerson({String? id, String? ownerId, String? name, bool? isFavorite, bool? isHidden}) {
|
||||
Future<PersonEntityData> newPerson({
|
||||
String? id,
|
||||
String? ownerId,
|
||||
String? name,
|
||||
bool? isFavorite,
|
||||
bool? isHidden,
|
||||
}) async {
|
||||
id ??= TestUtils.uuid();
|
||||
return db
|
||||
final person = await db
|
||||
.into(db.personEntity)
|
||||
.insertReturning(
|
||||
PersonEntityCompanion(
|
||||
id: .new(id),
|
||||
.insertReturning(PersonEntityCompanion(id: .new(id), name: .new(name ?? 'person_$id')));
|
||||
await db
|
||||
.into(db.personUserEntity)
|
||||
.insert(
|
||||
PersonUserEntityCompanion(
|
||||
personId: .new(person.id),
|
||||
ownerId: .new(TestUtils.uuid(ownerId)),
|
||||
name: .new(name ?? 'person_$id'),
|
||||
isFavorite: .new(isFavorite ?? false),
|
||||
isHidden: .new(isHidden ?? false),
|
||||
),
|
||||
);
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
Future<AssetFaceEntityData> newFace({String? assetId, String? personId, int? imageWidth, int? imageHeight}) {
|
||||
|
|
|
|||
19
mobile/test/unit/factories/person_factory.dart
Normal file
19
mobile/test/unit/factories/person_factory.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
|
||||
import '../../utils.dart';
|
||||
|
||||
class PersonFactory {
|
||||
static DriftPerson create({String? id, String? name}) {
|
||||
id ??= TestUtils.uuid();
|
||||
name ??= 'person_$id';
|
||||
return DriftPerson(
|
||||
id: id,
|
||||
name: name,
|
||||
createdAt: TestUtils.date(),
|
||||
updatedAt: TestUtils.date(),
|
||||
isFavorite: TestUtils.randDouble() > 0.5,
|
||||
isHidden: TestUtils.randDouble() > 0.5,
|
||||
birthDate: TestUtils.date(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue