@@ -38,24 +38,54 @@ class AppDependencies {
38
38
final _completer = Completer <void >();
39
39
40
40
// --- Repositories ---
41
+ /// A repository for managing [Headline] data.
41
42
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.
43
48
late final HtDataRepository <Source > sourceRepository;
49
+
50
+ /// A repository for managing [Country] data.
44
51
late final HtDataRepository <Country > countryRepository;
52
+
53
+ /// A repository for managing [User] data.
45
54
late final HtDataRepository <User > userRepository;
55
+
56
+ /// A repository for managing [UserAppSettings] data.
46
57
late final HtDataRepository <UserAppSettings > userAppSettingsRepository;
58
+
59
+ /// A repository for managing [UserContentPreferences] data.
47
60
late final HtDataRepository <UserContentPreferences >
48
61
userContentPreferencesRepository;
49
- late final HtDataRepository <AppConfig > appConfigRepository;
62
+
63
+ /// A repository for managing the global [RemoteConfig] data.
64
+ late final HtDataRepository <RemoteConfig > remoteConfigRepository;
50
65
51
66
// --- Services ---
67
+ /// A service for sending emails.
52
68
late final HtEmailRepository emailRepository;
69
+
70
+ /// A service for managing a blacklist of invalidated authentication tokens.
53
71
late final TokenBlacklistService tokenBlacklistService;
72
+
73
+ /// A service for generating and validating authentication tokens.
54
74
late final AuthTokenService authTokenService;
75
+
76
+ /// A service for storing and validating one-time verification codes.
55
77
late final VerificationCodeStorageService verificationCodeStorageService;
78
+
79
+ /// A service that orchestrates authentication logic.
56
80
late final AuthService authService;
81
+
82
+ /// A service for calculating and providing a summary for the dashboard.
57
83
late final DashboardSummaryService dashboardSummaryService;
84
+
85
+ /// A service for checking user permissions.
58
86
late final PermissionService permissionService;
87
+
88
+ /// A service for enforcing limits on user content preferences.
59
89
late final UserPreferenceLimitService userPreferenceLimitService;
60
90
61
91
/// Initializes all application dependencies.
@@ -100,189 +130,107 @@ class AppDependencies {
100
130
headlineRepository = _createRepository (
101
131
connection,
102
132
'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' ),
133
144
);
134
- categoryRepository = _createRepository (
145
+ topicRepository = _createRepository (
135
146
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 (),
149
150
);
150
151
sourceRepository = _createRepository (
151
152
connection,
152
153
'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' ),
173
158
);
174
159
countryRepository = _createRepository (
175
160
connection,
176
161
'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 (),
189
164
);
190
165
userRepository = _createRepository (
191
166
connection,
192
167
'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)),
205
169
(user) {
206
- // The `roles` field is a List<String>, but the database expects a
207
- // JSONB array. We must explicitly encode it.
208
170
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' ]);
210
176
return json;
211
177
},
212
178
);
213
179
userAppSettingsRepository = _createRepository (
214
180
connection,
215
181
'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,
223
183
(settings) {
224
184
final json = settings.toJson ();
225
185
// These fields are complex objects and must be JSON encoded for the DB.
226
186
json['display_settings' ] = jsonEncode (json['display_settings' ]);
227
187
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' ]);
232
188
return json;
233
189
},
234
190
);
235
191
userContentPreferencesRepository = _createRepository (
236
192
connection,
237
193
'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,
251
195
(preferences) {
252
196
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' ]);
254
199
json['followed_sources' ] = jsonEncode (json['followed_sources' ]);
255
200
json['followed_countries' ] = jsonEncode (json['followed_countries' ]);
256
201
json['saved_headlines' ] = jsonEncode (json['saved_headlines' ]);
257
202
return json;
258
203
},
259
204
);
260
- appConfigRepository = _createRepository (
205
+ remoteConfigRepository = _createRepository (
261
206
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;
273
221
},
274
- (c) => c.toJson (),
275
222
);
276
223
277
224
// 4. Initialize Services.
278
225
emailRepository = const HtEmailRepository (
279
226
emailClient: HtEmailInMemoryClient (),
280
227
);
281
- tokenBlacklistService = InMemoryTokenBlacklistService ();
228
+ tokenBlacklistService = InMemoryTokenBlacklistService (log : _log );
282
229
authTokenService = JwtAuthTokenService (
283
230
userRepository: userRepository,
284
231
blacklistService: tokenBlacklistService,
285
232
uuidGenerator: const Uuid (),
233
+ log: _log,
286
234
);
287
235
verificationCodeStorageService = InMemoryVerificationCodeStorageService ();
288
236
authService = AuthService (
@@ -293,15 +241,17 @@ class AppDependencies {
293
241
userAppSettingsRepository: userAppSettingsRepository,
294
242
userContentPreferencesRepository: userContentPreferencesRepository,
295
243
uuidGenerator: const Uuid (),
244
+ log: _log,
296
245
);
297
246
dashboardSummaryService = DashboardSummaryService (
298
247
headlineRepository: headlineRepository,
299
- categoryRepository : categoryRepository ,
248
+ topicRepository : topicRepository ,
300
249
sourceRepository: sourceRepository,
301
250
);
302
251
permissionService = const PermissionService ();
303
252
userPreferenceLimitService = DefaultUserPreferenceLimitService (
304
- appConfigRepository: appConfigRepository,
253
+ remoteConfigRepository: remoteConfigRepository,
254
+ log: _log,
305
255
);
306
256
}
307
257
@@ -321,4 +271,20 @@ class AppDependencies {
321
271
),
322
272
);
323
273
}
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
+ }
324
290
}
0 commit comments