176 lines
4.8 KiB
Dart
176 lines
4.8 KiB
Dart
import "dart:convert";
|
|
import "package:http/http.dart" as http;
|
|
|
|
import "../models/restaurant.dart";
|
|
import "../models/service_point.dart";
|
|
|
|
class Api {
|
|
static String get baseUrl {
|
|
const v = String.fromEnvironment("API_BASE_URL");
|
|
if (v.isEmpty) {
|
|
throw StateError(
|
|
"API_BASE_URL is not set. Example (Android emulator): "
|
|
"--dart-define=API_BASE_URL=http://10.0.2.2:8888/biz.payfrit.com/api",
|
|
);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static Uri _u(String path) {
|
|
final normalizedBase =
|
|
baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
|
|
final normalizedPath = path.startsWith("/") ? path : "/$path";
|
|
return Uri.parse("$normalizedBase$normalizedPath");
|
|
}
|
|
|
|
static Future<_ApiResponse> _getJson(String path) async {
|
|
final url = _u(path);
|
|
final resp = await http.get(url);
|
|
|
|
final body = resp.body;
|
|
Map<String, dynamic>? json;
|
|
try {
|
|
final decoded = jsonDecode(body);
|
|
if (decoded is Map<String, dynamic>) {
|
|
json = decoded;
|
|
}
|
|
} catch (_) {
|
|
// leave json null
|
|
}
|
|
|
|
// ignore: avoid_print
|
|
print("API GET => $url");
|
|
// ignore: avoid_print
|
|
print("STATUS => ${resp.statusCode}");
|
|
// ignore: avoid_print
|
|
print("BODY => ${body.length > 1200 ? body.substring(0, 1200) + '…' : body}");
|
|
|
|
return _ApiResponse(
|
|
statusCode: resp.statusCode,
|
|
rawBody: body,
|
|
json: json,
|
|
);
|
|
}
|
|
|
|
static Future<_ApiResponse> _postJson(String path, Map<String, dynamic> payload) async {
|
|
final url = _u(path);
|
|
final resp = await http.post(
|
|
url,
|
|
headers: {"Content-Type": "application/json"},
|
|
body: jsonEncode(payload),
|
|
);
|
|
|
|
final body = resp.body;
|
|
Map<String, dynamic>? json;
|
|
try {
|
|
final decoded = jsonDecode(body);
|
|
if (decoded is Map<String, dynamic>) {
|
|
json = decoded;
|
|
}
|
|
} catch (_) {
|
|
// leave json null
|
|
}
|
|
|
|
// ignore: avoid_print
|
|
print("API POST => $url");
|
|
// ignore: avoid_print
|
|
print("BODY IN => ${jsonEncode(payload)}");
|
|
// ignore: avoid_print
|
|
print("STATUS => ${resp.statusCode}");
|
|
// ignore: avoid_print
|
|
print("BODY OUT => ${body.length > 1200 ? body.substring(0, 1200) + '…' : body}");
|
|
|
|
return _ApiResponse(
|
|
statusCode: resp.statusCode,
|
|
rawBody: body,
|
|
json: json,
|
|
);
|
|
}
|
|
|
|
static void _throwIfApiError(Map<String, dynamic> data, String context) {
|
|
if (data.containsKey("OK") && data["OK"] is bool && (data["OK"] as bool) == false) {
|
|
final err = data["ERROR"];
|
|
final detail = data["DETAIL"];
|
|
final msg = data["MESSAGE"];
|
|
throw StateError(
|
|
"$context API returned OK=false"
|
|
"${err != null ? "\nERROR: $err" : ""}"
|
|
"${msg != null ? "\nMESSAGE: $msg" : ""}"
|
|
"${detail != null ? "\nDETAIL: $detail" : ""}",
|
|
);
|
|
}
|
|
}
|
|
|
|
static Future<_ApiResponse> listRestaurantsRaw() async {
|
|
return _getJson("/businesses/list.cfm");
|
|
}
|
|
|
|
static Future<List<Restaurant>> listRestaurants() async {
|
|
final r = await listRestaurantsRaw();
|
|
if (r.statusCode != 200) {
|
|
throw StateError("Restaurants request failed: ${r.statusCode}\n${r.rawBody}");
|
|
}
|
|
|
|
final data = r.json;
|
|
if (data == null) {
|
|
throw StateError("Restaurants response was not JSON.\n${r.rawBody}");
|
|
}
|
|
|
|
_throwIfApiError(data, "Restaurants");
|
|
|
|
final rows = data["Businesses"];
|
|
if (rows is! List) {
|
|
throw StateError(
|
|
"Restaurants JSON missing Businesses array.\nKeys present: ${data.keys.toList()}\nRaw: ${r.rawBody}",
|
|
);
|
|
}
|
|
|
|
return rows
|
|
.whereType<Map<String, dynamic>>()
|
|
.map((e) => Restaurant.fromJson(e))
|
|
.toList();
|
|
}
|
|
|
|
static Future<_ApiResponse> listServicePointsRaw({required int businessId}) async {
|
|
return _postJson("/servicepoints/list.cfm", {"BusinessID": businessId});
|
|
}
|
|
|
|
static Future<List<ServicePoint>> listServicePoints({required int businessId}) async {
|
|
final r = await listServicePointsRaw(businessId: businessId);
|
|
if (r.statusCode != 200) {
|
|
throw StateError("ServicePoints request failed: ${r.statusCode}\n${r.rawBody}");
|
|
}
|
|
|
|
final data = r.json;
|
|
if (data == null) {
|
|
throw StateError("ServicePoints response was not JSON.\n${r.rawBody}");
|
|
}
|
|
|
|
_throwIfApiError(data, "ServicePoints");
|
|
|
|
// STRICT: hump case
|
|
final rows = data["ServicePoints"];
|
|
if (rows is! List) {
|
|
throw StateError(
|
|
"ServicePoints JSON missing ServicePoints array.\nKeys present: ${data.keys.toList()}\nRaw: ${r.rawBody}",
|
|
);
|
|
}
|
|
|
|
return rows
|
|
.whereType<Map<String, dynamic>>()
|
|
.map((e) => ServicePoint.fromJson(e))
|
|
.toList();
|
|
}
|
|
}
|
|
|
|
class _ApiResponse {
|
|
final int statusCode;
|
|
final String rawBody;
|
|
final Map<String, dynamic>? json;
|
|
|
|
const _ApiResponse({
|
|
required this.statusCode,
|
|
required this.rawBody,
|
|
required this.json,
|
|
});
|
|
}
|