CRM/backend/app/Http/Controllers/Api/SettingController.php

179 خطوط
7.0 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();
$settings = Setting::all()
->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();
$settings = Setting::where('is_public', true)->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') ?: 'mock';
$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,
]);
}
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');
}
}