1- import 'dart:convert' ;
2- import 'dart:io' ;
3-
41import 'package:ht_shared/ht_shared.dart' ;
52import 'package:logging/logging.dart' ;
63import 'package:mongo_dart/mongo_dart.dart' ;
74
85/// {@template database_seeding_service}
96/// A service responsible for seeding the MongoDB database with initial data.
107///
11- /// This service reads data from local JSON fixture files and uses `upsert`
12- /// operations to ensure that the seeding process is idempotent. It can be
13- /// run multiple times without creating duplicate documents.
8+ /// This service reads data from predefined fixture lists in `ht_shared` and
9+ /// uses `upsert` operations to ensure that the seeding process is idempotent.
10+ /// It can be run multiple times without creating duplicate documents.
1411/// {@endtemplate}
1512class DatabaseSeedingService {
1613 /// {@macro database_seeding_service}
1714 const DatabaseSeedingService ({required Db db, required Logger log})
18- : _db = db,
19- _log = log;
15+ : _db = db,
16+ _log = log;
2017
2118 final Db _db;
2219 final Logger _log;
2320
2421 /// The main entry point for seeding all necessary data.
2522 Future <void > seedInitialData () async {
2623 _log.info ('Starting database seeding process...' );
27- await _seedCollection (
24+
25+ await _seedCollection <Country >(
2826 collectionName: 'countries' ,
29- fixturePath: 'lib/src/fixtures/countries.json' ,
27+ fixtureData: countriesFixturesData,
28+ getId: (item) => item.id,
29+ toJson: (item) => item.toJson (),
3030 );
31- await _seedCollection (
31+ await _seedCollection < Source > (
3232 collectionName: 'sources' ,
33- fixturePath: 'lib/src/fixtures/sources.json' ,
33+ fixtureData: sourcesFixturesData,
34+ getId: (item) => item.id,
35+ toJson: (item) => item.toJson (),
3436 );
35- await _seedCollection (
37+ await _seedCollection < Topic > (
3638 collectionName: 'topics' ,
37- fixturePath: 'lib/src/fixtures/topics.json' ,
39+ fixtureData: topicsFixturesData,
40+ getId: (item) => item.id,
41+ toJson: (item) => item.toJson (),
3842 );
39- await _seedCollection (
43+ await _seedCollection < Headline > (
4044 collectionName: 'headlines' ,
41- fixturePath: 'lib/src/fixtures/headlines.json' ,
45+ fixtureData: headlinesFixturesData,
46+ getId: (item) => item.id,
47+ toJson: (item) => item.toJson (),
48+ );
49+ await _seedCollection <User >(
50+ collectionName: 'users' ,
51+ fixtureData: usersFixturesData,
52+ getId: (item) => item.id,
53+ toJson: (item) => item.toJson (),
54+ );
55+ await _seedCollection <RemoteConfig >(
56+ collectionName: 'remote_configs' ,
57+ fixtureData: remoteConfigsFixturesData,
58+ getId: (item) => item.id,
59+ toJson: (item) => item.toJson (),
4260 );
43- await _seedInitialAdminAndConfig ();
61+
4462 _log.info ('Database seeding process completed.' );
4563 }
4664
47- /// Seeds a specific collection from a given JSON fixture file .
48- Future <void > _seedCollection ({
65+ /// Seeds a specific collection from a given list of fixture data .
66+ Future <void > _seedCollection < T > ({
4967 required String collectionName,
50- required String fixturePath,
68+ required List <T > fixtureData,
69+ required String Function (T ) getId,
70+ required Map <String , dynamic > Function (T ) toJson,
5171 }) async {
52- _log.info ('Seeding collection: "$collectionName " from "$ fixturePath " ...' );
72+ _log.info ('Seeding collection: "$collectionName "...' );
5373 try {
54- final collection = _db.collection (collectionName);
55- final file = File (fixturePath);
56- if (! await file.exists ()) {
57- _log.warning ('Fixture file not found: $fixturePath . Skipping.' );
58- return ;
59- }
60-
61- final content = await file.readAsString ();
62- final documents = jsonDecode (content) as List <dynamic >;
63-
64- if (documents.isEmpty) {
74+ if (fixtureData.isEmpty) {
6575 _log.info ('No documents to seed for "$collectionName ".' );
6676 return ;
6777 }
6878
69- final bulk = collection.initializeUnorderedBulkOperation ();
79+ final collection = _db.collection (collectionName);
80+ final operations = < Map <String , Object >> [];
7081
71- for (final doc in documents) {
72- final docMap = doc as Map <String , dynamic >;
73- final id = docMap['id' ] as String ? ;
82+ for (final item in fixtureData) {
83+ final id = getId (item);
7484
75- if (id == null || ! ObjectId .isValidHexId (id)) {
76- _log.warning ('Skipping document with invalid or missing ID : $doc ' );
85+ if (! ObjectId .isValidHexId (id)) {
86+ _log.warning ('Skipping document with invalid ID format : $id ' );
7787 continue ;
7888 }
7989
8090 final objectId = ObjectId .fromHexString (id);
81- // Remove the string 'id' field and use '_id' with ObjectId
82- docMap.remove ('id' );
91+ final document = toJson (item)..remove ('id' );
92+
93+ operations.add ({
94+ 'replaceOne' : {
95+ 'filter' : {'_id' : objectId},
96+ 'replacement' : document,
97+ 'upsert' : true ,
98+ },
99+ });
100+ }
83101
84- bulk.find ({'_id' : objectId}).upsert ().replaceOne (docMap);
102+ if (operations.isEmpty) {
103+ _log.info ('No valid documents to write for "$collectionName ".' );
104+ return ;
85105 }
86106
87- final result = await bulk. execute ( );
107+ final result = await collection. bulkWrite (operations );
88108 _log.info (
89109 'Seeding for "$collectionName " complete. '
90110 'Upserted: ${result .nUpserted }, Modified: ${result .nModified }.' ,
91111 );
92112 } on Exception catch (e, s) {
93- _log.severe (
94- 'Failed to seed collection "$collectionName " from "$fixturePath ".' ,
95- e,
96- s,
97- );
98- // Re-throwing to halt the startup process if seeding fails.
99- rethrow ;
100- }
101- }
102-
103- /// Seeds the initial admin user and remote config document.
104- Future <void > _seedInitialAdminAndConfig () async {
105- _log.info ('Seeding initial admin user and remote config...' );
106- try {
107- // --- Seed Admin User ---
108- final usersCollection = _db.collection ('users' );
109- final adminUser = User .fromJson (adminUserFixture);
110- final adminDoc = adminUser.toJson ()
111- ..['app_role' ] = adminUser.appRole.name
112- ..['dashboard_role' ] = adminUser.dashboardRole.name
113- ..['feed_action_status' ] = jsonEncode (adminUser.feedActionStatus)
114- ..remove ('id' );
115-
116- await usersCollection.updateOne (
117- where.id (ObjectId .fromHexString (adminUser.id)),
118- modify.set (
119- 'email' ,
120- adminDoc['email' ],
121- ).setAll (adminDoc), // Use setAll to add/update all fields
122- upsert: true ,
123- );
124- _log.info ('Admin user seeded successfully.' );
125-
126- // --- Seed Remote Config ---
127- final remoteConfigCollection = _db.collection ('remote_config' );
128- final remoteConfig = RemoteConfig .fromJson (remoteConfigFixture);
129- final remoteConfigDoc = remoteConfig.toJson ()
130- ..['user_preference_limits' ] =
131- jsonEncode (remoteConfig.userPreferenceConfig.toJson ())
132- ..['ad_config' ] = jsonEncode (remoteConfig.adConfig.toJson ())
133- ..['account_action_config' ] =
134- jsonEncode (remoteConfig.accountActionConfig.toJson ())
135- ..['app_status' ] = jsonEncode (remoteConfig.appStatus.toJson ())
136- ..remove ('id' );
137-
138- await remoteConfigCollection.updateOne (
139- where.id (ObjectId .fromHexString (remoteConfig.id)),
140- modify.setAll (remoteConfigDoc),
141- upsert: true ,
142- );
143- _log.info ('Remote config seeded successfully.' );
144- } on Exception catch (e, s) {
145- _log.severe ('Failed to seed admin user or remote config.' , e, s);
113+ _log.severe ('Failed to seed collection "$collectionName ".' , e, s);
146114 rethrow ;
147115 }
148116 }
149- }
117+ }
0 commit comments