Skip to content

Commit 9145188

Browse files
authored
Merge pull request #22 from laravelcm/feature-discussion
Feature discussion
2 parents 1183d79 + df138c9 commit 9145188

File tree

113 files changed

+15056
-457
lines changed

Some content is hidden

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

113 files changed

+15056
-457
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ SLACK_TEAM_URL="https://laravelcm.slack.com"
6767

6868
MARKDOWNX_GIPHY_API_KEY=
6969
TORCHLIGHT_TOKEN=
70+
MIX_TORCHLIGHT_TOKEN="${TORCHLIGHT_TOKEN}"
7071
UNSPLASH_ACCESS_KEY=
7172
TELEGRAM_BOT_TOKEN=
7273
MEDIA_DISK=media

app/Events/CommentWasAdded.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace App\Events;
4+
5+
use App\Models\Discussion;
6+
use App\Models\Reply;
7+
use Illuminate\Queue\SerializesModels;
8+
9+
class CommentWasAdded
10+
{
11+
use SerializesModels;
12+
13+
public function __construct(public Reply $reply, public Discussion $discussion)
14+
{
15+
}
16+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api;
4+
5+
use App\Events\CommentWasAdded;
6+
use App\Http\Controllers\Controller;
7+
use App\Http\Requests\Api\CreateReplyRequest;
8+
use App\Http\Requests\Api\UpdateReplyRequest;
9+
use App\Http\Resources\ReplyResource;
10+
use App\Models\Discussion;
11+
use App\Models\Reaction;
12+
use App\Models\Reply;
13+
use App\Models\User;
14+
use Illuminate\Http\JsonResponse;
15+
use Illuminate\Http\Request;
16+
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
17+
18+
class ReplyController extends Controller
19+
{
20+
public function all(int $target): AnonymousResourceCollection
21+
{
22+
/** @var Discussion $discussion */
23+
$discussion = Discussion::findOrFail($target);
24+
$replies = collect();
25+
26+
foreach ($discussion->replies as $reply) {
27+
if ($reply->allChildReplies->isNotEmpty()) {
28+
foreach ($reply->allChildReplies as $childReply) {
29+
$replies->add($childReply);
30+
}
31+
}
32+
33+
$replies->add($reply);
34+
}
35+
36+
return ReplyResource::collection($replies);
37+
}
38+
39+
public function store(CreateReplyRequest $request): ReplyResource
40+
{
41+
// dd($request->all());
42+
// if ($request->parent) {}
43+
$reply = new Reply(['body' => $request->body]);
44+
$author = User::find($request->user_id);
45+
46+
$target = Discussion::find($request->target);
47+
$reply->authoredBy($author);
48+
$reply->to($target);
49+
$reply->save();
50+
51+
// On envoie un event pour une nouvelle réponse à tous les abonnés de la discussion
52+
event(new CommentWasAdded($reply, $target));
53+
54+
return new ReplyResource($reply);
55+
}
56+
57+
public function update(UpdateReplyRequest $request, int $id): ReplyResource
58+
{
59+
$reply = Reply::find($id);
60+
$reply->update(['body' => $request->body]);
61+
62+
return new ReplyResource($reply);
63+
}
64+
65+
public function like(Request $request, int $id): ReplyResource
66+
{
67+
$reply = Reply::findOrFail($id);
68+
$react = Reaction::where('name', 'love')->first();
69+
/** @var User $user */
70+
$user = User::findOrFail($request->userId);
71+
72+
$user->reactTo($reply, $react);
73+
74+
return new ReplyResource($reply);
75+
}
76+
77+
public function delete(int $id): JsonResponse
78+
{
79+
/** @var Reply $reply */
80+
$reply = Reply::findOrFail($id);
81+
$reply->delete();
82+
83+
return response()->json(['message' => 'Commentaire supprimé avec succès']);
84+
}
85+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Models\Discussion;
6+
use App\Policies\DiscussionPolicy;
7+
8+
class DiscussionController extends Controller
9+
{
10+
public function __construct()
11+
{
12+
$this->middleware(['auth', 'verified'], ['except' => ['index', 'show']]);
13+
}
14+
15+
public function index()
16+
{
17+
return view('discussions.index');
18+
}
19+
20+
public function show(Discussion $discussion)
21+
{
22+
views($discussion)->record();
23+
24+
seo()
25+
->title($discussion->title)
26+
->description($discussion->excerpt(100))
27+
->image(asset('images/socialcard.png'))
28+
->twitterTitle($discussion->title)
29+
->twitterDescription($discussion->excerpt(100))
30+
->twitterImage(asset('images/socialcard.png'))
31+
->twitterSite('laravelcm')
32+
->withUrl();
33+
34+
return view('discussions.show', ['discussion' => $discussion->load('tags')]);
35+
}
36+
37+
public function create()
38+
{
39+
return view('discussions.new');
40+
}
41+
42+
public function edit(Discussion $discussion)
43+
{
44+
$this->authorize(DiscussionPolicy::UPDATE, $discussion);
45+
46+
return view('discussions.edit', compact('discussion'));
47+
}
48+
}

app/Http/Controllers/Forum/ReplyController.php

Lines changed: 0 additions & 12 deletions
This file was deleted.

app/Http/Controllers/Forum/ThreadController.php renamed to app/Http/Controllers/ThreadController.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<?php
22

3-
namespace App\Http\Controllers\Forum;
3+
namespace App\Http\Controllers;
44

5-
use App\Http\Controllers\Controller;
65
use App\Models\Channel;
76
use App\Models\Thread;
87
use Illuminate\Http\Request;

app/Http/Livewire/Articles/Browse.php

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,18 @@
55
use App\Models\Article;
66
use App\Models\Tag;
77
use App\Traits\WithInfiniteScroll;
8+
use App\Traits\WithTags;
89
use Livewire\Component;
910

1011
class Browse extends Component
1112
{
12-
use WithInfiniteScroll;
13-
14-
public string $sortBy = 'recent';
15-
public ?string $tag = null;
13+
use WithInfiniteScroll, WithTags;
1614

1715
protected $queryString = [
1816
'tag' => ['except' => ''],
1917
'sortBy' => ['except' => 'recent'],
2018
];
2119

22-
public function toggleTag($tag): void
23-
{
24-
$this->tag = $this->tag !== $tag && $this->tagExists($tag) ? $tag : null;
25-
}
26-
27-
public function sortBy($sort): void
28-
{
29-
$this->sortBy = $this->validSort($sort) ? $sort : 'recent';
30-
}
31-
32-
public function tagExists($tag): bool
33-
{
34-
return Tag::where('slug', $tag)->exists();
35-
}
36-
3720
public function validSort($sort): bool
3821
{
3922
return in_array($sort, [

app/Http/Livewire/Articles/Create.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Models\Tag;
77
use App\Traits\WithArticleAttributes;
88
use App\Traits\WithTagsAssociation;
9+
use Illuminate\Support\Facades\Auth;
910
use Livewire\Component;
1011
use Livewire\WithFileUploads;
1112

@@ -17,9 +18,11 @@ class Create extends Component
1718

1819
public function mount()
1920
{
20-
$this->submitted = ! auth()->user()->hasAnyRole(['admin', 'moderator']);
21-
$this->submitted_at = auth()->user()->hasAnyRole(['admin', 'moderator']) ? now() : null;
22-
$this->approved_at = auth()->user()->hasAnyRole(['admin', 'moderator']) ? now() : null;
21+
$user = Auth::user();
22+
23+
$this->submitted = ! $user->hasAnyRole(['admin', 'moderator']);
24+
$this->submitted_at = $user->hasAnyRole(['admin', 'moderator']) ? now() : null;
25+
$this->approved_at = $user->hasAnyRole(['admin', 'moderator']) ? now() : null;
2326
}
2427

2528
public function onMarkdownUpdate(string $content)
@@ -43,6 +46,8 @@ public function store()
4346
{
4447
$this->validate();
4548

49+
$user = Auth::user();
50+
4651
$article = Article::create([
4752
'title' => $this->title,
4853
'slug' => $this->slug,
@@ -52,10 +57,12 @@ public function store()
5257
'approved_at' => $this->approved_at,
5358
'show_toc' => $this->show_toc,
5459
'canonical_url' => $this->canonical_url,
55-
'user_id' => auth()->id(),
60+
'user_id' => $user->id,
5661
]);
5762

58-
$article->syncTags($this->associateTags);
63+
if (collect($this->associateTags)->isNotEmpty()) {
64+
$article->syncTags($this->associateTags);
65+
}
5966

6067
if ($this->file) {
6168
$article->addMedia($this->file->getRealPath())->toMediaCollection('media');
@@ -66,7 +73,7 @@ public function store()
6673
session()->flash('success', 'Merci d\'avoir soumis votre article. Vous aurez des nouvelles que lorsque nous accepterons votre article.');
6774
}
6875

69-
auth()->user()->hasRole('user') ?
76+
$user->hasRole('user') ?
7077
$this->redirect('/articles/me') :
7178
$this->redirect('/admin/articles');
7279
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace App\Http\Livewire\Discussions;
4+
5+
use App\Models\Discussion;
6+
use App\Models\Tag;
7+
use App\Traits\WithInfiniteScroll;
8+
use App\Traits\WithTags;
9+
use Livewire\Component;
10+
11+
class Browse extends Component
12+
{
13+
use WithInfiniteScroll, WithTags;
14+
15+
protected $queryString = [
16+
'tag' => ['except' => ''],
17+
'sortBy' => ['except' => 'recent'],
18+
];
19+
20+
public function validSort($sort): bool
21+
{
22+
return in_array($sort, [
23+
'recent',
24+
'popular',
25+
'active',
26+
]);
27+
}
28+
29+
public function render()
30+
{
31+
$discussions = Discussion::with('tags')
32+
->withCount('replies')
33+
->notPinned();
34+
35+
$tags = Tag::whereJsonContains('concerns', ['discussion'])->orderBy('name')->get();
36+
37+
$selectedTag = Tag::where('name', $this->tag)->first();
38+
39+
if ($this->tag) {
40+
$discussions->forTag($this->tag);
41+
}
42+
43+
$discussions->{$this->sortBy}();
44+
45+
return view('livewire.discussions.browse', [
46+
'discussions' => $discussions->paginate($this->perPage),
47+
'tags' => $tags,
48+
'selectedTag' => $selectedTag,
49+
'selectedSortBy' => $this->sortBy,
50+
]);
51+
}
52+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace App\Http\Livewire\Discussions;
4+
5+
use App\Models\Discussion;
6+
use App\Models\Tag;
7+
use App\Notifications\PostDiscussionToTelegram;
8+
use App\Traits\WithTagsAssociation;
9+
use Illuminate\Support\Facades\Auth;
10+
use Livewire\Component;
11+
12+
class Create extends Component
13+
{
14+
use WithTagsAssociation;
15+
16+
public string $title = '';
17+
public string $body = '';
18+
19+
protected $listeners = ['markdown-x:update' => 'onMarkdownUpdate'];
20+
protected $rules = [
21+
'title' => ['required', 'max:150'],
22+
'body' => ['required'],
23+
'tags_selected' => 'nullable|array',
24+
];
25+
26+
public function onMarkdownUpdate(string $content)
27+
{
28+
$this->body = $content;
29+
}
30+
31+
public function store()
32+
{
33+
$this->validate();
34+
35+
$discussion = Discussion::create([
36+
'title' => $this->title,
37+
'slug' => $this->title,
38+
'body' => $this->body,
39+
'user_id' => Auth::id(),
40+
]);
41+
42+
if (collect($this->associateTags)->isNotEmpty()) {
43+
$discussion->syncTags($this->associateTags);
44+
}
45+
46+
Auth::user()->notify(new PostDiscussionToTelegram($discussion));
47+
48+
$this->redirectRoute('discussions.show', $discussion);
49+
}
50+
51+
public function render()
52+
{
53+
return view('livewire.discussions.create', [
54+
'tags' => Tag::whereJsonContains('concerns', ['discussion'])->get(),
55+
]);
56+
}
57+
}

0 commit comments

Comments
 (0)