fix api
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// Barrel export file for constants
|
||||
export 'app_constants.dart';
|
||||
export 'environment_config.dart';
|
||||
export 'storage_constants.dart';
|
||||
140
lib/core/constants/environment_config.dart
Normal file
140
lib/core/constants/environment_config.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
/// Environment configuration for API endpoints and settings
|
||||
enum Environment {
|
||||
development,
|
||||
staging,
|
||||
production,
|
||||
}
|
||||
|
||||
/// Environment-specific configuration
|
||||
class EnvironmentConfig {
|
||||
// Private constructor to prevent instantiation
|
||||
const EnvironmentConfig._();
|
||||
|
||||
/// Current environment - Change this to switch environments
|
||||
static const Environment currentEnvironment = Environment.development;
|
||||
|
||||
/// Get base URL for current environment
|
||||
static String get baseUrl {
|
||||
switch (currentEnvironment) {
|
||||
case Environment.development:
|
||||
return 'http://localhost:3000';
|
||||
case Environment.staging:
|
||||
return 'https://api-staging.example.com';
|
||||
case Environment.production:
|
||||
return 'https://api.example.com';
|
||||
}
|
||||
}
|
||||
|
||||
/// Get API path for current environment
|
||||
static String get apiPath {
|
||||
switch (currentEnvironment) {
|
||||
case Environment.development:
|
||||
// No API prefix for local development - endpoints are directly at /auth/
|
||||
return '';
|
||||
case Environment.staging:
|
||||
case Environment.production:
|
||||
return '/api/v1';
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if current environment is development
|
||||
static bool get isDevelopment => currentEnvironment == Environment.development;
|
||||
|
||||
/// Check if current environment is staging
|
||||
static bool get isStaging => currentEnvironment == Environment.staging;
|
||||
|
||||
/// Check if current environment is production
|
||||
static bool get isProduction => currentEnvironment == Environment.production;
|
||||
|
||||
/// Get timeout configurations based on environment
|
||||
static int get connectTimeout {
|
||||
switch (currentEnvironment) {
|
||||
case Environment.development:
|
||||
return 10000; // 10 seconds for local development
|
||||
case Environment.staging:
|
||||
return 20000; // 20 seconds for staging
|
||||
case Environment.production:
|
||||
return 30000; // 30 seconds for production
|
||||
}
|
||||
}
|
||||
|
||||
static int get receiveTimeout {
|
||||
switch (currentEnvironment) {
|
||||
case Environment.development:
|
||||
return 15000; // 15 seconds for local development
|
||||
case Environment.staging:
|
||||
return 25000; // 25 seconds for staging
|
||||
case Environment.production:
|
||||
return 30000; // 30 seconds for production
|
||||
}
|
||||
}
|
||||
|
||||
static int get sendTimeout {
|
||||
switch (currentEnvironment) {
|
||||
case Environment.development:
|
||||
return 15000; // 15 seconds for local development
|
||||
case Environment.staging:
|
||||
return 25000; // 25 seconds for staging
|
||||
case Environment.production:
|
||||
return 30000; // 30 seconds for production
|
||||
}
|
||||
}
|
||||
|
||||
/// Get retry configurations based on environment
|
||||
static int get maxRetries {
|
||||
switch (currentEnvironment) {
|
||||
case Environment.development:
|
||||
return 2; // Fewer retries for local development
|
||||
case Environment.staging:
|
||||
return 3; // Standard retries for staging
|
||||
case Environment.production:
|
||||
return 3; // Standard retries for production
|
||||
}
|
||||
}
|
||||
|
||||
static Duration get retryDelay {
|
||||
switch (currentEnvironment) {
|
||||
case Environment.development:
|
||||
return const Duration(milliseconds: 500); // Faster retry for local
|
||||
case Environment.staging:
|
||||
return const Duration(seconds: 1); // Standard retry delay
|
||||
case Environment.production:
|
||||
return const Duration(seconds: 1); // Standard retry delay
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable/disable features based on environment
|
||||
static bool get enableLogging => !isProduction;
|
||||
static bool get enableDetailedLogging => isDevelopment;
|
||||
static bool get enableCertificatePinning => isProduction;
|
||||
|
||||
/// Authentication endpoints (consistent across environments)
|
||||
static const String authEndpoint = '/auth';
|
||||
static const String loginEndpoint = '$authEndpoint/login';
|
||||
static const String registerEndpoint = '$authEndpoint/register';
|
||||
static const String refreshEndpoint = '$authEndpoint/refresh';
|
||||
static const String logoutEndpoint = '$authEndpoint/logout';
|
||||
|
||||
/// Full API URLs
|
||||
static String get fullBaseUrl => baseUrl + apiPath;
|
||||
static String get loginUrl => baseUrl + loginEndpoint;
|
||||
static String get registerUrl => baseUrl + registerEndpoint;
|
||||
static String get refreshUrl => baseUrl + refreshEndpoint;
|
||||
static String get logoutUrl => baseUrl + logoutEndpoint;
|
||||
|
||||
/// Debug information
|
||||
static Map<String, dynamic> get debugInfo => {
|
||||
'environment': currentEnvironment.name,
|
||||
'baseUrl': baseUrl,
|
||||
'apiPath': apiPath,
|
||||
'fullBaseUrl': fullBaseUrl,
|
||||
'connectTimeout': connectTimeout,
|
||||
'receiveTimeout': receiveTimeout,
|
||||
'sendTimeout': sendTimeout,
|
||||
'maxRetries': maxRetries,
|
||||
'retryDelay': retryDelay.inMilliseconds,
|
||||
'enableLogging': enableLogging,
|
||||
'enableDetailedLogging': enableDetailedLogging,
|
||||
'enableCertificatePinning': enableCertificatePinning,
|
||||
};
|
||||
}
|
||||
377
lib/core/debug/api_debug_page.dart
Normal file
377
lib/core/debug/api_debug_page.dart
Normal file
@@ -0,0 +1,377 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../constants/environment_config.dart';
|
||||
import '../providers/app_providers.dart';
|
||||
import '../providers/network_providers.dart';
|
||||
|
||||
/// Debug page to verify API configuration and test connectivity
|
||||
/// This page helps verify that the localhost:3000 configuration is working correctly
|
||||
class ApiDebugPage extends ConsumerWidget {
|
||||
const ApiDebugPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final environmentInfo = ref.watch(environmentInfoProvider);
|
||||
final environmentDebug = ref.watch(environmentDebugInfoProvider);
|
||||
final apiConnectivityTest = ref.watch(apiConnectivityTestProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('API Configuration Debug'),
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildEnvironmentCard(context, environmentInfo),
|
||||
const SizedBox(height: 16),
|
||||
_buildEndpointsCard(context, environmentDebug),
|
||||
const SizedBox(height: 16),
|
||||
_buildConnectivityTestCard(context, apiConnectivityTest, ref),
|
||||
const SizedBox(height: 16),
|
||||
_buildQuickActionsCard(context, ref),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEnvironmentCard(BuildContext context, Map<String, dynamic> info) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.settings,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Environment Configuration',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('Environment', info['environment']),
|
||||
_buildInfoRow('Base URL', info['baseUrl']),
|
||||
_buildInfoRow('API Path', info['apiPath'].isEmpty ? 'None (Direct)' : info['apiPath']),
|
||||
_buildInfoRow('Full Base URL', info['fullBaseUrl']),
|
||||
const Divider(),
|
||||
Text(
|
||||
'Timeout Settings',
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow('Connect Timeout', '${info['timeouts']['connect']}ms'),
|
||||
_buildInfoRow('Receive Timeout', '${info['timeouts']['receive']}ms'),
|
||||
_buildInfoRow('Send Timeout', '${info['timeouts']['send']}ms'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEndpointsCard(BuildContext context, Map<String, dynamic> info) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.api,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'API Endpoints',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildEndpointRow(context, 'Login', '${info['baseUrl']}/auth/login'),
|
||||
_buildEndpointRow(context, 'Register', '${info['baseUrl']}/auth/register'),
|
||||
_buildEndpointRow(context, 'Refresh', '${info['baseUrl']}/auth/refresh'),
|
||||
_buildEndpointRow(context, 'Logout', '${info['baseUrl']}/auth/logout'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConnectivityTestCard(
|
||||
BuildContext context,
|
||||
AsyncValue<Map<String, dynamic>> testResult,
|
||||
WidgetRef ref,
|
||||
) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.network_check,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Connectivity Test',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
ref.read(apiConnectivityTestProvider.notifier).retry();
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: 'Retry Test',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
testResult.when(
|
||||
data: (data) => _buildTestResult(context, data),
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
error: (error, stackTrace) => _buildErrorResult(context, error.toString()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickActionsCard(BuildContext context, WidgetRef ref) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.flash_on,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Quick Actions',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _testLogin(context, ref),
|
||||
icon: const Icon(Icons.login),
|
||||
label: const Text('Test Login'),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _testRegister(context, ref),
|
||||
icon: const Icon(Icons.person_add),
|
||||
label: const Text('Test Register'),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _copyConfiguration(context),
|
||||
icon: const Icon(Icons.copy),
|
||||
label: const Text('Copy Config'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEndpointRow(BuildContext context, String name, String url) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
url,
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _copyToClipboard(context, url),
|
||||
icon: const Icon(Icons.copy, size: 16),
|
||||
tooltip: 'Copy URL',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTestResult(BuildContext context, Map<String, dynamic> data) {
|
||||
final isSuccess = data['status'] == 'success';
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSuccess
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isSuccess ? Icons.check_circle : Icons.error,
|
||||
color: isSuccess
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
data['message'] ?? (isSuccess ? 'Test passed' : 'Test failed'),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSuccess
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data['timestamp'] != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tested at: ${data['timestamp']}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorResult(BuildContext context, String error) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Error: $error',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _testLogin(BuildContext context, WidgetRef ref) {
|
||||
// This would be implemented based on your auth service
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Login test would be implemented here'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _testRegister(BuildContext context, WidgetRef ref) {
|
||||
// This would be implemented based on your auth service
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Register test would be implemented here'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _copyConfiguration(BuildContext context) {
|
||||
final config = EnvironmentConfig.debugInfo;
|
||||
final configText = config.entries
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join('\n');
|
||||
|
||||
_copyToClipboard(context, configText);
|
||||
}
|
||||
|
||||
void _copyToClipboard(BuildContext context, String text) {
|
||||
Clipboard.setData(ClipboardData(text: text));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Copied to clipboard'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,22 @@
|
||||
import '../constants/environment_config.dart';
|
||||
|
||||
/// API constants for network configuration
|
||||
class ApiConstants {
|
||||
// Private constructor to prevent instantiation
|
||||
const ApiConstants._();
|
||||
|
||||
// Base URLs for different environments
|
||||
static const String baseUrlDev = 'https://api-dev.example.com';
|
||||
static const String baseUrlStaging = 'https://api-staging.example.com';
|
||||
static const String baseUrlProd = 'https://api.example.com';
|
||||
// Environment-based configuration
|
||||
static String get baseUrl => EnvironmentConfig.baseUrl;
|
||||
static String get apiPath => EnvironmentConfig.apiPath;
|
||||
|
||||
// Current environment base URL
|
||||
// In a real app, this would be determined by build configuration
|
||||
static const String baseUrl = baseUrlDev;
|
||||
// Timeout configurations (environment-specific)
|
||||
static int get connectTimeout => EnvironmentConfig.connectTimeout;
|
||||
static int get receiveTimeout => EnvironmentConfig.receiveTimeout;
|
||||
static int get sendTimeout => EnvironmentConfig.sendTimeout;
|
||||
|
||||
// API versioning
|
||||
static const String apiVersion = 'v1';
|
||||
static const String apiPath = '/api/$apiVersion';
|
||||
|
||||
// Timeout configurations (in milliseconds)
|
||||
static const int connectTimeout = 30000; // 30 seconds
|
||||
static const int receiveTimeout = 30000; // 30 seconds
|
||||
static const int sendTimeout = 30000; // 30 seconds
|
||||
|
||||
// Retry configurations
|
||||
static const int maxRetries = 3;
|
||||
static const Duration retryDelay = Duration(seconds: 1);
|
||||
// Retry configurations (environment-specific)
|
||||
static int get maxRetries => EnvironmentConfig.maxRetries;
|
||||
static Duration get retryDelay => EnvironmentConfig.retryDelay;
|
||||
|
||||
// Headers
|
||||
static const String contentType = 'application/json';
|
||||
@@ -35,11 +28,12 @@ class ApiConstants {
|
||||
static const String bearerPrefix = 'Bearer';
|
||||
static const String apiKeyHeaderKey = 'X-API-Key';
|
||||
|
||||
// Common API endpoints
|
||||
static const String authEndpoint = '/auth';
|
||||
static const String loginEndpoint = '$authEndpoint/login';
|
||||
static const String refreshEndpoint = '$authEndpoint/refresh';
|
||||
static const String logoutEndpoint = '$authEndpoint/logout';
|
||||
// Authentication endpoints (from environment config)
|
||||
static String get authEndpoint => EnvironmentConfig.authEndpoint;
|
||||
static String get loginEndpoint => EnvironmentConfig.loginEndpoint;
|
||||
static String get registerEndpoint => EnvironmentConfig.registerEndpoint;
|
||||
static String get refreshEndpoint => EnvironmentConfig.refreshEndpoint;
|
||||
static String get logoutEndpoint => EnvironmentConfig.logoutEndpoint;
|
||||
static const String userEndpoint = '/user';
|
||||
static const String profileEndpoint = '$userEndpoint/profile';
|
||||
|
||||
@@ -68,9 +62,10 @@ class ApiConstants {
|
||||
// Example: 'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
|
||||
];
|
||||
|
||||
// Development flags
|
||||
static const bool enableLogging = true;
|
||||
static const bool enableCertificatePinning = false; // Disabled for development
|
||||
// Development flags (environment-specific)
|
||||
static bool get enableLogging => EnvironmentConfig.enableLogging;
|
||||
static bool get enableCertificatePinning => EnvironmentConfig.enableCertificatePinning;
|
||||
static bool get enableDetailedLogging => EnvironmentConfig.enableDetailedLogging;
|
||||
|
||||
// API rate limiting
|
||||
static const int maxRequestsPerMinute = 100;
|
||||
|
||||
242
lib/core/network/auth_service_example.dart
Normal file
242
lib/core/network/auth_service_example.dart
Normal file
@@ -0,0 +1,242 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../constants/environment_config.dart';
|
||||
import 'api_constants.dart';
|
||||
import 'dio_client.dart';
|
||||
|
||||
/// Example authentication service demonstrating the localhost:3000 configuration
|
||||
/// This service shows how to use the updated API configuration with the backend
|
||||
class AuthServiceExample {
|
||||
final DioClient _dioClient;
|
||||
|
||||
AuthServiceExample(this._dioClient);
|
||||
|
||||
/// Test connection to the backend
|
||||
Future<Map<String, dynamic>> testConnection() async {
|
||||
try {
|
||||
debugPrint('🔍 Testing connection to ${EnvironmentConfig.baseUrl}...');
|
||||
|
||||
final response = await _dioClient.get('/');
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'Connected to backend successfully',
|
||||
'baseUrl': EnvironmentConfig.baseUrl,
|
||||
'statusCode': response.statusCode,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} catch (error) {
|
||||
debugPrint('❌ Connection test failed: $error');
|
||||
return {
|
||||
'status': 'error',
|
||||
'message': 'Failed to connect to backend',
|
||||
'error': error.toString(),
|
||||
'baseUrl': EnvironmentConfig.baseUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Login with email and password
|
||||
/// Calls POST localhost:3000/auth/login
|
||||
Future<Map<String, dynamic>> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
debugPrint('🔐 Attempting login to ${EnvironmentConfig.loginUrl}...');
|
||||
|
||||
final response = await _dioClient.post(
|
||||
ApiConstants.loginEndpoint, // This resolves to /auth/login
|
||||
data: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
},
|
||||
);
|
||||
|
||||
debugPrint('✅ Login successful');
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': response.data,
|
||||
'endpoint': EnvironmentConfig.loginUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} on DioException catch (dioError) {
|
||||
debugPrint('❌ Login failed with DioException: ${dioError.message}');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': dioError.message ?? 'Unknown Dio error',
|
||||
'statusCode': dioError.response?.statusCode,
|
||||
'endpoint': EnvironmentConfig.loginUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} catch (error) {
|
||||
debugPrint('❌ Login failed: $error');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': error.toString(),
|
||||
'endpoint': EnvironmentConfig.loginUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Register new user
|
||||
/// Calls POST localhost:3000/auth/register
|
||||
Future<Map<String, dynamic>> register({
|
||||
required String email,
|
||||
required String password,
|
||||
required String name,
|
||||
}) async {
|
||||
try {
|
||||
debugPrint('📝 Attempting registration to ${EnvironmentConfig.registerUrl}...');
|
||||
|
||||
final response = await _dioClient.post(
|
||||
ApiConstants.registerEndpoint, // This resolves to /auth/register
|
||||
data: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
'name': name,
|
||||
},
|
||||
);
|
||||
|
||||
debugPrint('✅ Registration successful');
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': response.data,
|
||||
'endpoint': EnvironmentConfig.registerUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} on DioException catch (dioError) {
|
||||
debugPrint('❌ Registration failed with DioException: ${dioError.message}');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': dioError.message ?? 'Unknown Dio error',
|
||||
'statusCode': dioError.response?.statusCode,
|
||||
'endpoint': EnvironmentConfig.registerUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} catch (error) {
|
||||
debugPrint('❌ Registration failed: $error');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': error.toString(),
|
||||
'endpoint': EnvironmentConfig.registerUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh authentication token
|
||||
/// Calls POST localhost:3000/auth/refresh
|
||||
Future<Map<String, dynamic>> refreshToken(String refreshToken) async {
|
||||
try {
|
||||
debugPrint('🔄 Attempting token refresh to ${EnvironmentConfig.refreshUrl}...');
|
||||
|
||||
final response = await _dioClient.post(
|
||||
ApiConstants.refreshEndpoint, // This resolves to /auth/refresh
|
||||
data: {
|
||||
'refreshToken': refreshToken,
|
||||
},
|
||||
);
|
||||
|
||||
debugPrint('✅ Token refresh successful');
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': response.data,
|
||||
'endpoint': EnvironmentConfig.refreshUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} on DioException catch (dioError) {
|
||||
debugPrint('❌ Token refresh failed with DioException: ${dioError.message}');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': dioError.message ?? 'Unknown Dio error',
|
||||
'statusCode': dioError.response?.statusCode,
|
||||
'endpoint': EnvironmentConfig.refreshUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} catch (error) {
|
||||
debugPrint('❌ Token refresh failed: $error');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': error.toString(),
|
||||
'endpoint': EnvironmentConfig.refreshUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Logout user
|
||||
/// Calls POST localhost:3000/auth/logout
|
||||
Future<Map<String, dynamic>> logout(String accessToken) async {
|
||||
try {
|
||||
debugPrint('🚪 Attempting logout to ${EnvironmentConfig.logoutUrl}...');
|
||||
|
||||
final response = await _dioClient.post(
|
||||
ApiConstants.logoutEndpoint, // This resolves to /auth/logout
|
||||
options: Options(
|
||||
headers: {
|
||||
ApiConstants.authHeaderKey: '${ApiConstants.bearerPrefix} $accessToken',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
debugPrint('✅ Logout successful');
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': response.data,
|
||||
'endpoint': EnvironmentConfig.logoutUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} on DioException catch (dioError) {
|
||||
debugPrint('❌ Logout failed with DioException: ${dioError.message}');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': dioError.message ?? 'Unknown Dio error',
|
||||
'statusCode': dioError.response?.statusCode,
|
||||
'endpoint': EnvironmentConfig.logoutUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
} catch (error) {
|
||||
debugPrint('❌ Logout failed: $error');
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': error.toString(),
|
||||
'endpoint': EnvironmentConfig.logoutUrl,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current environment configuration for debugging
|
||||
Map<String, dynamic> getEnvironmentInfo() {
|
||||
return {
|
||||
'environment': EnvironmentConfig.currentEnvironment.name,
|
||||
'baseUrl': EnvironmentConfig.baseUrl,
|
||||
'apiPath': EnvironmentConfig.apiPath,
|
||||
'fullBaseUrl': EnvironmentConfig.fullBaseUrl,
|
||||
'endpoints': {
|
||||
'login': EnvironmentConfig.loginUrl,
|
||||
'register': EnvironmentConfig.registerUrl,
|
||||
'refresh': EnvironmentConfig.refreshUrl,
|
||||
'logout': EnvironmentConfig.logoutUrl,
|
||||
},
|
||||
'timeouts': {
|
||||
'connect': EnvironmentConfig.connectTimeout,
|
||||
'receive': EnvironmentConfig.receiveTimeout,
|
||||
'send': EnvironmentConfig.sendTimeout,
|
||||
},
|
||||
'retry': {
|
||||
'maxRetries': EnvironmentConfig.maxRetries,
|
||||
'retryDelay': EnvironmentConfig.retryDelay.inMilliseconds,
|
||||
},
|
||||
'flags': {
|
||||
'enableLogging': EnvironmentConfig.enableLogging,
|
||||
'enableDetailedLogging': EnvironmentConfig.enableDetailedLogging,
|
||||
'enableCertificatePinning': EnvironmentConfig.enableCertificatePinning,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
74
lib/core/network/test_api_connection.dart
Normal file
74
lib/core/network/test_api_connection.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../constants/environment_config.dart';
|
||||
|
||||
/// Simple utility to test API connection
|
||||
class ApiConnectionTest {
|
||||
static Future<void> testConnection(BuildContext context) async {
|
||||
final dio = Dio();
|
||||
|
||||
try {
|
||||
debugPrint('🔍 Testing API connection...');
|
||||
debugPrint('Base URL: ${EnvironmentConfig.baseUrl}');
|
||||
debugPrint('Auth endpoint: ${EnvironmentConfig.authEndpoint}');
|
||||
|
||||
// Test basic connectivity to the auth endpoint
|
||||
final response = await dio.get(
|
||||
'${EnvironmentConfig.baseUrl}${EnvironmentConfig.authEndpoint}',
|
||||
options: Options(
|
||||
validateStatus: (status) => true, // Accept any status code
|
||||
receiveTimeout: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
|
||||
debugPrint('✅ Connection successful!');
|
||||
debugPrint('Status code: ${response.statusCode}');
|
||||
debugPrint('Response: ${response.data}');
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('API Connected! Status: ${response.statusCode}'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Connection failed: $e');
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Connection failed: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current environment info as formatted string
|
||||
static String getEnvironmentInfo() {
|
||||
final info = EnvironmentConfig.debugInfo;
|
||||
final buffer = StringBuffer();
|
||||
|
||||
buffer.writeln('📊 Environment Configuration:');
|
||||
buffer.writeln('================================');
|
||||
info.forEach((key, value) {
|
||||
buffer.writeln('$key: $value');
|
||||
});
|
||||
buffer.writeln('================================');
|
||||
buffer.writeln('\n📍 Auth Endpoints:');
|
||||
buffer.writeln('Login: ${EnvironmentConfig.loginUrl}');
|
||||
buffer.writeln('Register: ${EnvironmentConfig.registerUrl}');
|
||||
buffer.writeln('Refresh: ${EnvironmentConfig.refreshUrl}');
|
||||
buffer.writeln('Logout: ${EnvironmentConfig.logoutUrl}');
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// Print environment info to console
|
||||
static void printEnvironmentInfo() {
|
||||
debugPrint(getEnvironmentInfo());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../constants/environment_config.dart';
|
||||
import '../database/hive_service.dart';
|
||||
import '../database/models/app_settings.dart';
|
||||
import '../database/providers/database_providers.dart';
|
||||
@@ -267,15 +268,33 @@ class AppConfiguration extends _$AppConfiguration {
|
||||
final buildMode = ref.watch(appBuildModeProvider);
|
||||
|
||||
return {
|
||||
'apiTimeout': 30000, // 30 seconds
|
||||
// Environment-specific API configuration
|
||||
'environment': EnvironmentConfig.currentEnvironment.name,
|
||||
'baseUrl': EnvironmentConfig.baseUrl,
|
||||
'apiPath': EnvironmentConfig.apiPath,
|
||||
'fullBaseUrl': EnvironmentConfig.fullBaseUrl,
|
||||
'connectTimeout': EnvironmentConfig.connectTimeout,
|
||||
'receiveTimeout': EnvironmentConfig.receiveTimeout,
|
||||
'sendTimeout': EnvironmentConfig.sendTimeout,
|
||||
'maxRetries': EnvironmentConfig.maxRetries,
|
||||
'retryDelay': EnvironmentConfig.retryDelay.inMilliseconds,
|
||||
|
||||
// Environment-specific logging
|
||||
'enableLogging': EnvironmentConfig.enableLogging,
|
||||
'enableDetailedLogging': EnvironmentConfig.enableDetailedLogging,
|
||||
'enableCertificatePinning': EnvironmentConfig.enableCertificatePinning,
|
||||
'logLevel': EnvironmentConfig.enableDetailedLogging ? 'verbose' : 'error',
|
||||
|
||||
// General configuration
|
||||
'cacheTimeout': 3600000, // 1 hour in milliseconds
|
||||
'maxRetries': 3,
|
||||
'retryDelay': 1000, // 1 second
|
||||
'enableLogging': buildMode == 'debug',
|
||||
'logLevel': buildMode == 'debug' ? 'verbose' : 'error',
|
||||
'maxCacheSize': 100 * 1024 * 1024, // 100MB
|
||||
'imageQuality': 85,
|
||||
'compressionEnabled': true,
|
||||
|
||||
// Environment flags
|
||||
'isDevelopment': EnvironmentConfig.isDevelopment,
|
||||
'isStaging': EnvironmentConfig.isStaging,
|
||||
'isProduction': EnvironmentConfig.isProduction,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -346,3 +365,68 @@ class ErrorTracker extends _$ErrorTracker {
|
||||
return state.reversed.take(count).toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// Environment debug information provider
|
||||
@riverpod
|
||||
Map<String, dynamic> environmentDebugInfo(EnvironmentDebugInfoRef ref) {
|
||||
return EnvironmentConfig.debugInfo;
|
||||
}
|
||||
|
||||
/// API connectivity test provider
|
||||
@riverpod
|
||||
class ApiConnectivityTest extends _$ApiConnectivityTest {
|
||||
@override
|
||||
Future<Map<String, dynamic>> build() async {
|
||||
return _testConnectivity();
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _testConnectivity() async {
|
||||
try {
|
||||
debugPrint('🔍 Testing API connectivity to ${EnvironmentConfig.baseUrl}...');
|
||||
|
||||
final result = {
|
||||
'baseUrl': EnvironmentConfig.baseUrl,
|
||||
'fullBaseUrl': EnvironmentConfig.fullBaseUrl,
|
||||
'loginUrl': EnvironmentConfig.loginUrl,
|
||||
'environment': EnvironmentConfig.currentEnvironment.name,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
'status': 'success',
|
||||
'message': 'Configuration loaded successfully',
|
||||
'endpoints': {
|
||||
'login': EnvironmentConfig.loginUrl,
|
||||
'register': EnvironmentConfig.registerUrl,
|
||||
'refresh': EnvironmentConfig.refreshUrl,
|
||||
'logout': EnvironmentConfig.logoutUrl,
|
||||
},
|
||||
'settings': {
|
||||
'connectTimeout': EnvironmentConfig.connectTimeout,
|
||||
'receiveTimeout': EnvironmentConfig.receiveTimeout,
|
||||
'sendTimeout': EnvironmentConfig.sendTimeout,
|
||||
'maxRetries': EnvironmentConfig.maxRetries,
|
||||
'retryDelay': EnvironmentConfig.retryDelay.inMilliseconds,
|
||||
'enableLogging': EnvironmentConfig.enableLogging,
|
||||
'enableDetailedLogging': EnvironmentConfig.enableDetailedLogging,
|
||||
}
|
||||
};
|
||||
|
||||
debugPrint('✅ API connectivity test completed successfully');
|
||||
return result;
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint('❌ API connectivity test failed: $error');
|
||||
debugPrint('Stack trace: $stackTrace');
|
||||
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': error.toString(),
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
'baseUrl': EnvironmentConfig.baseUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Retry connectivity test
|
||||
Future<void> retry() async {
|
||||
state = const AsyncValue.loading();
|
||||
state = AsyncValue.data(await _testConnectivity());
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,25 @@ final isAppReadyProvider = AutoDisposeProvider<bool>.internal(
|
||||
);
|
||||
|
||||
typedef IsAppReadyRef = AutoDisposeProviderRef<bool>;
|
||||
String _$environmentDebugInfoHash() =>
|
||||
r'8a936abed2173bd1539eec64231e8d970ed7382a';
|
||||
|
||||
/// Environment debug information provider
|
||||
///
|
||||
/// Copied from [environmentDebugInfo].
|
||||
@ProviderFor(environmentDebugInfo)
|
||||
final environmentDebugInfoProvider =
|
||||
AutoDisposeProvider<Map<String, dynamic>>.internal(
|
||||
environmentDebugInfo,
|
||||
name: r'environmentDebugInfoProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$environmentDebugInfoHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef EnvironmentDebugInfoRef = AutoDisposeProviderRef<Map<String, dynamic>>;
|
||||
String _$appInitializationHash() => r'cdf86e2d6985c6dcee80f618bc032edf81011fc9';
|
||||
|
||||
/// App initialization provider
|
||||
@@ -142,7 +161,7 @@ final featureFlagsProvider =
|
||||
);
|
||||
|
||||
typedef _$FeatureFlags = AutoDisposeNotifier<Map<String, bool>>;
|
||||
String _$appConfigurationHash() => r'115fff1ac67a37ff620bbd15ea142a7211e9dc9c';
|
||||
String _$appConfigurationHash() => r'7699bbd57d15b91cd520a876454368e5b97342bd';
|
||||
|
||||
/// App configuration provider
|
||||
///
|
||||
@@ -196,5 +215,24 @@ final errorTrackerProvider = AutoDisposeNotifierProvider<ErrorTracker,
|
||||
);
|
||||
|
||||
typedef _$ErrorTracker = AutoDisposeNotifier<List<Map<String, dynamic>>>;
|
||||
String _$apiConnectivityTestHash() =>
|
||||
r'19c63d75d09ad8f95452afb1a409528fcdd5cbaa';
|
||||
|
||||
/// API connectivity test provider
|
||||
///
|
||||
/// Copied from [ApiConnectivityTest].
|
||||
@ProviderFor(ApiConnectivityTest)
|
||||
final apiConnectivityTestProvider = AutoDisposeAsyncNotifierProvider<
|
||||
ApiConnectivityTest, Map<String, dynamic>>.internal(
|
||||
ApiConnectivityTest.new,
|
||||
name: r'apiConnectivityTestProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$apiConnectivityTestHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$ApiConnectivityTest = AutoDisposeAsyncNotifier<Map<String, dynamic>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
import '../network/auth_service_example.dart';
|
||||
import '../network/dio_client.dart';
|
||||
import '../network/network_info.dart';
|
||||
|
||||
@@ -49,3 +50,15 @@ final networkConnectionDetailsProvider = FutureProvider((ref) async {
|
||||
final networkInfo = ref.watch(networkInfoProvider) as NetworkInfoImpl;
|
||||
return await networkInfo.getConnectionDetails();
|
||||
});
|
||||
|
||||
/// Provider for AuthServiceExample - demonstrates localhost:3000 API usage
|
||||
final authServiceExampleProvider = Provider<AuthServiceExample>((ref) {
|
||||
final dioClient = ref.watch(dioClientProvider);
|
||||
return AuthServiceExample(dioClient);
|
||||
});
|
||||
|
||||
/// Provider for environment information debugging
|
||||
final environmentInfoProvider = Provider<Map<String, dynamic>>((ref) {
|
||||
final authService = ref.watch(authServiceExampleProvider);
|
||||
return authService.getEnvironmentInfo();
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:base_flutter/core/utils/utils.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../../../../core/network/dio_client.dart';
|
||||
import '../../../../core/network/api_constants.dart';
|
||||
import '../../../../core/services/api_service.dart';
|
||||
import '../models/user_model.dart';
|
||||
|
||||
abstract class AuthRemoteDataSource {
|
||||
@@ -34,58 +36,43 @@ abstract class AuthRemoteDataSource {
|
||||
});
|
||||
}
|
||||
|
||||
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
final DioClient dioClient;
|
||||
|
||||
AuthRemoteDataSourceImpl({required this.dioClient});
|
||||
class AuthRemoteDataSourceImpl extends BaseApiService implements AuthRemoteDataSource {
|
||||
AuthRemoteDataSourceImpl({required DioClient dioClient}) : super(dioClient);
|
||||
|
||||
@override
|
||||
Future<UserModel> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
// Using JSONPlaceholder as a mock API
|
||||
// In real app, this would be your actual auth endpoint
|
||||
final response = await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/posts',
|
||||
return executeRequest(
|
||||
() => dioClient.post(
|
||||
ApiConstants.loginEndpoint,
|
||||
data: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
},
|
||||
),
|
||||
(data) {
|
||||
final responseData = data as DataMap;
|
||||
|
||||
// If the backend returns user data in a 'user' field
|
||||
if (responseData.containsKey('user')) {
|
||||
final userData = DataMap.from(responseData['user']);
|
||||
// Add token if it's returned separately
|
||||
if (responseData.containsKey('token')) {
|
||||
userData['token'] = responseData['token'];
|
||||
}
|
||||
if (responseData.containsKey('tokenExpiry')) {
|
||||
userData['tokenExpiry'] = responseData['tokenExpiry'];
|
||||
}
|
||||
return UserModel.fromJson(userData);
|
||||
}
|
||||
// If the backend returns everything in the root
|
||||
else {
|
||||
return UserModel.fromJson(responseData);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Mock validation - accept any email/password for demo
|
||||
// In real app, the server would validate credentials
|
||||
if (email.isEmpty || password.isEmpty) {
|
||||
throw const ServerException('Invalid credentials');
|
||||
}
|
||||
|
||||
// Mock response for demonstration
|
||||
// In real app, parse actual API response
|
||||
final mockUser = {
|
||||
'id': '1',
|
||||
'email': email,
|
||||
'name': email.split('@').first,
|
||||
'token': 'mock_jwt_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
throw const ServerException('Invalid credentials');
|
||||
} else if (e.response?.statusCode == 404) {
|
||||
throw const ServerException('User not found');
|
||||
} else {
|
||||
throw ServerException(e.message ?? 'Login failed');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.toString().contains('Invalid credentials')) {
|
||||
rethrow;
|
||||
}
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,73 +81,76 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
required String password,
|
||||
required String name,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
final response = await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/users',
|
||||
return executeRequest(
|
||||
() => dioClient.post(
|
||||
ApiConstants.registerEndpoint,
|
||||
data: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
'name': name,
|
||||
},
|
||||
),
|
||||
(data) {
|
||||
final responseData = data as Map<String, dynamic>;
|
||||
|
||||
// If the backend returns user data in a 'user' field
|
||||
if (responseData.containsKey('user')) {
|
||||
final userData = Map<String, dynamic>.from(responseData['user']);
|
||||
// Add token if it's returned separately
|
||||
if (responseData.containsKey('token')) {
|
||||
userData['token'] = responseData['token'];
|
||||
}
|
||||
if (responseData.containsKey('tokenExpiry')) {
|
||||
userData['tokenExpiry'] = responseData['tokenExpiry'];
|
||||
}
|
||||
return UserModel.fromJson(userData);
|
||||
}
|
||||
// If the backend returns everything in the root
|
||||
else {
|
||||
return UserModel.fromJson(responseData);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Mock response
|
||||
final mockUser = {
|
||||
'id': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
'email': email,
|
||||
'name': name,
|
||||
'token': 'mock_jwt_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 409) {
|
||||
throw const ServerException('Email already exists');
|
||||
} else {
|
||||
throw ServerException(e.message ?? 'Registration failed');
|
||||
}
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
// Mock API call
|
||||
await dioClient.dio.post('https://jsonplaceholder.typicode.com/posts');
|
||||
// In real app, you might call a logout endpoint to invalidate token
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
await executeRequest(
|
||||
() => dioClient.post(ApiConstants.logoutEndpoint),
|
||||
(_) {}, // No return value needed for logout
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserModel> refreshToken(String token) async {
|
||||
try {
|
||||
// Mock API call
|
||||
final response = await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/users',
|
||||
return executeRequest(
|
||||
() => dioClient.post(
|
||||
ApiConstants.refreshEndpoint,
|
||||
options: Options(
|
||||
headers: {'Authorization': 'Bearer $token'},
|
||||
),
|
||||
);
|
||||
),
|
||||
(data) {
|
||||
final responseData = data as Map<String, dynamic>;
|
||||
|
||||
// Mock response
|
||||
final mockUser = {
|
||||
'id': '1',
|
||||
'email': 'user@example.com',
|
||||
'name': 'User',
|
||||
'token': 'refreshed_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
// If the backend returns user data in a 'user' field
|
||||
if (responseData.containsKey('user')) {
|
||||
final userData = Map<String, dynamic>.from(responseData['user']);
|
||||
// Add new token if it's returned separately
|
||||
if (responseData.containsKey('token')) {
|
||||
userData['token'] = responseData['token'];
|
||||
}
|
||||
if (responseData.containsKey('tokenExpiry')) {
|
||||
userData['tokenExpiry'] = responseData['tokenExpiry'];
|
||||
}
|
||||
return UserModel.fromJson(userData);
|
||||
}
|
||||
// If the backend returns everything in the root
|
||||
else {
|
||||
return UserModel.fromJson(responseData);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -168,30 +158,18 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
required String name,
|
||||
String? avatarUrl,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
final response = await dioClient.dio.put(
|
||||
'https://jsonplaceholder.typicode.com/users/1',
|
||||
// For now, keeping this as mock since profile endpoints are not specified
|
||||
// When you have the actual endpoint, update this to use executeRequest
|
||||
return executeRequest(
|
||||
() => dioClient.put(
|
||||
'/user/profile', // Replace with actual endpoint when available
|
||||
data: {
|
||||
'name': name,
|
||||
'avatarUrl': avatarUrl,
|
||||
},
|
||||
),
|
||||
(data) => UserModel.fromJson(data as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
// Mock response
|
||||
final mockUser = {
|
||||
'id': '1',
|
||||
'email': 'user@example.com',
|
||||
'name': name,
|
||||
'avatarUrl': avatarUrl,
|
||||
'token': 'mock_jwt_token_${DateTime.now().millisecondsSinceEpoch}',
|
||||
'tokenExpiry': DateTime.now().add(const Duration(days: 7)).toIso8601String(),
|
||||
};
|
||||
|
||||
return UserModel.fromJson(mockUser);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -199,34 +177,30 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
required String oldPassword,
|
||||
required String newPassword,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/posts',
|
||||
await executeRequest(
|
||||
() => dioClient.post(
|
||||
'/auth/change-password', // Replace with actual endpoint when available
|
||||
data: {
|
||||
'oldPassword': oldPassword,
|
||||
'newPassword': newPassword,
|
||||
},
|
||||
),
|
||||
(_) {}, // No return value needed
|
||||
);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> resetPassword({
|
||||
required String email,
|
||||
}) async {
|
||||
try {
|
||||
// Mock API call
|
||||
await dioClient.dio.post(
|
||||
'https://jsonplaceholder.typicode.com/posts',
|
||||
await executeRequest(
|
||||
() => dioClient.post(
|
||||
'/auth/reset-password', // Replace with actual endpoint when available
|
||||
data: {
|
||||
'email': email,
|
||||
},
|
||||
),
|
||||
(_) {}, // No return value needed
|
||||
);
|
||||
} catch (e) {
|
||||
throw ServerException(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user