Fix FLEXIAPI-455 Add GET endpoints to retrieve an account call logs, documentation and tests

This commit is contained in:
Timothée Jaussoin 2026-03-16 17:07:44 +01:00
parent ee5395677c
commit 32b1db3d78
7 changed files with 97 additions and 7 deletions

View file

@ -0,0 +1,32 @@
<?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 App\Http\Controllers\Api\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Controllers\Api\Admin\StatisticsCallController as AdminStatisticsCallController;
class StatisticsCallController extends Controller
{
public function index(Request $request)
{
return (new AdminStatisticsCallController)->index($request, $request->user()->id);
}
}

View file

@ -17,8 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api\Admin;
use App\Account;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\StatisticsCall; use App\StatisticsCall;
use App\StatisticsCallDevice; use App\StatisticsCallDevice;
@ -27,6 +28,22 @@ use Illuminate\Support\Facades\Log;
class StatisticsCallController extends Controller class StatisticsCallController extends Controller
{ {
public function index(Request $request, ?int $accountId)
{
$account = Account::findOrFail($accountId);
$toQuery = StatisticsCall::query()
->where('to_domain', $account->domain)
->where('to_username', $account->username);
$calls = StatisticsCall::where('from_domain', $account->domain)
->where('from_username', $account->username);
return $calls->with('devices')
->union($toQuery)
->orderBy('initiated_at', 'desc')
->paginate(30);
}
public function store(Request $request) public function store(Request $request)
{ {
$request->validate([ $request->validate([

View file

@ -34,6 +34,11 @@ class StatisticsCall extends Model
protected $casts = ['initiated_at' => 'datetime', 'ended_at' => 'datetime']; protected $casts = ['initiated_at' => 'datetime', 'ended_at' => 'datetime'];
protected $keyType = 'string'; protected $keyType = 'string';
public function devices()
{
return $this->hasMany(StatisticsCallDevice::class, 'call_id', 'id');
}
public function accountFrom() public function accountFrom()
{ {
return $this->belongsTo(Account::class, ['username', 'domain'], ['to_username', 'to_domain']); return $this->belongsTo(Account::class, ['username', 'domain'], ['to_username', 'to_domain']);

View file

@ -31,6 +31,6 @@ class StatisticsCallDevice extends Model
public function call() public function call()
{ {
return $this->hasOne(StatisticsCall::class, 'id', 'call_id'); return $this->belongsTo(StatisticsCall::class, 'id', 'call_id');
} }
} }

View file

@ -2,6 +2,12 @@
FlexiAPI can record logs generated by the FlexiSIP server and compile them into statistics. FlexiAPI can record logs generated by the FlexiSIP server and compile them into statistics.
### `GET /api/accounts/{id/me}/statistics/calls`
<span class="badge badge-warning">Admin</span>
<span class="badge badge-info">User</span>
Retrieve all the calls of an account, ordered by most recent first and paginated with their related `devices`.
### `POST /statistics/messages` ### `POST /statistics/messages`
<span class="badge badge-warning">Admin</span> <span class="badge badge-warning">Admin</span>

View file

@ -41,6 +41,7 @@ use App\Http\Controllers\Api\Admin\Account\CreationTokenController as AdminCreat
use App\Http\Controllers\Api\Admin\Account\DictionaryController; use App\Http\Controllers\Api\Admin\Account\DictionaryController;
use App\Http\Controllers\Api\Admin\Account\TypeController; use App\Http\Controllers\Api\Admin\Account\TypeController;
use App\Http\Controllers\Api\Admin\Account\VoicemailController as AdminVoicemailController; use App\Http\Controllers\Api\Admin\Account\VoicemailController as AdminVoicemailController;
use App\Http\Controllers\Api\Account\StatisticsCallController;
use App\Http\Controllers\Api\Admin\AccountController as AdminAccountController; use App\Http\Controllers\Api\Admin\AccountController as AdminAccountController;
use App\Http\Controllers\Api\Admin\ExternalAccountController; use App\Http\Controllers\Api\Admin\ExternalAccountController;
use App\Http\Controllers\Api\Admin\MessageController; use App\Http\Controllers\Api\Admin\MessageController;
@ -49,11 +50,11 @@ use App\Http\Controllers\Api\Admin\Space\CardDavServerController;
use App\Http\Controllers\Api\Admin\Space\ContactsListController; use App\Http\Controllers\Api\Admin\Space\ContactsListController;
use App\Http\Controllers\Api\Admin\Space\EmailServerController; use App\Http\Controllers\Api\Admin\Space\EmailServerController;
use App\Http\Controllers\Api\Admin\SpaceController; use App\Http\Controllers\Api\Admin\SpaceController;
use App\Http\Controllers\Api\Admin\StatisticsCallController as AdminStatisticsCallController;
use App\Http\Controllers\Api\Admin\VcardsStorageController as AdminVcardsStorageController; use App\Http\Controllers\Api\Admin\VcardsStorageController as AdminVcardsStorageController;
use App\Http\Controllers\Api\ApiController; use App\Http\Controllers\Api\ApiController;
use App\Http\Controllers\Api\PhoneCountryController; use App\Http\Controllers\Api\PhoneCountryController;
use App\Http\Controllers\Api\PingController; use App\Http\Controllers\Api\PingController;
use App\Http\Controllers\Api\StatisticsCallController;
use App\Http\Controllers\Api\StatisticsMessageController; use App\Http\Controllers\Api\StatisticsMessageController;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -114,6 +115,10 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
Route::apiResource('vcards-storage', VcardsStorageController::class); Route::apiResource('vcards-storage', VcardsStorageController::class);
Route::apiResource('voicemails', VoicemailController::class, ['only' => ['index', 'show', 'store', 'destroy']]); Route::apiResource('voicemails', VoicemailController::class, ['only' => ['index', 'show', 'store', 'destroy']]);
Route::apiResource('call_forwardings', CallForwardingController::class); Route::apiResource('call_forwardings', CallForwardingController::class);
Route::prefix('statistics/calls')->controller(\App\Http\Controllers\Api\Account\StatisticsCallController::class)->group(function () {
Route::get('/', 'index');
});
}); });
Route::group(['middleware' => ['auth.admin']], function () { Route::group(['middleware' => ['auth.admin']], function () {
@ -208,12 +213,16 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
Route::delete('/', 'destroy'); Route::delete('/', 'destroy');
}); });
Route::prefix('accounts/{id}/statistics/calls')->controller(AdminStatisticsCallController::class)->group(function () {
Route::get('/', 'index');
});
Route::prefix('statistics/messages')->controller(StatisticsMessageController::class)->group(function () { Route::prefix('statistics/messages')->controller(StatisticsMessageController::class)->group(function () {
Route::post('/', 'store'); Route::post('/', 'store');
Route::patch('{message_id}/to/{to}/devices/{device_id}', 'storeDevice'); Route::patch('{message_id}/to/{to}/devices/{device_id}', 'storeDevice');
}); });
Route::prefix('statistics/calls')->controller(StatisticsCallController::class)->group(function () { Route::prefix('statistics/calls')->controller(AdminStatisticsCallController::class)->group(function () {
Route::post('/', 'store'); Route::post('/', 'store');
Route::patch('{call_id}', 'update'); Route::patch('{call_id}', 'update');
Route::patch('{call_id}/devices/{device_id}', 'storeDevice'); Route::patch('{call_id}/devices/{device_id}', 'storeDevice');

View file

@ -31,6 +31,7 @@ class ApiStatisticsTest extends TestCase
protected $routeMessages = '/api/statistics/messages'; protected $routeMessages = '/api/statistics/messages';
protected $routeCalls = '/api/statistics/calls'; protected $routeCalls = '/api/statistics/calls';
protected $routeAccountCalls = '/api/accounts/me/statistics/calls';
public function testMessages() public function testMessages()
{ {
@ -140,6 +141,9 @@ class ApiStatisticsTest extends TestCase
'username' => $fromUsername, 'username' => $fromUsername,
'domain' => $fromDomain, 'domain' => $fromDomain,
]); ]);
$account->generateUserApiKey();
$routeAdminCalls = '/api/accounts/' . $account->id . '/statistics/calls';
$this->keyAuthenticated($admin) $this->keyAuthenticated($admin)
->json('POST', $this->routeCalls, [ ->json('POST', $this->routeCalls, [
@ -150,9 +154,19 @@ class ApiStatisticsTest extends TestCase
]) ])
->assertStatus(200); ->assertStatus(200);
$this->assertDatabaseHas('statistics_calls', [ $this->keyAuthenticated($admin)
'id' => $id ->get($routeAdminCalls)
]); ->assertStatus(200)
->assertJsonFragment([
'id' => $id
]);
$this->keyAuthenticated($account)
->get($this->routeAccountCalls)
->assertStatus(200)
->assertJsonFragment([
'id' => $id
]);
$this->keyAuthenticated($admin) $this->keyAuthenticated($admin)
->json('POST', $this->routeCalls, [ ->json('POST', $this->routeCalls, [
@ -207,6 +221,13 @@ class ApiStatisticsTest extends TestCase
$this->assertSame(1, StatisticsCallDevice::count()); $this->assertSame(1, StatisticsCallDevice::count());
$this->keyAuthenticated($admin)
->get($routeAdminCalls)
->assertStatus(200)
->assertJsonFragment([
'device_id' => $device
]);
// Update // Update
$endedAt = $this->faker->iso8601(); $endedAt = $this->faker->iso8601();