520 lines
26 KiB
Dart
520 lines
26 KiB
Dart
// lib/services/api_service.dart
|
|
|
|
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:sqflite/sqflite.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:environment_monitoring_app/services/base_api_service.dart';
|
|
|
|
// =======================================================================
|
|
// Part 1: Unified API Service
|
|
// =======================================================================
|
|
|
|
/// A unified service that consolidates all API interactions for the application.
|
|
/// It is organized by feature (e.g., marine, river) for clarity and provides
|
|
/// a central point for data synchronization.
|
|
class ApiService {
|
|
final BaseApiService _baseService = BaseApiService();
|
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
|
|
|
late final MarineApiService marine;
|
|
late final RiverApiService river;
|
|
late final AirApiService air;
|
|
|
|
static const String imageBaseUrl = 'https://dev14.pstw.com.my/';
|
|
|
|
ApiService() {
|
|
marine = MarineApiService(_baseService);
|
|
river = RiverApiService(_baseService);
|
|
air = AirApiService(_baseService);
|
|
}
|
|
|
|
// --- Core API Methods (Unchanged) ---
|
|
|
|
Future<Map<String, dynamic>> login(String email, String password) {
|
|
return _baseService.post('auth/login', {'email': email, 'password': password});
|
|
}
|
|
|
|
Future<Map<String, dynamic>> register({
|
|
required String username,
|
|
String? firstName,
|
|
String? lastName,
|
|
required String email,
|
|
required String password,
|
|
String? phoneNumber,
|
|
int? departmentId,
|
|
int? companyId,
|
|
int? positionId,
|
|
}) {
|
|
final Map<String, dynamic> body = {
|
|
'username': username,
|
|
'email': email,
|
|
'password': password,
|
|
'first_name': firstName ?? '',
|
|
'last_name': lastName ?? '',
|
|
'phone_number': phoneNumber ?? '',
|
|
'department_id': departmentId,
|
|
'company_id': companyId,
|
|
'position_id': positionId,
|
|
};
|
|
body.removeWhere((key, value) => value == null);
|
|
return _baseService.post('auth/register', body);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) {
|
|
return _baseService.post(endpoint, data);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> getProfile() => _baseService.get('profile');
|
|
Future<Map<String, dynamic>> getAllUsers() => _baseService.get('users');
|
|
|
|
Future<Map<String, dynamic>> getAllDepartments() => _baseService.get('departments');
|
|
Future<Map<String, dynamic>> getAllCompanies() => _baseService.get('companies');
|
|
Future<Map<String, dynamic>> getAllPositions() => _baseService.get('positions');
|
|
Future<Map<String, dynamic>> getAllStates() => _baseService.get('states');
|
|
|
|
|
|
Future<Map<String, dynamic>> sendTelegramAlert({
|
|
required String chatId,
|
|
required String message,
|
|
}) {
|
|
return _baseService.post('marine/telegram-alert', {
|
|
'chat_id': chatId,
|
|
'message': message,
|
|
});
|
|
}
|
|
|
|
Future<File?> downloadProfilePicture(String imageUrl, String localPath) async {
|
|
try {
|
|
final response = await http.get(Uri.parse(imageUrl));
|
|
if (response.statusCode == 200) {
|
|
final File file = File(localPath);
|
|
await file.parent.create(recursive: true);
|
|
await file.writeAsBytes(response.bodyBytes);
|
|
return file;
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Error downloading profile picture: $e');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<Map<String, dynamic>> uploadProfilePicture(File imageFile) {
|
|
return _baseService.postMultipart(
|
|
endpoint: 'profile/upload-picture',
|
|
fields: {},
|
|
files: {'profile_picture': imageFile}
|
|
);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> refreshProfile() async {
|
|
debugPrint('ApiService: Refreshing profile data from server...');
|
|
final result = await getProfile();
|
|
if (result['success'] == true && result['data'] != null) {
|
|
await _dbHelper.saveProfile(result['data']);
|
|
debugPrint('ApiService: Profile data refreshed and saved to local DB.');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// --- REWRITTEN FOR DELTA SYNC ---
|
|
|
|
/// Helper method to make a delta-sync API call.
|
|
Future<Map<String, dynamic>> _fetchDelta(String endpoint, String? lastSyncTimestamp) {
|
|
String url = endpoint;
|
|
if (lastSyncTimestamp != null) {
|
|
// Append the 'since' parameter to the URL for delta requests
|
|
url += '?since=$lastSyncTimestamp';
|
|
}
|
|
return _baseService.get(url);
|
|
}
|
|
|
|
/// Orchestrates a full DELTA sync from the server to the local database.
|
|
Future<Map<String, dynamic>> syncAllData({String? lastSyncTimestamp}) async {
|
|
debugPrint('ApiService: Starting DELTA data sync. Since: $lastSyncTimestamp');
|
|
try {
|
|
// Defines all data types to sync, their endpoints, and their DB handlers.
|
|
final syncTasks = {
|
|
'profile': {'endpoint': 'profile', 'handler': (d, id) async { if (d.isNotEmpty) await _dbHelper.saveProfile(d.first); }},
|
|
'allUsers': {'endpoint': 'users', 'handler': (d, id) async { await _dbHelper.upsertUsers(d); await _dbHelper.deleteUsers(id); }},
|
|
'tarballStations': {'endpoint': 'marine/tarball/stations', 'handler': (d, id) async { await _dbHelper.upsertTarballStations(d); await _dbHelper.deleteTarballStations(id); }},
|
|
'manualStations': {'endpoint': 'marine/manual/stations', 'handler': (d, id) async { await _dbHelper.upsertManualStations(d); await _dbHelper.deleteManualStations(id); }},
|
|
'tarballClassifications': {'endpoint': 'marine/tarball/classifications', 'handler': (d, id) async { await _dbHelper.upsertTarballClassifications(d); await _dbHelper.deleteTarballClassifications(id); }},
|
|
'riverManualStations': {'endpoint': 'river/manual-stations', 'handler': (d, id) async { await _dbHelper.upsertRiverManualStations(d); await _dbHelper.deleteRiverManualStations(id); }},
|
|
'riverTriennialStations': {'endpoint': 'river/triennial-stations', 'handler': (d, id) async { await _dbHelper.upsertRiverTriennialStations(d); await _dbHelper.deleteRiverTriennialStations(id); }},
|
|
'departments': {'endpoint': 'departments', 'handler': (d, id) async { await _dbHelper.upsertDepartments(d); await _dbHelper.deleteDepartments(id); }},
|
|
'companies': {'endpoint': 'companies', 'handler': (d, id) async { await _dbHelper.upsertCompanies(d); await _dbHelper.deleteCompanies(id); }},
|
|
'positions': {'endpoint': 'positions', 'handler': (d, id) async { await _dbHelper.upsertPositions(d); await _dbHelper.deletePositions(id); }},
|
|
'airManualStations': {'endpoint': 'air/manual-stations', 'handler': (d, id) async { await _dbHelper.upsertAirManualStations(d); await _dbHelper.deleteAirManualStations(id); }},
|
|
'airClients': {'endpoint': 'air/clients', 'handler': (d, id) async { await _dbHelper.upsertAirClients(d); await _dbHelper.deleteAirClients(id); }},
|
|
'states': {'endpoint': 'states', 'handler': (d, id) async { await _dbHelper.upsertStates(d); await _dbHelper.deleteStates(id); }},
|
|
'appSettings': {'endpoint': 'settings', 'handler': (d, id) async { await _dbHelper.upsertAppSettings(d); await _dbHelper.deleteAppSettings(id); }},
|
|
'parameterLimits': {'endpoint': 'parameter-limits', 'handler': (d, id) async { await _dbHelper.upsertParameterLimits(d); await _dbHelper.deleteParameterLimits(id); }},
|
|
// --- ADDED: New sync tasks for independent API and FTP configurations ---
|
|
'apiConfigs': {'endpoint': 'api-configs', 'handler': (d, id) async { await _dbHelper.upsertApiConfigs(d); await _dbHelper.deleteApiConfigs(id); }},
|
|
'ftpConfigs': {'endpoint': 'ftp-configs', 'handler': (d, id) async { await _dbHelper.upsertFtpConfigs(d); await _dbHelper.deleteFtpConfigs(id); }},
|
|
};
|
|
|
|
// Fetch all deltas in parallel
|
|
final fetchFutures = syncTasks.map((key, value) => MapEntry(key, _fetchDelta(value['endpoint'] as String, lastSyncTimestamp)));
|
|
final results = await Future.wait(fetchFutures.values);
|
|
final resultData = Map.fromIterables(fetchFutures.keys, results);
|
|
|
|
// Process and save all changes
|
|
for (var entry in resultData.entries) {
|
|
final key = entry.key;
|
|
final result = entry.value;
|
|
|
|
if (result['success'] == true && result['data'] != null) {
|
|
// The profile endpoint has a different structure, handle it separately.
|
|
if (key == 'profile') {
|
|
await (syncTasks[key]!['handler'] as Function)([result['data']], []);
|
|
} else {
|
|
final updated = List<Map<String, dynamic>>.from(result['data']['updated'] ?? []);
|
|
final deleted = List<dynamic>.from(result['data']['deleted'] ?? []);
|
|
await (syncTasks[key]!['handler'] as Function)(updated, deleted);
|
|
}
|
|
} else {
|
|
debugPrint('ApiService: Failed to sync $key. Message: ${result['message']}');
|
|
}
|
|
}
|
|
|
|
debugPrint('ApiService: Delta sync complete.');
|
|
return {'success': true, 'message': 'Delta sync successful.'};
|
|
|
|
} catch (e) {
|
|
debugPrint('ApiService: Delta data sync failed: $e');
|
|
return {'success': false, 'message': 'Data sync failed: $e'};
|
|
}
|
|
}
|
|
}
|
|
|
|
// =======================================================================
|
|
// Part 2: Feature-Specific API Services (Unchanged)
|
|
// =======================================================================
|
|
|
|
class AirApiService {
|
|
// ... (No changes needed here)
|
|
final BaseApiService _baseService;
|
|
AirApiService(this._baseService);
|
|
|
|
Future<Map<String, dynamic>> getManualStations() => _baseService.get('air/manual-stations');
|
|
Future<Map<String, dynamic>> getClients() => _baseService.get('air/clients');
|
|
|
|
Future<Map<String, dynamic>> uploadInstallationImages({
|
|
required String airManId,
|
|
required Map<String, File> files,
|
|
}) {
|
|
return _baseService.postMultipart(
|
|
endpoint: 'air/manual/installation-images',
|
|
fields: {'air_man_id': airManId},
|
|
files: files,
|
|
);
|
|
}
|
|
|
|
Future<Map<String, dynamic>> uploadCollectionImages({
|
|
required String airManId,
|
|
required Map<String, File> files,
|
|
}) {
|
|
return _baseService.postMultipart(
|
|
endpoint: 'air/manual/collection-images',
|
|
fields: {'air_man_id': airManId},
|
|
files: files,
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class MarineApiService {
|
|
// ... (No changes needed here)
|
|
final BaseApiService _baseService;
|
|
MarineApiService(this._baseService);
|
|
|
|
Future<Map<String, dynamic>> getTarballStations() => _baseService.get('marine/tarball/stations');
|
|
Future<Map<String, dynamic>> getManualStations() => _baseService.get('marine/manual/stations');
|
|
Future<Map<String, dynamic>> getTarballClassifications() => _baseService.get('marine/tarball/classifications');
|
|
|
|
Future<Map<String, dynamic>> submitTarballSample({
|
|
required Map<String, String> formData,
|
|
required Map<String, File?> imageFiles,
|
|
}) async {
|
|
final dataResult = await _baseService.post('marine/tarball/sample', formData);
|
|
if (dataResult['success'] != true) return {'status': 'L1', 'success': false, 'message': 'Failed to submit data: ${dataResult['message']}'};
|
|
|
|
final recordId = dataResult['data']?['autoid'];
|
|
if (recordId == null) return {'status': 'L2', 'success': false, 'message': 'Data submitted, but failed to get a record ID.'};
|
|
|
|
final filesToUpload = <String, File>{};
|
|
imageFiles.forEach((key, value) { if (value != null) filesToUpload[key] = value; });
|
|
|
|
if (filesToUpload.isEmpty) return {'status': 'L3', 'success': true, 'message': 'Data submitted successfully.', 'reportId': recordId};
|
|
|
|
final imageResult = await _baseService.postMultipart(endpoint: 'marine/tarball/images', fields: {'autoid': recordId.toString()}, files: filesToUpload);
|
|
if (imageResult['success'] != true) return {'status': 'L2', 'success': false, 'message': 'Data submitted, but image upload failed: ${imageResult['message']}', 'reportId': recordId};
|
|
|
|
return {'status': 'L3', 'success': true, 'message': 'Data and images submitted successfully.', 'reportId': recordId};
|
|
}
|
|
}
|
|
|
|
class RiverApiService {
|
|
// ... (No changes needed here)
|
|
final BaseApiService _baseService;
|
|
RiverApiService(this._baseService);
|
|
|
|
Future<Map<String, dynamic>> getManualStations() => _baseService.get('river/manual-stations');
|
|
Future<Map<String, dynamic>> getTriennialStations() => _baseService.get('river/triennial-stations');
|
|
}
|
|
|
|
// =======================================================================
|
|
// Part 3: Local Database Helper (Refactored for Delta Sync)
|
|
// =======================================================================
|
|
|
|
class DatabaseHelper {
|
|
static Database? _database;
|
|
static const String _dbName = 'app_data.db';
|
|
// Incremented DB version to trigger the onUpgrade method
|
|
static const int _dbVersion = 17;
|
|
|
|
static const String _profileTable = 'user_profile';
|
|
static const String _usersTable = 'all_users';
|
|
static const String _tarballStationsTable = 'marine_tarball_stations';
|
|
static const String _manualStationsTable = 'marine_manual_stations';
|
|
static const String _riverManualStationsTable = 'river_manual_stations';
|
|
static const String _riverTriennialStationsTable = 'river_triennial_stations';
|
|
static const String _tarballClassificationsTable = 'marine_tarball_classifications';
|
|
static const String _departmentsTable = 'departments';
|
|
static const String _companiesTable = 'companies';
|
|
static const String _positionsTable = 'positions';
|
|
static const String _alertQueueTable = 'alert_queue';
|
|
static const String _airManualStationsTable = 'air_manual_stations';
|
|
static const String _airClientsTable = 'air_clients';
|
|
static const String _statesTable = 'states';
|
|
// Added new table constants
|
|
static const String _appSettingsTable = 'app_settings';
|
|
static const String _parameterLimitsTable = 'manual_parameter_limits';
|
|
// --- ADDED: New tables for independent API and FTP configurations ---
|
|
static const String _apiConfigsTable = 'api_configurations';
|
|
static const String _ftpConfigsTable = 'ftp_configurations';
|
|
// --- ADDED: New table for the manual retry queue ---
|
|
static const String _retryQueueTable = 'retry_queue';
|
|
|
|
|
|
Future<Database> get database async {
|
|
if (_database != null) return _database!;
|
|
_database = await _initDB();
|
|
return _database!;
|
|
}
|
|
|
|
Future<Database> _initDB() async {
|
|
String dbPath = p.join(await getDatabasesPath(), _dbName);
|
|
return await openDatabase(dbPath, version: _dbVersion, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
|
}
|
|
|
|
Future _onCreate(Database db, int version) async {
|
|
await db.execute('CREATE TABLE $_profileTable(user_id INTEGER PRIMARY KEY, profile_json TEXT)');
|
|
await db.execute('CREATE TABLE $_usersTable(user_id INTEGER PRIMARY KEY, user_json TEXT)');
|
|
await db.execute('CREATE TABLE $_tarballStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
|
|
await db.execute('CREATE TABLE $_manualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
|
|
await db.execute('CREATE TABLE $_riverManualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
|
|
await db.execute('CREATE TABLE $_riverTriennialStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
|
|
await db.execute('CREATE TABLE $_tarballClassificationsTable(classification_id INTEGER PRIMARY KEY, classification_json TEXT)');
|
|
await db.execute('CREATE TABLE $_departmentsTable(department_id INTEGER PRIMARY KEY, department_json TEXT)');
|
|
await db.execute('CREATE TABLE $_companiesTable(company_id INTEGER PRIMARY KEY, company_json TEXT)');
|
|
await db.execute('CREATE TABLE $_positionsTable(position_id INTEGER PRIMARY KEY, position_json TEXT)');
|
|
await db.execute('''CREATE TABLE $_alertQueueTable (id INTEGER PRIMARY KEY AUTOINCREMENT, chat_id TEXT NOT NULL, message TEXT NOT NULL, created_at TEXT NOT NULL)''');
|
|
await db.execute('CREATE TABLE $_airManualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
|
|
await db.execute('CREATE TABLE $_airClientsTable(client_id INTEGER PRIMARY KEY, client_json TEXT)');
|
|
await db.execute('CREATE TABLE $_statesTable(state_id INTEGER PRIMARY KEY, state_json TEXT)');
|
|
// Added create statements for new tables
|
|
await db.execute('CREATE TABLE $_appSettingsTable(setting_id INTEGER PRIMARY KEY, setting_json TEXT)');
|
|
await db.execute('CREATE TABLE $_parameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
|
|
// --- ADDED: Create statements for new configuration tables ---
|
|
await db.execute('CREATE TABLE $_apiConfigsTable(api_config_id INTEGER PRIMARY KEY, config_json TEXT)');
|
|
await db.execute('CREATE TABLE $_ftpConfigsTable(ftp_config_id INTEGER PRIMARY KEY, config_json TEXT)');
|
|
// --- ADDED: Create statement for the new retry queue table ---
|
|
await db.execute('''
|
|
CREATE TABLE $_retryQueueTable(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
type TEXT NOT NULL,
|
|
endpoint_or_path TEXT NOT NULL,
|
|
payload TEXT,
|
|
timestamp TEXT NOT NULL,
|
|
status TEXT NOT NULL
|
|
)
|
|
''');
|
|
}
|
|
|
|
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
|
if (oldVersion < 11) {
|
|
await db.execute('CREATE TABLE IF NOT EXISTS $_airManualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
|
|
await db.execute('CREATE TABLE IF NOT EXISTS $_airClientsTable(client_id INTEGER PRIMARY KEY, client_json TEXT)');
|
|
}
|
|
if (oldVersion < 12) {
|
|
await db.execute('CREATE TABLE IF NOT EXISTS $_statesTable(state_id INTEGER PRIMARY KEY, state_json TEXT)');
|
|
}
|
|
if (oldVersion < 13) {
|
|
await db.execute('CREATE TABLE IF NOT EXISTS $_appSettingsTable(setting_id INTEGER PRIMARY KEY, setting_json TEXT)');
|
|
await db.execute('CREATE TABLE IF NOT EXISTS $_parameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
|
|
}
|
|
// --- ADDED: Upgrade logic for new configuration tables ---
|
|
if (oldVersion < 16) {
|
|
await db.execute('CREATE TABLE IF NOT EXISTS $_apiConfigsTable(api_config_id INTEGER PRIMARY KEY, config_json TEXT)');
|
|
await db.execute('CREATE TABLE IF NOT EXISTS $_ftpConfigsTable(ftp_config_id INTEGER PRIMARY KEY, config_json TEXT)');
|
|
}
|
|
// --- ADDED: Upgrade logic for the new retry queue table ---
|
|
if (oldVersion < 17) {
|
|
await db.execute('''
|
|
CREATE TABLE IF NOT EXISTS $_retryQueueTable(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
type TEXT NOT NULL,
|
|
endpoint_or_path TEXT NOT NULL,
|
|
payload TEXT,
|
|
timestamp TEXT NOT NULL,
|
|
status TEXT NOT NULL
|
|
)
|
|
''');
|
|
}
|
|
}
|
|
|
|
/// Performs an "upsert": inserts new records or replaces existing ones.
|
|
Future<void> _upsertData(String table, String idKeyName, List<Map<String, dynamic>> data, String jsonKeyName) async {
|
|
if (data.isEmpty) return;
|
|
final db = await database;
|
|
final batch = db.batch();
|
|
for (var item in data) {
|
|
batch.insert(
|
|
table,
|
|
{idKeyName: item[idKeyName], '${jsonKeyName}_json': jsonEncode(item)},
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
}
|
|
await batch.commit(noResult: true);
|
|
debugPrint("Upserted ${data.length} items into $table");
|
|
}
|
|
|
|
/// Deletes a list of records from a table by their primary keys.
|
|
Future<void> _deleteData(String table, String idKeyName, List<dynamic> ids) async {
|
|
if (ids.isEmpty) return;
|
|
final db = await database;
|
|
final placeholders = List.filled(ids.length, '?').join(', ');
|
|
await db.delete(
|
|
table,
|
|
where: '$idKeyName IN ($placeholders)',
|
|
whereArgs: ids,
|
|
);
|
|
debugPrint("Deleted ${ids.length} items from $table");
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>?> _loadData(String table, String jsonKey) async {
|
|
final db = await database;
|
|
final List<Map<String, dynamic>> maps = await db.query(table);
|
|
if (maps.isNotEmpty) {
|
|
return maps.map((map) => jsonDecode(map['${jsonKey}_json']) as Map<String, dynamic>).toList();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<void> saveProfile(Map<String, dynamic> profile) async {
|
|
final db = await database;
|
|
await db.insert(_profileTable, {'user_id': profile['user_id'], 'profile_json': jsonEncode(profile)}, conflictAlgorithm: ConflictAlgorithm.replace);
|
|
}
|
|
Future<Map<String, dynamic>?> loadProfile() async {
|
|
final db = await database;
|
|
final List<Map<String, dynamic>> maps = await db.query(_profileTable);
|
|
if(maps.isNotEmpty) return jsonDecode(maps.first['profile_json']);
|
|
return null;
|
|
}
|
|
|
|
// --- Upsert/Delete/Load methods for all data types ---
|
|
|
|
Future<void> upsertUsers(List<Map<String, dynamic>> data) => _upsertData(_usersTable, 'user_id', data, 'user');
|
|
Future<void> deleteUsers(List<dynamic> ids) => _deleteData(_usersTable, 'user_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadUsers() => _loadData(_usersTable, 'user');
|
|
|
|
Future<void> upsertTarballStations(List<Map<String, dynamic>> data) => _upsertData(_tarballStationsTable, 'station_id', data, 'station');
|
|
Future<void> deleteTarballStations(List<dynamic> ids) => _deleteData(_tarballStationsTable, 'station_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadTarballStations() => _loadData(_tarballStationsTable, 'station');
|
|
|
|
Future<void> upsertManualStations(List<Map<String, dynamic>> data) => _upsertData(_manualStationsTable, 'station_id', data, 'station');
|
|
Future<void> deleteManualStations(List<dynamic> ids) => _deleteData(_manualStationsTable, 'station_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadManualStations() => _loadData(_manualStationsTable, 'station');
|
|
|
|
Future<void> upsertRiverManualStations(List<Map<String, dynamic>> data) => _upsertData(_riverManualStationsTable, 'station_id', data, 'station');
|
|
Future<void> deleteRiverManualStations(List<dynamic> ids) => _deleteData(_riverManualStationsTable, 'station_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadRiverManualStations() => _loadData(_riverManualStationsTable, 'station');
|
|
|
|
Future<void> upsertRiverTriennialStations(List<Map<String, dynamic>> data) => _upsertData(_riverTriennialStationsTable, 'station_id', data, 'station');
|
|
Future<void> deleteRiverTriennialStations(List<dynamic> ids) => _deleteData(_riverTriennialStationsTable, 'station_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadRiverTriennialStations() => _loadData(_riverTriennialStationsTable, 'station');
|
|
|
|
Future<void> upsertTarballClassifications(List<Map<String, dynamic>> data) => _upsertData(_tarballClassificationsTable, 'classification_id', data, 'classification');
|
|
Future<void> deleteTarballClassifications(List<dynamic> ids) => _deleteData(_tarballClassificationsTable, 'classification_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadTarballClassifications() => _loadData(_tarballClassificationsTable, 'classification');
|
|
|
|
Future<void> upsertDepartments(List<Map<String, dynamic>> data) => _upsertData(_departmentsTable, 'department_id', data, 'department');
|
|
Future<void> deleteDepartments(List<dynamic> ids) => _deleteData(_departmentsTable, 'department_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadDepartments() => _loadData(_departmentsTable, 'department');
|
|
|
|
Future<void> upsertCompanies(List<Map<String, dynamic>> data) => _upsertData(_companiesTable, 'company_id', data, 'company');
|
|
Future<void> deleteCompanies(List<dynamic> ids) => _deleteData(_companiesTable, 'company_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadCompanies() => _loadData(_companiesTable, 'company');
|
|
|
|
Future<void> upsertPositions(List<Map<String, dynamic>> data) => _upsertData(_positionsTable, 'position_id', data, 'position');
|
|
Future<void> deletePositions(List<dynamic> ids) => _deleteData(_positionsTable, 'position_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadPositions() => _loadData(_positionsTable, 'position');
|
|
|
|
Future<void> upsertAirManualStations(List<Map<String, dynamic>> data) => _upsertData(_airManualStationsTable, 'station_id', data, 'station');
|
|
Future<void> deleteAirManualStations(List<dynamic> ids) => _deleteData(_airManualStationsTable, 'station_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadAirManualStations() => _loadData(_airManualStationsTable, 'station');
|
|
|
|
Future<void> upsertAirClients(List<Map<String, dynamic>> data) => _upsertData(_airClientsTable, 'client_id', data, 'client');
|
|
Future<void> deleteAirClients(List<dynamic> ids) => _deleteData(_airClientsTable, 'client_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadAirClients() => _loadData(_airClientsTable, 'client');
|
|
|
|
Future<void> upsertStates(List<Map<String, dynamic>> data) => _upsertData(_statesTable, 'state_id', data, 'state');
|
|
Future<void> deleteStates(List<dynamic> ids) => _deleteData(_statesTable, 'state_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadStates() => _loadData(_statesTable, 'state');
|
|
|
|
Future<void> upsertAppSettings(List<Map<String, dynamic>> data) => _upsertData(_appSettingsTable, 'setting_id', data, 'setting');
|
|
Future<void> deleteAppSettings(List<dynamic> ids) => _deleteData(_appSettingsTable, 'setting_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadAppSettings() => _loadData(_appSettingsTable, 'setting');
|
|
|
|
Future<void> upsertParameterLimits(List<Map<String, dynamic>> data) => _upsertData(_parameterLimitsTable, 'param_autoid', data, 'limit');
|
|
Future<void> deleteParameterLimits(List<dynamic> ids) => _deleteData(_parameterLimitsTable, 'param_autoid', ids);
|
|
Future<List<Map<String, dynamic>>?> loadParameterLimits() => _loadData(_parameterLimitsTable, 'limit');
|
|
|
|
// --- ADDED: Methods for independent API and FTP configurations ---
|
|
Future<void> upsertApiConfigs(List<Map<String, dynamic>> data) => _upsertData(_apiConfigsTable, 'api_config_id', data, 'config');
|
|
Future<void> deleteApiConfigs(List<dynamic> ids) => _deleteData(_apiConfigsTable, 'api_config_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadApiConfigs() => _loadData(_apiConfigsTable, 'config');
|
|
|
|
Future<void> upsertFtpConfigs(List<Map<String, dynamic>> data) => _upsertData(_ftpConfigsTable, 'ftp_config_id', data, 'config');
|
|
Future<void> deleteFtpConfigs(List<dynamic> ids) => _deleteData(_ftpConfigsTable, 'ftp_config_id', ids);
|
|
Future<List<Map<String, dynamic>>?> loadFtpConfigs() => _loadData(_ftpConfigsTable, 'config');
|
|
|
|
// --- ADDED: Methods for the new retry queue ---
|
|
Future<int> queueFailedRequest(Map<String, dynamic> data) async {
|
|
final db = await database;
|
|
return await db.insert(_retryQueueTable, data, conflictAlgorithm: ConflictAlgorithm.replace);
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getPendingRequests() async {
|
|
final db = await database;
|
|
return await db.query(_retryQueueTable, where: 'status = ?', whereArgs: ['pending']);
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> getRequestById(int id) async {
|
|
final db = await database;
|
|
final results = await db.query(_retryQueueTable, where: 'id = ?', whereArgs: [id]);
|
|
return results.isNotEmpty ? results.first : null;
|
|
}
|
|
|
|
Future<void> deleteRequestFromQueue(int id) async {
|
|
final db = await database;
|
|
await db.delete(_retryQueueTable, where: 'id = ?', whereArgs: [id]);
|
|
}
|
|
} |