mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	* refactor: user entity * chore: rebase fixes * refactor: remove int user Id * refactor: migrate store userId from int to string * refactor: rename uid to id * feat: drift * pr feedback * refactor: move common overrides to mixin * refactor: remove int user Id * refactor: migrate store userId from int to string * refactor: rename uid to id * feat: user & partner sync stream * pr changes * refactor: sync service and add tests * chore: remove generated change * chore: move sync model * rebase: convert string ids to byte uuids * rebase * add processing logs * batch db calls * rewrite isolate manager * rewrite with worker_manager * misc fixes * add sync order test --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
		
			
				
	
	
		
			205 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'dart:async';
 | 
						|
import 'dart:io';
 | 
						|
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:immich_mobile/domain/models/store.model.dart';
 | 
						|
import 'package:immich_mobile/domain/utils/background_sync.dart';
 | 
						|
import 'package:immich_mobile/entities/store.entity.dart';
 | 
						|
import 'package:immich_mobile/interfaces/auth.interface.dart';
 | 
						|
import 'package:immich_mobile/interfaces/auth_api.interface.dart';
 | 
						|
import 'package:immich_mobile/models/auth/auxilary_endpoint.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/background_sync.provider.dart';
 | 
						|
import 'package:immich_mobile/repositories/auth.repository.dart';
 | 
						|
import 'package:immich_mobile/repositories/auth_api.repository.dart';
 | 
						|
import 'package:immich_mobile/services/api.service.dart';
 | 
						|
import 'package:immich_mobile/services/network.service.dart';
 | 
						|
import 'package:logging/logging.dart';
 | 
						|
import 'package:openapi/api.dart';
 | 
						|
 | 
						|
final authServiceProvider = Provider(
 | 
						|
  (ref) => AuthService(
 | 
						|
    ref.watch(authApiRepositoryProvider),
 | 
						|
    ref.watch(authRepositoryProvider),
 | 
						|
    ref.watch(apiServiceProvider),
 | 
						|
    ref.watch(networkServiceProvider),
 | 
						|
    ref.watch(backgroundSyncProvider),
 | 
						|
  ),
 | 
						|
);
 | 
						|
 | 
						|
class AuthService {
 | 
						|
  final IAuthApiRepository _authApiRepository;
 | 
						|
  final IAuthRepository _authRepository;
 | 
						|
  final ApiService _apiService;
 | 
						|
  final NetworkService _networkService;
 | 
						|
  final BackgroundSyncManager _backgroundSyncManager;
 | 
						|
 | 
						|
  final _log = Logger("AuthService");
 | 
						|
 | 
						|
  AuthService(
 | 
						|
    this._authApiRepository,
 | 
						|
    this._authRepository,
 | 
						|
    this._apiService,
 | 
						|
    this._networkService,
 | 
						|
    this._backgroundSyncManager,
 | 
						|
  );
 | 
						|
 | 
						|
  /// Validates the provided server URL by resolving and setting the endpoint.
 | 
						|
  /// Also sets the device info header and stores the valid URL.
 | 
						|
  ///
 | 
						|
  /// [url] - The server URL to be validated.
 | 
						|
  ///
 | 
						|
  /// Returns the validated and resolved server URL as a [String].
 | 
						|
  ///
 | 
						|
  /// Throws an exception if the URL cannot be resolved or set.
 | 
						|
  Future<String> validateServerUrl(String url) async {
 | 
						|
    final validUrl = await _apiService.resolveAndSetEndpoint(url);
 | 
						|
    await _apiService.setDeviceInfoHeader();
 | 
						|
    Store.put(StoreKey.serverUrl, validUrl);
 | 
						|
 | 
						|
    return validUrl;
 | 
						|
  }
 | 
						|
 | 
						|
  Future<bool> validateAuxilaryServerUrl(String url) async {
 | 
						|
    final httpclient = HttpClient();
 | 
						|
    bool isValid = false;
 | 
						|
 | 
						|
    try {
 | 
						|
      final uri = Uri.parse('$url/users/me');
 | 
						|
      final request = await httpclient.getUrl(uri);
 | 
						|
 | 
						|
      // add auth token + any configured custom headers
 | 
						|
      final customHeaders = ApiService.getRequestHeaders();
 | 
						|
      customHeaders.forEach((key, value) {
 | 
						|
        request.headers.add(key, value);
 | 
						|
      });
 | 
						|
 | 
						|
      final response = await request.close();
 | 
						|
      if (response.statusCode == 200) {
 | 
						|
        isValid = true;
 | 
						|
      }
 | 
						|
    } catch (error) {
 | 
						|
      _log.severe("Error validating auxiliary endpoint", error);
 | 
						|
    } finally {
 | 
						|
      httpclient.close();
 | 
						|
    }
 | 
						|
 | 
						|
    return isValid;
 | 
						|
  }
 | 
						|
 | 
						|
  Future<LoginResponse> login(String email, String password) {
 | 
						|
    return _authApiRepository.login(email, password);
 | 
						|
  }
 | 
						|
 | 
						|
  /// Performs user logout operation by making a server request and clearing local data.
 | 
						|
  ///
 | 
						|
  /// This method attempts to log out the user through the authentication API repository.
 | 
						|
  /// If the server request fails, the error is logged but local data is still cleared.
 | 
						|
  /// The local data cleanup is guaranteed to execute regardless of the server request outcome.
 | 
						|
  ///
 | 
						|
  /// Throws any unhandled exceptions from the API request or local data clearing operations.
 | 
						|
  Future<void> logout() async {
 | 
						|
    try {
 | 
						|
      await _authApiRepository.logout();
 | 
						|
    } catch (error, stackTrace) {
 | 
						|
      _log.severe("Error logging out", error, stackTrace);
 | 
						|
    } finally {
 | 
						|
      await clearLocalData().catchError((error, stackTrace) {
 | 
						|
        _log.severe("Error clearing local data", error, stackTrace);
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /// Clears all local authentication-related data.
 | 
						|
  ///
 | 
						|
  /// This method performs a concurrent deletion of:
 | 
						|
  /// - Authentication repository data
 | 
						|
  /// - Current user information
 | 
						|
  /// - Access token
 | 
						|
  /// - Asset ETag
 | 
						|
  ///
 | 
						|
  /// All deletions are executed in parallel using [Future.wait].
 | 
						|
  Future<void> clearLocalData() async {
 | 
						|
    // Cancel any ongoing background sync operations before clearing data
 | 
						|
    await _backgroundSyncManager.cancel();
 | 
						|
    await Future.wait([
 | 
						|
      _authRepository.clearLocalData(),
 | 
						|
      Store.delete(StoreKey.currentUser),
 | 
						|
      Store.delete(StoreKey.accessToken),
 | 
						|
      Store.delete(StoreKey.assetETag),
 | 
						|
      Store.delete(StoreKey.autoEndpointSwitching),
 | 
						|
      Store.delete(StoreKey.preferredWifiName),
 | 
						|
      Store.delete(StoreKey.localEndpoint),
 | 
						|
      Store.delete(StoreKey.externalEndpointList),
 | 
						|
    ]);
 | 
						|
  }
 | 
						|
 | 
						|
  Future<void> changePassword(String newPassword) {
 | 
						|
    try {
 | 
						|
      return _authApiRepository.changePassword(newPassword);
 | 
						|
    } catch (error, stackTrace) {
 | 
						|
      _log.severe("Error changing password", error, stackTrace);
 | 
						|
      rethrow;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Future<String?> setOpenApiServiceEndpoint() async {
 | 
						|
    final enable = _authRepository.getEndpointSwitchingFeature();
 | 
						|
    if (!enable) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    final wifiName = await _networkService.getWifiName();
 | 
						|
    final savedWifiName = _authRepository.getPreferredWifiName();
 | 
						|
    String? endpoint;
 | 
						|
 | 
						|
    if (wifiName == savedWifiName) {
 | 
						|
      endpoint = await _setLocalConnection();
 | 
						|
    }
 | 
						|
 | 
						|
    endpoint ??= await _setRemoteConnection();
 | 
						|
 | 
						|
    return endpoint;
 | 
						|
  }
 | 
						|
 | 
						|
  Future<String?> _setLocalConnection() async {
 | 
						|
    try {
 | 
						|
      final localEndpoint = _authRepository.getLocalEndpoint();
 | 
						|
      if (localEndpoint != null) {
 | 
						|
        await _apiService.resolveAndSetEndpoint(localEndpoint);
 | 
						|
        return localEndpoint;
 | 
						|
      }
 | 
						|
    } catch (error, stackTrace) {
 | 
						|
      _log.severe("Cannot set local endpoint", error, stackTrace);
 | 
						|
    }
 | 
						|
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  Future<String?> _setRemoteConnection() async {
 | 
						|
    List<AuxilaryEndpoint> endpointList;
 | 
						|
 | 
						|
    try {
 | 
						|
      endpointList = _authRepository.getExternalEndpointList();
 | 
						|
    } catch (error, stackTrace) {
 | 
						|
      _log.severe("Cannot get external endpoint", error, stackTrace);
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    for (final endpoint in endpointList) {
 | 
						|
      try {
 | 
						|
        return await _apiService.resolveAndSetEndpoint(endpoint.url);
 | 
						|
      } on ApiException catch (error) {
 | 
						|
        _log.severe("Cannot resolve endpoint", error);
 | 
						|
        continue;
 | 
						|
      } catch (_) {
 | 
						|
        _log.severe("Auxiliary server is not valid");
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
}
 |