Skip to content

Commit c224789

Browse files
committed
refactor(data): improve structure of data handlers
- Extract common logic into helper functions - Simplify query parameter parsing - Remove unnecessary comments - Enhance readability and maintainability
1 parent 15d1a7e commit c224789

File tree

1 file changed

+116
-160
lines changed

1 file changed

+116
-160
lines changed

routes/api/v1/data/index.dart

Lines changed: 116 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import 'package:flutter_news_app_api_server_full_source_code/src/registry/model_
1010
import 'package:mongo_dart/mongo_dart.dart';
1111

1212
/// Handles requests for the /api/v1/data collection endpoint.
13-
/// Dispatches requests to specific handlers based on the HTTP method.
1413
Future<Response> onRequest(RequestContext context) async {
1514
switch (context.request.method) {
1615
case HttpMethod.get:
@@ -23,214 +22,171 @@ Future<Response> onRequest(RequestContext context) async {
2322
}
2423

2524
/// 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.
2925
Future<Response> _handleGet(RequestContext context) async {
30-
// Read dependencies provided by middleware
3126
final modelName = context.read<String>();
3227
final modelConfig = context.read<ModelConfig<dynamic>>();
3328
final authenticatedUser = context.read<User>();
34-
35-
// --- Parse Query Parameters ---
3629
final params = context.request.uri.queryParameters;
3730

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;
4934

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;
6845

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;
7553

76-
// --- Repository Call ---
7754
final userIdForRepoCall =
7855
(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+
);
14168

14269
return ResponseHelper.success(
14370
context: context,
14471
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>),
14874
);
14975
}
15076

15177
/// Handles POST requests: Creates a new item in a collection.
15278
Future<Response> _handlePost(RequestContext context) async {
153-
// Read dependencies from middleware
15479
final modelName = context.read<String>();
15580
final modelConfig = context.read<ModelConfig<dynamic>>();
15681
final authenticatedUser = context.read<User>();
15782

158-
// --- Parse Body ---
15983
final requestBody = await context.request.json() as Map<String, dynamic>?;
16084
if (requestBody == null) {
16185
throw const BadRequestException('Missing or invalid request body.');
16286
}
16387

164-
// Standardize ID and timestamps before model creation
16588
final now = DateTime.now().toUtc().toIso8601String();
16689
requestBody['id'] = ObjectId().oid;
16790
requestBody['createdAt'] = now;
16891
requestBody['updatedAt'] = now;
16992

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);
17894

179-
// --- Repository Call ---
18095
final userIdForRepoCall =
18196
(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+
);
185107

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+
) {
187129
switch (modelName) {
188130
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);
194133
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);
200136
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);
206139
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);
212142
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.',
217151
);
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);
218183
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);
224187
default:
225188
throw OperationFailedException(
226189
'Unsupported model type "$modelName" for POST.',
227190
);
228191
}
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-
);
236192
}

0 commit comments

Comments
 (0)