re-uploading work
This commit is contained in:
660
crates/api/src/routes/rules.rs
Normal file
660
crates/api/src/routes/rules.rs
Normal file
@@ -0,0 +1,660 @@
|
||||
//! Rule management API routes
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::{info, warn};
|
||||
use validator::Validate;
|
||||
|
||||
use attune_common::mq::{
|
||||
MessageEnvelope, MessageType, RuleCreatedPayload, RuleDisabledPayload, RuleEnabledPayload,
|
||||
};
|
||||
use attune_common::repositories::{
|
||||
action::ActionRepository,
|
||||
pack::PackRepository,
|
||||
rule::{CreateRuleInput, RuleRepository, UpdateRuleInput},
|
||||
trigger::TriggerRepository,
|
||||
Create, Delete, FindByRef, List, Update,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
auth::middleware::RequireAuth,
|
||||
dto::{
|
||||
common::{PaginatedResponse, PaginationParams},
|
||||
rule::{CreateRuleRequest, RuleResponse, RuleSummary, UpdateRuleRequest},
|
||||
ApiResponse, SuccessResponse,
|
||||
},
|
||||
middleware::{ApiError, ApiResult},
|
||||
state::AppState,
|
||||
validation::{validate_action_params, validate_trigger_params},
|
||||
};
|
||||
|
||||
/// List all rules with pagination
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/rules",
|
||||
tag = "rules",
|
||||
params(PaginationParams),
|
||||
responses(
|
||||
(status = 200, description = "List of rules", body = PaginatedResponse<RuleSummary>),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn list_rules(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Get all rules
|
||||
let rules = RuleRepository::list(&state.db).await?;
|
||||
|
||||
// Calculate pagination
|
||||
let total = rules.len() as u64;
|
||||
let start = ((pagination.page - 1) * pagination.limit()) as usize;
|
||||
let end = (start + pagination.limit() as usize).min(rules.len());
|
||||
|
||||
// Get paginated slice
|
||||
let paginated_rules: Vec<RuleSummary> = rules[start..end]
|
||||
.iter()
|
||||
.map(|r| RuleSummary::from(r.clone()))
|
||||
.collect();
|
||||
|
||||
let response = PaginatedResponse::new(paginated_rules, &pagination, total);
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// List enabled rules
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/rules/enabled",
|
||||
tag = "rules",
|
||||
params(PaginationParams),
|
||||
responses(
|
||||
(status = 200, description = "List of enabled rules", body = PaginatedResponse<RuleSummary>),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn list_enabled_rules(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Get enabled rules
|
||||
let rules = RuleRepository::find_enabled(&state.db).await?;
|
||||
|
||||
// Calculate pagination
|
||||
let total = rules.len() as u64;
|
||||
let start = ((pagination.page - 1) * pagination.limit()) as usize;
|
||||
let end = (start + pagination.limit() as usize).min(rules.len());
|
||||
|
||||
// Get paginated slice
|
||||
let paginated_rules: Vec<RuleSummary> = rules[start..end]
|
||||
.iter()
|
||||
.map(|r| RuleSummary::from(r.clone()))
|
||||
.collect();
|
||||
|
||||
let response = PaginatedResponse::new(paginated_rules, &pagination, total);
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// List rules by pack reference
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/packs/{pack_ref}/rules",
|
||||
tag = "rules",
|
||||
params(
|
||||
("pack_ref" = String, Path, description = "Pack reference"),
|
||||
PaginationParams
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of rules in pack", body = PaginatedResponse<RuleSummary>),
|
||||
(status = 404, description = "Pack not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn list_rules_by_pack(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(pack_ref): Path<String>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Verify pack exists
|
||||
let pack = PackRepository::find_by_ref(&state.db, &pack_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Pack '{}' not found", pack_ref)))?;
|
||||
|
||||
// Get rules for this pack
|
||||
let rules = RuleRepository::find_by_pack(&state.db, pack.id).await?;
|
||||
|
||||
// Calculate pagination
|
||||
let total = rules.len() as u64;
|
||||
let start = ((pagination.page - 1) * pagination.limit()) as usize;
|
||||
let end = (start + pagination.limit() as usize).min(rules.len());
|
||||
|
||||
// Get paginated slice
|
||||
let paginated_rules: Vec<RuleSummary> = rules[start..end]
|
||||
.iter()
|
||||
.map(|r| RuleSummary::from(r.clone()))
|
||||
.collect();
|
||||
|
||||
let response = PaginatedResponse::new(paginated_rules, &pagination, total);
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// List rules by action reference
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/actions/{action_ref}/rules",
|
||||
tag = "rules",
|
||||
params(
|
||||
("action_ref" = String, Path, description = "Action reference"),
|
||||
PaginationParams
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of rules using this action", body = PaginatedResponse<RuleSummary>),
|
||||
(status = 404, description = "Action not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn list_rules_by_action(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(action_ref): Path<String>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Verify action exists
|
||||
let action = ActionRepository::find_by_ref(&state.db, &action_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Action '{}' not found", action_ref)))?;
|
||||
|
||||
// Get rules for this action
|
||||
let rules = RuleRepository::find_by_action(&state.db, action.id).await?;
|
||||
|
||||
// Calculate pagination
|
||||
let total = rules.len() as u64;
|
||||
let start = ((pagination.page - 1) * pagination.limit()) as usize;
|
||||
let end = (start + pagination.limit() as usize).min(rules.len());
|
||||
|
||||
// Get paginated slice
|
||||
let paginated_rules: Vec<RuleSummary> = rules[start..end]
|
||||
.iter()
|
||||
.map(|r| RuleSummary::from(r.clone()))
|
||||
.collect();
|
||||
|
||||
let response = PaginatedResponse::new(paginated_rules, &pagination, total);
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// List rules by trigger reference
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/triggers/{trigger_ref}/rules",
|
||||
tag = "rules",
|
||||
params(
|
||||
("trigger_ref" = String, Path, description = "Trigger reference"),
|
||||
PaginationParams
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of rules using this trigger", body = PaginatedResponse<RuleSummary>),
|
||||
(status = 404, description = "Trigger not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn list_rules_by_trigger(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(trigger_ref): Path<String>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Verify trigger exists
|
||||
let trigger = TriggerRepository::find_by_ref(&state.db, &trigger_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Trigger '{}' not found", trigger_ref)))?;
|
||||
|
||||
// Get rules for this trigger
|
||||
let rules = RuleRepository::find_by_trigger(&state.db, trigger.id).await?;
|
||||
|
||||
// Calculate pagination
|
||||
let total = rules.len() as u64;
|
||||
let start = ((pagination.page - 1) * pagination.limit()) as usize;
|
||||
let end = (start + pagination.limit() as usize).min(rules.len());
|
||||
|
||||
// Get paginated slice
|
||||
let paginated_rules: Vec<RuleSummary> = rules[start..end]
|
||||
.iter()
|
||||
.map(|r| RuleSummary::from(r.clone()))
|
||||
.collect();
|
||||
|
||||
let response = PaginatedResponse::new(paginated_rules, &pagination, total);
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// Get a single rule by reference
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/rules/{ref}",
|
||||
tag = "rules",
|
||||
params(
|
||||
("ref" = String, Path, description = "Rule reference")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Rule details", body = ApiResponse<RuleResponse>),
|
||||
(status = 404, description = "Rule not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn get_rule(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(rule_ref): Path<String>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let rule = RuleRepository::find_by_ref(&state.db, &rule_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Rule '{}' not found", rule_ref)))?;
|
||||
|
||||
let response = ApiResponse::new(RuleResponse::from(rule));
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// Create a new rule
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/rules",
|
||||
tag = "rules",
|
||||
request_body = CreateRuleRequest,
|
||||
responses(
|
||||
(status = 201, description = "Rule created successfully", body = ApiResponse<RuleResponse>),
|
||||
(status = 400, description = "Invalid request"),
|
||||
(status = 404, description = "Pack, action, or trigger not found"),
|
||||
(status = 409, description = "Rule with same ref already exists"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn create_rule(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Json(request): Json<CreateRuleRequest>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Validate request
|
||||
request.validate()?;
|
||||
|
||||
// Check if rule with same ref already exists
|
||||
if let Some(_) = RuleRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Rule with ref '{}' already exists",
|
||||
request.r#ref
|
||||
)));
|
||||
}
|
||||
|
||||
// Verify pack exists and get its ID
|
||||
let pack = PackRepository::find_by_ref(&state.db, &request.pack_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Pack '{}' not found", request.pack_ref)))?;
|
||||
|
||||
// Verify action exists and get its ID
|
||||
let action = ActionRepository::find_by_ref(&state.db, &request.action_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Action '{}' not found", request.action_ref)))?;
|
||||
|
||||
// Verify trigger exists and get its ID
|
||||
let trigger = TriggerRepository::find_by_ref(&state.db, &request.trigger_ref)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::NotFound(format!("Trigger '{}' not found", request.trigger_ref))
|
||||
})?;
|
||||
|
||||
// Validate trigger parameters against schema
|
||||
validate_trigger_params(&trigger, &request.trigger_params)?;
|
||||
|
||||
// Validate action parameters against schema
|
||||
validate_action_params(&action, &request.action_params)?;
|
||||
|
||||
// Create rule input
|
||||
let rule_input = CreateRuleInput {
|
||||
r#ref: request.r#ref,
|
||||
pack: pack.id,
|
||||
pack_ref: pack.r#ref.clone(),
|
||||
label: request.label,
|
||||
description: request.description,
|
||||
action: action.id,
|
||||
action_ref: action.r#ref.clone(),
|
||||
trigger: trigger.id,
|
||||
trigger_ref: trigger.r#ref.clone(),
|
||||
conditions: request.conditions,
|
||||
action_params: request.action_params,
|
||||
trigger_params: request.trigger_params,
|
||||
enabled: request.enabled,
|
||||
is_adhoc: true, // Rules created via API are ad-hoc (not from pack installation)
|
||||
};
|
||||
|
||||
let rule = RuleRepository::create(&state.db, rule_input).await?;
|
||||
|
||||
// Publish RuleCreated message to notify sensor service
|
||||
if let Some(ref publisher) = state.publisher {
|
||||
let payload = RuleCreatedPayload {
|
||||
rule_id: rule.id,
|
||||
rule_ref: rule.r#ref.clone(),
|
||||
trigger_id: Some(rule.trigger),
|
||||
trigger_ref: rule.trigger_ref.clone(),
|
||||
action_id: Some(rule.action),
|
||||
action_ref: rule.action_ref.clone(),
|
||||
trigger_params: Some(rule.trigger_params.clone()),
|
||||
enabled: rule.enabled,
|
||||
};
|
||||
|
||||
let envelope =
|
||||
MessageEnvelope::new(MessageType::RuleCreated, payload).with_source("api-service");
|
||||
|
||||
if let Err(e) = publisher.publish_envelope(&envelope).await {
|
||||
warn!(
|
||||
"Failed to publish RuleCreated message for rule {}: {}",
|
||||
rule.r#ref, e
|
||||
);
|
||||
} else {
|
||||
info!("Published RuleCreated message for rule {}", rule.r#ref);
|
||||
}
|
||||
}
|
||||
|
||||
let response = ApiResponse::with_message(RuleResponse::from(rule), "Rule created successfully");
|
||||
|
||||
Ok((StatusCode::CREATED, Json(response)))
|
||||
}
|
||||
|
||||
/// Update an existing rule
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/rules/{ref}",
|
||||
tag = "rules",
|
||||
params(
|
||||
("ref" = String, Path, description = "Rule reference")
|
||||
),
|
||||
request_body = UpdateRuleRequest,
|
||||
responses(
|
||||
(status = 200, description = "Rule updated successfully", body = ApiResponse<RuleResponse>),
|
||||
(status = 400, description = "Invalid request"),
|
||||
(status = 404, description = "Rule not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn update_rule(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(rule_ref): Path<String>,
|
||||
Json(request): Json<UpdateRuleRequest>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Validate request
|
||||
request.validate()?;
|
||||
|
||||
// Check if rule exists
|
||||
let existing_rule = RuleRepository::find_by_ref(&state.db, &rule_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Rule '{}' not found", rule_ref)))?;
|
||||
|
||||
// If action parameters are being updated, validate against the action's schema
|
||||
if let Some(ref action_params) = request.action_params {
|
||||
let action = ActionRepository::find_by_ref(&state.db, &existing_rule.action_ref)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::NotFound(format!("Action '{}' not found", existing_rule.action_ref))
|
||||
})?;
|
||||
validate_action_params(&action, action_params)?;
|
||||
}
|
||||
|
||||
// If trigger parameters are being updated, validate against the trigger's schema
|
||||
if let Some(ref trigger_params) = request.trigger_params {
|
||||
let trigger = TriggerRepository::find_by_ref(&state.db, &existing_rule.trigger_ref)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::NotFound(format!("Trigger '{}' not found", existing_rule.trigger_ref))
|
||||
})?;
|
||||
validate_trigger_params(&trigger, trigger_params)?;
|
||||
}
|
||||
|
||||
// Track if trigger params changed
|
||||
let trigger_params_changed = request.trigger_params.is_some()
|
||||
&& request.trigger_params != Some(existing_rule.trigger_params.clone());
|
||||
|
||||
// Create update input
|
||||
let update_input = UpdateRuleInput {
|
||||
label: request.label,
|
||||
description: request.description,
|
||||
conditions: request.conditions,
|
||||
action_params: request.action_params,
|
||||
trigger_params: request.trigger_params,
|
||||
enabled: request.enabled,
|
||||
};
|
||||
|
||||
let rule = RuleRepository::update(&state.db, existing_rule.id, update_input).await?;
|
||||
|
||||
// If the rule is enabled and trigger params changed, publish RuleEnabled message
|
||||
// to notify sensors to restart with new parameters
|
||||
if rule.enabled && trigger_params_changed {
|
||||
if let Some(ref publisher) = state.publisher {
|
||||
let payload = RuleEnabledPayload {
|
||||
rule_id: rule.id,
|
||||
rule_ref: rule.r#ref.clone(),
|
||||
trigger_ref: rule.trigger_ref.clone(),
|
||||
trigger_params: Some(rule.trigger_params.clone()),
|
||||
};
|
||||
|
||||
let envelope =
|
||||
MessageEnvelope::new(MessageType::RuleEnabled, payload).with_source("api-service");
|
||||
|
||||
if let Err(e) = publisher.publish_envelope(&envelope).await {
|
||||
warn!(
|
||||
"Failed to publish RuleEnabled message for updated rule {}: {}",
|
||||
rule.r#ref, e
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"Published RuleEnabled message for updated rule {} (trigger params changed)",
|
||||
rule.r#ref
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = ApiResponse::with_message(RuleResponse::from(rule), "Rule updated successfully");
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// Delete a rule
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/rules/{ref}",
|
||||
tag = "rules",
|
||||
params(
|
||||
("ref" = String, Path, description = "Rule reference")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Rule deleted successfully", body = SuccessResponse),
|
||||
(status = 404, description = "Rule not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn delete_rule(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(rule_ref): Path<String>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Check if rule exists
|
||||
let rule = RuleRepository::find_by_ref(&state.db, &rule_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Rule '{}' not found", rule_ref)))?;
|
||||
|
||||
// Delete the rule
|
||||
let deleted = RuleRepository::delete(&state.db, rule.id).await?;
|
||||
|
||||
if !deleted {
|
||||
return Err(ApiError::NotFound(format!("Rule '{}' not found", rule_ref)));
|
||||
}
|
||||
|
||||
let response = SuccessResponse::new(format!("Rule '{}' deleted successfully", rule_ref));
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// Enable a rule
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/rules/{ref}/enable",
|
||||
tag = "rules",
|
||||
params(
|
||||
("ref" = String, Path, description = "Rule reference")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Rule enabled successfully", body = ApiResponse<RuleResponse>),
|
||||
(status = 404, description = "Rule not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn enable_rule(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(rule_ref): Path<String>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Check if rule exists
|
||||
let existing_rule = RuleRepository::find_by_ref(&state.db, &rule_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Rule '{}' not found", rule_ref)))?;
|
||||
|
||||
// Update rule to enabled
|
||||
let update_input = UpdateRuleInput {
|
||||
label: None,
|
||||
description: None,
|
||||
conditions: None,
|
||||
action_params: None,
|
||||
trigger_params: None,
|
||||
enabled: Some(true),
|
||||
};
|
||||
|
||||
let rule = RuleRepository::update(&state.db, existing_rule.id, update_input).await?;
|
||||
|
||||
// Publish RuleEnabled message to notify sensor service
|
||||
if let Some(ref publisher) = state.publisher {
|
||||
let payload = RuleEnabledPayload {
|
||||
rule_id: rule.id,
|
||||
rule_ref: rule.r#ref.clone(),
|
||||
trigger_ref: rule.trigger_ref.clone(),
|
||||
trigger_params: Some(rule.trigger_params.clone()),
|
||||
};
|
||||
|
||||
let envelope =
|
||||
MessageEnvelope::new(MessageType::RuleEnabled, payload).with_source("api-service");
|
||||
|
||||
if let Err(e) = publisher.publish_envelope(&envelope).await {
|
||||
warn!(
|
||||
"Failed to publish RuleEnabled message for rule {}: {}",
|
||||
rule.r#ref, e
|
||||
);
|
||||
} else {
|
||||
info!("Published RuleEnabled message for rule {}", rule.r#ref);
|
||||
}
|
||||
}
|
||||
|
||||
let response = ApiResponse::with_message(RuleResponse::from(rule), "Rule enabled successfully");
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// Disable a rule
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/rules/{ref}/disable",
|
||||
tag = "rules",
|
||||
params(
|
||||
("ref" = String, Path, description = "Rule reference")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Rule disabled successfully", body = ApiResponse<RuleResponse>),
|
||||
(status = 404, description = "Rule not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn disable_rule(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(rule_ref): Path<String>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Check if rule exists
|
||||
let existing_rule = RuleRepository::find_by_ref(&state.db, &rule_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Rule '{}' not found", rule_ref)))?;
|
||||
|
||||
// Update rule to disabled
|
||||
let update_input = UpdateRuleInput {
|
||||
label: None,
|
||||
description: None,
|
||||
conditions: None,
|
||||
action_params: None,
|
||||
trigger_params: None,
|
||||
enabled: Some(false),
|
||||
};
|
||||
|
||||
let rule = RuleRepository::update(&state.db, existing_rule.id, update_input).await?;
|
||||
|
||||
// Publish RuleDisabled message to notify sensor service
|
||||
if let Some(ref publisher) = state.publisher {
|
||||
let payload = RuleDisabledPayload {
|
||||
rule_id: rule.id,
|
||||
rule_ref: rule.r#ref.clone(),
|
||||
trigger_ref: rule.trigger_ref.clone(),
|
||||
};
|
||||
|
||||
let envelope =
|
||||
MessageEnvelope::new(MessageType::RuleDisabled, payload).with_source("api-service");
|
||||
|
||||
if let Err(e) = publisher.publish_envelope(&envelope).await {
|
||||
warn!(
|
||||
"Failed to publish RuleDisabled message for rule {}: {}",
|
||||
rule.r#ref, e
|
||||
);
|
||||
} else {
|
||||
info!("Published RuleDisabled message for rule {}", rule.r#ref);
|
||||
}
|
||||
}
|
||||
|
||||
let response =
|
||||
ApiResponse::with_message(RuleResponse::from(rule), "Rule disabled successfully");
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// Create rule routes
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/rules", get(list_rules).post(create_rule))
|
||||
.route("/rules/enabled", get(list_enabled_rules))
|
||||
.route(
|
||||
"/rules/{ref}",
|
||||
get(get_rule).put(update_rule).delete(delete_rule),
|
||||
)
|
||||
.route("/rules/{ref}/enable", post(enable_rule))
|
||||
.route("/rules/{ref}/disable", post(disable_rule))
|
||||
.route("/packs/{pack_ref}/rules", get(list_rules_by_pack))
|
||||
.route("/actions/{action_ref}/rules", get(list_rules_by_action))
|
||||
.route("/triggers/{trigger_ref}/rules", get(list_rules_by_trigger))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_rule_routes_structure() {
|
||||
// Just verify the router can be constructed
|
||||
let _router = routes();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user