mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 06:27:59 +01:00
Merge pull request #116 from Poniverse/feature/email_notifications
This merge is being done to avoid letting the email notifications branch diverge too far from master. While email notifications are still unfinished, their implementation at this point co-exists peacefully with the existing on-site notifications.
This commit is contained in:
commit
c2dbfd792c
38 changed files with 1529 additions and 654 deletions
55
app/Http/Controllers/NotificationsController.php
Normal file
55
app/Http/Controllers/NotificationsController.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use DB;
|
||||
use Poniverse\Ponyfm\Models\Email;
|
||||
use Poniverse\Ponyfm\Models\EmailSubscription;
|
||||
|
||||
// TODO: #25 - finish these endpoints and secure them properly
|
||||
|
||||
class NotificationsController extends Controller {
|
||||
public function getEmailClick($emailKey) {
|
||||
App::abort(403, "This isn't implemented yet!");
|
||||
|
||||
$emailKey = decrypt($emailKey);
|
||||
/** @var Email $email */
|
||||
$email = Email::findOrFail($emailKey);
|
||||
|
||||
DB::transaction(function() use ($email) {
|
||||
$email->emailClicks()->create(['ip_address' => \Request::ip()]);
|
||||
$email->notification->is_read = true;
|
||||
$email->notification->save();
|
||||
});
|
||||
|
||||
return redirect($email->getActivity()->url);
|
||||
}
|
||||
|
||||
public function getEmailUnsubscribe($subscriptionKey) {
|
||||
App::abort(403, "This isn't implemented yet!");
|
||||
|
||||
$subscriptionId = decrypt($subscriptionKey);
|
||||
$subscription = EmailSubscription::findOrFail($subscriptionId);
|
||||
|
||||
return var_export($subscription);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Poniverse\Ponyfm\Jobs\Job;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\AbstractDriver;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\EmailDriver;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\NativeDriver;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
@ -64,6 +65,9 @@ class SendNotifications extends Job implements ShouldQueue
|
|||
//NativeDriver::class
|
||||
];
|
||||
|
||||
// NOTE: PonyfmDriver MUST execute before any other drivers; it creates
|
||||
// the Notification records that the other drivers depend on!
|
||||
|
||||
foreach ($drivers as $driver) {
|
||||
/** @var $driver AbstractDriver */
|
||||
$driver = new $driver;
|
||||
|
|
|
@ -31,7 +31,16 @@ abstract class AbstractDriver implements NotificationHandler
|
|||
|
||||
public function __construct()
|
||||
{
|
||||
$this->recipientFinder = new RecipientFinder(get_class($this));
|
||||
$notificationDriverClass = get_class($this);
|
||||
|
||||
switch ($notificationDriverClass) {
|
||||
case EmailDriver::class:
|
||||
case PonyfmDriver::class:
|
||||
$this->recipientFinder = new RecipientFinder(get_class($this));
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("Invalid notification driver!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,11 +20,14 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Library\Notifications\Drivers;
|
||||
|
||||
use ArrayAccess;
|
||||
use Carbon\Carbon;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||
use Poniverse\Ponyfm\Mail\BaseNotification;
|
||||
use Poniverse\Ponyfm\Models\Activity;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Email;
|
||||
use Poniverse\Ponyfm\Models\Notification;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
|
@ -35,21 +38,39 @@ class PonyfmDriver extends AbstractDriver
|
|||
/**
|
||||
* A helper method for bulk insertion of notification records.
|
||||
*
|
||||
* @param int $activityId
|
||||
* @param Activity $activity
|
||||
* @param User[] $recipients collection of {@link User} objects
|
||||
*/
|
||||
private function insertNotifications(int $activityId, $recipients)
|
||||
private function insertNotifications(Activity $activity, $recipients)
|
||||
{
|
||||
$notifications = [];
|
||||
foreach ($recipients as $recipient) {
|
||||
$notifications[] = [
|
||||
'activity_id' => $activityId,
|
||||
'activity_id' => $activity->id,
|
||||
'user_id' => $recipient->id
|
||||
];
|
||||
}
|
||||
Notification::insert($notifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends out an email about the given activity to the given set of users.
|
||||
*
|
||||
* @param Activity $activity
|
||||
* @param User[] $recipients collection of {@link User} objects
|
||||
*/
|
||||
private function sendEmails(Activity $activity, $recipients) {
|
||||
foreach ($recipients as $recipient) {
|
||||
/** @var Notification $notification */
|
||||
$notification = $activity->notifications->where('user_id', $recipient->id)->first();
|
||||
/** @var Email $email */
|
||||
$email = $notification->email()->create([]);
|
||||
|
||||
Log::debug("Attempting to send an email about notification {$notification->id} to {$recipient->email}.");
|
||||
Mail::to($recipient->email)->queue(BaseNotification::factory($activity, $email));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -63,7 +84,11 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $track->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args());
|
||||
if (NULL !== $recipientsQuery) {
|
||||
$this->insertNotifications($activity, $recipientsQuery->get());
|
||||
$this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_PUBLISHED_TRACK)->get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,9 +104,16 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $playlist->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args());
|
||||
if (NULL !== $recipientsQuery) {
|
||||
$this->insertNotifications($activity, $recipientsQuery->get());
|
||||
$this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_PUBLISHED_PLAYLIST)->get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function newFollower(User $userBeingFollowed, User $follower)
|
||||
{
|
||||
$activity = Activity::create([
|
||||
|
@ -92,7 +124,11 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $userBeingFollowed->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args());
|
||||
if (NULL !== $recipientsQuery) {
|
||||
$this->insertNotifications($activity, $recipientsQuery->get());
|
||||
$this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_NEW_FOLLOWER)->get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,7 +144,11 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $comment->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args());
|
||||
if (NULL !== $recipientsQuery) {
|
||||
$this->insertNotifications($activity, $recipientsQuery->get());
|
||||
$this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_NEW_COMMENT)->get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,6 +164,10 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $entityBeingFavourited->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args());
|
||||
if (NULL !== $recipientsQuery) {
|
||||
$this->insertNotifications($activity, $recipientsQuery->get());
|
||||
$this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_CONTENT_FAVOURITED)->get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,14 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Library\Notifications;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||
use Poniverse\Ponyfm\Contracts\NotificationHandler;
|
||||
use Poniverse\Ponyfm\Jobs\SendNotifications;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\NativeDriver;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
||||
use Poniverse\Ponyfm\Models\Activity;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Subscription;
|
||||
|
@ -37,7 +39,8 @@ use Poniverse\Ponyfm\Models\User;
|
|||
* @package Poniverse\Ponyfm\Library\Notifications
|
||||
*
|
||||
* This class returns a list of users who are to receive a particular notification.
|
||||
* It is instantiated on a per-driver basis.
|
||||
* It is instantiated on a per-driver basis. Its methods return Eloquent query
|
||||
* objects for the PonyfmDriver.
|
||||
*/
|
||||
class RecipientFinder implements NotificationHandler
|
||||
{
|
||||
|
@ -63,7 +66,8 @@ class RecipientFinder implements NotificationHandler
|
|||
{
|
||||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
return $track->user->followers;
|
||||
return $track->user->followers();
|
||||
|
||||
case NativeDriver::class:
|
||||
$followerIds = [];
|
||||
$subIds = [];
|
||||
|
@ -91,7 +95,8 @@ class RecipientFinder implements NotificationHandler
|
|||
{
|
||||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
return $playlist->user->followers;
|
||||
return $playlist->user->followers();
|
||||
|
||||
case NativeDriver::class:
|
||||
$followerIds = [];
|
||||
$subIds = [];
|
||||
|
@ -119,7 +124,8 @@ class RecipientFinder implements NotificationHandler
|
|||
{
|
||||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
return [$userBeingFollowed];
|
||||
return $this->queryForUser($userBeingFollowed);
|
||||
|
||||
case NativeDriver::class:
|
||||
return Subscription::where('user_id', '=', $userBeingFollowed->id)->get();
|
||||
default:
|
||||
|
@ -136,8 +142,8 @@ class RecipientFinder implements NotificationHandler
|
|||
case PonyfmDriver::class:
|
||||
return
|
||||
$comment->user->id === $comment->resource->user->id
|
||||
? []
|
||||
: [$comment->resource->user];
|
||||
? NULL
|
||||
: $this->queryForUser($comment->resource->user);
|
||||
case NativeDriver::class:
|
||||
return Subscription::where('user_id', '=', $comment->resource->user->id)->get();
|
||||
default:
|
||||
|
@ -154,12 +160,23 @@ class RecipientFinder implements NotificationHandler
|
|||
case PonyfmDriver::class:
|
||||
return
|
||||
$favouriter->id === $entityBeingFavourited->user->id
|
||||
? []
|
||||
: [$entityBeingFavourited->user];
|
||||
? NULL
|
||||
: $this->queryForUser($entityBeingFavourited->user);
|
||||
case NativeDriver::class:
|
||||
return Subscription::where('user_id', '=', $entityBeingFavourited->user->id)->get();
|
||||
default:
|
||||
return $this->fail();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns an Eloquent query instance that will return
|
||||
* a specific user when executed.
|
||||
*
|
||||
* @param User $user
|
||||
* @return \Eloquent|Builder
|
||||
*/
|
||||
private function queryForUser(User $user):Builder {
|
||||
return User::where('id', '=', $user->id);
|
||||
}
|
||||
}
|
||||
|
|
135
app/Mail/BaseNotification.php
Normal file
135
app/Mail/BaseNotification.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Poniverse\Ponyfm\Models\Activity;
|
||||
use Poniverse\Ponyfm\Models\Email;
|
||||
|
||||
abstract class BaseNotification extends Mailable {
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/** @var Email */
|
||||
protected $emailRecord;
|
||||
|
||||
/** @var \Poniverse\Ponyfm\Models\Notification */
|
||||
protected $notificationRecord;
|
||||
|
||||
/** @var \Poniverse\Ponyfm\Models\Activity */
|
||||
protected $activityRecord;
|
||||
|
||||
/** @var \Poniverse\Ponyfm\Models\User */
|
||||
protected $initiatingUser;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param Email $email
|
||||
*/
|
||||
public function __construct(Email $email) {
|
||||
$this->emailRecord = $email;
|
||||
$this->notificationRecord = $email->notification;
|
||||
$this->activityRecord = $email->notification->activity;
|
||||
$this->initiatingUser = $email->notification->activity->initiatingUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that instantiates the appropriate {@link BaseNotification}
|
||||
* subclass for the given activity type and {@link Email} record.
|
||||
*
|
||||
* @param Activity $activity
|
||||
* @param Email $email
|
||||
* @return BaseNotification
|
||||
*/
|
||||
static public function factory(Activity $activity, Email $email): BaseNotification {
|
||||
switch ($activity->activity_type) {
|
||||
case Activity::TYPE_NEWS:
|
||||
break;
|
||||
case Activity::TYPE_PUBLISHED_TRACK:
|
||||
return new NewTrack($email);
|
||||
case Activity::TYPE_PUBLISHED_ALBUM:
|
||||
break;
|
||||
case Activity::TYPE_PUBLISHED_PLAYLIST:
|
||||
return new NewPlaylist($email);
|
||||
case Activity::TYPE_NEW_FOLLOWER:
|
||||
return new NewFollower($email);
|
||||
case Activity::TYPE_NEW_COMMENT:
|
||||
return new NewComment($email);
|
||||
case Activity::TYPE_CONTENT_FAVOURITED:
|
||||
return new ContentFavourited($email);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new \InvalidArgumentException("Email notifications for activity type {$activity->activity_type} are not implemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
abstract public function build();
|
||||
|
||||
/**
|
||||
* Generates an unsubscribe URL unique to the user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateUnsubscribeUrl() {
|
||||
$subscriptionKey = encrypt($this->emailRecord->getSubscription()->id);
|
||||
return route('email:unsubscribe', ['subscriptionKey' => $subscriptionKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a trackable URL to the content item this email is about.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateNotificationUrl() {
|
||||
$emailKey = encrypt($this->emailRecord->id);
|
||||
return route('email:click', ['emailKey' => $emailKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to eliminate duplication between different types of
|
||||
* notifications. Use it inside the build() method on this class's children.
|
||||
*
|
||||
* Note that data common to all notification types is merged into the
|
||||
* template variable array.
|
||||
*
|
||||
* @param string $templateName
|
||||
* @param string $subject
|
||||
* @param array $extraVariables
|
||||
* @return $this
|
||||
*/
|
||||
protected function renderEmail(string $templateName, string $subject, array $extraVariables) {
|
||||
return $this
|
||||
->subject($subject)
|
||||
->view("emails.notifications.{$templateName}")
|
||||
->text("emails.notifications.{$templateName}_plaintext")
|
||||
->with(array_merge($extraVariables, [
|
||||
'notificationUrl' => $this->generateNotificationUrl(),
|
||||
'unsubscribeUrl' => $this->generateUnsubscribeUrl()
|
||||
]));
|
||||
}
|
||||
}
|
40
app/Mail/ContentFavourited.php
Normal file
40
app/Mail/ContentFavourited.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Mail;
|
||||
|
||||
class ContentFavourited extends BaseNotification
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$creatorName = $this->initiatingUser->display_name;
|
||||
|
||||
return $this->renderEmail(
|
||||
'content-favourited',
|
||||
$this->activityRecord->text, [
|
||||
'creatorName' => $creatorName,
|
||||
'resourceType' => $this->activityRecord->getResourceTypeString(),
|
||||
'resourceTitle' => $this->activityRecord->resource->title,
|
||||
]);
|
||||
}
|
||||
}
|
53
app/Mail/NewComment.php
Normal file
53
app/Mail/NewComment.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Mail;
|
||||
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
||||
class NewComment extends BaseNotification
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$creatorName = $this->initiatingUser->display_name;
|
||||
|
||||
// Profile comments get a different template and subject line from
|
||||
// other types of comments.
|
||||
if ($this->activityRecord->getResourceTypeString() === User::class) {
|
||||
return $this->renderEmail(
|
||||
'new-comment-profile',
|
||||
$this->activityRecord->text, [
|
||||
'creatorName' => $creatorName,
|
||||
]);
|
||||
} else {
|
||||
return $this->renderEmail(
|
||||
'new-comment-content',
|
||||
$this->activityRecord->text, [
|
||||
'creatorName' => $creatorName,
|
||||
'resourceType' => $this->activityRecord->getResourceTypeString(),
|
||||
'resourceTitle' => $this->activityRecord->resource->resource->title,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
39
app/Mail/NewFollower.php
Normal file
39
app/Mail/NewFollower.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Mail;
|
||||
|
||||
class NewFollower extends BaseNotification
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$creatorName = $this->initiatingUser->display_name;
|
||||
|
||||
return $this->renderEmail(
|
||||
'new-follower',
|
||||
"{$creatorName} is now following you on Pony.fm!",
|
||||
[
|
||||
'creatorName' => $creatorName,
|
||||
]);
|
||||
}
|
||||
}
|
41
app/Mail/NewPlaylist.php
Normal file
41
app/Mail/NewPlaylist.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Mail;
|
||||
|
||||
class NewPlaylist extends BaseNotification
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$creatorName = $this->initiatingUser->display_name;
|
||||
$playlistTitle = $this->activityRecord->resource->title;
|
||||
|
||||
return $this->renderEmail(
|
||||
'new-playlist',
|
||||
"{$creatorName} created a playlist, \"{$playlistTitle}\"!",
|
||||
[
|
||||
'creatorName' => $creatorName,
|
||||
'playlistTitle' => $playlistTitle,
|
||||
]);
|
||||
}
|
||||
}
|
41
app/Mail/NewTrack.php
Normal file
41
app/Mail/NewTrack.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Mail;
|
||||
|
||||
class NewTrack extends BaseNotification
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$creatorName = $this->initiatingUser->display_name;
|
||||
$trackTitle = $this->activityRecord->resource->title;
|
||||
|
||||
return $this->renderEmail(
|
||||
'new-track',
|
||||
"{$creatorName} published \"{$trackTitle}\"!",
|
||||
[
|
||||
'creatorName' => $creatorName,
|
||||
'trackTitle' => $trackTitle,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -66,6 +66,11 @@ class Activity extends Model
|
|||
'resource_id' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* These constants are stored in the "activity_types" table for the purpose
|
||||
* of referential data integrity. Any additions or changes to them MUST
|
||||
* include a database migration to apply the changes to that table as well.
|
||||
*/
|
||||
const TYPE_NEWS = 1;
|
||||
const TYPE_PUBLISHED_TRACK = 2;
|
||||
const TYPE_PUBLISHED_ALBUM = 3;
|
||||
|
@ -206,6 +211,39 @@ class Activity extends Model
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a string representing the type of resource this activity is about
|
||||
* for use in human-facing notification text.
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getResourceTypeString():string
|
||||
{
|
||||
switch($this->activity_type) {
|
||||
case static::TYPE_NEW_COMMENT:
|
||||
if ($this->isProfileComment()) {
|
||||
return $this->resource->getResourceType();
|
||||
} else {
|
||||
return $this->resource->resource->getResourceType();
|
||||
}
|
||||
case static::TYPE_CONTENT_FAVOURITED:
|
||||
return $this->resource->getResourceType();
|
||||
}
|
||||
throw new \Exception("Unknown activity type {$this->activity_type} - cannot determine resource type.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isProfileComment():bool {
|
||||
return static::TYPE_NEW_COMMENT === $this->activity_type &&
|
||||
User::class === $this->resource->getResourceClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* The string this method generates is used for email subject lines as well
|
||||
* as on-site notifications.
|
||||
*
|
||||
* @return string human-readable Markdown string describing this notification
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
@ -226,17 +264,16 @@ class Activity extends Model
|
|||
return "{$this->initiatingUser->display_name} is now following you!";
|
||||
|
||||
case static::TYPE_NEW_COMMENT:
|
||||
// Is this a profile comment?
|
||||
if ($this->resource_type === User::class) {
|
||||
if ($this->isProfileComment()) {
|
||||
return "{$this->initiatingUser->display_name} left a comment on your profile!";
|
||||
|
||||
// Must be a content comment.
|
||||
// If it's not a profile comment, it must be a content comment.
|
||||
} else {
|
||||
return "{$this->initiatingUser->display_name} left a comment on your {$this->resource->resource->getResourceType()}, {$this->resource->resource->title}!";
|
||||
return "{$this->initiatingUser->display_name} left a comment on your {$this->getResourceTypeString()}, \"{$this->resource->resource->title}\"!";
|
||||
}
|
||||
|
||||
case static::TYPE_CONTENT_FAVOURITED:
|
||||
return "{$this->initiatingUser->display_name} favourited your {$this->resource->getResourceType()}, {$this->resource->title}!";
|
||||
return "{$this->initiatingUser->display_name} favourited your {$this->getResourceTypeString()}, \"{$this->resource->title}\"!";
|
||||
|
||||
default:
|
||||
throw new \Exception('This activity\'s activity type is unknown!');
|
||||
|
|
|
@ -144,6 +144,15 @@ class Comment extends Model
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class name of the object that this is a comment on.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getResourceClass():string {
|
||||
return get_class($this->resource);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
DB::transaction(function () {
|
||||
|
|
69
app/Models/Email.php
Normal file
69
app/Models/Email.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Alsofronie\Uuid\UuidModelTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Poniverse\Ponyfm\Models\Notification;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Email
|
||||
*
|
||||
* @property string $id
|
||||
* @property integer $notification_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property-read \Poniverse\Ponyfm\Models\Notification $notification
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\EmailClick[] $emailClicks
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Email whereId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Email whereNotificationId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Email whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Email whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Email extends Model
|
||||
{
|
||||
use UuidModelTrait;
|
||||
|
||||
public function notification() {
|
||||
return $this->belongsTo(Notification::class, 'notification_id', 'id', 'notifications');
|
||||
}
|
||||
|
||||
public function emailClicks() {
|
||||
return $this->hasMany(EmailClick::class, 'email_id', 'id');
|
||||
}
|
||||
|
||||
public function getActivity():Activity {
|
||||
return $this->notification->activity;
|
||||
}
|
||||
|
||||
public function getUser():User {
|
||||
return $this->notification->recipient;
|
||||
}
|
||||
|
||||
public function getSubscription():EmailSubscription {
|
||||
return $this
|
||||
->getUser()
|
||||
->emailSubscriptions()
|
||||
->where('activity_type', $this->getActivity()->activity_type)
|
||||
->first();
|
||||
}
|
||||
}
|
51
app/Models/EmailClick.php
Normal file
51
app/Models/EmailClick.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Alsofronie\Uuid\UuidModelTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\EmailClick
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $email_id
|
||||
* @property string $ip_address
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property-read \Poniverse\Ponyfm\Models\Email $email
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailClick whereId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailClick whereEmailId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailClick whereIpAddress($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailClick whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailClick whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class EmailClick extends Model
|
||||
{
|
||||
use UuidModelTrait;
|
||||
|
||||
protected $fillable = ['ip_address'];
|
||||
|
||||
public function email() {
|
||||
return $this->belongsTo(Email::class, 'email_id', 'id', 'emails');
|
||||
}
|
||||
}
|
53
app/Models/EmailSubscription.php
Normal file
53
app/Models/EmailSubscription.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Alsofronie\Uuid\UuidModelTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\EmailSubscription
|
||||
*
|
||||
* @property string $id
|
||||
* @property integer $user_id
|
||||
* @property integer $activity_type
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property string $deleted_at
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailSubscription whereId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailSubscription whereUserId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailSubscription whereActivityType($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailSubscription whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailSubscription whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\EmailSubscription whereDeletedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class EmailSubscription extends Model
|
||||
{
|
||||
use UuidModelTrait, SoftDeletes;
|
||||
|
||||
public function user() {
|
||||
return $this->belongsTo(User::class, 'user_id', 'id', 'users');
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @property integer $user_id
|
||||
* @property boolean $is_read
|
||||
* @property-read \Poniverse\Ponyfm\Models\Activity $activity
|
||||
* @property-read \Poniverse\Ponyfm\Models\Email $email
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $recipient
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Notification forUser($user)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Notification whereId($value)
|
||||
|
@ -60,6 +61,11 @@ class Notification extends Model
|
|||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function email()
|
||||
{
|
||||
return $this->hasOne(Email::class, 'notification_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* This scope grabs eager-loaded notifications for the given user.
|
||||
*
|
||||
|
|
|
@ -73,6 +73,8 @@ use Venturecraft\Revisionable\RevisionableTrait;
|
|||
* @property-read mixed $user
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Activity[] $activities
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Activity[] $notificationActivities
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Email[] $emails
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\EmailSubscription[] $emailSubscriptions
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereDisplayName($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereUsername($value)
|
||||
|
@ -91,6 +93,7 @@ use Venturecraft\Revisionable\RevisionableTrait;
|
|||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereRememberToken($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereIsArchived($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereDisabledAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User withEmailSubscriptionFor($activityType)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, \Illuminate\Contracts\Auth\Access\Authorizable, Searchable, Commentable
|
||||
|
@ -126,6 +129,19 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
return !$query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns users with an email subscription to the given activity type.
|
||||
*
|
||||
* @param $query
|
||||
* @param int $activityType one of the TYPE_* constants in the Activity class
|
||||
* @return mixed
|
||||
*/
|
||||
public function scopeWithEmailSubscriptionFor($query, int $activityType) {
|
||||
return $query->whereHas('emailSubscriptions', function ($query) use ($activityType) {
|
||||
$query->where('activity_type', $activityType);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the given string, slugifies it, and increments a counter if needed
|
||||
* to generate a unique slug version of it.
|
||||
|
@ -239,6 +255,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
return $this->hasManyThrough(Activity::class, Notification::class, 'user_id', 'notification_id', 'id');
|
||||
}
|
||||
|
||||
public function emails()
|
||||
{
|
||||
return $this->hasManyThrough(Email::class, Notification::class, 'user_id', 'notification_id', 'id');
|
||||
}
|
||||
|
||||
public function emailSubscriptions()
|
||||
{
|
||||
return $this->hasMany(EmailSubscription::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function getIsArchivedAttribute()
|
||||
{
|
||||
return (bool) $this->attributes['is_archived'];
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
"predis/predis": "^1.0",
|
||||
"ksubileau/color-thief-php": "^1.3",
|
||||
"graham-campbell/exceptions": "^9.1",
|
||||
"minishlink/web-push": "^1.0"
|
||||
"minishlink/web-push": "^1.0",
|
||||
"laravel/legacy-encrypter": "^1.0",
|
||||
"alsofronie/eloquent-uuid": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fzaninotto/faker": "~1.4",
|
||||
|
|
1125
composer.lock
generated
1125
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -54,7 +54,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'from' => ['address' => null, 'name' => null],
|
||||
'from' => ['address' => 'hello@pony.fm', 'name' => 'Pony.fm'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateEmailNotificationTables extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
DB::transaction(function(){
|
||||
// This table is used to enforce referential data integrity
|
||||
// for the polymorphic "activity" table.
|
||||
Schema::create('activity_types', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('activity_type')->primary();
|
||||
$table->string('description');
|
||||
});
|
||||
|
||||
DB::table('activity_types')->insert([
|
||||
['activity_type' => 1, 'description' => 'news'],
|
||||
['activity_type' => 2, 'description' => 'followee published a track'],
|
||||
['activity_type' => 3, 'description' => 'followee published an album'],
|
||||
['activity_type' => 4, 'description' => 'followee published a playlist'],
|
||||
['activity_type' => 5, 'description' => 'new follower'],
|
||||
['activity_type' => 6, 'description' => 'new comment'],
|
||||
['activity_type' => 7, 'description' => 'new favourite'],
|
||||
]);
|
||||
|
||||
Schema::table('activities', function (Blueprint $table) {
|
||||
$table->foreign('activity_type')->references('activity_type')->on('activity_types');
|
||||
});
|
||||
|
||||
|
||||
Schema::create('email_subscriptions', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('activity_type');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users');
|
||||
$table->foreign('activity_type')->references('activity_type')->on('activity_types');
|
||||
});
|
||||
|
||||
Schema::create('emails', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
// Clicking the email link should mark the corresponding on-site notification as read.
|
||||
$table->unsignedBigInteger('notification_id')->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('notification_id')->references('id')->on('notifications');
|
||||
});
|
||||
|
||||
Schema::create('email_clicks', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->uuid('email_id')->index();
|
||||
$table->ipAddress('ip_address');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('email_id')->references('id')->on('emails');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
DB::transaction(function() {
|
||||
Schema::drop('email_clicks');
|
||||
Schema::drop('emails');
|
||||
Schema::drop('email_subscriptions');
|
||||
|
||||
Schema::table('activities', function (Blueprint $table) {
|
||||
$table->dropForeign('activities_activity_type_foreign');
|
||||
});
|
||||
|
||||
Schema::drop('activity_types');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,12 +17,12 @@ The `Notification` facade is used to send notifications as follows:
|
|||
```php
|
||||
use Notification;
|
||||
|
||||
// Something happens, like a newtrack getting published.
|
||||
// Something happens, like a new track getting published.
|
||||
$track = new Track();
|
||||
...
|
||||
|
||||
// The "something" is done happening! Time to send a notification.
|
||||
Notification::publishedTrack($track);
|
||||
Notification::publishedNewTrack($track);
|
||||
```
|
||||
|
||||
This facade has a method for every notification type, drawn from the
|
||||
|
@ -45,10 +45,18 @@ Adding new notification types
|
|||
- [`RecipientFinder`](../app/Library/Notifications/RecipientFinder.php)
|
||||
- [`PonyfmDriver`](../app/Library/Notifications/PonyfmDriver.php)
|
||||
|
||||
3. Call the new method on the `Notification` facade from wherever the
|
||||
3. Create a migration to add the new notification type to the `activity_types`
|
||||
table. Add a constant for it to the [`Activity`](../app/Models/Activity.php)
|
||||
class.
|
||||
|
||||
3. Ensure you create HTML and plaintext templates, as well as a subclass of
|
||||
[`BaseNotification`](../app/Mail/BaseNotification.php) for the email version
|
||||
of the notification.
|
||||
|
||||
4. Call the new method on the `Notification` facade from wherever the
|
||||
new notification gets triggered.
|
||||
|
||||
4. Implement any necessary logic for the new notification type in the
|
||||
5. Implement any necessary logic for the new notification type in the
|
||||
[`Activity`](../app/Models/Activity.php) model.
|
||||
|
||||
|
||||
|
@ -92,3 +100,25 @@ There's one exception to the use of `NotificationHandler` - the
|
|||
data we store about an activity in the database to a notification's API
|
||||
representation had to go somewhere, and using the `NotificationHandler`
|
||||
interface here would have made this logic a lot more obtuse.
|
||||
|
||||
### Data flow
|
||||
|
||||
1. Some action that triggers a notification calls the `NotificationManager`
|
||||
facade.
|
||||
|
||||
2. An asynchronous job is kicked off that figures out how to send the
|
||||
notification.
|
||||
|
||||
3. An `Activity` record is created for the action.
|
||||
|
||||
4. A `Notification` record is created for every user who is to receive a
|
||||
notification about that activity. These records act as Pony.fm's on-site
|
||||
notifications and cannot be disabled.
|
||||
|
||||
5. Depending on subscription preferences, push and email notifications will be
|
||||
sent out as well, each creating their own respective database records. These
|
||||
are linked to a `Notification` record for unified read/unread tracking.
|
||||
|
||||
6. A `Notification` record is marked read when it is viewed on-site or any other
|
||||
notification type associated with it (like an email or push notification) is
|
||||
clicked.
|
||||
|
|
13
resources/views/emails/notifications/_layout.blade.php
Normal file
13
resources/views/emails/notifications/_layout.blade.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
@yield('content')
|
||||
|
||||
<hr>
|
||||
|
||||
<p><a href="{{ $unsubscribeUrl }}" target="_blank">Unsubscribe from this kind of email</a></p>
|
||||
|
||||
<address style="font-size:85%;">
|
||||
Poniverse<br>
|
||||
248-1641 Lonsdale Avenue<br>
|
||||
North Vancouver<br>
|
||||
BC V7M 2J5<br>
|
||||
Canada
|
||||
</address>
|
|
@ -0,0 +1,10 @@
|
|||
@yield('content')
|
||||
|
||||
---
|
||||
Unsubscribe from this kind of email:
|
||||
{{ $unsubscribeUrl }}
|
||||
|
||||
Poniverse
|
||||
248-1641 Lonsdale Avenue
|
||||
North Vancouver
|
||||
BC V7M 2J5
|
|
@ -0,0 +1,9 @@
|
|||
@extends('emails.notifications._layout')
|
||||
|
||||
@section('content')
|
||||
<p>
|
||||
{{ $creatorName }} favourited your {{ $resourceType }},
|
||||
<em><a href="{{ $notificationUrl }}" target="_blank">{{ $resourceTitle }}</a></em>
|
||||
Yay!
|
||||
</p>
|
||||
@endsection
|
|
@ -0,0 +1,8 @@
|
|||
@extends('emails.notifications._layout_plaintext')
|
||||
|
||||
@section('content')
|
||||
{{ $creatorName }} favourited your {{ $resourceType }}, "{{ $resourceTitle }}". Yay!
|
||||
|
||||
Here's a link to the {{ $resourceType }}:
|
||||
{{ $notificationUrl }}
|
||||
@endsection
|
|
@ -0,0 +1,9 @@
|
|||
@extends('emails.notifications._layout')
|
||||
|
||||
@section('content')
|
||||
<p>
|
||||
{{ $creatorName }} left a comment on your {{ $resourceType }},
|
||||
<a href="{{ $notificationUrl }}" target="_blank"><em>{{ $resourceTitle }}</em></a>!
|
||||
<a href="{{ $notificationUrl }}" target="_blank">Visit it</a> to read the comment and reply.
|
||||
</p>
|
||||
@endsection
|
|
@ -0,0 +1,8 @@
|
|||
@extends('emails.notifications._layout_plaintext')
|
||||
|
||||
@section('content')
|
||||
{{ $creatorName }} left a comment on your {{ $resourceType }}, "{{ $resourceTitle }}"!
|
||||
|
||||
Visit the following link to read the comment and reply:
|
||||
{{ $notificationUrl }}
|
||||
@endsection
|
|
@ -0,0 +1,9 @@
|
|||
@extends('emails.notifications._layout')
|
||||
|
||||
@section('content')
|
||||
<p>
|
||||
{{ $creatorName }} left a comment on your Pony.fm profile!
|
||||
<a href="{{ $notificationUrl }}" target="_blank">Visit your profile</a> to
|
||||
read it and reply.
|
||||
</p>
|
||||
@endsection
|
|
@ -0,0 +1,8 @@
|
|||
@extends('emails.notifications._layout_plaintext')
|
||||
|
||||
@section('content')
|
||||
{{ $creatorName }} left a comment on your Pony.fm profile!
|
||||
|
||||
Visit your profile with the following link to read it and reply:
|
||||
{{ $notificationUrl }}
|
||||
@endsection
|
|
@ -0,0 +1,9 @@
|
|||
@extends('emails.notifications._layout')
|
||||
|
||||
@section('content')
|
||||
<p>
|
||||
Congrats!
|
||||
<a href="{{ $notificationUrl }}" target="_blank">{{ $creatorName }}</a>
|
||||
is now following you on Pony.fm!
|
||||
</p>
|
||||
@endsection
|
|
@ -0,0 +1,8 @@
|
|||
@extends('emails.notifications._layout_plaintext')
|
||||
|
||||
@section('content')
|
||||
Congrats! {{ $creatorName }} is now following you on Pony.fm!
|
||||
|
||||
Here's a link to their profile:
|
||||
{{ $notificationUrl }}
|
||||
@endsection
|
|
@ -0,0 +1,8 @@
|
|||
@extends('emails.notifications._layout')
|
||||
|
||||
@section('content')
|
||||
<p>{{ $creatorName }} created a new playlist on Pony.fm! Check it out:</p>
|
||||
|
||||
<p><a href="{{ $notificationUrl }}" target="_blank">{{ $playlistTitle }}</a></p>
|
||||
|
||||
@endsection
|
|
@ -0,0 +1,10 @@
|
|||
@extends('emails.notifications._layout_plaintext')
|
||||
|
||||
@section('content')
|
||||
{{ $creatorName }} created a new playlist on Pony.fm!
|
||||
|
||||
Title: {{ $playlistTitle }}
|
||||
|
||||
Listen to it:
|
||||
{{ $notificationUrl }}
|
||||
@endsection
|
8
resources/views/emails/notifications/new-track.blade.php
Normal file
8
resources/views/emails/notifications/new-track.blade.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
@extends('emails.notifications._layout')
|
||||
|
||||
@section('content')
|
||||
<p>{{ $creatorName }} published a new track on Pony.fm! Listen to it now:</p>
|
||||
|
||||
<p><a href="{{ $notificationUrl }}" target="_blank">{{ $trackTitle }}</a></p>
|
||||
|
||||
@endsection
|
|
@ -0,0 +1,10 @@
|
|||
@extends('emails.notifications._layout_plaintext')
|
||||
|
||||
@section('content')
|
||||
{{ $creatorName }} published a new track on Pony.fm!
|
||||
|
||||
Title: {{ $trackTitle }}
|
||||
|
||||
Listen to it:
|
||||
{{ $notificationUrl }}
|
||||
@endsection
|
|
@ -77,6 +77,11 @@ Route::get('p{id}/dl.{extension}', 'PlaylistsController@getDownload');
|
|||
|
||||
Route::get('notifications', 'AccountController@getNotifications');
|
||||
|
||||
|
||||
Route::get('notifications/email/unsubscribe/{subscriptionKey}', 'NotificationsController@getEmailUnsubscribe')->name('email:unsubscribe');
|
||||
Route::get('notifications/email/click/{emailKey}', 'NotificationsController@getEmailClick')->name('email:click');
|
||||
|
||||
|
||||
Route::get('oembed', 'TracksController@getOembed');
|
||||
|
||||
Route::group(['prefix' => 'api/v1', 'middleware' => 'json-exceptions'], function () {
|
||||
|
|
Loading…
Reference in a new issue