+ {{ trans('errors.link-expired') }}
+
diff --git a/routes/web.php b/routes/web.php
index e353cb29..9d14ff98 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,5 +1,6 @@
name('mention-search');
Route::get('user/{username}', \App\Http\Controllers\PublicUserController::class)->name('public-user');
});
+
+Route::get('/unsubscribe/{item}/{user}', [ItemEmailUnsubscribeController::class, '__invoke'])
+ ->name('items.email-unsubscribe')
+ ->middleware('signed');
diff --git a/tests/Feature/Controllers/ItemEmailUnsubscribeControllerTest.php b/tests/Feature/Controllers/ItemEmailUnsubscribeControllerTest.php
new file mode 100644
index 00000000..6497bc58
--- /dev/null
+++ b/tests/Feature/Controllers/ItemEmailUnsubscribeControllerTest.php
@@ -0,0 +1,101 @@
+create();
+ $item = Item::factory()->create();
+
+ DB::table('votes')->insert([
+ 'user_id' => $user->id,
+ 'model_id' => $item->id,
+ 'model_type' => Item::class,
+ 'subscribed' => true,
+ ]);
+
+ $this->assertTrue($user->isSubscribedToItem($item));
+
+ $response = $this->get(URL::signedRoute('items.email-unsubscribe', [
+ 'item' => $item,
+ 'user' => $user,
+ ]));
+
+ $this->assertFalse($user->isSubscribedToItem($item));
+
+ $this->assertDatabaseHas('votes', [
+ 'user_id' => $user->id,
+ 'model_id' => $item->id,
+ 'model_type' => Item::class,
+ 'subscribed' => false,
+ ]);
+
+ $response->assertRedirect(route('items.show', $item->getAttributeValue('slug')));
+ $this->assertGuest();
+});
+
+it('does not resubscribe a user who clicks the link whilst unsubscribed', function () {
+
+ $user = User::factory()->create();
+ $item = Item::factory()->create();
+
+ DB::table('votes')->insert([
+ 'user_id' => $user->id,
+ 'model_id' => $item->id,
+ 'model_type' => Item::class,
+ 'subscribed' => false,
+ ]);
+
+ $this->assertFalse($user->isSubscribedToItem($item));
+
+ $response = $this->get(URL::signedRoute('items.email-unsubscribe', [
+ 'item' => $item,
+ 'user' => $user,
+ ]));
+
+ $this->assertFalse($user->isSubscribedToItem($item));
+ $response->assertRedirect(route('home'));
+ $this->assertGuest();
+});
+
+it('returns a 403 if the signed link has been modified', function () {
+
+ $user = User::factory()->create();
+ $item = Item::factory()->create();
+
+ $response = $this->get(URL::signedRoute('items.email-unsubscribe', [
+ 'item' => $item,
+ 'user' => $user,
+ ]) . '&foo=bar');
+
+ $response->assertStatus(403);
+});
+
+it('returns a 404 if the item does not exist', function () {
+
+ $user = User::factory()->create();
+ $item = Item::factory()->create();
+
+ $response = $this->get(URL::signedRoute('items.email-unsubscribe', [
+ 'item' => 999,
+ 'user' => $user,
+ ]));
+
+ $response->assertStatus(404);
+});
+
+it('returns a 404 if the user does not exist', function () {
+
+ $user = User::factory()->create();
+ $item = Item::factory()->create();
+
+ $response = $this->get(URL::signedRoute('items.email-unsubscribe', [
+ 'item' => $item,
+ 'user' => 999,
+ ]));
+
+ $response->assertStatus(404);
+});
diff --git a/tests/Unit/Models/UserTest.php b/tests/Unit/Models/UserTest.php
index 62f64c6a..25e75b97 100644
--- a/tests/Unit/Models/UserTest.php
+++ b/tests/Unit/Models/UserTest.php
@@ -5,6 +5,7 @@
use App\Enums\UserRole;
use App\Models\Comment;
use App\Settings\GeneralSettings;
+use Illuminate\Support\Facades\DB;
it('can generate an username upon user creation', function () {
$user = createUser();
@@ -111,3 +112,60 @@
expect($user->fresh())->toBeNull();
});
+
+it('can return true if a user is a subscribed to an item', function () {
+
+ $user = createUser();
+ $item = Item::factory()->create();
+
+ DB::table('votes')->insert([
+ 'user_id' => $user->id,
+ 'model_id' => $item->id,
+ 'model_type' => Item::class,
+ 'subscribed' => true,
+ ]);
+
+ $this->assertTrue($user->isSubscribedToItem($item));
+});
+
+it('can return false if a user is not subscribed to an item', function () {
+
+ $user = createUser();
+ $item = Item::factory()->create();
+
+ DB::table('votes')->insert([
+ 'user_id' => $user->id,
+ 'model_id' => $item->id,
+ 'model_type' => Item::class,
+ 'subscribed' => false,
+ ]);
+
+ $this->assertFalse($user->isSubscribedToItem($item));
+});
+
+it('toggles the subscription state of a vote the user belongs to', function () {
+
+ $user = createUser();
+ $item = Item::factory()->create();
+
+ DB::table('votes')->insert([
+ 'user_id' => $user->id,
+ 'model_id' => $item->id,
+ 'model_type' => Item::class,
+ 'subscribed' => false,
+ ]);
+
+ $user->toggleVoteSubscription($item->id, Item::class);
+
+ $this->assertTrue($user->isSubscribedToItem($item));
+});
+
+it('does not toggle the subscription if the user does not have a vote for that item', function () {
+
+ $user = createUser();
+ $item = Item::factory()->create();
+
+ $user->toggleVoteSubscription($item->id, Item::class);
+
+ $this->assertFalse($user->isSubscribedToItem($item));
+});