1
- //
2
- // ignore_for_file: lines_longer_than_80_chars, avoid_catches_without_on_clauses, avoid_catching_errors
3
-
4
1
import 'dart:io' ;
5
2
6
3
import 'package:dart_frog/dart_frog.dart' ;
7
- import 'package:ht_api/src/registry/model_registry.dart' ;
8
- import 'package:dart_frog/dart_frog.dart' ;
4
+ // Import RequestId from the middleware file where it's defined
9
5
import 'package:ht_api/src/registry/model_registry.dart' ;
10
6
import 'package:ht_data_repository/ht_data_repository.dart' ;
11
7
import 'package:ht_http_client/ht_http_client.dart' ; // Import exceptions
12
- import 'package:ht_shared/ht_shared.dart' ; // Import models, SuccessApiResponse, PaginatedResponse
8
+ // Import models, SuccessApiResponse, PaginatedResponse, ResponseMetadata
9
+ import 'package:ht_shared/ht_shared.dart' ;
10
+
11
+ import '../../../_middleware.dart' ;
13
12
14
13
/// Handles requests for the /api/v1/data collection endpoint.
15
14
/// Dispatches requests to specific handlers based on the HTTP method.
@@ -18,13 +17,18 @@ Future<Response> onRequest(RequestContext context) async {
18
17
final modelName = context.read <String >();
19
18
// Read ModelConfig for fromJson (needed for POST)
20
19
final modelConfig = context.read <ModelConfig <dynamic >>();
20
+ // Read the unique RequestId provided by the root middleware
21
+ // Note: This assumes RequestId is always provided by `routes/_middleware.dart`
22
+ final requestId = context.read <RequestId >().id;
21
23
22
24
try {
23
25
switch (context.request.method) {
24
26
case HttpMethod .get :
25
- return await _handleGet (context, modelName);
27
+ // Pass requestId down to the handler
28
+ return await _handleGet (context, modelName, requestId);
26
29
case HttpMethod .post:
27
- return await _handlePost (context, modelName, modelConfig);
30
+ // Pass requestId down to the handler
31
+ return await _handlePost (context, modelName, modelConfig, requestId);
28
32
// Add cases for other methods if needed in the future
29
33
default :
30
34
// Methods not allowed on the collection endpoint
@@ -38,8 +42,9 @@ Future<Response> onRequest(RequestContext context) async {
38
42
rethrow ;
39
43
} catch (e, stackTrace) {
40
44
// Handle any other unexpected errors locally (e.g., provider resolution)
45
+ // Include requestId in the server log for easier debugging
41
46
print (
42
- 'Unexpected error in /data/index.dart handler: $e \n $stackTrace ' ,
47
+ '[ReqID: $ requestId ] Unexpected error in /data/index.dart handler: $e \n $stackTrace ' ,
43
48
);
44
49
return Response (
45
50
statusCode: HttpStatus .internalServerError,
@@ -50,8 +55,12 @@ Future<Response> onRequest(RequestContext context) async {
50
55
51
56
// --- GET Handler ---
52
57
/// Handles GET requests: Retrieves all items for the specified model
53
- /// (with optional query/pagination).
54
- Future <Response > _handleGet (RequestContext context, String modelName) async {
58
+ /// (with optional query/pagination). Includes request metadata in response.
59
+ Future <Response > _handleGet (
60
+ RequestContext context,
61
+ String modelName,
62
+ String requestId, // Receive requestId
63
+ ) async {
55
64
// Read query parameters
56
65
final queryParams = context.request.uri.queryParameters;
57
66
final startAfterId = queryParams['startAfterId' ];
@@ -112,8 +121,9 @@ Future<Response> _handleGet(RequestContext context, String modelName) async {
112
121
}
113
122
} catch (e) {
114
123
// Catch potential provider errors during context.read within this handler
124
+ // Include requestId in the server log
115
125
print (
116
- 'Error reading repository provider for model "$modelName " in _handleGet: $e ' ,
126
+ '[ReqID: $ requestId ] Error reading repository provider for model "$modelName " in _handleGet: $e ' ,
117
127
);
118
128
return Response (
119
129
statusCode: HttpStatus .internalServerError,
@@ -122,10 +132,16 @@ Future<Response> _handleGet(RequestContext context, String modelName) async {
122
132
);
123
133
}
124
134
125
- // Wrap the PaginatedResponse in SuccessApiResponse and serialize
135
+ // Create metadata including the request ID and current timestamp
136
+ final metadata = ResponseMetadata (
137
+ requestId: requestId,
138
+ timestamp: DateTime .now ().toUtc (), // Use UTC for consistency
139
+ );
140
+
141
+ // Wrap the PaginatedResponse in SuccessApiResponse with metadata
126
142
final successResponse = SuccessApiResponse <PaginatedResponse <dynamic >>(
127
143
data: paginatedResponse,
128
- // metadata: ResponseMetadata(timestamp: DateTime.now()) , // Optional
144
+ metadata: metadata , // Include the created metadata
129
145
);
130
146
131
147
// Need to provide the correct toJsonT for PaginatedResponse
@@ -135,15 +151,18 @@ Future<Response> _handleGet(RequestContext context, String modelName) async {
135
151
),
136
152
);
137
153
154
+ // Return 200 OK with the wrapped and serialized response
138
155
return Response .json (body: responseJson);
139
156
}
140
157
141
158
// --- POST Handler ---
142
159
/// Handles POST requests: Creates a new item for the specified model.
160
+ /// Includes request metadata in response.
143
161
Future <Response > _handlePost (
144
162
RequestContext context,
145
163
String modelName,
146
164
ModelConfig modelConfig,
165
+ String requestId, // Receive requestId
147
166
) async {
148
167
final requestBody = await context.request.json () as Map <String , dynamic >? ;
149
168
if (requestBody == null ) {
@@ -159,13 +178,15 @@ Future<Response> _handlePost(
159
178
newItem = modelConfig.fromJson (requestBody);
160
179
} on TypeError catch (e) {
161
180
// Catch errors during deserialization (e.g., missing required fields)
162
- print ('Deserialization TypeError in POST /data: $e ' );
181
+ // Include requestId in the server log
182
+ print ('[ReqID: $requestId ] Deserialization TypeError in POST /data: $e ' );
163
183
return Response .json (
164
184
statusCode: HttpStatus .badRequest, // 400
165
185
body: {
166
186
'error' : {
167
187
'code' : 'INVALID_REQUEST_BODY' ,
168
- 'message' : 'Invalid request body: Missing or invalid required field(s).' ,
188
+ 'message' :
189
+ 'Invalid request body: Missing or invalid required field(s).' ,
169
190
// 'details': e.toString(), // Optional: Include details in dev
170
191
},
171
192
},
@@ -176,39 +197,58 @@ Future<Response> _handlePost(
176
197
dynamic createdItem; // Use dynamic
177
198
// Repository exceptions (like BadRequestException from create) will propagate
178
199
// up to the main onRequest try/catch and be re-thrown to the middleware.
179
- switch (modelName) {
180
- case 'headline' :
181
- final repo = context.read <HtDataRepository <Headline >>();
182
- createdItem = await repo.create (newItem as Headline );
183
- case 'category' :
184
- final repo = context.read <HtDataRepository <Category >>();
185
- createdItem = await repo.create (newItem as Category );
186
- case 'source' :
187
- final repo = context.read <HtDataRepository <Source >>();
188
- createdItem = await repo.create (newItem as Source );
189
- case 'country' :
190
- final repo = context.read <HtDataRepository <Country >>();
191
- createdItem = await repo.create (newItem as Country );
192
- default :
193
- // This case should ideally be caught by middleware, but added for safety
194
- return Response (
195
- statusCode: HttpStatus .internalServerError,
196
- body:
197
- 'Internal Server Error: Unsupported model type "$modelName " reached handler.' ,
198
- );
200
+ try {
201
+ switch (modelName) {
202
+ case 'headline' :
203
+ final repo = context.read <HtDataRepository <Headline >>();
204
+ createdItem = await repo.create (newItem as Headline );
205
+ case 'category' :
206
+ final repo = context.read <HtDataRepository <Category >>();
207
+ createdItem = await repo.create (newItem as Category );
208
+ case 'source' :
209
+ final repo = context.read <HtDataRepository <Source >>();
210
+ createdItem = await repo.create (newItem as Source );
211
+ case 'country' :
212
+ final repo = context.read <HtDataRepository <Country >>();
213
+ createdItem = await repo.create (newItem as Country );
214
+ default :
215
+ // This case should ideally be caught by middleware, but added for safety
216
+ return Response (
217
+ statusCode: HttpStatus .internalServerError,
218
+ body:
219
+ 'Internal Server Error: Unsupported model type "$modelName " reached handler.' ,
220
+ );
221
+ }
222
+ } catch (e) {
223
+ // Catch potential provider errors during context.read within this handler
224
+ // Include requestId in the server log
225
+ print (
226
+ '[ReqID: $requestId ] Error reading repository provider for model "$modelName " in _handlePost: $e ' ,
227
+ );
228
+ return Response (
229
+ statusCode: HttpStatus .internalServerError,
230
+ body:
231
+ 'Internal Server Error: Could not resolve repository for model "$modelName ".' ,
232
+ );
199
233
}
200
234
201
- // Wrap the created item in SuccessApiResponse and serialize
235
+ // Create metadata including the request ID and current timestamp
236
+ final metadata = ResponseMetadata (
237
+ requestId: requestId,
238
+ timestamp: DateTime .now ().toUtc (), // Use UTC for consistency
239
+ );
240
+
241
+ // Wrap the created item in SuccessApiResponse with metadata
202
242
final successResponse = SuccessApiResponse <dynamic >(
203
243
data: createdItem,
204
- // metadata: ResponseMetadata(timestamp: DateTime.now()) , // Optional
244
+ metadata: metadata , // Include the created metadata
205
245
);
206
246
207
247
// Provide the correct toJsonT for the specific model type
208
248
final responseJson = successResponse.toJson (
209
249
(item) => (item as dynamic ).toJson (), // Assuming all models have toJson
210
250
);
211
251
212
- // Return the serialized response
252
+ // Return 201 Created with the wrapped and serialized response
213
253
return Response .json (statusCode: HttpStatus .created, body: responseJson);
214
254
}
0 commit comments