215 خطوط
8.6 KiB
PHP
215 خطوط
8.6 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Setting;
|
|
use App\Services\ActivityLogger;
|
|
use App\Services\VoIP\VoIPManager;
|
|
use App\Support\SettingsCatalog;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
class SettingController extends Controller
|
|
{
|
|
public function __construct(private VoIPManager $voipManager) {}
|
|
|
|
public function index(): JsonResponse
|
|
{
|
|
Gate::authorize('viewAny', Setting::class);
|
|
SettingsCatalog::ensureDefaults();
|
|
$runtimeKeys = collect(SettingsCatalog::definitions())
|
|
->where('is_runtime_enforced', true)
|
|
->pluck('key');
|
|
|
|
$settings = Setting::whereIn('key', $runtimeKeys)->get()
|
|
->map(function (Setting $setting) {
|
|
$meta = SettingsCatalog::metaFor($setting->key) ?? [];
|
|
if ($setting->is_secret) {
|
|
$setting->value = $setting->value ? '********' : '';
|
|
}
|
|
|
|
return array_merge($setting->toArray(), [
|
|
'label' => $meta['label'] ?? $setting->key,
|
|
'hint' => $meta['hint'] ?? $setting->description,
|
|
'used_by' => $meta['used_by'] ?? [],
|
|
'coming_soon' => $meta['coming_soon'] ?? ! $setting->is_runtime_enforced,
|
|
]);
|
|
})
|
|
->groupBy('group');
|
|
|
|
return response()->json($settings);
|
|
}
|
|
|
|
public function update(Request $request): JsonResponse
|
|
{
|
|
Gate::authorize('update', Setting::class);
|
|
SettingsCatalog::ensureDefaults();
|
|
|
|
$request->validate([
|
|
'settings' => 'required|array',
|
|
'settings.*.key' => 'required|string',
|
|
'settings.*.value' => 'nullable',
|
|
'settings.*.group' => 'nullable|string',
|
|
'settings.*.type' => 'nullable|string',
|
|
]);
|
|
|
|
foreach ($request->settings as $setting) {
|
|
$existing = Setting::where('key', $setting['key'])->first();
|
|
$this->validateSettingValue($setting, $existing);
|
|
|
|
$oldValue = $existing?->value;
|
|
$newValue = ($existing?->is_secret && ($setting['value'] ?? '') === '********')
|
|
? $oldValue
|
|
: $this->normalizeValue($setting['value'] ?? '', $setting['type'] ?? $existing?->type ?? 'string');
|
|
|
|
$meta = SettingsCatalog::metaFor($setting['key']);
|
|
Setting::updateOrCreate(
|
|
['key' => $setting['key']],
|
|
[
|
|
'value' => $newValue,
|
|
'group' => $meta['group'] ?? $setting['group'] ?? $existing?->group ?? 'general',
|
|
'type' => $meta['type'] ?? $setting['type'] ?? $existing?->type ?? 'string',
|
|
'default_value' => $meta['default_value'] ?? $existing?->default_value,
|
|
'allowed_values' => $meta['allowed_values'] ?? $existing?->allowed_values,
|
|
'is_secret' => $meta['is_secret'] ?? $existing?->is_secret ?? false,
|
|
'is_public' => $meta['is_public'] ?? $existing?->is_public ?? false,
|
|
'is_runtime_enforced' => $meta['is_runtime_enforced'] ?? $existing?->is_runtime_enforced ?? true,
|
|
'description' => $meta['hint'] ?? $existing?->description,
|
|
]
|
|
);
|
|
|
|
if ($this->isSensitiveChange($setting['key'], $meta ?? null)) {
|
|
$old = ($existing?->is_secret ?? false) ? '[secret]' : $oldValue;
|
|
ActivityLogger::log('sensitive_setting_changed', "Setting {$setting['key']} changed from {$old}");
|
|
}
|
|
}
|
|
|
|
ActivityLogger::log('settings_updated', 'System settings updated');
|
|
|
|
return response()->json(['message' => 'تنظیمات ذخیره شد']);
|
|
}
|
|
|
|
public function public(): JsonResponse
|
|
{
|
|
SettingsCatalog::ensureDefaults();
|
|
$publicRuntimeKeys = collect(SettingsCatalog::definitions())
|
|
->where('is_runtime_enforced', true)
|
|
->where('is_public', true)
|
|
->pluck('key');
|
|
$settings = Setting::whereIn('key', $publicRuntimeKeys)->get()
|
|
->filter(fn (Setting $setting) => ! $setting->is_secret)
|
|
->mapWithKeys(fn (Setting $setting) => [$setting->key => $setting->effectiveValue()]);
|
|
|
|
return response()->json($settings);
|
|
}
|
|
|
|
public function testVoip(): JsonResponse
|
|
{
|
|
Gate::authorize('viewAny', Setting::class);
|
|
$provider = Setting::where('key', 'voip_provider')->value('value') ?: 'none';
|
|
$missing = [];
|
|
if ($provider === 'ami') {
|
|
foreach (['voip_ami_host', 'voip_ami_port', 'voip_ami_username', 'voip_ami_secret', 'voip_ami_channel_technology', 'voip_ami_context'] as $key) {
|
|
if (! Setting::where('key', $key)->value('value')) {
|
|
$missing[] = $key;
|
|
}
|
|
}
|
|
}
|
|
if ($provider === 'api') {
|
|
foreach (['voip_api_base_url', 'voip_api_token'] as $key) {
|
|
if (! Setting::where('key', $key)->value('value')) {
|
|
$missing[] = $key;
|
|
}
|
|
}
|
|
}
|
|
if ($provider === 'socket') {
|
|
foreach (['voip_socket_host', 'voip_socket_port'] as $key) {
|
|
if (! Setting::where('key', $key)->value('value')) {
|
|
$missing[] = $key;
|
|
}
|
|
}
|
|
}
|
|
|
|
ActivityLogger::log('voip_connection_tested', "VoIP provider {$provider} tested");
|
|
if ($missing === []) {
|
|
$result = $this->voipManager->testConnection();
|
|
|
|
return response()->json([
|
|
'ok' => (bool) ($result['ok'] ?? false),
|
|
'provider' => $provider,
|
|
'message' => $result['message'] ?? 'نتیجه تست اتصال مشخص نیست.',
|
|
'missing' => $result['missing'] ?? [],
|
|
]);
|
|
}
|
|
|
|
return response()->json([
|
|
'ok' => ! $missing,
|
|
'provider' => $provider,
|
|
'message' => $missing ? 'تنظیمات اتصال کامل نیست.' : 'تنظیمات اتصال معتبر است.',
|
|
'missing' => $missing,
|
|
]);
|
|
}
|
|
|
|
public function testVoipCall(Request $request): JsonResponse
|
|
{
|
|
Gate::authorize('update', Setting::class);
|
|
$validated = $request->validate([
|
|
'phone' => ['required', 'string', 'max:30', 'regex:/^[0-9+*#]+$/'],
|
|
'extension' => ['required', 'string', 'max:20', 'regex:/^[0-9*#]+$/'],
|
|
]);
|
|
$result = $this->voipManager->initiateCall($validated['phone'], $validated['extension']);
|
|
ActivityLogger::log('voip_test_call_requested', 'A real VoIP test call was requested', null, null, [
|
|
'success' => (bool) ($result['success'] ?? false),
|
|
'status' => $result['status'] ?? null,
|
|
]);
|
|
|
|
return response()->json([
|
|
'ok' => (bool) ($result['success'] ?? false),
|
|
'message' => $result['message'] ?? 'نتیجه تماس آزمایشی مشخص نیست.',
|
|
'provider_call_id' => $result['provider_call_id'] ?? null,
|
|
'status' => $result['status'] ?? null,
|
|
], ($result['success'] ?? false) ? 200 : 422);
|
|
}
|
|
|
|
private function validateSettingValue(array $payload, ?Setting $existing): void
|
|
{
|
|
$type = $payload['type'] ?? $existing?->type ?? 'string';
|
|
$value = $payload['value'] ?? '';
|
|
$rules = $existing?->validation_rules ?: match ($type) {
|
|
'boolean' => ['in:true,false,1,0'],
|
|
'integer' => ['integer'],
|
|
default => ['nullable', 'string'],
|
|
};
|
|
if ($type === 'boolean') {
|
|
$rules = ['in:true,false,1,0'];
|
|
}
|
|
|
|
if ($existing?->allowed_values) {
|
|
$rules[] = 'in:'.implode(',', $existing->allowed_values);
|
|
}
|
|
|
|
Validator::make(['value' => $value], ['value' => $rules])->validate();
|
|
}
|
|
|
|
private function normalizeValue(mixed $value, string $type): string
|
|
{
|
|
return match ($type) {
|
|
'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false',
|
|
'integer' => (string) (int) $value,
|
|
default => (string) $value,
|
|
};
|
|
}
|
|
|
|
private function isSensitiveChange(string $key, ?array $meta): bool
|
|
{
|
|
return ($meta['is_secret'] ?? false)
|
|
|| in_array($meta['group'] ?? '', ['security', 'import_export', 'voip'], true)
|
|
|| str_contains($key, 'password')
|
|
|| str_contains($key, 'token')
|
|
|| str_contains($key, 'export');
|
|
}
|
|
}
|