mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 06:27:59 +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 Illuminate\Queue\InteractsWithQueue;
|
||||||
use Poniverse\Ponyfm\Jobs\Job;
|
use Poniverse\Ponyfm\Jobs\Job;
|
||||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\AbstractDriver;
|
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\NativeDriver;
|
||||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
||||||
use Poniverse\Ponyfm\Models\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
@ -64,6 +65,9 @@ class SendNotifications extends Job implements ShouldQueue
|
||||||
//NativeDriver::class
|
//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) {
|
foreach ($drivers as $driver) {
|
||||||
/** @var $driver AbstractDriver */
|
/** @var $driver AbstractDriver */
|
||||||
$driver = new $driver;
|
$driver = new $driver;
|
||||||
|
|
|
@ -31,7 +31,16 @@ abstract class AbstractDriver implements NotificationHandler
|
||||||
|
|
||||||
public function __construct()
|
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 ArrayAccess;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Log;
|
||||||
|
use Mail;
|
||||||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||||
use Poniverse\Ponyfm\Models\Activity;
|
use Poniverse\Ponyfm\Models\Activity;
|
||||||
use Poniverse\Ponyfm\Models\Comment;
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
|
use Poniverse\Ponyfm\Models\Email;
|
||||||
use Poniverse\Ponyfm\Models\Notification;
|
use Poniverse\Ponyfm\Models\Notification;
|
||||||
use Poniverse\Ponyfm\Models\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\Models\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
@ -35,21 +38,36 @@ class PonyfmDriver extends AbstractDriver
|
||||||
/**
|
/**
|
||||||
* A helper method for bulk insertion of notification records.
|
* A helper method for bulk insertion of notification records.
|
||||||
*
|
*
|
||||||
* @param int $activityId
|
* @param Activity $activity
|
||||||
* @param User[] $recipients collection of {@link User} objects
|
* @param User[] $recipients collection of {@link User} objects
|
||||||
*/
|
*/
|
||||||
private function insertNotifications(int $activityId, $recipients)
|
private function insertNotifications(Activity $activity, $recipients)
|
||||||
{
|
{
|
||||||
$notifications = [];
|
$notifications = [];
|
||||||
foreach ($recipients as $recipient) {
|
foreach ($recipients as $recipient) {
|
||||||
$notifications[] = [
|
$notifications[] = [
|
||||||
'activity_id' => $activityId,
|
'activity_id' => $activity->id,
|
||||||
'user_id' => $recipient->id
|
'user_id' => $recipient->id
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
Notification::insert($notifications);
|
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
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -63,7 +81,10 @@ class PonyfmDriver extends AbstractDriver
|
||||||
'resource_id' => $track->id,
|
'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,
|
'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)
|
public function newFollower(User $userBeingFollowed, User $follower)
|
||||||
|
@ -92,7 +113,7 @@ class PonyfmDriver extends AbstractDriver
|
||||||
'resource_id' => $userBeingFollowed->id,
|
'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,
|
'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,
|
'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\Favouritable;
|
||||||
use Poniverse\Ponyfm\Contracts\NotificationHandler;
|
use Poniverse\Ponyfm\Contracts\NotificationHandler;
|
||||||
use Poniverse\Ponyfm\Jobs\SendNotifications;
|
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\NativeDriver;
|
||||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
||||||
|
use Poniverse\Ponyfm\Models\Activity;
|
||||||
use Poniverse\Ponyfm\Models\Comment;
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
use Poniverse\Ponyfm\Models\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\Models\Subscription;
|
use Poniverse\Ponyfm\Models\Subscription;
|
||||||
|
@ -63,7 +65,13 @@ class RecipientFinder implements NotificationHandler
|
||||||
{
|
{
|
||||||
switch ($this->notificationDriver) {
|
switch ($this->notificationDriver) {
|
||||||
case PonyfmDriver::class:
|
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:
|
case NativeDriver::class:
|
||||||
$followerIds = [];
|
$followerIds = [];
|
||||||
$subIds = [];
|
$subIds = [];
|
||||||
|
@ -119,6 +127,7 @@ class RecipientFinder implements NotificationHandler
|
||||||
{
|
{
|
||||||
switch ($this->notificationDriver) {
|
switch ($this->notificationDriver) {
|
||||||
case PonyfmDriver::class:
|
case PonyfmDriver::class:
|
||||||
|
case EmailDriver::class:
|
||||||
return [$userBeingFollowed];
|
return [$userBeingFollowed];
|
||||||
case NativeDriver::class:
|
case NativeDriver::class:
|
||||||
return Subscription::where('user_id', '=', $userBeingFollowed->id)->get();
|
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',
|
'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_NEWS = 1;
|
||||||
const TYPE_PUBLISHED_TRACK = 2;
|
const TYPE_PUBLISHED_TRACK = 2;
|
||||||
const TYPE_PUBLISHED_ALBUM = 3;
|
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 integer $user_id
|
||||||
* @property boolean $is_read
|
* @property boolean $is_read
|
||||||
* @property-read \Poniverse\Ponyfm\Models\Activity $activity
|
* @property-read \Poniverse\Ponyfm\Models\Activity $activity
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Email $email
|
||||||
* @property-read \Poniverse\Ponyfm\Models\User $recipient
|
* @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 forUser($user)
|
||||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Notification whereId($value)
|
* @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');
|
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.
|
* This scope grabs eager-loaded notifications for the given user.
|
||||||
*
|
*
|
||||||
|
|
|
@ -73,6 +73,8 @@ use Venturecraft\Revisionable\RevisionableTrait;
|
||||||
* @property-read mixed $user
|
* @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[] $activities
|
||||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Activity[] $notificationActivities
|
* @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 whereId($value)
|
||||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereDisplayName($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)
|
* @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 whereRememberToken($value)
|
||||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User whereIsArchived($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 whereDisabledAt($value)
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User withEmailSubscriptionFor($activityType)
|
||||||
* @mixin \Eloquent
|
* @mixin \Eloquent
|
||||||
*/
|
*/
|
||||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, \Illuminate\Contracts\Auth\Access\Authorizable, Searchable, Commentable
|
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;
|
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
|
* Takes the given string, slugifies it, and increments a counter if needed
|
||||||
* to generate a unique slug version of it.
|
* 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');
|
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()
|
public function getIsArchivedAttribute()
|
||||||
{
|
{
|
||||||
return (bool) $this->attributes['is_archived'];
|
return (bool) $this->attributes['is_archived'];
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
"predis/predis": "^1.0",
|
"predis/predis": "^1.0",
|
||||||
"ksubileau/color-thief-php": "^1.3",
|
"ksubileau/color-thief-php": "^1.3",
|
||||||
"graham-campbell/exceptions": "^9.1",
|
"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": {
|
"require-dev": {
|
||||||
"fzaninotto/faker": "~1.4",
|
"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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,11 +44,14 @@ Adding new notification types
|
||||||
- [`NotificationManager`](../app/Library/Notifications/NotificationManager.php)
|
- [`NotificationManager`](../app/Library/Notifications/NotificationManager.php)
|
||||||
- [`RecipientFinder`](../app/Library/Notifications/RecipientFinder.php)
|
- [`RecipientFinder`](../app/Library/Notifications/RecipientFinder.php)
|
||||||
- [`PonyfmDriver`](../app/Library/Notifications/PonyfmDriver.php)
|
- [`PonyfmDriver`](../app/Library/Notifications/PonyfmDriver.php)
|
||||||
|
|
||||||
|
3. Ensure you create HTML and plaintext templates for the email version of the
|
||||||
|
notification.
|
||||||
|
|
||||||
3. Call the new method on the `Notification` facade from wherever the
|
4. Call the new method on the `Notification` facade from wherever the
|
||||||
new notification gets triggered.
|
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.
|
[`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
|
data we store about an activity in the database to a notification's API
|
||||||
representation had to go somewhere, and using the `NotificationHandler`
|
representation had to go somewhere, and using the `NotificationHandler`
|
||||||
interface here would have made this logic a lot more obtuse.
|
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', '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::get('oembed', 'TracksController@getOembed');
|
||||||
|
|
||||||
Route::group(['prefix' => 'api/v1', 'middleware' => 'json-exceptions'], function () {
|
Route::group(['prefix' => 'api/v1', 'middleware' => 'json-exceptions'], function () {
|
||||||
|
|
Loading…
Reference in a new issue