305 lines
9.3 KiB
Dart
305 lines
9.3 KiB
Dart
/// 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<GetSessionResponse> getSession() async {
|
|
try {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/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<GetSessionResponse> login({
|
|
required String phone,
|
|
required String csrfToken,
|
|
required String sid,
|
|
String? password, // Reserved for future use
|
|
}) async {
|
|
try {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/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<List<City>> getCities({
|
|
required String csrfToken,
|
|
required String sid,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/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<String, dynamic>))
|
|
.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<List<CustomerGroup>> getCustomerGroups({
|
|
required String csrfToken,
|
|
required String sid,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/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,
|
|
'order_by': 'custom_line_no asc'
|
|
},
|
|
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<String, dynamic>))
|
|
.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<Map<String, dynamic>> 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<String>? certificatesBase64,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/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');
|
|
}
|
|
}
|
|
}
|