Skip to content

Commit 5e9f921

Browse files
authored
Merge pull request #13 from headlines-toolkit/fix_database_bugs
Fix database bugs
2 parents bf23c06 + 3ff1b60 commit 5e9f921

File tree

3 files changed

+45
-17
lines changed

3 files changed

+45
-17
lines changed

lib/src/config/app_dependencies.dart

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,21 @@ class AppDependencies {
115115
}
116116
return Headline.fromJson(json);
117117
},
118-
(h) => h.toJson(), // toJson already handles DateTime correctly
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+
},
119133
);
120134
categoryRepository = _createRepository(
121135
connection,
@@ -147,7 +161,15 @@ class AppDependencies {
147161
}
148162
return Source.fromJson(json);
149163
},
150-
(s) => s.toJson(),
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+
},
151173
);
152174
countryRepository = _createRepository(
153175
connection,

lib/src/services/database_seeding_service.dart

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ class DatabaseSeedingService {
106106
CREATE TABLE IF NOT EXISTS headlines (
107107
id TEXT PRIMARY KEY,
108108
title TEXT NOT NULL,
109-
source_id TEXT NOT NULL,
110-
category_id TEXT NOT NULL,
109+
source_id TEXT REFERENCES sources(id),
110+
category_id TEXT REFERENCES categories(id),
111111
image_url TEXT,
112112
url TEXT,
113113
published_at TIMESTAMPTZ,
@@ -260,19 +260,10 @@ class DatabaseSeedingService {
260260
final headline = Headline.fromJson(data);
261261
final params = headline.toJson();
262262

263-
// The `source_id` and `category_id` columns are NOT NULL. If a fixture
264-
// is missing the nested source or category object, we cannot proceed.
265-
if (headline.source == null || headline.category == null) {
266-
_log.warning(
267-
'Skipping headline fixture with missing source or category ID: '
268-
'${headline.title}',
269-
);
270-
continue;
271-
}
272-
273263
// Extract IDs from nested objects and remove the objects to match schema.
274-
params['source_id'] = headline.source!.id;
275-
params['category_id'] = headline.category!.id;
264+
// These are now nullable to match the schema.
265+
params['source_id'] = headline.source?.id;
266+
params['category_id'] = headline.category?.id;
276267
params.remove('source');
277268
params.remove('category');
278269

routes/api/v1/data/index.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import 'package:ht_shared/ht_shared.dart';
88

99
import '../../../_middleware.dart'; // Assuming RequestId is here
1010

11+
/// Converts a camelCase string to snake_case.
12+
String _camelToSnake(String input) {
13+
return input
14+
.replaceAllMapped(
15+
RegExp(r'(?<!^)(?=[A-Z])'),
16+
(match) => '_${match.group(0)}',
17+
)
18+
.toLowerCase();
19+
}
20+
1121
/// Handles requests for the /api/v1/data collection endpoint.
1222
/// Dispatches requests to specific handlers based on the HTTP method.
1323
Future<Response> onRequest(RequestContext context) async {
@@ -109,10 +119,15 @@ Future<Response> _handleGet(
109119
final queryParams = context.request.uri.queryParameters;
110120
final startAfterId = queryParams['startAfterId'];
111121
final limitParam = queryParams['limit'];
112-
final sortBy = queryParams['sortBy'];
122+
final sortByParam = queryParams['sortBy'];
113123
final sortOrderRaw = queryParams['sortOrder']?.toLowerCase();
114124
final limit = limitParam != null ? int.tryParse(limitParam) : null;
115125

126+
// Convert sortBy from camelCase to snake_case for the database query.
127+
// This prevents errors where the client sends 'createdAt' and the database
128+
// expects 'created_at'.
129+
final sortBy = sortByParam != null ? _camelToSnake(sortByParam) : null;
130+
116131
SortOrder? sortOrder;
117132
if (sortOrderRaw != null) {
118133
if (sortOrderRaw == 'asc') {

0 commit comments

Comments
 (0)