Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 33 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ TIMATIC_API_TOKEN=your-api-token-here
The SDK connector is automatically registered in Laravel's service container, making it easy to inject into your controllers, commands, and other classes:

```php
use Timatic\Requests\Budget\GetBudgetsCollectionRequest;
use Timatic\Requests\Budget\GetBudgetRequest;
use Timatic\Requests\Budget\PostBudgetsRequest;
use Timatic\Requests\BudgetType\GetBudgetTypesCollectionRequest;

use Timatic\TimaticConnector;
use Timatic\Requests\BudgetType\GetBudgetTypeCollection;

class BudgetController extends Controller
{
Expand All @@ -53,15 +57,20 @@ class BudgetController extends Controller

public function index()
{
// Using resource methods
$budgets = $this->timatic->budget()->getBudgets()->dto();

// Using direct send() with dtoOrFail() for automatic DTO conversion
// fetch one or more items, limited by the default page size from the api
$budgetTypes = $this->timatic
->send(new GetBudgetTypeCollection())
->dtoOrFail();

return view('budgets.index', compact('budgets', 'budgetTypes'));
->send(new GetBudgetTypesCollectionRequest())
->dto();

$defaultBudget = $this->timatic->send(
new \Timatic\Requests\Budget\GetBudgetRequest(id: '1337')
)->dtoOrFail();

// fetch all DTO's
$budgets = $this->timatic->paginate(new GetBudgetsCollectionRequest())
->dtoCollection();

return view('budgets.index', compact('budgets', 'budgetTypes', 'defaultBudget'));
}

public function store(Request $request)
Expand All @@ -72,7 +81,7 @@ class BudgetController extends Controller
]);

$created = $this->timatic
->send(new \Timatic\Requests\Budget\PostBudgets($budget))
->send(new PostBudgetsRequest($budget))
->dtoOrFail();

return redirect()->route('budgets.show', $created->id);
Expand All @@ -83,14 +92,17 @@ class BudgetController extends Controller
**In Console Commands:**

```php
use \Timatic\Requests\Budget\GetBudgetsCollectionRequest;
use Timatic\TimaticConnector;

class SyncBudgetsCommand extends Command
{
public function handle(TimaticConnector $timatic): int
{
$budgets = $timatic->budget()->getBudgets()->dto();

$budgets = $timatic->paginate(
new GetBudgetsCollectionRequest()
)->dtoCollection()

foreach ($budgets as $budget) {
// Process budgets
}
Expand All @@ -111,6 +123,7 @@ use Timatic\TimaticConnector;
use Timatic\Dto\Budget;
use Timatic\Dto\BudgetType;
use Timatic\Requests\Budget\GetBudgetsRequest;
use Timatic\Requests\Budget\PostBudgetsRequest;
use Timatic\Requests\BudgetType\GetBudgetTypesRequest;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;
Expand All @@ -121,7 +134,7 @@ test('it displays budgets and budget types', function () {
$budgetType = BudgetType::factory()->state(['id' => '1'])->make();

// Create mock responses using factory-generated data
$mockClient = new MockClient([
$mockClient = MockClient::global([
GetBudgetsRequest::class => MockResponse::make([
'data' => [$budget->toJsonApi()],
], 200),
Expand All @@ -130,11 +143,6 @@ test('it displays budgets and budget types', function () {
], 200),
]);

// Bind mock to container
$connector = new TimaticConnector();
$connector->withMockClient($mockClient);
$this->app->instance(TimaticConnector::class, $connector);

// Make request
$response = $this->get(route('budgets.index'));

Expand All @@ -152,16 +160,12 @@ test('it creates a new budget', function () {
'totalPrice' => '5000.00',
])->make();

$mockClient = new MockClient([
$mockClient = MockClient::global([
PostBudgetsRequest::class => MockResponse::make([
'data' => $budget->toJsonApi(),
], 201),
]);

$connector = new TimaticConnector();
$connector->withMockClient($mockClient);
$this->app->instance(TimaticConnector::class, $connector);

$response = $this->post(route('budgets.store'), [
'title' => 'New Budget',
'total_price' => 5000.00,
Expand All @@ -184,35 +188,22 @@ test('it sends a POST request to create a budget using the SDK', function () {
'customerId' => 'customer-123',
])->make();

$mockClient = new MockClient([
$mockClient = MockClient::global([
PostBudgetsRequest::class => MockResponse::make([
'data' => $createdBudget->toJsonApi(),
], 201),
]);

$connector = new TimaticConnector();
$connector->withMockClient($mockClient);

$response = $connector->send(new PostBudgetsRequest($budgetToCreate));
artisan('sync:budgets')->assertOk();

// Assert the request body was sent correctly
$mockClient->assertSent(function (\Saloon\Http\Request $request) {
$mockClient->assertSent(function (PostBudgetsRequest $request) {
$body = $request->body()->all();

return $body['data']['attributes']['title'] === 'New Budget'
&& $body['data']['attributes']['totalPrice'] === '5000.00'
&& $body['data']['attributes']['customerId'] === 'customer-123';
});

// Assert response
expect($response->status())->toBe(201);

$dto = $response->dto();
expect($dto)
->toBeInstanceOf(Budget::class)
->id->toBe('created-456')
->title->toBe('New Budget')
->totalPrice->toBe('5000.00');
});
```

Expand All @@ -221,7 +212,7 @@ test('it sends a POST request to create a budget using the SDK', function () {
Every DTO in the SDK has a corresponding factory class with the following methods:

```php
// Create a single model with random data
// Create a single model with random data, without an ID
$budget = Budget::factory()->make();

// Create multiple models with unique UUID IDs
Expand Down Expand Up @@ -267,7 +258,7 @@ class BudgetController extends Controller
}

// Or collect all items at once
$allBudgets = $paginator->collect();
$allBudgets = $paginator->dtoCollection();
}
}
```
Expand All @@ -282,7 +273,7 @@ The paginator:
All responses are instances of `TimaticResponse` which extends Saloon's Response with JSON:API convenience methods:

```php
$response = $timatic->budget()->getBudgets();
$response = $timatic->send(new GetBudgetsCollectionRequest());

// Get the first item from a collection
$firstBudget = $response->firstItem();
Expand Down
8 changes: 8 additions & 0 deletions generator/JsonApiPestTestGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,19 @@ protected function getTestFunctionStubPath(Endpoint $endpoint): string

/**
* Add "Request" suffix to match JsonApiRequestGenerator behavior
* For collection requests, add "Collection" before "Request"
*/
protected function getRequestClassName(Endpoint $endpoint): string
{
// Use inline collection detection (can't access JsonApiRequestGenerator's method)
$isCollection = $endpoint->method->isGet() && empty($endpoint->pathParameters);

$className = NameHelper::requestClassName($endpoint->name ?: NameHelper::pathBasedName($endpoint));

if ($isCollection) {
$className .= 'Collection';
}

if (! str_ends_with($className, 'Request')) {
$className .= 'Request';
}
Expand Down
6 changes: 6 additions & 0 deletions generator/JsonApiRequestGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,17 @@ protected function shouldIncludeEndpoint(Endpoint $endpoint): bool

/**
* Hook: Add "Request" suffix to class names
* For collection requests, add "Collection" before "Request"
*/
protected function getRequestClassName(Endpoint $endpoint): string
{
$className = parent::getRequestClassName($endpoint);

// For collection requests, add "Collection" suffix
if ($this->isCollectionRequest($endpoint)) {
$className .= 'Collection';
}

if (! str_ends_with($className, 'Request')) {
$className .= 'Request';
}
Expand Down
8 changes: 8 additions & 0 deletions generator/JsonApiResourceGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@ protected function shouldIncludeEndpoint(Endpoint $endpoint): bool

/**
* Hook: Add "Request" suffix to request class names
* For collection requests, add "Collection" before "Request"
*/
protected function getRequestClassName(Endpoint $endpoint): string
{
// Use inline collection detection
$isCollection = $endpoint->method->isGet() && empty($endpoint->pathParameters);

$className = parent::getRequestClassName($endpoint);

if ($isCollection) {
$className .= 'Collection';
}

if (! str_ends_with($className, 'Request')) {
$className .= 'Request';
}
Expand Down
2 changes: 1 addition & 1 deletion generator/generate.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
);

foreach ($files as $fileinfo) {
if (! $fileinfo->isFile() || $fileinfo->getExtension() === 'php') {
if (! $fileinfo->isFile() || $fileinfo->getExtension() !== 'php') {
continue;
}

Expand Down
9 changes: 7 additions & 2 deletions src/Pagination/JsonApiPaginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@

namespace Timatic\Pagination;

use Illuminate\Support\Collection;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\PaginationPlugin\Paginator;

class JsonApiPaginator extends Paginator
{
public function dtoCollection(): Collection
{
return parent::collect()->collect();
}

protected function isLastPage(Response $response): bool
{
return $response->json('links.next') === null;
}

protected function getPageItems(Response $response, Request $request): array
{
// Return the 'data' array from JSON:API response
return $response->json('data', []);
return $response->dto()->toArray();
}

protected function applyPagination(Request $request): Request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* getBudgets
*/
class GetBudgetsRequest extends Request implements Paginatable
class GetBudgetsCollectionRequest extends Request implements Paginatable
{
use HasFilters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* getBudgetTimeSpentTotals
*/
class GetBudgetTimeSpentTotalsRequest extends Request implements Paginatable
class GetBudgetTimeSpentTotalsCollectionRequest extends Request implements Paginatable
{
use HasFilters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* getBudgetTypes
*/
class GetBudgetTypesRequest extends Request implements Paginatable
class GetBudgetTypesCollectionRequest extends Request implements Paginatable
{
protected $model = BudgetType::class;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* getChanges
*/
class GetChangesRequest extends Request implements Paginatable
class GetChangesCollectionRequest extends Request implements Paginatable
{
protected Method $method = Method::GET;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* getCustomers
*/
class GetCustomersRequest extends Request implements Paginatable
class GetCustomersCollectionRequest extends Request implements Paginatable
{
use HasFilters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* getDailyProgresses
*/
class GetDailyProgressesRequest extends Request implements Paginatable
class GetDailyProgressesCollectionRequest extends Request implements Paginatable
{
protected $model = DailyProgress::class;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* getEntries
*/
class GetEntriesRequest extends Request implements Paginatable
class GetEntriesCollectionRequest extends Request implements Paginatable
{
use HasFilters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* getEntrySuggestions
*/
class GetEntrySuggestionsRequest extends Request implements Paginatable
class GetEntrySuggestionsCollectionRequest extends Request implements Paginatable
{
use HasFilters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* getBudgetsExportMails
*/
class GetBudgetsExportMailsRequest extends Request implements Paginatable
class GetBudgetsExportMailsCollectionRequest extends Request implements Paginatable
{
protected $model = ExportMail::class;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* getIncidents
*/
class GetIncidentsRequest extends Request implements Paginatable
class GetIncidentsCollectionRequest extends Request implements Paginatable
{
protected Method $method = Method::GET;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/**
* getMes
*/
class GetMesRequest extends Request implements Paginatable
class GetMesCollectionRequest extends Request implements Paginatable
{
protected Method $method = Method::GET;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* getOvertimes
*/
class GetOvertimesRequest extends Request implements Paginatable
class GetOvertimesCollectionRequest extends Request implements Paginatable
{
use HasFilters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* getTeams
*/
class GetTeamsRequest extends Request implements Paginatable
class GetTeamsCollectionRequest extends Request implements Paginatable
{
protected $model = Team::class;

Expand Down
Loading
Loading