feat(api): Implement API Resource Standard compliance
- Create BaseResource with formatDate() and formatDecimal() utilities - Create 11 API Resource classes for all models - Update all 6 controllers to return wrapped responses via wrapResource() - Update frontend API client with unwrapResponse() helper - Update all 63+ backend tests to expect 'data' wrapper - Regenerate Scribe API documentation BREAKING CHANGE: All API responses now wrap data in 'data' key per architecture spec. Backend Tests: 70 passed, 5 failed (unrelated to data wrapper) Frontend Unit: 10 passed E2E Tests: 102 passed, 20 skipped API Docs: Generated successfully Refs: openspec/changes/api-resource-standard
This commit is contained in:
18
backend/app/Http/Resources/BaseResource.php
Normal file
18
backend/app/Http/Resources/BaseResource.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
abstract class BaseResource extends JsonResource
|
||||
{
|
||||
protected function formatDate($date): ?string
|
||||
{
|
||||
return $date?->toIso8601String();
|
||||
}
|
||||
|
||||
protected function formatDecimal($value, int $decimals = 2): ?float
|
||||
{
|
||||
return $value !== null ? round((float) $value, $decimals) : null;
|
||||
}
|
||||
}
|
||||
18
backend/app/Http/Resources/CapacityResource.php
Normal file
18
backend/app/Http/Resources/CapacityResource.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class CapacityResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'team_member_id' => $this->resource['team_member_id'] ?? null,
|
||||
'month' => $this->resource['month'] ?? null,
|
||||
'working_days' => $this->resource['working_days'] ?? null,
|
||||
'person_days' => $this->resource['person_days'] ?? null,
|
||||
'hours' => $this->resource['hours'] ?? null,
|
||||
'details' => $this->resource['details'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
16
backend/app/Http/Resources/HolidayResource.php
Normal file
16
backend/app/Http/Resources/HolidayResource.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class HolidayResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'date' => $this->date?->toDateString(),
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
];
|
||||
}
|
||||
}
|
||||
23
backend/app/Http/Resources/ProjectResource.php
Normal file
23
backend/app/Http/Resources/ProjectResource.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class ProjectResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'code' => $this->code,
|
||||
'title' => $this->title,
|
||||
'status' => $this->whenLoaded('status', fn () => new ProjectStatusResource($this->status)),
|
||||
'type' => $this->whenLoaded('type', fn () => new ProjectTypeResource($this->type)),
|
||||
'approved_estimate' => $this->formatDecimal($this->approved_estimate),
|
||||
'forecasted_effort' => $this->forecasted_effort,
|
||||
'start_date' => $this->formatDate($this->start_date),
|
||||
'end_date' => $this->formatDate($this->end_date),
|
||||
'created_at' => $this->formatDate($this->created_at),
|
||||
'updated_at' => $this->formatDate($this->updated_at),
|
||||
];
|
||||
}
|
||||
}
|
||||
17
backend/app/Http/Resources/ProjectStatusResource.php
Normal file
17
backend/app/Http/Resources/ProjectStatusResource.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class ProjectStatusResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'order' => $this->order,
|
||||
'is_active' => $this->is_active,
|
||||
'is_billable' => $this->is_billable,
|
||||
];
|
||||
}
|
||||
}
|
||||
15
backend/app/Http/Resources/ProjectTypeResource.php
Normal file
15
backend/app/Http/Resources/ProjectTypeResource.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class ProjectTypeResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
];
|
||||
}
|
||||
}
|
||||
20
backend/app/Http/Resources/PtoResource.php
Normal file
20
backend/app/Http/Resources/PtoResource.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class PtoResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'team_member_id' => $this->team_member_id,
|
||||
'team_member' => $this->whenLoaded('teamMember', fn () => new TeamMemberResource($this->teamMember)),
|
||||
'start_date' => $this->start_date?->toDateString(),
|
||||
'end_date' => $this->end_date?->toDateString(),
|
||||
'reason' => $this->reason,
|
||||
'status' => $this->status,
|
||||
'created_at' => $this->formatDate($this->created_at),
|
||||
];
|
||||
}
|
||||
}
|
||||
15
backend/app/Http/Resources/RevenueResource.php
Normal file
15
backend/app/Http/Resources/RevenueResource.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class RevenueResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'month' => $this->resource['month'] ?? null,
|
||||
'possible_revenue' => $this->resource['possible_revenue'] ?? null,
|
||||
'member_revenues' => $this->resource['member_revenues'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
18
backend/app/Http/Resources/RoleResource.php
Normal file
18
backend/app/Http/Resources/RoleResource.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class RoleResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
];
|
||||
}
|
||||
}
|
||||
16
backend/app/Http/Resources/TeamCapacityResource.php
Normal file
16
backend/app/Http/Resources/TeamCapacityResource.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class TeamCapacityResource extends BaseResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'month' => $this->resource['month'] ?? null,
|
||||
'total_person_days' => $this->resource['person_days'] ?? null,
|
||||
'total_hours' => $this->resource['hours'] ?? null,
|
||||
'members' => $this->resource['members'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
22
backend/app/Http/Resources/TeamMemberResource.php
Normal file
22
backend/app/Http/Resources/TeamMemberResource.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class TeamMemberResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'role' => $this->whenLoaded('role', fn () => new RoleResource($this->role)),
|
||||
'hourly_rate' => $this->formatDecimal($this->hourly_rate),
|
||||
'active' => $this->active,
|
||||
'created_at' => $this->formatDate($this->created_at),
|
||||
'updated_at' => $this->formatDate($this->updated_at),
|
||||
];
|
||||
}
|
||||
}
|
||||
22
backend/app/Http/Resources/UserResource.php
Normal file
22
backend/app/Http/Resources/UserResource.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
class UserResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'role' => $this->role,
|
||||
'active' => $this->active,
|
||||
'created_at' => $this->formatDate($this->created_at),
|
||||
'updated_at' => $this->formatDate($this->updated_at),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user