feat: Update API endpoint usage in repositories to remove redundant path property

- Refactored multiple repository implementations across client and staff features to directly use endpoint objects without accessing the `path` property.
- Introduced a new `FeatureGate` class for client-side feature gating based on user scopes, allowing for better access control to API endpoints.
- Added `ApiEndpoint` class to represent API endpoints with their paths and required scopes for future feature gating.
This commit is contained in:
Achintha Isuru
2026-03-17 12:01:06 -04:00
parent 57bba8ab4e
commit 376b4e4431
47 changed files with 240 additions and 139 deletions

View File

@@ -23,6 +23,7 @@ export 'src/entities/enums/staff_status.dart';
export 'src/entities/enums/user_role.dart';
// Core
export 'src/core/services/api_services/api_endpoint.dart';
export 'src/core/services/api_services/api_response.dart';
export 'src/core/services/api_services/base_api_service.dart';
export 'src/core/services/api_services/base_core_service.dart';

View File

@@ -0,0 +1,16 @@
/// Represents an API endpoint with its path and required scopes for future
/// feature gating.
class ApiEndpoint {
/// Creates an [ApiEndpoint] with the given [path] and optional
/// [requiredScopes].
const ApiEndpoint(this.path, {this.requiredScopes = const <String>[]});
/// The relative URL path (e.g. '/auth/client/sign-in').
final String path;
/// Scopes required to access this endpoint. Empty means no gate.
final List<String> requiredScopes;
@override
String toString() => path;
}

View File

@@ -1,36 +1,38 @@
import 'api_endpoint.dart';
import 'api_response.dart';
/// Abstract base class for API services.
///
/// This defines the contract for making HTTP requests.
/// Methods accept [ApiEndpoint] which carries the path and required scopes.
/// Implementations should validate scopes via [FeatureGate] before executing.
abstract class BaseApiService {
/// Performs a GET request to the specified [endpoint].
Future<ApiResponse> get(String endpoint, {Map<String, dynamic>? params});
Future<ApiResponse> get(ApiEndpoint endpoint, {Map<String, dynamic>? params});
/// Performs a POST request to the specified [endpoint].
Future<ApiResponse> post(
String endpoint, {
ApiEndpoint endpoint, {
dynamic data,
Map<String, dynamic>? params,
});
/// Performs a PUT request to the specified [endpoint].
Future<ApiResponse> put(
String endpoint, {
ApiEndpoint endpoint, {
dynamic data,
Map<String, dynamic>? params,
});
/// Performs a PATCH request to the specified [endpoint].
Future<ApiResponse> patch(
String endpoint, {
ApiEndpoint endpoint, {
dynamic data,
Map<String, dynamic>? params,
});
/// Performs a DELETE request to the specified [endpoint].
Future<ApiResponse> delete(
String endpoint, {
ApiEndpoint endpoint, {
dynamic data,
Map<String, dynamic>? params,
});

View File

@@ -330,3 +330,22 @@ class NotAuthenticatedException extends AppException {
@override
String get messageKey => 'errors.auth.not_authenticated';
}
/// Thrown when the user lacks the required scopes to access an endpoint.
class InsufficientScopeException extends AppException {
/// Creates an [InsufficientScopeException].
const InsufficientScopeException({
required this.requiredScopes,
required this.userScopes,
super.technicalMessage,
}) : super(code: 'SCOPE_001');
/// The scopes required by the endpoint.
final List<String> requiredScopes;
/// The scopes the user currently has.
final List<String> userScopes;
@override
String get messageKey => 'errors.generic.insufficient_scope';
}