Skip to content

Commit 11ff628

Browse files
committed
feat: add getArticleList functionality with pagination and search criteria
1 parent 186057d commit 11ff628

File tree

8 files changed

+205
-1
lines changed

8 files changed

+205
-1
lines changed

contexts/ArticlePublishing/Application/Coordinators/ArticlePublishingCoordinator.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Contexts\ArticlePublishing\Domain\Models\Article;
1111
use Carbon\CarbonImmutable;
1212
use Contexts\ArticlePublishing\Application\DTOs\CreateArticleDTO;
13+
use Contexts\ArticlePublishing\Application\DTOs\GetArticleListDTO;
14+
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
1315

1416
class ArticlePublishingCoordinator extends BaseCoordinator
1517
{
@@ -68,4 +70,9 @@ public function getArticle(int $id): Article
6870
{
6971
return $this->repository->getById(new ArticleId($id));
7072
}
73+
74+
public function getArticleList(GetArticleListDTO $data): LengthAwarePaginator
75+
{
76+
return $this->repository->paginate($data->page, $data->perPage, $data->toCriteria());
77+
}
7178
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Contexts\ArticlePublishing\Application\DTOs;
6+
7+
class GetArticleListDTO
8+
{
9+
public function __construct(
10+
public readonly ?string $id,
11+
public readonly ?string $title,
12+
public readonly ?string $status,
13+
public readonly ?array $createdAtRange,
14+
public readonly int $page,
15+
public readonly int $perPage,
16+
) {
17+
}
18+
19+
public static function fromRequest(array $data): self
20+
{
21+
return new self(
22+
$data['id'] ?? null,
23+
$data['title'] ?? null,
24+
$data['status'] ?? null,
25+
$data['created_at_range'] ?? null,
26+
$data['page'] ?? 1,
27+
$data['per_page'] ?? 10,
28+
);
29+
}
30+
31+
public function toCriteria(): array
32+
{
33+
return [
34+
'id' => $this->id,
35+
'title' => $this->title,
36+
'status' => $this->status,
37+
'created_at_range' => $this->createdAtRange,
38+
];
39+
}
40+
}

contexts/ArticlePublishing/Infrastructure/Records/ArticleRecord.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Contexts\ArticlePublishing\Domain\Models\ArticleId;
1010
use Contexts\ArticlePublishing\Domain\Models\ArticleStatus;
1111
use Illuminate\Support\Carbon;
12+
use Illuminate\Database\Eloquent\Builder;
1213

1314
/**
1415
* @property int $id
@@ -60,4 +61,26 @@ public function toDomain(array $events = []): Article
6061
events: $events
6162
);
6263
}
64+
65+
public function scopeSearch(Builder $query, array $criteria = [])
66+
{
67+
$query->when(isset($criteria['id']), function ($query) use ($criteria) {
68+
$query->where('id', $criteria['id']);
69+
});
70+
71+
$query->when(isset($criteria['title']), function ($query) use ($criteria) {
72+
$query->where('title', 'like', "%{$criteria['title']}%");
73+
});
74+
75+
$query->when(isset($criteria['status']), function ($query) use ($criteria) {
76+
$query->where('status', $criteria['status']);
77+
});
78+
79+
$query->when(isset($criteria['created_at_range']), function ($query) use ($criteria) {
80+
[$start, $end] = $criteria['created_at_range'];
81+
$query->whereBetween('created_at', [$start, $end]);
82+
});
83+
84+
return $query;
85+
}
6386
}

contexts/ArticlePublishing/Infrastructure/Repositories/ArticleRepository.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Contexts\ArticlePublishing\Domain\Models\Article;
88
use Contexts\ArticlePublishing\Domain\Models\ArticleId;
99
use Contexts\ArticlePublishing\Infrastructure\Records\ArticleRecord;
10+
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
1011

1112
class ArticleRepository
1213
{
@@ -42,4 +43,9 @@ public function update(Article $article): Article
4243

4344
return $record->toDomain($article->getEvents());
4445
}
46+
47+
public function paginate(int $page = 1, int $perPage = 10, array $criteria = []): LengthAwarePaginator
48+
{
49+
return ArticleRecord::query()->search($criteria)->paginate($perPage, ['*'], 'page', $page);
50+
}
4551
}

contexts/ArticlePublishing/Infrastructure/Routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Route::middleware([])->name('ArticlePublishing.')->group(function () {
99
Route::controller(ArticlePublishingController::class)->prefix('articles')->name('ArticlePublishing.')->group(function () {
1010
Route::get('{id}', 'getArticle')->name('getArticle');
11+
Route::get('', 'getArticleList')->name('getArticleList');
1112
Route::post('', 'createArticle')->name('createArticle');
1213
Route::put('{id}/publish', 'publishDraft')->name('publishDraft');
1314
});

contexts/ArticlePublishing/Presentation/Controllers/ArticlePublishingController.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use Contexts\ArticlePublishing\Application\DTOs\CreateArticleDTO;
1212
use Contexts\ArticlePublishing\Presentation\Requests\PublishDraftRequest;
1313
use Contexts\ArticlePublishing\Presentation\Requests\GetArticleRequest;
14+
use Contexts\ArticlePublishing\Presentation\Requests\GetArticleListRequest;
15+
use Contexts\ArticlePublishing\Application\DTOs\GetArticleListDTO;
1416

1517
class ArticlePublishingController extends BaseController
1618
{
@@ -40,4 +42,13 @@ public function getArticle(GetArticleRequest $request)
4042

4143
return $this->success($result, ArticleResource::class)->send();
4244
}
45+
46+
public function getArticleList(GetArticleListRequest $request)
47+
{
48+
$result = app(ArticlePublishingCoordinator::class)->getArticleList(
49+
GetArticleListDTO::fromRequest($request->validated())
50+
);
51+
52+
return $this->success($result, ArticleResource::class)->send();
53+
}
4354
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Contexts\ArticlePublishing\Presentation\Requests;
6+
7+
use App\Http\Requests\BaseFormRequest;
8+
9+
class GetArticleListRequest extends BaseFormRequest
10+
{
11+
public function rules(): array
12+
{
13+
return [
14+
'id' => ['integer', 'gt:0'],
15+
'title' => ['string', 'max:255'],
16+
'status' => ['string', 'in:draft,published'],
17+
'created_at_range' => ['array', 'size:2'],
18+
'created_at_range.*' => ['date'],
19+
'page' => ['integer', 'gt:0'],
20+
'per_page' => ['integer', 'gt:0'],
21+
];
22+
}
23+
}

contexts/ArticlePublishing/Tests/Feature/Infrastructure/Repositories/ArticleRepositoryTest.php

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?php
22

33
declare(strict_types=1);
4-
54
use Contexts\ArticlePublishing\Domain\Models\Article;
65
use Contexts\ArticlePublishing\Domain\Models\ArticleId;
76
use Contexts\ArticlePublishing\Domain\Models\ArticleStatus;
@@ -82,3 +81,97 @@
8281
expect($result->getBody())->toBe('Updated Content');
8382
expect($result->getStatus()->equals(ArticleStatus::published()))->toBeTrue();
8483
});
84+
85+
it('can paginate articles', function () {
86+
// Create multiple test articles
87+
$articleRepository = new ArticleRepository();
88+
89+
// Create 5 articles
90+
for ($i = 1; $i <= 5; $i++) {
91+
$article = Article::createPublished(
92+
new ArticleId(0),
93+
"Article $i",
94+
"Content $i",
95+
new CarbonImmutable()
96+
);
97+
$articleRepository->create($article);
98+
}
99+
100+
// Test pagination with default criteria
101+
$result = $articleRepository->paginate(1, 2, []);
102+
103+
// Assert pagination metadata
104+
expect($result->total())->toBe(5);
105+
expect($result->perPage())->toBe(2);
106+
expect($result->currentPage())->toBe(1);
107+
expect(count($result->items()))->toBe(2); // 2 items on the first page
108+
109+
// Test second page
110+
$result2 = $articleRepository->paginate(2, 2, []);
111+
expect($result2->currentPage())->toBe(2);
112+
expect(count($result2->items()))->toBe(2); // 2 items on the second page
113+
114+
// Test last page
115+
$result3 = $articleRepository->paginate(3, 2, []);
116+
expect($result3->currentPage())->toBe(3);
117+
expect(count($result3->items()))->toBe(1); // 1 item on the last page
118+
});
119+
120+
it('can filter articles with search criteria', function () {
121+
$articleRepository = new ArticleRepository();
122+
123+
// Create articles with specific titles
124+
$article1 = Article::createDraft(
125+
new ArticleId(0),
126+
"Laravel Article",
127+
"Content about Laravel",
128+
new CarbonImmutable()
129+
);
130+
$articleRepository->create($article1);
131+
132+
$article2 = Article::createDraft(
133+
new ArticleId(0),
134+
"PHP Tutorial",
135+
"Content about PHP",
136+
new CarbonImmutable()
137+
);
138+
$articleRepository->create($article2);
139+
140+
$article3 = Article::createPublished(
141+
new ArticleId(0),
142+
"Laravel Tips",
143+
"More Laravel content",
144+
new CarbonImmutable()
145+
);
146+
$articleRepository->create($article3);
147+
148+
// Test search by title criteria
149+
$result = $articleRepository->paginate(1, 10, ['title' => 'Laravel']);
150+
expect($result->total())->toBe(2); // Should find the two Laravel articles
151+
152+
// Test search with status criteria
153+
$result = $articleRepository->paginate(1, 10, [
154+
'title' => 'Laravel',
155+
'status' => ArticleRecord::mapStatusToRecord(ArticleStatus::published())
156+
]);
157+
expect($result->total())->toBe(1); // Should only find the published Laravel article
158+
159+
// Test with no matching criteria
160+
$result = $articleRepository->paginate(1, 10, ['title' => 'Django']);
161+
expect($result->total())->toBe(0);
162+
163+
// Test search by created_at_range criteria
164+
$article4 = Article::createPublished(
165+
new ArticleId(0),
166+
"Laravel Tips",
167+
"More Laravel content",
168+
new CarbonImmutable('2021-01-01')
169+
);
170+
$articleRepository->create($article4);
171+
172+
$result = $articleRepository->paginate(1, 10, [
173+
'created_at_range' => ['2021-01-01', '2021-01-02']
174+
]);
175+
176+
expect($result->total())->toBe(1); // Should find the article created on 2021-01-01
177+
});

0 commit comments

Comments
 (0)