Forráskód Böngészése

Added additional username default recipient option

Will Browning 5 éve
szülő
commit
05e1cc12b5

+ 26 - 1
app/AdditionalUsername.php

@@ -32,7 +32,8 @@ class AdditionalUsername extends Model
     protected $casts = [
         'id' => 'string',
         'user_id' => 'string',
-        'active' => 'boolean'
+        'active' => 'boolean',
+        'default_recipient_id' => 'string'
     ];
 
     public static function boot()
@@ -60,6 +61,30 @@ class AdditionalUsername extends Model
         return $this->belongsTo(User::class);
     }
 
+    /**
+     * Get all of the additional usernames's aliases.
+     */
+    public function aliases()
+    {
+        return $this->morphMany(Alias::class, 'aliasable');
+    }
+
+    /**
+     * Get the additional usernames's default recipient.
+     */
+    public function defaultRecipient()
+    {
+        return $this->hasOne(Recipient::class, 'id', 'default_recipient_id');
+    }
+    /**
+     * Set the additional usernames's default recipient.
+     */
+    public function setDefaultRecipientAttribute($recipient)
+    {
+        $this->attributes['default_recipient_id'] = $recipient->id;
+        $this->setRelation('defaultRecipient', $recipient);
+    }
+
     /**
      * Deactivate the username.
      */

+ 10 - 8
app/Alias.php

@@ -26,7 +26,8 @@ class Alias extends Model
         'email',
         'local_part',
         'domain',
-        'domain_id'
+        'aliasable_id',
+        'aliasable_type'
     ];
 
     protected $dates = [
@@ -38,7 +39,8 @@ class Alias extends Model
     protected $casts = [
         'id' => 'string',
         'user_id' => 'string',
-        'domain_id' => 'string',
+        'aliasable_id' => 'string',
+        'aliasable_type' => 'string',
         'active' => 'boolean'
     ];
 
@@ -66,11 +68,11 @@ class Alias extends Model
     }
 
     /**
-     * Get the custom domain for the email alias.
+     * Get the owning aliasable model.
      */
-    public function customDomain()
+    public function aliasable()
     {
-        return $this->belongsTo(Domain::class, 'domain_id');
+        return $this->morphTo();
     }
 
     /**
@@ -95,9 +97,9 @@ class Alias extends Model
     public function verifiedRecipientsOrDefault()
     {
         if ($this->verifiedRecipients()->count() === 0) {
-            // If the alias is for a custom domain that has a default recipient set.
-            if (isset($this->customDomain->defaultRecipient)) {
-                return $this->customDomain->defaultRecipient();
+            // If the alias is for a custom domain or additional username that has a default recipient set.
+            if (isset($this->aliasable->defaultRecipient)) {
+                return $this->aliasable->defaultRecipient();
             }
 
             return $this->user->defaultRecipient();

+ 6 - 3
app/Console/Commands/ReceiveEmail.php

@@ -93,6 +93,7 @@ class ReceiveEmail extends Command
                     // Check if this is an additional username.
                     if ($additionalUsername = AdditionalUsername::where('username', $subdomain)->first()) {
                         $user = $additionalUsername->user;
+                        $aliasable = $additionalUsername;
                     } else {
                         $user = User::where('username', $subdomain)->first();
                     }
@@ -102,6 +103,7 @@ class ReceiveEmail extends Command
                     // Check if this is a custom domain.
                     if ($customDomain = Domain::where('domain', $recipient['domain'])->first()) {
                         $user = $customDomain->user;
+                        $aliasable = $customDomain;
                     }
 
                     // Check if this is a uuid generated alias.
@@ -125,7 +127,7 @@ class ReceiveEmail extends Command
                 if ($recipient['extension'] === sha1(config('anonaddy.secret').$displayTo)) {
                     $this->handleReply($user, $recipient, $displayTo);
                 } else {
-                    $this->handleForward($user, $recipient, $customDomain->id ?? null);
+                    $this->handleForward($user, $recipient, $aliasable ?? null);
                 }
             }
         } catch (\Exception $e) {
@@ -166,13 +168,14 @@ class ReceiveEmail extends Command
         }
     }
 
-    protected function handleForward($user, $recipient, $customDomainId)
+    protected function handleForward($user, $recipient, $aliasable)
     {
         $alias = $user->aliases()->firstOrNew([
             'email' => $recipient['local_part'] . '@' . $recipient['domain'],
             'local_part' => $recipient['local_part'],
             'domain' => $recipient['domain'],
-            'domain_id' => $customDomainId
+            'aliasable_id' => $aliasable->id ?? null,
+            'aliasable_type' => $aliasable ? 'App\\'.class_basename($aliasable) : null
         ]);
 
         if (!isset($alias->id)) {

+ 1 - 1
app/Domain.php

@@ -60,7 +60,7 @@ class Domain extends Model
      */
     public function aliases()
     {
-        return $this->hasMany(Alias::class);
+        return $this->morphMany(Alias::class, 'aliasable');
     }
 
     /**

+ 4 - 4
app/Http/Controllers/Api/AdditionalUsernameController.php

@@ -12,14 +12,14 @@ class AdditionalUsernameController extends Controller
 {
     public function index()
     {
-        return AdditionalUsernameResource::collection(user()->additionalUsernames()->latest()->get());
+        return AdditionalUsernameResource::collection(user()->additionalUsernames()->with(['aliases', 'defaultRecipient'])->latest()->get());
     }
 
     public function show($id)
     {
         $username = user()->additionalUsernames()->findOrFail($id);
 
-        return new AdditionalUsernameResource($username);
+        return new AdditionalUsernameResource($username->load(['aliases', 'defaultRecipient']));
     }
 
     public function store(StoreAdditionalUsernameRequest $request)
@@ -32,7 +32,7 @@ class AdditionalUsernameController extends Controller
 
         user()->increment('username_count');
 
-        return new AdditionalUsernameResource($username->refresh());
+        return new AdditionalUsernameResource($username->refresh()->load(['aliases', 'defaultRecipient']));
     }
 
     public function update(UpdateAdditionalUsernameRequest $request, $id)
@@ -41,7 +41,7 @@ class AdditionalUsernameController extends Controller
 
         $username->update(['description' => $request->description]);
 
-        return new AdditionalUsernameResource($username->refresh());
+        return new AdditionalUsernameResource($username->refresh()->load(['aliases', 'defaultRecipient']));
     }
 
     public function destroy($id)

+ 25 - 0
app/Http/Controllers/Api/AdditionalUsernameDefaultRecipientController.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\UpdateAdditionalUsernameDefaultRecipientRequest;
+use App\Http\Resources\AdditionalUsernameResource;
+
+class AdditionalUsernameDefaultRecipientController extends Controller
+{
+    public function update(UpdateAdditionalUsernameDefaultRecipientRequest $request, $id)
+    {
+        $additionalUsername = user()->additionalUsernames()->findOrFail($id);
+        if (empty($request->default_recipient)) {
+            $additionalUsername->default_recipient_id = null;
+        } else {
+            $recipient = user()->verifiedRecipients()->findOrFail($request->default_recipient);
+            $additionalUsername->default_recipient = $recipient;
+        }
+
+        $additionalUsername->save();
+
+        return new AdditionalUsernameResource($additionalUsername);
+    }
+}

+ 1 - 1
app/Http/Controllers/ShowAdditionalUsernameController.php

@@ -7,7 +7,7 @@ class ShowAdditionalUsernameController extends Controller
     public function index()
     {
         return view('usernames.index', [
-            'usernames' => user()->additionalUsernames()->latest()->get()
+            'usernames' => user()->additionalUsernames()->with(['aliases', 'defaultRecipient'])->latest()->get()
         ]);
     }
 }

+ 1 - 1
app/Http/Controllers/ShowAliasController.php

@@ -8,7 +8,7 @@ class ShowAliasController extends Controller
     {
         return view('aliases.index', [
             'defaultRecipient' => user()->defaultRecipient,
-            'aliases' => user()->aliases()->with(['recipients', 'customDomain.defaultRecipient'])->latest()->get(),
+            'aliases' => user()->aliases()->with(['recipients', 'aliasable.defaultRecipient'])->latest()->get(),
             'recipients' => user()->verifiedRecipients,
             'totalForwarded' => user()->totalEmailsForwarded(),
             'totalBlocked' => user()->totalEmailsBlocked(),

+ 30 - 0
app/Http/Requests/UpdateAdditionalUsernameDefaultRecipientRequest.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class UpdateAdditionalUsernameDefaultRecipientRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'default_recipient' => 'nullable|string'
+        ];
+    }
+}

+ 2 - 0
app/Http/Resources/AdditionalUsernameResource.php

@@ -13,6 +13,8 @@ class AdditionalUsernameResource extends JsonResource
             'user_id' => $this->user_id,
             'username' => $this->username,
             'description' => $this->description,
+            'aliases' => AliasResource::collection($this->whenLoaded('aliases')),
+            'default_recipient' => new RecipientResource($this->whenLoaded('defaultRecipient')),
             'active' => $this->active,
             'created_at' => $this->created_at->toDateTimeString(),
             'updated_at' => $this->updated_at->toDateTimeString(),

+ 2 - 1
app/Http/Resources/AliasResource.php

@@ -11,7 +11,8 @@ class AliasResource extends JsonResource
         return [
             'id' => $this->id,
             'user_id' => $this->user_id,
-            'domain_id' => $this->domain_id,
+            'aliasable_id' => $this->aliasable_id,
+            'aliasable_type' => $this->aliasable_type,
             'local_part' => $this->local_part,
             'extension' => $this->extension,
             'domain' => $this->domain,

+ 110 - 100
composer.lock

@@ -270,31 +270,30 @@
         },
         {
             "name": "doctrine/dbal",
-            "version": "v2.9.2",
+            "version": "v2.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/dbal.git",
-                "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9"
+                "reference": "0c9a646775ef549eb0a213a4f9bd4381d9b4d934"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
-                "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
+                "url": "https://api.github.com/repos/doctrine/dbal/zipball/0c9a646775ef549eb0a213a4f9bd4381d9b4d934",
+                "reference": "0c9a646775ef549eb0a213a4f9bd4381d9b4d934",
                 "shasum": ""
             },
             "require": {
                 "doctrine/cache": "^1.0",
                 "doctrine/event-manager": "^1.0",
                 "ext-pdo": "*",
-                "php": "^7.1"
+                "php": "^7.2"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^5.0",
-                "jetbrains/phpstorm-stubs": "^2018.1.2",
-                "phpstan/phpstan": "^0.10.1",
-                "phpunit/phpunit": "^7.4",
-                "symfony/console": "^2.0.5|^3.0|^4.0",
-                "symfony/phpunit-bridge": "^3.4.5|^4.0.5"
+                "doctrine/coding-standard": "^6.0",
+                "jetbrains/phpstorm-stubs": "^2019.1",
+                "phpstan/phpstan": "^0.11.3",
+                "phpunit/phpunit": "^8.4.1",
+                "symfony/console": "^2.0.5|^3.0|^4.0|^5.0"
             },
             "suggest": {
                 "symfony/console": "For helpful console commands such as SQL execution and import of files."
@@ -305,7 +304,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.9.x-dev",
+                    "dev-master": "2.10.x-dev",
                     "dev-develop": "3.0.x-dev"
                 }
             },
@@ -319,6 +318,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -327,10 +330,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -341,14 +340,25 @@
             "keywords": [
                 "abstraction",
                 "database",
+                "db2",
                 "dbal",
+                "mariadb",
+                "mssql",
                 "mysql",
-                "persistence",
+                "oci8",
+                "oracle",
+                "pdo",
                 "pgsql",
-                "php",
-                "queryobject"
-            ],
-            "time": "2018-12-31T03:27:51+00:00"
+                "postgresql",
+                "queryobject",
+                "sasql",
+                "sql",
+                "sqlanywhere",
+                "sqlite",
+                "sqlserver",
+                "sqlsrv"
+            ],
+            "time": "2019-11-03T16:50:43+00:00"
         },
         {
             "name": "doctrine/event-manager",
@@ -1001,16 +1011,16 @@
         },
         {
             "name": "intervention/image",
-            "version": "2.5.0",
+            "version": "2.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Intervention/image.git",
-                "reference": "39eaef720d082ecc54c64bf54541c55f10db546d"
+                "reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Intervention/image/zipball/39eaef720d082ecc54c64bf54541c55f10db546d",
-                "reference": "39eaef720d082ecc54c64bf54541c55f10db546d",
+                "url": "https://api.github.com/repos/Intervention/image/zipball/abbf18d5ab8367f96b3205ca3c89fb2fa598c69e",
+                "reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e",
                 "shasum": ""
             },
             "require": {
@@ -1067,7 +1077,7 @@
                 "thumbnail",
                 "watermark"
             ],
-            "time": "2019-06-24T14:06:31+00:00"
+            "time": "2019-11-02T09:15:47+00:00"
         },
         {
             "name": "jakub-onderka/php-console-color",
@@ -1855,16 +1865,16 @@
         },
         {
             "name": "nesbot/carbon",
-            "version": "2.25.3",
+            "version": "2.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
-                "reference": "d07636581795383e2fea2d711212d30f941f2039"
+                "reference": "e01ecc0b71168febb52ae1fdc1cfcc95428e604e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/d07636581795383e2fea2d711212d30f941f2039",
-                "reference": "d07636581795383e2fea2d711212d30f941f2039",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e01ecc0b71168febb52ae1fdc1cfcc95428e604e",
+                "reference": "e01ecc0b71168febb52ae1fdc1cfcc95428e604e",
                 "shasum": ""
             },
             "require": {
@@ -1918,7 +1928,7 @@
                 "datetime",
                 "time"
             ],
-            "time": "2019-10-20T11:05:44+00:00"
+            "time": "2019-10-21T21:32:25+00:00"
         },
         {
             "name": "nikic/php-parser",
@@ -2752,16 +2762,16 @@
         },
         {
             "name": "psr/log",
-            "version": "1.1.1",
+            "version": "1.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2"
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
-                "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
                 "shasum": ""
             },
             "require": {
@@ -2795,7 +2805,7 @@
                 "psr",
                 "psr-3"
             ],
-            "time": "2019-10-25T08:06:51+00:00"
+            "time": "2019-11-01T11:05:21+00:00"
         },
         {
             "name": "psr/simple-cache",
@@ -3158,16 +3168,16 @@
         },
         {
             "name": "symfony/console",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "929ddf360d401b958f611d44e726094ab46a7369"
+                "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/929ddf360d401b958f611d44e726094ab46a7369",
-                "reference": "929ddf360d401b958f611d44e726094ab46a7369",
+                "url": "https://api.github.com/repos/symfony/console/zipball/136c4bd62ea871d00843d1bc0316de4c4a84bb78",
+                "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78",
                 "shasum": ""
             },
             "require": {
@@ -3229,11 +3239,11 @@
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2019-10-07T12:36:49+00:00"
+            "time": "2019-10-30T12:58:49+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
@@ -3286,16 +3296,16 @@
         },
         {
             "name": "symfony/debug",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "cc5c1efd0edfcfd10b354750594a46b3dd2afbbe"
+                "reference": "5ea9c3e01989a86ceaa0283f21234b12deadf5e2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/cc5c1efd0edfcfd10b354750594a46b3dd2afbbe",
-                "reference": "cc5c1efd0edfcfd10b354750594a46b3dd2afbbe",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/5ea9c3e01989a86ceaa0283f21234b12deadf5e2",
+                "reference": "5ea9c3e01989a86ceaa0283f21234b12deadf5e2",
                 "shasum": ""
             },
             "require": {
@@ -3338,11 +3348,11 @@
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2019-09-19T15:51:53+00:00"
+            "time": "2019-10-28T17:07:32+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
@@ -3470,16 +3480,16 @@
         },
         {
             "name": "symfony/finder",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "5e575faa95548d0586f6bedaeabec259714e44d1"
+                "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/5e575faa95548d0586f6bedaeabec259714e44d1",
-                "reference": "5e575faa95548d0586f6bedaeabec259714e44d1",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f",
+                "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f",
                 "shasum": ""
             },
             "require": {
@@ -3515,20 +3525,20 @@
             ],
             "description": "Symfony Finder Component",
             "homepage": "https://symfony.com",
-            "time": "2019-09-16T11:29:48+00:00"
+            "time": "2019-10-30T12:53:54+00:00"
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "76590ced16d4674780863471bae10452b79210a5"
+                "reference": "38f63e471cda9d37ac06e76d14c5ea2ec5887051"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/76590ced16d4674780863471bae10452b79210a5",
-                "reference": "76590ced16d4674780863471bae10452b79210a5",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/38f63e471cda9d37ac06e76d14c5ea2ec5887051",
+                "reference": "38f63e471cda9d37ac06e76d14c5ea2ec5887051",
                 "shasum": ""
             },
             "require": {
@@ -3570,20 +3580,20 @@
             ],
             "description": "Symfony HttpFoundation Component",
             "homepage": "https://symfony.com",
-            "time": "2019-10-04T19:48:13+00:00"
+            "time": "2019-10-30T12:58:49+00:00"
         },
         {
             "name": "symfony/http-kernel",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-kernel.git",
-                "reference": "5f08141850932e8019c01d8988bf3ed6367d2991"
+                "reference": "56acfda9e734e8715b3b0e6859cdb4f5b28757bf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5f08141850932e8019c01d8988bf3ed6367d2991",
-                "reference": "5f08141850932e8019c01d8988bf3ed6367d2991",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/56acfda9e734e8715b3b0e6859cdb4f5b28757bf",
+                "reference": "56acfda9e734e8715b3b0e6859cdb4f5b28757bf",
                 "shasum": ""
             },
             "require": {
@@ -3662,20 +3672,20 @@
             ],
             "description": "Symfony HttpKernel Component",
             "homepage": "https://symfony.com",
-            "time": "2019-10-07T15:06:41+00:00"
+            "time": "2019-11-01T10:00:03+00:00"
         },
         {
             "name": "symfony/mime",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/mime.git",
-                "reference": "32f71570547b91879fdbd9cf50317d556ae86916"
+                "reference": "3c0e197529da6e59b217615ba8ee7604df88b551"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/mime/zipball/32f71570547b91879fdbd9cf50317d556ae86916",
-                "reference": "32f71570547b91879fdbd9cf50317d556ae86916",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/3c0e197529da6e59b217615ba8ee7604df88b551",
+                "reference": "3c0e197529da6e59b217615ba8ee7604df88b551",
                 "shasum": ""
             },
             "require": {
@@ -3721,7 +3731,7 @@
                 "mime",
                 "mime-type"
             ],
-            "time": "2019-09-19T17:00:15+00:00"
+            "time": "2019-10-30T12:58:49+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
@@ -4184,16 +4194,16 @@
         },
         {
             "name": "symfony/process",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b"
+                "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/50556892f3cc47d4200bfd1075314139c4c9ff4b",
-                "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b",
+                "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0",
+                "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0",
                 "shasum": ""
             },
             "require": {
@@ -4229,7 +4239,7 @@
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2019-09-26T21:17:10+00:00"
+            "time": "2019-10-28T17:07:32+00:00"
         },
         {
             "name": "symfony/psr-http-message-bridge",
@@ -4298,16 +4308,16 @@
         },
         {
             "name": "symfony/routing",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/routing.git",
-                "reference": "3b174ef04fe66696524efad1e5f7a6c663d822ea"
+                "reference": "63a9920cc86fcc745e5ea254e362f02b615290b9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/routing/zipball/3b174ef04fe66696524efad1e5f7a6c663d822ea",
-                "reference": "3b174ef04fe66696524efad1e5f7a6c663d822ea",
+                "url": "https://api.github.com/repos/symfony/routing/zipball/63a9920cc86fcc745e5ea254e362f02b615290b9",
+                "reference": "63a9920cc86fcc745e5ea254e362f02b615290b9",
                 "shasum": ""
             },
             "require": {
@@ -4370,7 +4380,7 @@
                 "uri",
                 "url"
             ],
-            "time": "2019-10-04T20:57:10+00:00"
+            "time": "2019-10-30T12:58:49+00:00"
         },
         {
             "name": "symfony/service-contracts",
@@ -4432,16 +4442,16 @@
         },
         {
             "name": "symfony/translation",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "fe6193b066c457c144333c06aaa869a2d42a167f"
+                "reference": "a3aa590ce944afb3434d7a55f81b00927144d5ec"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/fe6193b066c457c144333c06aaa869a2d42a167f",
-                "reference": "fe6193b066c457c144333c06aaa869a2d42a167f",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/a3aa590ce944afb3434d7a55f81b00927144d5ec",
+                "reference": "a3aa590ce944afb3434d7a55f81b00927144d5ec",
                 "shasum": ""
             },
             "require": {
@@ -4504,7 +4514,7 @@
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2019-09-27T14:37:39+00:00"
+            "time": "2019-10-30T12:53:54+00:00"
         },
         {
             "name": "symfony/translation-contracts",
@@ -4565,16 +4575,16 @@
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "bde8957fc415fdc6964f33916a3755737744ff05"
+                "reference": "ea4940845535c85ff5c505e13b3205b0076d07bf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/bde8957fc415fdc6964f33916a3755737744ff05",
-                "reference": "bde8957fc415fdc6964f33916a3755737744ff05",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ea4940845535c85ff5c505e13b3205b0076d07bf",
+                "reference": "ea4940845535c85ff5c505e13b3205b0076d07bf",
                 "shasum": ""
             },
             "require": {
@@ -4637,7 +4647,7 @@
                 "debug",
                 "dump"
             ],
-            "time": "2019-10-04T19:48:13+00:00"
+            "time": "2019-10-13T12:02:04+00:00"
         },
         {
             "name": "tijsverkoyen/css-to-inline-styles",
@@ -5337,16 +5347,16 @@
         },
         {
             "name": "friendsofphp/php-cs-fixer",
-            "version": "v2.15.3",
+            "version": "v2.16.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
-                "reference": "705490b0f282f21017d73561e9498d2b622ee34c"
+                "reference": "ceaff36bee1ed3f1bbbedca36d2528c0826c336d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/705490b0f282f21017d73561e9498d2b622ee34c",
-                "reference": "705490b0f282f21017d73561e9498d2b622ee34c",
+                "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ceaff36bee1ed3f1bbbedca36d2528c0826c336d",
+                "reference": "ceaff36bee1ed3f1bbbedca36d2528c0826c336d",
                 "shasum": ""
             },
             "require": {
@@ -5422,7 +5432,7 @@
                 }
             ],
             "description": "A tool to automatically fix PHP code style",
-            "time": "2019-08-31T12:51:54+00:00"
+            "time": "2019-11-03T13:31:09+00:00"
         },
         {
             "name": "fzaninotto/faker",
@@ -7084,7 +7094,7 @@
         },
         {
             "name": "symfony/filesystem",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
@@ -7134,16 +7144,16 @@
         },
         {
             "name": "symfony/options-resolver",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/options-resolver.git",
-                "reference": "81c2e120522a42f623233968244baebd6b36cb6a"
+                "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/81c2e120522a42f623233968244baebd6b36cb6a",
-                "reference": "81c2e120522a42f623233968244baebd6b36cb6a",
+                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f46c7fc8e207bd8a2188f54f8738f232533765a4",
+                "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4",
                 "shasum": ""
             },
             "require": {
@@ -7184,7 +7194,7 @@
                 "configuration",
                 "options"
             ],
-            "time": "2019-08-08T09:29:19+00:00"
+            "time": "2019-10-28T20:59:01+00:00"
         },
         {
             "name": "symfony/polyfill-php70",
@@ -7247,7 +7257,7 @@
         },
         {
             "name": "symfony/stopwatch",
-            "version": "v4.3.5",
+            "version": "v4.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/stopwatch.git",

+ 32 - 0
database/migrations/2019_11_05_100750_add_default_recipient_id_column_to_additional_usernames_table.php

@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddDefaultRecipientIdColumnToAdditionalUsernamesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('additional_usernames', function (Blueprint $table) {
+            $table->uuid('default_recipient_id')->nullable()->after('user_id');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('additional_usernames', function (Blueprint $table) {
+            $table->dropColumn('default_recipient_id');
+        });
+    }
+}

+ 34 - 0
database/migrations/2019_11_05_105047_add_aliasable_columns_to_aliases_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddAliasableColumnsToAliasesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('aliases', function (Blueprint $table) {
+            $table->string('aliasable_type')->nullable()->after('user_id');
+            $table->uuid('aliasable_id')->nullable()->after('user_id');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('aliases', function (Blueprint $table) {
+            $table->dropColumn('aliasable_type');
+            $table->dropColumn('aliasable_id');
+        });
+    }
+}

+ 32 - 0
database/migrations/2019_11_05_143834_remove_domain_id_column_from_aliases_table.php

@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class RemoveDomainIdColumnFromAliasesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('aliases', function (Blueprint $table) {
+            $table->dropColumn('domain_id');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('aliases', function (Blueprint $table) {
+            $table->uuid('domain_id')->after('user_id')->nullable();
+        });
+    }
+}

+ 4 - 3
resources/js/pages/Aliases.vue

@@ -234,10 +234,11 @@
             aliasRecipientsToEdit.length ? aliasRecipientsToEdit.length : '1'
           }}</span>
           <span
-            v-else-if="has(props.row.custom_domain, 'default_recipient.email')"
+            v-else-if="has(props.row.aliasable, 'default_recipient.email')"
             class="py-1 px-2 text-sm bg-yellow-200 text-yellow-900 rounded-full tooltip outline-none"
-            :data-tippy-content="props.row.custom_domain.default_recipient.email"
-            >domain's default</span
+            :data-tippy-content="props.row.aliasable.default_recipient.email"
+            >{{ props.row.aliasable_type === 'App\\Domain' ? 'domain' : 'username' }}'s
+            default</span
           >
           <span
             v-else

+ 127 - 1
resources/js/pages/Usernames.vue

@@ -115,6 +115,26 @@
             />
           </div>
         </span>
+        <span v-else-if="props.column.field === 'default_recipient'">
+          <div v-if="props.row.default_recipient">
+            {{ props.row.default_recipient.email | truncate(30) }}
+            <icon
+              name="edit"
+              class="ml-2 inline-block w-6 h-6 text-grey-200 fill-current cursor-pointer"
+              @click.native="openUsernameDefaultRecipientModal(props.row)"
+            />
+          </div>
+          <div class="flex justify-center" v-else>
+            <icon
+              name="plus"
+              class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
+              @click.native="openUsernameDefaultRecipientModal(props.row)"
+            />
+          </div>
+        </span>
+        <span v-else-if="props.column.field === 'aliases_count'">
+          {{ props.row.aliases.length }}
+        </span>
         <span v-else-if="props.column.field === 'active'" class="flex items-center">
           <Toggle
             v-model="rows[props.row.originalIndex].active"
@@ -197,6 +217,54 @@
       </div>
     </Modal>
 
+    <Modal :open="usernameDefaultRecipientModalOpen" @close="closeUsernameDefaultRecipientModal">
+      <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
+        <h2
+          class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
+        >
+          Update Default Recipient
+        </h2>
+        <p class="my-4 text-grey-700">
+          Select the default recipient for this username. This overrides the default recipient in
+          your account settings. Leave it empty if you would like to use the default recipient in
+          your account settings.
+        </p>
+        <multiselect
+          v-model="defaultRecipient"
+          :options="recipientOptions"
+          :multiple="false"
+          :close-on-select="true"
+          :clear-on-select="false"
+          :searchable="false"
+          :allow-empty="true"
+          placeholder="Select recipient"
+          label="email"
+          track-by="email"
+          :preselect-first="false"
+          :show-labels="false"
+        >
+        </multiselect>
+        <div class="mt-6">
+          <button
+            type="button"
+            @click="editDefaultRecipient()"
+            class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
+            :class="editDefaultRecipientLoading ? 'cursor-not-allowed' : ''"
+            :disabled="editDefaultRecipientLoading"
+          >
+            Update Default Recipient
+            <loader v-if="editDefaultRecipientLoading" />
+          </button>
+          <button
+            @click="closeUsernameDefaultRecipientModal()"
+            class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
+          >
+            Cancel
+          </button>
+        </div>
+      </div>
+    </Modal>
+
     <Modal :open="deleteUsernameModalOpen" @close="closeDeleteModal">
       <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
         <h2
@@ -236,6 +304,7 @@
 import Modal from './../components/Modal.vue'
 import Toggle from './../components/Toggle.vue'
 import tippy from 'tippy.js'
+import Multiselect from 'vue-multiselect'
 
 export default {
   props: {
@@ -247,10 +316,15 @@ export default {
       type: Number,
       required: true,
     },
+    recipientOptions: {
+      type: Array,
+      required: true,
+    },
   },
   components: {
     Modal,
     Toggle,
+    Multiselect,
   },
   mounted() {
     this.addTooltips()
@@ -266,6 +340,10 @@ export default {
       usernameDescriptionToEdit: '',
       deleteUsernameLoading: false,
       deleteUsernameModalOpen: false,
+      usernameDefaultRecipientModalOpen: false,
+      defaultRecipientUsernameToEdit: {},
+      defaultRecipient: {},
+      editDefaultRecipientLoading: false,
       errors: {},
       columns: [
         {
@@ -280,7 +358,18 @@ export default {
         {
           label: 'Description',
           field: 'description',
-          width: '500px',
+        },
+        {
+          label: 'Default Recipient',
+          field: 'default_recipient',
+          sortable: false,
+          globalSearchDisabled: true,
+        },
+        {
+          label: 'Alias Count',
+          field: 'aliases_count',
+          type: 'number',
+          globalSearchDisabled: true,
         },
         {
           label: 'Active',
@@ -370,6 +459,16 @@ export default {
       this.deleteUsernameModalOpen = false
       this.usernameIdToDelete = null
     },
+    openUsernameDefaultRecipientModal(username) {
+      this.usernameDefaultRecipientModalOpen = true
+      this.defaultRecipientUsernameToEdit = username
+      this.defaultRecipient = username.default_recipient
+    },
+    closeUsernameDefaultRecipientModal() {
+      this.usernameDefaultRecipientModalOpen = false
+      this.defaultRecipientUsernameToEdit = {}
+      this.defaultRecipient = {}
+    },
     editUsername(username) {
       if (this.usernameDescriptionToEdit.length > 100) {
         return this.error('Description cannot be more than 100 characters')
@@ -397,6 +496,33 @@ export default {
           this.error()
         })
     },
+    editDefaultRecipient() {
+      this.editDefaultRecipientLoading = true
+      axios
+        .patch(
+          `/api/v1/usernames/${this.defaultRecipientUsernameToEdit.id}/default-recipient`,
+          JSON.stringify({
+            default_recipient: this.defaultRecipient ? this.defaultRecipient.id : '',
+          }),
+          {
+            headers: { 'Content-Type': 'application/json' },
+          }
+        )
+        .then(response => {
+          let username = _.find(this.rows, ['id', this.defaultRecipientUsernameToEdit.id])
+          username.default_recipient = this.defaultRecipient
+          this.usernameDefaultRecipientModalOpen = false
+          this.editDefaultRecipientLoading = false
+          this.defaultRecipient = {}
+          this.success("Additional Username's default recipient updated")
+        })
+        .catch(error => {
+          this.usernameDefaultRecipientModalOpen = false
+          this.editDefaultRecipientLoading = false
+          this.defaultRecipient = {}
+          this.error()
+        })
+    },
     activateUsername(id) {
       axios
         .post(

+ 1 - 1
resources/views/usernames/index.blade.php

@@ -4,6 +4,6 @@
     <div class="container py-8">
         @include('shared.status')
 
-        <usernames :initial-usernames="{{json_encode($usernames)}}" :username-count="{{config('anonaddy.additional_username_limit')}}" />
+        <usernames :initial-usernames="{{json_encode($usernames)}}" :username-count="{{config('anonaddy.additional_username_limit')}}" :recipient-options="{{ json_encode(Auth::user()->verifiedRecipients) }}" />
     </div>
 @endsection

+ 1 - 0
routes/api.php

@@ -52,6 +52,7 @@ Route::group([
     Route::post('/usernames', 'Api\AdditionalUsernameController@store');
     Route::patch('/usernames/{id}', 'Api\AdditionalUsernameController@update');
     Route::delete('/usernames/{id}', 'Api\AdditionalUsernameController@destroy');
+    Route::patch('/usernames/{id}/default-recipient', 'Api\AdditionalUsernameDefaultRecipientController@update');
 
     Route::post('/active-usernames', 'Api\ActiveAdditionalUsernameController@store');
     Route::delete('/active-usernames/{id}', 'Api\ActiveAdditionalUsernameController@destroy');

+ 73 - 0
tests/Feature/Api/AdditionalUsernamesTest.php

@@ -4,6 +4,7 @@ namespace Tests\Feature\Api;
 
 use App\AdditionalUsername;
 use App\DeletedUsername;
+use App\Recipient;
 use App\User;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Tests\TestCase;
@@ -217,4 +218,76 @@ class AdditionalUsernamesTest extends TestCase
 
         $this->assertEquals(DeletedUsername::first()->username, $username->username);
     }
+
+    /** @test */
+    public function user_can_update_additional_username_default_recipient()
+    {
+        $additionalUsername = factory(AdditionalUsername::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $newDefaultRecipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $response = $this->json('PATCH', '/api/v1/usernames/'.$additionalUsername->id.'/default-recipient', [
+            'default_recipient' => $newDefaultRecipient->id
+        ]);
+
+        $response->assertStatus(200);
+        $this->assertDatabaseHas('additional_usernames', [
+            'id' => $additionalUsername->id,
+            'default_recipient_id' => $newDefaultRecipient->id
+        ]);
+
+        $this->assertEquals($newDefaultRecipient->email, $additionalUsername->refresh()->defaultRecipient->email);
+    }
+
+    /** @test */
+    public function user_cannot_update_additional_username_default_recipient_with_unverified_recipient()
+    {
+        $additionalUsername = factory(AdditionalUsername::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $newDefaultRecipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id,
+            'email_verified_at' => null
+        ]);
+
+        $response = $this->json('PATCH', '/api/v1/usernames/'.$additionalUsername->id.'/default-recipient', [
+            'default_recipient' => $newDefaultRecipient->id
+        ]);
+
+        $response->assertStatus(404);
+        $this->assertDatabaseMissing('additional_usernames', [
+            'id' => $additionalUsername->id,
+            'default_recipient_id' => $newDefaultRecipient->id
+        ]);
+    }
+
+    /** @test */
+    public function user_can_remove_additional_username_default_recipient()
+    {
+        $defaultRecipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $additionalUsername = factory(AdditionalUsername::class)->create([
+            'user_id' => $this->user->id,
+            'default_recipient_id' => $defaultRecipient->id
+        ]);
+
+        $response = $this->json('PATCH', '/api/v1/usernames/'.$additionalUsername->id.'/default-recipient', [
+            'default_recipient' => ''
+        ]);
+
+        $response->assertStatus(200);
+        $this->assertDatabaseHas('additional_usernames', [
+            'id' => $additionalUsername->id,
+            'default_recipient_id' => null
+        ]);
+
+        $this->assertNull($additionalUsername->refresh()->defaultRecipient);
+    }
 }

+ 58 - 3
tests/Feature/ReceiveEmailTest.php

@@ -792,7 +792,8 @@ class ReceiveEmailTest extends TestCase
         )->assertExitCode(0);
 
         $this->assertDatabaseHas('aliases', [
-            'domain_id' => $domain->id,
+            'aliasable_id' => $domain->id,
+            'aliasable_type' => 'App\Domain',
             'email' => 'ebay@example.com',
             'local_part' => 'ebay',
             'domain' => 'example.com',
@@ -818,7 +819,7 @@ class ReceiveEmailTest extends TestCase
 
         Mail::assertNothingSent();
 
-        factory(AdditionalUsername::class)->create([
+        $additionalUsername = factory(AdditionalUsername::class)->create([
             'user_id' => $this->user->id,
             'username' => 'janedoe'
         ]);
@@ -837,6 +838,8 @@ class ReceiveEmailTest extends TestCase
         )->assertExitCode(0);
 
         $this->assertDatabaseHas('aliases', [
+            'aliasable_id' => $additionalUsername->id,
+            'aliasable_type' => 'App\AdditionalUsername',
             'email' => 'ebay@janedoe.anonaddy.com',
             'local_part' => 'ebay',
             'domain' => 'janedoe.anonaddy.com',
@@ -1012,7 +1015,8 @@ class ReceiveEmailTest extends TestCase
         )->assertExitCode(0);
 
         $this->assertDatabaseHas('aliases', [
-            'domain_id' => $domain->id,
+            'aliasable_id' => $domain->id,
+            'aliasable_type' => 'App\Domain',
             'email' => 'ebay@example.com',
             'local_part' => 'ebay',
             'domain' => 'example.com',
@@ -1030,4 +1034,55 @@ class ReceiveEmailTest extends TestCase
             return $mail->hasTo($recipient->email);
         });
     }
+
+    /** @test */
+    public function it_can_forward_email_for_additional_username_with_default_recipient()
+    {
+        Mail::fake();
+
+        Mail::assertNothingSent();
+
+        $recipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $additionalUsername = factory(AdditionalUsername::class)->create([
+            'user_id' => $this->user->id,
+            'default_recipient_id' => $recipient->id,
+            'username' => 'janedoe'
+        ]);
+
+        $this->artisan(
+            'anonaddy:receive-email',
+            [
+                'file' => base_path('tests/emails/email_additional_username.eml'),
+                '--sender' => 'will@anonaddy.com',
+                '--recipient' => ['ebay@janedoe.anonaddy.com'],
+                '--local_part' => ['ebay'],
+                '--extension' => [''],
+                '--domain' => ['janedoe.anonaddy.com'],
+                '--size' => '559'
+            ]
+        )->assertExitCode(0);
+
+        $this->assertDatabaseHas('aliases', [
+            'aliasable_id' => $additionalUsername->id,
+            'aliasable_type' => 'App\AdditionalUsername',
+            'email' => 'ebay@janedoe.anonaddy.com',
+            'local_part' => 'ebay',
+            'domain' => 'janedoe.anonaddy.com',
+            'emails_forwarded' => 1,
+            'emails_blocked' => 0
+        ]);
+        $this->assertDatabaseHas('users', [
+            'id' => $this->user->id,
+            'username' => 'johndoe',
+            'bandwidth' => '559'
+        ]);
+        $this->assertEquals(1, $this->user->aliases()->count());
+
+        Mail::assertQueued(ForwardEmail::class, function ($mail) use ($recipient) {
+            return $mail->hasTo($recipient->email);
+        });
+    }
 }

+ 17 - 2
tests/Feature/SettingsTest.php

@@ -176,13 +176,20 @@ class SettingsTest extends TestCase
 
         $aliasWithCustomDomain = factory(Alias::class)->create([
             'user_id' => $this->user->id,
-            'domain_id' => $domain->id
+            'aliasable_id' => $domain->id,
+            'aliasable_type' => 'App\Domain'
         ]);
 
         $additionalUsername = factory(AdditionalUsername::class)->create([
             'user_id' => $this->user->id
         ]);
 
+        $aliasWithAdditionalUsername = factory(Alias::class)->create([
+            'user_id' => $this->user->id,
+            'aliasable_id' => $additionalUsername->id,
+            'aliasable_type' => 'App\AdditionaUsername'
+        ]);
+
         $response = $this->post('/settings/account', [
             'current_password_delete' => 'mypassword'
         ]);
@@ -207,7 +214,15 @@ class SettingsTest extends TestCase
         $this->assertDatabaseMissing('aliases', [
             'id' => $aliasWithCustomDomain->id,
             'user_id' => $this->user->id,
-            'domain_id' => $domain->id
+            'aliasable_id' => $domain->id,
+            'aliasable_type' => 'App\Domain'
+        ]);
+
+        $this->assertDatabaseMissing('aliases', [
+            'id' => $aliasWithAdditionalUsername->id,
+            'user_id' => $this->user->id,
+            'aliasable_id' => $additionalUsername->id,
+            'aliasable_type' => 'App\AdditionalUsername'
         ]);
 
         $this->assertDatabaseMissing('recipients', [