Skip to content

Commit

Permalink
chore: merge branch 'feat-http-query'
Browse files Browse the repository at this point in the history
* feat-http-query:
  refactor(tests): rearrange the way of test sequence runs
  chore(api): normalize nested resource responses
  chore(api): ensure to use model' contract binding and mark all http class as final
  chore(tests): ensure 94580b2 works as expected
  chore(api): apply feature defined in 94580b2
  feat(api): add `with` query string to include certain fields or relations
  • Loading branch information
feryardiant committed Oct 2, 2023
2 parents b0256af + c5762c5 commit ee52247
Show file tree
Hide file tree
Showing 18 changed files with 547 additions and 256 deletions.
22 changes: 15 additions & 7 deletions src/Http/Controllers/DistrictController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,35 @@

namespace Creasi\Nusa\Http\Controllers;

use Creasi\Nusa\Contracts\District;
use Creasi\Nusa\Http\Requests\NusaRequest;
use Creasi\Nusa\Http\Resources\NusaResource;
use Creasi\Nusa\Models\District;

class DistrictController
final class DistrictController
{
public function index(NusaRequest $request, District $district)
public function __construct(
private District $model
) {
// .
}

public function index(NusaRequest $request)
{
return NusaResource::collection($request->apply($district));
return NusaResource::collection($request->apply($this->model));
}

public function show(int $district)
public function show(NusaRequest $request, int $district)
{
$district = District::query()->findOrFail($district);
$district = $this->model->findOrFail($district);

$district->load($request->relations($district));

return new NusaResource($district);
}

public function villages(int $district)
{
$district = District::query()->findOrFail($district);
$district = $this->model->findOrFail($district);

return NusaResource::collection($district->villages()->paginate());
}
Expand Down
26 changes: 17 additions & 9 deletions src/Http/Controllers/ProvinceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,49 @@

namespace Creasi\Nusa\Http\Controllers;

use Creasi\Nusa\Contracts\Province;
use Creasi\Nusa\Http\Requests\NusaRequest;
use Creasi\Nusa\Http\Resources\NusaResource;
use Creasi\Nusa\Models\Province;

class ProvinceController
final class ProvinceController
{
public function index(NusaRequest $request, Province $province)
public function __construct(
private Province $model
) {
// .
}

public function index(NusaRequest $request)
{
return NusaResource::collection($request->apply($province));
return NusaResource::collection($request->apply($this->model));
}

public function show(int $province)
public function show(NusaRequest $request, int $province)
{
$province = Province::query()->find($province);
$province = $this->model->find($province);

$province->load($request->relations($province));

return new NusaResource($province);
}

public function regencies(int $province)
{
$province = Province::query()->findOrFail($province);
$province = $this->model->findOrFail($province);

return NusaResource::collection($province->regencies()->paginate());
}

public function districts(int $province)
{
$province = Province::query()->findOrFail($province);
$province = $this->model->findOrFail($province);

return NusaResource::collection($province->districts()->paginate());
}

public function villages(int $province)
{
$province = Province::query()->findOrFail($province);
$province = $this->model->findOrFail($province);

return NusaResource::collection($province->villages()->paginate());
}
Expand Down
24 changes: 16 additions & 8 deletions src/Http/Controllers/RegencyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,42 @@

namespace Creasi\Nusa\Http\Controllers;

use Creasi\Nusa\Contracts\Regency;
use Creasi\Nusa\Http\Requests\NusaRequest;
use Creasi\Nusa\Http\Resources\NusaResource;
use Creasi\Nusa\Models\Regency;

class RegencyController
final class RegencyController
{
public function index(NusaRequest $request, Regency $regency)
public function __construct(
private Regency $model
) {
// .
}

public function index(NusaRequest $request)
{
return NusaResource::collection($request->apply($regency));
return NusaResource::collection($request->apply($this->model));
}

public function show(int $regency)
public function show(NusaRequest $request, int $regency)
{
$regency = Regency::query()->findOrFail($regency);
$regency = $this->model->findOrFail($regency);

$regency->load($request->relations($regency));

return new NusaResource($regency);
}

public function districts(int $regency)
{
$regency = Regency::query()->findOrFail($regency);
$regency = $this->model->findOrFail($regency);

return NusaResource::collection($regency->districts()->paginate());
}

public function villages(int $regency)
{
$regency = Regency::query()->findOrFail($regency);
$regency = $this->model->findOrFail($regency);

return NusaResource::collection($regency->villages()->paginate());
}
Expand Down
20 changes: 14 additions & 6 deletions src/Http/Controllers/VillageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@

namespace Creasi\Nusa\Http\Controllers;

use Creasi\Nusa\Contracts\Village;
use Creasi\Nusa\Http\Requests\NusaRequest;
use Creasi\Nusa\Http\Resources\NusaResource;
use Creasi\Nusa\Models\Village;

class VillageController
final class VillageController
{
public function index(NusaRequest $request, Village $village)
public function __construct(
private Village $model
) {
// .
}

public function index(NusaRequest $request)
{
return NusaResource::collection($request->apply($village));
return NusaResource::collection($request->apply($this->model));
}

public function show(int $village)
public function show(NusaRequest $request, int $village)
{
$village = Village::query()->findOrFail($village);
$village = $this->model->findOrFail($village);

$village->load($request->relations($village));

return new NusaResource($village);
}
Expand Down
24 changes: 19 additions & 5 deletions src/Http/Requests/NusaRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,31 @@

namespace Creasi\Nusa\Http\Requests;

use Creasi\Nusa\Models\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Http\FormRequest;

class NusaRequest extends FormRequest
final class NusaRequest extends FormRequest
{
public function rules(): array
{
return [
'with' => ['nullable', 'array'],
'with.*' => ['string'],
'codes' => ['nullable', 'array'],
'codes.*' => ['nullable', 'numeric'],
'codes.*' => ['numeric'],
'search' => ['nullable', 'string'],
'page' => ['nullable', 'numeric'],
'per-page' => ['nullable', 'numeric'],
];
}

public function apply(Model $model)
/**
* @param \Creasi\Nusa\Models\Model $model
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function apply($model)
{
$result = $model->newQuery()
$result = $model->load($this->relations($model))->query()
->when($this->has('search'), function (Builder $query) {
$query->search($this->query('search'));
})
Expand All @@ -31,4 +36,13 @@ public function apply(Model $model)

return $result->paginate($this->query('per-page'));
}

/**
* @param \Creasi\Nusa\Models\Model $model
* @return string[]
*/
public function relations($model): array
{
return \array_filter((array) $this->query('with', []), fn (string $relate) => \method_exists($model, $relate));
}
}
68 changes: 66 additions & 2 deletions src/Http/Resources/NusaResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,77 @@

namespace Creasi\Nusa\Http\Resources;

use Creasi\Nusa\Contracts\Village;
use Creasi\Nusa\Models\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

/**
* @property-read Model $resource
*/
class NusaResource extends JsonResource
final class NusaResource extends JsonResource
{
//
public function __construct($resource)
{
parent::__construct($resource);

$this->additional([
'meta' => [],
]);
}

public function toArray(Request $request): array
{
$with = $request->query('with', []);
$arr = $this->normalize($this->resource, $with);

foreach ($with as $relation) {
$arr[$relation] = $this->whenLoaded($relation, function () use ($relation, $with) {
$relate = $this->resource->$relation;

if ($relate instanceof Model) {
return $this->normalize($relate, $with);
}

return $relate->map(fn (Model $model) => $this->normalize($model, $with));
});
}

return \array_filter($arr);
}

private function normalize(Model $resource, array $with = []): array
{
$arr = [
$resource->getKeyName() => $resource->getKey(),
'name' => $resource->name,
'district_code' => $resource->district_code,
'regency_code' => $resource->regency_code,
'province_code' => $resource->province_code,
'postal_code' => $this->when(
$this->isVillage($resource),
fn () => $resource->postal_code
),
];

if (\in_array('postal_codes', $with, true)) {
$arr['postal_codes'] = $this->when(
! $this->isVillage($resource),
fn () => $resource->postal_codes
);
}

if (\in_array('coordinates', $with, true)) {
$arr['latitude'] = $resource->latitude;
$arr['longitude'] = $resource->longitude;
$arr['coordinates'] = $resource->coordinates;
}

return \array_filter($arr);
}

private function isVillage(Model $resource): bool
{
return $resource instanceof Village;
}
}
8 changes: 2 additions & 6 deletions src/Models/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* @property-read string $name
* @property-read null|array $coordinates
*
* @method static static search(string|int $keyword)
* @method static static search(string $keyword)
* @method Builder whereCode(int $code)
* @method Builder whereName(string $name)
*
Expand Down Expand Up @@ -48,12 +48,8 @@ public function getFillable()
return \array_merge(parent::getFillable(), ['code', 'name', 'coordinates']);
}

public function scopeSearch(Builder $query, string|int $keyword)
public function scopeSearch(Builder $query, string $keyword)
{
if (\is_numeric($keyword)) {
return $query->where('code', $keyword);
}

return $query->whereRaw(match ($this->getConnection()->getDriverName()) {
'pgsql' => 'name ilike ?',
'mysql' => 'lower(name) like lower(?)',
Expand Down
Loading

0 comments on commit ee52247

Please sign in to comment.