11import 'package:core/core.dart' ;
2+ import 'package:flutter_news_app_api_server_full_source_code/src/config/environment_config.dart' ;
23import 'package:flutter_news_app_api_server_full_source_code/src/services/mongodb_token_blacklist_service.dart' ;
34import 'package:flutter_news_app_api_server_full_source_code/src/services/mongodb_verification_code_storage_service.dart' ;
45import 'package:logging/logging.dart' ;
@@ -25,6 +26,7 @@ class DatabaseSeedingService {
2526 _log.info ('Starting database seeding process...' );
2627
2728 await _ensureIndexes ();
29+ await _seedOverrideAdminUser ();
2830
2931 await _seedCollection <Country >(
3032 collectionName: 'countries' ,
@@ -73,6 +75,120 @@ class DatabaseSeedingService {
7375 _log.info ('Database seeding process completed.' );
7476 }
7577
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+
76192 /// Seeds a specific collection from a given list of fixture data.
77193 Future <void > _seedCollection <T >({
78194 required String collectionName,
0 commit comments