mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-04-17 11:48:28 +00:00
Fix FLEXIAPI-433 Implement CallForwarding
This commit is contained in:
parent
126f25de5a
commit
3f0ecc297b
33 changed files with 1388 additions and 596 deletions
|
|
@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|||
- **Rockylinux 10 support** Packages are now available in the official repository
|
||||
- **Artisan cleanup script for statistics** Add an artisan console script to clear statistics after n days `app:clear-statistics {days} {--apply}`
|
||||
- **Add Voicemail features and related API endpoints** to integrate with `flexisip-voicemail`
|
||||
- **Add Call Forwarding features and related API endpoints**
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,20 @@ class Account extends Authenticatable
|
|||
return $this->hasMany(AccountFile::class)->latest();
|
||||
}
|
||||
|
||||
public function callForwardings()
|
||||
{
|
||||
return $this->hasMany(CallForwarding::class)->latest();
|
||||
}
|
||||
|
||||
public function getCallForwardingsDefaultAttribute()
|
||||
{
|
||||
$callForwardings = $this->callForwardings->keyBy('type');
|
||||
$resolved['always'] = $callForwardings['always'] ?? new CallForwarding(['type' => 'always']);
|
||||
$resolved['away'] = $callForwardings['away'] ?? new CallForwarding(['type' => 'away']);
|
||||
$resolved['busy'] = $callForwardings['busy'] ?? new CallForwarding(['type' => 'busy']);
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
public function voicemails()
|
||||
{
|
||||
return $this->hasMany(AccountFile::class)
|
||||
|
|
|
|||
19
flexiapi/app/CallForwarding.php
Normal file
19
flexiapi/app/CallForwarding.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CallForwarding extends Model
|
||||
{
|
||||
protected $casts = [
|
||||
'enabled' => 'boolean'
|
||||
];
|
||||
protected $fillable = ['enabled', 'account_id', 'type', 'forward_to', 'sip_uri'];
|
||||
protected $hidden = ['account_id'];
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -175,21 +175,21 @@ function captchaConfigured(): bool
|
|||
return env('HCAPTCHA_SECRET', false) != false || env('HCAPTCHA_SITEKEY', false) != false;
|
||||
}
|
||||
|
||||
function resolveUserContacts(Request $request)
|
||||
function resolveUserContacts(Account $account)
|
||||
{
|
||||
$selected = ['id', 'username', 'domain', 'activated', 'dtmf_protocol', 'display_name'];
|
||||
|
||||
return Account::withoutGlobalScopes()->whereIn('id', function ($query) use ($request) {
|
||||
return Account::withoutGlobalScopes()->whereIn('id', function ($query) use ($account) {
|
||||
$query->select('contact_id')
|
||||
->from('contacts')
|
||||
->where('account_id', $request->user()->id)
|
||||
->where('account_id', $account->id)
|
||||
->union(
|
||||
DB::table('contacts_list_contact')
|
||||
->select('contact_id')
|
||||
->whereIn('contacts_list_id', function ($query) use ($request) {
|
||||
->whereIn('contacts_list_id', function ($query) use ($account) {
|
||||
$query->select('contacts_list_id')
|
||||
->from('account_contacts_list')
|
||||
->where('account_id', $request->user()->id);
|
||||
->where('account_id', $account->id);
|
||||
})
|
||||
);
|
||||
})->select($selected);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class ContactVcardController extends Controller
|
|||
public function index(Request $request)
|
||||
{
|
||||
return response(
|
||||
resolveUserContacts($request)->get()->map(function ($contact) {
|
||||
resolveUserContacts($request->user())->get()->map(function ($contact) {
|
||||
return $contact->toVcard4();
|
||||
})->implode("\n")
|
||||
);
|
||||
|
|
@ -36,7 +36,7 @@ class ContactVcardController extends Controller
|
|||
|
||||
public function show(Request $request, string $sip)
|
||||
{
|
||||
return resolveUserContacts($request)
|
||||
return resolveUserContacts($request->user())
|
||||
->sip($sip)
|
||||
->firstOrFail()
|
||||
->toVcard4();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Account;
|
||||
|
||||
use App\Account;
|
||||
use App\CallForwarding;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CallForwardingController extends Controller
|
||||
{
|
||||
public function update(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
$contactsIds = resolveUserContacts($account)->pluck('id')->toArray();
|
||||
|
||||
$forwardTo = 'required|in:sip_uri,contact,voicemail';
|
||||
|
||||
$request->validate([
|
||||
'always.forward_to' => $forwardTo,
|
||||
'always.sip_uri' => 'nullable|starts_with:sip:|required_if:always.forward_to,sip_uri',
|
||||
'always.contact_id' => ['required_if:always.forward_to,contact', Rule::in($contactsIds)],
|
||||
|
||||
'away.forward_to' => $forwardTo,
|
||||
'away.sip_uri' => 'nullable|starts_with:sip:|required_if:away.forward_to,sip_uri',
|
||||
'away.contact_id' => ['required_if:away.forward_to,contact', Rule::in($contactsIds)],
|
||||
|
||||
'busy.forward_to' => $forwardTo,
|
||||
'busy.sip_uri' => 'nullable|starts_with:sip:|required_if:busy.forward_to,sip_uri',
|
||||
'busy.contact_id' => ['required_if:busy.forward_to,contact', Rule::in($contactsIds)],
|
||||
]);
|
||||
|
||||
$account->callForwardings()->update(['enabled' => false]);
|
||||
|
||||
if (array_key_exists('enabled', $request->get('always'))) {
|
||||
$alwaysForwarding = $account->callForwardings()->where('type', 'always')->first() ?? new CallForwarding;
|
||||
$alwaysForwarding->enabled = true;
|
||||
$alwaysForwarding->account_id = $account->id;
|
||||
$alwaysForwarding->type = 'always';
|
||||
$alwaysForwarding->forward_to = $request->get('always')['forward_to'];
|
||||
$alwaysForwarding->sip_uri = $request->get('always')['sip_uri'];
|
||||
$alwaysForwarding->contact_id = $request->get('always')['forward_to'] == 'contact'
|
||||
? $request->get('always')['contact_id']
|
||||
: null;
|
||||
$alwaysForwarding->save();
|
||||
}
|
||||
|
||||
if (array_key_exists('enabled', $request->get('away'))) {
|
||||
$awayForwarding = $account->callForwardings()->where('type', 'away')->first() ?? new CallForwarding;
|
||||
$awayForwarding->enabled = true;
|
||||
$awayForwarding->account_id = $account->id;
|
||||
$awayForwarding->type = 'away';
|
||||
$awayForwarding->forward_to = $request->get('away')['forward_to'];
|
||||
$awayForwarding->sip_uri = $request->get('away')['sip_uri'];
|
||||
$awayForwarding->contact_id = $request->get('away')['forward_to'] == 'contact'
|
||||
? $request->get('away')['contact_id']
|
||||
: null;
|
||||
$awayForwarding->save();
|
||||
}
|
||||
|
||||
if (array_key_exists('enabled', $request->get('busy'))) {
|
||||
$busyForwarding = $account->callForwardings()->where('type', 'busy')->first() ?? new CallForwarding;
|
||||
$busyForwarding->enabled = true;
|
||||
$busyForwarding->account_id = $account->id;
|
||||
$busyForwarding->type = 'busy';
|
||||
$busyForwarding->forward_to = $request->get('busy')['forward_to'];
|
||||
$busyForwarding->sip_uri = $request->get('busy')['sip_uri'];
|
||||
$busyForwarding->contact_id = $request->get('busy')['forward_to'] == 'contact'
|
||||
? $request->get('busy')['contact_id']
|
||||
: null;
|
||||
$busyForwarding->save();
|
||||
}
|
||||
|
||||
return redirect()->route('admin.account.telephony.show', $account);
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ class ContactController extends Controller
|
|||
|
||||
return view('admin.account.contact.index', [
|
||||
'account' => $account,
|
||||
'contacts_lists' => ContactsList::whereNotIn('id', function ($query) use ($accountId) {
|
||||
'contacts_lists' => $account->space->contactsLists()->whereNotIn('id', function ($query) use ($accountId) {
|
||||
$query->select('contacts_list_id')
|
||||
->from('account_contacts_list')
|
||||
->where('account_id', $accountId);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Account;
|
||||
|
||||
use App\Account;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TelephonyController extends Controller
|
||||
{
|
||||
public function show(int $accountId)
|
||||
{
|
||||
return view('admin.account.telephony.show', [
|
||||
'account' => Account::findOrFail($accountId)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use \App\Http\Controllers\Api\Admin\Account\CallForwardingController as AdminCallForwardingController;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CallForwardingController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return (new AdminCallForwardingController)->index($request, $request->user()->id);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
return (new AdminCallForwardingController)->store($request, $request->user()->id);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
return (new AdminCallForwardingController)->update($request, $request->user()->id, $id);
|
||||
}
|
||||
|
||||
public function show(Request $request, string $id)
|
||||
{
|
||||
return (new AdminCallForwardingController)->show($request, $request->user()->id, $id);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, string $id)
|
||||
{
|
||||
return (new AdminCallForwardingController)->destroy($request, $request->user()->id, $id);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,11 +26,11 @@ class ContactController extends Controller
|
|||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return resolveUserContacts($request)->get();
|
||||
return resolveUserContacts($request->user())->get();
|
||||
}
|
||||
|
||||
public function show(Request $request, string $sip)
|
||||
{
|
||||
return resolveUserContacts($request)->sip($sip)->firstOrFail();
|
||||
return resolveUserContacts($request->user())->sip($sip)->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin\Account;
|
||||
|
||||
use App\Account;
|
||||
use App\CallForwarding;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Rules\CallForwardingEnable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CallForwardingController extends Controller
|
||||
{
|
||||
public function index(Request $request, int $accountId)
|
||||
{
|
||||
return Account::findOrFail($accountId)->callForwardings;
|
||||
}
|
||||
|
||||
public function store(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
$request->validate([
|
||||
'type' => [
|
||||
'required',
|
||||
'in:always,away,busy',
|
||||
Rule::unique('call_forwardings', 'type')->where(fn($query) => $query->where('account_id', $accountId))
|
||||
],
|
||||
'forward_to' => 'required|in:sip_uri,contact,voicemail',
|
||||
'sip_uri' => 'nullable|starts_with:sip:|required_if:forward_to,sip_uri',
|
||||
'enabled' => ['required', 'boolean', new CallForwardingEnable($request, $account)],
|
||||
'contact_id' => ['required_if:forward_to,contact', Rule::in(resolveUserContacts($account)->pluck('id')->toArray())],
|
||||
]);
|
||||
|
||||
$callForwarding = new CallForwarding;
|
||||
$callForwarding->account_id = $account->id;
|
||||
$callForwarding->type = $request->get('type');
|
||||
$callForwarding->forward_to = $request->get('forward_to');
|
||||
$callForwarding->sip_uri = $request->get('sip_uri');
|
||||
$callForwarding->enabled = $request->get('enabled');
|
||||
$callForwarding->contact_id = $request->get('contact_id');
|
||||
$callForwarding->save();
|
||||
|
||||
return $callForwarding;
|
||||
}
|
||||
|
||||
public function update(Request $request, int $accountId, string $id)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
$callForwarding = $account->callForwardings()->where('id', $id)->firstOrFail();
|
||||
|
||||
$request->validate([
|
||||
'type' => [
|
||||
'required',
|
||||
'in:always,away,busy',
|
||||
Rule::unique('call_forwardings', 'type')
|
||||
->where(fn($query) => $query->where('account_id', $accountId))
|
||||
->ignore($callForwarding->id)
|
||||
],
|
||||
'forward_to' => 'required|in:sip_uri',
|
||||
'sip_uri' => 'required|starts_with:sip',
|
||||
'enabled' => ['required', 'boolean', new CallForwardingEnable($request, $account)]
|
||||
]);
|
||||
|
||||
$callForwarding->forward_to = $request->get('forward_to');
|
||||
$callForwarding->sip_uri = $request->get('sip_uri');
|
||||
$callForwarding->enabled = $request->get('enabled');
|
||||
$callForwarding->save();
|
||||
|
||||
return $callForwarding;
|
||||
}
|
||||
|
||||
public function show(Request $request, int $accountId, string $id)
|
||||
{
|
||||
return Account::findOrFail($accountId)->callForwardings()->where('id', $id)->firstOrFail();
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $accountId, string $id)
|
||||
{
|
||||
return Account::findOrFail($accountId)->callForwardings()->where('id', $id)->delete();
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ class AccountController extends Controller
|
|||
|
||||
public function search(Request $request, string $sip)
|
||||
{
|
||||
$account = $request->space->accounts()->sip($sip)->first();
|
||||
$account = $request->space->accounts()->sip($sip)->with('callForwardings')->first();
|
||||
|
||||
if (!$account)
|
||||
abort(404, 'SIP address not found');
|
||||
|
|
@ -77,7 +77,7 @@ class AccountController extends Controller
|
|||
|
||||
public function searchByEmail(Request $request, string $email)
|
||||
{
|
||||
$account = $request->space->accounts()->where('email', $email)->first();
|
||||
$account = $request->space->accounts()->where('email', $email)->with('callForwardings')->first();
|
||||
|
||||
if (!$account)
|
||||
abort(404, 'Email address not found');
|
||||
|
|
|
|||
|
|
@ -81,6 +81,18 @@ class SpaceController extends Controller
|
|||
return $space->refresh();
|
||||
}
|
||||
|
||||
public function resolve(Request $request, string $sip)
|
||||
{
|
||||
$account = $request->space->accounts()->sip($sip)->with('callForwardings')->firstOrFail();
|
||||
$arrayAccount = $account->toArray();
|
||||
unset($arrayAccount['space']);
|
||||
|
||||
return json_encode([
|
||||
'type' => 'account',
|
||||
'payload' => $arrayAccount
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(string $domain)
|
||||
{
|
||||
return Space::where('domain', $domain)->firstOrFail();
|
||||
|
|
|
|||
26
flexiapi/app/Rules/CallForwardingEnable.php
Normal file
26
flexiapi/app/Rules/CallForwardingEnable.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\Account;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CallForwardingEnable implements ValidationRule
|
||||
{
|
||||
public function __construct(private Request $request, private Account $account)
|
||||
{
|
||||
}
|
||||
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if ($value == true) {
|
||||
$filter = $this->request->get('type') == 'always' ? ['away', 'busy'] : ['always'];
|
||||
|
||||
if ($this->account->callForwardings()->whereIn('type', $filter)->where('enabled', true)->exists()) {
|
||||
$fail('type: always and type: always/busy cannot be enabled at the same time');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1177
flexiapi/composer.lock
generated
1177
flexiapi/composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('call_forwardings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('account_id')->unsigned()->nullable();
|
||||
$table->foreign('account_id')->references('id')
|
||||
->on('accounts')->onDelete('cascade');
|
||||
$table->string('type');
|
||||
$table->string('forward_to');
|
||||
$table->string('sip_uri')->nullable();
|
||||
$table->boolean('enabled')->default(false);
|
||||
$table->integer('contact_id')->unsigned()->nullable();
|
||||
$table->foreign('contact_id')->references('id')
|
||||
->on('accounts')->onDelete('cascade');
|
||||
$table->unique(['account_id', 'type']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('call_forwardings');
|
||||
}
|
||||
};
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
"Admin": "Administrateur",
|
||||
"Administration": "Administration",
|
||||
"Admins": "Administrateurs",
|
||||
"All the calls": "Tous les appels",
|
||||
"All the admins will be super admins": "Tous les administrateurs seront super-administrateurs",
|
||||
"Allow a custom CSS theme": "Autoriser un thème CSS personnalisé",
|
||||
"Allow client settings to be overwritten by the provisioning ones": "Écraser la configuration client avec celle du déploiement",
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
"Api Keys": "Clefs d'API",
|
||||
"App Configuration": "Configuration de l'App",
|
||||
"App settings": "Paramètres d'application",
|
||||
"No anwser": "Pas de réponse",
|
||||
"Assistant": "Assistant",
|
||||
"Best regards,":"Cordialement,",
|
||||
"Blocked": "Bloqué",
|
||||
|
|
@ -38,6 +40,7 @@
|
|||
"By phone": "Par téléphone",
|
||||
"By": "Par",
|
||||
"Call Recording": "Enregistrement d'appels",
|
||||
"Call Forwarding": "Redirection d'appels",
|
||||
"Calls logs": "Journaux d'appel",
|
||||
"Cancel": "Annuler",
|
||||
"Cannot be changed once created.": "Ne peut être changé par la suite.",
|
||||
|
|
@ -63,6 +66,7 @@
|
|||
"Connection": "Connexion",
|
||||
"Contacts List": "Liste de Contacts",
|
||||
"Contacts Lists": "Listes de Contacts",
|
||||
"Contact": "Contact",
|
||||
"Contacts": "Contacts",
|
||||
"Copyright text": "Texte droits d'auteurs",
|
||||
"Country code": "Code du pays",
|
||||
|
|
@ -154,11 +158,13 @@
|
|||
"No account yet?": "Pas encore de compte ?",
|
||||
"No email yet": "Pas d'email pour le moment",
|
||||
"No limit": "Sans limite",
|
||||
"No answer": "Pas de réponse",
|
||||
"No phone yet": "Pas de téléphone pour le moment",
|
||||
"Number of minutes to expire the key after the last request.": "Nombre de minutes avant l'expiration de la clef après son dernier usage.",
|
||||
"Open the app": "Ouvrir l'application",
|
||||
"Other information": "Autres informations",
|
||||
"Outbound proxy": "Outbound proxy",
|
||||
"Line occupied": "Ligne occupée",
|
||||
"Password": "Mot de passe",
|
||||
"Phone Countries": "Numéros Internationaux",
|
||||
"Phone number": "Numéro de téléphone",
|
||||
|
|
@ -203,8 +209,7 @@
|
|||
"Separated by commas": "Séparé par des virgules",
|
||||
"Settings": "Paramètres",
|
||||
"Show usernames only": "Afficher uniquement les noms d'utilisateur",
|
||||
"SIP address":"Adresse SIP",
|
||||
"Sip Adress": "Adresse SIP",
|
||||
"SIP Adress": "Adresse SIP",
|
||||
"SIP Domain": "Domaine SIP",
|
||||
"Space": "Espace",
|
||||
"Spaces": "Espaces",
|
||||
|
|
@ -212,6 +217,7 @@
|
|||
"Subdomain": "Sous-domaine",
|
||||
"Super Admin": "Super Admin",
|
||||
"Super Space": "Super Espace",
|
||||
"Telephony": "Téléphonie",
|
||||
"Thank you for registering on :space.":"Merci de vous être inscrit sur :space.",
|
||||
"Thanks for the validation": "Nous vous remercions pour la validation",
|
||||
"The :attribute should not be a phone number": "Le champ :attribute ne peut pas être un numéro de téléphone",
|
||||
|
|
|
|||
13
flexiapi/public/css/form.css
vendored
13
flexiapi/public/css/form.css
vendored
|
|
@ -400,3 +400,16 @@ div.checkbox:hover > input[type="checkbox"] + label {
|
|||
div.checkbox > input[type="checkbox"]:checked + label {
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
/* Telephony subselect */
|
||||
|
||||
div.select[data-value] ~ div.togglable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.select[data-value=voicemail] ~ div.togglable.voicemail,
|
||||
div.select[data-value=contact] ~ div.togglable.contact,
|
||||
div.select[data-value=sip_uri] ~ div.togglable.sip_uri {
|
||||
display: block;
|
||||
}
|
||||
8
flexiapi/public/scripts/utils.js
vendored
8
flexiapi/public/scripts/utils.js
vendored
|
|
@ -135,3 +135,11 @@ function copyValueTo(from, to, append) {
|
|||
to.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
function setCheckboxValue(name, value) {
|
||||
let checkbox = document.getElementsByName(name)[0];
|
||||
|
||||
if (checkbox) {
|
||||
checkbox.checked = value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<h3>
|
||||
{{ __('Call Forwarding') }}
|
||||
</h3>
|
||||
|
||||
<form id="edit" method="POST" action="{{ route('admin.account.call_forwardings.update', $account->id) }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('put')
|
||||
@php($callForwardings = $account->callForwardingsDefault)
|
||||
|
||||
<section>
|
||||
<h4>{{ __('All the calls') }}</h4>
|
||||
|
||||
<div class="checkbox">
|
||||
<input id="always[enabled]" type="checkbox" @if ($callForwardings['always']->enabled) checked @endif name="always[enabled]"
|
||||
onchange="if (this.checked) { setCheckboxValue('away[enabled]', false); setCheckboxValue('busy[enabled]', false); }">
|
||||
<label for="always[enabled]"></label>
|
||||
</div>
|
||||
|
||||
@include('admin.account.call_forwardings.edit_select_part', ['callForwarding' => $callForwardings, 'type' => 'always'])
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h4>{{ __('No answer') }}</h4>
|
||||
|
||||
<div class="checkbox">
|
||||
<input id="away[enabled]" type="checkbox" @if ($callForwardings['away']->enabled) checked @endif name="away[enabled]"
|
||||
onchange="if (this.checked) { setCheckboxValue('always[enabled]', false); }">
|
||||
<label for="away[enabled]"></label>
|
||||
</div>
|
||||
|
||||
@include('admin.account.call_forwardings.edit_select_part', ['callForwarding' => $callForwardings, 'type' => 'away'])
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h4>{{ __('Line occupied') }}</h4>
|
||||
|
||||
<div class="checkbox">
|
||||
<input id="busy[enabled]" type="checkbox" @if ($callForwardings['busy']->enabled) checked @endif name="busy[enabled]"
|
||||
onchange="if (this.checked) { setCheckboxValue('always[enabled]', false); }">
|
||||
<label for="busy[enabled]"></label>
|
||||
</div>
|
||||
|
||||
@include('admin.account.call_forwardings.edit_select_part', ['callForwarding' => $callForwardings, 'type' => 'busy'])
|
||||
</section>
|
||||
|
||||
<div class="large">
|
||||
<input class="btn small oppose" type="submit" value="{{ __('Update') }}">
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<div class="select" data-value="{{ $callForwardings[$type]->forward_to }}">
|
||||
<select name="{{ $type }}[forward_to]" onchange="this.parentNode.dataset.value = this.value">
|
||||
<option @if ($callForwardings[$type]->forward_to == 'voicemail') selected @endif value="voicemail">{{ __('Voicemails') }}</option>
|
||||
<option @if ($callForwardings[$type]->forward_to == null || $callForwardings[$type]->forward_to == 'sip_uri') selected @endif value="sip_uri">{{ __('SIP Adress') }}</option>
|
||||
<option @if ($callForwardings[$type]->forward_to == 'contact') selected @endif value="contact">{{ __('Contact') }}</option>
|
||||
</select>
|
||||
<label for="{{ $type }}[forward_to]">{{ __('Destination') }}</label>
|
||||
</div>
|
||||
<div class="togglable sip_uri">
|
||||
<input placeholder="sip:username@server.com" list="contacts" name="{{ $type }}[sip_uri]" type="text" id="busy[sip_uri]" value="{{ $callForwardings[$type]->sip_uri }}">
|
||||
<label for="sip">{{ __('SIP Adress') }}</label>
|
||||
@include('parts.errors', ['name' => $type . '.sip_uri'])
|
||||
</div>
|
||||
<div class="togglable voicemail"></div>
|
||||
<div class="select togglable contact">
|
||||
<select name="{{ $type }}[contact_id]">
|
||||
@foreach (resolveUserContacts($account)->get() as $contact)
|
||||
<option @if ($callForwardings[$type]->contact_id == $contact->id) selected @endif value="{{ $contact->id }}">{{ $contact->identifier }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label for="contact">{{ __('Contact') }}</label>
|
||||
@include('parts.errors', ['name' => $type . '.contact'])
|
||||
</div>
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
@foreach ($account->contactsLists as $contactsList)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ route('admin.spaces.contacts_lists.edit', [$space, $contactsList->id]) }}">{{ $contactsList->title }}</a>
|
||||
<a href="{{ route('admin.spaces.contacts_lists.edit', [$account->space, $contactsList->id]) }}">{{ $contactsList->title }}</a>
|
||||
<small>{{ $contactsList->contacts_count }} {{ __('Contacts') }}</small>
|
||||
</td>
|
||||
<td class="actions">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
@include('parts.tabs', [
|
||||
'items' => [
|
||||
route('admin.account.show', $account) => __('Information'),
|
||||
route('admin.account.telephony.show', $account) => __('Telephony'),
|
||||
route('admin.account.contact.index', $account) => __('Contacts'),
|
||||
route('admin.account.statistics.show_call_logs', $account) => __('Calls logs'),
|
||||
route('admin.account.statistics.show', $account) => __('Statistics'),
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card">
|
||||
<div class="card large">
|
||||
<h3>
|
||||
{{ __('Devices') }}
|
||||
</h3>
|
||||
|
|
@ -211,53 +211,6 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>
|
||||
{{ __('Voicemails') }}
|
||||
</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('Created') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($account->uploadedVoicemails->isEmpty())
|
||||
<tr class="empty">
|
||||
<td colspan="2">{{ __('Empty') }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@foreach ($account->uploadedVoicemails as $voicemail)
|
||||
<tr>
|
||||
<td>
|
||||
{{ $voicemail->created_at }}
|
||||
@if ($voicemail->url)
|
||||
<a style="margin-left: 1rem;" href="{{ $voicemail->download_url }}" download>
|
||||
<i class="ph ph-download"></i>
|
||||
</a>
|
||||
@endif
|
||||
@if ($voicemail->sip_from)
|
||||
<br/>
|
||||
<small>{{ $voicemail->sip_from }}</small>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($voicemail->url)
|
||||
<audio class="oppose" controls src="{{ $voicemail->url }}"></audio>
|
||||
<a type="button"
|
||||
class="oppose btn tertiary"
|
||||
href="{{ route('admin.account.file.delete', [$account, $voicemail->id]) }}">
|
||||
<i class="ph ph-trash"></i>
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card large">
|
||||
<a class="btn small oppose" href="{{ route('admin.account.dictionary.create', $account) }}">
|
||||
<i class="ph ph-plus"></i>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
@include('admin.parts.breadcrumb.accounts.show', ['account' => $account])
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="ph ph-phone"></i> {{ $account->identifier }}</h1>
|
||||
</header>
|
||||
@include('admin.account.parts.tabs')
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>
|
||||
{{ __('Voicemails') }}
|
||||
</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('Created') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($account->uploadedVoicemails->isEmpty())
|
||||
<tr class="empty">
|
||||
<td colspan="2">{{ __('Empty') }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@foreach ($account->uploadedVoicemails as $voicemail)
|
||||
<tr>
|
||||
<td>
|
||||
{{ $voicemail->created_at }}
|
||||
@if ($voicemail->url)
|
||||
<a style="margin-left: 1rem;" href="{{ $voicemail->download_url }}" download>
|
||||
<i class="ph ph-download"></i>
|
||||
</a>
|
||||
@endif
|
||||
@if ($voicemail->sip_from)
|
||||
<br/>
|
||||
<small>{{ $voicemail->sip_from }}</small>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($voicemail->url)
|
||||
<audio class="oppose" controls src="{{ $voicemail->url }}"></audio>
|
||||
<a type="button"
|
||||
class="oppose btn tertiary"
|
||||
href="{{ route('admin.account.file.delete', [$account, $voicemail->id]) }}">
|
||||
<i class="ph ph-trash"></i>
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@include('admin.account.call_forwardings.edit', ['account' => $account])
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
## Account Call Forwardings
|
||||
|
||||
### `GET /accounts/{id/me}/call_forwardings`
|
||||
<span class="badge badge-info">User</span>
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Return the user Call Forwardings.
|
||||
|
||||
### `GET /accounts/{id/me}/call_forwardings/{call_forwarding_id}`
|
||||
<span class="badge badge-info">User</span>
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Return a Call Forwarding configuration.
|
||||
|
||||
### `POST /accounts/{id/me}/call_forwardings`
|
||||
<span class="badge badge-info">User</span>
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Create a new Call Forwarding configuration.
|
||||
|
||||
JSON parameters:
|
||||
|
||||
* `type` **required**, must be `always`, `away` or `busy`, one of each declaration maximum per account
|
||||
* `forward_to` **required**, must be `sip_uri`, `voicemail` or `contact`
|
||||
* `sip_uri` **required if `forward_to` is set to `sip_uri`**, must be a SIP URI, must be set when `forward_to` set to `sip_uri`
|
||||
* `contact_id` **required if `forward_to` is set to `contact`**, must be a valid `contact_id` of the contact
|
||||
* `enabled` **required**, boolean. If `type: always` is enabled `away` and `busy` must be disabled. If `type: away or busy` are enabled `always` must be disabled.
|
||||
|
||||
### `PUT /accounts/{id/me}/call_forwardings/{call_forwarding_id}`
|
||||
<span class="badge badge-info">User</span>
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Create a new Call Forwarding configuration.
|
||||
|
||||
JSON parameters:
|
||||
|
||||
* `forward_to` **required**, must be `sip_uri`, `voicemail` or `contact`
|
||||
* `sip_uri` **required if `forward_to` is set to `sip_uri`**, must be a SIP URI, must be set when `forward_to` set to `sip_uri`
|
||||
* `contact_id` **required if `forward_to` is set to `contact`**, must be a valid `contact_id` of the contact
|
||||
* `enabled` **required**, boolean. If `type: always` is enabled `away` and `busy` must be disabled. If `type: away or busy` are enabled `always` must be disabled.
|
||||
|
||||
### `DELETE /accounts/{id/me}/call_forwardings/{call_forwarding_id}`
|
||||
<span class="badge badge-info">User</span>
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Remove a Call Forwarding configuration.
|
||||
|
|
@ -9,6 +9,19 @@
|
|||
|
||||
Returns `pong`
|
||||
|
||||
## SIP URI Resolving
|
||||
|
||||
### `GET /resolve/{sip}`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
|
||||
Resolve a specific `SIP URI` in the space.
|
||||
All the resolved object can be reached using direct API endpoints. This endpoint is only there as a shortcut and should be used sparingly.
|
||||
|
||||
Will return a JSON message with:
|
||||
|
||||
* `type` resolved API object `type`, can be `acccount`
|
||||
* `payload` that contains the resolved object
|
||||
|
||||
@include('api.documentation.spaces')
|
||||
|
||||
@include('api.documentation.spaces.carddav')
|
||||
|
|
@ -25,6 +38,8 @@ Returns `pong`
|
|||
|
||||
@include('api.documentation.accounts.contacts_lists')
|
||||
|
||||
@include('api.documentation.accounts.call_forwarding')
|
||||
|
||||
@include('api.documentation.accounts.contacts')
|
||||
|
||||
@include('api.documentation.accounts.dictionary')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
@extends('errors::minimal')
|
||||
|
||||
@section('title', __('Service Unavailable'))
|
||||
@section('code', '503')
|
||||
|
||||
@if (app()->isDownForMaintenance())
|
||||
@section('title', __('We will be back soon!'))
|
||||
@section('message', 'Sorry for the inconvenience but we are performing some maintenance at the moment.')
|
||||
@else
|
||||
@section('title', __('Service Unavailable'))
|
||||
@section('message', $exception->getMessage())
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
@if (isset($errors) && isset($name) && count($errors->get($name)) > 0)
|
||||
@foreach ($errors->get($name) as $error)
|
||||
<small class="error">
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
use App\Http\Controllers\Api\Account\AccountController;
|
||||
use App\Http\Controllers\Api\Account\ApiKeyController;
|
||||
use App\Http\Controllers\Api\Account\AuthTokenController;
|
||||
use App\Http\Controllers\Api\Account\CallForwardingController;
|
||||
use App\Http\Controllers\Api\Account\ContactController;
|
||||
use App\Http\Controllers\Api\Account\CreationRequestToken;
|
||||
use App\Http\Controllers\Api\Account\CreationTokenController;
|
||||
|
|
@ -33,6 +34,7 @@ use App\Http\Controllers\Api\Account\RecoveryTokenController;
|
|||
use App\Http\Controllers\Api\Account\VcardsStorageController;
|
||||
use App\Http\Controllers\Api\Account\VoicemailController;
|
||||
use App\Http\Controllers\Api\Admin\Account\ActionController;
|
||||
use App\Http\Controllers\Api\Admin\Account\CallForwardingController as AdminCallForwardingController;
|
||||
use App\Http\Controllers\Api\Admin\Account\CardDavCredentialsController;
|
||||
use App\Http\Controllers\Api\Admin\Account\ContactController as AdminContactController;
|
||||
use App\Http\Controllers\Api\Admin\Account\CreationTokenController as AdminCreationTokenController;
|
||||
|
|
@ -111,6 +113,7 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
|
|||
|
||||
Route::apiResource('vcards-storage', VcardsStorageController::class);
|
||||
Route::apiResource('voicemails', VoicemailController::class, ['only' => ['index', 'show', 'store', 'destroy']]);
|
||||
Route::apiResource('call_forwardings', CallForwardingController::class);
|
||||
});
|
||||
|
||||
Route::group(['middleware' => ['auth.admin']], function () {
|
||||
|
|
@ -134,6 +137,8 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
|
|||
Route::post('phone_countries/{code}/deactivate', [AdminPhoneCountryController::class, 'deactivate']);
|
||||
});
|
||||
|
||||
Route::get('resolve/{sip}', [SpaceController::class, 'resolve']);
|
||||
|
||||
// Account creation token
|
||||
Route::post('account_creation_tokens', [AdminCreationTokenController::class, 'create']);
|
||||
|
||||
|
|
@ -181,6 +186,7 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
|
|||
Route::apiResource('account_types', TypeController::class);
|
||||
Route::apiResource('accounts/{id}/vcards-storage', AdminVcardsStorageController::class);
|
||||
Route::apiResource('accounts/{id}/voicemails', AdminVoicemailController::class, ['only' => ['index', 'show', 'store', 'destroy']]);
|
||||
Route::apiResource('accounts/{id}/call_forwardings', AdminCallForwardingController::class);
|
||||
|
||||
Route::apiResource('contacts_lists', ContactsListController::class);
|
||||
Route::prefix('contacts_lists')->controller(ContactsListController::class)->group(function () {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ use App\Http\Controllers\Account\VcardsStorageController;
|
|||
use App\Http\Controllers\Admin\Account\AccountTypeController;
|
||||
use App\Http\Controllers\Admin\Account\ActionController;
|
||||
use App\Http\Controllers\Admin\Account\ActivityController;
|
||||
use App\Http\Controllers\Admin\Account\CallForwardingController;
|
||||
use App\Http\Controllers\Admin\Account\CardDavCredentialsController;
|
||||
use App\Http\Controllers\Admin\Account\ContactController;
|
||||
use App\Http\Controllers\Admin\Account\DeviceController as AdminAccountDeviceController;
|
||||
|
|
@ -43,6 +44,7 @@ use App\Http\Controllers\Admin\Account\DictionaryController;
|
|||
use App\Http\Controllers\Admin\Account\FileController as AdminFileController;
|
||||
use App\Http\Controllers\Admin\Account\ImportController;
|
||||
use App\Http\Controllers\Admin\Account\StatisticsController as AdminAccountStatisticsController;
|
||||
use App\Http\Controllers\Admin\Account\TelephonyController;
|
||||
use App\Http\Controllers\Admin\Account\TypeController;
|
||||
use App\Http\Controllers\Admin\AccountController as AdminAccountController;
|
||||
use App\Http\Controllers\Admin\ApiKeyController as AdminApiKeyController;
|
||||
|
|
@ -312,6 +314,14 @@ Route::middleware(['feature.web_panel_enabled'])->group(function () {
|
|||
Route::delete('/', 'destroy')->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('telephony.')->prefix('{account}/telephony')->controller(TelephonyController::class)->group(function () {
|
||||
Route::get('/', 'show')->name('show');
|
||||
});
|
||||
|
||||
Route::name('call_forwardings.')->prefix('{account}/call_forwardings')->controller(CallForwardingController::class)->group(function () {
|
||||
Route::put('/', 'update')->name('update');
|
||||
});
|
||||
|
||||
Route::name('device.')->prefix('{account}/devices')->controller(AdminAccountDeviceController::class)->group(function () {
|
||||
Route::get('{device_id}/delete', 'delete')->name('delete');
|
||||
Route::delete('/', 'destroy')->name('destroy');
|
||||
|
|
|
|||
162
flexiapi/tests/Feature/ApiAccountCallForwardingTest.php
Normal file
162
flexiapi/tests/Feature/ApiAccountCallForwardingTest.php
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2026 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Account;
|
||||
use Tests\TestCase;
|
||||
use function PHPUnit\Framework\assertJson;
|
||||
|
||||
class ApiAccountCallForwardingTest extends TestCase
|
||||
{
|
||||
protected $route = '/api/accounts/me/call_forwardings';
|
||||
protected $method = 'POST';
|
||||
|
||||
public function testResolving()
|
||||
{
|
||||
$account = Account::factory()->create();
|
||||
$account->generateUserApiKey();
|
||||
|
||||
$uri = 'sip:uri';
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'sip_uri',
|
||||
'sip_uri' => $uri,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertStatus(201);
|
||||
|
||||
$admin = Account::factory()->admin()->create();
|
||||
$admin->generateUserApiKey();
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->get('/api/resolve/' . $account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment(['type' => 'account']);
|
||||
}
|
||||
|
||||
public function testCrud()
|
||||
{
|
||||
$account = Account::factory()->create();
|
||||
$account->generateUserApiKey();
|
||||
$admin = Account::factory()->admin()->create();
|
||||
$admin->generateUserApiKey();
|
||||
|
||||
$uri = 'sip:uri';
|
||||
|
||||
// Contacts
|
||||
|
||||
$contactAccount = Account::factory()->create();
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'contact',
|
||||
'enabled' => true
|
||||
])
|
||||
->assertJsonValidationErrors(['contact_id']);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'contact',
|
||||
'contact_id' => $contactAccount->id,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertJsonValidationErrors(['contact_id']);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->json($this->method, '/api/accounts/' . $account->id . '/contacts/' . $contactAccount->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$response = $this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'contact',
|
||||
'contact_id' => $contactAccount->id,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertStatus(201);
|
||||
|
||||
// SIP URI
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'sip_uri',
|
||||
'sip_uri' => null,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertJsonValidationErrors(['type']);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('DELETE', $this->route . '/' . $response['id'])
|
||||
->assertStatus(200);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'sip_uri',
|
||||
'sip_uri' => null,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertJsonValidationErrors(['sip_uri']);
|
||||
|
||||
$response = $this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'sip_uri',
|
||||
'sip_uri' => $uri,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertStatus(201);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'away',
|
||||
'forward_to' => 'sip_uri',
|
||||
'sip_uri' => $uri,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertJsonValidationErrors(['enabled']);
|
||||
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('PUT', $this->route . '/' . $response->json()['id'], [
|
||||
'type' => 'always',
|
||||
'forward_to' => 'sip_uri',
|
||||
'sip_uri' => $uri,
|
||||
'enabled' => false
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, $this->route, [
|
||||
'type' => 'away',
|
||||
'forward_to' => 'sip_uri',
|
||||
'sip_uri' => $uri,
|
||||
'enabled' => true
|
||||
])
|
||||
->assertStatus(201);
|
||||
|
||||
$this->assertCount(2, $this->keyAuthenticated($account)
|
||||
->json('GET', $this->route)->json());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue