@@ -38,24 +38,54 @@ class AppDependencies {
3838 final _completer = Completer <void >();
3939
4040 // --- Repositories ---
41+ /// A repository for managing [Headline] data.
4142 late final HtDataRepository <Headline > headlineRepository;
42- late final HtDataRepository <Category > categoryRepository;
43+
44+ /// A repository for managing [Topic] data.
45+ late final HtDataRepository <Topic > topicRepository;
46+
47+ /// A repository for managing [Source] data.
4348 late final HtDataRepository <Source > sourceRepository;
49+
50+ /// A repository for managing [Country] data.
4451 late final HtDataRepository <Country > countryRepository;
52+
53+ /// A repository for managing [User] data.
4554 late final HtDataRepository <User > userRepository;
55+
56+ /// A repository for managing [UserAppSettings] data.
4657 late final HtDataRepository <UserAppSettings > userAppSettingsRepository;
58+
59+ /// A repository for managing [UserContentPreferences] data.
4760 late final HtDataRepository <UserContentPreferences >
4861 userContentPreferencesRepository;
49- late final HtDataRepository <AppConfig > appConfigRepository;
62+
63+ /// A repository for managing the global [RemoteConfig] data.
64+ late final HtDataRepository <RemoteConfig > remoteConfigRepository;
5065
5166 // --- Services ---
67+ /// A service for sending emails.
5268 late final HtEmailRepository emailRepository;
69+
70+ /// A service for managing a blacklist of invalidated authentication tokens.
5371 late final TokenBlacklistService tokenBlacklistService;
72+
73+ /// A service for generating and validating authentication tokens.
5474 late final AuthTokenService authTokenService;
75+
76+ /// A service for storing and validating one-time verification codes.
5577 late final VerificationCodeStorageService verificationCodeStorageService;
78+
79+ /// A service that orchestrates authentication logic.
5680 late final AuthService authService;
81+
82+ /// A service for calculating and providing a summary for the dashboard.
5783 late final DashboardSummaryService dashboardSummaryService;
84+
85+ /// A service for checking user permissions.
5886 late final PermissionService permissionService;
87+
88+ /// A service for enforcing limits on user content preferences.
5989 late final UserPreferenceLimitService userPreferenceLimitService;
6090
6191 /// Initializes all application dependencies.
@@ -100,189 +130,107 @@ class AppDependencies {
100130 headlineRepository = _createRepository (
101131 connection,
102132 'headlines' ,
103- (json) {
104- if (json['created_at' ] is DateTime ) {
105- json['created_at' ] =
106- (json['created_at' ] as DateTime ).toIso8601String ();
107- }
108- if (json['updated_at' ] is DateTime ) {
109- json['updated_at' ] =
110- (json['updated_at' ] as DateTime ).toIso8601String ();
111- }
112- if (json['published_at' ] is DateTime ) {
113- json['published_at' ] =
114- (json['published_at' ] as DateTime ).toIso8601String ();
115- }
116- return Headline .fromJson (json);
117- },
118- (headline) {
119- final json = headline.toJson ();
120- // The database expects source_id and category_id, not nested objects.
121- // We extract the IDs and remove the original objects to match the
122- // schema.
123- if (headline.source != null ) {
124- json['source_id' ] = headline.source! .id;
125- }
126- if (headline.category != null ) {
127- json['category_id' ] = headline.category! .id;
128- }
129- json.remove ('source' );
130- json.remove ('category' );
131- return json;
132- },
133+ // The HtDataPostgresClient returns DateTime objects from TIMESTAMPTZ
134+ // columns. The Headline.fromJson factory expects ISO 8601 strings.
135+ // This handler converts them before deserialization.
136+ (json) => Headline .fromJson (_convertTimestampsToString (json)),
137+ (headline) => headline.toJson ()
138+ ..['source_id' ] = headline.source.id
139+ ..['topic_id' ] = headline.topic.id
140+ ..['event_country_id' ] = headline.eventCountry.id
141+ ..remove ('source' )
142+ ..remove ('topic' )
143+ ..remove ('eventCountry' ),
133144 );
134- categoryRepository = _createRepository (
145+ topicRepository = _createRepository (
135146 connection,
136- 'categories' ,
137- (json) {
138- if (json['created_at' ] is DateTime ) {
139- json['created_at' ] =
140- (json['created_at' ] as DateTime ).toIso8601String ();
141- }
142- if (json['updated_at' ] is DateTime ) {
143- json['updated_at' ] =
144- (json['updated_at' ] as DateTime ).toIso8601String ();
145- }
146- return Category .fromJson (json);
147- },
148- (c) => c.toJson (),
147+ 'topics' ,
148+ (json) => Topic .fromJson (_convertTimestampsToString (json)),
149+ (topic) => topic.toJson (),
149150 );
150151 sourceRepository = _createRepository (
151152 connection,
152153 'sources' ,
153- (json) {
154- if (json['created_at' ] is DateTime ) {
155- json['created_at' ] =
156- (json['created_at' ] as DateTime ).toIso8601String ();
157- }
158- if (json['updated_at' ] is DateTime ) {
159- json['updated_at' ] =
160- (json['updated_at' ] as DateTime ).toIso8601String ();
161- }
162- return Source .fromJson (json);
163- },
164- (source) {
165- final json = source.toJson ();
166- // The database expects headquarters_country_id, not a nested object.
167- // We extract the ID and remove the original object to match the
168- // schema.
169- json['headquarters_country_id' ] = source.headquarters? .id;
170- json.remove ('headquarters' );
171- return json;
172- },
154+ (json) => Source .fromJson (_convertTimestampsToString (json)),
155+ (source) => source.toJson ()
156+ ..['headquarters_country_id' ] = source.headquarters.id
157+ ..remove ('headquarters' ),
173158 );
174159 countryRepository = _createRepository (
175160 connection,
176161 'countries' ,
177- (json) {
178- if (json['created_at' ] is DateTime ) {
179- json['created_at' ] =
180- (json['created_at' ] as DateTime ).toIso8601String ();
181- }
182- if (json['updated_at' ] is DateTime ) {
183- json['updated_at' ] =
184- (json['updated_at' ] as DateTime ).toIso8601String ();
185- }
186- return Country .fromJson (json);
187- },
188- (c) => c.toJson (),
162+ (json) => Country .fromJson (_convertTimestampsToString (json)),
163+ (country) => country.toJson (),
189164 );
190165 userRepository = _createRepository (
191166 connection,
192167 'users' ,
193- (json) {
194- // The postgres driver returns DateTime objects, but the model's
195- // fromJson expects ISO 8601 strings. We must convert them first.
196- if (json['created_at' ] is DateTime ) {
197- json['created_at' ] = (json['created_at' ] as DateTime ).toIso8601String ();
198- }
199- if (json['last_engagement_shown_at' ] is DateTime ) {
200- json['last_engagement_shown_at' ] =
201- (json['last_engagement_shown_at' ] as DateTime ).toIso8601String ();
202- }
203- return User .fromJson (json);
204- },
168+ (json) => User .fromJson (_convertTimestampsToString (json)),
205169 (user) {
206- // The `roles` field is a List<String>, but the database expects a
207- // JSONB array. We must explicitly encode it.
208170 final json = user.toJson ();
209- json['roles' ] = jsonEncode (json['roles' ]);
171+ // Convert enums to their string names for the database.
172+ json['app_role' ] = user.appRole.name;
173+ json['dashboard_role' ] = user.dashboardRole.name;
174+ // The `feed_action_status` map must be JSON encoded for the JSONB column.
175+ json['feed_action_status' ] = jsonEncode (json['feed_action_status' ]);
210176 return json;
211177 },
212178 );
213179 userAppSettingsRepository = _createRepository (
214180 connection,
215181 'user_app_settings' ,
216- (json) {
217- // The DB has created_at/updated_at, but the model doesn't.
218- // Remove them before deserialization to avoid CheckedFromJsonException.
219- json.remove ('created_at' );
220- json.remove ('updated_at' );
221- return UserAppSettings .fromJson (json);
222- },
182+ UserAppSettings .fromJson,
223183 (settings) {
224184 final json = settings.toJson ();
225185 // These fields are complex objects and must be JSON encoded for the DB.
226186 json['display_settings' ] = jsonEncode (json['display_settings' ]);
227187 json['feed_preferences' ] = jsonEncode (json['feed_preferences' ]);
228- json['engagement_shown_counts' ] =
229- jsonEncode (json['engagement_shown_counts' ]);
230- json['engagement_last_shown_timestamps' ] =
231- jsonEncode (json['engagement_last_shown_timestamps' ]);
232188 return json;
233189 },
234190 );
235191 userContentPreferencesRepository = _createRepository (
236192 connection,
237193 'user_content_preferences' ,
238- (json) {
239- // The postgres driver returns DateTime objects, but the model's
240- // fromJson expects ISO 8601 strings. We must convert them first.
241- if (json['created_at' ] is DateTime ) {
242- json['created_at' ] =
243- (json['created_at' ] as DateTime ).toIso8601String ();
244- }
245- if (json['updated_at' ] is DateTime ) {
246- json['updated_at' ] =
247- (json['updated_at' ] as DateTime ).toIso8601String ();
248- }
249- return UserContentPreferences .fromJson (json);
250- },
194+ UserContentPreferences .fromJson,
251195 (preferences) {
252196 final json = preferences.toJson ();
253- json['followed_categories' ] = jsonEncode (json['followed_categories' ]);
197+ // These fields are lists of complex objects and must be JSON encoded.
198+ json['followed_topics' ] = jsonEncode (json['followed_topics' ]);
254199 json['followed_sources' ] = jsonEncode (json['followed_sources' ]);
255200 json['followed_countries' ] = jsonEncode (json['followed_countries' ]);
256201 json['saved_headlines' ] = jsonEncode (json['saved_headlines' ]);
257202 return json;
258203 },
259204 );
260- appConfigRepository = _createRepository (
205+ remoteConfigRepository = _createRepository (
261206 connection,
262- 'app_config' ,
263- (json) {
264- if (json['created_at' ] is DateTime ) {
265- json['created_at' ] =
266- (json['created_at' ] as DateTime ).toIso8601String ();
267- }
268- if (json['updated_at' ] is DateTime ) {
269- json['updated_at' ] =
270- (json['updated_at' ] as DateTime ).toIso8601String ();
271- }
272- return AppConfig .fromJson (json);
207+ 'remote_config' ,
208+ (json) => RemoteConfig .fromJson (_convertTimestampsToString (json)),
209+ (config) {
210+ final json = config.toJson ();
211+ // All nested config objects must be JSON encoded for JSONB columns.
212+ json['user_preference_limits' ] = jsonEncode (
213+ json['user_preference_limits' ],
214+ );
215+ json['ad_config' ] = jsonEncode (json['ad_config' ]);
216+ json['account_action_config' ] = jsonEncode (
217+ json['account_action_config' ],
218+ );
219+ json['app_status' ] = jsonEncode (json['app_status' ]);
220+ return json;
273221 },
274- (c) => c.toJson (),
275222 );
276223
277224 // 4. Initialize Services.
278225 emailRepository = const HtEmailRepository (
279226 emailClient: HtEmailInMemoryClient (),
280227 );
281- tokenBlacklistService = InMemoryTokenBlacklistService ();
228+ tokenBlacklistService = InMemoryTokenBlacklistService (log : _log );
282229 authTokenService = JwtAuthTokenService (
283230 userRepository: userRepository,
284231 blacklistService: tokenBlacklistService,
285232 uuidGenerator: const Uuid (),
233+ log: _log,
286234 );
287235 verificationCodeStorageService = InMemoryVerificationCodeStorageService ();
288236 authService = AuthService (
@@ -293,15 +241,17 @@ class AppDependencies {
293241 userAppSettingsRepository: userAppSettingsRepository,
294242 userContentPreferencesRepository: userContentPreferencesRepository,
295243 uuidGenerator: const Uuid (),
244+ log: _log,
296245 );
297246 dashboardSummaryService = DashboardSummaryService (
298247 headlineRepository: headlineRepository,
299- categoryRepository : categoryRepository ,
248+ topicRepository : topicRepository ,
300249 sourceRepository: sourceRepository,
301250 );
302251 permissionService = const PermissionService ();
303252 userPreferenceLimitService = DefaultUserPreferenceLimitService (
304- appConfigRepository: appConfigRepository,
253+ remoteConfigRepository: remoteConfigRepository,
254+ log: _log,
305255 );
306256 }
307257
@@ -321,4 +271,20 @@ class AppDependencies {
321271 ),
322272 );
323273 }
274+
275+ /// Converts DateTime values in a JSON map to ISO 8601 strings.
276+ ///
277+ /// The postgres driver returns DateTime objects for TIMESTAMPTZ columns,
278+ /// but our models' `fromJson` factories expect ISO 8601 strings. This
279+ /// utility function performs the conversion for known timestamp fields.
280+ Map <String , dynamic > _convertTimestampsToString (Map <String , dynamic > json) {
281+ const timestampKeys = {'created_at' , 'updated_at' };
282+ final newJson = Map <String , dynamic >.from (json);
283+ for (final key in timestampKeys) {
284+ if (newJson[key] is DateTime ) {
285+ newJson[key] = (newJson[key] as DateTime ).toIso8601String ();
286+ }
287+ }
288+ return newJson;
289+ }
324290}
0 commit comments