From c90107dd5c2d7cd80da6d5c99d58eb28de8fc755 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 16 Dec 2025 10:31:58 +0530 Subject: [PATCH 1/4] init t_community db version 3 migration --- lib/services/db/app/communities.dart | 52 ++++++++++++++++++++++++++++ lib/services/db/app/db.dart | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/services/db/app/communities.dart b/lib/services/db/app/communities.dart index 46eefac1..a02332f2 100644 --- a/lib/services/db/app/communities.dart +++ b/lib/services/db/app/communities.dart @@ -3,6 +3,7 @@ import 'package:citizenwallet/services/config/config.dart'; import 'package:citizenwallet/services/config/legacy.dart'; import 'package:citizenwallet/services/config/service.dart'; import 'package:citizenwallet/services/db/db.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:sqflite/sqflite.dart'; @@ -28,6 +29,50 @@ Future> legacyToV4(Database db, String name) async { return v4Configs; } +Future> V5Migration(Database db, String name) async { + try { + final ConfigService config = ConfigService(); + final localConfigs = await config.getLocalConfigs(); + + final List> maps = await db.query(name); + final existingCommunities = List.generate(maps.length, (i) { + return DBCommunity.fromMap(maps[i]); + }); + + final List updatedConfigs = []; + + for (final localConfig in localConfigs) { + final existingCommunity = existingCommunities.firstWhereOrNull( + (c) => c.alias == localConfig.community.alias, + ); + + if (existingCommunity != null) { + // Update existing community, preserve online status + final updatedCommunity = DBCommunity( + alias: localConfig.community.alias, + config: localConfig.toJson(), + hidden: localConfig.community.hidden, + version: localConfig.version, + online: existingCommunity.online, + ); + updatedConfigs.add(updatedCommunity); + } else { + // New community in v5 + updatedConfigs.add(DBCommunity.fromConfig(localConfig)); + } + } + + return updatedConfigs; + } catch (e, s) { + debugPrint('ERROR in V5Migration: $e'); + debugPrintStack(stackTrace: s); + + // Return existing data unchanged on error + final List> maps = await db.query(name); + return List.generate(maps.length, (i) => DBCommunity.fromMap(maps[i])); + } +} + class DBCommunity { final String alias; // index final bool hidden; @@ -118,6 +163,9 @@ class CommunityTable extends DBTable { 2: [ 'V4Migration', ], + 3: [ + 'V5Migration', + ], }; for (var i = oldVersion + 1; i <= newVersion; i++) { @@ -131,6 +179,10 @@ class CommunityTable extends DBTable { final updatedConfigs = await legacyToV4(db, name); await upsert(updatedConfigs); continue; + case 'V5Migration': + final updatedConfigs = await V5Migration(db, name); + await upsert(updatedConfigs); + continue; } await db.execute(query); diff --git a/lib/services/db/app/db.dart b/lib/services/db/app/db.dart index f73c927d..65e25b60 100644 --- a/lib/services/db/app/db.dart +++ b/lib/services/db/app/db.dart @@ -27,7 +27,7 @@ class AppDBService extends DBService { await communities.migrate(db, oldVersion, newVersion); return; }, - version: 2, + version: 3, ); final db = await databaseFactory.openDatabase( From 29c78bc542884be9f40a60fde74f17dae89c944a Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 16 Dec 2025 11:11:56 +0530 Subject: [PATCH 2/4] wrap function implementation in try-catch --- lib/services/config/service.dart | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/services/config/service.dart b/lib/services/config/service.dart index 4b1b3ef0..80b3748f 100644 --- a/lib/services/config/service.dart +++ b/lib/services/config/service.dart @@ -163,13 +163,19 @@ class ConfigService { } Future> getLocalConfigs() async { - final localConfigs = jsonDecode(await rootBundle.loadString( - 'assets/config/v$version/$communityConfigListFileName.json')); + try { + final localConfigs = jsonDecode(await rootBundle.loadString( + 'assets/config/v$version/$communityConfigListFileName.json')); - final configs = - (localConfigs as List).map((e) => Config.fromJson(e)).toList(); + final configs = + (localConfigs as List).map((e) => Config.fromJson(e)).toList(); - return configs; + return configs; + } catch (e, s) { + debugPrint('ERROR in getLocalConfigs: $e'); + debugPrintStack(stackTrace: s); + return []; + } } Future getRemoteConfig(String remoteConfigUrl) async { From 9db6e9eec4dc6b48cd3066d3f1818b5b63c30acd Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 16 Dec 2025 12:09:12 +0530 Subject: [PATCH 3/4] wrap t_community seed implementation in try-catch --- lib/services/db/app/communities.dart | 39 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/services/db/app/communities.dart b/lib/services/db/app/communities.dart index a02332f2..7ed443a8 100644 --- a/lib/services/db/app/communities.dart +++ b/lib/services/db/app/communities.dart @@ -180,6 +180,7 @@ class CommunityTable extends DBTable { await upsert(updatedConfigs); continue; case 'V5Migration': + debugPrint('V5Migration'); final updatedConfigs = await V5Migration(db, name); await upsert(updatedConfigs); continue; @@ -196,26 +197,32 @@ class CommunityTable extends DBTable { } Future seed() async { - final localConfigs = await _config.getLocalConfigs(); + try { + // Check if the table is empty + final count = Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM $name')); - // Check if the table is empty - final count = - Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM $name')); - if (count != null && count > 0) { - return; // Table is not empty, skip seeding - } + if (count != null && count > 0) { + return; // Table is not empty, skip seeding + } - // Prepare batch operation for efficient insertion - final batch = db.batch(); + final localConfigs = await _config.getLocalConfigs(); - for (final config in localConfigs) { - batch.insert( - name, - DBCommunity.fromConfig(config).toMap(), - ); - } + // Prepare batch operation for efficient insertion + final batch = db.batch(); - await batch.commit(noResult: true); + for (final config in localConfigs) { + batch.insert( + name, + DBCommunity.fromConfig(config).toMap(), + ); + } + + await batch.commit(noResult: true); + } catch (e, s) { + print('Error seeding communities table: $e'); + print('Stack trace: $s'); + } } Future upsert(List communities) async { From a65427d0b6e870ac688032f8b44d7a71c6dbd676 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 16 Dec 2025 13:17:38 +0530 Subject: [PATCH 4/4] fix: parse API wrapper structure in config service responses The config API endpoints now return responses wrapped in an object with the actual config data nested in a 'json' field. Updated all API response parsing to extract the 'json' field before passing to Config.fromJson(). Changes: - /api/communities: extract 'json' field from each array item - /api/communities/:alias: extract 'json' field with null checks - Remote config URL: extract 'json' field with null checks --- lib/services/config/service.dart | 39 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/services/config/service.dart b/lib/services/config/service.dart index 80b3748f..aa9fda6c 100644 --- a/lib/services/config/service.dart +++ b/lib/services/config/service.dart @@ -157,7 +157,11 @@ class ConfigService { _pref.setConfigs(response); - final configs = (response as List).map((e) => Config.fromJson(e)).toList(); + // The API returns an array of wrapper objects with the config nested in the 'json' field + final configs = (response as List).map((e) { + final configData = e['json'] as Map; + return Config.fromJson(configData); + }).toList(); return configs; } @@ -196,7 +200,19 @@ class ConfigService { final dynamic response = await remote.get(url: '?cachebuster=${generateCacheBusterValue()}'); - final config = Config.fromJson(response); + if (response == null) { + debugPrint('Empty response for remote config'); + return null; + } + + // The API returns a wrapper object with the config nested in the 'json' field + final configData = response['json'] as Map?; + if (configData == null) { + debugPrint('No json field in response for remote config'); + return null; + } + + final config = Config.fromJson(configData); return config; } catch (e, s) { @@ -220,8 +236,11 @@ class ConfigService { final List response = await _api.get(url: '/api/communities'); - final List communities = - response.map((item) => Config.fromJson(item)).toList(); + // The API returns an array of wrapper objects with the config nested in the 'json' field + final List communities = response.map((item) { + final configData = item['json'] as Map; + return Config.fromJson(configData); + }).toList(); return communities; } @@ -238,7 +257,17 @@ class ConfigService { try { final response = await _api.get(url: '/api/communities/$alias'); - return Config.fromJson(response); + if (response == null) { + debugPrint('Empty response for config: $alias'); + return null; + } + // The API returns a wrapper object with the config nested in the 'json' field + final configData = response['json'] as Map?; + if (configData == null) { + debugPrint('No json field in response for config: $alias'); + return null; + } + return Config.fromJson(configData); } catch (e, s) { debugPrint('Error fetching config for $alias: $e'); debugPrint('Stacktrace: $s');