NasMikrotik::class, 'package-profile' => NasPackageProfile::class, 'olt' => NasOlt::class, 'webfig' => NasWebfigDevice::class, ]; private const SECRET_FIELDS = [ 'mikrotik' => ['api_password', 'radius_secret'], 'olt' => ['community', 'auth_password', 'privacy_password'], 'webfig' => ['password'], 'package-profile' => [], ]; public function list(string $resource, array $filters, User $actor, ?int $tenantId) { $model = $this->model($resource); return $model::query() ->with(['tenant:id,tenant_code,tenant_name', ...($resource === 'mikrotik' ? ['tenant.nasSetting'] : []), ...($resource === 'package-profile' ? ['scripts.latestVersion'] : [])]) ->when(! $actor->isMasterAdmin(), fn ($q) => $q->where('tenant_id', $tenantId)) ->when($actor->isMasterAdmin() && $tenantId, fn ($q) => $q->where('tenant_id', $tenantId)) ->when(! empty($filters['status']), fn ($q) => $q->where('status', $filters['status'])) ->when(! empty($filters['connection_type']) && $resource === 'mikrotik', fn ($q) => $q->where('connection_type', $filters['connection_type'])) ->when(! empty($filters['service_type']) && $resource === 'package-profile', fn ($q) => $q->where('service_type', $filters['service_type'])) ->when(! empty($filters['search']), function ($q) use ($filters, $resource) { $columns = match ($resource) { 'mikrotik' => ['name', 'host', 'api_username'], 'package-profile' => ['name', 'profile_code'], 'olt' => ['name', 'host', 'vendor', 'model'], 'webfig' => ['name', 'url', 'device_type'], }; $q->where(function ($search) use ($columns, $filters) { foreach ($columns as $index => $column) { $method = $index === 0 ? 'where' : 'orWhere'; $search->{$method}($column, 'ILIKE', "%{$filters['search']}%"); } }); }) ->orderBy($filters['sort_by'] ?? 'id', $filters['sort_direction'] ?? 'desc') ->paginate($filters['per_page'] ?? 10); } public function find(string $resource, int $id): Model { $model = $this->model($resource); return $model::with(['tenant:id,tenant_code,tenant_name', ...($resource === 'mikrotik' ? ['tenant.nasSetting'] : []), ...($resource === 'package-profile' ? ['scripts.latestVersion'] : [])])->findOrFail($id); } public function create(string $resource, array $data, User $actor, ?int $tenantId): Model { if ($resource === 'mikrotik') $data['connection_type'] = 'api'; if ($resource === 'package-profile') $data = $this->normalizePackageProfile($data); $scripts = $resource === 'package-profile' ? ($data['scripts'] ?? []) : []; unset($data['scripts']); $data['tenant_id'] = $actor->isMasterAdmin() ? ($data['tenant_id'] ?? $tenantId) : $tenantId; if (! $data['tenant_id']) { throw ValidationException::withMessages(['tenant_id' => 'Tenant wajib dipilih.']); } $data['status'] ??= 'active'; $model = $this->model($resource); $created = $model::create($data); if ($resource === 'package-profile') $this->syncProfileScripts($created, $scripts); return $created->load('tenant:id,tenant_code,tenant_name'); } public function update(string $resource, Model $model, array $data, User $actor): Model { if ($resource === 'mikrotik') $data['connection_type'] = 'api'; if ($resource === 'package-profile') $data = $this->normalizePackageProfile($data, $model); $hasScripts = $resource === 'package-profile' && array_key_exists('scripts', $data); $scripts = $data['scripts'] ?? []; unset($data['scripts']); if (! $actor->isMasterAdmin()) { unset($data['tenant_id']); } foreach (self::SECRET_FIELDS[$resource] as $field) { if (array_key_exists($field, $data) && blank($data[$field])) { unset($data[$field]); } } $model->update($data); if ($hasScripts) $this->syncProfileScripts($model, $scripts); return $model->fresh()->load('tenant:id,tenant_code,tenant_name'); } private function syncProfileScripts(Model $profile, array $scripts): void { $sync=[]; foreach($scripts as $row) { $policy = NasScriptPolicy::with('versions:id,nas_script_policy_id')->findOrFail($row['policy_id']); if ($policy->service_type !== $profile->service_type || $policy->event !== $row['event']) { throw ValidationException::withMessages(['scripts' => "Script {$policy->name} tidak sesuai tipe layanan atau event profile."]); } if (($row['version_id'] ?? null) && ! $policy->versions->contains('id', $row['version_id'])) { throw ValidationException::withMessages(['scripts' => "Versi Script {$policy->name} tidak valid."]); } $sync[$row['policy_id']]=['event'=>$row['event'],'nas_script_policy_version_id'=>$row['version_id'] ?? null,'enabled'=>true]; } $profile->scripts()->sync($sync); } private function normalizePackageProfile(array $data, ?Model $profile = null): array { $type = $data['service_type'] ?? $profile?->service_type; if ($type !== 'hotspot') { foreach (['usage_mode','validity_start','validity_value','validity_unit','uptime_limit_seconds','data_limit_bytes','grace_period_minutes'] as $field) $data[$field] = null; } if ($type !== 'ppp') foreach (['local_address', 'remote_address_pool'] as $field) $data[$field] = null; if ($type !== 'hotspot') foreach (['session_timeout', 'idle_timeout', 'shared_users'] as $field) $data[$field] = null; if (! in_array($type, ['static_ip', 'dynamic_ip'], true)) { foreach (['ip_pool','subnet_cidr','gateway','dns_servers','vlan_id'] as $field) $data[$field] = null; $data['mac_lock_required'] = false; } else { $data['mac_lock_required'] = true; if ($type === 'static_ip') $data['ip_pool'] = null; } return $data; } public function delete(Model $model): bool { return $model->delete(); } private function model(string $resource): string { abort_unless(isset(self::MODELS[$resource]), 404); return self::MODELS[$resource]; } }