Skip to content

Commit 59ca987

Browse files
committed
chore: rebase main
1 parent 31cfce0 commit 59ca987

File tree

11 files changed

+241
-57
lines changed

11 files changed

+241
-57
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Actions\Article;
6+
7+
use App\Data\Article\CreateArticleData;
8+
use App\Gamify\Points\ArticleCreated;
9+
use App\Models\Article;
10+
use App\Notifications\PostArticleToTelegram;
11+
use Carbon\Carbon;
12+
use DateTimeInterface;
13+
use Illuminate\Support\Facades\Auth;
14+
15+
final class CreateArticleAction
16+
{
17+
public function execute(CreateArticleData $articleData): Article
18+
{
19+
if ($articleData->publishedAt && ! ($articleData->publishedAt instanceof DateTimeInterface)) {
20+
$articleData->publishedAt = new Carbon(
21+
time: $articleData->publishedAt,
22+
tz: config('app.timezone')
23+
);
24+
}
25+
26+
/** @var Article $article */
27+
$article = Article::query()->create([
28+
'title' => $articleData->title,
29+
'slug' => $articleData->title,
30+
'body' => $articleData->body,
31+
'published_at' => $articleData->publishedAt,
32+
'submitted_at' => $articleData->submittedAt,
33+
'approved_at' => $articleData->approvedAt,
34+
'show_toc' => $articleData->showToc,
35+
'canonical_url' => $articleData->canonicalUrl,
36+
'user_id' => Auth::id(),
37+
]);
38+
39+
if (collect($articleData->tags)->isNotEmpty()) {
40+
$article->syncTags(tags: $articleData->tags);
41+
}
42+
43+
if ($articleData->file) {
44+
$article->addMedia($articleData->file->getRealPath())->toMediaCollection('media');
45+
}
46+
47+
if ($article->isAwaitingApproval()) {
48+
// Envoi de la notification sur le channel Telegram pour la validation de l'article.
49+
Auth::user()?->notify(new PostArticleToTelegram($article));
50+
session()->flash('status', __('notifications.article.created'));
51+
}
52+
53+
if (Auth::user()?->hasAnyRole(['admin', 'moderator'])) {
54+
givePoint(new ArticleCreated($article));
55+
}
56+
57+
return $article;
58+
}
59+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Data\Article;
6+
7+
use Carbon\Carbon;
8+
use Illuminate\Http\UploadedFile;
9+
use Spatie\LaravelData\Data;
10+
11+
final class CreateArticleData extends Data
12+
{
13+
public function __construct(
14+
public string $title,
15+
public string $slug,
16+
public string $body,
17+
public string $showToc,
18+
public string $canonicalUrl,
19+
public ?Carbon $publishedAt,
20+
public ?Carbon $submittedAt,
21+
public ?Carbon $approvedAt,
22+
public ?UploadedFile $file,
23+
public array $tags = [],
24+
) {}
25+
}

app/Gamify/Points/ArticleCreated.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Gamify\Points;
6+
7+
use App\Models\Article;
8+
use App\Models\User;
9+
use QCod\Gamify\PointType;
10+
11+
final class ArticleCreated extends PointType
12+
{
13+
public int $points = 50;
14+
15+
public function __construct(Article $subject)
16+
{
17+
$this->subject = $subject;
18+
}
19+
20+
public function payee(): User
21+
{
22+
// @phpstan-ignore-next-line
23+
return $this->getSubject()->user;
24+
}
25+
}

app/Livewire/Articles/Create.php

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44

55
namespace App\Livewire\Articles;
66

7-
use App\Events\ArticleWasSubmittedForApproval;
8-
use App\Gamify\Points\PostCreated;
9-
use App\Models\Article;
7+
use App\Actions\Article\CreateArticleAction;
8+
use App\Data\Article\CreateArticleData;
109
use App\Models\Tag;
1110
use App\Models\User;
1211
use App\Traits\WithArticleAttributes;
1312
use App\Traits\WithTagsAssociation;
14-
use Carbon\Carbon;
15-
use DateTimeInterface;
1613
use Illuminate\Contracts\View\View;
1714
use Illuminate\Support\Facades\Auth;
1815
use Livewire\Component;
@@ -52,46 +49,16 @@ public function store(): void
5249
/** @var User $user */
5350
$user = Auth::user();
5451

55-
if ($this->published_at && ! ($this->published_at instanceof DateTimeInterface)) {
56-
$this->published_at = new Carbon(
57-
time: $this->published_at,
58-
tz: config('app.timezone')
59-
);
60-
}
61-
62-
/** @var Article $article */
63-
$article = Article::create([
52+
$article = app(CreateArticleAction::class)->execute(CreateArticleData::from([
6453
'title' => $this->title,
6554
'slug' => $this->slug,
6655
'body' => $this->body,
67-
'published_at' => $this->published_at,
68-
'submitted_at' => $this->submitted_at,
69-
'approved_at' => $this->approved_at,
70-
'show_toc' => $this->show_toc,
71-
'canonical_url' => $this->canonical_url,
72-
'user_id' => $user->id,
73-
]);
74-
75-
if (collect($this->associateTags)->isNotEmpty()) {
76-
$article->syncTags(tags: $this->associateTags);
77-
}
78-
79-
if ($this->file) {
80-
$article->addMedia($this->file->getRealPath())->toMediaCollection('media');
81-
}
82-
83-
if ($article->isAwaitingApproval()) {
84-
if (app()->environment('production')) {
85-
// Envoi de la notification sur le channel Telegram pour la validation de l'article.
86-
event(new ArticleWasSubmittedForApproval($article));
87-
}
88-
89-
session()->flash('status', __('Merci d\'avoir soumis votre article. Vous aurez des nouvelles que lorsque nous accepterons votre article.'));
90-
}
91-
92-
if ($user->hasAnyRole(['admin', 'moderator'])) {
93-
givePoint(new PostCreated($article));
94-
}
56+
'publishedAt' => $this->published_at,
57+
'submittedAt' => $this->submitted_at,
58+
'approvedAt' => $this->approved_at,
59+
'showToc' => $this->show_toc,
60+
'canonicalUrl' => $this->canonical_url,
61+
]));
9562

9663
$user->hasRole('user') ?
9764
$this->redirectRoute('dashboard') :

app/Traits/WithArticleAttributes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ trait WithArticleAttributes
3939
'tags_selected' => 'nullable|array',
4040
'canonical_url' => 'nullable|url',
4141
'file' => 'nullable|image|max:2048', // 1MB Max
42+
'show_toc' => 'boolean',
4243
];
4344

4445
public function removeImage(): void

lang/en/notifications.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return [
6+
7+
'article' => [
8+
'created' => 'Thank you for submitting your article. We will only contact you once we have accepted your article.',
9+
],
10+
11+
];

lang/fr/notifications.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return [
6+
7+
'article' => [
8+
'created' => 'Merci d\'avoir soumis votre article. Vous aurez des nouvelles que lorsque nous accepterons votre article.',
9+
],
10+
11+
];

phpunit.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3-
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4-
bootstrap="vendor/autoload.php"
5-
colors="true"
3+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
66
>
77
<testsuites>
88
<testsuite name="Unit">

resources/views/discussions/show.blade.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
<header class="space-y-5 border-b border-skin-base">
66
<div>
77
<h1
8-
class="font-heading text-2xl font-extrabold tracking-tight text-skin-inverted sm:text-3xl sm:leading-8"
8+
class="text-2xl font-extrabold tracking-tight font-heading text-skin-inverted sm:text-3xl sm:leading-8"
99
>
1010
{{ $discussion->title }}
1111
</h1>
1212
<div class="mt-2 space-x-4 sm:flex sm:items-center">
1313
<span
14-
class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-skin-card-gray text-skin-base"
14+
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-skin-card-gray text-skin-base"
1515
>
16-
<x-heroicon-s-tag class="h-5 w-5" />
16+
<x-heroicon-s-tag class="w-5 h-5" />
1717
</span>
1818
@if ($discussion->tags->isNotEmpty())
1919
<div class="flex items-center space-x-2">
@@ -29,13 +29,13 @@ class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-skin-card
2929
<div class="flex items-center sm:items-start">
3030
<div class="relative">
3131
<img
32-
class="h-10 w-10 rounded-full bg-skin-card-gray object-cover ring-8 ring-body"
32+
class="object-cover w-10 h-10 rounded-full bg-skin-card-gray ring-8 ring-body"
3333
src="{{ $discussion->user->profile_photo_url }}"
3434
alt="{{ $discussion->user->name }}"
3535
/>
3636
<span class="absolute -right-1 top-5 rounded-tl bg-skin-body px-0.5 py-px">
3737
<svg
38-
class="h-5 w-5 text-skin-muted"
38+
class="w-5 h-5 text-skin-muted"
3939
xmlns="http://www.w3.org/2000/svg"
4040
viewBox="0 0 24 24"
4141
fill="currentColor"
@@ -57,7 +57,7 @@ class="h-5 w-5 text-skin-muted"
5757
@endif
5858
</h4>
5959
</a>
60-
<div class="whitespace-nowrap text-sm font-normal text-skin-muted">
60+
<div class="text-sm font-normal whitespace-nowrap text-skin-muted">
6161
<time
6262
class="sr-only"
6363
datetime="{{ $discussion->created_at->format('Y-m-d') }}"
@@ -69,7 +69,7 @@ class="sr-only"
6969
</div>
7070
</div>
7171
</div>
72-
<div class="min-w-0 flex-1">
72+
<div class="flex-1 min-w-0">
7373
<div class="hidden sm:block">
7474
<a href="{{ route('profile', $discussion->user->username) }}">
7575
<h4 class="inline-flex items-center text-sm font-medium text-skin-inverted">
@@ -79,7 +79,7 @@ class="sr-only"
7979
@endif
8080
</h4>
8181
</a>
82-
<div class="whitespace-nowrap text-sm font-normal text-skin-muted">
82+
<div class="text-sm font-normal whitespace-nowrap text-skin-muted">
8383
<time
8484
class="sr-only"
8585
datetime="{{ $discussion->created_at->format('Y-m-d') }}"
@@ -91,10 +91,10 @@ class="sr-only"
9191
</div>
9292
</div>
9393
<x-markdown-content
94-
class="prose prose-green mx-auto mt-3 max-w-none text-sm text-skin-base md:prose-lg"
94+
class="mx-auto mt-3 text-sm prose prose-green max-w-none text-skin-base md:prose-lg"
9595
:content="$discussion->body"
9696
/>
97-
<div class="relative mt-3 inline-flex">
97+
<div class="relative inline-flex mt-3">
9898
<livewire:reactions
9999
wire:key="{{ $discussion->id }}"
100100
:model="$discussion"
@@ -103,7 +103,7 @@ class="prose prose-green mx-auto mt-3 max-w-none text-sm text-skin-base md:prose
103103
/>
104104
</div>
105105
@can(App\Policies\DiscussionPolicy::UPDATE, $discussion)
106-
<div class="mt-2 flex items-center space-x-2">
106+
<div class="flex items-center mt-2 space-x-2">
107107
<a
108108
href="{{ route('discussions.edit', $discussion) }}"
109109
class="font-sans text-sm leading-5 text-skin-base hover:underline focus:outline-none"
@@ -116,7 +116,7 @@ class="font-sans text-sm leading-5 text-skin-base hover:underline focus:outline-
116116
type="button"
117117
class="font-sans text-sm leading-5 text-red-500 hover:underline focus:outline-none"
118118
>
119-
{{ __('Supprimer' }) }}
119+
{{ __('Supprimer') }}
120120
</button>
121121
</div>
122122
@endcan
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+
use App\Actions\Article\CreateArticleAction;
6+
use App\Data\Article\CreateArticleData;
7+
use App\Models\Article;
8+
use App\Models\Tag;
9+
10+
beforeEach(function (): void {
11+
$this->user = $this->login();
12+
$this->tagOne = Tag::factory()->create(['name' => 'Tag 1', 'concerns' => ['post']]);
13+
$this->tagTwo = Tag::factory()->create(['name' => 'Tag 2', 'concerns' => ['tutorial', 'post']]);
14+
});
15+
16+
describe(CreateArticleAction::class, function (): void {
17+
it('return the created article', function (): void {
18+
$articleDataWithoutTag = CreateArticleData::from([
19+
'title' => 'Article title',
20+
'slug' => 'Article slug',
21+
'publishedAt' => now(),
22+
'submittedAt' => null,
23+
'approvedAt' => null,
24+
'showToc' => 'Article show_toc',
25+
'canonicalUrl' => 'Article canonical_url',
26+
'body' => 'Article body',
27+
'tags' => [$this->tagOne->id, $this->tagTwo->id],
28+
]);
29+
30+
$article = app(CreateArticleAction::class)->execute($articleDataWithoutTag);
31+
32+
expect($article)
33+
->toBeInstanceOf(Article::class)
34+
->and($article->tags)
35+
->toHaveCount(2)
36+
->and($article->user_id)
37+
->toBe($this->user->id);
38+
});
39+
})->group('Articles');

0 commit comments

Comments
 (0)