init
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../config.dart';
|
||||
import '../models/api_exception.dart';
|
||||
import '../models/login_result.dart';
|
||||
import '../models/support_message.dart';
|
||||
import '../models/support_ticket.dart';
|
||||
|
||||
class SupportApi {
|
||||
SupportApi({Dio? dio, String? origin, this.onUnauthorized})
|
||||
: dio =
|
||||
dio ??
|
||||
Dio(
|
||||
BaseOptions(
|
||||
baseUrl: origin ?? baseUrl,
|
||||
connectTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20),
|
||||
headers: const {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
),
|
||||
) {
|
||||
this.dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
final token = authToken;
|
||||
if (token != null && token.isNotEmpty) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
handler.next(options);
|
||||
},
|
||||
onError: (error, handler) {
|
||||
final apiError = _exceptionFor(error);
|
||||
final sentAuth =
|
||||
error.requestOptions.headers['Authorization'] != null;
|
||||
if (apiError.isUnauthorized && sentAuth) {
|
||||
onUnauthorized?.call();
|
||||
}
|
||||
handler.reject(
|
||||
DioException(
|
||||
requestOptions: error.requestOptions,
|
||||
response: error.response,
|
||||
type: error.type,
|
||||
error: apiError,
|
||||
message: apiError.message,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final Dio dio;
|
||||
String? authToken;
|
||||
void Function()? onUnauthorized;
|
||||
|
||||
Future<LoginResult> login(String username, String password) {
|
||||
return _send(
|
||||
dio.post<dynamic>(
|
||||
'/api/auth/login',
|
||||
data: {'username': username, 'password': password},
|
||||
),
|
||||
(data) {
|
||||
if (data is! Map) {
|
||||
throw const FormatException('Unexpected login payload');
|
||||
}
|
||||
return LoginResult.fromJson(Map<String, dynamic>.from(data));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<SupportTicket>> listTickets({String? status, String? category}) {
|
||||
return _send(
|
||||
dio.get<dynamic>(
|
||||
'/api/support/tickets',
|
||||
queryParameters: ticketListQuery(status: status, category: category),
|
||||
),
|
||||
(data) {
|
||||
if (data is! Map || data['tickets'] is! List) {
|
||||
throw const FormatException('Unexpected ticket list');
|
||||
}
|
||||
return [
|
||||
for (final item in data['tickets'] as List)
|
||||
if (item is Map)
|
||||
SupportTicket.fromJson(Map<String, dynamic>.from(item)),
|
||||
];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<SupportThread> getThread(String ticketId) {
|
||||
return _send(dio.get<dynamic>('/api/support/tickets/$ticketId'), _thread);
|
||||
}
|
||||
|
||||
Future<SupportThread> reply(String ticketId, String body) {
|
||||
return _send(
|
||||
dio.post<dynamic>(
|
||||
'/api/support/tickets/$ticketId/messages',
|
||||
data: {'body': body},
|
||||
),
|
||||
_thread,
|
||||
);
|
||||
}
|
||||
|
||||
Future<SupportThread> setStatus(String ticketId, String status) {
|
||||
return _send(
|
||||
dio.post<dynamic>(
|
||||
'/api/support/tickets/$ticketId/status',
|
||||
data: {'status': status},
|
||||
),
|
||||
_thread,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> registerDevice({
|
||||
required String token,
|
||||
required String platform,
|
||||
}) {
|
||||
return _send(
|
||||
dio.put<dynamic>(
|
||||
'/api/support/devices',
|
||||
data: {'token': token, 'platform': platform},
|
||||
),
|
||||
(_) {},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> unregisterDevice(String token) {
|
||||
return _send(
|
||||
dio.delete<dynamic>('/api/support/devices', data: {'token': token}),
|
||||
(_) {},
|
||||
);
|
||||
}
|
||||
|
||||
SupportThread _thread(Object? data) {
|
||||
if (data is! Map) {
|
||||
throw const FormatException('Unexpected thread payload');
|
||||
}
|
||||
return SupportThread.fromJson(Map<String, dynamic>.from(data));
|
||||
}
|
||||
|
||||
Future<T> _send<T>(
|
||||
Future<Response<dynamic>> call,
|
||||
T Function(Object? data) parse,
|
||||
) async {
|
||||
try {
|
||||
final response = await call;
|
||||
return parse(response.data);
|
||||
} on DioException catch (error) {
|
||||
final wrapped = error.error;
|
||||
if (wrapped is ApiException) {
|
||||
throw wrapped;
|
||||
}
|
||||
throw _exceptionFor(error);
|
||||
} on FormatException {
|
||||
throw const ApiException(message: 'Unexpected response from the server.');
|
||||
}
|
||||
}
|
||||
|
||||
ApiException _exceptionFor(DioException error) {
|
||||
final offline =
|
||||
error.type == DioExceptionType.connectionError ||
|
||||
error.type == DioExceptionType.connectionTimeout ||
|
||||
error.type == DioExceptionType.receiveTimeout ||
|
||||
error.type == DioExceptionType.sendTimeout;
|
||||
return ApiException.fromBody(
|
||||
statusCode: error.response?.statusCode,
|
||||
body: error.response?.data,
|
||||
fallback: offline
|
||||
? 'Could not reach the server.'
|
||||
: 'Something went wrong.',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user