202 lines
6.3 KiB
Dart
202 lines
6.3 KiB
Dart
// lib/services/firefly_api_service.dart
|
|
|
|
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import '../models/firefly_account.dart';
|
|
|
|
class FireflyApiService {
|
|
/// Mengambil daftar akun dari Firefly III API.
|
|
///
|
|
/// [baseUrl] adalah URL dasar instance Firefly III (e.g., https://firefly.yourdomain.com).
|
|
/// [accessToken] adalah Personal Access Token pengguna.
|
|
/// [type] adalah filter opsional untuk tipe akun (e.g., 'asset', 'revenue').
|
|
static Future<List<FireflyAccount>> fetchAccounts({
|
|
required String baseUrl,
|
|
required String accessToken,
|
|
String? type,
|
|
}) async {
|
|
final uri = type != null
|
|
? Uri.parse('$baseUrl/api/v1/accounts?type=$type')
|
|
: Uri.parse('$baseUrl/api/v1/accounts');
|
|
|
|
final response = await http.get(
|
|
uri,
|
|
headers: {
|
|
'Authorization': 'Bearer $accessToken',
|
|
'Accept': 'application/json',
|
|
},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final dynamic responseBody = json.decode(response.body);
|
|
|
|
if (responseBody is Map<String, dynamic> &&
|
|
responseBody.containsKey('data')) {
|
|
final List accountsJson = responseBody['data'] as List;
|
|
|
|
final List<FireflyAccount> accounts = accountsJson
|
|
.map((accountJson) => FireflyAccount.fromJson(accountJson))
|
|
.toList();
|
|
|
|
return accounts;
|
|
} else {
|
|
throw Exception('Format respons API tidak sesuai');
|
|
}
|
|
} else {
|
|
// Handle error - misalnya, lempar exception
|
|
throw Exception('Gagal mengambil akun: ${response.statusCode}');
|
|
}
|
|
}
|
|
|
|
/// Mengirim transaksi dummy ke Firefly III API.
|
|
///
|
|
/// [baseUrl] adalah URL dasar instance Firefly III.
|
|
/// [accessToken] adalah Personal Access Token pengguna.
|
|
/// [sourceId] dan [destinationId] adalah ID akun yang valid.
|
|
/// [type] adalah tipe transaksi (default 'deposit').
|
|
static Future<String?> submitDummyTransaction({
|
|
required String baseUrl,
|
|
required String accessToken,
|
|
required String sourceId,
|
|
required String destinationId,
|
|
String type = 'deposit',
|
|
String description = 'Pengeluaran Dummy via Flutter App',
|
|
String date = '2025-08-19', // Gunakan format ISO 8601
|
|
String amount = '50.00',
|
|
}) async {
|
|
final uri = Uri.parse('$baseUrl/api/v1/transactions');
|
|
final payload = jsonEncode({
|
|
"transactions": [
|
|
{
|
|
"type": type,
|
|
"date": date,
|
|
"amount": amount,
|
|
"description": description,
|
|
"source_id": sourceId,
|
|
"destination_id": destinationId,
|
|
}
|
|
]
|
|
});
|
|
|
|
try {
|
|
final response = await http.post(
|
|
uri,
|
|
headers: {
|
|
'Authorization': 'Bearer $accessToken',
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: payload,
|
|
);
|
|
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
// Parse response to get transaction ID
|
|
try {
|
|
final dynamic responseBody = json.decode(response.body);
|
|
|
|
if (responseBody is Map<String, dynamic> &&
|
|
responseBody.containsKey('data')) {
|
|
final dynamic data = responseBody['data'];
|
|
|
|
// Handle case where data is a single object with an 'id' field
|
|
if (data is Map<String, dynamic> && data.containsKey('id')) {
|
|
final transactionId = data['id'].toString();
|
|
return transactionId;
|
|
}
|
|
|
|
// Handle case where data is a list with at least one object
|
|
if (data is List && data.isNotEmpty) {
|
|
final firstItem = data[0];
|
|
if (firstItem is Map<String, dynamic>) {
|
|
if (firstItem.containsKey('id')) {
|
|
final transactionId = firstItem['id'].toString();
|
|
return transactionId;
|
|
} else if (firstItem.containsKey('transaction_id')) {
|
|
final transactionId = firstItem['transaction_id'].toString();
|
|
return transactionId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Don't crash if parsing fails, just return success
|
|
}
|
|
|
|
// If we can't parse the ID, return a default success indicator
|
|
return "success";
|
|
} else {
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Mengunggah lampiran langsung ke transaksi di Firefly III.
|
|
///
|
|
/// [baseUrl] adalah URL dasar instance Firefly III.
|
|
/// [accessToken] adalah Personal Access Token pengguna.
|
|
/// [transactionId] adalah ID transaksi yang akan dilampiri.
|
|
/// [fileBytes] adalah byte data dari file lampiran.
|
|
static Future<bool> uploadAttachment({
|
|
required String baseUrl,
|
|
required String accessToken,
|
|
required String transactionId,
|
|
required List<int> fileBytes,
|
|
}) async {
|
|
final uri = Uri.parse('$baseUrl/api/v1/attachments/$transactionId/upload');
|
|
|
|
try {
|
|
// Kirim file sebagai raw binary data dengan Content-Type: application/octet-stream
|
|
final response = await http.post(
|
|
uri,
|
|
headers: {
|
|
'Authorization': 'Bearer $accessToken',
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/octet-stream',
|
|
},
|
|
body: fileBytes, // Kirim byte array langsung sebagai body
|
|
);
|
|
|
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
|
return true;
|
|
} else {
|
|
print('Upload attachment failed with status: ${response.statusCode}');
|
|
print('Response body: ${response.body}');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
print('Upload attachment failed with exception: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Menguji koneksi ke Firefly III.
|
|
static Future<bool> testConnection({required String baseUrl}) async {
|
|
try {
|
|
final response = await http.get(Uri.parse(baseUrl));
|
|
return response.statusCode == 200;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Menguji autentikasi dengan token.
|
|
static Future<bool> testAuthentication(
|
|
{required String baseUrl, required String accessToken}) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$baseUrl/api/v1/about/user'),
|
|
headers: {
|
|
'Authorization': 'Bearer $accessToken',
|
|
'Accept': 'application/json',
|
|
},
|
|
);
|
|
return response.statusCode == 200;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|