1
- import 'dart:convert' ;
2
- import 'dart:io' ;
3
-
4
1
import 'package:ht_shared/ht_shared.dart' ;
5
2
import 'package:logging/logging.dart' ;
6
3
import 'package:mongo_dart/mongo_dart.dart' ;
7
4
8
5
/// {@template database_seeding_service}
9
6
/// A service responsible for seeding the MongoDB database with initial data.
10
7
///
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.
14
11
/// {@endtemplate}
15
12
class DatabaseSeedingService {
16
13
/// {@macro database_seeding_service}
17
14
const DatabaseSeedingService ({required Db db, required Logger log})
18
- : _db = db,
19
- _log = log;
15
+ : _db = db,
16
+ _log = log;
20
17
21
18
final Db _db;
22
19
final Logger _log;
23
20
24
21
/// The main entry point for seeding all necessary data.
25
22
Future <void > seedInitialData () async {
26
23
_log.info ('Starting database seeding process...' );
27
- await _seedCollection (
24
+
25
+ await _seedCollection <Country >(
28
26
collectionName: 'countries' ,
29
- fixturePath: 'lib/src/fixtures/countries.json' ,
27
+ fixtureData: countriesFixturesData,
28
+ getId: (item) => item.id,
29
+ toJson: (item) => item.toJson (),
30
30
);
31
- await _seedCollection (
31
+ await _seedCollection < Source > (
32
32
collectionName: 'sources' ,
33
- fixturePath: 'lib/src/fixtures/sources.json' ,
33
+ fixtureData: sourcesFixturesData,
34
+ getId: (item) => item.id,
35
+ toJson: (item) => item.toJson (),
34
36
);
35
- await _seedCollection (
37
+ await _seedCollection < Topic > (
36
38
collectionName: 'topics' ,
37
- fixturePath: 'lib/src/fixtures/topics.json' ,
39
+ fixtureData: topicsFixturesData,
40
+ getId: (item) => item.id,
41
+ toJson: (item) => item.toJson (),
38
42
);
39
- await _seedCollection (
43
+ await _seedCollection < Headline > (
40
44
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 (),
42
60
);
43
- await _seedInitialAdminAndConfig ();
61
+
44
62
_log.info ('Database seeding process completed.' );
45
63
}
46
64
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 > ({
49
67
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,
51
71
}) async {
52
- _log.info ('Seeding collection: "$collectionName " from "$ fixturePath " ...' );
72
+ _log.info ('Seeding collection: "$collectionName "...' );
53
73
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) {
65
75
_log.info ('No documents to seed for "$collectionName ".' );
66
76
return ;
67
77
}
68
78
69
- final bulk = collection.initializeUnorderedBulkOperation ();
79
+ final collection = _db.collection (collectionName);
80
+ final operations = < Map <String , Object >> [];
70
81
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);
74
84
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 ' );
77
87
continue ;
78
88
}
79
89
80
90
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
+ }
83
101
84
- bulk.find ({'_id' : objectId}).upsert ().replaceOne (docMap);
102
+ if (operations.isEmpty) {
103
+ _log.info ('No valid documents to write for "$collectionName ".' );
104
+ return ;
85
105
}
86
106
87
- final result = await bulk. execute ( );
107
+ final result = await collection. bulkWrite (operations );
88
108
_log.info (
89
109
'Seeding for "$collectionName " complete. '
90
110
'Upserted: ${result .nUpserted }, Modified: ${result .nModified }.' ,
91
111
);
92
112
} 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);
146
114
rethrow ;
147
115
}
148
116
}
149
- }
117
+ }
0 commit comments