ruleDefModel = new RuleDefModel(); $this->ruleActionModel = new RuleActionModel(); } public function index() { try { $eventCode = $this->request->getGet('EventCode'); $active = $this->request->getGet('Active'); $scopeType = $this->request->getGet('ScopeType'); $testSiteID = $this->request->getGet('TestSiteID'); $search = $this->request->getGet('search'); $builder = $this->ruleDefModel->where('EndDate', null); if ($eventCode !== null && $eventCode !== '') { $builder->where('EventCode', $eventCode); } if ($active !== null && $active !== '') { $builder->where('Active', (int) $active); } if ($scopeType !== null && $scopeType !== '') { $builder->where('ScopeType', $scopeType); } if ($testSiteID !== null && $testSiteID !== '' && is_numeric($testSiteID)) { $builder->where('TestSiteID', (int) $testSiteID); } if ($search !== null && $search !== '') { $builder->like('Name', $search); } $rows = $builder ->orderBy('Priority', 'ASC') ->orderBy('RuleID', 'ASC') ->findAll(); return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $rows, ], 200); } catch (\Throwable $e) { log_message('error', 'RuleController::index error: ' . $e->getMessage()); return $this->respond([ 'status' => 'failed', 'message' => 'Failed to fetch rules', 'data' => [], ], 500); } } public function show($id = null) { try { if (!$id || !is_numeric($id)) { return $this->failValidationErrors('RuleID is required'); } $rule = $this->ruleDefModel->where('EndDate', null)->find((int) $id); if (!$rule) { return $this->respond([ 'status' => 'failed', 'message' => 'Rule not found', 'data' => [], ], 404); } $actions = $this->ruleActionModel ->where('RuleID', (int) $id) ->where('EndDate', null) ->orderBy('Seq', 'ASC') ->orderBy('RuleActionID', 'ASC') ->findAll(); $rule['actions'] = $actions; return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $rule, ], 200); } catch (\Throwable $e) { log_message('error', 'RuleController::show error: ' . $e->getMessage()); return $this->respond([ 'status' => 'failed', 'message' => 'Failed to fetch rule', 'data' => [], ], 500); } } public function create() { $input = $this->request->getJSON(true) ?? []; $validation = service('validation'); $validation->setRules([ 'Name' => 'required|max_length[100]', 'EventCode' => 'required|max_length[50]', 'ScopeType' => 'required|in_list[GLOBAL,TESTSITE]', 'TestSiteID' => 'permit_empty|is_natural_no_zero', 'ConditionExpr' => 'permit_empty|max_length[1000]', 'Priority' => 'permit_empty|integer', 'Active' => 'permit_empty|in_list[0,1]', ]); if (!$validation->run($input)) { return $this->failValidationErrors($validation->getErrors()); } if (($input['ScopeType'] ?? 'GLOBAL') === 'TESTSITE') { if (empty($input['TestSiteID'])) { return $this->failValidationErrors(['TestSiteID' => 'TestSiteID is required for TESTSITE scope']); } $testDef = new TestDefSiteModel(); $exists = $testDef->where('EndDate', null)->find((int) $input['TestSiteID']); if (!$exists) { return $this->failValidationErrors(['TestSiteID' => 'TestSiteID not found']); } } else { $input['TestSiteID'] = null; } $db = \Config\Database::connect(); $db->transStart(); try { $ruleData = [ 'Name' => $input['Name'], 'Description' => $input['Description'] ?? null, 'EventCode' => $input['EventCode'], 'ScopeType' => $input['ScopeType'], 'TestSiteID' => $input['TestSiteID'] ?? null, 'ConditionExpr' => $input['ConditionExpr'] ?? null, 'Priority' => $input['Priority'] ?? 100, 'Active' => isset($input['Active']) ? (int) $input['Active'] : 1, ]; $ruleID = $this->ruleDefModel->insert($ruleData, true); if (!$ruleID) { throw new \Exception('Failed to create rule'); } if (isset($input['actions']) && is_array($input['actions'])) { foreach ($input['actions'] as $action) { if (!is_array($action)) { continue; } $actionType = $action['ActionType'] ?? null; if (!$actionType) { continue; } $params = $action['ActionParams'] ?? null; if (is_array($params)) { $params = json_encode($params); } $this->ruleActionModel->insert([ 'RuleID' => $ruleID, 'Seq' => $action['Seq'] ?? 1, 'ActionType' => $actionType, 'ActionParams' => is_string($params) ? $params : null, ]); } } $db->transComplete(); if ($db->transStatus() === false) { throw new \Exception('Transaction failed'); } return $this->respondCreated([ 'status' => 'success', 'message' => 'Rule created successfully', 'data' => ['RuleID' => $ruleID], ], 201); } catch (\Throwable $e) { $db->transRollback(); log_message('error', 'RuleController::create error: ' . $e->getMessage()); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } public function update($id = null) { $input = $this->request->getJSON(true) ?? []; if (!$id || !is_numeric($id)) { $id = $input['RuleID'] ?? null; } if (!$id || !is_numeric($id)) { return $this->failValidationErrors('RuleID is required'); } $existing = $this->ruleDefModel->where('EndDate', null)->find((int) $id); if (!$existing) { return $this->respond([ 'status' => 'failed', 'message' => 'Rule not found', 'data' => [], ], 404); } $validation = service('validation'); $validation->setRules([ 'Name' => 'permit_empty|max_length[100]', 'EventCode' => 'permit_empty|max_length[50]', 'ScopeType' => 'permit_empty|in_list[GLOBAL,TESTSITE]', 'TestSiteID' => 'permit_empty|is_natural_no_zero', 'ConditionExpr' => 'permit_empty|max_length[1000]', 'Priority' => 'permit_empty|integer', 'Active' => 'permit_empty|in_list[0,1]', ]); if (!$validation->run($input)) { return $this->failValidationErrors($validation->getErrors()); } $scopeType = $input['ScopeType'] ?? $existing['ScopeType'] ?? 'GLOBAL'; $testSiteID = array_key_exists('TestSiteID', $input) ? $input['TestSiteID'] : ($existing['TestSiteID'] ?? null); if ($scopeType === 'TESTSITE') { if (empty($testSiteID)) { return $this->failValidationErrors(['TestSiteID' => 'TestSiteID is required for TESTSITE scope']); } $testDef = new TestDefSiteModel(); $exists = $testDef->where('EndDate', null)->find((int) $testSiteID); if (!$exists) { return $this->failValidationErrors(['TestSiteID' => 'TestSiteID not found']); } } else { $testSiteID = null; } try { $updateData = []; foreach (['Name', 'Description', 'EventCode', 'ScopeType', 'ConditionExpr', 'Priority', 'Active'] as $field) { if (array_key_exists($field, $input)) { $updateData[$field] = $input[$field]; } } $updateData['TestSiteID'] = $testSiteID; if (!empty($updateData)) { $this->ruleDefModel->update((int) $id, $updateData); } return $this->respond([ 'status' => 'success', 'message' => 'Rule updated successfully', 'data' => ['RuleID' => (int) $id], ], 200); } catch (\Throwable $e) { log_message('error', 'RuleController::update error: ' . $e->getMessage()); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } public function delete($id = null) { try { if (!$id || !is_numeric($id)) { return $this->failValidationErrors('RuleID is required'); } $existing = $this->ruleDefModel->where('EndDate', null)->find((int) $id); if (!$existing) { return $this->respond([ 'status' => 'failed', 'message' => 'Rule not found', 'data' => [], ], 404); } $this->ruleDefModel->delete((int) $id); return $this->respondDeleted([ 'status' => 'success', 'message' => 'Rule deleted successfully', 'data' => ['RuleID' => (int) $id], ]); } catch (\Throwable $e) { log_message('error', 'RuleController::delete error: ' . $e->getMessage()); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } public function validateExpr() { $input = $this->request->getJSON(true) ?? []; $expr = $input['expr'] ?? ''; $context = $input['context'] ?? []; if (!is_string($expr) || trim($expr) === '') { return $this->failValidationErrors(['expr' => 'expr is required']); } if (!is_array($context)) { return $this->failValidationErrors(['context' => 'context must be an object']); } try { $svc = new RuleExpressionService(); $result = $svc->evaluate($expr, $context); return $this->respond([ 'status' => 'success', 'data' => [ 'valid' => true, 'result' => $result, ], ], 200); } catch (\Throwable $e) { return $this->respond([ 'status' => 'failed', 'data' => [ 'valid' => false, 'error' => $e->getMessage(), ], ], 200); } } }