mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-22 04:58:01 +01:00
#25: New tracks have working email notifications now!
This commit is contained in:
parent
510d0e80ac
commit
5822408655
21 changed files with 1091 additions and 639 deletions
45
app/Http/Controllers/NotificationsController.php
Normal file
45
app/Http/Controllers/NotificationsController.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?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 Poniverse\Ponyfm\Models\Email;
|
||||
use Poniverse\Ponyfm\Models\EmailClick;
|
||||
use Poniverse\Ponyfm\Models\EmailSubscription;
|
||||
use View;
|
||||
|
||||
class NotificationsController extends Controller {
|
||||
public function getEmailClick($emailKey) {
|
||||
$emailKey = decrypt($emailKey);
|
||||
/** @var Email $email */
|
||||
$email = Email::findOrFail($emailKey);
|
||||
|
||||
$email->emailClicks()->create(['ip_address' => \Request::ip()]);
|
||||
|
||||
return redirect($email->getActivity()->url);
|
||||
}
|
||||
|
||||
public function getEmailUnsubscribe($subscriptionKey) {
|
||||
$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!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,9 +22,12 @@ namespace Poniverse\Ponyfm\Library\Notifications\Drivers;
|
|||
|
||||
use ArrayAccess;
|
||||
use Carbon\Carbon;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||
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,36 @@ 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) {
|
||||
$notification = $activity->notifications->where('user_id', $recipient->id)->first();
|
||||
$email = $notification->email()->create([]);
|
||||
Log::debug("Attempting to send an email about notification {$notification->id} to {$recipient->email}.");
|
||||
Mail::to($recipient->email)->queue(new \Poniverse\Ponyfm\Mail\NewTrack($email));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -63,7 +81,10 @@ 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());
|
||||
|
||||
$this->insertNotifications($activity, $recipientsQuery->get());
|
||||
$this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_PUBLISHED_TRACK)->get());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +100,7 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $playlist->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
|
||||
public function newFollower(User $userBeingFollowed, User $follower)
|
||||
|
@ -92,7 +113,7 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $userBeingFollowed->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,7 +129,7 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $comment->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,6 +145,6 @@ class PonyfmDriver extends AbstractDriver
|
|||
'resource_id' => $entityBeingFavourited->id,
|
||||
]);
|
||||
|
||||
$this->insertNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
$this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,10 @@ 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\EmailDriver;
|
||||
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;
|
||||
|
@ -63,7 +65,13 @@ class RecipientFinder implements NotificationHandler
|
|||
{
|
||||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
return $track->user->followers;
|
||||
return $track->user->followers();
|
||||
|
||||
case EmailDriver::class:
|
||||
return $track->user->followers()->whereHas('emailSubscriptions', function($query) {
|
||||
$query->where('activity_type', Activity::TYPE_PUBLISHED_TRACK);
|
||||
})->get();
|
||||
|
||||
case NativeDriver::class:
|
||||
$followerIds = [];
|
||||
$subIds = [];
|
||||
|
@ -119,6 +127,7 @@ class RecipientFinder implements NotificationHandler
|
|||
{
|
||||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
case EmailDriver::class:
|
||||
return [$userBeingFollowed];
|
||||
case NativeDriver::class:
|
||||
return Subscription::where('user_id', '=', $userBeingFollowed->id)->get();
|
||||
|
|
101
app/Mail/BaseNotification.php
Normal file
101
app/Mail/BaseNotification.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?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\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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.{$templateName}")
|
||||
->text("emails.{$templateName}_plaintext")
|
||||
->with(array_merge($extraVariables, [
|
||||
'notificationUrl' => $this->generateNotificationUrl(),
|
||||
'unsubscribeUrl' => $this->generateUnsubscribeUrl()
|
||||
]));
|
||||
}
|
||||
}
|
25
app/Mail/NewTrack.php
Normal file
25
app/Mail/NewTrack.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Poniverse\Ponyfm\Mail;
|
||||
|
||||
class NewTrack extends BaseNotification
|
||||
{
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$artistName = $this->initiatingUser->display_name;
|
||||
$trackTitle = $this->activityRecord->resource->title;
|
||||
|
||||
return $this->renderEmail(
|
||||
'new-track',
|
||||
"{$artistName} published \"{$trackTitle}\"",
|
||||
[
|
||||
'artist' => $artistName,
|
||||
'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;
|
||||
|
|
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');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -45,10 +45,13 @@ 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. Ensure you create HTML and plaintext templates 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 +95,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.
|
||||
|
|
5
resources/views/emails/new-track.blade.php
Normal file
5
resources/views/emails/new-track.blade.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<p>{{ $artist }} published a new track on Pony.fm! Listen to it now:</p>
|
||||
|
||||
<p><a href="{{ $notificationUrl }}" target="_blank">{{ $trackTitle }}</a></p>
|
||||
|
||||
<p><a href="{{ $unsubscribeUrl }}" target="_blank">Unsubscribe from email notifications for new tracks</a></p>
|
10
resources/views/emails/new-track_plaintext.blade.php
Normal file
10
resources/views/emails/new-track_plaintext.blade.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{ $artist }} published a new track on Pony.fm!
|
||||
|
||||
Title: {{ $trackTitle }}
|
||||
|
||||
Listen to it:
|
||||
{{ $notificationUrl }}
|
||||
|
||||
---
|
||||
Unsubscribe from email notifications for new tracks:
|
||||
{{ $unsubscribeUrl }}
|
|
@ -77,6 +77,10 @@ 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