Skip to content

Commit ef13aa5

Browse files
committed
Add migration, article twigs
1 parent 26aef5d commit ef13aa5

277 files changed

Lines changed: 30287 additions & 269 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/App/src/Fixture/articles_by_category.json

Lines changed: 3835 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Migration;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20260615112411 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('CREATE TABLE articles (title VARCHAR(500) NOT NULL, slug VARCHAR(500) NOT NULL, post_date DATETIME NOT NULL, status ENUM(\'draft\', \'published\', \'private\') DEFAULT \'draft\' NOT NULL, id CHAR(36) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, category_id CHAR(36) NOT NULL, author_id CHAR(36) NOT NULL, UNIQUE INDEX UNIQ_BFDD3168989D9B62 (slug), INDEX IDX_BFDD316812469DE2 (category_id), INDEX IDX_BFDD3168F675F31B (author_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
24+
$this->addSql('CREATE TABLE authors (name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, bio LONGTEXT DEFAULT NULL, email VARCHAR(255) NOT NULL, id CHAR(36) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8E0C2A51989D9B62 (slug), UNIQUE INDEX UNIQ_8E0C2A51E7927C74 (email), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
25+
$this->addSql('CREATE TABLE categories (name VARCHAR(500) NOT NULL, slug VARCHAR(500) NOT NULL, id CHAR(36) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
26+
$this->addSql('ALTER TABLE articles ADD CONSTRAINT FK_BFDD316812469DE2 FOREIGN KEY (category_id) REFERENCES categories (id)');
27+
$this->addSql('ALTER TABLE articles ADD CONSTRAINT FK_BFDD3168F675F31B FOREIGN KEY (author_id) REFERENCES authors (id)');
28+
}
29+
30+
public function down(Schema $schema): void
31+
{
32+
// this down() migration is auto-generated, please modify it to your needs
33+
$this->addSql('ALTER TABLE articles DROP FOREIGN KEY FK_BFDD316812469DE2');
34+
$this->addSql('ALTER TABLE articles DROP FOREIGN KEY FK_BFDD3168F675F31B');
35+
$this->addSql('DROP TABLE articles');
36+
$this->addSql('DROP TABLE authors');
37+
$this->addSql('DROP TABLE categories');
38+
}
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Migration;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20260617101307 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('ALTER TABLE articles ADD excerpt LONGTEXT NOT NULL');
24+
$this->addSql('ALTER TABLE categories ADD isVisible TINYINT NOT NULL');
25+
}
26+
27+
public function down(Schema $schema): void
28+
{
29+
// this down() migration is auto-generated, please modify it to your needs
30+
$this->addSql('ALTER TABLE articles DROP excerpt');
31+
$this->addSql('ALTER TABLE categories DROP isVisible');
32+
}
33+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<aside class="col-lg-3 col-md-4">
2+
<div class="sticky-top" style="top: 90px; z-index: 10;">
3+
4+
<div class="card border-0 shadow-sm mb-4 rounded-3">
5+
<div class="card-body p-3">
6+
<h6 class="fw-bold text-uppercase text-muted small px-1 mb-3">Search</h6>
7+
<div class="input-group">
8+
<span class="input-group-text bg-light border-end-0 text-muted">
9+
<i class="bi bi-search"></i>
10+
</span>
11+
<input type="text" class="form-control border-start-0 bg-light shadow-none" placeholder="Search articles…">
12+
</div>
13+
</div>
14+
</div>
15+
16+
<div class="card border-0 shadow-sm rounded-3">
17+
<div class="card-body p-3">
18+
<h6 class="fw-bold text-uppercase text-muted small px-1 mb-2">Categories</h6>
19+
20+
<div class="list-group list-group-flush mb-3 pe-2" style="max-height: 250px; overflow-y: auto; overflow-x: hidden;">
21+
{% for category in categories %}
22+
<a href="{{ url('page::category-resource', {slug: category.slug}) }}"
23+
class="list-group-item list-group-item-action d-flex align-items-center justify-content-between py-2 px-1 border-0 rounded text-secondary small">
24+
<span>{{ category.name }}</span>
25+
<i class="bi bi-chevron-right text-muted" style="font-size: 10px;"></i>
26+
</a>
27+
{% endfor %}
28+
</div>
29+
<div class="pt-2 border-top text-center">
30+
<a href="{{ url('page::categories') }}" class="text-primary small fw-semibold text-decoration-none d-inline-flex align-items-center gap-1">
31+
See all categories <i class="bi bi-arrow-right fs-7"></i>
32+
</a>
33+
</div>
34+
</div>
35+
</div>
36+
37+
</div>
38+
</aside>

src/Blog/src/Entity/Article.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class Article extends AbstractEntity
2424
#[ORM\Column(name: 'post_date', type: 'datetime_immutable')]
2525
private DateTimeImmutable $postDate;
2626

27+
#[ORM\Column(name: 'excerpt', type: 'text')]
28+
private string $excerpt;
29+
2730
#[ORM\Column(
2831
name: 'status',
2932
type: 'article_status_enum',
@@ -100,12 +103,23 @@ public function setAuthor(Author $author): void
100103
$this->author = $author;
101104
}
102105

106+
public function getExcerpt(): string
107+
{
108+
return $this->excerpt;
109+
}
110+
111+
public function setExcerpt(string $excerpt): void
112+
{
113+
$this->excerpt = $excerpt;
114+
}
115+
103116
/**
104117
* @return array{
105118
* id: non-empty-string,
106119
* title: string,
107120
* slug: string,
108121
* status: string,
122+
* excerpt: string,
109123
* postDate: string,
110124
* category: array{id: non-empty-string, name: string, slug: string},
111125
* author: array{id: non-empty-string, name: string, slug: string, bio: string|null}
@@ -118,6 +132,7 @@ public function getArrayCopy(): array
118132
'title' => $this->title,
119133
'slug' => $this->slug,
120134
'status' => $this->status->value,
135+
'excerpt' => $this->excerpt,
121136
'postDate' => $this->postDate->format('Y-m-d H:i:s'),
122137
'category' => $this->category->getArrayCopy(),
123138
'author' => $this->author->getArrayCopy(),

src/Blog/src/Entity/Category.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class Category extends AbstractEntity
1919
#[ORM\Column(name: 'slug', type: 'string', length: 500)]
2020
private string $slug;
2121

22+
#[ORM\Column(name: 'isVisible', type: 'boolean')]
23+
private bool $isVisible = true;
24+
2225
public function getName(): string
2326
{
2427
return $this->name;
@@ -39,10 +42,21 @@ public function setSlug(string $slug): void
3942
$this->slug = $slug;
4043
}
4144

45+
public function isVisible(): bool
46+
{
47+
return $this->isVisible;
48+
}
49+
50+
public function setVisibility(bool $isVisible): void
51+
{
52+
$this->isVisible = $isVisible;
53+
}
54+
4255
/**
4356
* @return array{
4457
* id: non-empty-string,
4558
* name: string,
59+
* isVisible: bool,
4660
* slug: string
4761
* }
4862
*/
@@ -51,6 +65,7 @@ public function getArrayCopy(): array
5165
return [
5266
'id' => $this->id->toString(),
5367
'name' => $this->name,
68+
'isVisible' => $this->isVisible,
5469
'slug' => $this->slug,
5570
];
5671
}

src/Blog/src/Factory/Articles/ArticleResourceHandlerFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Light\Blog\Handler\GetArticleResourceHandler;
88
use Light\Blog\Repository\ArticleRepository;
9+
use Light\Blog\Repository\CategoryRepository;
910
use Mezzio\Template\TemplateRendererInterface;
1011
use Psr\Container\ContainerExceptionInterface;
1112
use Psr\Container\ContainerInterface;
@@ -22,11 +23,12 @@ class ArticleResourceHandlerFactory
2223
public function __invoke(ContainerInterface $container, string $requestedName): GetArticleResourceHandler
2324
{
2425
$repository = $container->get(ArticleRepository::class);
26+
$categoryRepository = $container->get(CategoryRepository::class);
2527
$template = $container->get(TemplateRendererInterface::class);
2628

2729
assert($repository instanceof ArticleRepository);
2830
assert($template instanceof TemplateRendererInterface);
2931

30-
return new GetArticleResourceHandler($template, $repository);
32+
return new GetArticleResourceHandler($template, $repository, $categoryRepository);
3133
}
3234
}

src/Blog/src/Handler/GetArticleCollectionHandler.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public function __construct(
2727
public function handle(ServerRequestInterface $request): ResponseInterface
2828
{
2929
$queryParams = $request->getQueryParams();
30-
3130
$params = Paginator::getParams($queryParams, 'articles.id');
3231
$data = Paginator::wrapper(
3332
$this->articleRepository->getArticles($params),

src/Blog/src/Handler/GetArticleResourceHandler.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Laminas\Diactoros\Response\HtmlResponse;
88
use Light\Blog\Repository\ArticleRepository;
9+
use Light\Blog\Repository\CategoryRepository;
910
use Mezzio\Template\TemplateRendererInterface;
1011
use Psr\Http\Message\ResponseInterface;
1112
use Psr\Http\Message\ServerRequestInterface;
@@ -16,17 +17,19 @@ class GetArticleResourceHandler implements RequestHandlerInterface
1617
public function __construct(
1718
private readonly TemplateRendererInterface $template,
1819
private readonly ArticleRepository $articleRepository,
20+
private readonly CategoryRepository $categoryRepository,
1921
) {
2022
}
2123

2224
public function handle(ServerRequestInterface $request): ResponseInterface
2325
{
2426
$slug = $request->getAttribute('slug');
2527
$article = $this->articleRepository->getArticleResource($slug);
28+
$categories = $this->categoryRepository->getCategories();
2629
return new HtmlResponse(
27-
$this->template->render('page::blog-resource/' . $slug, [
30+
$this->template->render('page::blog-resource/' . $article->getCategory()->getSlug() . '/' . $slug, [
2831
'article' => $article,
32+
'categories' => $categories,
2933
])
3034
);
31-
}
32-
}
35+
}}

src/Blog/src/Repository/CategoryRepository.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CategoryRepository extends AbstractRepository
1717
public function getCategories(): array
1818
{
1919
$qb = $this->getQueryBuilder()
20-
->select('categories.name, categories.slug, categories.created')
20+
->select('categories.name, categories.slug, categories.isVisible')
2121
->from(Category::class, 'categories');
2222

2323
return $qb->getQuery()->getResult();

0 commit comments

Comments
 (0)