mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 10:08:05 +00:00
Fix FLEXIAPI-366 Add voicemails endpoints
This commit is contained in:
parent
09d386a303
commit
76436d7407
21 changed files with 525 additions and 36 deletions
|
|
@ -1,9 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/
|
||||
sudo -su www-data && php artisan digest:clear-nonces 60
|
||||
sudo -su www-data && php artisan accounts:clear-api-keys 60
|
||||
sudo -su www-data && php artisan accounts:clear-accounts-tombstones 7 --apply
|
||||
sudo -su www-data && php artisan accounts:clear-api-keys 60
|
||||
sudo -su www-data && php artisan accounts:clear-files 30 --apply
|
||||
sudo -su www-data && php artisan accounts:clear-unconfirmed 30 --apply
|
||||
sudo -su www-data && php artisan spaces:expiration-emails
|
||||
sudo -su www-data && php artisan app:clear-statistics 30 --apply
|
||||
sudo -su www-data && php artisan digest:clear-nonces 60
|
||||
sudo -su www-data && php artisan spaces:expiration-emails
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/
|
||||
php artisan digest:clear-nonces 60
|
||||
php artisan accounts:clear-api-keys 60
|
||||
php artisan accounts:clear-accounts-tombstones 7 --apply
|
||||
php artisan accounts:clear-api-keys 60
|
||||
php artisan accounts:clear-files 30 --apply
|
||||
php artisan accounts:clear-unconfirmed 30 --apply
|
||||
php artisan app:clear-statistics 30 --apply
|
||||
php artisan digest:clear-nonces 60
|
||||
php artisan spaces:expiration-emails
|
||||
php artisan app:clear-statistics 30 --apply
|
||||
|
|
@ -134,6 +134,18 @@ class Account extends Authenticatable
|
|||
return $this->belongsToMany(Account::class, 'contacts', 'account_id', 'contact_id');
|
||||
}
|
||||
|
||||
public function files()
|
||||
{
|
||||
return $this->hasMany(AccountFile::class)->latest();
|
||||
}
|
||||
|
||||
public function voicemails()
|
||||
{
|
||||
return $this->hasMany(AccountFile::class)
|
||||
->whereIn('content_type', AccountFile::VOICEMAIL_CONTENTTYPES)
|
||||
->latest();
|
||||
}
|
||||
|
||||
public function vcardsStorage()
|
||||
{
|
||||
return $this->hasMany(VcardStorage::class);
|
||||
|
|
|
|||
52
flexiapi/app/AccountFile.php
Normal file
52
flexiapi/app/AccountFile.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AccountFile extends Model
|
||||
{
|
||||
use HasUuids;
|
||||
|
||||
public const VOICEMAIL_CONTENTTYPES = ['audio/opus', 'audio/wav'];
|
||||
public const FILES_PATH = 'files';
|
||||
protected $hidden = ['account_id', 'updated_at'];
|
||||
protected $appends = ['download_url'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::deleting(function ($category) {
|
||||
Storage::delete($this->getPathAttribute());
|
||||
});
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function getMaxUploadSizeAttribute(): ?int
|
||||
{
|
||||
return maxUploadSize();
|
||||
}
|
||||
|
||||
public function getUploadUrlAttribute(): ?string
|
||||
{
|
||||
return route('file.upload', $this->attributes['id']);
|
||||
}
|
||||
|
||||
public function getPathAttribute(): string
|
||||
{
|
||||
return self::FILES_PATH . '/' . $this->attributes['name'];
|
||||
}
|
||||
|
||||
public function getDownloadUrlAttribute(): ?string
|
||||
{
|
||||
return !empty($this->attributes['name'])
|
||||
&& !empty($this->attributes['id'])
|
||||
? route('file.show', [$this->attributes['id'], $this->attributes['name']])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,11 +29,6 @@ class ClearAccountsTombstones extends Command
|
|||
protected $signature = 'accounts:clear-accounts-tombstones {days} {--apply}';
|
||||
protected $description = 'Clear deleted accounts tombstones after n days';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$tombstones = AccountTombstone::where(
|
||||
|
|
|
|||
35
flexiapi/app/Console/Commands/Accounts/ClearFiles.php
Normal file
35
flexiapi/app/Console/Commands/Accounts/ClearFiles.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands\Accounts;
|
||||
|
||||
use App\AccountFile;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearFiles extends Command
|
||||
{
|
||||
protected $signature = 'accounts:clear-files {days} {--apply}';
|
||||
protected $description = 'Remove the uploaded files after n days';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$files = AccountFile::where(
|
||||
'created_at',
|
||||
'<',
|
||||
Carbon::now()->subDays($this->argument('days'))->toDateTimeString()
|
||||
);
|
||||
|
||||
$count = $files->count();
|
||||
|
||||
if ($this->option('apply')) {
|
||||
$this->info($count . ' files in deletion…');
|
||||
$files->delete();
|
||||
$this->info($count . ' files deleted');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info($count . ' files to delete');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,11 +29,6 @@ class ClearUnconfirmed extends Command
|
|||
protected $signature = 'accounts:clear-unconfirmed {days} {--apply} {--and-confirmed}';
|
||||
protected $description = 'Clear unconfirmed accounts after n days';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$accounts = Account::where(
|
||||
|
|
|
|||
|
|
@ -30,11 +30,6 @@ class CreateAdminTest extends Command
|
|||
protected $signature = 'accounts:create-admin-test';
|
||||
protected $description = 'Create a test admin account, only for tests purpose';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$username = 'admin_test';
|
||||
|
|
|
|||
|
|
@ -28,11 +28,6 @@ class Seed extends Command
|
|||
protected $signature = 'accounts:seed {json-file-path}';
|
||||
protected $description = 'Seed some accounts from a JSON file';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$file = $this->argument('json-file-path');
|
||||
|
|
|
|||
|
|
@ -28,11 +28,6 @@ class SetAdmin extends Command
|
|||
protected $signature = 'accounts:set-admin {id}';
|
||||
protected $description = 'Give the admin role to an account';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$account = Account::withoutGlobalScopes()->where('id', $this->argument('id'))->first();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Account;
|
||||
|
|
@ -30,13 +31,13 @@ use Illuminate\Support\Facades\DB;
|
|||
|
||||
function space(): ?Space
|
||||
{
|
||||
return is_object(request()->space) ? request()->space :null;
|
||||
return is_object(request()->space) ? request()->space : null;
|
||||
}
|
||||
|
||||
function passwordAlgorithms(): array
|
||||
{
|
||||
return [
|
||||
'MD5' => 'md5',
|
||||
'MD5' => 'md5',
|
||||
'SHA-256' => 'sha256',
|
||||
];
|
||||
}
|
||||
|
|
@ -100,7 +101,7 @@ function markdownDocumentationView(string $view): string
|
|||
$converter->getEnvironment()->addExtension(new TableOfContentsExtension());
|
||||
|
||||
return (string) $converter->convert(
|
||||
(string)view($view, [
|
||||
(string) view($view, [
|
||||
'app_name' => space()->name
|
||||
])->render()
|
||||
);
|
||||
|
|
@ -161,6 +162,19 @@ function resolveDomain(Request $request): string
|
|||
: $request->space->domain;
|
||||
}
|
||||
|
||||
function maxUploadSize(): ?int
|
||||
{
|
||||
$uploadMaxSizeInBytes = ini_parse_quantity(ini_get('upload_max_filesize'));
|
||||
if ($uploadMaxSizeInBytes > 0) {
|
||||
return $uploadMaxSizeInBytes / 1024;
|
||||
}
|
||||
|
||||
$postMaxSizeInBytes = ini_parse_quantity(ini_get('post_max_size'));
|
||||
if ($postMaxSizeInBytes > 0) {
|
||||
return $postMaxSizeInBytes / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
function captchaConfigured(): bool
|
||||
{
|
||||
return env('HCAPTCHA_SECRET', false) != false || env('HCAPTCHA_SITEKEY', false) != false;
|
||||
|
|
@ -205,7 +219,7 @@ function validateIsoDate($attribute, $value, $parameters, $validator): bool
|
|||
// Regex from https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
|
||||
: '/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/';
|
||||
|
||||
return (bool)preg_match($regex, $value);
|
||||
return (bool) preg_match($regex, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
51
flexiapi/app/Http/Controllers/Api/Account/FileController.php
Normal file
51
flexiapi/app/Http/Controllers/Api/Account/FileController.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\AccountFile;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileController extends Controller
|
||||
{
|
||||
public function show(string $uuid, string $name)
|
||||
{
|
||||
$file = AccountFile::findOrFail($uuid);
|
||||
|
||||
if ($file->name != $name) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return Storage::download($file->path);
|
||||
}
|
||||
|
||||
public function upload(Request $request, string $uuid)
|
||||
{
|
||||
$file = AccountFile::findOrFail($uuid);
|
||||
|
||||
if (!empty($file->name)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimetypes:' . $file->content_type
|
||||
]);
|
||||
|
||||
$uploadedFile = $request->file('file');
|
||||
$name = Str::random(8) . '_' . $uploadedFile->getClientOriginalName();
|
||||
|
||||
if ($uploadedFile->storeAs('files', $name)) {
|
||||
$file->name = $name;
|
||||
$file->size = $uploadedFile->getSize();
|
||||
$file->uploaded_at = Carbon::now();
|
||||
$file->save();
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
abort(503);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Api\Admin\Account\VoicemailController as AdminVoicemailController;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VoicemailController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return (new AdminVoicemailController)->index($request, $request->user()->id);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
return (new AdminVoicemailController)->store($request, $request->user()->id);
|
||||
}
|
||||
|
||||
public function show(Request $request, string $uuid)
|
||||
{
|
||||
return (new AdminVoicemailController)->show($request, $request->user()->id, $uuid);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, string $uuid)
|
||||
{
|
||||
return (new AdminVoicemailController)->destroy($request, $request->user()->id, $uuid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin\Account;
|
||||
|
||||
use App\Account;
|
||||
use App\AccountFile;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class VoicemailController extends Controller
|
||||
{
|
||||
public function index(Request $request, int $accountId)
|
||||
{
|
||||
return Account::findOrFail($accountId)->voicemails;
|
||||
}
|
||||
|
||||
public function store(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$request->validate([
|
||||
'sip_from' => 'nullable|starts_with:sip',
|
||||
'content_type' => [
|
||||
'required',
|
||||
Rule::in(AccountFile::VOICEMAIL_CONTENTTYPES),
|
||||
]
|
||||
]);
|
||||
|
||||
$voicemail = new AccountFile;
|
||||
$voicemail->account_id = $account->id;
|
||||
$voicemail->sip_from = $request->get('sip_from');
|
||||
$voicemail->content_type = $request->get('content_type');
|
||||
$voicemail->save();
|
||||
|
||||
$voicemail->append(['upload_url', 'max_upload_size']);
|
||||
|
||||
return $voicemail;
|
||||
}
|
||||
|
||||
public function show(Request $request, int $accountId, string $uuid)
|
||||
{
|
||||
return Account::findOrFail($accountId)->voicemails()->where('id', $uuid)->firstOrFail();
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $accountId, string $uuid)
|
||||
{
|
||||
return Account::findOrFail($accountId)->voicemails()->where('id', $uuid)->delete();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?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('account_files', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->integer('account_id')->unsigned()->nullable();
|
||||
$table->foreign('account_id')->references('id')
|
||||
->on('accounts')->onDelete('cascade');
|
||||
$table->string('name')->nullable();
|
||||
$table->integer('size')->nullable();
|
||||
$table->string('content_type')->index();
|
||||
$table->text('sip_from')->nullable();
|
||||
$table->dateTime('uploaded_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('account_files');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
## Voicemails
|
||||
|
||||
### `GET /accounts/{id/me}/voicemails`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
<span class="badge badge-info">User</span>
|
||||
|
||||
Return the currently stored voicemails
|
||||
|
||||
### `GET /accounts/{id/me}/voicemails/{uuid}`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
<span class="badge badge-info">User</span>
|
||||
|
||||
```
|
||||
{
|
||||
id: '{uuid}',
|
||||
sip_from: '{sip_address}',
|
||||
get_url: 'https://{the file_url}',
|
||||
file_size: 2451400, // the file size, in bytes
|
||||
content_type: 'audio/{format}',
|
||||
created_at: '2025-10-09T12:59:32Z',
|
||||
uploaded_at: '2025-10-09T12:59:40Z'
|
||||
}
|
||||
```
|
||||
|
||||
Return a stored voicemail
|
||||
|
||||
### `POST /accounts/{id/me}/voicemails`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
<span class="badge badge-info">User</span>
|
||||
|
||||
Create a new voicemail slot
|
||||
|
||||
JSON parameters:
|
||||
|
||||
* `sip_from`, mandatory, a valid SIP address
|
||||
* `content_type`, mandatory, the content type of the audio file to upload, must be `audio/opus` or `audio/wav`
|
||||
|
||||
This endpoint will return the following JSON:
|
||||
|
||||
```
|
||||
{
|
||||
id: '{uuid}',
|
||||
sip_from: '{sip_address}',
|
||||
upload_url: 'https://{upload_service_unique_url}', // unique URL generated to upload the audio file
|
||||
download_url: 'https://{download_service_unique_url}', // unique URL generated to download the audio file, null before upload
|
||||
max_upload_size: 3000000, // here 3MB file size limit, in bytes
|
||||
content_type: 'audio/{format}',
|
||||
created_at: '2025-10-09T12:59:32Z', // time of the slot creation
|
||||
uploaded_at: null // time when the slot was filled with the audio file
|
||||
}
|
||||
```
|
||||
|
||||
### `DELETE /accounts/{id/me}/voicemails/{uuid}`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
<span class="badge badge-info">User</span>
|
||||
|
||||
Delete a stored voicemail, if the file is managed by the platform it will be deleted as well
|
||||
|
|
@ -39,6 +39,23 @@ Returns `pong`
|
|||
|
||||
@include('api.documentation.accounts.vcards_storage')
|
||||
|
||||
@include('api.documentation.accounts.voicemail')
|
||||
|
||||
## File Upload
|
||||
|
||||
### `POST /files/{uuid}`
|
||||
<span class="badge badge-info">User</span>
|
||||
|
||||
Upload a file to a previously created slot. This endpoint will directly be returned when creating the upload slot in the `upload_url` parameter.
|
||||
|
||||
Related endpoints:
|
||||
|
||||
* [Voicemails](#voicemails)
|
||||
|
||||
HTTP [Form-Data](https://developer.mozilla.org/fr/docs/Web/API/FormData) parameters:
|
||||
|
||||
* `file` **required**, the file to upload, must have the same `content_type` as requested in the slot
|
||||
|
||||
## Messages
|
||||
|
||||
### `POST /messages`
|
||||
|
|
|
|||
|
|
@ -25,17 +25,20 @@ use App\Http\Controllers\Api\Account\CreationRequestToken;
|
|||
use App\Http\Controllers\Api\Account\CreationTokenController;
|
||||
use App\Http\Controllers\Api\Account\DeviceController;
|
||||
use App\Http\Controllers\Api\Account\EmailController;
|
||||
use App\Http\Controllers\Api\Account\FileController;
|
||||
use App\Http\Controllers\Api\Account\PasswordController;
|
||||
use App\Http\Controllers\Api\Account\PhoneController;
|
||||
use App\Http\Controllers\Api\Account\PushNotificationController;
|
||||
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\CardDavCredentialsController;
|
||||
use App\Http\Controllers\Api\Admin\Account\ContactController as AdminContactController;
|
||||
use App\Http\Controllers\Api\Admin\Account\CreationTokenController as AdminCreationTokenController;
|
||||
use App\Http\Controllers\Api\Admin\Account\DictionaryController;
|
||||
use App\Http\Controllers\Api\Admin\Account\TypeController;
|
||||
use App\Http\Controllers\Api\Admin\Account\VoicemailController as AdminVoicemailController;
|
||||
use App\Http\Controllers\Api\Admin\AccountController as AdminAccountController;
|
||||
use App\Http\Controllers\Api\Admin\ExternalAccountController;
|
||||
use App\Http\Controllers\Api\Admin\MessageController;
|
||||
|
|
@ -78,6 +81,7 @@ Route::get('phone_countries', [PhoneCountryController::class, 'index']);
|
|||
Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blocked']], function () {
|
||||
Route::get('accounts/auth_token/{auth_token}/attach', [AuthTokenController::class, 'attach']);
|
||||
Route::post('account_creation_tokens/consume', [CreationTokenController::class, 'consume']);
|
||||
Route::post('files/{uuid}', 'Api\Account\FileController@upload')->name('file.upload');
|
||||
|
||||
Route::post('push_notification', [PushNotificationController::class, 'push']);
|
||||
|
||||
|
|
@ -105,6 +109,7 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
|
|||
Route::get('contacts', [ContactController::class, 'index']);
|
||||
|
||||
Route::apiResource('vcards-storage', VcardsStorageController::class);
|
||||
Route::apiResource('voicemails', VoicemailController::class, ['only' => ['index', 'show', 'store', 'destroy']]);
|
||||
});
|
||||
|
||||
Route::group(['middleware' => ['auth.admin']], function () {
|
||||
|
|
@ -174,6 +179,7 @@ Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key', 'auth.check_blo
|
|||
Route::apiResource('accounts/{id}/actions', ActionController::class);
|
||||
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('contacts_lists', ContactsListController::class);
|
||||
Route::prefix('contacts_lists')->controller(ContactsListController::class)->group(function () {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ use App\Http\Controllers\Admin\Space\ContactsListController;
|
|||
use App\Http\Controllers\Admin\Space\EmailServerController;
|
||||
use App\Http\Controllers\Admin\SpaceController;
|
||||
use App\Http\Controllers\Admin\StatisticsController;
|
||||
use App\Http\Controllers\Api\Account\FileController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::redirect('/', 'login')->name('account.home');
|
||||
|
|
@ -77,6 +78,8 @@ Route::middleware(['feature.web_panel_enabled'])->group(function () {
|
|||
});
|
||||
|
||||
Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key']], function () {
|
||||
Route::get('files/{uuid}/{name}', [FileController::class, 'show'])->name('file.show');
|
||||
|
||||
Route::get('provisioning/me', [ProvisioningController::class, 'me'])->name('provisioning.me');
|
||||
|
||||
// vCard 4.0
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ namespace Tests\Feature;
|
|||
|
||||
use App\Account;
|
||||
use App\Space;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ApiSpaceTest extends TestCase
|
||||
|
|
|
|||
157
flexiapi/tests/Feature/ApiVoicemailTest.php
Normal file
157
flexiapi/tests/Feature/ApiVoicemailTest.php
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2025 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 Illuminate\Http\UploadedFile;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ApiVoicemailTest extends TestCase
|
||||
{
|
||||
protected $route = '/api/accounts/me/voicemails';
|
||||
protected $uploadRoute = '/api/files/';
|
||||
|
||||
public function testAccount()
|
||||
{
|
||||
$account = Account::factory()->create();
|
||||
$account->generateUserApiKey();
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('POST', $this->route, [])
|
||||
->assertJsonValidationErrors(['content_type']);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('POST', $this->route, [
|
||||
'content_type' => 'image/jpg'
|
||||
])
|
||||
->assertJsonValidationErrors(['content_type']);
|
||||
|
||||
$accountFile = $this->keyAuthenticated($account)
|
||||
->json('POST', $this->route, [
|
||||
'content_type' => 'audio/opus'
|
||||
])->assertCreated();
|
||||
|
||||
$uuid = $accountFile->json()['id'];
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->get($this->route)
|
||||
->assertJsonFragment(['id' => $uuid]);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->get($this->route . '/' . $uuid)
|
||||
->assertJsonFragment([
|
||||
'id' => $uuid,
|
||||
'name' => null,
|
||||
'size' => null,
|
||||
'sip_from' => null,
|
||||
'uploaded_at' => null,
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->delete($this->route . '/' . $uuid)
|
||||
->assertOk();
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->get($this->route . '/' . $uuid)
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function testAdmin()
|
||||
{
|
||||
$admin = Account::factory()->admin()->create();
|
||||
$admin->generateUserApiKey();
|
||||
|
||||
$account = Account::factory()->create();
|
||||
$account->generateUserApiKey();
|
||||
|
||||
$adminRoute = '/api/accounts/' . $account->id . '/voicemails';
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('POST', $adminRoute, [])
|
||||
->assertForbidden();
|
||||
|
||||
$accountFile = $this->keyAuthenticated($admin)
|
||||
->json('POST', $adminRoute, [
|
||||
'content_type' => 'audio/opus'
|
||||
])->assertCreated();
|
||||
|
||||
$uuid = $accountFile->json()['id'];
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->get($adminRoute . '/' . $uuid)
|
||||
->assertJsonFragment(['id' => $uuid]);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->get($adminRoute . '/' . $uuid)
|
||||
->assertJsonFragment([
|
||||
'id' => $uuid
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->delete($adminRoute . '/' . $uuid)
|
||||
->assertOk();
|
||||
|
||||
$this->keyAuthenticated($admin)
|
||||
->get($adminRoute . '/' . $uuid)
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function testUpload()
|
||||
{
|
||||
$account = Account::factory()->create();
|
||||
$account->generateUserApiKey();
|
||||
|
||||
$accountFile = $this->keyAuthenticated($account)
|
||||
->json('POST', $this->route, [
|
||||
'content_type' => 'audio/opus'
|
||||
])->assertCreated();
|
||||
|
||||
$uuid = $accountFile->json()['id'];
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('POST', $this->uploadRoute . $uuid, [
|
||||
'file' => UploadedFile::fake()->image('photo.jpg')
|
||||
])->assertJsonValidationErrors(['file']);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('POST', $this->uploadRoute . $uuid, [
|
||||
'file' => UploadedFile::fake()->create('audio.wav', 500, 'audio/wav')
|
||||
])->assertJsonValidationErrors(['file']);
|
||||
|
||||
$file = $this->keyAuthenticated($account)
|
||||
->json('POST', $this->uploadRoute . $uuid, data: [
|
||||
'file' => UploadedFile::fake()->create('audio.opus', 500, 'audio/opus')
|
||||
])->assertOk();
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json('POST', $this->uploadRoute . $uuid, data: [
|
||||
'file' => UploadedFile::fake()->create('audio.opus', 500, 'audio/opus')
|
||||
])->assertNotFound();
|
||||
|
||||
$this->head($file->json()['download_url'])->assertOk();
|
||||
|
||||
// Delete the file
|
||||
$this->keyAuthenticated($account)
|
||||
->delete($this->route . '/' . $file->json()['id'])
|
||||
->assertOk();
|
||||
|
||||
$this->head($file->json()['download_url'])->assertNotFound();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue