176 lines
4.8 KiB
Dart
176 lines
4.8 KiB
Dart
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.',
|
|
);
|
|
}
|
|
}
|