diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php
index e2cd144..9c7571e 100644
--- a/flexiapi/app/Http/Controllers/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php
@@ -168,7 +168,7 @@ class AccountController extends Controller
return redirect()->route('admin.account.index');
}
- public function attachContactsList(Request $request, int $id)
+ public function contactsListAdd(Request $request, int $id)
{
$request->validate([
'contacts_list_id' => 'required|exists:contacts_lists,id'
@@ -181,7 +181,7 @@ class AccountController extends Controller
return redirect()->route('admin.account.edit', $id);
}
- public function detachContactsList(Request $request, int $id)
+ public function contactsListRemove(Request $request, int $id)
{
$account = Account::findOrFail($id);
$account->contactsLists()->detach([$request->get('contacts_list_id')]);
diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
index 0ee61ee..2f86d2e 100644
--- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
@@ -29,6 +29,7 @@ use App\Account;
use App\AccountTombstone;
use App\AccountType;
use App\ActivationExpiration;
+use App\ContactsList;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
use App\Http\Requests\CreateAccountRequest;
use App\Http\Requests\UpdateAccountRequest;
@@ -205,6 +206,26 @@ class AccountController extends Controller
return Account::findOrFail($id)->types()->detach($typeId);
}
+ public function contactsListAdd(int $id, int $contactsListId)
+ {
+ if (Account::findOrFail($id)->contactsLists()->pluck('id')->contains($contactsListId)) {
+ abort(403);
+ }
+
+ if (ContactsList::findOrFail($contactsListId)) {
+ return Account::findOrFail($id)->contactsLists()->attach($contactsListId);
+ }
+ }
+
+ public function contactsListRemove(int $id, int $contactsListId)
+ {
+ if (!Account::findOrFail($id)->contactsLists()->pluck('id')->contains($contactsListId)) {
+ abort(403);
+ }
+
+ return Account::findOrFail($id)->contactsLists()->detach($contactsListId);
+ }
+
public function recoverByEmail(int $id)
{
$account = Account::findOrFail($id);
diff --git a/flexiapi/app/Http/Controllers/Api/Admin/ContactsListController.php b/flexiapi/app/Http/Controllers/Api/Admin/ContactsListController.php
new file mode 100644
index 0000000..9f98a47
--- /dev/null
+++ b/flexiapi/app/Http/Controllers/Api/Admin/ContactsListController.php
@@ -0,0 +1,79 @@
+firstOrFail();
+ }
+
+ public function store(Request $request)
+ {
+ $request->validate([
+ 'title' => ['required'],
+ 'description' => ['required']
+ ]);
+
+ $contactsList = new ContactsList;
+ $contactsList->title = $request->get('title');
+ $contactsList->description = $request->get('description');
+ $contactsList->save();
+
+ return $contactsList;
+ }
+
+ public function update(Request $request, int $contactsListId)
+ {
+ $request->validate([
+ 'title' => ['required'],
+ 'description' => ['required']
+ ]);
+
+ $contactsList = ContactsList::where('id', $contactsListId)
+ ->firstOrFail();
+ $contactsList->title = $request->get('title');
+ $contactsList->description = $request->get('description');
+ $contactsList->save();
+
+ return $contactsList;
+ }
+
+ public function destroy(int $contactsListId)
+ {
+ return ContactsList::where('id', $contactsListId)
+ ->delete();
+ }
+
+ public function contactAdd(int $id, int $contactId)
+ {
+ if (ContactsList::findOrFail($id)->contacts()->pluck('id')->contains($contactId)) {
+ abort(403);
+ }
+
+ if (Account::findOrFail($contactId)) {
+ return ContactsList::findOrFail($id)->contacts()->attach($contactId);
+ }
+ }
+
+ public function contactRemove(int $id, int $contactId)
+ {
+ if (!ContactsList::findOrFail($id)->contacts()->pluck('id')->contains($contactId)) {
+ abort(403);
+ }
+
+ return ContactsList::findOrFail($id)->contacts()->detach($contactId);
+ }
+}
diff --git a/flexiapi/composer.lock b/flexiapi/composer.lock
index ff0f160..01f8118 100644
--- a/flexiapi/composer.lock
+++ b/flexiapi/composer.lock
@@ -399,16 +399,16 @@
},
{
"name": "doctrine/dbal",
- "version": "3.6.3",
+ "version": "3.6.4",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "9a747d29e7e6b39509b8f1847e37a23a0163ea6a"
+ "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/9a747d29e7e6b39509b8f1847e37a23a0163ea6a",
- "reference": "9a747d29e7e6b39509b8f1847e37a23a0163ea6a",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f",
+ "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f",
"shasum": ""
},
"require": {
@@ -491,7 +491,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/3.6.3"
+ "source": "https://github.com/doctrine/dbal/tree/3.6.4"
},
"funding": [
{
@@ -507,7 +507,7 @@
"type": "tidelift"
}
],
- "time": "2023-06-01T05:46:46+00:00"
+ "time": "2023-06-15T07:40:12+00:00"
},
{
"name": "doctrine/deprecations",
@@ -1729,16 +1729,16 @@
},
{
"name": "laravel/framework",
- "version": "v9.52.9",
+ "version": "v9.52.10",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "c512ece7b1ee393eac5893f37cb2b029a5413b97"
+ "reference": "858add225ce88a76c43aec0e7866288321ee0ee9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/c512ece7b1ee393eac5893f37cb2b029a5413b97",
- "reference": "c512ece7b1ee393eac5893f37cb2b029a5413b97",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/858add225ce88a76c43aec0e7866288321ee0ee9",
+ "reference": "858add225ce88a76c43aec0e7866288321ee0ee9",
"shasum": ""
},
"require": {
@@ -1923,7 +1923,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2023-06-08T20:06:23+00:00"
+ "time": "2023-06-27T13:25:54+00:00"
},
{
"name": "laravel/serializable-closure",
@@ -2615,16 +2615,16 @@
},
{
"name": "nesbot/carbon",
- "version": "2.67.0",
+ "version": "2.68.1",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
- "reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8"
+ "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/c1001b3bc75039b07f38a79db5237c4c529e04c8",
- "reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4f991ed2a403c85efbc4f23eb4030063fdbe01da",
+ "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da",
"shasum": ""
},
"require": {
@@ -2713,7 +2713,7 @@
"type": "tidelift"
}
],
- "time": "2023-05-25T22:09:47+00:00"
+ "time": "2023-06-20T18:29:04+00:00"
},
{
"name": "nette/schema",
@@ -7484,16 +7484,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v4.15.5",
+ "version": "v4.16.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
+ "reference": "19526a33fb561ef417e822e85f08a00db4059c17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
- "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
+ "reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": ""
},
"require": {
@@ -7534,9 +7534,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
},
- "time": "2023-05-19T20:20:00+00:00"
+ "time": "2023-06-25T14:52:30+00:00"
},
{
"name": "nunomaduro/collision",
diff --git a/flexiapi/public/css/far.css b/flexiapi/public/css/far.css
index 99eaa14..2e21569 100644
--- a/flexiapi/public/css/far.css
+++ b/flexiapi/public/css/far.css
@@ -88,6 +88,14 @@ body {
--danger-7: rgba(158, 53, 72, 1);
--danger-8: rgba(127, 37, 61, 1);
--danger-9: rgba(104, 26, 54, 1);
+
+ --color-orange: rgba(254, 94, 0, 1);
+ --color-grey: rgba(108, 122, 135, 1);
+ --color-green: rgba(79, 174, 128, 1);
+ --color-blue: rgba(9, 197, 244, 1);
+ --color-yellow: rgba(255, 220, 46, 1);
+ --color-pink: rgba(255, 94, 102, 1);
+ --color-purple: rgba(151, 71, 255, 1);
}
body.show_menu {
@@ -468,6 +476,7 @@ h3 {
font-size: 1.75rem;
color: var(--second-6);
padding: 0.5rem 0;
+ margin-top: 1.25rem;
}
h4 {
@@ -488,6 +497,18 @@ h4 {
padding: 0.25rem 1.25rem;
}
+.badge.badge-info {
+ background-color: var(--color-blue);
+}
+
+.badge.badge-success {
+ background-color: var(--color-green);
+}
+
+.badge.badge-warning {
+ background-color: var(--color-yellow);
+}
+
/** Table **/
table {
diff --git a/flexiapi/public/scripts/utils.js b/flexiapi/public/scripts/utils.js
new file mode 100644
index 0000000..8210295
--- /dev/null
+++ b/flexiapi/public/scripts/utils.js
@@ -0,0 +1,111 @@
+/**
+ * @brief Set object in localStorage
+ * @param key string
+ * @param value the object
+ */
+Storage.prototype.setObject = function (key, value) {
+ this.setItem(key, JSON.stringify(value));
+};
+
+/**
+ * @brief Get object in localStorage
+ * @param key
+ */
+Storage.prototype.getObject = function (key) {
+ return JSON.parse(this.getItem(key));
+};
+
+var Utils = {
+ toggleAll: function (checkbox) {
+ checkbox.closest('table').querySelectorAll('tbody input[type=checkbox]').forEach(element => {
+ element.checked = checkbox.checked;
+ element.dispatchEvent(new Event('change'));
+ });
+ },
+
+ getStorageList: function (key) {
+ var list = sessionStorage.getObject('list.' + key);
+
+ if (list == null) {
+ list = [];
+ }
+
+ return list;
+ },
+
+ addToStorageList: function (key, id) {
+ var list = Utils.getStorageList(key);
+
+ if (!list.includes(id)) {
+ list.push(id);
+ }
+
+ sessionStorage.setObject('list.' + key, list);
+ },
+
+ removeFromStorageList: function(key, id) {
+ var list = Utils.getStorageList(key);
+
+ list.splice(list.indexOf(id), 1);
+
+ sessionStorage.setObject('list.' + key, list);
+ },
+
+ existsInStorageList: function(key, id) {
+ var list = Utils.getStorageList(key);
+ return (list && list.includes(id));
+ },
+
+ clearStorageList: function (key) {
+ sessionStorage.setObject('list.' + key, []);
+ },
+
+ /** List toggle */
+}
+
+var ListToggle = {
+ init: function() {
+ document.querySelectorAll('input[type=checkbox].list_toggle').forEach(checkbox => {
+ checkbox.checked = Utils.existsInStorageList(checkbox.dataset.listId, checkbox.dataset.id);
+
+ checkbox.addEventListener('change', e => {
+ if (checkbox.checked) {
+ Utils.addToStorageList(checkbox.dataset.listId, checkbox.dataset.id);
+ } else {
+ Utils.removeFromStorageList(checkbox.dataset.listId, checkbox.dataset.id);
+ }
+
+ ListToggle.refreshFormList();
+ ListToggle.refreshCounters();
+ })
+ });
+
+ ListToggle.refreshFormList();
+ ListToggle.refreshCounters();
+ },
+
+ refreshFormList: function() {
+ document.querySelectorAll('select.list_toggle').forEach(select => {
+ select.innerHTML = '';
+ select.multiple = true;
+
+ Utils.getStorageList(select.dataset.listId).forEach(id => {
+ const option = document.createElement("option");
+ option.value = id;
+ option.text = id;
+ option.selected = true;
+ select.add(option, null);
+ });
+ });
+ },
+
+ refreshCounters: function() {
+ document.querySelectorAll('span.list_toggle').forEach(counter => {
+ counter.innerHTML = Utils.getStorageList(counter.dataset.listId).length;
+ });
+ }
+}
+
+document.addEventListener("DOMContentLoaded", function(event) {
+ ListToggle.init();
+});
\ No newline at end of file
diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php
index 2bb5963..fe948e9 100644
--- a/flexiapi/resources/views/api/documentation_markdown.blade.php
+++ b/flexiapi/resources/views/api/documentation_markdown.blade.php
@@ -428,6 +428,46 @@ JSON parameters:
Admin
Delete an account related action.
+## Contacts Lists
+
+### `GET /contacts_lists`
+Admin
+Show all the contacts lists.
+
+### `GET /contacts_lists/{id}`
+Admin
+Show a contacts list.
+
+### `POST /contacts_lists`
+Admin
+Create a contacts list.
+
+JSON parameters:
+
+* `title` required
+* `description` required
+
+### `PUT /contacts_lists/{id}`
+Admin
+Update a contacts list.
+
+JSON parameters:
+
+* `title` required
+* `description` required
+
+### `DELETE /contacts_lists/{id}`
+Admin
+Delete a contacts list.
+
+### `POST /accounts/{id}/contacts_lists/{contacts_list_id}`
+Admin
+Add a contacts list to the account.
+
+### `DELETE /accounts/{id}/contacts_lists/{contacts_list_id}`
+Admin
+Remove a contacts list from the account.
+
## Account Types
### `GET /account_types`
@@ -464,7 +504,7 @@ Add a type to the account.
### `DELETE /accounts/{id}/contacts/{type_id}`
Admin
-Remove a a type from the account.
+Remove a type from the account.
## Messages
diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php
index 0413d29..25c51bb 100644
--- a/flexiapi/routes/api.php
+++ b/flexiapi/routes/api.php
@@ -17,6 +17,11 @@
along with this program. If not, see .
*/
+use App\Http\Controllers\Api\Admin\AccountActionController;
+use App\Http\Controllers\Api\Admin\AccountContactController;
+use App\Http\Controllers\Api\Admin\AccountController as AdminAccountController;
+use App\Http\Controllers\Api\Admin\AccountTypeController;
+use App\Http\Controllers\Api\Admin\ContactsListController;
use Illuminate\Http\Request;
Route::get('/', 'Api\ApiController@documentation')->name('api');
@@ -81,41 +86,43 @@ Route::group(['middleware' => ['auth.digest_or_key']], function () {
Route::post('account_creation_tokens', 'Api\Admin\AccountCreationTokenController@create');
// Accounts
- Route::get('accounts/{id}/activate', 'Api\Admin\AccountController@activate');
- Route::get('accounts/{id}/deactivate', 'Api\Admin\AccountController@deactivate');
- Route::get('accounts/{id}/provision', 'Api\Admin\AccountController@provision');
+ Route::prefix('accounts')->controller(AdminAccountController::class)->group(function () {
+ Route::get('{id}/activate', 'activate');
+ Route::get('{id}/deactivate', 'deactivate');
+ Route::get('{id}/provision', 'provision');
- Route::post('accounts/{id}/recover-by-email', 'Api\Admin\AccountController@recoverByEmail');
+ Route::post('{id}/recover-by-email', 'recoverByEmail');
- Route::post('accounts', 'Api\Admin\AccountController@store');
- Route::put('accounts/{id}', 'Api\Admin\AccountController@update');
- Route::get('accounts', 'Api\Admin\AccountController@index');
- Route::get('accounts/{id}', 'Api\Admin\AccountController@show');
- Route::delete('accounts/{id}', 'Api\Admin\AccountController@destroy');
- Route::get('accounts/{sip}/search', 'Api\Admin\AccountController@search');
- Route::get('accounts/{email}/search-by-email', 'Api\Admin\AccountController@searchByEmail');
+ Route::post('/', 'store');
+ Route::put('{id}', 'update');
+ Route::get('/', 'index');
+ Route::get('{id}', 'show');
+ Route::delete('{id}', 'destroy');
+ Route::get('{sip}/search', 'search');
+ Route::get('{email}/search-by-email', 'searchByEmail');
- // Account actions
- Route::get('accounts/{id}/actions', 'Api\Admin\AccountActionController@index');
- Route::get('accounts/{id}/actions/{action_id}', 'Api\Admin\AccountActionController@show');
- Route::post('accounts/{id}/actions', 'Api\Admin\AccountActionController@store');
- Route::delete('accounts/{id}/actions/{action_id}', 'Api\Admin\AccountActionController@destroy');
- Route::put('accounts/{id}/actions/{action_id}', 'Api\Admin\AccountActionController@update');
+ Route::post('{id}/types/{type_id}', 'typeAdd');
+ Route::delete('{id}/types/{type_id}', 'typeRemove');
+
+ Route::post('{id}/contacts_lists/{contacts_list_id}', 'contactsListAdd');
+ Route::delete('{id}/contacts_lists/{contacts_list_id}', 'contactsListRemove');
+ });
// Account contacts
- Route::get('accounts/{id}/contacts', 'Api\Admin\AccountContactController@index');
- Route::get('accounts/{id}/contacts/{contact_id}', 'Api\Admin\AccountContactController@show');
- Route::post('accounts/{id}/contacts/{contact_id}', 'Api\Admin\AccountContactController@add');
- Route::delete('accounts/{id}/contacts/{contact_id}', 'Api\Admin\AccountContactController@remove');
+ Route::prefix('accounts/{id}/contacts')->controller(AccountContactController::class)->group(function () {
+ Route::get('/', 'index');
+ Route::get('{contact_id}', 'show');
+ Route::post('{contact_id}', 'add');
+ Route::delete('{contact_id}', 'remove');
+ });
- // Account types
- Route::get('account_types', 'Api\Admin\AccountTypeController@index');
- Route::get('account_types/{id}', 'Api\Admin\AccountTypeController@show');
- Route::post('account_types', 'Api\Admin\AccountTypeController@store');
- Route::delete('account_types/{id}', 'Api\Admin\AccountTypeController@destroy');
- Route::put('account_types/{id}', 'Api\Admin\AccountTypeController@update');
+ Route::apiResource('accounts/{id}/actions', AccountActionController::class);
+ Route::apiResource('account_types', AccountTypeController::class);
- Route::post('accounts/{id}/types/{type_id}', 'Api\Admin\AccountController@typeAdd');
- Route::delete('accounts/{id}/types/{type_id}', 'Api\Admin\AccountController@typeRemove');
+ Route::apiResource('contacts_lists', ContactsListController::class);
+ Route::prefix('contacts_lists')->controller(ContactsListController::class)->group(function () {
+ Route::post('{id}/contacts/{contacts_id}', 'contactAdd');
+ Route::delete('{id}/contacts/{contacts_id}', 'contactRemove');
+ });
});
});
diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php
index b9db97a..2dbeabc 100644
--- a/flexiapi/routes/web.php
+++ b/flexiapi/routes/web.php
@@ -129,7 +129,6 @@ if (config('app.web_panel')) {
Route::get('auth_tokens/qrcode/{token}', 'Account\AuthTokenController@qrcode')->name('auth_tokens.qrcode');
Route::get('auth_tokens/auth/{token}', 'Account\AuthTokenController@auth')->name('auth_tokens.auth');
- //Route::get('admin/accounts/{acc}/cl/{$cl}/detach', 'Admin\AccountController@detachContactsList')->name('admin.account.contacts_lists.detach2');
Route::name('admin.')->prefix('admin')->middleware(['auth.admin'])->group(function () {
// Statistics
@@ -155,8 +154,8 @@ if (config('app.web_panel')) {
Route::get('/', 'index')->name('index');
Route::post('search', 'search')->name('search');
- Route::get('{account_id}/contacts_lists/detach', 'detachContactsList')->name('contacts_lists.detach');
- Route::post('{account_id}/contacts_lists', 'attachContactsList')->name('contacts_lists.attach');
+ Route::get('{account_id}/contacts_lists/detach', 'contactsListRemove')->name('contacts_lists.detach');
+ Route::post('{account_id}/contacts_lists', 'contactsListAdd')->name('contacts_lists.attach');
});
Route::name('type.')->prefix('types')->controller(AccountTypeController::class)->group(function () {
diff --git a/flexiapi/tests/Feature/ApiAccountContactsTest.php b/flexiapi/tests/Feature/ApiAccountContactsTest.php
index b0126f5..9e8fd86 100644
--- a/flexiapi/tests/Feature/ApiAccountContactsTest.php
+++ b/flexiapi/tests/Feature/ApiAccountContactsTest.php
@@ -32,6 +32,7 @@ class ApiAccountContactTest extends TestCase
use RefreshDatabase;
protected $route = '/api/accounts';
+ protected $contactsListsRoute = '/api/contacts_lists';
protected $method = 'POST';
public function testCreate()
@@ -155,11 +156,50 @@ class ApiAccountContactTest extends TestCase
*
*/
- // This will need to be done through the API
- $contactList = ContactsList::factory()->create();
- $contactList->contacts()->attach([$password1->account->id, $password2->account->id, $password3->account->id]);
+ // Create the Contacts list
+ $contactsListsTitle = 'Contacts List title';
- $admin->account->contactsLists()->attach([$contactList->id]);
+ $this->keyAuthenticated($admin->account)
+ ->json($this->method, $this->contactsListsRoute, [
+ 'title' => $contactsListsTitle,
+ 'description' => 'Description'
+ ])
+ ->assertStatus(201);
+
+ $this->assertDatabaseHas('contacts_lists', [
+ 'title' => $contactsListsTitle
+ ]);
+
+ // Attach the Contacts and the Contacts List
+
+ $contactsList = ContactsList::first();
+
+ $this->keyAuthenticated($admin->account)
+ ->post($this->contactsListsRoute . '/' . $contactsList->id . '/contacts/' . $password1->account->id)
+ ->assertStatus(200);
+
+ $this->keyAuthenticated($admin->account)
+ ->post($this->contactsListsRoute . '/' . $contactsList->id . '/contacts/' . $password2->account->id)
+ ->assertStatus(200);
+
+ $this->keyAuthenticated($admin->account)
+ ->post($this->contactsListsRoute . '/' . $contactsList->id . '/contacts/' . $password3->account->id)
+ ->assertStatus(200);
+
+ $this->keyAuthenticated($admin->account)
+ ->post($this->contactsListsRoute . '/' . $contactsList->id . '/contacts/1234')
+ ->assertStatus(404);
+
+
+ $this->keyAuthenticated($admin->account)
+ ->post($this->route . '/' . $admin->account->id . '/contacts_lists/' . $contactsList->id)
+ ->assertStatus(200);
+
+ $this->keyAuthenticated($admin->account)
+ ->post($this->route . '/' . $admin->account->id . '/contacts_lists/' . $contactsList->id)
+ ->assertStatus(403);
+
+ // Get the contacts and vcards
$this->keyAuthenticated($admin->account)
->get($this->route . '/me/contacts')