|
| 1 | +--- |
| 2 | +title: 'Architectural Deep Dive: Data Access Flow' |
| 3 | +description: A detailed walkthrough of how the API server processes a generic data request from start to finish. |
| 4 | +--- |
| 5 | + |
| 6 | +import { Aside, Steps } from '@astrojs/starlight/components'; |
| 7 | + |
| 8 | +This document provides a comprehensive, step-by-step explanation of the entire request lifecycle for the generic data endpoint (`/api/v1/data`). Understanding this flow is key to appreciating the server's robust, maintainable, and extensible design. |
| 9 | + |
| 10 | +The architecture relies on a series of middleware and two central registries working in concert to process requests securely and efficiently. |
| 11 | + |
| 12 | +### Core Components |
| 13 | + |
| 14 | +- **`ModelRegistry`**: A map that links a model's string name (e.g., `"headline"`) to a `ModelConfig` object. This config holds metadata for authorization rules and type-specific functions (`fromJson`, `getOwnerId`). It answers the question: **"What are the rules for this model?"** |
| 15 | + |
| 16 | +- **`DataOperationRegistry`**: A map that links a model's string name to the actual functions that perform CRUD operations (e.g., `create`, `read`, `update`, `delete`). It answers the question: **"How do I perform this action for this model?"** |
| 17 | + |
| 18 | +- **Middleware**: A chain of functions that process the request sequentially. Each middleware has a specific responsibility, such as authentication, authorization, or data fetching. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +### Request Lifecycle for a Collection (`GET /api/v1/data?model=...`) |
| 23 | + |
| 24 | +This flow applies to requests for a collection of items. |
| 25 | + |
| 26 | +<Steps> |
| 27 | +1. **Authentication (`authenticationProvider`)** |
| 28 | + The first middleware validates the `Bearer` token and injects the `User` object into the request context. If the token is invalid, the user is considered unauthenticated. |
| 29 | + |
| 30 | +2. **Enforce Authentication (`requireAuthentication`)** |
| 31 | + This middleware checks if a `User` object is present in the context. Since the `/data` endpoint is protected, it will throw an `UnauthorizedException` if the user is not authenticated, aborting the request. |
| 32 | + |
| 33 | +3. **Rate Limiting (`_dataRateLimiterMiddleware`)** |
| 34 | + The user's request count is checked. If the limit is exceeded, the request is aborted with a `429 Too Many Requests` error. Admins and publishers bypass this check. |
| 35 | + |
| 36 | +4. **Model Validation (`_modelValidationAndProviderMiddleware`)** |
| 37 | + The `?model=` query parameter is read and validated against the `ModelRegistry`. If the model is valid, its `ModelConfig` is fetched and injected into the context for later use. If not, a `400 Bad Request` is thrown. |
| 38 | + |
| 39 | +5. **Authorization (`authorizationMiddleware`)** |
| 40 | + This crucial middleware uses the `ModelConfig` to determine the required permission for a `GET` collection request. It checks if the authenticated user has this permission. If not, a `403 Forbidden` error is thrown. |
| 41 | + |
| 42 | +6. **Route Handler (`/routes/api/v1/data/index.dart`)** |
| 43 | + The request finally reaches the handler. The handler uses the `DataOperationRegistry` to find the correct `readAll` function for the specified model and executes it, passing along any filter, sort, or pagination parameters. |
| 44 | + |
| 45 | +7. **Response** |
| 46 | + The handler wraps the data from the repository in a standard `SuccessApiResponse` and sends it back to the client. |
| 47 | +</Steps> |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +### Request Lifecycle for a Single Item (`GET /api/v1/data/[id]?model=...`) |
| 52 | + |
| 53 | +This flow is more complex, involving additional middleware to handle fetching and ownership checks. |
| 54 | + |
| 55 | +<Steps> |
| 56 | +1. **Authentication, Rate Limiting, Model Validation, and Authorization** |
| 57 | + The first five steps are identical to the collection request lifecycle. The `authorizationMiddleware` checks the `getItemPermission` from the `ModelConfig`. |
| 58 | + |
| 59 | +2. **Data Fetching (`dataFetchMiddleware`)** |
| 60 | + This is the first item-specific middleware. It reads the item `id` from the URL and uses the `DataOperationRegistry` to find and execute the correct `read` function for the model. |
| 61 | + - If the item is not found, it throws a `NotFoundException`. |
| 62 | + - If found, it wraps the item in a `FetchedItem` object and injects it into the context. |
| 63 | + |
| 64 | +3. **Ownership Check (`ownershipCheckMiddleware`)** |
| 65 | + This middleware inspects the `ModelConfig`. If the action requires an ownership check (`requiresOwnershipCheck: true`) and the user is not an admin, it: |
| 66 | + - Reads the pre-fetched item from the context. |
| 67 | + - Uses the `getOwnerId` function from the `ModelConfig` to get the item's owner ID. |
| 68 | + - Compares the owner ID to the authenticated user's ID. |
| 69 | + - If they don't match, it throws a `403 Forbidden` error. |
| 70 | + |
| 71 | +4. **Route Handler (`/routes/api/v1/data/[id]/index.dart`)** |
| 72 | + The request reaches the final handler. Because of the preceding middleware, the handler can safely assume the item exists and the user is authorized to access it. It simply reads the `FetchedItem` from the context and prepares the success response. |
| 73 | + |
| 74 | +5. **Response** |
| 75 | + The handler wraps the pre-fetched item in a `SuccessApiResponse` and sends it to the client. |
| 76 | +</Steps> |
| 77 | + |
| 78 | +<Aside type="note" title="PUT and DELETE Requests"> |
| 79 | +The flow for `PUT` and `DELETE` requests is identical to the single item `GET` request. The only difference is that the `authorizationMiddleware` checks the `putPermission` or `deletePermission` from the `ModelConfig`, and the final route handler calls the appropriate `update` or `delete` function from the `DataOperationRegistry`. |
| 80 | +</Aside> |
0 commit comments