Files
2026-10-16 01:25:50 +05:30

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.',
);
}
}