From c1e355a829d4eb864ff21f8ff121799e34b7fea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Tue, 4 Jul 2023 14:35:21 +0000 Subject: [PATCH] Add ContactList feature, complete the lists and attach them to accounts --- flexiapi/app/Account.php | 5 + flexiapi/app/ContactsList.php | 18 ++ flexiapi/app/Helpers/Utils.php | 21 ++ .../Account/ContactVcardController.php | 5 +- .../Admin/AccountAccountTypeController.php | 4 +- .../Admin/AccountActionController.php | 6 +- .../Admin/AccountContactController.php | 4 +- .../Controllers/Admin/AccountController.php | 45 ++-- .../Admin/ContactsListContactController.php | 82 ++++++++ .../Admin/ContactsListController.php | 101 +++++++++ .../Api/Account/ContactController.php | 11 +- .../factories/ContactsListFactory.php | 16 ++ ..._27_134132_create_contacts_lists_table.php | 51 +++++ flexiapi/public/css/far.css | 193 +++++++++++++++--- flexiapi/public/css/form.css | 55 ++++- .../views/account/dashboard.blade.php | 139 +++++++------ .../views/account/devices/index.blade.php | 6 +- .../account/account_type/create.blade.php | 6 +- .../account/action/create_edit.blade.php | 2 +- .../admin/account/action/delete.blade.php | 2 +- .../admin/account/contact/create.blade.php | 40 ++-- .../admin/account/contact/delete.blade.php | 2 +- .../views/admin/account/create_edit.blade.php | 183 +++++++++++++++-- .../views/admin/account/delete.blade.php | 22 +- .../views/admin/account/index.blade.php | 18 +- .../views/admin/account/show.blade.php | 126 ------------ .../views/admin/account/type/index.blade.php | 6 +- .../contacts_list/contacts/add.blade.php | 76 +++++++ .../admin/contacts_list/create_edit.blade.php | 92 +++++++++ .../admin/contacts_list/delete.blade.php | 22 ++ .../views/admin/contacts_list/index.blade.php | 49 +++++ .../resources/views/layouts/main.blade.php | 1 + .../resources/views/parts/sidebar.blade.php | 1 + flexiapi/routes/web.php | 78 ++++--- .../tests/Feature/ApiAccountContactsTest.php | 121 +++++++---- 35 files changed, 1200 insertions(+), 409 deletions(-) create mode 100644 flexiapi/app/ContactsList.php create mode 100644 flexiapi/app/Http/Controllers/Admin/ContactsListContactController.php create mode 100644 flexiapi/app/Http/Controllers/Admin/ContactsListController.php create mode 100644 flexiapi/database/factories/ContactsListFactory.php create mode 100644 flexiapi/database/migrations/2023_06_27_134132_create_contacts_lists_table.php delete mode 100644 flexiapi/resources/views/admin/account/show.blade.php create mode 100644 flexiapi/resources/views/admin/contacts_list/contacts/add.blade.php create mode 100644 flexiapi/resources/views/admin/contacts_list/create_edit.blade.php create mode 100644 flexiapi/resources/views/admin/contacts_list/delete.blade.php create mode 100644 flexiapi/resources/views/admin/contacts_list/index.blade.php diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index eb58f47..e28687b 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -132,6 +132,11 @@ class Account extends Authenticatable return $this->belongsToMany(Account::class, 'contacts', 'account_id', 'contact_id'); } + public function contactsLists() + { + return $this->belongsToMany(ContactsList::class, 'account_contacts_list', 'account_id', 'contacts_list_id'); + } + public function nonces() { return $this->hasMany(DigestNonce::class); diff --git a/flexiapi/app/ContactsList.php b/flexiapi/app/ContactsList.php new file mode 100644 index 0000000..a1f3c61 --- /dev/null +++ b/flexiapi/app/ContactsList.php @@ -0,0 +1,18 @@ +belongsToMany(Account::class, 'contacts_list_contact', 'contacts_list_id', 'contact_id'); + } +} diff --git a/flexiapi/app/Helpers/Utils.php b/flexiapi/app/Helpers/Utils.php index d4ce249..4b662bc 100644 --- a/flexiapi/app/Helpers/Utils.php +++ b/flexiapi/app/Helpers/Utils.php @@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Schema; use League\CommonMark\CommonMarkConverter; use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension; use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension; +use Illuminate\Support\Facades\DB; function generateNonce(): string { @@ -130,3 +131,23 @@ function resolveDomain(Request $request): string ? $request->get('domain') : config('app.sip_domain'); } + +function resolveUserContacts(Request $request) +{ + $selected = ['id', 'username', 'domain', 'activated', 'dtmf_protocol']; + + return Account::whereIn('id', function ($query) use ($request) { + $query->select('contact_id') + ->from('contacts') + ->where('account_id', $request->user()->id) + ->union( + DB::table('contacts_list_contact') + ->select('contact_id') + ->whereIn('contacts_list_id', function ($query) use ($request) { + $query->select('contacts_list_id') + ->from('account_contacts_list') + ->where('account_id', $request->user()->id); + }) + ); + })->select($selected); +} diff --git a/flexiapi/app/Http/Controllers/Account/ContactVcardController.php b/flexiapi/app/Http/Controllers/Account/ContactVcardController.php index 1051f85..e91130b 100644 --- a/flexiapi/app/Http/Controllers/Account/ContactVcardController.php +++ b/flexiapi/app/Http/Controllers/Account/ContactVcardController.php @@ -28,7 +28,7 @@ class ContactVcardController extends Controller public function index(Request $request) { return response( - $request->user()->contacts->map(function ($contact) { + resolveUserContacts($request)->get()->map(function ($contact) { return $contact->toVcard4(); })->implode("\n") ); @@ -36,8 +36,7 @@ class ContactVcardController extends Controller public function show(Request $request, string $sip) { - return $request->user() - ->contacts() + return resolveUserContacts($request) ->sip($sip) ->firstOrFail() ->toVcard4(); diff --git a/flexiapi/app/Http/Controllers/Admin/AccountAccountTypeController.php b/flexiapi/app/Http/Controllers/Admin/AccountAccountTypeController.php index d64d955..b3c9982 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountAccountTypeController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountAccountTypeController.php @@ -56,7 +56,7 @@ class AccountAccountTypeController extends Controller $request->session()->flash('success', 'Type successfully added'); Log::channel('events')->info('Web Admin: Account type attached', ['id' => $account->identifier, 'type_id' => $request->get('account_type_id')]); - return redirect()->route('admin.account.show', $account); + return redirect()->route('admin.account.edit', $account); } public function destroy(Request $request, int $id, int $typeId) @@ -68,6 +68,6 @@ class AccountAccountTypeController extends Controller $request->session()->flash('success', 'Type successfully removed'); Log::channel('events')->info('Web Admin: Account type detached', ['id' => $account->identifier, 'type_id' => $request->get('account_type_id')]); - return redirect()->route('admin.account.show', $account); + return redirect()->route('admin.account.edit', $account); } } diff --git a/flexiapi/app/Http/Controllers/Admin/AccountActionController.php b/flexiapi/app/Http/Controllers/Admin/AccountActionController.php index de94fae..60eb88e 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountActionController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountActionController.php @@ -57,7 +57,7 @@ class AccountActionController extends Controller $request->session()->flash('success', 'Action successfully created'); Log::channel('events')->info('Web Admin: Account action created', ['id' => $account->identifier, 'action' => $accountAction->key]); - return redirect()->route('admin.account.show', $accountAction->account); + return redirect()->route('admin.account.edit', $accountAction->account); } public function edit(int $id, int $actionId) @@ -93,7 +93,7 @@ class AccountActionController extends Controller $request->session()->flash('success', 'Action successfully updated'); Log::channel('events')->info('Web Admin: Account action updated', ['id' => $account->identifier, 'action' => $accountAction->key]); - return redirect()->route('admin.account.show', $account); + return redirect()->route('admin.account.edit', $account); } public function delete(int $id, int $actionId) @@ -120,6 +120,6 @@ class AccountActionController extends Controller Log::channel('events')->info('Web Admin: Account action deleted', ['id' => $accountAction->account->identifier, 'action_id' => $accountAction->key]); - return redirect()->route('admin.account.show', $accountAction->account); + return redirect()->route('admin.account.edit', $accountAction->account); } } diff --git a/flexiapi/app/Http/Controllers/Admin/AccountContactController.php b/flexiapi/app/Http/Controllers/Admin/AccountContactController.php index 44a03d4..40c7638 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountContactController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountContactController.php @@ -54,7 +54,7 @@ class AccountContactController extends Controller Log::channel('events')->info('Web Admin: Account contact added', ['id' => $account->identifier, 'contact' => $contact->identifier]); - return redirect()->route('admin.account.show', $account); + return redirect()->route('admin.account.edit', $account); } public function delete(int $id, int $contactId) @@ -78,6 +78,6 @@ class AccountContactController extends Controller $request->session()->flash('success', 'Type successfully removed'); Log::channel('events')->info('Web Admin: Account contact removed', ['id' => $account->identifier, 'contact' => $contact->identifier]); - return redirect()->route('admin.account.show', $account); + return redirect()->route('admin.account.edit', $account); } } diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php index 0bd7b04..e2cd144 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php @@ -25,7 +25,7 @@ use Illuminate\Support\Facades\Log; use Carbon\Carbon; use App\Account; -use App\Admin; +use App\ContactsList; use App\ExternalAccount; use App\Http\Requests\CreateAccountRequest; use App\Http\Requests\UpdateAccountRequest; @@ -58,14 +58,6 @@ class AccountController extends Controller return redirect()->route('admin.account.index', $request->except('_token')); } - public function show(int $id) - { - return view('admin.account.show', [ - 'external_accounts_count' => ExternalAccount::where('used', false)->count(), - 'account' => Account::findOrFail($id) - ]); - } - public function create(Request $request) { return view('admin.account.create_edit', [ @@ -93,14 +85,20 @@ class AccountController extends Controller Log::channel('events')->info('Web Admin: Account created', ['id' => $account->identifier]); - return redirect()->route('admin.account.show', $account->id); + return redirect()->route('admin.account.edit', $account->id); } public function edit(int $id) { return view('admin.account.create_edit', [ 'account' => Account::findOrFail($id), - 'protocols' => [null => 'None'] + Account::$dtmfProtocols + 'protocols' => [null => 'None'] + Account::$dtmfProtocols, + 'external_accounts_count' => ExternalAccount::where('used', false)->count(), + 'contacts_lists' => ContactsList::whereNotIn('id', function ($query) use ($id) { + $query->select('contacts_list_id') + ->from('account_contacts_list') + ->where('account_id', $id); + })->get() ]); } @@ -125,7 +123,7 @@ class AccountController extends Controller Log::channel('events')->info('Web Admin: Account updated', ['id' => $account->identifier]); - return redirect()->route('admin.account.show', $id); + return redirect()->route('admin.account.edit', $id); } public function attachExternalAccount(int $id) @@ -146,7 +144,7 @@ class AccountController extends Controller Log::channel('events')->info('Web Admin: Account provisioned', ['id' => $account->identifier]); - return redirect()->back(); + return redirect()->back()->withFragment('provisioning'); } public function delete(int $id) @@ -169,4 +167,25 @@ class AccountController extends Controller return redirect()->route('admin.account.index'); } + + public function attachContactsList(Request $request, int $id) + { + $request->validate([ + 'contacts_list_id' => 'required|exists:contacts_lists,id' + ]); + + $account = Account::findOrFail($id); + $account->contactsLists()->detach([$request->get('contacts_list_id')]); + $account->contactsLists()->attach([$request->get('contacts_list_id')]); + + return redirect()->route('admin.account.edit', $id); + } + + public function detachContactsList(Request $request, int $id) + { + $account = Account::findOrFail($id); + $account->contactsLists()->detach([$request->get('contacts_list_id')]); + + return redirect()->route('admin.account.edit', $id); + } } diff --git a/flexiapi/app/Http/Controllers/Admin/ContactsListContactController.php b/flexiapi/app/Http/Controllers/Admin/ContactsListContactController.php new file mode 100644 index 0000000..26c8190 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Admin/ContactsListContactController.php @@ -0,0 +1,82 @@ +. +*/ + +namespace App\Http\Controllers\Admin; + +use App\Account; +use App\ContactsList; +use App\Http\Controllers\Controller; +use Illuminate\Http\Request; + +class ContactsListContactController extends Controller +{ + public function add(Request $request, int $contactsListId) + { + $accounts = Account::orderBy('updated_at', $request->get('updated_at_order', 'desc')) + ->with('externalAccount'); + + if ($request->has('search')) { + $accounts = $accounts->where('username', 'like', '%' . $request->get('search') . '%'); + } + + return view('admin.contacts_list.contacts.add', [ + 'contacts_list' => ContactsList::firstOrFail($contactsListId), + 'params' => [ + 'search' => $request->get('search'), + 'contacts_list_id' => $contactsListId, + 'updated_at_order' => $request->get('updated_at_order') == 'desc' ? 'asc' : 'desc' + ], + 'accounts' => $accounts->whereNotIn('id', function ($query) use ($contactsListId) { + $query->select('contact_id') + ->from('contacts_list_contact') + ->where('contacts_list_id', $contactsListId); + })->paginate(20)->appends($request->query()), + ]); + } + + public function search(Request $request, int $contactsListId) + { + return redirect()->route('admin.contacts_lists.contacts.add', ['contacts_list_id' => $contactsListId] + $request->except('_token')); + } + + public function store(Request $request, int $contactsListId) + { + $request->validate([ + 'contacts_ids' => 'required|exists:accounts,id' + ]); + + $contactsList = ContactsList::firstOrFail($contactsListId); + $contactsList->contacts()->detach($request->get('contacts_ids')); // Just in case + $contactsList->contacts()->attach($request->get('contacts_ids')); + + return redirect()->route('admin.contacts_lists.edit', $contactsList->id); + } + + public function destroy(Request $request, int $contactsListId) + { + $request->validate([ + 'contacts_ids' => 'required|exists:accounts,id' + ]); + + $contactsList = ContactsList::findOrFail($contactsListId); + $contactsList->contacts()->detach($request->get('contacts_ids')); + + return redirect()->route('admin.contacts_lists.edit', $contactsList->id); + } +} diff --git a/flexiapi/app/Http/Controllers/Admin/ContactsListController.php b/flexiapi/app/Http/Controllers/Admin/ContactsListController.php new file mode 100644 index 0000000..667fc38 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Admin/ContactsListController.php @@ -0,0 +1,101 @@ +. +*/ + +namespace App\Http\Controllers\Admin; + +use App\ContactsList; +use App\Http\Controllers\Controller; +use Illuminate\Http\Request; + +class ContactsListController extends Controller +{ + public function index(Request $request) + { + return view('admin.contacts_list.index', [ + 'contacts_lists' => ContactsList::orderBy('updated_at', $request->get('updated_at_order', 'desc')) + ->paginate(20) + ->appends($request->query()), + 'updated_at_order' => $request->get('updated_at_order') == 'desc' ? 'asc' : 'desc' + ]); + } + + public function show(int $id) + { + } + + public function create(Request $request) + { + return view('admin.contacts_list.create_edit', [ + 'contacts_list' => new ContactsList, + ]); + } + + 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 redirect()->route('admin.contacts_lists.edit', $contactsList->id); + } + + public function edit(int $id) + { + return view('admin.contacts_list.create_edit', [ + 'contacts_list' => ContactsList::findOrFail($id), + ]); + } + + public function update(Request $request, int $id) + { + $request->validate([ + 'title' => 'required', + 'description' => 'required' + ]); + + $contactsList = ContactsList::findOrFail($id); + $contactsList->title = $request->get('title'); + $contactsList->description = $request->get('description'); + $contactsList->save(); + + return redirect()->route('admin.contacts_lists.index'); + } + + + public function delete(int $id) + { + return view('admin.contacts_list.delete', [ + 'contacts_list' => ContactsList::findOrFail($id), + ]); + } + + public function destroy(Request $request) + { + $contactsList = ContactsList::findOrFail($request->get('contacts_lists_id')); + $contactsList->delete(); + + return redirect()->route('admin.contacts_lists.index'); + } +} diff --git a/flexiapi/app/Http/Controllers/Api/Account/ContactController.php b/flexiapi/app/Http/Controllers/Api/Account/ContactController.php index 50f717a..343c992 100644 --- a/flexiapi/app/Http/Controllers/Api/Account/ContactController.php +++ b/flexiapi/app/Http/Controllers/Api/Account/ContactController.php @@ -20,24 +20,17 @@ namespace App\Http\Controllers\Api\Account; use Illuminate\Http\Request; - use App\Http\Controllers\Controller; class ContactController extends Controller { - private $selected = ['id', 'username', 'domain', 'activated', 'dtmf_protocol']; - public function index(Request $request) { - return $request->user()->contacts()->select($this->selected)->get(); + return resolveUserContacts($request)->get(); } public function show(Request $request, string $sip) { - return $request->user() - ->contacts() - ->select($this->selected) - ->sip($sip) - ->firstOrFail(); + return resolveUserContacts($request)->sip($sip)->firstOrFail(); } } diff --git a/flexiapi/database/factories/ContactsListFactory.php b/flexiapi/database/factories/ContactsListFactory.php new file mode 100644 index 0000000..0fafdcc --- /dev/null +++ b/flexiapi/database/factories/ContactsListFactory.php @@ -0,0 +1,16 @@ + $this->faker->title, + 'description' => $this->faker->paragraph, + ]; + } +} diff --git a/flexiapi/database/migrations/2023_06_27_134132_create_contacts_lists_table.php b/flexiapi/database/migrations/2023_06_27_134132_create_contacts_lists_table.php new file mode 100644 index 0000000..33067d7 --- /dev/null +++ b/flexiapi/database/migrations/2023_06_27_134132_create_contacts_lists_table.php @@ -0,0 +1,51 @@ +id(); + $table->string('title'); + $table->text('description'); + $table->timestamps(); + }); + + Schema::create('account_contacts_list', function (Blueprint $table) { + $table->integer('account_id')->unsigned(); + $table->bigInteger('contacts_list_id')->unsigned(); + $table->foreign('account_id')->references('id') + ->on('accounts')->onDelete('cascade'); + $table->foreign('contacts_list_id')->references('id') + ->on('contacts_lists')->onDelete('cascade'); + $table->unique(['account_id', 'contacts_list_id']); + $table->timestamps(); + }); + + Schema::create('contacts_list_contact', function (Blueprint $table) { + $table->integer('contact_id')->unsigned(); + $table->bigInteger('contacts_list_id')->unsigned(); + $table->foreign('contact_id')->references('id') + ->on('accounts')->onDelete('cascade'); + $table->foreign('contacts_list_id')->references('id') + ->on('contacts_lists')->onDelete('cascade'); + $table->unique(['contact_id', 'contacts_list_id']); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('account_contacts_list'); + Schema::dropIfExists('contacts_list_contact'); + Schema::dropIfExists('contacts_lists'); + } +}; diff --git a/flexiapi/public/css/far.css b/flexiapi/public/css/far.css index cf6c1a6..99eaa14 100644 --- a/flexiapi/public/css/far.css +++ b/flexiapi/public/css/far.css @@ -96,11 +96,26 @@ body.show_menu { } p, -a { +a, +ul li, +pre { font-size: 1.5rem; color: var(--second-7); } +ul li { + margin-left: 2rem; + list-style-type: disc; +} + +ul li ul li { + list-style-type: circle; +} + +ul li ul li ul li { + list-style-type: square; +} + p { margin-bottom: 1rem; } @@ -114,7 +129,13 @@ p i { margin-right: 1rem; } +code { + color: var(--second-6); + font-family: monospace; +} + p>a:not(.btn), +table tr td a:hover, label>a { text-decoration: underline; color: var(--main-5); @@ -134,6 +155,20 @@ body.welcome content { max-width: 1024px; } +hr { + border-bottom: 1px solid var(--grey-3); + margin: 2rem 0; +} + +hr.clear { + clear: both; + border-bottom: none; +} + +a.permalink { + margin-left: 1rem; +} + /** Tabs **/ ul.tabs { @@ -148,6 +183,7 @@ ul.tabs li { font-weight: 800; color: var(--main-6); border-bottom: 2px solid transparent; + list-style-type: none; line-height: 4rem; font-size: 3rem; margin: 0 1rem; @@ -238,7 +274,7 @@ header nav a#logo span { header nav a#logo { position: absolute; left: calc(50% - 1.5rem); - top: 0.75rem; + top: 1.5rem; padding: 0; } } @@ -275,6 +311,25 @@ content section { box-sizing: border-box; } +content section header { + display: flex; + gap: 1rem; + align-items: center; + margin-bottom: 1rem; +} + +content section header p { + margin-bottom: 0; +} + +content section header form { + display: inline-block; +} + +content section header > *.oppose { + margin-left: auto; +} + content nav + section { min-width: calc(80% - 20rem); } @@ -309,6 +364,33 @@ content > nav a { margin-left: 2rem; padding-right: 2rem; position: relative; + white-space: nowrap; +} + +content > nav a.current { + background-color: white; + border-radius: 4rem; + color: var(--main-5); + box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2); +} + +content > nav a.current:after { + content: ''; + display: block; + width: 1rem; + height: 1rem; + background-color: white; + border-radius: 1rem; + position: absolute; + left: -2rem; + top: calc(50% - 0.5rem); + box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2); +} + +content > nav a i { + margin: 0 1rem; + margin-left: 2rem; + font-size: 2rem; } @media screen and (max-width: 800px) { @@ -326,37 +408,19 @@ content > nav a { transform: translateX(-100%); } + content > nav a { + margin-left: 0; + } + + content > nav a.current:after { + display: none; + } + body.show_menu content > nav { transform: translateX(0); } } -content > nav a.current { - background-color: white; - border-radius: 4rem; - color: var(--main-5); - box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2); -} - -content > nav a.current:before { - content: ''; - display: block; - width: 1rem; - height: 1rem; - background-color: white; - border-radius: 1rem; - position: absolute; - left: -2rem; - top: 50% - 0.5rem; - box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2); -} - -content > nav a i { - margin: 0 1rem; - margin-left: 2rem; - font-size: 2rem; -} - /** Footer **/ body.welcome::after { @@ -378,7 +442,6 @@ h1 { line-height: 4rem; font-weight: 800; color: var(--second-6); - margin-bottom: 1rem; display: flex; align-items: center; margin-right: 1rem; @@ -401,6 +464,18 @@ h2 i { margin-right: 1rem; } +h3 { + font-size: 1.75rem; + color: var(--second-6); + padding: 0.5rem 0; +} + +h4 { + font-size: 1.6rem; + color: var(--second-9); + padding: 0.5rem 0; +} + /** Badge **/ .badge { @@ -420,13 +495,25 @@ table { width: 100%; } +table tr td a { + display: block; +} + table tr td, -table tr th{ +table tr th { line-height: 4rem; - padding: 0 2rem; + padding: 0 1rem; font-size: 1.5rem; } +table tr td.line, +table tr th.line { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 0; +} + table tr th, table tr th a { text-transform: uppercase; @@ -449,6 +536,29 @@ table tr:nth-child(2n) { background-color: var(--grey-1); } +table tr.empty { + background-color: var(--grey-2); + text-align: center; + color: var(--second-4); +} + +table tr.empty td { + font-size: 2rem; + padding-bottom: 9rem; +} + +table tr.empty td:before { + content: '\e5c9'; + font-family: 'Material Icons'; + font-size: 8rem; + color: var(--second-4); + display: block; + text-align: center; + margin: 12rem; + margin-bottom: 1rem; + line-height: 8rem; +} + /* Display/hide */ .on_mobile { @@ -476,6 +586,21 @@ table tr:nth-child(2n) { } } +/** Chips **/ + +.chip { + display: inline-block; + background-color: var(--grey-1); + border: 1px solid var(--grey-2); + border-radius: 3rem; + line-height: 2.5rem; + padding: 0 1rem; +} + +.chip i { + margin: 0; +} + /** Pagination **/ ul.pagination { @@ -517,3 +642,9 @@ ul.pagination li:not(.disabled):not(.active) .page-link:hover { ul.pagination li:not(.disabled) .page-link:hover { border-color: var(--main-5); } + +/** List Toggle */ + +select.list_toggle { + display: none; +} \ No newline at end of file diff --git a/flexiapi/public/css/form.css b/flexiapi/public/css/form.css index 21107c6..42956d5 100644 --- a/flexiapi/public/css/form.css +++ b/flexiapi/public/css/form.css @@ -10,7 +10,7 @@ line-height: 2rem; padding: 1rem 2rem; color: white; - margin: 0 1rem; + white-space: nowrap; } .btn i { @@ -58,10 +58,24 @@ color: white; } +.btn.btn-tertiary { + background-color: var(--main-1); + border-color: transparent; + color: var(--main-5); +} + +.btn.btn-tertiary:hover { + background-color: var(--main-2); +} + +.btn.btn-tertiary:active { + background-color: var(--main-3); +} + form { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 1.5rem 0.5rem; + gap: 1.5rem 2.5rem; } form.inline { @@ -85,6 +99,12 @@ form h2 { grid-column: 1/-1; } +form .disabled { + opacity: 0.5; + pointer-events: none; + filter: blur(0.25rem); +} + @media screen and (max-width: 1024px) { form div { grid-column: 1/-1; @@ -110,6 +130,7 @@ form input[required]+label:after { } form input:not([type=checkbox]) ~ label, +form textarea ~ label, form select ~ label { position: absolute; top: 0; @@ -127,12 +148,14 @@ form div .btn.oppose { } form div input, +form div textarea, form div select { padding: 1rem 2rem; background-color: var(--grey-1); border-radius: 3rem; border: 1px solid var(--grey-2); font-size: 1.5rem; + resize: vertical; } form div select { @@ -158,19 +181,24 @@ form div.select:after { line-height: 4rem; } -form div input[disabled] { +form div input[disabled], +form div textarea[disabled] { border-color: var(--grey-4); color: var(--grey-4); background-color: var(--grey-2); pointer-events: none; } -form div input[type=checkbox] { - margin-right: 1rem; +input[type=checkbox] { accent-color: var(--main-5); } +form div input[type=checkbox] { + margin-right: 1rem; +} + form div input:not([type=checkbox]):not([type=radio]):not(.btn), +form div textarea, form div select { margin-top: 2.5rem; box-sizing: border-box; @@ -193,24 +221,33 @@ form div input:autofill { } form div input:hover, +form div textarea:hover, form div select:hover { border-color: var(--second-4); } -form div input:focus-visible, form div input:active { +form div input:focus-visible, +form div input:active, +form div textarea:focus-visible, +form div textarea:active { color: var(--main-5); border-color: var(--main-5); } -form div input:focus-visible+label, form div input:active+label { +form div input:focus-visible+label, +form div input:active+label, +form div textarea:focus-visible+label, +form div textarea:active+label { color: var(--main-5); } -form div input:invalid { +form div input:invalid, +form div textarea:invalid { border-color: var(--danger-6); color: var(--danger-5); } -form div input:invalid+label { +form div input:invalid+label, +form div textarea:invalid+label { color: var(--danger-5); } \ No newline at end of file diff --git a/flexiapi/resources/views/account/dashboard.blade.php b/flexiapi/resources/views/account/dashboard.blade.php index 0806227..b5339ac 100644 --- a/flexiapi/resources/views/account/dashboard.blade.php +++ b/flexiapi/resources/views/account/dashboard.blade.php @@ -1,94 +1,93 @@ @extends('layouts.main') @section('content') +
+

dashboard Dashboard

+
-

dashboard Dashboard

-

- email - @if (!empty($account->email)) - {{ $account->email }} - @else - No email yet +

+ email + @if (!empty($account->email)) + {{ $account->email }} + @else + No email yet + @endif + Change my current account email +

+ +

+ call + @if (!empty($account->phone)) + {{ $account->phone }} + @else + No phone yet + @endif + Change my current account phone +

+ @if (config('app.devices_management') == true) +

+ laptop + Manage my devices +

@endif - Change my current account email -

+

+ lock + + @if ($account->passwords()->count() > 0) + Change my password + @else + Set my password + @endif + +

+

-

- call - @if (!empty($account->phone)) - {{ $account->phone }} - @else - No phone yet +

+ delete + Delete my account +

+ +

person Account information

+ +

alternate_email SIP address: sip:{{ $account->identifier }}

+

person Username: {{ $account->username }}

+

dns Domain: {{ $account->domain }}

+ + @if (!empty(config('app.proxy_registrar_address'))) +

lan Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}

@endif - Change my current account phone -

-@if (config('app.devices_management') == true) -

- laptop - Manage my devices -

-@endif -

- lock - - @if ($account->passwords()->count() > 0) - Change my password - @else - Set my password + @if (!empty(config('app.transport_protocol_text'))) +

settings_ethernet Transport: {{ config('app.transport_protocol_text') }}

@endif - -

-

-

- delete - Delete my account -

+ + {!! Form::open(['route' => 'account.auth_tokens.create']) !!} + + {!! Form::close() !!}--> -

keyAPI Key

+

keyAPI Key

-

You can generate an API key and use it to request the different API endpoints, check the related API documentation to know how to use that key.

+

You can generate an API key and use it to request the different API endpoints, check + the related API documentation to know how to use that key.

-{!! Form::open(['route' => 'account.api_key.generate']) !!} + {!! Form::open(['route' => 'account.api_key.generate']) !!}
apiKey) - value="{{ $account->apiKey->key }}" - @endif - > + @if ($account->apiKey) value="{{ $account->apiKey->key }}" @endif>
-{!! Form::close() !!} + {!! Form::close() !!} -@include('parts.account_variables', ['account' => $account]) - -@endsection \ No newline at end of file + @include('parts.account_variables', ['account' => $account]) +@endsection diff --git a/flexiapi/resources/views/account/devices/index.blade.php b/flexiapi/resources/views/account/devices/index.blade.php index 9834e79..645e90a 100644 --- a/flexiapi/resources/views/account/devices/index.blade.php +++ b/flexiapi/resources/views/account/devices/index.blade.php @@ -6,11 +6,11 @@ @section('content') - +
- - + + diff --git a/flexiapi/resources/views/admin/account/account_type/create.blade.php b/flexiapi/resources/views/admin/account/account_type/create.blade.php index 1602c3f..c93daf5 100644 --- a/flexiapi/resources/views/admin/account/account_type/create.blade.php +++ b/flexiapi/resources/views/admin/account/account_type/create.blade.php @@ -5,7 +5,7 @@ Accounts - - -@endsection - @section('content') +

Add a Contact to the Account

-

Add a Contact to the Account

- -{!! Form::model($account, [ - 'route' => ['admin.account.contact.store', $account->id], - 'method' => 'post' -]) !!} -
-
- {!! Form::label('sip', 'Adresse SIP') !!} - {!! Form::text('sip', null, ['class' => 'form-control', 'placeholder' => 'username@server.com']); !!} +
+ @csrf + @method('post') +
+ +
-
- -{!! Form::submit('Add', ['class' => 'btn btn-success btn-centered']) !!} -{!! Form::close() !!} - -@endsection \ No newline at end of file +
+ +
+ +@endsection diff --git a/flexiapi/resources/views/admin/account/contact/delete.blade.php b/flexiapi/resources/views/admin/account/contact/delete.blade.php index 10f9a60..4fc3dd9 100644 --- a/flexiapi/resources/views/admin/account/contact/delete.blade.php +++ b/flexiapi/resources/views/admin/account/contact/delete.blade.php @@ -5,7 +5,7 @@ Accounts
User AgentUser Agent
+ + @foreach ($account->actions as $action) + + + + + + @endforeach + +
{{ $action->key }}{{ $action->code }} + Edit + Delete +
+ + Add + + @else +

To manage actions, you must configure the DTMF protocol in the account settings.

+ @endif + +

Types

+ + + + @foreach ($account->types as $type) + + + + + @endforeach + +
{{ $type->key }} + {!! Form::open(['route' => ['admin.account.account_type.destroy', $account, $type->id], 'method' => 'delete']) !!} + {!! Form::submit('Delete', ['class' => 'btn btn-sm mr-2']) !!} + {!! Form::close() !!} +
+ + Add + + + + @endif + @endsection diff --git a/flexiapi/resources/views/admin/account/delete.blade.php b/flexiapi/resources/views/admin/account/delete.blade.php index 7bd5800..f68cbfd 100644 --- a/flexiapi/resources/views/admin/account/delete.blade.php +++ b/flexiapi/resources/views/admin/account/delete.blade.php @@ -1,15 +1,21 @@ @extends('layouts.main') -

Delete an account

+@section('content') +

Delete an account

-{!! Form::open(['route' => 'admin.account.destroy', 'method' => 'delete']) !!} + {!! Form::open(['route' => 'admin.account.destroy', 'method' => 'delete']) !!} -

You are going to permanently delete the following account. Please confirm your action.

-

{{ $account->identifier }}

+
+

You are going to permanently delete the following account. Please confirm your action.
+ {{ $account->identifier }} +

-{!! Form::hidden('account_id', $account->id) !!} + {!! Form::hidden('account_id', $account->id) !!} +
+
+ {!! Form::submit('Delete', ['class' => 'btn']) !!} -{!! Form::submit('Delete', ['class' => 'btn btn-danger btn-centered']) !!} -{!! Form::close() !!} +
-@endsection \ No newline at end of file + {!! Form::close() !!} +@endsection diff --git a/flexiapi/resources/views/admin/account/index.blade.php b/flexiapi/resources/views/admin/account/index.blade.php index 5ab7cac..da3aa25 100644 --- a/flexiapi/resources/views/admin/account/index.blade.php +++ b/flexiapi/resources/views/admin/account/index.blade.php @@ -2,13 +2,13 @@ @section('content') -
+
+

people Account

add_circle Create -

people Account

-
+
@csrf @@ -27,14 +27,12 @@
-
- - +
- - - + + + @endforeach diff --git a/flexiapi/resources/views/admin/account/show.blade.php b/flexiapi/resources/views/admin/account/show.blade.php deleted file mode 100644 index 1cae5b2..0000000 --- a/flexiapi/resources/views/admin/account/show.blade.php +++ /dev/null @@ -1,126 +0,0 @@ -@extends('layouts.main') - -@section('breadcrumb') - - -@endsection - -@section('content') - -Delete -Edit - -

Account

- -

- Id: {{ $account->id }}
- Identifier: {{ $account->identifier }}
- Email: {{ $account->email }}
- DTMF Protocol: @if ($account->dtmf_protocol) {{ $account->resolvedDtmfProtocol }}@endif
- @if ($account->alias)Phone number: {{ $account->phone }}
@endif - @if ($account->group)Group: {{ $account->group }}
@endif - @if ($account->display_name)Display name: {{ $account->display_name }}
@endif -

- -@if ($account->sha256Password) - SHA256 -@endif - -
- -

External Account

- -@if ($account->externalAccount) -

- Identifier: {{ $account->externalAccount->identifier }}
-

-@else - Attach an External Account ({{ $external_accounts_count}} left) -@endif - -

Contacts

- -
Identifier (email) + Identifier (email) Updated @if ($updated_at_order == 'desc') @@ -71,7 +69,7 @@ SHA256 @endif - {{ $account->created_at}}{{ $account->updated_at}}
- - @foreach ($account->contacts as $contact) - - - - - @endforeach - -
{{ $contact->identifier }} - Delete -
- -Add - -

Actions

- -@if ($account->dtmf_protocol) - - - - @foreach ($account->actions as $action) - - - - - - @endforeach - -
{{ $action->key }}{{ $action->code }} - Edit - Delete -
- -Add - -@else -

To manage actions, you must configure the DTMF protocol in the account settings.

-@endif - -

Types

- - - - @foreach ($account->types as $type) - - - - - @endforeach - -
{{ $type->key }} - {!! Form::open(['route' => ['admin.account.account_type.destroy', $account, $type->id], 'method' => 'delete']) !!} - {!! Form::submit('Delete', ['class' => 'btn btn-sm mr-2']) !!} - {!! Form::close() !!} -
- -Add - -

Provisioning

- -@if ($account->provisioning_token) -

Share the following picture with the user or the one-time-use link bellow.

- -
- -
-

The following link can only be visited once

- -

- Renew the provision link - The current one will be unavailable -

-@else -

- Generate a provision link -

-@endif - -@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/admin/account/type/index.blade.php b/flexiapi/resources/views/admin/account/type/index.blade.php index 74d9b34..94d3295 100644 --- a/flexiapi/resources/views/admin/account/type/index.blade.php +++ b/flexiapi/resources/views/admin/account/type/index.blade.php @@ -18,11 +18,11 @@ - +
- - + + diff --git a/flexiapi/resources/views/admin/contacts_list/contacts/add.blade.php b/flexiapi/resources/views/admin/contacts_list/contacts/add.blade.php new file mode 100644 index 0000000..aec2491 --- /dev/null +++ b/flexiapi/resources/views/admin/contacts_list/contacts/add.blade.php @@ -0,0 +1,76 @@ +@extends('layouts.main') + +@section('content') +
+

account_box Contacts List | Add contacts

+

+ selected +

+ +
+ @csrf + @method('post') + + + + + +
+ +
+
+ @csrf +
+ + +
+
+ Reset + +
+ +
+ +
KeyKey
+ + + + + + + + + @if ($accounts->isEmpty()) + + + + @endif + @foreach ($accounts as $account) + + + + + + @endforeach + +
+ + Username + + Updated + @if ($params['updated_at_order'] == 'desc') + expand_more + @else + expand_less + @endif + +
No Contact
+ + + {{ $account->identifier }} + {{ $account->updated_at}}
+ + {{ $accounts->links('pagination::bootstrap-4') }} +@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php b/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php new file mode 100644 index 0000000..071a1ab --- /dev/null +++ b/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php @@ -0,0 +1,92 @@ +@extends('layouts.main') + +@section('content') +
+ @if ($contacts_list->id) +

account_box Edit a Contacts List

+ + delete + Delete + + @else +

account_box Create a Contacts List

+ @endif +
+ + @if ($contacts_list->id) +

Updated on {{ $contacts_list->updated_at->format('d/m/Y') }} + @endif + +

+ @csrf + @method($contacts_list->id ? 'put' : 'post') +
+ + + @include('parts.errors', ['name' => 'title']) +
+ +
+ + + @include('parts.errors', ['name' => 'description']) +
+ +
+ +
+
+ + @if ($contacts_list->id) +
+ +
+

+ selected +

+ +
+ @csrf + @method('delete') + + + + +
+ + + add Add contacts + +
+ + + + + + + + + + @if ($contacts_list->contacts->isEmpty()) + + + + @endif + @foreach ($contacts_list->contacts as $contact) + + + + + @endforeach + +
+ + Username
No Contact
+ + {{ $contact->identifier }}
+ @endif +@endsection diff --git a/flexiapi/resources/views/admin/contacts_list/delete.blade.php b/flexiapi/resources/views/admin/contacts_list/delete.blade.php new file mode 100644 index 0000000..fa9260a --- /dev/null +++ b/flexiapi/resources/views/admin/contacts_list/delete.blade.php @@ -0,0 +1,22 @@ +@extends('layouts.main') + +@section('content') +

Delete a Contact List

+ +
+ @csrf + @method('delete') + +
+

You are going to permanently delete the following contacts list. Please confirm your action.
+ {{ $contacts_list->title }} +

+ + +
+
+ +
+ +
+@endsection diff --git a/flexiapi/resources/views/admin/contacts_list/index.blade.php b/flexiapi/resources/views/admin/contacts_list/index.blade.php new file mode 100644 index 0000000..e7c9548 --- /dev/null +++ b/flexiapi/resources/views/admin/contacts_list/index.blade.php @@ -0,0 +1,49 @@ +@extends('layouts.main') + +@section('content') + +
+

account_box Contacts Lists

+ + add_circle + Create + +
+ + + + + + + + + + + + @foreach ($contacts_lists as $contacts_list) + + + + + + + @endforeach + +
NameDescriptionNumber of Contacts + + Updated + @if ($updated_at_order == 'desc') + expand_more + @else + expand_less + @endif + +
+ + {{ $contacts_list->title }} + + {{ $contacts_list->description }}{{ $contacts_list->contacts_count }}{{ $contacts_list->updated_at}}
+ +{{ $contacts_lists->links('pagination::bootstrap-4') }} + +@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/layouts/main.blade.php b/flexiapi/resources/views/layouts/main.blade.php index ec7fce6..2d1f57d 100644 --- a/flexiapi/resources/views/layouts/main.blade.php +++ b/flexiapi/resources/views/layouts/main.blade.php @@ -13,6 +13,7 @@ @else @endif + diff --git a/flexiapi/resources/views/parts/sidebar.blade.php b/flexiapi/resources/views/parts/sidebar.blade.php index 1caca1b..c6466c1 100644 --- a/flexiapi/resources/views/parts/sidebar.blade.php +++ b/flexiapi/resources/views/parts/sidebar.blade.php @@ -6,6 +6,7 @@ if (auth()->user() && auth()->user()->admin) { $items['admin.account.index'] = ['title' => 'Accounts', 'icon' => 'people']; + $items['admin.contacts_lists.index'] = ['title' => 'Contacts Lists', 'icon' => 'account_box']; $items['admin.statistics.show.day'] = ['title' => 'Statistics', 'icon' => 'analytics']; } @endphp diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index 5f36671..b9db97a 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -30,6 +30,8 @@ use App\Http\Controllers\Admin\AccountActionController; use App\Http\Controllers\Admin\AccountContactController; use App\Http\Controllers\Admin\AccountTypeController; use App\Http\Controllers\Admin\AccountController as AdminAccountController; +use App\Http\Controllers\Admin\ContactsListController; +use App\Http\Controllers\Admin\ContactsListContactController; use Illuminate\Support\Facades\Route; @@ -127,39 +129,16 @@ 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 Route::get('statistics/day', 'Admin\StatisticsController@showDay')->name('statistics.show.day'); Route::get('statistics/week', 'Admin\StatisticsController@showWeek')->name('statistics.show.week'); Route::get('statistics/month', 'Admin\StatisticsController@showMonth')->name('statistics.show.month'); - Route::prefix('accounts')->group(function () { - Route::name('account.type.')->prefix('types')->controller(AccountTypeController::class)->group(function () { - Route::get('/', 'index')->name('index'); - Route::get('create', 'create')->name('create'); - Route::post('/', 'store')->name('store'); - Route::get('{type_id}/edit', 'edit')->name('edit'); - Route::put('{type_id}', 'update')->name('update'); - Route::get('{type_id}/delete', 'delete')->name('delete'); - Route::delete('{type_id}', 'destroy')->name('destroy'); - }); - - Route::name('account.account_type.')->prefix('{account}/types')->controller(AccountAccountTypeController::class)->group(function () { - Route::get('create', 'create')->name('create'); - Route::post('/', 'store')->name('store'); - Route::delete('{type_id}', 'destroy')->name('destroy'); - }); - - Route::name('account.contact.')->prefix('{account}/contacts')->controller(AccountContactController::class)->group(function () { - Route::get('create', 'create')->name('create'); - Route::post('/', 'store')->name('store'); - Route::get('{contact_id}/delete', 'delete')->name('delete'); - Route::delete('/', 'destroy')->name('destroy'); - }); - - Route::name('account.')->controller(AdminAccountController::class)->group(function () { - Route::get('{account_id}/show', 'show')->name('show'); - + Route::name('account.')->prefix('accounts')->group(function () { + Route::controller(AdminAccountController::class)->group(function () { Route::get('{account_id}/external_account/attach', 'attachExternalAccount')->name('external_account.attach'); Route::get('{account_id}/provision', 'provision')->name('provision'); @@ -175,9 +154,35 @@ 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::name('account.action.')->prefix('{account}/actions')->controller(AccountActionController::class)->group(function () { + Route::name('type.')->prefix('types')->controller(AccountTypeController::class)->group(function () { + Route::get('/', 'index')->name('index'); + Route::get('create', 'create')->name('create'); + Route::post('/', 'store')->name('store'); + Route::get('{type_id}/edit', 'edit')->name('edit'); + Route::put('{type_id}', 'update')->name('update'); + Route::get('{type_id}/delete', 'delete')->name('delete'); + Route::delete('{type_id}', 'destroy')->name('destroy'); + }); + + Route::name('account_type.')->prefix('{account}/types')->controller(AccountAccountTypeController::class)->group(function () { + Route::get('create', 'create')->name('create'); + Route::post('/', 'store')->name('store'); + Route::delete('{type_id}', 'destroy')->name('destroy'); + }); + + Route::name('contact.')->prefix('{account}/contacts')->controller(AccountContactController::class)->group(function () { + Route::get('create', 'create')->name('create'); + Route::post('/', 'store')->name('store'); + Route::get('{contact_id}/delete', 'delete')->name('delete'); + Route::delete('/', 'destroy')->name('destroy'); + }); + + Route::name('action.')->prefix('{account}/actions')->controller(AccountActionController::class)->group(function () { Route::get('create', 'create')->name('create'); Route::post('/', 'store')->name('store'); Route::get('{action_id}/edit', 'edit')->name('edit'); @@ -186,5 +191,22 @@ if (config('app.web_panel')) { Route::delete('{action_id}', 'destroy')->name('destroy'); }); }); + + Route::name('contacts_lists.')->prefix('contacts_lists')->controller(ContactsListController::class)->group(function () { + Route::get('/', 'index')->name('index'); + Route::get('create', 'create')->name('create'); + Route::post('/', 'store')->name('store'); + Route::get('{contacts_list_id}/edit', 'edit')->name('edit'); + Route::put('{contacts_list_id}', 'update')->name('update'); + Route::get('{contacts_list_id}/delete', 'delete')->name('delete'); + Route::delete('{contacts_list_id}', 'destroy')->name('destroy'); + + Route::name('contacts.')->prefix('{contacts_list_id}/contacts')->controller(ContactsListContactController::class)->group(function () { + Route::get('add', 'add')->name('add'); + Route::post('search', 'search')->name('search'); + Route::post('/', 'store')->name('store'); + Route::delete('/', 'destroy')->name('destroy'); + }); + }); }); } diff --git a/flexiapi/tests/Feature/ApiAccountContactsTest.php b/flexiapi/tests/Feature/ApiAccountContactsTest.php index 135b61c..b0126f5 100644 --- a/flexiapi/tests/Feature/ApiAccountContactsTest.php +++ b/flexiapi/tests/Feature/ApiAccountContactsTest.php @@ -22,7 +22,7 @@ namespace Tests\Feature; use App\Password; use App\AccountType; use App\Admin; - +use App\ContactsList; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Tests\TestCase; @@ -48,13 +48,13 @@ class ApiAccountContactTest extends TestCase $admin->account->generateApiKey(); $this->keyAuthenticated($admin->account) - ->json($this->method, $this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id) + ->json($this->method, $this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id) ->assertStatus(200); $this->assertEquals(1, DB::table('contacts')->count()); $this->keyAuthenticated($admin->account) - ->json($this->method, $this->route.'/'.$password1->account->id.'/contacts/'.$password3->account->id) + ->json($this->method, $this->route . '/' . $password1->account->id . '/contacts/' . $password3->account->id) ->assertStatus(200); $this->assertEquals(2, DB::table('contacts')->count()); @@ -69,85 +69,132 @@ class ApiAccountContactTest extends TestCase $accountType = AccountType::first(); $this->keyAuthenticated($admin->account) - ->json($this->method, '/api/accounts/'.$password2->account->id.'/types/'.$accountType->id) + ->json($this->method, '/api/accounts/' . $password2->account->id . '/types/' . $accountType->id) ->assertStatus(200); // Action $this->keyAuthenticated($admin->account) - ->json($this->method, $this->route.'/'.$password2->account->id.'/actions', [ + ->json($this->method, $this->route . '/' . $password2->account->id . '/actions', [ 'key' => $actionKey, 'code' => $actionCode ]); // Retry $this->keyAuthenticated($admin->account) - ->json($this->method, $this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id) - ->assertStatus(403); + ->json($this->method, $this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id) + ->assertStatus(403); $this->assertEquals(2, DB::table('contacts')->count()); $this->keyAuthenticated($admin->account) - ->get($this->route.'/'.$password1->account->id.'/contacts') - ->assertJson([ + ->get($this->route . '/' . $password1->account->id . '/contacts') + ->assertJson([ [ 'id' => $password2->account->id ] - ]); + ]); // /me $password1->account->generateApiKey(); $password1->account->save(); $this->keyAuthenticated($password1->account) - ->get($this->route.'/me/contacts') - ->assertStatus(200) - ->assertJson([[ + ->get($this->route . '/me/contacts') + ->assertStatus(200) + ->assertJson([[ 'username' => $password2->account->username, 'activated' => true - ]]); + ]]); $this->keyAuthenticated($password1->account) - ->get($this->route.'/me/contacts/'.$password2->account->identifier) - ->assertStatus(200) - ->assertJson([ + ->get($this->route . '/me/contacts/' . $password2->account->identifier) + ->assertStatus(200) + ->assertJson([ 'username' => $password2->account->username, 'activated' => true - ]); + ]); // Vcard 4.0 $this->keyAuthenticated($password1->account) - ->get('/contacts/vcard') - ->assertStatus(200) - ->assertSeeText("FN:".$password2->display_name) - ->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:".$typeKey) - ->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:".$password2->dtmf_protocol) - ->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:".$actionKey.';'.$actionCode); + ->get('/contacts/vcard') + ->assertStatus(200) + ->assertSeeText("FN:" . $password2->display_name) + ->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:" . $typeKey) + ->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:" . $password2->dtmf_protocol) + ->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:" . $actionKey . ';' . $actionCode); $this->keyAuthenticated($password1->account) - ->get('/contacts/vcard/'.$password2->account->identifier) - ->assertStatus(200) - ->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:".$typeKey) - ->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:".$password2->dtmf_protocol) - ->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:".$actionKey.';'.$actionCode); + ->get('/contacts/vcard/' . $password2->account->identifier) + ->assertStatus(200) + ->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:" . $typeKey) + ->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:" . $password2->dtmf_protocol) + ->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:" . $actionKey . ';' . $actionCode); $this->keyAuthenticated($password1->account) - ->get($this->route.'/me/contacts/'.$password2->account->identifier) - ->assertStatus(200) - ->assertJson([ + ->get($this->route . '/me/contacts/' . $password2->account->identifier) + ->assertStatus(200) + ->assertJson([ 'username' => $password2->account->username, 'activated' => true - ]); + ]); // Remove $this->keyAuthenticated($admin->account) - ->delete($this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id) - ->assertStatus(200); + ->delete($this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id) + ->assertStatus(200); $this->assertEquals(1, DB::table('contacts')->count()); // Retry $this->keyAuthenticated($admin->account) - ->delete($this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id) - ->assertStatus(403); + ->delete($this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id) + ->assertStatus(403); $this->assertEquals(1, DB::table('contacts')->count()); + + /** + * Contacts lists + * + */ + + // 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]); + + $admin->account->contactsLists()->attach([$contactList->id]); + + $this->keyAuthenticated($admin->account) + ->get($this->route . '/me/contacts') + ->assertStatus(200) + ->assertJsonFragment([ + 'username' => $password1->account->username, + 'activated' => true + ]) + ->assertJsonFragment([ + 'username' => $password2->account->username, + 'activated' => true + ]) + ->assertJsonFragment([ + 'username' => $password3->account->username, + 'activated' => true + ]); + + $this->keyAuthenticated($admin->account) + ->get($this->route . '/me/contacts/' . $password2->account->identifier) + ->assertStatus(200) + ->assertJsonFragment([ + 'username' => $password2->account->username, + 'activated' => true + ]); + + $this->keyAuthenticated($admin->account) + ->get('/contacts/vcard') + ->assertStatus(200) + ->assertSeeText("FN:" . $password1->display_name) + ->assertSeeText("FN:" . $password2->display_name) + ->assertSeeText("FN:" . $password3->display_name); + + $this->keyAuthenticated($admin->account) + ->get('/contacts/vcard/' . $password2->account->identifier) + ->assertStatus(200) + ->assertSeeText("FN:" . $password2->display_name); } }