Skip to content

Commit f5398a7

Browse files
committed
feat: add category support to article creation and updates, including validation and relationships
1 parent 8e3e313 commit f5398a7

File tree

12 files changed

+111
-11
lines changed

12 files changed

+111
-11
lines changed

contexts/ArticlePublishing/Application/Coordinators/ArticlePublishingCoordinator.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Contexts\ArticlePublishing\Application\DTOs\CreateArticleDTO;
1111
use Contexts\ArticlePublishing\Application\DTOs\GetArticleListDTO;
1212
use Contexts\ArticlePublishing\Application\DTOs\UpdateArticleDTO;
13+
use Contexts\ArticlePublishing\Domain\Gateway\CategoryGateway;
1314
use Contexts\ArticlePublishing\Domain\Models\Article;
1415
use Contexts\ArticlePublishing\Domain\Models\ArticleId;
1516
use Contexts\ArticlePublishing\Domain\Models\ArticleStatus;
@@ -19,7 +20,8 @@
1920
class ArticlePublishingCoordinator extends BaseCoordinator
2021
{
2122
public function __construct(
22-
private ArticleRepository $repository
23+
private ArticleRepository $repository,
24+
private CategoryGateway $categoryGateway
2325
) {}
2426

2527
public function create(CreateArticleDTO $data): Article
@@ -42,6 +44,7 @@ private function createDraft(CreateArticleDTO $data): Article
4244
ArticleId::null(),
4345
$data->title,
4446
$data->body,
47+
$this->categoryGateway->getArticleCategories($data->category_ids),
4548
$data->created_at ? CarbonImmutable::parse($data->created_at) : null
4649
);
4750

@@ -54,6 +57,7 @@ private function createPublished(CreateArticleDTO $data): Article
5457
ArticleId::null(),
5558
$data->title,
5659
$data->body,
60+
$this->categoryGateway->getArticleCategories($data->category_ids),
5761
$data->created_at ? CarbonImmutable::parse($data->created_at) : null
5862
);
5963

@@ -87,6 +91,7 @@ public function updateArticle(int $id, UpdateArticleDTO $data): Article
8791
$data->title,
8892
$data->body,
8993
$data->status ? ArticleStatus::fromString($data->status) : null,
94+
$this->categoryGateway->getArticleCategories($data->category_ids),
9095
$data->created_at ? CarbonImmutable::parse($data->created_at) : null
9196
);
9297

contexts/ArticlePublishing/Application/DTOs/CreateArticleDTO.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public function __construct(
1010
public readonly string $title,
1111
public readonly string $body,
1212
public readonly string $status,
13+
public readonly array $category_ids,
1314
public readonly ?string $created_at
1415
) {}
1516

@@ -19,6 +20,7 @@ public static function fromRequest(array $data): self
1920
$data['title'],
2021
$data['body'] ?? '',
2122
$data['status'] ?? 'draft',
23+
$data['category_ids'] ?? [],
2224
$data['created_at'] ?? null,
2325
);
2426
}

contexts/ArticlePublishing/Application/DTOs/GetArticleListDTO.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public function __construct(
1010
public readonly ?string $id,
1111
public readonly ?string $title,
1212
public readonly ?string $status,
13+
public readonly ?int $categoryId,
1314
public readonly ?array $createdAtRange,
1415
public readonly int $page,
1516
public readonly int $perPage,
@@ -21,6 +22,7 @@ public static function fromRequest(array $data): self
2122
$data['id'] ?? null,
2223
$data['title'] ?? null,
2324
$data['status'] ?? null,
25+
$data['category_id'] ?? null,
2426
$data['created_at_range'] ?? null,
2527
$data['page'] ?? 1,
2628
$data['per_page'] ?? 10,
@@ -33,6 +35,7 @@ public function toCriteria(): array
3335
'id' => $this->id,
3436
'title' => $this->title,
3537
'status' => $this->status,
38+
'category_id' => $this->categoryId,
3639
'created_at_range' => $this->createdAtRange,
3740
];
3841
}

contexts/ArticlePublishing/Application/DTOs/UpdateArticleDTO.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public function __construct(
1010
public readonly ?string $title,
1111
public readonly ?string $body,
1212
public readonly ?string $status,
13+
public readonly ?array $category_ids,
1314
public readonly ?string $created_at
1415
) {}
1516

@@ -19,6 +20,7 @@ public static function fromRequest(array $data): self
1920
$data['title'] ?? null,
2021
$data['body'] ?? null,
2122
$data['status'] ?? null,
23+
$data['category_ids'] ?? null,
2224
$data['created_at'] ?? null,
2325
);
2426
}

contexts/ArticlePublishing/Infrastructure/Migrations/2025_03_01_022301_create_articles_table.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ public function up(): void
1919
$table->text('body')->nullable();
2020
$table->tinyInteger('status')->default(0)->comment('0: draft, 1: published, 2: archived, 3: deleted');
2121
$table->timestamps();
22+
$table->softDeletes();
23+
});
24+
25+
Schema::create('pivot_article_category', function (Blueprint $table) {
26+
$table->increments('id');
27+
$table->unsignedInteger('article_id');
28+
$table->unsignedInteger('category_id');
29+
$table->timestamps();
2230
});
2331
}
2432

contexts/ArticlePublishing/Infrastructure/Records/ArticleRecord.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
use App\Exceptions\SysException;
88
use App\Http\Models\BaseModel;
99
use Contexts\ArticlePublishing\Domain\Models\Article;
10+
use Contexts\ArticlePublishing\Domain\Models\ArticleCategory;
11+
use Contexts\ArticlePublishing\Domain\Models\ArticleCategoryCollection;
1012
use Contexts\ArticlePublishing\Domain\Models\ArticleId;
1113
use Contexts\ArticlePublishing\Domain\Models\ArticleStatus;
14+
use Contexts\CategoryManagement\Infrastructure\Records\CategoryRecord;
1215
use Illuminate\Database\Eloquent\Builder;
16+
use Illuminate\Database\Eloquent\SoftDeletes;
1317
use Illuminate\Support\Carbon;
1418

1519
/**
@@ -22,10 +26,17 @@
2226
*/
2327
class ArticleRecord extends BaseModel
2428
{
29+
use SoftDeletes;
30+
2531
protected $table = 'articles';
2632

2733
protected $fillable = ['title', 'body', 'status', 'created_at'];
2834

35+
public function categories()
36+
{
37+
return $this->belongsToMany(CategoryRecord::class, 'pivot_article_category', 'article_id', 'category_id');
38+
}
39+
2940
public const STATUS_MAPPING = [
3041
0 => 'draft',
3142
1 => 'published',
@@ -53,11 +64,16 @@ public static function mapStatusToRecord(ArticleStatus $status): int
5364

5465
public function toDomain(array $events = []): Article
5566
{
67+
$categories = new ArticleCategoryCollection(
68+
$this->categories()->get()->map(fn ($category) => new ArticleCategory($category->id, $category->label))->toArray()
69+
);
70+
5671
return Article::reconstitute(
5772
ArticleId::fromInt($this->id),
5873
$this->title,
5974
$this->body,
6075
self::mapStatusToDomain($this->status),
76+
$categories,
6177
$this->created_at->toImmutable(),
6278
$this->updated_at?->toImmutable(),
6379
events: $events
@@ -78,6 +94,12 @@ public function scopeSearch(Builder $query, array $criteria = [])
7894
$query->where('status', $criteria['status']);
7995
});
8096

97+
$query->when(isset($criteria['category_id']), function ($query) use ($criteria) {
98+
$query->whereHas('categories', function ($query) use ($criteria) {
99+
$query->where('category_id', $criteria['category_id']);
100+
});
101+
});
102+
81103
$query->when(isset($criteria['created_at_range']), function ($query) use ($criteria) {
82104
[$start, $end] = $criteria['created_at_range'];
83105
$query->whereBetween('created_at', [$start, $end]);

contexts/ArticlePublishing/Presentation/Requests/CreateArticleRequest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public function rules(): array
1414
'title' => ['required', 'string', 'max:255'],
1515
'body' => ['string', 'max:20000'],
1616
'status' => ['required', 'string', 'in:draft,published'],
17+
'category_ids' => ['array'],
18+
'category_ids.*' => ['integer', 'gt:0'],
1719
'created_at' => ['date', 'date_format: Y-m-d H:i:s'],
1820
];
1921
}

contexts/ArticlePublishing/Presentation/Requests/GetArticleListRequest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function rules(): array
1414
'id' => ['integer', 'gt:0'],
1515
'title' => ['string', 'max:255'],
1616
'status' => ['string', 'in:draft,published'],
17+
'category_id' => ['integer', 'gt:0'],
1718
'created_at_range' => ['array', 'size:2'],
1819
'created_at_range.*' => ['date'],
1920
'page' => ['integer', 'gt:0'],

contexts/ArticlePublishing/Presentation/Requests/UpdateArticleRequest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public function rules(): array
1515
'title' => ['string', 'max:255'],
1616
'body' => ['string', 'max:20000'],
1717
'status' => ['string', 'in:draft,published'],
18+
'category_ids' => ['array'],
19+
'category_ids.*' => ['integer', 'gt:0'],
1820
'created_at' => ['date', 'date_format: Y-m-d H:i:s'],
1921
];
2022
}

contexts/ArticlePublishing/Presentation/Resources/ArticleResource.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ public function toArray(Request $request): array
2222
'title' => (string) $article->getTitle(),
2323
'body' => (string) $article->getbody(),
2424
'status' => (string) $article->getStatus()->getValue(),
25-
25+
'categories' => $article->getCategories()->map(fn ($category) => [
26+
'id' => $category->getId(),
27+
'label' => $category->getLabel(),
28+
])->toArray(),
2629
'created_at' => $article->getCreatedAt()->format('Y-m-d H:i:s'),
2730
'updated_at' => $article->getUpdatedAt()?->format('Y-m-d H:i:s'),
2831
];

0 commit comments

Comments
 (0)