Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions app/Events/ReplyWasCreated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Events;

use App\Models\Reply;
use Illuminate\Queue\SerializesModels;

final class ReplyWasCreated
{
use SerializesModels;

/**
* @var \App\Models\Reply
*/
public $reply;

public function __construct(Reply $reply)
{
$this->reply = $reply;
}
}
2 changes: 1 addition & 1 deletion app/Exceptions/CannotCreateUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Exception;

class CannotCreateUser extends Exception
final class CannotCreateUser extends Exception
{
public static function duplicateEmailAddress(string $emailAddress): self
{
Expand Down
2 changes: 1 addition & 1 deletion app/Exceptions/CouldNotMarkReplyAsSolution.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Exception;
use App\Models\Reply;

class CouldNotMarkReplyAsSolution extends Exception
final class CouldNotMarkReplyAsSolution extends Exception
{
public static function replyAbleIsNotAThread(Reply $reply): self
{
Expand Down
29 changes: 29 additions & 0 deletions app/Helpers/HasUuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Helpers;

use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;

trait HasUuid
{
public function uuid(): UuidInterface
{
return Uuid::fromString($this->uuid);
}

public function getKeyName()
{
return 'uuid';
}

public function getIncrementing()
{
return false;
}

public static function findByUuidOrFail(UuidInterface $uuid): self
{
return static::where('uuid', $uuid->toString())->firstOrFail();
}
}
30 changes: 30 additions & 0 deletions app/Helpers/ProvidesSubscriptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Helpers;

use App\User;
use App\Models\Subscription;
use Illuminate\Database\Eloquent\Relations\MorphMany;

trait ProvidesSubscriptions
{
/**
* @return \App\Models\Subscription[]
*/
public function subscriptions()
{
return $this->subscriptionsRelation;
}

public function subscriptionsRelation(): MorphMany
{
return $this->morphMany(Subscription::class, 'subscriptionable');
}

public function hasSubscriber(User $user): bool
{
return $this->subscriptionsRelation()
->where('user_id', $user->id())
->exists();
}
}
15 changes: 10 additions & 5 deletions app/Http/Controllers/Auth/GithubController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function redirectToProvider()
public function handleProviderCallback()
{
try {
$socialiteUser = Socialite::driver('github')->user();
$socialiteUser = $this->getSocialiteUser();
} catch (InvalidStateException $exception) {
$this->error('errors.github_invalid_state');

Expand All @@ -38,16 +38,21 @@ public function handleProviderCallback()

try {
$user = User::findByGithubId($socialiteUser->getId());

return $this->userFound($user, $socialiteUser);
} catch (ModelNotFoundException $exception) {
return $this->userNotFound(new GithubUser($socialiteUser->user));
return $this->userNotFound(new GithubUser($socialiteUser->getRaw()));
}

return $this->userFound($user, $socialiteUser);
}

private function getSocialiteUser(): SocialiteUser
{
return Socialite::driver('github')->user();
}

private function userFound(User $user, SocialiteUser $socialiteUser): RedirectResponse
{
$this->dispatchNow(new UpdateProfile($user, ['github_username' => $socialiteUser->nickname]));
$this->dispatchNow(new UpdateProfile($user, ['github_username' => $socialiteUser->getNickname()]));

Auth::login($user);

Expand Down
25 changes: 25 additions & 0 deletions app/Http/Controllers/Forum/ThreadsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
use App\Jobs\CreateThread;
use App\Jobs\DeleteThread;
use App\Jobs\UpdateThread;
use Illuminate\Http\Request;
use App\Policies\ThreadPolicy;
use App\Queries\SearchThreads;
use App\Jobs\MarkThreadSolution;
use App\Jobs\UnmarkThreadSolution;
use App\Http\Controllers\Controller;
use App\Http\Requests\ThreadRequest;
use App\Jobs\SubscribeToSubscriptionAble;
use Illuminate\Auth\Middleware\Authenticate;
use App\Jobs\UnsubscribeFromSubscriptionAble;
use App\Http\Middleware\RedirectIfUnconfirmed;

class ThreadsController extends Controller
Expand Down Expand Up @@ -97,4 +100,26 @@ public function unmarkSolution(Thread $thread)

return redirect()->route('thread', $thread->slug());
}

public function subscribe(Request $request, Thread $thread)
{
$this->authorize(ThreadPolicy::SUBSCRIBE, $thread);

$this->dispatchNow(new SubscribeToSubscriptionAble($request->user(), $thread));

$this->success("You're now subscribed to this thread.");

return redirect()->route('thread', $thread->slug());
}

public function unsubscribe(Request $request, Thread $thread)
{
$this->authorize(ThreadPolicy::UNSUBSCRIBE, $thread);

$this->dispatchNow(new UnsubscribeFromSubscriptionAble($request->user(), $thread));

$this->success("You're now unsubscribed from this thread.");

return redirect()->route('thread', $thread->slug());
}
}
20 changes: 20 additions & 0 deletions app/Http/Controllers/SubscriptionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Http\Controllers;

use App\Models\Subscription;
use App\Jobs\UnsubscribeFromSubscriptionAble;

class SubscriptionController extends Controller
{
public function unsubscribe(Subscription $subscription)
{
$thread = $subscription->subscriptionAble();

$this->dispatch(new UnsubscribeFromSubscriptionAble($subscription->user(), $thread));

$this->success("You're now unsubscribed from this thread.");

return redirect()->route('thread', $thread->slug());
}
}
15 changes: 15 additions & 0 deletions app/Jobs/CreateReply.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

use App\User;
use App\Models\Reply;
use Ramsey\Uuid\Uuid;
use App\Models\ReplyAble;
use App\Models\Subscription;
use App\Events\ReplyWasCreated;
use App\Models\SubscriptionAble;
use App\Http\Requests\CreateReplyRequest;

final class CreateReply
Expand Down Expand Up @@ -49,6 +53,17 @@ public function handle(): Reply
$reply->to($this->replyAble);
$reply->save();

event(new ReplyWasCreated($reply));

if ($this->replyAble instanceof SubscriptionAble && ! $this->replyAble->hasSubscriber($this->author)) {
$subscription = new Subscription();
$subscription->uuid = Uuid::uuid4()->toString();
$subscription->userRelation()->associate($this->author);
$subscription->subscriptionAbleRelation()->associate($this->replyAble);

$this->replyAble->subscriptionsRelation()->save($subscription);
}

return $reply;
}
}
10 changes: 10 additions & 0 deletions app/Jobs/CreateThread.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace App\Jobs;

use App\User;
use Ramsey\Uuid\Uuid;
use App\Models\Thread;
use App\Models\Subscription;
use App\Http\Requests\ThreadRequest;

final class CreateThread
Expand Down Expand Up @@ -65,6 +67,14 @@ public function handle(): Thread
$thread->syncTags($this->tags);
$thread->save();

// Subscribe author to the thread.
$subscription = new Subscription();
$subscription->uuid = Uuid::uuid4()->toString();
$subscription->userRelation()->associate($this->author);
$subscription->subscriptionAbleRelation()->associate($thread);

$thread->subscriptionsRelation()->save($subscription);

return $thread;
}
}
4 changes: 2 additions & 2 deletions app/Jobs/SendEmailConfirmation.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace App\Jobs;

use App\User;
use App\Mail\EmailConfirmation;
use App\Mail\EmailConfirmationEmail;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;

Expand All @@ -24,6 +24,6 @@ public function __construct(User $user)
public function handle(Mailer $mailer)
{
$mailer->to($this->user->emailAddress())
->send(new EmailConfirmation($this->user));
->send(new EmailConfirmationEmail($this->user));
}
}
35 changes: 35 additions & 0 deletions app/Jobs/SubscribeToSubscriptionAble.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Jobs;

use App\User;
use Ramsey\Uuid\Uuid;
use App\Models\Subscription;
use App\Models\SubscriptionAble;

final class SubscribeToSubscriptionAble
{
/**
* @var \App\User
*/
private $user;

/**
* @var \App\Models\SubscriptionAble
*/
private $subscriptionAble;

public function __construct(User $user, SubscriptionAble $subscriptionAble)
{
$this->user = $user;
$this->subscriptionAble = $subscriptionAble;
}

public function handle()
{
$subscription = new Subscription();
$subscription->uuid = Uuid::uuid4()->toString();
$subscription->userRelation()->associate($this->user);
$this->subscriptionAble->subscriptionsRelation()->save($subscription);
}
}
32 changes: 32 additions & 0 deletions app/Jobs/UnsubscribeFromSubscriptionAble.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Jobs;

use App\User;
use App\Models\SubscriptionAble;

final class UnsubscribeFromSubscriptionAble
{
/**
* @var \App\User
*/
private $user;

/**
* @var \App\Models\SubscriptionAble
*/
private $subscriptionAble;

public function __construct(User $user, SubscriptionAble $subscriptionAble)
{
$this->user = $user;
$this->subscriptionAble = $subscriptionAble;
}

public function handle()
{
$this->subscriptionAble->subscriptionsRelation()
->where('user_id', $this->user->id())
->delete();
}
}
24 changes: 24 additions & 0 deletions app/Listeners/SendNewReplyNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Listeners;

use App\User;
use App\Events\ReplyWasCreated;
use App\Notifications\NewReplyNotification;

final class SendNewReplyNotification
{
public function handle(ReplyWasCreated $event): void
{
foreach ($event->reply->replyAble()->subscriptions() as $subscription) {
if ($this->replyAuthorDoesNotMatchSubscriber($event->reply->author(), $subscription)) {
$subscription->user()->notify(new NewReplyNotification($event->reply, $subscription));
}
}
}

private function replyAuthorDoesNotMatchSubscriber(User $author, $subscription): bool
{
return ! $author->matches($subscription->user());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

final class EmailConfirmation extends Mailable
final class EmailConfirmationEmail extends Mailable
{
use SerializesModels;

Expand Down
Loading