1
1
import 'package:core/core.dart' ;
2
+ import 'package:flutter_news_app_api_server_full_source_code/src/config/environment_config.dart' ;
2
3
import 'package:flutter_news_app_api_server_full_source_code/src/services/mongodb_token_blacklist_service.dart' ;
3
4
import 'package:flutter_news_app_api_server_full_source_code/src/services/mongodb_verification_code_storage_service.dart' ;
4
5
import 'package:logging/logging.dart' ;
@@ -25,6 +26,7 @@ class DatabaseSeedingService {
25
26
_log.info ('Starting database seeding process...' );
26
27
27
28
await _ensureIndexes ();
29
+ await _seedOverrideAdminUser ();
28
30
29
31
await _seedCollection <Country >(
30
32
collectionName: 'countries' ,
@@ -73,6 +75,120 @@ class DatabaseSeedingService {
73
75
_log.info ('Database seeding process completed.' );
74
76
}
75
77
78
+ /// Ensures the single administrator account is correctly configured based on
79
+ /// the `OVERRIDE_ADMIN_EMAIL` environment variable.
80
+ Future <void > _seedOverrideAdminUser () async {
81
+ _log.info ('Checking for admin user override...' );
82
+ final overrideEmail = EnvironmentConfig .overrideAdminEmail;
83
+
84
+ if (overrideEmail == null || overrideEmail.isEmpty) {
85
+ _log.info (
86
+ 'OVERRIDE_ADMIN_EMAIL not set. Skipping admin user override.' ,
87
+ );
88
+ return ;
89
+ }
90
+
91
+ final usersCollection = _db.collection ('users' );
92
+ final existingAdmin = await usersCollection.findOne (
93
+ where.eq ('dashboardRole' , DashboardUserRole .admin.name),
94
+ );
95
+
96
+ // Case 1: An admin exists.
97
+ if (existingAdmin != null ) {
98
+ final existingAdminEmail = existingAdmin['email' ] as String ;
99
+ // If the existing admin's email is the same as the override, do nothing.
100
+ if (existingAdminEmail == overrideEmail) {
101
+ _log.info (
102
+ 'Admin user with email $overrideEmail already exists and matches '
103
+ 'override. No action needed.' ,
104
+ );
105
+ return ;
106
+ }
107
+
108
+ // If emails differ, delete the old admin and their data.
109
+ _log.warning (
110
+ 'Found existing admin with email "$existingAdminEmail ". It will be '
111
+ 'replaced by the override email "$overrideEmail ".' ,
112
+ );
113
+ final oldAdminId = existingAdmin['_id' ] as ObjectId ;
114
+ await _deleteUserAndData (oldAdminId);
115
+ }
116
+
117
+ // Case 2: No admin exists, or the old one was just deleted.
118
+ // Create the new admin.
119
+ _log.info ('Creating admin user for email: $overrideEmail ' );
120
+ final newAdminId = ObjectId ();
121
+ final newAdminUser = User (
122
+ id: newAdminId.oid,
123
+ email: overrideEmail,
124
+ appRole: AppUserRole .standardUser,
125
+ dashboardRole: DashboardUserRole .admin,
126
+ createdAt: DateTime .now (),
127
+ feedActionStatus: Map .fromEntries (
128
+ FeedActionType .values.map (
129
+ (type) =>
130
+ MapEntry (type, const UserFeedActionStatus (isCompleted: false )),
131
+ ),
132
+ ),
133
+ );
134
+
135
+ await usersCollection.insertOne (
136
+ {'_id' : newAdminId, ...newAdminUser.toJson ()..remove ('id' )},
137
+ );
138
+
139
+ // Create default settings and preferences for the new admin.
140
+ await _createUserSubDocuments (newAdminId);
141
+
142
+ _log.info ('Successfully created admin user for $overrideEmail .' );
143
+ }
144
+
145
+ /// Deletes a user and their associated sub-documents.
146
+ Future <void > _deleteUserAndData (ObjectId userId) async {
147
+ await _db.collection ('users' ).deleteOne (where.eq ('_id' , userId));
148
+ await _db
149
+ .collection ('user_app_settings' )
150
+ .deleteOne (where.eq ('_id' , userId));
151
+ await _db
152
+ .collection ('user_content_preferences' )
153
+ .deleteOne (where.eq ('_id' , userId));
154
+ _log.info ('Deleted user and associated data for ID: ${userId .oid }' );
155
+ }
156
+
157
+ /// Creates the default sub-documents (settings, preferences) for a new user.
158
+ Future <void > _createUserSubDocuments (ObjectId userId) async {
159
+ final defaultAppSettings = UserAppSettings (
160
+ id: userId.oid,
161
+ displaySettings: const DisplaySettings (
162
+ baseTheme: AppBaseTheme .system,
163
+ accentTheme: AppAccentTheme .defaultBlue,
164
+ fontFamily: 'SystemDefault' ,
165
+ textScaleFactor: AppTextScaleFactor .medium,
166
+ fontWeight: AppFontWeight .regular,
167
+ ),
168
+ language: 'en' ,
169
+ feedPreferences: const FeedDisplayPreferences (
170
+ headlineDensity: HeadlineDensity .standard,
171
+ headlineImageStyle: HeadlineImageStyle .largeThumbnail,
172
+ showSourceInHeadlineFeed: true ,
173
+ showPublishDateInHeadlineFeed: true ,
174
+ ),
175
+ );
176
+ await _db.collection ('user_app_settings' ).insertOne (
177
+ {'_id' : userId, ...defaultAppSettings.toJson ()..remove ('id' )},
178
+ );
179
+
180
+ final defaultUserPreferences = UserContentPreferences (
181
+ id: userId.oid,
182
+ followedCountries: const [],
183
+ followedSources: const [],
184
+ followedTopics: const [],
185
+ savedHeadlines: const [],
186
+ );
187
+ await _db.collection ('user_content_preferences' ).insertOne (
188
+ {'_id' : userId, ...defaultUserPreferences.toJson ()..remove ('id' )},
189
+ );
190
+ }
191
+
76
192
/// Seeds a specific collection from a given list of fixture data.
77
193
Future <void > _seedCollection <T >({
78
194
required String collectionName,
0 commit comments