import 'dart:async'; import 'dart:convert'; import 'package:get/get.dart'; import 'package:tuuli_app/api/model/access_token_model.dart'; import 'package:http/browser_client.dart'; import 'package:http/http.dart'; import 'package:tuuli_app/api/model/table_field_model.dart'; import 'package:tuuli_app/api/model/tables_list_model.dart'; class ErrorOrData { final T? data; final Exception? error; ErrorOrData(this.data, this.error); void unfold( void Function(T data) onData, void Function(Exception error) onError) { if (data != null) { onData(data as T); } else { onError(error!); } } } typedef FutureErrorOrData = Future>; typedef TableItemsData = Map; typedef TableItemsDataList = List; class ApiClient { final BrowserClient _client = BrowserClient(); var _accessToken = ''; final Uri baseUrl; ApiClient(this.baseUrl); ApiClient.fromString(String baseUrl) : this(Uri.parse(baseUrl)); void setAccessToken(String accessToken) { _accessToken = accessToken; } FutureErrorOrData login( String username, String password, ) async { AccessTokenModel? data; Exception? error; final response = await post('/api/getAccessToken', body: { 'username': username, 'password': password, }, headers: { 'Content-Type': 'application/json', }); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()); if (body['error'] != null) { error = Exception(body['error']); } else if (body['access_token'] == null) { error = Exception('No access token'); } else { data = AccessTokenModel(accessToken: body['access_token']); } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(data, error); } FutureErrorOrData tablesList() async { TablesListModel? data; Exception? error; final response = await get('/api/listTables'); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()); if (body['error'] != null) { error = Exception(body['error']); } else if (body['tables'] == null) { error = Exception('Server error'); } else { data = TablesListModel.fromJson(body); } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(data, error); } FutureErrorOrData createTable( String tableName, List columns) async { bool? ignored; Exception? error; final response = await post('/api/createTable/${Uri.encodeComponent(tableName)}', body: { 'columns': columns.map((e) => e.toColumnDefinition()).toList(growable: false), }, headers: { 'Content-Type': 'application/json', }); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()); if (body['error'] != null) { error = Exception(body['error']); } else { ignored = true; } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(ignored, error); } FutureErrorOrData dropTable(String tableName) async { bool? ignored; Exception? error; final response = await post('/api/dropTable/${Uri.encodeComponent(tableName)}'); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()); if (body['error'] != null) { error = Exception(body['error']); } else { ignored = true; } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(ignored, error); } FutureErrorOrData getTableItems(TableModel table) async { TableItemsDataList? data; Exception? error; final response = await post( '/items/${Uri.encodeComponent(table.tableName)}', body: { "fields": ["*"] }, headers: { 'Content-Type': 'application/json', }, ); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()) as Map; if (body['error'] != null) { error = Exception(body['error']); } else if (body['items'] == null) { error = Exception('Server error'); } else { data = (body['items'] as List) .map((e) => e as TableItemsData) .toList(growable: false); } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(data, error); } FutureErrorOrData insertItem( TableModel table, TableItemsData newItem) async { bool? ignored; Exception? error; final response = await post( '/items/${Uri.encodeComponent(table.tableName)}/+', body: newItem.map((key, value) => MapEntry(key, value is DateTime ? value.toIso8601String() : value)), headers: { 'Content-Type': 'application/json', }, ); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()); if (body['error'] != null) { error = Exception(body['error']); } else { ignored = true; } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(ignored, error); } FutureErrorOrData updateItem( TableModel table, TableItemsData newItem, TableItemsData oldItem) async { bool? ignored; Exception? error; final response = await post( '/items/${Uri.encodeComponent(table.tableName)}/*', body: { "item": newItem.map((key, value) => MapEntry(key, value is DateTime ? value.toIso8601String() : value)), "oldItem": oldItem.map((key, value) => MapEntry(key, value is DateTime ? value.toIso8601String() : value)), }, headers: { 'Content-Type': 'application/json', }, ); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()); if (body['error'] != null) { error = Exception(body['error']); } else { ignored = true; } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(ignored, error); } FutureErrorOrData deleteItem(TableModel table, TableItemsData e) async { bool? ignored; Exception? error; TableField? primaryField = table.columns.firstWhereOrNull((el) => el.isPrimary); TableField? uniqueField = table.columns.firstWhereOrNull((el) => el.isUnique); final response = await post( '/items/${Uri.encodeComponent(table.tableName)}/-', body: { "defs": [ if (primaryField != null) { "name": primaryField.fieldName, "value": e[primaryField.fieldName], } else if (uniqueField != null) { "name": uniqueField.fieldName, "value": e[uniqueField.fieldName], } else for (final field in table.columns) { "name": field.fieldName, "value": e[field.fieldName], } ], }, headers: { 'Content-Type': 'application/json', }, ); if (response.statusCode == 200) { final body = json.decode(await response.stream.bytesToString()); if (body['error'] != null) { error = Exception(body['error']); } else { ignored = true; } } else if (response.statusCode == 422) { error = Exception('Invalid request parameters'); } else { error = Exception('HTTP ${response.statusCode}'); } return ErrorOrData(ignored, error); } // REGION: HTTP Methods implementation Future get( String path, { Map? headers, }) { return _request(path, 'GET', headers: headers); } Future post( String path, { Map? headers, dynamic body, }) { return _request(path, 'POST', headers: headers, body: body); } Future _request( String path, String method, { Map? headers, dynamic body, }) async { final uri = baseUrl.resolve(path); final request = Request(method, uri); if (headers != null) { request.headers.addAll(headers); } if (_accessToken.isNotEmpty) { request.headers["Access-Token"] = _accessToken; } if (body != null) { request.body = json.encode(body); } return _client.send(request); } }