/// Authentication Remote Data Source /// /// Handles all authentication-related API calls. library; import 'package:dio/dio.dart'; import 'package:worker/core/errors/exceptions.dart'; import 'package:worker/features/auth/data/models/auth_session_model.dart'; import 'package:worker/features/auth/domain/entities/city.dart'; import 'package:worker/features/auth/domain/entities/customer_group.dart'; /// Authentication Remote Data Source /// /// Provides methods for: /// - Getting session (CSRF token and SID) /// - Fetching cities for registration /// - Fetching customer groups (roles) for registration /// - User registration class AuthRemoteDataSource { final Dio _dio; AuthRemoteDataSource(this._dio); /// Get Session /// /// Fetches session data including SID and CSRF token. /// This should be called before making authenticated requests. /// /// API: POST /api/method/dbiz_common.dbiz_common.api.auth.get_session Future getSession() async { try { final response = await _dio.post>( '/api/method/dbiz_common.dbiz_common.api.auth.get_session', data: '', ); if (response.statusCode == 200 && response.data != null) { return GetSessionResponse.fromJson(response.data!); } else { throw ServerException( 'Failed to get session: ${response.statusCode}', ); } } on DioException catch (e) { if (e.response?.statusCode == 401) { throw const UnauthorizedException(); } else if (e.response?.statusCode == 404) { throw NotFoundException('Session endpoint not found'); } else { throw NetworkException( e.message ?? 'Failed to get session', ); } } catch (e) { throw ServerException('Unexpected error: $e'); } } /// Login /// /// Authenticates user with phone number. /// Requires existing session (CSRF token and Cookie). /// Returns new session with user credentials. /// /// API: POST /api/method/building_material.building_material.api.auth.login /// Body: { "username": "phone", "googleid": null, "facebookid": null, "zaloid": null } /// /// Response includes new sid and csrf_token for authenticated user. Future login({ required String phone, required String csrfToken, required String sid, String? password, // Reserved for future use }) async { try { final response = await _dio.post>( '/api/method/building_material.building_material.api.auth.login', data: { 'username': phone, 'googleid': null, 'facebookid': null, 'zaloid': null, // Password field reserved for future use // 'password': password, }, options: Options( headers: { 'X-Frappe-Csrf-Token': csrfToken, 'Cookie': 'sid=$sid', 'Content-Type': 'application/json', }, ), ); if (response.statusCode == 200 && response.data != null) { return GetSessionResponse.fromJson(response.data!); } else { throw ServerException( 'Login failed: ${response.statusCode}', ); } } on DioException catch (e) { if (e.response?.statusCode == 401) { throw const UnauthorizedException('Invalid credentials'); } else if (e.response?.statusCode == 404) { throw NotFoundException('Login endpoint not found'); } else { throw NetworkException( e.message ?? 'Failed to login', ); } } catch (e) { throw ServerException('Unexpected error during login: $e'); } } /// Get Cities /// /// Fetches list of cities/provinces for address selection. /// Requires authenticated session (CSRF token and Cookie). /// /// API: POST /api/method/frappe.client.get_list /// DocType: City Future> getCities({ required String csrfToken, required String sid, }) async { try { final response = await _dio.post>( '/api/method/frappe.client.get_list', data: { 'doctype': 'City', 'fields': ['city_name', 'name', 'code'], 'limit_page_length': 0, }, options: Options( headers: { 'X-Frappe-Csrf-Token': csrfToken, 'Cookie': 'sid=$sid', }, ), ); if (response.statusCode == 200 && response.data != null) { final message = response.data!['message']; if (message is List) { return message .map((json) => City.fromJson(json as Map)) .toList(); } else { throw ServerException('Invalid response format for cities'); } } else { throw ServerException( 'Failed to get cities: ${response.statusCode}', ); } } on DioException catch (e) { if (e.response?.statusCode == 401) { throw const UnauthorizedException(); } else if (e.response?.statusCode == 404) { throw NotFoundException('Cities endpoint not found'); } else { throw NetworkException( e.message ?? 'Failed to get cities', ); } } catch (e) { throw ServerException('Unexpected error: $e'); } } /// Get Customer Groups (Roles) /// /// Fetches list of customer groups for user role selection. /// Requires authenticated session (CSRF token and Cookie). /// /// API: POST /api/method/frappe.client.get_list /// DocType: Customer Group Future> getCustomerGroups({ required String csrfToken, required String sid, }) async { try { final response = await _dio.post>( '/api/method/frappe.client.get_list', data: { 'doctype': 'Customer Group', 'fields': ['customer_group_name', 'name', 'value'], 'filters': { 'is_group': 0, 'is_active': 1, 'customer': 1, }, 'limit_page_length': 0, }, options: Options( headers: { 'X-Frappe-Csrf-Token': csrfToken, 'Cookie': 'sid=$sid', }, ), ); if (response.statusCode == 200 && response.data != null) { final message = response.data!['message']; if (message is List) { return message .map((json) => CustomerGroup.fromJson(json as Map)) .toList(); } else { throw ServerException('Invalid response format for customer groups'); } } else { throw ServerException( 'Failed to get customer groups: ${response.statusCode}', ); } } on DioException catch (e) { if (e.response?.statusCode == 401) { throw const UnauthorizedException(); } else if (e.response?.statusCode == 404) { throw NotFoundException('Customer groups endpoint not found'); } else { throw NetworkException( e.message ?? 'Failed to get customer groups', ); } } catch (e) { throw ServerException('Unexpected error: $e'); } } /// Register User /// /// Registers a new user with the provided information. /// Requires authenticated session (CSRF token and Cookie). /// /// API: POST /api/method/building_material.building_material.api.user.register Future> register({ required String csrfToken, required String sid, required String fullName, required String phone, required String email, required String customerGroupCode, required String cityCode, String? companyName, String? taxCode, String? idCardFrontBase64, String? idCardBackBase64, List? certificatesBase64, }) async { try { final response = await _dio.post>( '/api/method/building_material.building_material.api.user.register', data: { 'full_name': fullName, 'phone': phone, 'email': email, 'customer_group_code': customerGroupCode, 'city_code': cityCode, 'company_name': companyName, 'tax_code': taxCode, 'id_card_front_base64': idCardFrontBase64, 'id_card_back_base64': idCardBackBase64, 'certificates_base64': certificatesBase64 ?? [], }, options: Options( headers: { 'X-Frappe-Csrf-Token': csrfToken, 'Cookie': 'sid=$sid', }, ), ); if (response.statusCode == 200 && response.data != null) { return response.data!; } else { throw ServerException( 'Failed to register: ${response.statusCode}', ); } } on DioException catch (e) { if (e.response?.statusCode == 401) { throw const UnauthorizedException(); } else if (e.response?.statusCode == 400) { throw ValidationException( e.response?.data?['message'] as String? ?? 'Validation error', ); } else if (e.response?.statusCode == 404) { throw NotFoundException('Register endpoint not found'); } else { throw NetworkException( e.message ?? 'Failed to register', ); } } catch (e) { throw ServerException('Unexpected error: $e'); } } }