@@ -10,7 +10,6 @@ import 'package:flutter_news_app_api_server_full_source_code/src/registry/model_
10
10
import 'package:mongo_dart/mongo_dart.dart' ;
11
11
12
12
/// Handles requests for the /api/v1/data collection endpoint.
13
- /// Dispatches requests to specific handlers based on the HTTP method.
14
13
Future <Response > onRequest (RequestContext context) async {
15
14
switch (context.request.method) {
16
15
case HttpMethod .get :
@@ -23,214 +22,171 @@ Future<Response> onRequest(RequestContext context) async {
23
22
}
24
23
25
24
/// Handles GET requests: Retrieves a collection of items.
26
- ///
27
- /// This handler now accepts a single, JSON-encoded `filter` parameter for
28
- /// MongoDB-style queries, along with `sort` and pagination parameters.
29
25
Future <Response > _handleGet (RequestContext context) async {
30
- // Read dependencies provided by middleware
31
26
final modelName = context.read <String >();
32
27
final modelConfig = context.read <ModelConfig <dynamic >>();
33
28
final authenticatedUser = context.read <User >();
34
-
35
- // --- Parse Query Parameters ---
36
29
final params = context.request.uri.queryParameters;
37
30
38
- // 1. Parse Filter (MongoDB-style)
39
- Map <String , dynamic >? filter;
40
- if (params.containsKey ('filter' )) {
41
- try {
42
- filter = jsonDecode (params['filter' ]! ) as Map <String , dynamic >;
43
- } on FormatException catch (e) {
44
- throw BadRequestException (
45
- 'Invalid "filter" parameter: Not valid JSON. $e ' ,
46
- );
47
- }
48
- }
31
+ final filter = params.containsKey ('filter' )
32
+ ? jsonDecode (params['filter' ]! ) as Map <String , dynamic >
33
+ : null ;
49
34
50
- // 2. Parse Sort
51
- List <SortOption >? sort;
52
- if (params.containsKey ('sort' )) {
53
- try {
54
- sort = params['sort' ]! .split (',' ).map ((s) {
55
- final parts = s.split (':' );
56
- final field = parts[0 ];
57
- final order = (parts.length > 1 && parts[1 ] == 'desc' )
58
- ? SortOrder .desc
59
- : SortOrder .asc;
60
- return SortOption (field, order);
61
- }).toList ();
62
- } catch (e) {
63
- throw const BadRequestException (
64
- 'Invalid "sort" parameter format. Use "field:order,field2:order".' ,
65
- );
66
- }
67
- }
35
+ final sort = params.containsKey ('sort' )
36
+ ? (params['sort' ]! .split (',' ).map ((s) {
37
+ final parts = s.split (':' );
38
+ final field = parts[0 ];
39
+ final order = (parts.length > 1 && parts[1 ] == 'desc' )
40
+ ? SortOrder .desc
41
+ : SortOrder .asc;
42
+ return SortOption (field, order);
43
+ }).toList ())
44
+ : null ;
68
45
69
- // 3. Parse Pagination
70
- PaginationOptions ? pagination;
71
- if (params.containsKey ('limit' ) || params.containsKey ('cursor' )) {
72
- final limit = int .tryParse (params['limit' ] ?? '' );
73
- pagination = PaginationOptions (cursor: params['cursor' ], limit: limit);
74
- }
46
+ final pagination =
47
+ (params.containsKey ('limit' ) || params.containsKey ('cursor' ))
48
+ ? PaginationOptions (
49
+ cursor: params['cursor' ],
50
+ limit: int .tryParse (params['limit' ] ?? '' ),
51
+ )
52
+ : null ;
75
53
76
- // --- Repository Call ---
77
54
final userIdForRepoCall =
78
55
(modelConfig.getOwnerId != null &&
79
- ! context.read <PermissionService >().isAdmin (authenticatedUser))
80
- ? authenticatedUser.id
81
- : null ;
82
-
83
- dynamic responseData;
84
-
85
- // The switch statement now only dispatches to the correct repository type.
86
- // The query logic is handled by the repository/client.
87
- switch (modelName) {
88
- case 'headline' :
89
- final repo = context.read <DataRepository <Headline >>();
90
- responseData = await repo.readAll (
91
- userId: userIdForRepoCall,
92
- filter: filter,
93
- sort: sort,
94
- pagination: pagination,
95
- );
96
- case 'topic' :
97
- final repo = context.read <DataRepository <Topic >>();
98
- responseData = await repo.readAll (
99
- userId: userIdForRepoCall,
100
- filter: filter,
101
- sort: sort,
102
- pagination: pagination,
103
- );
104
- case 'source' :
105
- final repo = context.read <DataRepository <Source >>();
106
- responseData = await repo.readAll (
107
- userId: userIdForRepoCall,
108
- filter: filter,
109
- sort: sort,
110
- pagination: pagination,
111
- );
112
- case 'country' :
113
- final repo = context.read <DataRepository <Country >>();
114
- responseData = await repo.readAll (
115
- userId: userIdForRepoCall,
116
- filter: filter,
117
- sort: sort,
118
- pagination: pagination,
119
- );
120
- case 'language' :
121
- final repo = context.read <DataRepository <Language >>();
122
- responseData = await repo.readAll (
123
- userId: userIdForRepoCall,
124
- filter: filter,
125
- sort: sort,
126
- pagination: pagination,
127
- );
128
- case 'user' :
129
- final repo = context.read <DataRepository <User >>();
130
- responseData = await repo.readAll (
131
- userId: userIdForRepoCall,
132
- filter: filter,
133
- sort: sort,
134
- pagination: pagination,
135
- );
136
- default :
137
- throw OperationFailedException (
138
- 'Unsupported model type "$modelName " for GET all.' ,
139
- );
140
- }
56
+ ! context.read <PermissionService >().isAdmin (authenticatedUser))
57
+ ? authenticatedUser.id
58
+ : null ;
59
+
60
+ final responseData = await _readAllItems (
61
+ context,
62
+ modelName,
63
+ userIdForRepoCall,
64
+ filter,
65
+ sort,
66
+ pagination,
67
+ );
141
68
142
69
return ResponseHelper .success (
143
70
context: context,
144
71
data: responseData,
145
- toJsonT: (paginated) => (paginated as PaginatedResponse <dynamic >).toJson (
146
- (item) => (item as dynamic ).toJson () as Map <String , dynamic >,
147
- ),
72
+ toJsonT: (paginated) => (paginated as PaginatedResponse <dynamic >)
73
+ .toJson ((item) => (item as dynamic ).toJson () as Map <String , dynamic >),
148
74
);
149
75
}
150
76
151
77
/// Handles POST requests: Creates a new item in a collection.
152
78
Future <Response > _handlePost (RequestContext context) async {
153
- // Read dependencies from middleware
154
79
final modelName = context.read <String >();
155
80
final modelConfig = context.read <ModelConfig <dynamic >>();
156
81
final authenticatedUser = context.read <User >();
157
82
158
- // --- Parse Body ---
159
83
final requestBody = await context.request.json () as Map <String , dynamic >? ;
160
84
if (requestBody == null ) {
161
85
throw const BadRequestException ('Missing or invalid request body.' );
162
86
}
163
87
164
- // Standardize ID and timestamps before model creation
165
88
final now = DateTime .now ().toUtc ().toIso8601String ();
166
89
requestBody['id' ] = ObjectId ().oid;
167
90
requestBody['createdAt' ] = now;
168
91
requestBody['updatedAt' ] = now;
169
92
170
- dynamic itemToCreate;
171
- try {
172
- itemToCreate = modelConfig.fromJson (requestBody);
173
- } on TypeError catch (e) {
174
- throw BadRequestException (
175
- 'Invalid request body: Missing or invalid required field(s). $e ' ,
176
- );
177
- }
93
+ final itemToCreate = modelConfig.fromJson (requestBody);
178
94
179
- // --- Repository Call ---
180
95
final userIdForRepoCall =
181
96
(modelConfig.getOwnerId != null &&
182
- ! context.read <PermissionService >().isAdmin (authenticatedUser))
183
- ? authenticatedUser.id
184
- : null ;
97
+ ! context.read <PermissionService >().isAdmin (authenticatedUser))
98
+ ? authenticatedUser.id
99
+ : null ;
100
+
101
+ final createdItem = await _createItem (
102
+ context,
103
+ modelName,
104
+ itemToCreate,
105
+ userIdForRepoCall,
106
+ );
185
107
186
- dynamic createdItem;
108
+ return ResponseHelper .success (
109
+ context: context,
110
+ data: createdItem,
111
+ toJsonT: (item) => (item as dynamic ).toJson () as Map <String , dynamic >,
112
+ statusCode: HttpStatus .created,
113
+ );
114
+ }
115
+
116
+ // =============================================================================
117
+ // --- Helper Functions ---
118
+ // =============================================================================
119
+
120
+ /// Encapsulates the logic for reading a collection of items by type.
121
+ Future <PaginatedResponse <dynamic >> _readAllItems (
122
+ RequestContext context,
123
+ String modelName,
124
+ String ? userId,
125
+ Map <String , dynamic >? filter,
126
+ List <SortOption >? sort,
127
+ PaginationOptions ? pagination,
128
+ ) {
187
129
switch (modelName) {
188
130
case 'headline' :
189
- final repo = context.read <DataRepository <Headline >>();
190
- createdItem = await repo.create (
191
- item: itemToCreate as Headline ,
192
- userId: userIdForRepoCall,
193
- );
131
+ return context.read <DataRepository <Headline >>().readAll (
132
+ userId: userId, filter: filter, sort: sort, pagination: pagination);
194
133
case 'topic' :
195
- final repo = context.read <DataRepository <Topic >>();
196
- createdItem = await repo.create (
197
- item: itemToCreate as Topic ,
198
- userId: userIdForRepoCall,
199
- );
134
+ return context.read <DataRepository <Topic >>().readAll (
135
+ userId: userId, filter: filter, sort: sort, pagination: pagination);
200
136
case 'source' :
201
- final repo = context.read <DataRepository <Source >>();
202
- createdItem = await repo.create (
203
- item: itemToCreate as Source ,
204
- userId: userIdForRepoCall,
205
- );
137
+ return context.read <DataRepository <Source >>().readAll (
138
+ userId: userId, filter: filter, sort: sort, pagination: pagination);
206
139
case 'country' :
207
- final repo = context.read <DataRepository <Country >>();
208
- createdItem = await repo.create (
209
- item: itemToCreate as Country ,
210
- userId: userIdForRepoCall,
211
- );
140
+ return context.read <DataRepository <Country >>().readAll (
141
+ userId: userId, filter: filter, sort: sort, pagination: pagination);
212
142
case 'language' :
213
- final repo = context.read <DataRepository <Language >>();
214
- createdItem = await repo.create (
215
- item: itemToCreate as Language ,
216
- userId: userIdForRepoCall,
143
+ return context.read <DataRepository <Language >>().readAll (
144
+ userId: userId, filter: filter, sort: sort, pagination: pagination);
145
+ case 'user' :
146
+ return context.read <DataRepository <User >>().readAll (
147
+ userId: userId, filter: filter, sort: sort, pagination: pagination);
148
+ default :
149
+ throw OperationFailedException (
150
+ 'Unsupported model type "$modelName " for GET all.' ,
217
151
);
152
+ }
153
+ }
154
+
155
+ /// Encapsulates the logic for creating an item by its type.
156
+ Future <dynamic > _createItem (
157
+ RequestContext context,
158
+ String modelName,
159
+ dynamic itemToCreate,
160
+ String ? userId,
161
+ ) {
162
+ switch (modelName) {
163
+ case 'headline' :
164
+ return context
165
+ .read <DataRepository <Headline >>()
166
+ .create (item: itemToCreate as Headline , userId: userId);
167
+ case 'topic' :
168
+ return context
169
+ .read <DataRepository <Topic >>()
170
+ .create (item: itemToCreate as Topic , userId: userId);
171
+ case 'source' :
172
+ return context
173
+ .read <DataRepository <Source >>()
174
+ .create (item: itemToCreate as Source , userId: userId);
175
+ case 'country' :
176
+ return context
177
+ .read <DataRepository <Country >>()
178
+ .create (item: itemToCreate as Country , userId: userId);
179
+ case 'language' :
180
+ return context
181
+ .read <DataRepository <Language >>()
182
+ .create (item: itemToCreate as Language , userId: userId);
218
183
case 'remote_config' :
219
- final repo = context.read <DataRepository <RemoteConfig >>();
220
- createdItem = await repo.create (
221
- item: itemToCreate as RemoteConfig ,
222
- userId: userIdForRepoCall,
223
- );
184
+ return context
185
+ .read <DataRepository <RemoteConfig >>()
186
+ .create (item: itemToCreate as RemoteConfig , userId: userId);
224
187
default :
225
188
throw OperationFailedException (
226
189
'Unsupported model type "$modelName " for POST.' ,
227
190
);
228
191
}
229
-
230
- return ResponseHelper .success (
231
- context: context,
232
- data: createdItem,
233
- toJsonT: (item) => (item as dynamic ).toJson () as Map <String , dynamic >,
234
- statusCode: HttpStatus .created,
235
- );
236
192
}
0 commit comments