Skip to content

Commit

Permalink
refactor: auth service and added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shenlong-tanwen committed Feb 16, 2025
1 parent 68daca2 commit b6f490a
Show file tree
Hide file tree
Showing 28 changed files with 425 additions and 131 deletions.
2 changes: 1 addition & 1 deletion mobile/lib/domain/interfaces/user.interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserRepository {
Future<User?> tryGet(String id);

Future<User?> update(User user);
Future<User> update(User user);
}
4 changes: 2 additions & 2 deletions mobile/lib/domain/models/user.model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class User {
this.profileImagePath = '',
this.memoryEnabled = true,
this.inTimeline = false,
required this.quotaSizeInBytes,
required this.quotaUsageInBytes,
this.quotaSizeInBytes = 0,
this.quotaUsageInBytes = 0,
this.isPartnerSharedBy = false,
this.isPartnerSharedWith = false,
});
Expand Down
41 changes: 21 additions & 20 deletions mobile/lib/domain/services/auth.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,50 @@ class AuthService {
User? _currentUser;
late final StreamController<User?> _userStreamController;

String? _currentUserId;
late final StreamSubscription<String?> _userIdStream;

AuthService({
required IStoreRepository storeRepo,
required IUserRepository userRepo,
}) : _storeRepo = storeRepo,
_userRepo = userRepo {
_userStreamController = StreamController.broadcast();
_userIdStream = _storeRepo.watch(StoreKey.currentUserId).listen((userId) {
if (_currentUserId == userId) {
return;
}
_currentUserId = userId;
unawaited(loadOfflineUser());
});
// Pre-load offline user
unawaited(_loadOfflineUser());
}

void _updateCurrentUser(User? user) {
void _notifyListeners(User? user) {
_currentUser = user;
_userStreamController.add(user);
}

User getCurrentUser() {
Future<void> _loadOfflineUser() async {
final userId = await _storeRepo.tryGet(StoreKey.currentUserId);
if (userId == null) {
_currentUser = null;
return;
}
final user = await _userRepo.tryGet(userId);
_notifyListeners(user);
}

User? tryGetUser() => _currentUser;

User getUser() {
if (_currentUser == null) {
throw const UserNotLoggedInException();
}
return _currentUser!;
}

User? tryGetCurrentUser() => _currentUser;

Stream<User?> watchCurrentUser() => _userStreamController.stream;
Stream<User?> watchUser() => _userStreamController.stream;

Future<User?> loadOfflineUser() async {
if (_currentUserId == null) return null;
final user = await _userRepo.tryGet(_currentUserId!);
_updateCurrentUser(user);
Future<User?> updateUser(User user) async {
await _userRepo.update(user);
await _storeRepo.update(StoreKey.currentUserId, user.id);
_notifyListeners(user);
return user;
}

Future<void> cleanup() async {
await _userIdStream.cancel();
await _userStreamController.close();
}
}
16 changes: 10 additions & 6 deletions mobile/lib/domain/services/store.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@ class StoreService {
return _instance!;
}

// TODO: Replace the implementation with the one from create after removing the typedef
/// Initializes the store with the given [storeRepository]
static Future<StoreService> init(IStoreRepository storeRepository) async {
if (_instance != null) {
return _instance!;
}
_instance = StoreService._(storeRepository);
await _instance!._populateCache();
_instance!._subscription = _instance!._listenForChange();
_instance ??= await create(storeRepository);
return _instance!;
}

/// Initializes the store with the given [storeRepository]
static Future<StoreService> create(IStoreRepository storeRepository) async {
final instance = StoreService._(storeRepository);
await instance._populateCache();
instance._subscription = instance._listenForChange();
return instance;
}

/// Fills the cache with the values from the DB
Future<void> _populateCache() async {
for (StoreKey key in StoreKey.values) {
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/domain/services/user.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ class UserService {

const UserService({required IUserRepository userRepo}) : _userRepo = userRepo;

Future<User?> updateUser(User user) => _userRepo.update(user);
Future<User> updateUser(User user) => _userRepo.update(user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class IsarUserRepository extends IsarDatabaseRepository
}

@override
Future<User?> update(User user) {
Future<User> update(User user) {
return nestTxn(() async {
await _db.users.put(user.toOldUser());
return user;
Expand Down
24 changes: 6 additions & 18 deletions mobile/lib/providers/auth.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/auth.service.dart' as nauth;
import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/models/auth/auth_state.model.dart';
import 'package:immich_mobile/models/auth/login_response.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/domain/auth.provider.dart';
import 'package:immich_mobile/providers/domain/user.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:immich_mobile/utils/hash.dart';
Expand All @@ -22,25 +20,19 @@ final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
ref.watch(authServiceProviderOld),
ref.watch(apiServiceProvider),
ref.watch(authServiceProvider),
ref.watch(userServiceProvider),
);
});

class AuthNotifier extends StateNotifier<AuthState> {
final AuthService _authService;
final ApiService _apiService;
final nauth.AuthService _nAuthService;
final UserService _userService;
final _log = Logger("AuthenticationNotifier");

static const Duration _timeoutDuration = Duration(seconds: 7);

AuthNotifier(
this._authService,
this._apiService,
this._nAuthService,
this._userService,
) : super(
AuthNotifier(this._authService, this._apiService, this._nAuthService)
: super(
AuthState(
deviceId: "",
userId: "",
Expand Down Expand Up @@ -115,8 +107,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
String deviceId =
Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;

_nAuthService.loadOfflineUser();
User? user = _nAuthService.tryGetCurrentUser()?.toOldUser();
User? user = _nAuthService.tryGetUser()?.toOldUser();

UserAdminResponseDto? userResponse;
UserPreferencesResponseDto? userPreferences;
Expand Down Expand Up @@ -155,13 +146,10 @@ class AuthNotifier extends StateNotifier<AuthState> {
if (userResponse != null) {
await Store.put(StoreKey.deviceId, deviceId);
await Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
user = (await _userService.updateUser(
final updatedUser = await _nAuthService.updateUser(
User.fromUserDto(userResponse, userPreferences).toDomain(),
))
?.toOldUser();
if (user != null) {
await Store.put(StoreKey.currentUserId, user.id);
}
);
user = updatedUser?.toOldUser();
await Store.put(StoreKey.accessToken, accessToken);
} else {
_log.severe("Unable to get user information from the server.");
Expand Down
23 changes: 6 additions & 17 deletions mobile/lib/providers/user.provider.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import 'dart:async';

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/auth.service.dart';
import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/domain/auth.provider.dart';
import 'package:immich_mobile/providers/domain/user.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';

class CurrentUserProvider extends StateNotifier<User?> {
CurrentUserProvider(this._apiService, this._authService, this._userService)
: super(null) {
state = _authService.tryGetCurrentUser()?.toOldUser();
CurrentUserProvider(this._apiService, this._authService) : super(null) {
state = _authService.tryGetUser()?.toOldUser();
streamSub = _authService
.watchCurrentUser()
.watchUser()
.map((user) => user?.toOldUser())
.listen((user) => state = user);
}

final ApiService _apiService;
final AuthService _authService;
final UserService _userService;

late final StreamSubscription<User?> streamSub;

// TODO: Move method this to AuthService
Expand All @@ -34,13 +29,8 @@ class CurrentUserProvider extends StateNotifier<User?> {
final user = await _apiService.usersApi.getMyUser();
if (user != null) {
final userPreferences = await _apiService.usersApi.getMyPreferences();
final updatedUser = (await _userService.updateUser(
User.fromUserDto(user, userPreferences).toDomain(),
))
?.toOldUser();
if (updatedUser != null) {
await Store.put(StoreKey.currentUserId, updatedUser.id);
}
await _authService
.updateUser(User.fromUserDto(user, userPreferences).toDomain());
}
} catch (_) {}
}
Expand All @@ -57,7 +47,6 @@ final currentUserProvider =
return CurrentUserProvider(
ref.watch(apiServiceProvider),
ref.watch(authServiceProvider),
ref.watch(userServiceProvider),
);
});

Expand Down
4 changes: 2 additions & 2 deletions mobile/lib/repositories/album.repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
if (shared != null) {
query = query.sharedEqualTo(shared);
}
final user = _authService.getCurrentUser().toOldUser();
final user = _authService.getUser().toOldUser();
if (owner == true) {
query = query.owner((q) => q.isarIdEqualTo(user.isarId));
} else if (owner == false) {
Expand Down Expand Up @@ -140,7 +140,7 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
String searchTerm,
QuickFilterMode filterMode,
) async {
final user = _authService.getCurrentUser().toOldUser();
final user = _authService.getUser().toOldUser();
var query = db.albums
.filter()
.nameContains(searchTerm, caseSensitive: false)
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/repositories/album_media.repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
shared: false,
activityEnabled: false,
);
album.owner.value = _authService.getCurrentUser().toOldUser();
album.owner.value = _authService.getUser().toOldUser();
album.localId = assetPathEntity.id;
album.isAll = assetPathEntity.isAll;
return album;
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/repositories/asset_media.repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class AssetMediaRepository implements IAssetMediaRepository {
final Asset asset = Asset(
checksum: "",
localId: local.id,
ownerId: authService.getCurrentUser().toOldUser().isarId,
ownerId: authService.getUser().toOldUser().isarId,
fileCreatedAt: local.createDateTime,
fileModifiedAt: local.modifiedDateTime,
updatedAt: local.modifiedDateTime,
Expand Down
6 changes: 3 additions & 3 deletions mobile/lib/repositories/user.repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class UserRepository extends DatabaseRepository implements IUserRepository {

@override
Future<List<User>> getAll({bool self = true, UserSort? sortBy}) {
final user = _authService.getCurrentUser().toOldUser();
final user = _authService.getUser().toOldUser();
final baseQuery = db.users.where();
final int userId = user.isarId;
final QueryBuilder<User, User, QAfterWhereClause> afterWhere =
Expand All @@ -48,7 +48,7 @@ class UserRepository extends DatabaseRepository implements IUserRepository {
}

@override
Future<User> me() => Future.value(_authService.getCurrentUser().toOldUser());
Future<User> me() => Future.value(_authService.getUser().toOldUser());

@override
Future<void> deleteById(List<int> ids) => txn(() => db.users.deleteAll(ids));
Expand All @@ -64,6 +64,6 @@ class UserRepository extends DatabaseRepository implements IUserRepository {
.filter()
.isPartnerSharedWithEqualTo(true)
.or()
.isarIdEqualTo(_authService.getCurrentUser().toOldUser().isarId)
.isarIdEqualTo(_authService.getUser().toOldUser().isarId)
.findAll();
}
20 changes: 6 additions & 14 deletions mobile/lib/routing/tab_navigation_observer.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/domain/user.provider.dart';
import 'package:immich_mobile/providers/domain/auth.provider.dart';
import 'package:immich_mobile/providers/memory.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';

class TabNavigationObserver extends AutoRouterObserver {
/// Riverpod Instance
final WidgetRef ref;

TabNavigationObserver({
required this.ref,
});
TabNavigationObserver({required this.ref});

@override
Future<void> didChangeTabRoute(
Expand All @@ -38,15 +34,11 @@ class TabNavigationObserver extends AutoRouterObserver {
return;
}

final user = (await ref.read(userServiceProvider).updateUser(
User.fromUserDto(userResponseDto, userPreferences).toDomain(),
))
?.toOldUser();
if (user != null) {
await Store.put(StoreKey.currentUserId, user.id);
}
await ref.read(authServiceProvider).updateUser(
User.fromUserDto(userResponseDto, userPreferences).toDomain(),
);

ref.read(serverInfoProvider.notifier).getServerVersion();
await ref.read(serverInfoProvider.notifier).getServerVersion();
} catch (e) {
debugPrint("Error refreshing user info $e");
}
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/services/album.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ class AlbumService {

Future<bool> deleteAlbum(Album album) async {
try {
final userId = _authService.getCurrentUser().toOldUser().isarId;
final userId = _authService.getUser().toOldUser().isarId;
if (album.owner.value?.isarId == userId) {
await _albumApiRepository.delete(album.remoteId!);
}
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/services/backup_verification.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class BackupVerificationService {

/// Returns at most [limit] assets that were backed up without exif
Future<List<Asset>> findWronglyBackedUpAssets({int limit = 100}) async {
final owner = _authService.getCurrentUser().toOldUser().isarId;
final owner = _authService.getUser().toOldUser().isarId;
final List<Asset> onlyLocal = await _assetRepository.getAll(
ownerId: owner,
state: AssetState.local,
Expand Down
Loading

0 comments on commit b6f490a

Please sign in to comment.