mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
feat: sync AuthUserV1 (#21565)
* feat: sync AuthUserV1 * migration * chore: fix analyze * fix user updatedAt check * fix: auth user sync query * generate sql * bump schema version and update migration --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Jason Rasmussen <jason@rasm.me> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
6a55c36762
commit
059a0e8aa8
25 changed files with 158 additions and 80 deletions
BIN
mobile/drift_schemas/main/drift_schema_v10.json
generated
Normal file
BIN
mobile/drift_schemas/main/drift_schema_v10.json
generated
Normal file
Binary file not shown.
|
|
@ -1,7 +1,36 @@
|
||||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
enum AvatarColor {
|
||||||
|
// do not change this order or reuse indices for other purposes, adding is OK
|
||||||
|
primary("primary"),
|
||||||
|
pink("pink"),
|
||||||
|
red("red"),
|
||||||
|
yellow("yellow"),
|
||||||
|
blue("blue"),
|
||||||
|
green("green"),
|
||||||
|
purple("purple"),
|
||||||
|
orange("orange"),
|
||||||
|
gray("gray"),
|
||||||
|
amber("amber");
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
const AvatarColor(this.value);
|
||||||
|
|
||||||
|
Color toColor({bool isDarkTheme = false}) => switch (this) {
|
||||||
|
AvatarColor.primary => isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
||||||
|
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
|
||||||
|
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
|
||||||
|
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
|
||||||
|
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
|
||||||
|
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
|
||||||
|
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
|
||||||
|
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
|
||||||
|
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
|
||||||
|
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Rename to User once Isar is removed
|
// TODO: Rename to User once Isar is removed
|
||||||
class UserDto {
|
class UserDto {
|
||||||
|
|
@ -9,7 +38,7 @@ class UserDto {
|
||||||
final String email;
|
final String email;
|
||||||
final String name;
|
final String name;
|
||||||
final bool isAdmin;
|
final bool isAdmin;
|
||||||
final DateTime updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
final AvatarColor avatarColor;
|
final AvatarColor avatarColor;
|
||||||
|
|
||||||
|
|
@ -31,8 +60,8 @@ class UserDto {
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.email,
|
required this.email,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.isAdmin,
|
this.isAdmin = false,
|
||||||
required this.updatedAt,
|
this.updatedAt,
|
||||||
required this.profileChangedAt,
|
required this.profileChangedAt,
|
||||||
this.avatarColor = AvatarColor.primary,
|
this.avatarColor = AvatarColor.primary,
|
||||||
this.memoryEnabled = true,
|
this.memoryEnabled = true,
|
||||||
|
|
@ -99,7 +128,8 @@ profileChangedAt: $profileChangedAt
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other.id == id &&
|
return other.id == id &&
|
||||||
other.updatedAt.isAtSameMomentAs(updatedAt) &&
|
((updatedAt == null && other.updatedAt == null) ||
|
||||||
|
(updatedAt != null && other.updatedAt != null && other.updatedAt!.isAtSameMomentAs(updatedAt!))) &&
|
||||||
other.avatarColor == avatarColor &&
|
other.avatarColor == avatarColor &&
|
||||||
other.email == email &&
|
other.email == email &&
|
||||||
other.name == name &&
|
other.name == name &&
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import 'dart:ui';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
enum UserMetadataKey {
|
enum UserMetadataKey {
|
||||||
// do not change this order!
|
// do not change this order!
|
||||||
|
|
@ -7,36 +7,6 @@ enum UserMetadataKey {
|
||||||
license,
|
license,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AvatarColor {
|
|
||||||
// do not change this order or reuse indices for other purposes, adding is OK
|
|
||||||
primary("primary"),
|
|
||||||
pink("pink"),
|
|
||||||
red("red"),
|
|
||||||
yellow("yellow"),
|
|
||||||
blue("blue"),
|
|
||||||
green("green"),
|
|
||||||
purple("purple"),
|
|
||||||
orange("orange"),
|
|
||||||
gray("gray"),
|
|
||||||
amber("amber");
|
|
||||||
|
|
||||||
final String value;
|
|
||||||
const AvatarColor(this.value);
|
|
||||||
|
|
||||||
Color toColor({bool isDarkTheme = false}) => switch (this) {
|
|
||||||
AvatarColor.primary => isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
|
||||||
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
|
|
||||||
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
|
|
||||||
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
|
|
||||||
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
|
|
||||||
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
|
|
||||||
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
|
|
||||||
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
|
|
||||||
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
|
|
||||||
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class Onboarding {
|
class Onboarding {
|
||||||
final bool isOnboarded;
|
final bool isOnboarded;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ class SyncStreamService {
|
||||||
Future<void> _handleSyncData(SyncEntityType type, Iterable<Object> data) async {
|
Future<void> _handleSyncData(SyncEntityType type, Iterable<Object> data) async {
|
||||||
_logger.fine("Processing sync data for $type of length ${data.length}");
|
_logger.fine("Processing sync data for $type of length ${data.length}");
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case SyncEntityType.authUserV1:
|
||||||
|
return _syncStreamRepository.updateAuthUsersV1(data.cast());
|
||||||
case SyncEntityType.userV1:
|
case SyncEntityType.userV1:
|
||||||
return _syncStreamRepository.updateUsersV1(data.cast());
|
return _syncStreamRepository.updateUsersV1(data.cast());
|
||||||
case SyncEntityType.userDeleteV1:
|
case SyncEntityType.userDeleteV1:
|
||||||
|
|
|
||||||
27
mobile/lib/infrastructure/entities/auth_user.entity.dart
Normal file
27
mobile/lib/infrastructure/entities/auth_user.entity.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class AuthUserEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const AuthUserEntity();
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
TextColumn get email => text()();
|
||||||
|
BoolColumn get isAdmin => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
|
// Profile image
|
||||||
|
BoolColumn get hasProfileImage => boolean().withDefault(const Constant(false))();
|
||||||
|
DateTimeColumn get profileChangedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
IntColumn get avatarColor => intEnum<AvatarColor>()();
|
||||||
|
|
||||||
|
// Quota
|
||||||
|
IntColumn get quotaSizeInBytes => integer().withDefault(const Constant(0))();
|
||||||
|
IntColumn get quotaUsageInBytes => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
|
// Locked Folder
|
||||||
|
TextColumn get pinCode => text().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
||||||
BIN
mobile/lib/infrastructure/entities/auth_user.entity.drift.dart
generated
Normal file
BIN
mobile/lib/infrastructure/entities/auth_user.entity.drift.dart
generated
Normal file
Binary file not shown.
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:drift/drift.dart' hide Index;
|
import 'package:drift/drift.dart' hide Index;
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
import 'package:immich_mobile/utils/hash.dart';
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
@ -44,7 +43,7 @@ class User {
|
||||||
|
|
||||||
static User fromDto(UserDto dto) => User(
|
static User fromDto(UserDto dto) => User(
|
||||||
id: dto.id,
|
id: dto.id,
|
||||||
updatedAt: dto.updatedAt,
|
updatedAt: dto.updatedAt ?? DateTime(2025),
|
||||||
email: dto.email,
|
email: dto.email,
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
isAdmin: dto.isAdmin,
|
isAdmin: dto.isAdmin,
|
||||||
|
|
@ -81,13 +80,12 @@ class UserEntity extends Table with DriftDefaultsMixin {
|
||||||
|
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
BoolColumn get isAdmin => boolean().withDefault(const Constant(false))();
|
|
||||||
TextColumn get email => text()();
|
TextColumn get email => text()();
|
||||||
|
|
||||||
|
// Profile image
|
||||||
BoolColumn get hasProfileImage => boolean().withDefault(const Constant(false))();
|
BoolColumn get hasProfileImage => boolean().withDefault(const Constant(false))();
|
||||||
DateTimeColumn get profileChangedAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get profileChangedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
IntColumn get avatarColor => intEnum<AvatarColor>().withDefault(const Constant(0))();
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -5,6 +5,7 @@ import 'package:drift_flutter/drift_flutter.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
|
||||||
|
|
@ -43,6 +44,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
||||||
|
|
||||||
@DriftDatabase(
|
@DriftDatabase(
|
||||||
tables: [
|
tables: [
|
||||||
|
AuthUserEntity,
|
||||||
UserEntity,
|
UserEntity,
|
||||||
UserMetadataEntity,
|
UserMetadataEntity,
|
||||||
PartnerEntity,
|
PartnerEntity,
|
||||||
|
|
@ -68,7 +70,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
||||||
: super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true)));
|
: super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true)));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 9;
|
int get schemaVersion => 10;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
|
|
@ -126,6 +128,11 @@ class Drift extends $Drift implements IDatabaseRepository {
|
||||||
from8To9: (m, v9) async {
|
from8To9: (m, v9) async {
|
||||||
await m.addColumn(v9.localAlbumEntity, v9.localAlbumEntity.linkedRemoteAlbumId);
|
await m.addColumn(v9.localAlbumEntity, v9.localAlbumEntity.linkedRemoteAlbumId);
|
||||||
},
|
},
|
||||||
|
from9To10: (m, v10) async {
|
||||||
|
await m.createTable(v10.authUserEntity);
|
||||||
|
await m.addColumn(v10.userEntity, v10.userEntity.avatarColor);
|
||||||
|
await m.alterTable(TableMigration(v10.userEntity));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -202,14 +202,13 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
isAdmin: user.isAdmin,
|
|
||||||
updatedAt: user.updatedAt,
|
|
||||||
memoryEnabled: true,
|
memoryEnabled: true,
|
||||||
inTimeline: false,
|
inTimeline: false,
|
||||||
isPartnerSharedBy: false,
|
isPartnerSharedBy: false,
|
||||||
isPartnerSharedWith: false,
|
isPartnerSharedWith: false,
|
||||||
profileChangedAt: user.profileChangedAt,
|
profileChangedAt: user.profileChangedAt,
|
||||||
hasProfileImage: user.hasProfileImage,
|
hasProfileImage: user.hasProfileImage,
|
||||||
|
avatarColor: user.avatarColor,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.get();
|
.get();
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepo
|
||||||
const (bool) => entity.intValue == 1,
|
const (bool) => entity.intValue == 1,
|
||||||
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||||
const (UserDto) =>
|
const (UserDto) =>
|
||||||
entity.stringValue == null ? null : await DriftUserRepository(_db).get(entity.stringValue!),
|
entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!),
|
||||||
_ => null,
|
_ => null,
|
||||||
}
|
}
|
||||||
as T?;
|
as T?;
|
||||||
|
|
@ -184,7 +184,7 @@ class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepo
|
||||||
const (String) => (null, value as String),
|
const (String) => (null, value as String),
|
||||||
const (bool) => ((value as bool) ? 1 : 0, null),
|
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||||
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||||
const (UserDto) => (null, (await DriftUserRepository(_db).upsert(value as UserDto)).id),
|
const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id),
|
||||||
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
||||||
};
|
};
|
||||||
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ class SyncApiRepository {
|
||||||
request.body = jsonEncode(
|
request.body = jsonEncode(
|
||||||
SyncStreamDto(
|
SyncStreamDto(
|
||||||
types: [
|
types: [
|
||||||
|
SyncRequestType.authUsersV1,
|
||||||
SyncRequestType.usersV1,
|
SyncRequestType.usersV1,
|
||||||
SyncRequestType.assetsV1,
|
SyncRequestType.assetsV1,
|
||||||
SyncRequestType.assetExifsV1,
|
SyncRequestType.assetExifsV1,
|
||||||
|
|
@ -133,6 +134,7 @@ class SyncApiRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
const _kResponseMap = <SyncEntityType, Function(Object)>{
|
const _kResponseMap = <SyncEntityType, Function(Object)>{
|
||||||
|
SyncEntityType.authUserV1: SyncAuthUserV1.fromJson,
|
||||||
SyncEntityType.userV1: SyncUserV1.fromJson,
|
SyncEntityType.userV1: SyncUserV1.fromJson,
|
||||||
SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson,
|
SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/memory.model.dart';
|
import 'package:immich_mobile/domain/models/memory.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
|
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/memory_asset.entity.drift.dart';
|
||||||
|
|
@ -59,6 +62,35 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateAuthUsersV1(Iterable<SyncAuthUserV1> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final user in data) {
|
||||||
|
final companion = AuthUserEntityCompanion(
|
||||||
|
name: Value(user.name),
|
||||||
|
email: Value(user.email),
|
||||||
|
hasProfileImage: Value(user.hasProfileImage),
|
||||||
|
profileChangedAt: Value(user.profileChangedAt),
|
||||||
|
avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary),
|
||||||
|
isAdmin: Value(user.isAdmin),
|
||||||
|
pinCode: Value(user.pinCode),
|
||||||
|
quotaSizeInBytes: Value(user.quotaSizeInBytes ?? 0),
|
||||||
|
quotaUsageInBytes: Value(user.quotaUsageInBytes),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.authUserEntity,
|
||||||
|
companion.copyWith(id: Value(user.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: SyncAuthUserV1', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
|
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
|
||||||
try {
|
try {
|
||||||
await _db.userEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.userId)));
|
await _db.userEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.userId)));
|
||||||
|
|
@ -77,6 +109,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||||
email: Value(user.email),
|
email: Value(user.email),
|
||||||
hasProfileImage: Value(user.hasProfileImage),
|
hasProfileImage: Value(user.hasProfileImage),
|
||||||
profileChangedAt: Value(user.profileChangedAt),
|
profileChangedAt: Value(user.profileChangedAt),
|
||||||
|
avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary),
|
||||||
);
|
);
|
||||||
|
|
||||||
batch.insert(_db.userEntity, companion.copyWith(id: Value(user.id)), onConflict: DoUpdate((_) => companion));
|
batch.insert(_db.userEntity, companion.copyWith(id: Value(user.id)), onConflict: DoUpdate((_) => companion));
|
||||||
|
|
@ -603,3 +636,7 @@ extension on String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension on UserAvatarColor {
|
||||||
|
AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == value);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity;
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity;
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
@ -68,12 +68,12 @@ class IsarUserRepository extends IsarDatabaseRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DriftUserRepository extends DriftDatabaseRepository {
|
class DriftAuthUserRepository extends DriftDatabaseRepository {
|
||||||
final Drift _db;
|
final Drift _db;
|
||||||
const DriftUserRepository(super.db) : _db = db;
|
const DriftAuthUserRepository(super.db) : _db = db;
|
||||||
|
|
||||||
Future<UserDto?> get(String id) async {
|
Future<UserDto?> get(String id) async {
|
||||||
final user = await _db.managers.userEntity.filter((user) => user.id.equals(id)).getSingleOrNull();
|
final user = await _db.managers.authUserEntity.filter((user) => user.id.equals(id)).getSingleOrNull();
|
||||||
|
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
|
|
||||||
|
|
@ -84,43 +84,30 @@ class DriftUserRepository extends DriftDatabaseRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserDto> upsert(UserDto user) async {
|
Future<UserDto> upsert(UserDto user) async {
|
||||||
await _db.userEntity.insertOnConflictUpdate(
|
await _db.authUserEntity.insertOnConflictUpdate(
|
||||||
UserEntityCompanion(
|
AuthUserEntityCompanion(
|
||||||
id: Value(user.id),
|
id: Value(user.id),
|
||||||
isAdmin: Value(user.isAdmin),
|
|
||||||
updatedAt: Value(user.updatedAt),
|
|
||||||
name: Value(user.name),
|
name: Value(user.name),
|
||||||
email: Value(user.email),
|
email: Value(user.email),
|
||||||
hasProfileImage: Value(user.hasProfileImage),
|
hasProfileImage: Value(user.hasProfileImage),
|
||||||
profileChangedAt: Value(user.profileChangedAt),
|
profileChangedAt: Value(user.profileChangedAt),
|
||||||
|
isAdmin: Value(user.isAdmin),
|
||||||
|
quotaSizeInBytes: Value(user.quotaSizeInBytes),
|
||||||
|
quotaUsageInBytes: Value(user.quotaUsageInBytes),
|
||||||
|
avatarColor: Value(user.avatarColor),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<UserDto>> getAll() async {
|
|
||||||
final users = await _db.userEntity.select().get();
|
|
||||||
final List<UserDto> result = [];
|
|
||||||
|
|
||||||
for (final user in users) {
|
|
||||||
final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(user.id));
|
|
||||||
final metadata = await query.map((row) => row.toDto()).get();
|
|
||||||
result.add(user.toDto(metadata));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
extension on AuthUserEntityData {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension on UserEntityData {
|
|
||||||
UserDto toDto([List<UserMetadata>? metadata]) {
|
UserDto toDto([List<UserMetadata>? metadata]) {
|
||||||
AvatarColor avatarColor = AvatarColor.primary;
|
|
||||||
bool memoryEnabled = true;
|
bool memoryEnabled = true;
|
||||||
|
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
for (final meta in metadata) {
|
for (final meta in metadata) {
|
||||||
if (meta.key == UserMetadataKey.preferences && meta.preferences != null) {
|
if (meta.key == UserMetadataKey.preferences && meta.preferences != null) {
|
||||||
avatarColor = meta.preferences?.userAvatarColor ?? AvatarColor.primary;
|
|
||||||
memoryEnabled = meta.preferences?.memoriesEnabled ?? true;
|
memoryEnabled = meta.preferences?.memoriesEnabled ?? true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,12 +117,13 @@ extension on UserEntityData {
|
||||||
id: id,
|
id: id,
|
||||||
email: email,
|
email: email,
|
||||||
name: name,
|
name: name,
|
||||||
isAdmin: isAdmin,
|
|
||||||
updatedAt: updatedAt,
|
|
||||||
profileChangedAt: profileChangedAt,
|
profileChangedAt: profileChangedAt,
|
||||||
hasProfileImage: hasProfileImage,
|
hasProfileImage: hasProfileImage,
|
||||||
avatarColor: avatarColor,
|
avatarColor: avatarColor,
|
||||||
memoryEnabled: memoryEnabled,
|
memoryEnabled: memoryEnabled,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
quotaSizeInBytes: quotaSizeInBytes,
|
||||||
|
quotaUsageInBytes: quotaUsageInBytes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
// TODO: Move to repository once all classes are refactored
|
// TODO: Move to repository once all classes are refactored
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
@ -26,11 +25,9 @@ final driftUsersProvider = FutureProvider.autoDispose<List<UserDto>>((ref) async
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
name: entity.name,
|
name: entity.name,
|
||||||
email: entity.email,
|
email: entity.email,
|
||||||
isAdmin: entity.isAdmin,
|
|
||||||
updatedAt: entity.updatedAt,
|
|
||||||
isPartnerSharedBy: false,
|
isPartnerSharedBy: false,
|
||||||
isPartnerSharedWith: false,
|
isPartnerSharedWith: false,
|
||||||
avatarColor: AvatarColor.primary,
|
avatarColor: entity.avatarColor,
|
||||||
memoryEnabled: true,
|
memoryEnabled: true,
|
||||||
inTimeline: true,
|
inTimeline: true,
|
||||||
profileChangedAt: entity.profileChangedAt,
|
profileChangedAt: entity.profileChangedAt,
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,9 @@ class SyncService {
|
||||||
dbUsers,
|
dbUsers,
|
||||||
compare: (UserDto a, UserDto b) => a.id.compareTo(b.id),
|
compare: (UserDto a, UserDto b) => a.id.compareTo(b.id),
|
||||||
both: (UserDto a, UserDto b) {
|
both: (UserDto a, UserDto b) {
|
||||||
if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) ||
|
if ((a.updatedAt == null && b.updatedAt != null) ||
|
||||||
|
(a.updatedAt != null && b.updatedAt == null) ||
|
||||||
|
(a.updatedAt != null && b.updatedAt != null && !a.updatedAt!.isAtSameMomentAs(b.updatedAt!)) ||
|
||||||
a.isPartnerSharedBy != b.isPartnerSharedBy ||
|
a.isPartnerSharedBy != b.isPartnerSharedBy ||
|
||||||
a.isPartnerSharedWith != b.isPartnerSharedWith ||
|
a.isPartnerSharedWith != b.isPartnerSharedWith ||
|
||||||
a.inTimeline != b.inTimeline) {
|
a.inTimeline != b.inTimeline) {
|
||||||
|
|
|
||||||
BIN
mobile/test/drift/main/generated/schema.dart
generated
BIN
mobile/test/drift/main/generated/schema.dart
generated
Binary file not shown.
BIN
mobile/test/drift/main/generated/schema_v10.dart
generated
Normal file
BIN
mobile/test/drift/main/generated/schema_v10.dart
generated
Normal file
Binary file not shown.
1
mobile/test/fixtures/user.stub.dart
vendored
1
mobile/test/fixtures/user.stub.dart
vendored
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
|
||||||
|
|
||||||
abstract final class UserStub {
|
abstract final class UserStub {
|
||||||
const UserStub._();
|
const UserStub._();
|
||||||
|
|
|
||||||
|
|
@ -591,6 +591,7 @@ from
|
||||||
where
|
where
|
||||||
"user"."updateId" < $1
|
"user"."updateId" < $1
|
||||||
and "user"."updateId" > $2
|
and "user"."updateId" > $2
|
||||||
|
and "id" = $3
|
||||||
order by
|
order by
|
||||||
"user"."updateId" asc
|
"user"."updateId" asc
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,7 @@ class AuthUserSync extends BaseSync {
|
||||||
return this.upsertQuery('user', options)
|
return this.upsertQuery('user', options)
|
||||||
.select(columns.syncUser)
|
.select(columns.syncUser)
|
||||||
.select(['isAdmin', 'pinCode', 'oauthId', 'storageLabel', 'quotaSizeInBytes', 'quotaUsageInBytes'])
|
.select(['isAdmin', 'pinCode', 'oauthId', 'storageLabel', 'quotaSizeInBytes', 'quotaUsageInBytes'])
|
||||||
|
.where('id', '=', options.userId)
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,4 +84,23 @@ describe(SyncEntityType.AuthUserV1, () => {
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should only sync the auth user', async () => {
|
||||||
|
const { auth, user, ctx } = await setup(await getKyselyDB());
|
||||||
|
|
||||||
|
await ctx.newUser();
|
||||||
|
|
||||||
|
const response = await ctx.syncStream(auth, [SyncRequestType.AuthUsersV1]);
|
||||||
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
id: user.id,
|
||||||
|
isAdmin: false,
|
||||||
|
}),
|
||||||
|
type: 'AuthUserV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue