# Purpose Define the API resource standardization requirements for Headroom. All API responses MUST follow Laravel API Resource format with a consistent `"data"` wrapper to ensure predictable response structures across the entire API surface. --- ## Requirements ### Requirement: API Response Standardization All API responses MUST follow Laravel API Resource format with consistent `"data"` wrapper. #### Scenario: Single resource response - **WHEN** an API endpoint returns a single model - **THEN** the response MUST have a `"data"` key containing the resource - **AND** the resource MUST include all required fields defined in the resource class **Example:** ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "John Doe", "role": { "id": 1, "name": "Backend Developer" }, "hourly_rate": 150.00, "active": true, "created_at": "2026-02-01T10:00:00Z" } } ``` #### Scenario: Collection response - **WHEN** an API endpoint returns multiple models - **THEN** the response MUST have a `"data"` key containing an array - **AND** each item in the array MUST follow the single resource format **Example:** ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "John Doe", "role": { "id": 1, "name": "Backend Developer" }, "hourly_rate": 150.00, "active": true, "created_at": "2026-02-01T10:00:00Z" }, { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "Jane Smith", "role": { "id": 2, "name": "Frontend Developer" }, "hourly_rate": 140.00, "active": true, "created_at": "2026-02-01T10:00:00Z" } ] } ``` #### Scenario: Nested relationships - **WHEN** a resource has relationships (e.g., TeamMember has Role) - **THEN** the relationship MUST be nested within the resource - **AND** the nested resource SHOULD also follow the standard format #### Scenario: Error responses - **WHEN** an error occurs (validation, not found, etc.) - **THEN** the response format remains unchanged (no `"data"` wrapper for errors) - **AND** errors continue to use standard Laravel error format: ```json { "message": "The given data was invalid.", "errors": { "field": ["Error message"] } } ``` ### Requirement: Resource Classes The following resource classes MUST be implemented: **UserResource** - **Model:** User - **Purpose:** Transform user data for API responses - **Fields:** id (UUID), name (string), email (string), role (RoleResource when loaded) - **Excluded:** password, remember_token **RoleResource** - **Model:** Role - **Purpose:** Transform role data - **Fields:** id (integer), name (string), description (string|null) **TeamMemberResource** - **Model:** TeamMember - **Purpose:** Transform team member data - **Fields:** id (UUID), name (string), role (RoleResource), hourly_rate (number), active (boolean), created_at, updated_at (ISO 8601) **ProjectStatusResource** - **Model:** ProjectStatus - **Purpose:** Transform project status data - **Fields:** id (integer), name (string), order (integer), is_active (boolean), is_billable (boolean) **ProjectTypeResource** - **Model:** ProjectType - **Purpose:** Transform project type data - **Fields:** id (integer), name (string), description (string|null) **ProjectResource** - **Model:** Project - **Purpose:** Transform project data - **Fields:** id (UUID), code (string), title (string), status (ProjectStatusResource), type (ProjectTypeResource), approved_estimate (number|null), forecasted_effort (object), start_date, end_date (date|null), created_at, updated_at (ISO 8601) **HolidayResource** - **Model:** Holiday - **Purpose:** Transform holiday data - **Fields:** id (UUID), date (date), name (string), description (string|null) **PtoResource** - **Model:** Pto - **Purpose:** Transform PTO request data - **Fields:** id (UUID), team_member (TeamMemberResource when loaded), team_member_id (UUID), start_date, end_date (date), reason (string|null), status (string: pending|approved|rejected), created_at (ISO 8601) **CapacityResource** - **Model:** N/A (calculated data) - **Purpose:** Transform individual capacity calculation - **Fields:** team_member_id (UUID), month (YYYY-MM), working_days (integer), person_days (number), hours (integer), details (array of day-by-day breakdown) **TeamCapacityResource** - **Model:** N/A (calculated data) - **Purpose:** Transform team capacity aggregation - **Fields:** month (YYYY-MM), total_person_days (number), total_hours (integer), members (array of member capacities) **RevenueResource** - **Model:** N/A (calculated data) - **Purpose:** Transform revenue calculation - **Fields:** month (YYYY-MM), possible_revenue (number), member_revenues (array of individual revenues) ### Requirement: API Endpoints Updated Format All existing endpoints remain functionally identical, only response format changes to use Resource classes with `"data"` wrapper: **Auth Endpoints:** - POST /api/auth/login → UserResource with tokens - POST /api/auth/refresh → UserResource with new token **Team Member Endpoints:** - GET /api/team-members → TeamMemberResource[] (collection) - POST /api/team-members → TeamMemberResource (single) - GET /api/team-members/{id} → TeamMemberResource (single) - PUT /api/team-members/{id} → TeamMemberResource (single) - DELETE /api/team-members/{id} → { message: "..." } **Project Endpoints:** - GET /api/projects → ProjectResource[] (collection) - POST /api/projects → ProjectResource (single) - GET /api/projects/{id} → ProjectResource (single) - PUT /api/projects/{id} → ProjectResource (single) - PUT /api/projects/{id}/status → ProjectResource (single) - PUT /api/projects/{id}/estimate → ProjectResource (single) - PUT /api/projects/{id}/forecast → ProjectResource (single) - DELETE /api/projects/{id} → { message: "..." } **Capacity Endpoints:** - GET /api/capacity → CapacityResource (single) - GET /api/capacity/team → TeamCapacityResource (single) - GET /api/capacity/revenue → RevenueResource (single) **Holiday Endpoints:** - GET /api/holidays → HolidayResource[] (collection) - POST /api/holidays → HolidayResource (single) - DELETE /api/holidays/{id} → { message: "..." } **PTO Endpoints:** - GET /api/ptos → PtoResource[] (collection) - POST /api/ptos → PtoResource (single) - PUT /api/ptos/{id}/approve → PtoResource (single) ### Requirement: Test Coverage **Resource Unit Tests** Each resource MUST have unit tests verifying: 1. Single resource returns `"data"` wrapper 2. Collection returns `"data"` array wrapper 3. All expected fields are present 4. Sensitive fields are excluded (e.g., password) 5. Relationships are properly nested 6. Date formatting follows ISO 8601 **Feature Test Updates** All 63 existing feature tests MUST be updated to: 1. Assert response has `"data"` key 2. Access nested data via `response.json()['data']` 3. Verify collection responses have `"data"` array **E2E Test Verification** All 134 E2E tests MUST pass after frontend API client is updated. ### Requirement: Deprecated Patterns The following patterns are DEPRECATED and MUST NOT be used: - Direct `response()->json($model)` calls in controllers - Raw array/object responses without `"data"` wrapper