mirror of https://github.com/immich-app/immich.git
refactor: user entity (#16655)
* refactor: user entity * fix: add users to album & user profile url * chore: rebase fixes * generate files * fix(mobile): timeline not reset on login * fix: test stub * refactor: rename user model (#16813) * refactor: rename user model * simplify import --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com> * chore: generate files * fix: use getAllAccessible instead of getAll --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>pull/16823/head
parent
a75718ce99
commit
d1c8fe5303
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
|
abstract interface class IUserRepository implements IDatabaseRepository {
|
||||||
|
Future<bool> insert(UserDto user);
|
||||||
|
|
||||||
|
Future<UserDto?> get(int id);
|
||||||
|
|
||||||
|
Future<UserDto?> getByUserId(String id);
|
||||||
|
|
||||||
|
Future<List<UserDto?>> getByUserIds(List<String> ids);
|
||||||
|
|
||||||
|
Future<List<UserDto>> getAll({SortUserBy? sortBy});
|
||||||
|
|
||||||
|
Future<bool> updateAll(List<UserDto> users);
|
||||||
|
|
||||||
|
Future<UserDto> update(UserDto user);
|
||||||
|
|
||||||
|
Future<void> delete(List<int> ids);
|
||||||
|
|
||||||
|
Future<void> deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SortUserBy { id }
|
||||||
@ -0,0 +1,157 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
|
|
||||||
|
enum AvatarColor {
|
||||||
|
// do not change this order or reuse indices for other purposes, adding is OK
|
||||||
|
primary,
|
||||||
|
pink,
|
||||||
|
red,
|
||||||
|
yellow,
|
||||||
|
blue,
|
||||||
|
green,
|
||||||
|
purple,
|
||||||
|
orange,
|
||||||
|
gray,
|
||||||
|
amber;
|
||||||
|
|
||||||
|
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
|
||||||
|
class UserDto {
|
||||||
|
final String uid;
|
||||||
|
final String email;
|
||||||
|
final String name;
|
||||||
|
final bool isAdmin;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
|
||||||
|
final String? profileImagePath;
|
||||||
|
final AvatarColor avatarColor;
|
||||||
|
|
||||||
|
final bool memoryEnabled;
|
||||||
|
final bool inTimeline;
|
||||||
|
|
||||||
|
final bool isPartnerSharedBy;
|
||||||
|
final bool isPartnerSharedWith;
|
||||||
|
|
||||||
|
final int quotaUsageInBytes;
|
||||||
|
final int quotaSizeInBytes;
|
||||||
|
|
||||||
|
int get id => fastHash(uid);
|
||||||
|
bool get hasQuota => quotaSizeInBytes > 0;
|
||||||
|
|
||||||
|
const UserDto({
|
||||||
|
required this.uid,
|
||||||
|
required this.email,
|
||||||
|
required this.name,
|
||||||
|
required this.isAdmin,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.profileImagePath,
|
||||||
|
this.avatarColor = AvatarColor.primary,
|
||||||
|
this.memoryEnabled = true,
|
||||||
|
this.inTimeline = false,
|
||||||
|
this.isPartnerSharedBy = false,
|
||||||
|
this.isPartnerSharedWith = false,
|
||||||
|
this.quotaUsageInBytes = 0,
|
||||||
|
this.quotaSizeInBytes = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''User: {
|
||||||
|
id: $id,
|
||||||
|
uid: $uid,
|
||||||
|
email: $email,
|
||||||
|
name: $name,
|
||||||
|
isAdmin: $isAdmin,
|
||||||
|
updatedAt: $updatedAt,
|
||||||
|
profileImagePath: ${profileImagePath ?? '<NA>'},
|
||||||
|
avatarColor: $avatarColor,
|
||||||
|
memoryEnabled: $memoryEnabled,
|
||||||
|
inTimeline: $inTimeline,
|
||||||
|
isPartnerSharedBy: $isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: $isPartnerSharedWith,
|
||||||
|
quotaUsageInBytes: $quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: $quotaSizeInBytes,
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDto copyWith({
|
||||||
|
String? uid,
|
||||||
|
String? email,
|
||||||
|
String? name,
|
||||||
|
bool? isAdmin,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
String? profileImagePath,
|
||||||
|
AvatarColor? avatarColor,
|
||||||
|
bool? memoryEnabled,
|
||||||
|
bool? inTimeline,
|
||||||
|
bool? isPartnerSharedBy,
|
||||||
|
bool? isPartnerSharedWith,
|
||||||
|
int? quotaUsageInBytes,
|
||||||
|
int? quotaSizeInBytes,
|
||||||
|
}) =>
|
||||||
|
UserDto(
|
||||||
|
uid: uid ?? this.uid,
|
||||||
|
email: email ?? this.email,
|
||||||
|
name: name ?? this.name,
|
||||||
|
isAdmin: isAdmin ?? this.isAdmin,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
profileImagePath: profileImagePath ?? this.profileImagePath,
|
||||||
|
avatarColor: avatarColor ?? this.avatarColor,
|
||||||
|
memoryEnabled: memoryEnabled ?? this.memoryEnabled,
|
||||||
|
inTimeline: inTimeline ?? this.inTimeline,
|
||||||
|
isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith,
|
||||||
|
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant UserDto other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.uid == uid &&
|
||||||
|
other.updatedAt.isAtSameMomentAs(updatedAt) &&
|
||||||
|
other.avatarColor == avatarColor &&
|
||||||
|
other.email == email &&
|
||||||
|
other.name == name &&
|
||||||
|
other.isPartnerSharedBy == isPartnerSharedBy &&
|
||||||
|
other.isPartnerSharedWith == isPartnerSharedWith &&
|
||||||
|
other.profileImagePath == profileImagePath &&
|
||||||
|
other.isAdmin == isAdmin &&
|
||||||
|
other.memoryEnabled == memoryEnabled &&
|
||||||
|
other.inTimeline == inTimeline &&
|
||||||
|
other.quotaUsageInBytes == quotaUsageInBytes &&
|
||||||
|
other.quotaSizeInBytes == quotaSizeInBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
uid.hashCode ^
|
||||||
|
name.hashCode ^
|
||||||
|
email.hashCode ^
|
||||||
|
updatedAt.hashCode ^
|
||||||
|
isAdmin.hashCode ^
|
||||||
|
profileImagePath.hashCode ^
|
||||||
|
avatarColor.hashCode ^
|
||||||
|
memoryEnabled.hashCode ^
|
||||||
|
inTimeline.hashCode ^
|
||||||
|
isPartnerSharedBy.hashCode ^
|
||||||
|
isPartnerSharedWith.hashCode ^
|
||||||
|
quotaUsageInBytes.hashCode ^
|
||||||
|
quotaSizeInBytes.hashCode;
|
||||||
|
}
|
||||||
@ -1,181 +0,0 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
|
||||||
import 'package:immich_mobile/utils/hash.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
|
|
||||||
part 'user.entity.g.dart';
|
|
||||||
|
|
||||||
@Collection(inheritance: false)
|
|
||||||
class User {
|
|
||||||
User({
|
|
||||||
required this.id,
|
|
||||||
required this.updatedAt,
|
|
||||||
required this.email,
|
|
||||||
required this.name,
|
|
||||||
required this.isAdmin,
|
|
||||||
this.isPartnerSharedBy = false,
|
|
||||||
this.isPartnerSharedWith = false,
|
|
||||||
this.profileImagePath = '',
|
|
||||||
this.avatarColor = AvatarColorEnum.primary,
|
|
||||||
this.memoryEnabled = true,
|
|
||||||
this.inTimeline = false,
|
|
||||||
this.quotaUsageInBytes = 0,
|
|
||||||
this.quotaSizeInBytes = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
Id get isarId => fastHash(id);
|
|
||||||
|
|
||||||
User.fromUserDto(
|
|
||||||
UserAdminResponseDto dto,
|
|
||||||
UserPreferencesResponseDto? preferences,
|
|
||||||
) : id = dto.id,
|
|
||||||
updatedAt = dto.updatedAt,
|
|
||||||
email = dto.email,
|
|
||||||
name = dto.name,
|
|
||||||
isPartnerSharedBy = false,
|
|
||||||
isPartnerSharedWith = false,
|
|
||||||
profileImagePath = dto.profileImagePath,
|
|
||||||
isAdmin = dto.isAdmin,
|
|
||||||
memoryEnabled = preferences?.memories.enabled ?? false,
|
|
||||||
avatarColor = dto.avatarColor.toAvatarColor(),
|
|
||||||
inTimeline = false,
|
|
||||||
quotaUsageInBytes = dto.quotaUsageInBytes ?? 0,
|
|
||||||
quotaSizeInBytes = dto.quotaSizeInBytes ?? 0;
|
|
||||||
|
|
||||||
User.fromPartnerDto(PartnerResponseDto dto)
|
|
||||||
: id = dto.id,
|
|
||||||
updatedAt = DateTime.now(),
|
|
||||||
email = dto.email,
|
|
||||||
name = dto.name,
|
|
||||||
isPartnerSharedBy = false,
|
|
||||||
isPartnerSharedWith = false,
|
|
||||||
profileImagePath = dto.profileImagePath,
|
|
||||||
isAdmin = false,
|
|
||||||
memoryEnabled = false,
|
|
||||||
avatarColor = dto.avatarColor.toAvatarColor(),
|
|
||||||
inTimeline = dto.inTimeline ?? false,
|
|
||||||
quotaUsageInBytes = 0,
|
|
||||||
quotaSizeInBytes = 0;
|
|
||||||
|
|
||||||
/// Base user dto used where the complete user object is not required
|
|
||||||
User.fromSimpleUserDto(UserResponseDto dto)
|
|
||||||
: id = dto.id,
|
|
||||||
email = dto.email,
|
|
||||||
name = dto.name,
|
|
||||||
profileImagePath = dto.profileImagePath,
|
|
||||||
avatarColor = dto.avatarColor.toAvatarColor(),
|
|
||||||
// Fill the remaining fields with placeholders
|
|
||||||
isAdmin = false,
|
|
||||||
inTimeline = false,
|
|
||||||
memoryEnabled = false,
|
|
||||||
isPartnerSharedBy = false,
|
|
||||||
isPartnerSharedWith = false,
|
|
||||||
updatedAt = DateTime.now(),
|
|
||||||
quotaUsageInBytes = 0,
|
|
||||||
quotaSizeInBytes = 0;
|
|
||||||
|
|
||||||
@Index(unique: true, replace: false, type: IndexType.hash)
|
|
||||||
String id;
|
|
||||||
DateTime updatedAt;
|
|
||||||
String email;
|
|
||||||
String name;
|
|
||||||
bool isPartnerSharedBy;
|
|
||||||
bool isPartnerSharedWith;
|
|
||||||
bool isAdmin;
|
|
||||||
String profileImagePath;
|
|
||||||
@Enumerated(EnumType.ordinal)
|
|
||||||
AvatarColorEnum avatarColor;
|
|
||||||
bool memoryEnabled;
|
|
||||||
bool inTimeline;
|
|
||||||
int quotaUsageInBytes;
|
|
||||||
int quotaSizeInBytes;
|
|
||||||
|
|
||||||
bool get hasQuota => quotaSizeInBytes > 0;
|
|
||||||
@Backlink(to: 'owner')
|
|
||||||
final IsarLinks<Album> albums = IsarLinks<Album>();
|
|
||||||
@Backlink(to: 'sharedUsers')
|
|
||||||
final IsarLinks<Album> sharedAlbums = IsarLinks<Album>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(other) {
|
|
||||||
if (other is! User) return false;
|
|
||||||
return id == other.id &&
|
|
||||||
updatedAt.isAtSameMomentAs(other.updatedAt) &&
|
|
||||||
avatarColor == other.avatarColor &&
|
|
||||||
email == other.email &&
|
|
||||||
name == other.name &&
|
|
||||||
isPartnerSharedBy == other.isPartnerSharedBy &&
|
|
||||||
isPartnerSharedWith == other.isPartnerSharedWith &&
|
|
||||||
profileImagePath == other.profileImagePath &&
|
|
||||||
isAdmin == other.isAdmin &&
|
|
||||||
memoryEnabled == other.memoryEnabled &&
|
|
||||||
inTimeline == other.inTimeline &&
|
|
||||||
quotaUsageInBytes == other.quotaUsageInBytes &&
|
|
||||||
quotaSizeInBytes == other.quotaSizeInBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@ignore
|
|
||||||
int get hashCode =>
|
|
||||||
id.hashCode ^
|
|
||||||
updatedAt.hashCode ^
|
|
||||||
email.hashCode ^
|
|
||||||
name.hashCode ^
|
|
||||||
isPartnerSharedBy.hashCode ^
|
|
||||||
isPartnerSharedWith.hashCode ^
|
|
||||||
profileImagePath.hashCode ^
|
|
||||||
avatarColor.hashCode ^
|
|
||||||
isAdmin.hashCode ^
|
|
||||||
memoryEnabled.hashCode ^
|
|
||||||
inTimeline.hashCode ^
|
|
||||||
quotaUsageInBytes.hashCode ^
|
|
||||||
quotaSizeInBytes.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AvatarColorEnum {
|
|
||||||
// do not change this order or reuse indices for other purposes, adding is OK
|
|
||||||
primary,
|
|
||||||
pink,
|
|
||||||
red,
|
|
||||||
yellow,
|
|
||||||
blue,
|
|
||||||
green,
|
|
||||||
purple,
|
|
||||||
orange,
|
|
||||||
gray,
|
|
||||||
amber,
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AvatarColorEnumHelper on UserAvatarColor {
|
|
||||||
AvatarColorEnum toAvatarColor() => switch (this) {
|
|
||||||
UserAvatarColor.primary => AvatarColorEnum.primary,
|
|
||||||
UserAvatarColor.pink => AvatarColorEnum.pink,
|
|
||||||
UserAvatarColor.red => AvatarColorEnum.red,
|
|
||||||
UserAvatarColor.yellow => AvatarColorEnum.yellow,
|
|
||||||
UserAvatarColor.blue => AvatarColorEnum.blue,
|
|
||||||
UserAvatarColor.green => AvatarColorEnum.green,
|
|
||||||
UserAvatarColor.purple => AvatarColorEnum.purple,
|
|
||||||
UserAvatarColor.orange => AvatarColorEnum.orange,
|
|
||||||
UserAvatarColor.gray => AvatarColorEnum.gray,
|
|
||||||
UserAvatarColor.amber => AvatarColorEnum.amber,
|
|
||||||
_ => AvatarColorEnum.primary,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AvatarColorToColorHelper on AvatarColorEnum {
|
|
||||||
Color toColor([bool isDarkTheme = false]) => switch (this) {
|
|
||||||
AvatarColorEnum.primary =>
|
|
||||||
isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
|
||||||
AvatarColorEnum.pink => const Color.fromARGB(255, 244, 114, 182),
|
|
||||||
AvatarColorEnum.red => const Color.fromARGB(255, 239, 68, 68),
|
|
||||||
AvatarColorEnum.yellow => const Color.fromARGB(255, 234, 179, 8),
|
|
||||||
AvatarColorEnum.blue => const Color.fromARGB(255, 59, 130, 246),
|
|
||||||
AvatarColorEnum.green => const Color.fromARGB(255, 22, 163, 74),
|
|
||||||
AvatarColorEnum.purple => const Color.fromARGB(255, 147, 51, 234),
|
|
||||||
AvatarColorEnum.orange => const Color.fromARGB(255, 234, 88, 12),
|
|
||||||
AvatarColorEnum.gray => const Color.fromARGB(255, 75, 85, 99),
|
|
||||||
AvatarColorEnum.amber => const Color.fromARGB(255, 217, 119, 6),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
part 'user.entity.g.dart';
|
||||||
|
|
||||||
|
@Collection(inheritance: false)
|
||||||
|
class User {
|
||||||
|
Id get isarId => fastHash(id);
|
||||||
|
@Index(unique: true, replace: false, type: IndexType.hash)
|
||||||
|
final String id;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final String email;
|
||||||
|
final String name;
|
||||||
|
final bool isPartnerSharedBy;
|
||||||
|
final bool isPartnerSharedWith;
|
||||||
|
final bool isAdmin;
|
||||||
|
final String profileImagePath;
|
||||||
|
@Enumerated(EnumType.ordinal)
|
||||||
|
final AvatarColor avatarColor;
|
||||||
|
final bool memoryEnabled;
|
||||||
|
final bool inTimeline;
|
||||||
|
final int quotaUsageInBytes;
|
||||||
|
final int quotaSizeInBytes;
|
||||||
|
|
||||||
|
const User({
|
||||||
|
required this.id,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.email,
|
||||||
|
required this.name,
|
||||||
|
required this.isAdmin,
|
||||||
|
this.isPartnerSharedBy = false,
|
||||||
|
this.isPartnerSharedWith = false,
|
||||||
|
this.profileImagePath = '',
|
||||||
|
this.avatarColor = AvatarColor.primary,
|
||||||
|
this.memoryEnabled = true,
|
||||||
|
this.inTimeline = false,
|
||||||
|
this.quotaUsageInBytes = 0,
|
||||||
|
this.quotaSizeInBytes = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
static User fromDto(UserDto dto) => User(
|
||||||
|
id: dto.uid,
|
||||||
|
updatedAt: dto.updatedAt,
|
||||||
|
email: dto.email,
|
||||||
|
name: dto.name,
|
||||||
|
isAdmin: dto.isAdmin,
|
||||||
|
isPartnerSharedBy: dto.isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: dto.isPartnerSharedWith,
|
||||||
|
profileImagePath: dto.profileImagePath ?? "",
|
||||||
|
avatarColor: dto.avatarColor,
|
||||||
|
memoryEnabled: dto.memoryEnabled,
|
||||||
|
inTimeline: dto.inTimeline,
|
||||||
|
quotaUsageInBytes: dto.quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: dto.quotaSizeInBytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
UserDto toDto() => UserDto(
|
||||||
|
uid: id,
|
||||||
|
email: email,
|
||||||
|
name: name,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
profileImagePath: profileImagePath.isEmpty ? null : profileImagePath,
|
||||||
|
avatarColor: avatarColor,
|
||||||
|
memoryEnabled: memoryEnabled,
|
||||||
|
inTimeline: inTimeline,
|
||||||
|
isPartnerSharedBy: isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: isPartnerSharedWith,
|
||||||
|
quotaUsageInBytes: quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: quotaSizeInBytes,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||||
|
as entity;
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
class IsarUserRepository extends IsarDatabaseRepository
|
||||||
|
implements IUserRepository {
|
||||||
|
final Isar _db;
|
||||||
|
const IsarUserRepository(super.db) : _db = db;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(List<int> ids) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.deleteAll(ids);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteAll() async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UserDto?> get(int id) async {
|
||||||
|
return (await _db.users.get(id))?.toDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
|
||||||
|
return (await _db.users
|
||||||
|
.where()
|
||||||
|
.optional(
|
||||||
|
sortBy != null,
|
||||||
|
(query) => switch (sortBy!) {
|
||||||
|
SortUserBy.id => query.sortById(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.findAll())
|
||||||
|
.map((u) => u.toDto())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UserDto?> getByUserId(String id) async {
|
||||||
|
return (await _db.users.getById(id))?.toDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<UserDto?>> getByUserIds(List<String> ids) async {
|
||||||
|
return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> insert(UserDto user) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.put(entity.User.fromDto(user));
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UserDto> update(UserDto user) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.put(entity.User.fromDto(user));
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> updateAll(List<UserDto> users) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.putAll(users.map(entity.User.fromDto).toList());
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
abstract final class UserConverter {
|
||||||
|
/// Base user dto used where the complete user object is not required
|
||||||
|
static UserDto fromSimpleUserDto(UserResponseDto dto) => UserDto(
|
||||||
|
uid: dto.id,
|
||||||
|
email: dto.email,
|
||||||
|
name: dto.name,
|
||||||
|
isAdmin: false,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
profileImagePath: dto.profileImagePath,
|
||||||
|
avatarColor: dto.avatarColor.toAvatarColor(),
|
||||||
|
);
|
||||||
|
|
||||||
|
static UserDto fromAdminDto(
|
||||||
|
UserAdminResponseDto adminDto, [
|
||||||
|
UserPreferencesResponseDto? preferenceDto,
|
||||||
|
]) =>
|
||||||
|
UserDto(
|
||||||
|
uid: adminDto.id,
|
||||||
|
email: adminDto.email,
|
||||||
|
name: adminDto.name,
|
||||||
|
isAdmin: adminDto.isAdmin,
|
||||||
|
updatedAt: adminDto.updatedAt,
|
||||||
|
profileImagePath: adminDto.profileImagePath,
|
||||||
|
avatarColor: adminDto.avatarColor.toAvatarColor(),
|
||||||
|
memoryEnabled: preferenceDto?.memories.enabled ?? true,
|
||||||
|
inTimeline: false,
|
||||||
|
isPartnerSharedBy: false,
|
||||||
|
isPartnerSharedWith: false,
|
||||||
|
quotaUsageInBytes: adminDto.quotaUsageInBytes ?? 0,
|
||||||
|
quotaSizeInBytes: adminDto.quotaSizeInBytes ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
static UserDto fromPartnerDto(PartnerResponseDto dto) => UserDto(
|
||||||
|
uid: dto.id,
|
||||||
|
email: dto.email,
|
||||||
|
name: dto.name,
|
||||||
|
isAdmin: false,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
profileImagePath: dto.profileImagePath,
|
||||||
|
avatarColor: dto.avatarColor.toAvatarColor(),
|
||||||
|
memoryEnabled: false,
|
||||||
|
inTimeline: dto.inTimeline ?? false,
|
||||||
|
isPartnerSharedBy: false,
|
||||||
|
isPartnerSharedWith: false,
|
||||||
|
quotaUsageInBytes: 0,
|
||||||
|
quotaSizeInBytes: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on UserAvatarColor {
|
||||||
|
AvatarColor toAvatarColor() => switch (this) {
|
||||||
|
UserAvatarColor.red => AvatarColor.red,
|
||||||
|
UserAvatarColor.green => AvatarColor.green,
|
||||||
|
UserAvatarColor.blue => AvatarColor.blue,
|
||||||
|
UserAvatarColor.purple => AvatarColor.purple,
|
||||||
|
UserAvatarColor.orange => AvatarColor.orange,
|
||||||
|
UserAvatarColor.pink => AvatarColor.pink,
|
||||||
|
UserAvatarColor.amber => AvatarColor.amber,
|
||||||
|
UserAvatarColor.yellow => AvatarColor.yellow,
|
||||||
|
UserAvatarColor.gray => AvatarColor.gray,
|
||||||
|
UserAvatarColor.primary || _ => AvatarColor.primary,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
abstract class IPartnerRepository {
|
abstract class IPartnerRepository {
|
||||||
Future<List<User>> getSharedWith();
|
Future<List<UserDto>> getSharedWith();
|
||||||
Future<List<User>> getSharedBy();
|
Future<List<UserDto>> getSharedBy();
|
||||||
Stream<List<User>> watchSharedWith();
|
Stream<List<UserDto>> watchSharedWith();
|
||||||
Stream<List<User>> watchSharedBy();
|
Stream<List<UserDto>> watchSharedBy();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/interfaces/database.interface.dart';
|
|
||||||
|
|
||||||
abstract interface class IUserRepository implements IDatabaseRepository {
|
|
||||||
Future<User?> get(String id);
|
|
||||||
|
|
||||||
Future<User?> getByDbId(int id);
|
|
||||||
|
|
||||||
Future<List<User>> getByIds(List<String> ids);
|
|
||||||
|
|
||||||
Future<List<User>> getAll({bool self = true, UserSort? sortBy});
|
|
||||||
|
|
||||||
/// Returns all users whose assets can be accessed (self+partners)
|
|
||||||
Future<List<User>> getAllAccessible();
|
|
||||||
|
|
||||||
Future<List<User>> upsertAll(List<User> users);
|
|
||||||
|
|
||||||
Future<User> update(User user);
|
|
||||||
|
|
||||||
Future<void> deleteById(List<int> ids);
|
|
||||||
|
|
||||||
Future<User> me();
|
|
||||||
|
|
||||||
Future<void> clearTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UserSort { id }
|
|
||||||
@ -1,9 +1,14 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/user.service.dart';
|
import 'package:immich_mobile/services/user.service.dart';
|
||||||
|
|
||||||
final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) {
|
final otherUsersProvider =
|
||||||
|
FutureProvider.autoDispose<List<UserDto>>((ref) async {
|
||||||
UserService userService = ref.watch(userServiceProvider);
|
UserService userService = ref.watch(userServiceProvider);
|
||||||
|
final currentUser = ref.watch(currentUserProvider);
|
||||||
|
|
||||||
return userService.getUsers();
|
final allUsers = await userService.getAll();
|
||||||
|
allUsers.removeWhere((u) => currentUser?.id == u.id);
|
||||||
|
return allUsers;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'store.provider.g.dart';
|
part 'store.provider.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@Riverpod(keepAlive: true)
|
||||||
IStoreRepository storeRepository(Ref ref) =>
|
IStoreRepository storeRepository(Ref ref) =>
|
||||||
IsarStoreRepository(ref.watch(isarProvider));
|
IsarStoreRepository(ref.watch(isarProvider));
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
StoreService storeService(Ref _) => StoreService.I;
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'user.provider.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
IUserRepository userRepository(Ref ref) =>
|
||||||
|
IsarUserRepository(ref.watch(isarProvider));
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'user.provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$userRepositoryHash() => r'1a2ac726bcc44397dcaecf449084fefd336696d4';
|
||||||
|
|
||||||
|
/// See also [userRepository].
|
||||||
|
@ProviderFor(userRepository)
|
||||||
|
final userRepositoryProvider = Provider<IUserRepository>.internal(
|
||||||
|
userRepository,
|
||||||
|
name: r'userRepositoryProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$userRepositoryHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef UserRepositoryRef = ProviderRef<IUserRepository>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
|
||||||
import 'package:immich_mobile/repositories/database.repository.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
|
|
||||||
final userRepositoryProvider =
|
|
||||||
Provider((ref) => UserRepository(ref.watch(dbProvider)));
|
|
||||||
|
|
||||||
class UserRepository extends DatabaseRepository implements IUserRepository {
|
|
||||||
UserRepository(super.db);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> getByIds(List<String> ids) async =>
|
|
||||||
(await db.users.getAllById(ids)).nonNulls.toList();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User?> get(String id) => db.users.getById(id);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> getAll({bool self = true, UserSort? sortBy}) {
|
|
||||||
final baseQuery = db.users.where();
|
|
||||||
final int userId = Store.get(StoreKey.currentUser).isarId;
|
|
||||||
final QueryBuilder<User, User, QAfterWhereClause> afterWhere =
|
|
||||||
self ? baseQuery.noOp() : baseQuery.isarIdNotEqualTo(userId);
|
|
||||||
final QueryBuilder<User, User, QAfterSortBy> query = switch (sortBy) {
|
|
||||||
null => afterWhere.noOp(),
|
|
||||||
UserSort.id => afterWhere.sortById(),
|
|
||||||
};
|
|
||||||
return query.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User> update(User user) async {
|
|
||||||
await txn(() => db.users.put(user));
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User> me() => Future.value(Store.get(StoreKey.currentUser));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteById(List<int> ids) => txn(() => db.users.deleteAll(ids));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> upsertAll(List<User> users) async {
|
|
||||||
await txn(() => db.users.putAll(users));
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> getAllAccessible() => db.users
|
|
||||||
.filter()
|
|
||||||
.isPartnerSharedWithEqualTo(true)
|
|
||||||
.or()
|
|
||||||
.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
|
||||||
.findAll();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User?> getByDbId(int id) async {
|
|
||||||
return await db.users.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> clearTable() async {
|
|
||||||
await txn(() async {
|
|
||||||
await db.users.clear();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +1,35 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
abstract final class UserStub {
|
abstract final class UserStub {
|
||||||
const UserStub._();
|
const UserStub._();
|
||||||
|
|
||||||
static final admin = User(
|
static final admin = UserDto(
|
||||||
id: "admin",
|
uid: "admin",
|
||||||
updatedAt: DateTime(2021),
|
|
||||||
email: "admin@test.com",
|
email: "admin@test.com",
|
||||||
name: "admin",
|
name: "admin",
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
profileImagePath: '',
|
updatedAt: DateTime(2021),
|
||||||
avatarColor: AvatarColorEnum.green,
|
profileImagePath: null,
|
||||||
|
avatarColor: AvatarColor.green,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final user1 = User(
|
static final user1 = UserDto(
|
||||||
id: "user1",
|
uid: "user1",
|
||||||
updatedAt: DateTime(2022),
|
|
||||||
email: "user1@test.com",
|
email: "user1@test.com",
|
||||||
name: "user1",
|
name: "user1",
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
profileImagePath: '',
|
updatedAt: DateTime(2022),
|
||||||
avatarColor: AvatarColorEnum.red,
|
profileImagePath: null,
|
||||||
|
avatarColor: AvatarColor.red,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final user2 = User(
|
static final user2 = UserDto(
|
||||||
id: "user2",
|
uid: "user2",
|
||||||
updatedAt: DateTime(2023),
|
|
||||||
email: "user2@test.com",
|
email: "user2@test.com",
|
||||||
name: "user2",
|
name: "user2",
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
profileImagePath: '',
|
updatedAt: DateTime(2023),
|
||||||
avatarColor: AvatarColorEnum.primary,
|
profileImagePath: null,
|
||||||
|
avatarColor: AvatarColor.primary,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockCurrentUserProvider extends StateNotifier<User?>
|
class MockCurrentUserProvider extends StateNotifier<UserDto?>
|
||||||
with Mock
|
with Mock
|
||||||
implements CurrentUserProvider {
|
implements CurrentUserProvider {
|
||||||
MockCurrentUserProvider() : super(null);
|
MockCurrentUserProvider() : super(null);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set state(User? user) => super.state = user;
|
set state(UserDto? user) => super.state = user;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue