-
Notifications
You must be signed in to change notification settings - Fork 0
Fix data route #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Fix data route #40
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
3247e93
feat(data): add logging to data collection handler
fulleni a1d9ff3
refactor(data): enhance logging and error handling in CRUD operations
fulleni 605dceb
feat(routes): enhance logger to include error and stack trace details
fulleni c13a2ad
feat(middleware): implement data fetch and ownership check chain
fulleni c15f78f
feat(middlewares): implement data fetch middleware
fulleni e63fbbf
refactor(data_fetch_middleware): remove unused import
fulleni c1048a2
refactor(ownershipCheckMiddleware): improve and clarify middleware logic
fulleni 89db404
refactor(data): simplify item fetch logic in GET handler
fulleni File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import 'package:core/core.dart'; | ||
import 'package:dart_frog/dart_frog.dart'; | ||
import 'package:data_repository/data_repository.dart'; | ||
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/ownership_check_middleware.dart'; | ||
import 'package:flutter_news_app_api_server_full_source_code/src/services/dashboard_summary_service.dart'; | ||
import 'package:logging/logging.dart'; | ||
|
||
final _log = Logger('DataFetchMiddleware'); | ||
|
||
/// Middleware to fetch a data item by its ID and provide it to the context. | ||
/// | ||
/// This middleware is responsible for: | ||
/// 1. Reading the `modelName` and item `id` from the context. | ||
/// 2. Calling the appropriate data repository to fetch the item. | ||
/// 3. If the item is found, providing it to the downstream context wrapped in a | ||
/// [FetchedItem] for type safety. | ||
/// 4. If the item is not found, throwing a [NotFoundException] to halt the | ||
/// request pipeline early. | ||
/// | ||
/// This centralizes the item fetching logic for all item-specific routes, | ||
/// ensuring that subsequent middleware (like ownership checks) and the final | ||
/// route handler can safely assume the item exists in the context. | ||
Middleware dataFetchMiddleware() { | ||
return (handler) { | ||
return (context) async { | ||
final modelName = context.read<String>(); | ||
final id = context.request.uri.pathSegments.last; | ||
|
||
_log.info('Fetching item for model "$modelName", id "$id".'); | ||
|
||
final item = await _fetchItem(context, modelName, id); | ||
|
||
if (item == null) { | ||
_log.warning( | ||
'Item not found for model "$modelName", id "$id".', | ||
); | ||
throw NotFoundException( | ||
'The requested item of type "$modelName" with id "$id" was not found.', | ||
); | ||
} | ||
|
||
_log.finer('Item found. Providing to context.'); | ||
final updatedContext = context.provide<FetchedItem<dynamic>>( | ||
() => FetchedItem(item), | ||
); | ||
|
||
return handler(updatedContext); | ||
}; | ||
}; | ||
} | ||
|
||
/// Helper function to fetch an item from the correct repository based on the | ||
/// model name. | ||
/// | ||
/// This function contains the switch statement that maps a `modelName` string | ||
/// to a specific `DataRepository` call. | ||
/// | ||
/// Throws [OperationFailedException] for unsupported model types. | ||
Future<dynamic> _fetchItem( | ||
RequestContext context, | ||
String modelName, | ||
String id, | ||
) async { | ||
// The `userId` is not needed here because this middleware's purpose is to | ||
// fetch the item regardless of ownership. Ownership is checked in a | ||
// subsequent middleware. We pass `null` for `userId` to ensure we are | ||
// performing a global lookup for the item. | ||
const String? userId = null; | ||
|
||
try { | ||
switch (modelName) { | ||
case 'headline': | ||
return await context.read<DataRepository<Headline>>().read( | ||
id: id, | ||
userId: userId, | ||
); | ||
case 'topic': | ||
return await context | ||
.read<DataRepository<Topic>>() | ||
.read(id: id, userId: userId); | ||
case 'source': | ||
return await context.read<DataRepository<Source>>().read( | ||
id: id, | ||
userId: userId, | ||
); | ||
case 'country': | ||
return await context.read<DataRepository<Country>>().read( | ||
id: id, | ||
userId: userId, | ||
); | ||
case 'language': | ||
return await context.read<DataRepository<Language>>().read( | ||
id: id, | ||
userId: userId, | ||
); | ||
case 'user': | ||
return await context | ||
.read<DataRepository<User>>() | ||
.read(id: id, userId: userId); | ||
case 'user_app_settings': | ||
return await context.read<DataRepository<UserAppSettings>>().read( | ||
id: id, | ||
userId: userId, | ||
); | ||
case 'user_content_preferences': | ||
return await context | ||
.read<DataRepository<UserContentPreferences>>() | ||
.read( | ||
id: id, | ||
userId: userId, | ||
); | ||
case 'remote_config': | ||
return await context.read<DataRepository<RemoteConfig>>().read( | ||
id: id, | ||
userId: userId, | ||
); | ||
case 'dashboard_summary': | ||
// This is a special case that doesn't use a standard repository. | ||
return await context.read<DashboardSummaryService>().getSummary(); | ||
default: | ||
_log.warning('Unsupported model type "$modelName" for fetch operation.'); | ||
throw OperationFailedException( | ||
'Unsupported model type "$modelName" for fetch operation.', | ||
); | ||
} | ||
} on NotFoundException { | ||
// The repository will throw this if the item doesn't exist. | ||
// We return null to let the main middleware handler throw a more | ||
// detailed exception. | ||
return null; | ||
} catch (e, s) { | ||
_log.severe( | ||
'Unhandled exception in _fetchItem for model "$modelName", id "$id".', | ||
e, | ||
s, | ||
); | ||
// Re-throw as a standard exception type that the main error handler | ||
// can process into a 500 error, while preserving the original cause. | ||
throw OperationFailedException( | ||
'An internal error occurred while fetching the item: $e', | ||
); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,27 @@ | ||
import 'package:dart_frog/dart_frog.dart'; | ||
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/data_fetch_middleware.dart'; | ||
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/ownership_check_middleware.dart'; | ||
|
||
/// Middleware specific to the item-level `/api/v1/data/[id]` route path. | ||
/// | ||
/// This middleware applies the [ownershipCheckMiddleware] to perform an | ||
/// ownership check on the requested item *after* the parent middleware | ||
/// (`/api/v1/data/_middleware.dart`) has already performed authentication and | ||
/// authorization checks. | ||
/// This middleware chain is responsible for fetching the requested data item | ||
/// and then performing an ownership check on it. | ||
/// | ||
/// This ensures that only authorized users can proceed, and then this | ||
/// middleware adds the final layer of security by verifying item ownership | ||
/// for non-admin users when required by the model's configuration. | ||
/// The execution order is as follows: | ||
/// 1. `dataFetchMiddleware`: This runs first. It fetches the item by its ID | ||
/// from the database and provides it to the context. If the item is not | ||
/// found, it throws a `NotFoundException`, aborting the request. | ||
/// 2. `ownershipCheckMiddleware`: This runs second. It reads the fetched item | ||
/// from the context and verifies that the authenticated user is the owner, | ||
/// if the model's configuration requires such a check. | ||
/// | ||
/// This ensures that the final route handler only executes for valid, | ||
/// authorized requests and can safely assume the requested item exists. | ||
Handler middleware(Handler handler) { | ||
// The `ownershipCheckMiddleware` will run after the middleware from | ||
// `/api/v1/data/_middleware.dart` (authn, authz, model validation). | ||
return handler.use(ownershipCheckMiddleware()); | ||
// The middleware is applied in reverse order of execution. | ||
// `ownershipCheckMiddleware` is the inner middleware, running after | ||
// `dataFetchMiddleware`. | ||
return handler | ||
.use(ownershipCheckMiddleware()) // Runs second | ||
.use(dataFetchMiddleware()); // Runs first | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.