Skip to content

Commit 877b929

Browse files
committed
refactor(data-api): separate model configuration and request handling
- Remove toJson from ModelConfig and use model-specific toJson methods - Refactor data request handling to use separate logic for GET, PUT, and DELETE - Update repository reading and writing to use strongly-typed methods - Improve error handling and logging
1 parent 5745b46 commit 877b929

File tree

3 files changed

+293
-171
lines changed

3 files changed

+293
-171
lines changed

lib/src/registry/model_registry.dart

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,29 @@
33

44
import 'package:dart_frog/dart_frog.dart';
55
import 'package:ht_data_client/ht_data_client.dart';
6-
// HtDataRepository import is no longer needed here
76
import 'package:ht_shared/ht_shared.dart';
87

98
/// {@template model_config}
109
/// Configuration holder for a specific data model type [T].
1110
///
12-
/// Contains the necessary functions (serialization, ID extraction)
13-
/// required to handle requests for this model type within the
14-
/// generic `/data` endpoint.
11+
/// Contains the necessary functions (deserialization, ID extraction) required
12+
/// to handle requests for this model type within the generic `/data` endpoint.
1513
/// {@endtemplate}
1614
class ModelConfig<T> {
1715
/// {@macro model_config}
1816
const ModelConfig({
1917
required this.fromJson,
20-
required this.toJson,
18+
// toJson removed
2119
required this.getId,
22-
// repositoryProvider removed
2320
});
2421

2522
/// Function to deserialize JSON into an object of type [T].
2623
final FromJson<T> fromJson;
2724

28-
/// Function to serialize an object of type [T] into JSON.
29-
final ToJson<T> toJson;
25+
// toJson field removed
3026

3127
/// Function to extract the unique string ID from an item of type [T].
3228
final String Function(T item) getId;
33-
34-
// repositoryProvider field removed
3529
}
3630

3731
// Repository providers are no longer defined here.
@@ -47,23 +41,22 @@ class ModelConfig<T> {
4741
final modelRegistry = <String, ModelConfig>{
4842
'headline': ModelConfig<Headline>(
4943
fromJson: Headline.fromJson,
50-
toJson: (h) => h.toJson(),
44+
// toJson removed
5145
getId: (h) => h.id,
5246
),
5347
'category': ModelConfig<Category>(
5448
fromJson: Category.fromJson,
55-
toJson: (c) => c.toJson(),
49+
// toJson removed
5650
getId: (c) => c.id,
5751
),
5852
'source': ModelConfig<Source>(
5953
fromJson: Source.fromJson,
60-
toJson: (s) => s.toJson(),
54+
// toJson removed
6155
getId: (s) => s.id,
6256
),
63-
// Add entry for Country model
6457
'country': ModelConfig<Country>(
6558
fromJson: Country.fromJson,
66-
toJson: (c) => c.toJson(),
59+
// toJson removed
6760
getId: (c) => c.id, // Assuming Country has an 'id' field
6861
),
6962
};

routes/api/v1/data/[id].dart

Lines changed: 145 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,109 +17,185 @@ import 'package:ht_shared/ht_shared.dart'; // Import models
1717
Future<Response> onRequest(RequestContext context, String id) async {
1818
// Read dependencies provided by middleware
1919
final modelName = context.read<String>();
20+
// Read ModelConfig for fromJson/getId (needed for PUT)
2021
final modelConfig = context.read<ModelConfig<dynamic>>();
2122

22-
// Determine which repository to use based on the model name
23-
// Assumes repositories are provided globally (e.g., in routes/_middleware.dart)
24-
HtDataRepository<dynamic> repository;
2523
try {
26-
switch (modelName) {
27-
case 'headline':
28-
repository = context.read<HtDataRepository<Headline>>();
29-
case 'category':
30-
repository = context.read<HtDataRepository<Category>>();
31-
case 'source':
32-
repository = context.read<HtDataRepository<Source>>();
33-
case 'country': // Added case for Country
34-
repository = context.read<HtDataRepository<Country>>();
35-
default:
36-
// This should technically be caught by the middleware,
37-
// but added for safety.
24+
// --- GET Request ---
25+
if (context.request.method == HttpMethod.get) {
26+
Map<String, dynamic> itemJson;
27+
try {
28+
switch (modelName) {
29+
case 'headline':
30+
final repo = context.read<HtDataRepository<Headline>>();
31+
final item = await repo.read(id);
32+
// Serialize using the specific model's toJson method
33+
itemJson = item.toJson();
34+
case 'category':
35+
final repo = context.read<HtDataRepository<Category>>();
36+
final item = await repo.read(id);
37+
itemJson = item.toJson();
38+
case 'source':
39+
final repo = context.read<HtDataRepository<Source>>();
40+
final item = await repo.read(id);
41+
itemJson = item.toJson();
42+
case 'country':
43+
final repo = context.read<HtDataRepository<Country>>();
44+
final item = await repo.read(id);
45+
itemJson = item.toJson();
46+
default:
47+
return Response(
48+
statusCode: HttpStatus.internalServerError,
49+
body:
50+
'Internal Server Error: Unsupported model type "$modelName" reached handler.',
51+
);
52+
}
53+
} catch (e) {
54+
// Catch potential provider errors during context.read
55+
print(
56+
'Error reading repository provider for model "$modelName" in GET [id]: $e',
57+
);
3858
return Response(
3959
statusCode: HttpStatus.internalServerError,
4060
body:
41-
'Internal Server Error: Unsupported model type "$modelName" reached handler.',
61+
'Internal Server Error: Could not resolve repository for model "$modelName".',
4262
);
63+
}
64+
// Return the serialized item
65+
return Response.json(body: itemJson);
4366
}
44-
} catch (e) {
45-
// Catch potential provider errors if a repository wasn't provided correctly
46-
print('Error reading repository provider for model "$modelName": $e');
47-
return Response(
48-
statusCode: HttpStatus.internalServerError,
49-
body:
50-
'Internal Server Error: Could not resolve repository for model "$modelName".',
51-
);
52-
}
53-
54-
try {
55-
switch (context.request.method) {
56-
case HttpMethod.get:
57-
final item = await repository.read(id);
58-
// Serialize using the model-specific toJson from ModelConfig
59-
return Response.json(body: modelConfig.toJson(item));
6067

61-
case HttpMethod.put:
62-
final requestBody =
63-
await context.request.json() as Map<String, dynamic>?;
64-
if (requestBody == null) {
65-
return Response(
66-
statusCode: HttpStatus.badRequest,
67-
body: 'Missing or invalid request body.',
68-
);
69-
}
70-
// Deserialize using the model-specific fromJson from ModelConfig
71-
final itemToUpdate = modelConfig.fromJson(requestBody);
68+
// --- PUT Request ---
69+
if (context.request.method == HttpMethod.put) {
70+
final requestBody = await context.request.json() as Map<String, dynamic>?;
71+
if (requestBody == null) {
72+
return Response(
73+
statusCode: HttpStatus.badRequest,
74+
body: 'Missing or invalid request body.',
75+
);
76+
}
7277

73-
// Optional: Validate ID consistency if needed (depends on requirements)
74-
final incomingId = modelConfig.getId(itemToUpdate);
75-
if (incomingId != id) {
76-
return Response(
77-
statusCode: HttpStatus.badRequest,
78-
body:
79-
'Bad Request: ID in request body ("$incomingId") does not match ID in path ("$id").',
80-
);
81-
}
78+
// Deserialize using ModelConfig's fromJson
79+
final itemToUpdate = modelConfig.fromJson(requestBody);
8280

83-
final updatedItem = await repository.update(id, itemToUpdate);
84-
// Serialize the response using the model-specific toJson
85-
return Response.json(body: modelConfig.toJson(updatedItem));
81+
// Validate ID consistency using ModelConfig's getId
82+
final incomingId = modelConfig.getId(itemToUpdate);
83+
if (incomingId != id) {
84+
return Response(
85+
statusCode: HttpStatus.badRequest,
86+
body:
87+
'Bad Request: ID in request body ("$incomingId") does not match ID in path ("$id").',
88+
);
89+
}
8690

87-
case HttpMethod.delete:
88-
await repository.delete(id);
89-
// Return 204 No Content for successful deletion
90-
return Response(statusCode: HttpStatus.noContent);
91+
Map<String, dynamic> updatedJson;
92+
try {
93+
switch (modelName) {
94+
case 'headline':
95+
final repo = context.read<HtDataRepository<Headline>>();
96+
// Cast itemToUpdate to the specific type expected by the repository's update method
97+
final updatedItem = await repo.update(id, itemToUpdate as Headline);
98+
// Serialize using the specific model's toJson method
99+
updatedJson = updatedItem.toJson();
100+
case 'category':
101+
final repo = context.read<HtDataRepository<Category>>();
102+
final updatedItem = await repo.update(id, itemToUpdate as Category);
103+
updatedJson = updatedItem.toJson();
104+
case 'source':
105+
final repo = context.read<HtDataRepository<Source>>();
106+
final updatedItem = await repo.update(id, itemToUpdate as Source);
107+
updatedJson = updatedItem.toJson();
108+
case 'country':
109+
final repo = context.read<HtDataRepository<Country>>();
110+
final updatedItem = await repo.update(id, itemToUpdate as Country);
111+
updatedJson = updatedItem.toJson();
112+
default:
113+
return Response(
114+
statusCode: HttpStatus.internalServerError,
115+
body:
116+
'Internal Server Error: Unsupported model type "$modelName" reached handler.',
117+
);
118+
}
119+
} catch (e) {
120+
// Catch potential provider errors during context.read
121+
print(
122+
'Error reading repository provider for model "$modelName" in PUT [id]: $e',
123+
);
124+
return Response(
125+
statusCode: HttpStatus.internalServerError,
126+
body:
127+
'Internal Server Error: Could not resolve repository for model "$modelName".',
128+
);
129+
}
130+
// Return the serialized updated item
131+
return Response.json(body: updatedJson);
132+
}
91133

92-
// Methods not allowed on the item endpoint
93-
case HttpMethod.post: // POST is for collection endpoint
94-
case HttpMethod
95-
.patch: // PATCH could be added if partial updates are needed
96-
case HttpMethod.head:
97-
case HttpMethod.options:
98-
return Response(statusCode: HttpStatus.methodNotAllowed);
134+
// --- DELETE Request ---
135+
if (context.request.method == HttpMethod.delete) {
136+
try {
137+
// No serialization needed, just call delete based on type
138+
switch (modelName) {
139+
case 'headline':
140+
await context.read<HtDataRepository<Headline>>().delete(id);
141+
case 'category':
142+
await context.read<HtDataRepository<Category>>().delete(id);
143+
case 'source':
144+
await context.read<HtDataRepository<Source>>().delete(id);
145+
case 'country':
146+
await context.read<HtDataRepository<Country>>().delete(id);
147+
default:
148+
return Response(
149+
statusCode: HttpStatus.internalServerError,
150+
body:
151+
'Internal Server Error: Unsupported model type "$modelName" reached handler.',
152+
);
153+
}
154+
} catch (e) {
155+
// Catch potential provider errors during context.read
156+
print(
157+
'Error reading repository provider for model "$modelName" in DELETE [id]: $e',
158+
);
159+
return Response(
160+
statusCode: HttpStatus.internalServerError,
161+
body:
162+
'Internal Server Error: Could not resolve repository for model "$modelName".',
163+
);
164+
}
165+
// Return 204 No Content for successful deletion
166+
return Response(statusCode: HttpStatus.noContent);
99167
}
168+
169+
// --- Other Methods ---
170+
// Methods not allowed on the item endpoint
171+
return Response(statusCode: HttpStatus.methodNotAllowed);
100172
} on NotFoundException catch (e) {
101173
// Handle specific case where the item ID is not found
174+
// This should be caught by the central error handler, but added as fallback
102175
return Response(statusCode: HttpStatus.notFound, body: e.message);
103176
} on HtHttpException catch (e) {
104177
// Handle other known HTTP exceptions
178+
// These should ideally be caught by the central error handler middleware
105179
if (e is BadRequestException) {
106180
return Response(statusCode: HttpStatus.badRequest, body: e.message);
107181
}
108-
print('HtHttpException occurred: $e');
182+
print('HtHttpException occurred in /data/[id].dart: $e');
109183
return Response(
110184
statusCode: HttpStatus.internalServerError,
111185
body: 'API Error: ${e.message}',
112186
);
113187
} on FormatException catch (e) {
114188
// Handle potential JSON parsing/serialization errors during PUT
115-
print('FormatException occurred: $e');
189+
print('FormatException occurred in /data/[id].dart: $e');
116190
return Response(
117191
statusCode: HttpStatus.badRequest,
118192
body: 'Invalid data format: ${e.message}',
119193
);
120194
} catch (e, stackTrace) {
121195
// Catch any other unexpected errors
122-
print('Unexpected error in /data/[id] handler: $e\n$stackTrace');
196+
print(
197+
'Unexpected error in /data/[id].dart handler: $e\n$stackTrace',
198+
);
123199
return Response(
124200
statusCode: HttpStatus.internalServerError,
125201
body: 'Internal Server Error.',

0 commit comments

Comments
 (0)