diff --git a/lib/src/config/app_dependencies.dart b/lib/src/config/app_dependencies.dart index ee2d104..15b5ffe 100644 --- a/lib/src/config/app_dependencies.dart +++ b/lib/src/config/app_dependencies.dart @@ -115,7 +115,21 @@ class AppDependencies { } return Headline.fromJson(json); }, - (h) => h.toJson(), // toJson already handles DateTime correctly + (headline) { + final json = headline.toJson(); + // The database expects source_id and category_id, not nested objects. + // We extract the IDs and remove the original objects to match the + // schema. + if (headline.source != null) { + json['source_id'] = headline.source!.id; + } + if (headline.category != null) { + json['category_id'] = headline.category!.id; + } + json.remove('source'); + json.remove('category'); + return json; + }, ); categoryRepository = _createRepository( connection, @@ -147,7 +161,15 @@ class AppDependencies { } return Source.fromJson(json); }, - (s) => s.toJson(), + (source) { + final json = source.toJson(); + // The database expects headquarters_country_id, not a nested object. + // We extract the ID and remove the original object to match the + // schema. + json['headquarters_country_id'] = source.headquarters?.id; + json.remove('headquarters'); + return json; + }, ); countryRepository = _createRepository( connection, diff --git a/lib/src/services/database_seeding_service.dart b/lib/src/services/database_seeding_service.dart index 6ba301d..57eab06 100644 --- a/lib/src/services/database_seeding_service.dart +++ b/lib/src/services/database_seeding_service.dart @@ -106,8 +106,8 @@ class DatabaseSeedingService { CREATE TABLE IF NOT EXISTS headlines ( id TEXT PRIMARY KEY, title TEXT NOT NULL, - source_id TEXT NOT NULL, - category_id TEXT NOT NULL, + source_id TEXT REFERENCES sources(id), + category_id TEXT REFERENCES categories(id), image_url TEXT, url TEXT, published_at TIMESTAMPTZ, @@ -260,19 +260,10 @@ class DatabaseSeedingService { final headline = Headline.fromJson(data); final params = headline.toJson(); - // The `source_id` and `category_id` columns are NOT NULL. If a fixture - // is missing the nested source or category object, we cannot proceed. - if (headline.source == null || headline.category == null) { - _log.warning( - 'Skipping headline fixture with missing source or category ID: ' - '${headline.title}', - ); - continue; - } - // Extract IDs from nested objects and remove the objects to match schema. - params['source_id'] = headline.source!.id; - params['category_id'] = headline.category!.id; + // These are now nullable to match the schema. + params['source_id'] = headline.source?.id; + params['category_id'] = headline.category?.id; params.remove('source'); params.remove('category'); diff --git a/routes/api/v1/data/index.dart b/routes/api/v1/data/index.dart index 9384235..87fa33b 100644 --- a/routes/api/v1/data/index.dart +++ b/routes/api/v1/data/index.dart @@ -8,6 +8,16 @@ import 'package:ht_shared/ht_shared.dart'; import '../../../_middleware.dart'; // Assuming RequestId is here +/// Converts a camelCase string to snake_case. +String _camelToSnake(String input) { + return input + .replaceAllMapped( + RegExp(r'(? '_${match.group(0)}', + ) + .toLowerCase(); +} + /// Handles requests for the /api/v1/data collection endpoint. /// Dispatches requests to specific handlers based on the HTTP method. Future onRequest(RequestContext context) async { @@ -109,10 +119,15 @@ Future _handleGet( final queryParams = context.request.uri.queryParameters; final startAfterId = queryParams['startAfterId']; final limitParam = queryParams['limit']; - final sortBy = queryParams['sortBy']; + final sortByParam = queryParams['sortBy']; final sortOrderRaw = queryParams['sortOrder']?.toLowerCase(); final limit = limitParam != null ? int.tryParse(limitParam) : null; + // Convert sortBy from camelCase to snake_case for the database query. + // This prevents errors where the client sends 'createdAt' and the database + // expects 'created_at'. + final sortBy = sortByParam != null ? _camelToSnake(sortByParam) : null; + SortOrder? sortOrder; if (sortOrderRaw != null) { if (sortOrderRaw == 'asc') {