refactor(core): fill PlatformContext with pool/connection/websocket
- PlatformContext now holds pool, connection_manager, ws_manager - bootstrap_platform returns PlatformBuilder for pre-Arc setup - Feeder AppState embeds PlatformContext (state.platform.pool etc.) - Ops AppState embeds PlatformContext with real DB connection - Remove WebSocket type duplication: feeder re-exports from core - Add subscribe_room/send_to_room/remove_room_if_empty to WebSocketManager Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
368faf290a
commit
429c2d0b17
|
|
@ -138,6 +138,7 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"plc_platform_core",
|
"plc_platform_core",
|
||||||
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
|
|
||||||
|
|
@ -4,27 +4,25 @@ use crate::{
|
||||||
config::AppConfig,
|
config::AppConfig,
|
||||||
connection::ConnectionManager,
|
connection::ConnectionManager,
|
||||||
control,
|
control,
|
||||||
db::init_database,
|
|
||||||
event::EventManager,
|
event::EventManager,
|
||||||
router::build_router,
|
router::build_router,
|
||||||
websocket,
|
websocket::WebSocketManager,
|
||||||
};
|
};
|
||||||
|
use plc_platform_core::platform_context::PlatformContext;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub config: AppConfig,
|
pub config: AppConfig,
|
||||||
pub pool: sqlx::PgPool,
|
pub platform: PlatformContext,
|
||||||
pub connection_manager: Arc<ConnectionManager>,
|
|
||||||
pub event_manager: Arc<EventManager>,
|
pub event_manager: Arc<EventManager>,
|
||||||
pub ws_manager: Arc<websocket::WebSocketManager>,
|
|
||||||
pub control_runtime: Arc<control::runtime::ControlRuntimeStore>,
|
pub control_runtime: Arc<control::runtime::ControlRuntimeStore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn run() {
|
pub async fn run() {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
plc_platform_core::util::log::init_logger();
|
plc_platform_core::util::log::init_logger();
|
||||||
let _platform = plc_platform_core::bootstrap::bootstrap_platform();
|
|
||||||
let _single_instance =
|
let _single_instance =
|
||||||
match plc_platform_core::util::single_instance::try_acquire("PLCControl.FeederDistributor") {
|
match plc_platform_core::util::single_instance::try_acquire("PLCControl.FeederDistributor") {
|
||||||
Ok(guard) => guard,
|
Ok(guard) => guard,
|
||||||
|
|
@ -39,31 +37,31 @@ pub async fn run() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = AppConfig::from_env().expect("Failed to load configuration");
|
let config = AppConfig::from_env().expect("Failed to load configuration");
|
||||||
let pool = init_database(&config.database_url)
|
let mut builder = plc_platform_core::bootstrap::bootstrap_platform(&config.database_url)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to initialize database");
|
.expect("Failed to bootstrap platform");
|
||||||
|
|
||||||
let mut connection_manager = ConnectionManager::new();
|
|
||||||
let ws_manager = Arc::new(websocket::WebSocketManager::new());
|
|
||||||
let event_manager = Arc::new(EventManager::new(
|
let event_manager = Arc::new(EventManager::new(
|
||||||
pool.clone(),
|
builder.pool.clone(),
|
||||||
Arc::new(connection_manager.clone()),
|
Arc::new(builder.connection_manager.clone()),
|
||||||
Some(ws_manager.clone()),
|
Some(builder.ws_manager.clone()),
|
||||||
));
|
));
|
||||||
connection_manager.set_event_manager(event_manager.clone());
|
|
||||||
connection_manager.set_pool_and_start_reconnect_task(Arc::new(pool.clone()));
|
|
||||||
|
|
||||||
let connection_manager = Arc::new(connection_manager);
|
builder.connection_manager.set_event_manager(event_manager.clone());
|
||||||
|
builder.connection_manager.set_pool_and_start_reconnect_task(Arc::new(builder.pool.clone()));
|
||||||
|
|
||||||
|
let platform = builder.build();
|
||||||
|
|
||||||
let control_runtime = Arc::new(control::runtime::ControlRuntimeStore::new());
|
let control_runtime = Arc::new(control::runtime::ControlRuntimeStore::new());
|
||||||
|
|
||||||
let sources = crate::service::get_all_enabled_sources(&pool)
|
let sources = crate::service::get_all_enabled_sources(&platform.pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to fetch sources");
|
.expect("Failed to fetch sources");
|
||||||
|
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
for source in sources {
|
for source in sources {
|
||||||
let cm = connection_manager.clone();
|
let cm = platform.connection_manager.clone();
|
||||||
let p = pool.clone();
|
let p = platform.pool.clone();
|
||||||
let source_name = source.name.clone();
|
let source_name = source.name.clone();
|
||||||
let source_id = source.id;
|
let source_id = source.id;
|
||||||
|
|
||||||
|
|
@ -84,10 +82,8 @@ pub async fn run() {
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
pool,
|
platform,
|
||||||
connection_manager: connection_manager.clone(),
|
|
||||||
event_manager,
|
event_manager,
|
||||||
ws_manager,
|
|
||||||
control_runtime: control_runtime.clone(),
|
control_runtime: control_runtime.clone(),
|
||||||
};
|
};
|
||||||
control::engine::start(state.clone(), control_runtime);
|
control::engine::start(state.clone(), control_runtime);
|
||||||
|
|
@ -106,7 +102,7 @@ pub async fn run() {
|
||||||
let rt_handle = tokio::runtime::Handle::current();
|
let rt_handle = tokio::runtime::Handle::current();
|
||||||
init_tray(ui_url, shutdown_tx.clone(), rt_handle);
|
init_tray(ui_url, shutdown_tx.clone(), rt_handle);
|
||||||
|
|
||||||
let connection_manager_for_shutdown = connection_manager.clone();
|
let connection_manager_for_shutdown = state.platform.connection_manager.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::signal::ctrl_c()
|
tokio::signal::ctrl_c()
|
||||||
.await
|
.await
|
||||||
|
|
@ -133,12 +129,13 @@ pub fn test_state() -> AppState {
|
||||||
.connect_lazy(&database_url)
|
.connect_lazy(&database_url)
|
||||||
.expect("lazy pool should build");
|
.expect("lazy pool should build");
|
||||||
let connection_manager = Arc::new(ConnectionManager::new());
|
let connection_manager = Arc::new(ConnectionManager::new());
|
||||||
let ws_manager = Arc::new(websocket::WebSocketManager::new());
|
let ws_manager = Arc::new(WebSocketManager::new());
|
||||||
let event_manager = Arc::new(EventManager::new(
|
let event_manager = Arc::new(EventManager::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
connection_manager.clone(),
|
connection_manager.clone(),
|
||||||
Some(ws_manager.clone()),
|
Some(ws_manager.clone()),
|
||||||
));
|
));
|
||||||
|
let platform = PlatformContext::new(pool, connection_manager, ws_manager);
|
||||||
|
|
||||||
AppState {
|
AppState {
|
||||||
config: AppConfig {
|
config: AppConfig {
|
||||||
|
|
@ -148,10 +145,8 @@ pub fn test_state() -> AppState {
|
||||||
write_api_key: Some("test-write-key".to_string()),
|
write_api_key: Some("test-write-key".to_string()),
|
||||||
simulate_plc: false,
|
simulate_plc: false,
|
||||||
},
|
},
|
||||||
pool,
|
platform,
|
||||||
connection_manager,
|
|
||||||
event_manager,
|
event_manager,
|
||||||
ws_manager,
|
|
||||||
control_runtime: Arc::new(control::runtime::ControlRuntimeStore::new()),
|
control_runtime: Arc::new(control::runtime::ControlRuntimeStore::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ async fn supervise(state: AppState, store: Arc<ControlRuntimeStore>) {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
match crate::service::get_all_enabled_units(&state.pool).await {
|
match crate::service::get_all_enabled_units(&state.platform.pool).await {
|
||||||
Ok(units) => {
|
Ok(units) => {
|
||||||
for unit in units {
|
for unit in units {
|
||||||
let needs_spawn = tasks
|
let needs_spawn = tasks
|
||||||
|
|
@ -63,7 +63,7 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Reload unit config on each iteration to detect disable/delete.
|
// Reload unit config on each iteration to detect disable/delete.
|
||||||
let unit = match crate::service::get_unit_by_id(&state.pool, unit_id).await {
|
let unit = match crate::service::get_unit_by_id(&state.platform.pool, unit_id).await {
|
||||||
Ok(Some(u)) if u.enabled => u,
|
Ok(Some(u)) if u.enabled => u,
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::info!("Engine: unit {} disabled or deleted, task exiting", unit_id);
|
tracing::info!("Engine: unit {} disabled or deleted, task exiting", unit_id);
|
||||||
|
|
@ -114,11 +114,11 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Send feeder start command.
|
// Send feeder start command.
|
||||||
let monitor = state.connection_manager.get_point_monitor_data_read_guard().await;
|
let monitor = state.platform.connection_manager.get_point_monitor_data_read_guard().await;
|
||||||
let cmd = kind_roles.get("coal_feeder").and_then(|r| find_cmd(r, "start_cmd", &monitor));
|
let cmd = kind_roles.get("coal_feeder").and_then(|r| find_cmd(r, "start_cmd", &monitor));
|
||||||
drop(monitor);
|
drop(monitor);
|
||||||
if let Some((pid, vt)) = cmd {
|
if let Some((pid, vt)) = cmd {
|
||||||
if let Err(e) = send_pulse_command(&state.connection_manager, pid, vt.as_ref(), 300).await {
|
if let Err(e) = send_pulse_command(&state.platform.connection_manager, pid, vt.as_ref(), 300).await {
|
||||||
tracing::warn!("Engine: start feeder failed for unit {}: {}", unit_id, e);
|
tracing::warn!("Engine: start feeder failed for unit {}: {}", unit_id, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -146,11 +146,11 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Stop feeder.
|
// Stop feeder.
|
||||||
let monitor = state.connection_manager.get_point_monitor_data_read_guard().await;
|
let monitor = state.platform.connection_manager.get_point_monitor_data_read_guard().await;
|
||||||
let cmd = kind_roles.get("coal_feeder").and_then(|r| find_cmd(r, "stop_cmd", &monitor));
|
let cmd = kind_roles.get("coal_feeder").and_then(|r| find_cmd(r, "stop_cmd", &monitor));
|
||||||
drop(monitor);
|
drop(monitor);
|
||||||
if let Some((pid, vt)) = cmd {
|
if let Some((pid, vt)) = cmd {
|
||||||
if let Err(e) = send_pulse_command(&state.connection_manager, pid, vt.as_ref(), 300).await {
|
if let Err(e) = send_pulse_command(&state.platform.connection_manager, pid, vt.as_ref(), 300).await {
|
||||||
tracing::warn!("Engine: stop feeder failed for unit {}: {}", unit_id, e);
|
tracing::warn!("Engine: stop feeder failed for unit {}: {}", unit_id, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -166,11 +166,11 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
|
||||||
|
|
||||||
if unit.acc_time_sec > 0 && runtime.accumulated_run_sec >= unit.acc_time_sec as i64 * 1000 {
|
if unit.acc_time_sec > 0 && runtime.accumulated_run_sec >= unit.acc_time_sec as i64 * 1000 {
|
||||||
// Accumulated threshold reached; start distributor.
|
// Accumulated threshold reached; start distributor.
|
||||||
let monitor = state.connection_manager.get_point_monitor_data_read_guard().await;
|
let monitor = state.platform.connection_manager.get_point_monitor_data_read_guard().await;
|
||||||
let dist_cmd = kind_roles.get("distributor").and_then(|r| find_cmd(r, "start_cmd", &monitor));
|
let dist_cmd = kind_roles.get("distributor").and_then(|r| find_cmd(r, "start_cmd", &monitor));
|
||||||
drop(monitor);
|
drop(monitor);
|
||||||
if let Some((pid, vt)) = dist_cmd {
|
if let Some((pid, vt)) = dist_cmd {
|
||||||
if let Err(e) = send_pulse_command(&state.connection_manager, pid, vt.as_ref(), 300).await {
|
if let Err(e) = send_pulse_command(&state.platform.connection_manager, pid, vt.as_ref(), 300).await {
|
||||||
tracing::warn!("Engine: start distributor failed for unit {}: {}", unit_id, e);
|
tracing::warn!("Engine: start distributor failed for unit {}: {}", unit_id, e);
|
||||||
} else if state.config.simulate_plc {
|
} else if state.config.simulate_plc {
|
||||||
if let Some(eq_id) = kind_eq_ids.get("distributor").copied() {
|
if let Some(eq_id) = kind_eq_ids.get("distributor").copied() {
|
||||||
|
|
@ -191,11 +191,11 @@ async fn unit_task(state: AppState, store: Arc<ControlRuntimeStore>, unit_id: Uu
|
||||||
if !wait_phase(&state, &store, &unit, &all_roles, ¬ify, &mut fault_tick).await {
|
if !wait_phase(&state, &store, &unit, &all_roles, ¬ify, &mut fault_tick).await {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let monitor = state.connection_manager.get_point_monitor_data_read_guard().await;
|
let monitor = state.platform.connection_manager.get_point_monitor_data_read_guard().await;
|
||||||
let cmd = kind_roles.get("distributor").and_then(|r| find_cmd(r, "stop_cmd", &monitor));
|
let cmd = kind_roles.get("distributor").and_then(|r| find_cmd(r, "stop_cmd", &monitor));
|
||||||
drop(monitor);
|
drop(monitor);
|
||||||
if let Some((pid, vt)) = cmd {
|
if let Some((pid, vt)) = cmd {
|
||||||
if let Err(e) = send_pulse_command(&state.connection_manager, pid, vt.as_ref(), 300).await {
|
if let Err(e) = send_pulse_command(&state.platform.connection_manager, pid, vt.as_ref(), 300).await {
|
||||||
tracing::warn!("Engine: stop distributor failed for unit {}: {}", unit_id, e);
|
tracing::warn!("Engine: stop distributor failed for unit {}: {}", unit_id, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +269,7 @@ async fn wait_phase(
|
||||||
|
|
||||||
async fn push_ws(state: &AppState, runtime: &UnitRuntime) {
|
async fn push_ws(state: &AppState, runtime: &UnitRuntime) {
|
||||||
if let Err(e) = state
|
if let Err(e) = state
|
||||||
.ws_manager
|
.platform.ws_manager
|
||||||
.send_to_public(WsMessage::UnitRuntimeChanged(runtime.clone()))
|
.send_to_public(WsMessage::UnitRuntimeChanged(runtime.clone()))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
@ -286,7 +286,7 @@ async fn check_fault_comm(
|
||||||
all_roles: &[(Uuid, HashMap<String, EquipmentRolePoint>)],
|
all_roles: &[(Uuid, HashMap<String, EquipmentRolePoint>)],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let monitor = state
|
let monitor = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -410,10 +410,10 @@ type EquipMaps = (
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn load_equipment_maps(state: &AppState, unit_id: Uuid) -> Result<EquipMaps, sqlx::Error> {
|
async fn load_equipment_maps(state: &AppState, unit_id: Uuid) -> Result<EquipMaps, sqlx::Error> {
|
||||||
let equipment_list = crate::service::get_equipment_by_unit_id(&state.pool, unit_id).await?;
|
let equipment_list = crate::service::get_equipment_by_unit_id(&state.platform.pool, unit_id).await?;
|
||||||
let equipment_ids: Vec<Uuid> = equipment_list.iter().map(|equip| equip.id).collect();
|
let equipment_ids: Vec<Uuid> = equipment_list.iter().map(|equip| equip.id).collect();
|
||||||
let role_point_rows =
|
let role_point_rows =
|
||||||
crate::service::get_signal_role_points_batch(&state.pool, &equipment_ids).await?;
|
crate::service::get_signal_role_points_batch(&state.platform.pool, &equipment_ids).await?;
|
||||||
let mut role_points_by_equipment: HashMap<Uuid, Vec<EquipmentRolePoint>> = HashMap::new();
|
let mut role_points_by_equipment: HashMap<Uuid, Vec<EquipmentRolePoint>> = HashMap::new();
|
||||||
for row in role_point_rows {
|
for row in role_point_rows {
|
||||||
role_points_by_equipment
|
role_points_by_equipment
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ async fn run(state: AppState) {
|
||||||
tokio::time::sleep(Duration::from_secs(wait_secs)).await;
|
tokio::time::sleep(Duration::from_secs(wait_secs)).await;
|
||||||
|
|
||||||
// Pick a random enabled unit.
|
// Pick a random enabled unit.
|
||||||
let units = match crate::service::get_all_enabled_units(&state.pool).await {
|
let units = match crate::service::get_all_enabled_units(&state.platform.pool).await {
|
||||||
Ok(u) if !u.is_empty() => u,
|
Ok(u) if !u.is_empty() => u,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
@ -39,7 +39,7 @@ async fn run(state: AppState) {
|
||||||
|
|
||||||
// Pick a random equipment in that unit.
|
// Pick a random equipment in that unit.
|
||||||
let equipments =
|
let equipments =
|
||||||
match crate::service::get_equipment_by_unit_id(&state.pool, unit.id).await {
|
match crate::service::get_equipment_by_unit_id(&state.platform.pool, unit.id).await {
|
||||||
Ok(e) if !e.is_empty() => e,
|
Ok(e) if !e.is_empty() => e,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
@ -47,7 +47,7 @@ async fn run(state: AppState) {
|
||||||
|
|
||||||
// Find which of rem / flt this equipment has.
|
// Find which of rem / flt this equipment has.
|
||||||
let role_points =
|
let role_points =
|
||||||
match crate::service::get_equipment_role_points(&state.pool, eq.id).await {
|
match crate::service::get_equipment_role_points(&state.platform.pool, eq.id).await {
|
||||||
Ok(rp) if !rp.is_empty() => rp,
|
Ok(rp) if !rp.is_empty() => rp,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
@ -105,7 +105,7 @@ async fn run(state: AppState) {
|
||||||
/// Called by the engine and control handler when SIMULATE_PLC=true.
|
/// Called by the engine and control handler when SIMULATE_PLC=true.
|
||||||
pub async fn simulate_run_feedback(state: &AppState, equipment_id: Uuid, run_on: bool) {
|
pub async fn simulate_run_feedback(state: &AppState, equipment_id: Uuid, run_on: bool) {
|
||||||
let role_points =
|
let role_points =
|
||||||
match crate::service::get_equipment_role_points(&state.pool, equipment_id).await {
|
match crate::service::get_equipment_role_points(&state.platform.pool, equipment_id).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("simulate_run_feedback: db error: {}", e);
|
tracing::warn!("simulate_run_feedback: db error: {}", e);
|
||||||
|
|
@ -123,7 +123,7 @@ pub async fn simulate_run_feedback(state: &AppState, equipment_id: Uuid, run_on:
|
||||||
pub async fn patch_signal(state: &AppState, point_id: Uuid, value_on: bool) {
|
pub async fn patch_signal(state: &AppState, point_id: Uuid, value_on: bool) {
|
||||||
let write_json = serde_json::json!(if value_on { 1 } else { 0 });
|
let write_json = serde_json::json!(if value_on { 1 } else { 0 });
|
||||||
let write_ok = match state
|
let write_ok = match state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.write_point_values_batch(BatchSetPointValueReq {
|
.write_point_values_batch(BatchSetPointValueReq {
|
||||||
items: vec![SetPointValueReqItem {
|
items: vec![SetPointValueReqItem {
|
||||||
point_id,
|
point_id,
|
||||||
|
|
@ -143,7 +143,7 @@ pub async fn patch_signal(state: &AppState, point_id: Uuid, value_on: bool) {
|
||||||
// Fallback: patch the monitor cache directly and broadcast over WS.
|
// Fallback: patch the monitor cache directly and broadcast over WS.
|
||||||
let (value, value_type, value_text) = {
|
let (value, value_type, value_text) = {
|
||||||
let guard = state
|
let guard = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
match guard.get(&point_id).and_then(|m| m.value_type.as_ref()) {
|
match guard.get(&point_id).and_then(|m| m.value_type.as_ref()) {
|
||||||
|
|
@ -182,7 +182,7 @@ pub async fn patch_signal(state: &AppState, point_id: Uuid, value_on: bool) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = state
|
if let Err(e) = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.update_point_monitor_data(monitor.clone())
|
.update_point_monitor_data(monitor.clone())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
@ -191,7 +191,7 @@ pub async fn patch_signal(state: &AppState, point_id: Uuid, value_on: bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = state
|
let _ = state
|
||||||
.ws_manager
|
.platform.ws_manager
|
||||||
.send_to_public(WsMessage::PointNewValue(monitor))
|
.send_to_public(WsMessage::PointNewValue(monitor))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,11 @@ pub async fn validate_manual_control(
|
||||||
equipment_id: Uuid,
|
equipment_id: Uuid,
|
||||||
action: ControlAction,
|
action: ControlAction,
|
||||||
) -> Result<ManualControlContext, ApiErr> {
|
) -> Result<ManualControlContext, ApiErr> {
|
||||||
let equipment = crate::service::get_equipment_by_id(&state.pool, equipment_id)
|
let equipment = crate::service::get_equipment_by_id(&state.platform.pool, equipment_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Equipment not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Equipment not found".to_string(), None))?;
|
||||||
|
|
||||||
let role_points = crate::service::get_equipment_role_points(&state.pool, equipment_id).await?;
|
let role_points = crate::service::get_equipment_role_points(&state.platform.pool, equipment_id).await?;
|
||||||
if role_points.is_empty() {
|
if role_points.is_empty() {
|
||||||
return Err(ApiErr::BadRequest(
|
return Err(ApiErr::BadRequest(
|
||||||
"Equipment has no bound role points".to_string(),
|
"Equipment has no bound role points".to_string(),
|
||||||
|
|
@ -75,7 +75,7 @@ pub async fn validate_manual_control(
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let monitor_guard = state
|
let monitor_guard = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ pub async fn validate_manual_control(
|
||||||
}
|
}
|
||||||
|
|
||||||
let command_value_type = state
|
let command_value_type = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await
|
.await
|
||||||
.get(&command_point.point_id)
|
.get(&command_point.point_id)
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@ pub async fn get_unit_list(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
query.validate()?;
|
query.validate()?;
|
||||||
|
|
||||||
let total = crate::service::get_units_count(&state.pool, query.keyword.as_deref()).await?;
|
let total = crate::service::get_units_count(&state.platform.pool, query.keyword.as_deref()).await?;
|
||||||
let units = crate::service::get_units_paginated(
|
let units = crate::service::get_units_paginated(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
query.keyword.as_deref(),
|
query.keyword.as_deref(),
|
||||||
query.pagination.page_size,
|
query.pagination.page_size,
|
||||||
query.pagination.offset(),
|
query.pagination.offset(),
|
||||||
|
|
@ -81,14 +81,14 @@ pub async fn get_unit_list(
|
||||||
|
|
||||||
let unit_ids: Vec<Uuid> = units.iter().map(|u| u.id).collect();
|
let unit_ids: Vec<Uuid> = units.iter().map(|u| u.id).collect();
|
||||||
let all_equipments =
|
let all_equipments =
|
||||||
crate::service::get_equipment_by_unit_ids(&state.pool, &unit_ids).await?;
|
crate::service::get_equipment_by_unit_ids(&state.platform.pool, &unit_ids).await?;
|
||||||
|
|
||||||
let eq_ids: Vec<Uuid> = all_equipments.iter().map(|e| e.id).collect();
|
let eq_ids: Vec<Uuid> = all_equipments.iter().map(|e| e.id).collect();
|
||||||
let role_point_rows =
|
let role_point_rows =
|
||||||
crate::service::get_signal_role_points_batch(&state.pool, &eq_ids).await?;
|
crate::service::get_signal_role_points_batch(&state.platform.pool, &eq_ids).await?;
|
||||||
|
|
||||||
let monitor_guard = state
|
let monitor_guard = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -161,7 +161,7 @@ async fn send_equipment_command(
|
||||||
let pulse_ms = 300u64;
|
let pulse_ms = 300u64;
|
||||||
|
|
||||||
crate::control::command::send_pulse_command(
|
crate::control::command::send_pulse_command(
|
||||||
&state.connection_manager,
|
&state.platform.connection_manager,
|
||||||
context.command_point.point_id,
|
context.command_point.point_id,
|
||||||
context.command_value_type.as_ref(),
|
context.command_value_type.as_ref(),
|
||||||
pulse_ms,
|
pulse_ms,
|
||||||
|
|
@ -206,18 +206,18 @@ pub async fn get_unit(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let unit = crate::service::get_unit_by_id(&state.pool, unit_id)
|
let unit = crate::service::get_unit_by_id(&state.platform.pool, unit_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
let runtime = state.control_runtime.get(unit_id).await;
|
let runtime = state.control_runtime.get(unit_id).await;
|
||||||
|
|
||||||
let all_equipments =
|
let all_equipments =
|
||||||
crate::service::get_equipment_by_unit_id(&state.pool, unit_id).await?;
|
crate::service::get_equipment_by_unit_id(&state.platform.pool, unit_id).await?;
|
||||||
let eq_ids: Vec<Uuid> = all_equipments.iter().map(|e| e.id).collect();
|
let eq_ids: Vec<Uuid> = all_equipments.iter().map(|e| e.id).collect();
|
||||||
let role_point_rows =
|
let role_point_rows =
|
||||||
crate::service::get_signal_role_points_batch(&state.pool, &eq_ids).await?;
|
crate::service::get_signal_role_points_batch(&state.platform.pool, &eq_ids).await?;
|
||||||
let monitor_guard = state
|
let monitor_guard = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
let mut role_points_map: std::collections::HashMap<
|
let mut role_points_map: std::collections::HashMap<
|
||||||
|
|
@ -273,18 +273,18 @@ pub async fn get_unit_detail(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let unit = crate::service::get_unit_by_id(&state.pool, unit_id)
|
let unit = crate::service::get_unit_by_id(&state.platform.pool, unit_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
|
|
||||||
let runtime = state.control_runtime.get(unit_id).await;
|
let runtime = state.control_runtime.get(unit_id).await;
|
||||||
|
|
||||||
let equipments = crate::service::get_equipment_by_unit_id(&state.pool, unit_id).await?;
|
let equipments = crate::service::get_equipment_by_unit_id(&state.platform.pool, unit_id).await?;
|
||||||
let equipment_ids: Vec<Uuid> = equipments.iter().map(|e| e.id).collect();
|
let equipment_ids: Vec<Uuid> = equipments.iter().map(|e| e.id).collect();
|
||||||
let all_points = crate::service::get_points_by_equipment_ids(&state.pool, &equipment_ids).await?;
|
let all_points = crate::service::get_points_by_equipment_ids(&state.platform.pool, &equipment_ids).await?;
|
||||||
|
|
||||||
let monitor_guard = state
|
let monitor_guard = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -358,7 +358,7 @@ pub async fn create_unit(
|
||||||
|
|
||||||
validate_unit_timing_order(run_time_sec, acc_time_sec)?;
|
validate_unit_timing_order(run_time_sec, acc_time_sec)?;
|
||||||
|
|
||||||
if crate::service::get_unit_by_code(&state.pool, &payload.code)
|
if crate::service::get_unit_by_code(&state.platform.pool, &payload.code)
|
||||||
.await?
|
.await?
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
|
|
@ -369,7 +369,7 @@ pub async fn create_unit(
|
||||||
}
|
}
|
||||||
|
|
||||||
let unit_id = crate::service::create_unit(
|
let unit_id = crate::service::create_unit(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
crate::service::CreateUnitParams {
|
crate::service::CreateUnitParams {
|
||||||
code: &payload.code,
|
code: &payload.code,
|
||||||
name: &payload.name,
|
name: &payload.name,
|
||||||
|
|
@ -421,7 +421,7 @@ pub async fn update_unit(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
payload.validate()?;
|
payload.validate()?;
|
||||||
|
|
||||||
let existing_unit = crate::service::get_unit_by_id(&state.pool, unit_id)
|
let existing_unit = crate::service::get_unit_by_id(&state.platform.pool, unit_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
|
|
||||||
|
|
@ -431,7 +431,7 @@ pub async fn update_unit(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(code) = payload.code.as_deref() {
|
if let Some(code) = payload.code.as_deref() {
|
||||||
let duplicate = crate::service::get_unit_by_code(&state.pool, code).await?;
|
let duplicate = crate::service::get_unit_by_code(&state.platform.pool, code).await?;
|
||||||
if duplicate.as_ref().is_some_and(|item| item.id != unit_id) {
|
if duplicate.as_ref().is_some_and(|item| item.id != unit_id) {
|
||||||
return Err(ApiErr::BadRequest(
|
return Err(ApiErr::BadRequest(
|
||||||
"Unit code already exists".to_string(),
|
"Unit code already exists".to_string(),
|
||||||
|
|
@ -454,7 +454,7 @@ pub async fn update_unit(
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::service::update_unit(
|
crate::service::update_unit(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
unit_id,
|
unit_id,
|
||||||
crate::service::UpdateUnitParams {
|
crate::service::UpdateUnitParams {
|
||||||
code: payload.code.as_deref(),
|
code: payload.code.as_deref(),
|
||||||
|
|
@ -479,7 +479,7 @@ pub async fn delete_unit(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let deleted = crate::service::delete_unit(&state.pool, unit_id).await?;
|
let deleted = crate::service::delete_unit(&state.platform.pool, unit_id).await?;
|
||||||
if !deleted {
|
if !deleted {
|
||||||
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
||||||
}
|
}
|
||||||
|
|
@ -503,13 +503,13 @@ pub async fn get_event_list(
|
||||||
query.validate()?;
|
query.validate()?;
|
||||||
|
|
||||||
let total = crate::service::get_events_count(
|
let total = crate::service::get_events_count(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
query.unit_id,
|
query.unit_id,
|
||||||
query.event_type.as_deref(),
|
query.event_type.as_deref(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let data = crate::service::get_events_paginated(
|
let data = crate::service::get_events_paginated(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
query.unit_id,
|
query.unit_id,
|
||||||
query.event_type.as_deref(),
|
query.event_type.as_deref(),
|
||||||
query.pagination.page_size,
|
query.pagination.page_size,
|
||||||
|
|
@ -529,7 +529,7 @@ pub async fn start_auto_unit(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let unit = crate::service::get_unit_by_id(&state.pool, unit_id)
|
let unit = crate::service::get_unit_by_id(&state.platform.pool, unit_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
|
|
||||||
|
|
@ -564,7 +564,7 @@ pub async fn stop_auto_unit(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
crate::service::get_unit_by_id(&state.pool, unit_id)
|
crate::service::get_unit_by_id(&state.platform.pool, unit_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
|
|
||||||
|
|
@ -581,7 +581,7 @@ pub async fn stop_auto_unit(
|
||||||
pub async fn batch_start_auto(
|
pub async fn batch_start_auto(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let units = crate::service::get_all_enabled_units(&state.pool).await?;
|
let units = crate::service::get_all_enabled_units(&state.platform.pool).await?;
|
||||||
let mut started = Vec::new();
|
let mut started = Vec::new();
|
||||||
let mut skipped = Vec::new();
|
let mut skipped = Vec::new();
|
||||||
|
|
||||||
|
|
@ -611,7 +611,7 @@ pub async fn batch_start_auto(
|
||||||
pub async fn batch_stop_auto(
|
pub async fn batch_stop_auto(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let units = crate::service::get_all_enabled_units(&state.pool).await?;
|
let units = crate::service::get_all_enabled_units(&state.platform.pool).await?;
|
||||||
let mut stopped = Vec::new();
|
let mut stopped = Vec::new();
|
||||||
|
|
||||||
for unit in units {
|
for unit in units {
|
||||||
|
|
@ -635,7 +635,7 @@ pub async fn ack_fault_unit(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
crate::service::get_unit_by_id(&state.pool, unit_id)
|
crate::service::get_unit_by_id(&state.platform.pool, unit_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
|
|
||||||
|
|
@ -669,7 +669,7 @@ pub async fn get_unit_runtime(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
crate::service::get_unit_by_id(&state.pool, unit_id)
|
crate::service::get_unit_by_id(&state.platform.pool, unit_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,9 @@ pub async fn get_equipment_list(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
query.validate()?;
|
query.validate()?;
|
||||||
|
|
||||||
let total = crate::service::get_equipment_count(&state.pool, query.keyword.as_deref()).await?;
|
let total = crate::service::get_equipment_count(&state.platform.pool, query.keyword.as_deref()).await?;
|
||||||
let items = crate::service::get_equipment_paginated(
|
let items = crate::service::get_equipment_paginated(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
query.keyword.as_deref(),
|
query.keyword.as_deref(),
|
||||||
query.pagination.page_size,
|
query.pagination.page_size,
|
||||||
query.pagination.offset(),
|
query.pagination.offset(),
|
||||||
|
|
@ -66,10 +66,10 @@ pub async fn get_equipment_list(
|
||||||
|
|
||||||
let equipment_ids: Vec<uuid::Uuid> = items.iter().map(|item| item.equipment.id).collect();
|
let equipment_ids: Vec<uuid::Uuid> = items.iter().map(|item| item.equipment.id).collect();
|
||||||
let role_point_rows =
|
let role_point_rows =
|
||||||
crate::service::get_signal_role_points_batch(&state.pool, &equipment_ids).await?;
|
crate::service::get_signal_role_points_batch(&state.platform.pool, &equipment_ids).await?;
|
||||||
|
|
||||||
let monitor_guard = state
|
let monitor_guard = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ pub async fn get_equipment(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(equipment_id): Path<Uuid>,
|
Path(equipment_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let equipment = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?;
|
let equipment = crate::service::get_equipment_by_id(&state.platform.pool, equipment_id).await?;
|
||||||
|
|
||||||
match equipment {
|
match equipment {
|
||||||
Some(item) => Ok(Json(item)),
|
Some(item) => Ok(Json(item)),
|
||||||
|
|
@ -122,12 +122,12 @@ pub async fn get_equipment_points(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(equipment_id): Path<Uuid>,
|
Path(equipment_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let exists = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?;
|
let exists = crate::service::get_equipment_by_id(&state.platform.pool, equipment_id).await?;
|
||||||
if exists.is_none() {
|
if exists.is_none() {
|
||||||
return Err(ApiErr::NotFound("Equipment not found".to_string(), None));
|
return Err(ApiErr::NotFound("Equipment not found".to_string(), None));
|
||||||
}
|
}
|
||||||
|
|
||||||
let points = crate::service::get_points_by_equipment_id(&state.pool, equipment_id).await?;
|
let points = crate::service::get_points_by_equipment_id(&state.platform.pool, equipment_id).await?;
|
||||||
Ok(Json(points))
|
Ok(Json(points))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +165,7 @@ pub async fn create_equipment(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
payload.validate()?;
|
payload.validate()?;
|
||||||
|
|
||||||
let exists = crate::service::get_equipment_by_code(&state.pool, &payload.code).await?;
|
let exists = crate::service::get_equipment_by_code(&state.platform.pool, &payload.code).await?;
|
||||||
if exists.is_some() {
|
if exists.is_some() {
|
||||||
return Err(ApiErr::BadRequest(
|
return Err(ApiErr::BadRequest(
|
||||||
"Equipment code already exists".to_string(),
|
"Equipment code already exists".to_string(),
|
||||||
|
|
@ -174,14 +174,14 @@ pub async fn create_equipment(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(unit_id) = payload.unit_id {
|
if let Some(unit_id) = payload.unit_id {
|
||||||
let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?;
|
let unit_exists = crate::service::get_unit_by_id(&state.platform.pool, unit_id).await?;
|
||||||
if unit_exists.is_none() {
|
if unit_exists.is_none() {
|
||||||
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let equipment_id = crate::service::create_equipment(
|
let equipment_id = crate::service::create_equipment(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
payload.unit_id,
|
payload.unit_id,
|
||||||
&payload.code,
|
&payload.code,
|
||||||
&payload.name,
|
&payload.name,
|
||||||
|
|
@ -219,7 +219,7 @@ pub async fn update_equipment(
|
||||||
return Ok(Json(serde_json::json!({"ok_msg": "No fields to update"})));
|
return Ok(Json(serde_json::json!({"ok_msg": "No fields to update"})));
|
||||||
}
|
}
|
||||||
|
|
||||||
let exists = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?;
|
let exists = crate::service::get_equipment_by_id(&state.platform.pool, equipment_id).await?;
|
||||||
let existing_equipment = if let Some(equipment) = exists {
|
let existing_equipment = if let Some(equipment) = exists {
|
||||||
equipment
|
equipment
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -227,14 +227,14 @@ pub async fn update_equipment(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(Some(unit_id)) = payload.unit_id {
|
if let Some(Some(unit_id)) = payload.unit_id {
|
||||||
let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?;
|
let unit_exists = crate::service::get_unit_by_id(&state.platform.pool, unit_id).await?;
|
||||||
if unit_exists.is_none() {
|
if unit_exists.is_none() {
|
||||||
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(code) = payload.code.as_deref() {
|
if let Some(code) = payload.code.as_deref() {
|
||||||
let duplicate = crate::service::get_equipment_by_code(&state.pool, code).await?;
|
let duplicate = crate::service::get_equipment_by_code(&state.platform.pool, code).await?;
|
||||||
if duplicate
|
if duplicate
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|item| item.id != equipment_id)
|
.is_some_and(|item| item.id != equipment_id)
|
||||||
|
|
@ -247,7 +247,7 @@ pub async fn update_equipment(
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::service::update_equipment(
|
crate::service::update_equipment(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
equipment_id,
|
equipment_id,
|
||||||
payload.unit_id,
|
payload.unit_id,
|
||||||
payload.code.as_deref(),
|
payload.code.as_deref(),
|
||||||
|
|
@ -289,17 +289,17 @@ pub async fn batch_set_equipment_unit(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(unit_id) = payload.unit_id {
|
if let Some(unit_id) = payload.unit_id {
|
||||||
let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?;
|
let unit_exists = crate::service::get_unit_by_id(&state.platform.pool, unit_id).await?;
|
||||||
if unit_exists.is_none() {
|
if unit_exists.is_none() {
|
||||||
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let before_unit_ids =
|
let before_unit_ids =
|
||||||
crate::service::get_unit_ids_by_equipment_ids(&state.pool, &payload.equipment_ids).await?;
|
crate::service::get_unit_ids_by_equipment_ids(&state.platform.pool, &payload.equipment_ids).await?;
|
||||||
|
|
||||||
let updated_count = crate::service::batch_set_equipment_unit(
|
let updated_count = crate::service::batch_set_equipment_unit(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
&payload.equipment_ids,
|
&payload.equipment_ids,
|
||||||
payload.unit_id,
|
payload.unit_id,
|
||||||
)
|
)
|
||||||
|
|
@ -321,8 +321,8 @@ pub async fn delete_equipment(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(equipment_id): Path<Uuid>,
|
Path(equipment_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let unit_ids = crate::service::get_unit_ids_by_equipment_ids(&state.pool, &[equipment_id]).await?;
|
let unit_ids = crate::service::get_unit_ids_by_equipment_ids(&state.platform.pool, &[equipment_id]).await?;
|
||||||
let deleted = crate::service::delete_equipment(&state.pool, equipment_id).await?;
|
let deleted = crate::service::delete_equipment(&state.platform.pool, equipment_id).await?;
|
||||||
if !deleted {
|
if !deleted {
|
||||||
return Err(ApiErr::NotFound("Equipment not found".to_string(), None));
|
return Err(ApiErr::NotFound("Equipment not found".to_string(), None));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ pub async fn get_page_list(
|
||||||
Query(query): Query<GetPageListQuery>,
|
Query(query): Query<GetPageListQuery>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
query.validate()?;
|
query.validate()?;
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
let pages: Vec<Page> = if let Some(name) = query.name {
|
let pages: Vec<Page> = if let Some(name) = query.name {
|
||||||
sqlx::query_as::<_, Page>(
|
sqlx::query_as::<_, Page>(
|
||||||
|
|
@ -50,7 +50,7 @@ pub async fn get_page(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let page = sqlx::query_as::<_, Page>("SELECT * FROM page WHERE id = $1")
|
let page = sqlx::query_as::<_, Page>("SELECT * FROM page WHERE id = $1")
|
||||||
.bind(page_id)
|
.bind(page_id)
|
||||||
.fetch_optional(&state.pool)
|
.fetch_optional(&state.platform.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
match page {
|
match page {
|
||||||
|
|
@ -88,7 +88,7 @@ pub async fn create_page(
|
||||||
)
|
)
|
||||||
.bind(&payload.name)
|
.bind(&payload.name)
|
||||||
.bind(SqlxJson(payload.data))
|
.bind(SqlxJson(payload.data))
|
||||||
.fetch_one(&state.pool)
|
.fetch_one(&state.platform.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((StatusCode::CREATED, Json(serde_json::json!({
|
Ok((StatusCode::CREATED, Json(serde_json::json!({
|
||||||
|
|
@ -106,7 +106,7 @@ pub async fn update_page(
|
||||||
|
|
||||||
let exists = sqlx::query("SELECT 1 FROM page WHERE id = $1")
|
let exists = sqlx::query("SELECT 1 FROM page WHERE id = $1")
|
||||||
.bind(page_id)
|
.bind(page_id)
|
||||||
.fetch_optional(&state.pool)
|
.fetch_optional(&state.platform.pool)
|
||||||
.await?;
|
.await?;
|
||||||
if exists.is_none() {
|
if exists.is_none() {
|
||||||
return Err(ApiErr::NotFound("Page not found".to_string(), None));
|
return Err(ApiErr::NotFound("Page not found".to_string(), None));
|
||||||
|
|
@ -145,7 +145,7 @@ pub async fn update_page(
|
||||||
}
|
}
|
||||||
query = query.bind(page_id);
|
query = query.bind(page_id);
|
||||||
|
|
||||||
query.execute(&state.pool).await?;
|
query.execute(&state.platform.pool).await?;
|
||||||
|
|
||||||
Ok(Json(serde_json::json!({
|
Ok(Json(serde_json::json!({
|
||||||
"ok_msg": "Page updated successfully"
|
"ok_msg": "Page updated successfully"
|
||||||
|
|
@ -158,7 +158,7 @@ pub async fn delete_page(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let result = sqlx::query("DELETE FROM page WHERE id = $1")
|
let result = sqlx::query("DELETE FROM page WHERE id = $1")
|
||||||
.bind(page_id)
|
.bind(page_id)
|
||||||
.execute(&state.pool)
|
.execute(&state.platform.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if result.rows_affected() == 0 {
|
if result.rows_affected() == 0 {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pub async fn get_point_list(
|
||||||
Query(query): Query<GetPointListQuery>,
|
Query(query): Query<GetPointListQuery>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
query.validate()?;
|
query.validate()?;
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
// Count total rows.
|
// Count total rows.
|
||||||
let total = crate::service::get_points_count(pool, query.source_id, query.equipment_id).await?;
|
let total = crate::service::get_points_count(pool, query.source_id, query.equipment_id).await?;
|
||||||
|
|
@ -85,7 +85,7 @@ pub async fn get_point_list(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let monitor_guard = state
|
let monitor_guard = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_monitor_data_read_guard()
|
.get_point_monitor_data_read_guard()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ pub async fn get_point(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(point_id): Path<Uuid>,
|
Path(point_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
let point = crate::service::get_point_by_id(pool, point_id).await?;
|
let point = crate::service::get_point_by_id(pool, point_id).await?;
|
||||||
|
|
||||||
Ok(Json(point))
|
Ok(Json(point))
|
||||||
|
|
@ -125,7 +125,7 @@ pub async fn get_point_history(
|
||||||
Path(point_id): Path<Uuid>,
|
Path(point_id): Path<Uuid>,
|
||||||
Query(query): Query<GetPointHistoryQuery>,
|
Query(query): Query<GetPointHistoryQuery>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
let point = crate::service::get_point_by_id(pool, point_id).await?;
|
let point = crate::service::get_point_by_id(pool, point_id).await?;
|
||||||
if point.is_none() {
|
if point.is_none() {
|
||||||
return Err(ApiErr::NotFound("Point not found".to_string(), None));
|
return Err(ApiErr::NotFound("Point not found".to_string(), None));
|
||||||
|
|
@ -133,7 +133,7 @@ pub async fn get_point_history(
|
||||||
|
|
||||||
let limit = query.limit.unwrap_or(120).clamp(1, 1000);
|
let limit = query.limit.unwrap_or(120).clamp(1, 1000);
|
||||||
let history = state
|
let history = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.get_point_history(point_id, limit)
|
.get_point_history(point_id, limit)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -194,7 +194,7 @@ pub async fn update_point(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
payload.validate()?;
|
payload.validate()?;
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
if payload.name.is_none()
|
if payload.name.is_none()
|
||||||
&& payload.description.is_none()
|
&& payload.description.is_none()
|
||||||
|
|
@ -317,7 +317,7 @@ pub async fn batch_set_point_tags(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
// If tag_id is provided, ensure tag exists.
|
// If tag_id is provided, ensure tag exists.
|
||||||
if let Some(tag_id) = payload.tag_id {
|
if let Some(tag_id) = payload.tag_id {
|
||||||
|
|
@ -372,7 +372,7 @@ pub async fn batch_set_point_equipment(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
if let Some(equipment_id) = payload.equipment_id {
|
if let Some(equipment_id) = payload.equipment_id {
|
||||||
let equipment_exists = sqlx::query(r#"SELECT 1 FROM equipment WHERE id = $1"#)
|
let equipment_exists = sqlx::query(r#"SELECT 1 FROM equipment WHERE id = $1"#)
|
||||||
|
|
@ -429,7 +429,7 @@ pub async fn delete_point(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(point_id): Path<Uuid>,
|
Path(point_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
let affected_unit_ids = crate::service::get_unit_ids_by_point_ids(pool, &[point_id]).await?;
|
let affected_unit_ids = crate::service::get_unit_ids_by_point_ids(pool, &[point_id]).await?;
|
||||||
|
|
||||||
let source_id = {
|
let source_id = {
|
||||||
|
|
@ -494,7 +494,7 @@ pub async fn batch_create_points(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
payload.validate()?;
|
payload.validate()?;
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
if payload.node_ids.is_empty() {
|
if payload.node_ids.is_empty() {
|
||||||
return Err(ApiErr::BadRequest(
|
return Err(ApiErr::BadRequest(
|
||||||
|
|
@ -614,7 +614,7 @@ pub async fn batch_delete_points(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
let point_ids = payload.point_ids;
|
let point_ids = payload.point_ids;
|
||||||
|
|
||||||
let grouped = crate::service::get_points_grouped_by_source(pool, &point_ids).await?;
|
let grouped = crate::service::get_points_grouped_by_source(pool, &point_ids).await?;
|
||||||
|
|
@ -673,7 +673,7 @@ pub async fn batch_set_point_value(
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = state
|
let result = state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.write_point_values_batch(payload)
|
.write_point_values_batch(payload)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ApiErr::Internal(e, None))?;
|
.map_err(|e| ApiErr::Internal(e, None))?;
|
||||||
|
|
|
||||||
|
|
@ -98,12 +98,12 @@ impl From<Source> for SourcePublic {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_source_list(State(state): State<AppState>) -> Result<impl IntoResponse, ApiErr> {
|
pub async fn get_source_list(State(state): State<AppState>) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
let sources: Vec<Source> = crate::service::get_all_enabled_sources(pool).await?;
|
let sources: Vec<Source> = crate::service::get_all_enabled_sources(pool).await?;
|
||||||
|
|
||||||
// 鑾峰彇鎵€鏈夎繛鎺ョ姸鎬?
|
// 鑾峰彇鎵€鏈夎繛鎺ョ姸鎬?
|
||||||
let status_map: std::collections::HashMap<Uuid, (bool, Option<String>, Option<DateTime<Utc>>)> =
|
let status_map: std::collections::HashMap<Uuid, (bool, Option<String>, Option<DateTime<Utc>>)> =
|
||||||
state.connection_manager.get_all_status().await
|
state.platform.connection_manager.get_all_status().await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(source_id, s)| (source_id, (s.is_connected, s.last_error, Some(s.last_time))))
|
.map(|(source_id, s)| (source_id, (s.is_connected, s.last_error, Some(s.last_time))))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -132,7 +132,7 @@ pub async fn get_node_tree(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(source_id): Path<Uuid>,
|
Path(source_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
// 鏌ヨ鎵€鏈夊睘浜庤source鐨勮妭鐐?
|
// 鏌ヨ鎵€鏈夊睘浜庤source鐨勮妭鐐?
|
||||||
let nodes: Vec<Node> = sqlx::query_as::<_, Node>(
|
let nodes: Vec<Node> = sqlx::query_as::<_, Node>(
|
||||||
|
|
@ -212,7 +212,7 @@ pub async fn create_source(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
payload.validate()?;
|
payload.validate()?;
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
let new_id = Uuid::new_v4();
|
let new_id = Uuid::new_v4();
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
|
|
@ -261,7 +261,7 @@ pub async fn update_source(
|
||||||
return Ok(Json(serde_json::json!({"ok_msg": "No fields to update"})));
|
return Ok(Json(serde_json::json!({"ok_msg": "No fields to update"})));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
let exists = sqlx::query("SELECT 1 FROM source WHERE id = $1")
|
let exists = sqlx::query("SELECT 1 FROM source WHERE id = $1")
|
||||||
.bind(source_id)
|
.bind(source_id)
|
||||||
|
|
@ -311,7 +311,7 @@ pub async fn delete_source(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(source_id): Path<Uuid>,
|
Path(source_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
let source_name = sqlx::query_scalar::<_, String>("SELECT name FROM source WHERE id = $1")
|
let source_name = sqlx::query_scalar::<_, String>("SELECT name FROM source WHERE id = $1")
|
||||||
.bind(source_id)
|
.bind(source_id)
|
||||||
|
|
@ -334,7 +334,7 @@ pub async fn reconnect_source(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(source_id): Path<Uuid>,
|
Path(source_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
let exists = sqlx::query("SELECT 1 FROM source WHERE id = $1")
|
let exists = sqlx::query("SELECT 1 FROM source WHERE id = $1")
|
||||||
.bind(source_id)
|
.bind(source_id)
|
||||||
|
|
@ -349,7 +349,7 @@ pub async fn reconnect_source(
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
.connection_manager
|
.platform.connection_manager
|
||||||
.reconnect(pool, source_id)
|
.reconnect(pool, source_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ApiErr::Internal(e, None))?;
|
.map_err(|e| ApiErr::Internal(e, None))?;
|
||||||
|
|
@ -362,7 +362,7 @@ pub async fn browse_and_save_nodes(
|
||||||
Path(source_id): Path<Uuid>,
|
Path(source_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
|
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
// 纭 source 瀛樺湪
|
// 纭 source 瀛樺湪
|
||||||
sqlx::query("SELECT 1 FROM source WHERE id = $1")
|
sqlx::query("SELECT 1 FROM source WHERE id = $1")
|
||||||
|
|
@ -370,7 +370,7 @@ pub async fn browse_and_save_nodes(
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let session = state.connection_manager
|
let session = state.platform.connection_manager
|
||||||
.get_session(source_id)
|
.get_session(source_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| anyhow::anyhow!("Source not connected"))?;
|
.ok_or_else(|| anyhow::anyhow!("Source not connected"))?;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ pub async fn get_tag_list(
|
||||||
Query(query): Query<GetTagListQuery>,
|
Query(query): Query<GetTagListQuery>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
query.validate()?;
|
query.validate()?;
|
||||||
let pool = &state.pool;
|
let pool = &state.platform.pool;
|
||||||
|
|
||||||
// Count total rows.
|
// Count total rows.
|
||||||
let total = crate::service::get_tags_count(pool).await?;
|
let total = crate::service::get_tags_count(pool).await?;
|
||||||
|
|
@ -43,7 +43,7 @@ pub async fn get_tag_points(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(tag_id): Path<Uuid>,
|
Path(tag_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let points = crate::service::get_tag_points(&state.pool, tag_id).await?;
|
let points = crate::service::get_tag_points(&state.platform.pool, tag_id).await?;
|
||||||
Ok(Json(points))
|
Ok(Json(points))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ pub async fn create_tag(
|
||||||
|
|
||||||
let point_ids = payload.point_ids.as_deref().unwrap_or(&[]);
|
let point_ids = payload.point_ids.as_deref().unwrap_or(&[]);
|
||||||
let tag_id = crate::service::create_tag(
|
let tag_id = crate::service::create_tag(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
&payload.name,
|
&payload.name,
|
||||||
payload.description.as_deref(),
|
payload.description.as_deref(),
|
||||||
point_ids,
|
point_ids,
|
||||||
|
|
@ -93,13 +93,13 @@ pub async fn update_tag(
|
||||||
payload.validate()?;
|
payload.validate()?;
|
||||||
|
|
||||||
// Ensure the target tag exists.
|
// Ensure the target tag exists.
|
||||||
let exists = crate::service::get_tag_by_id(&state.pool, tag_id).await?;
|
let exists = crate::service::get_tag_by_id(&state.platform.pool, tag_id).await?;
|
||||||
if exists.is_none() {
|
if exists.is_none() {
|
||||||
return Err(ApiErr::NotFound("Tag not found".to_string(), None));
|
return Err(ApiErr::NotFound("Tag not found".to_string(), None));
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::service::update_tag(
|
crate::service::update_tag(
|
||||||
&state.pool,
|
&state.platform.pool,
|
||||||
tag_id,
|
tag_id,
|
||||||
payload.name.as_deref(),
|
payload.name.as_deref(),
|
||||||
payload.description.as_deref(),
|
payload.description.as_deref(),
|
||||||
|
|
@ -116,7 +116,7 @@ pub async fn delete_tag(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(tag_id): Path<Uuid>,
|
Path(tag_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
let deleted = crate::service::delete_tag(&state.pool, tag_id).await?;
|
let deleted = crate::service::delete_tag(&state.platform.pool, tag_id).await?;
|
||||||
|
|
||||||
if !deleted {
|
if !deleted {
|
||||||
return Err(ApiErr::NotFound("Tag not found".to_string(), None));
|
return Err(ApiErr::NotFound("Tag not found".to_string(), None));
|
||||||
|
|
|
||||||
|
|
@ -5,145 +5,20 @@
|
||||||
},
|
},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{broadcast, RwLock};
|
use tokio::sync::broadcast;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// WebSocket message payload types.
|
pub use plc_platform_core::websocket::{
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
RoomManager, WebSocketManager, WsClientMessage, WsMessage,
|
||||||
#[serde(tag = "type", content = "data")]
|
};
|
||||||
pub enum WsMessage {
|
|
||||||
PointNewValue(crate::telemetry::PointMonitorInfo),
|
|
||||||
PointSetValueBatchResult(crate::connection::BatchSetPointValueRes),
|
|
||||||
EventCreated(plc_platform_core::model::EventRecord),
|
|
||||||
UnitRuntimeChanged(crate::control::runtime::UnitRuntime),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
|
|
||||||
pub enum WsClientMessage {
|
|
||||||
AuthWrite(WsAuthWriteReq),
|
|
||||||
PointSetValueBatch(crate::connection::BatchSetPointValueReq),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct WsAuthWriteReq {
|
|
||||||
pub key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Room manager: room_id -> broadcast sender.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RoomManager {
|
|
||||||
rooms: Arc<RwLock<HashMap<String, broadcast::Sender<WsMessage>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RoomManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
rooms: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get or create room sender.
|
|
||||||
pub async fn get_or_create_room(&self, room_id: &str) -> broadcast::Sender<WsMessage> {
|
|
||||||
let mut rooms = self.rooms.write().await;
|
|
||||||
|
|
||||||
if let Some(sender) = rooms.get(room_id) {
|
|
||||||
return sender.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (sender, _) = broadcast::channel(100);
|
|
||||||
rooms.insert(room_id.to_string(), sender.clone());
|
|
||||||
tracing::info!("Created new room: {}", room_id);
|
|
||||||
sender
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get room sender if room exists.
|
|
||||||
pub async fn get_room(&self, room_id: &str) -> Option<broadcast::Sender<WsMessage>> {
|
|
||||||
let rooms = self.rooms.read().await;
|
|
||||||
rooms.get(room_id).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove room if there are no receivers left.
|
|
||||||
pub async fn remove_room_if_empty(&self, room_id: &str) {
|
|
||||||
let mut rooms = self.rooms.write().await;
|
|
||||||
let should_remove = rooms
|
|
||||||
.get(room_id)
|
|
||||||
.map(|sender| sender.receiver_count() == 0)
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if should_remove {
|
|
||||||
rooms.remove(room_id);
|
|
||||||
tracing::info!("Removed empty room: {}", room_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to room.
|
|
||||||
///
|
|
||||||
/// Returns:
|
|
||||||
/// - Ok(n): n subscribers received it
|
|
||||||
/// - Ok(0): room missing or no active subscribers
|
|
||||||
pub async fn send_to_room(&self, room_id: &str, message: WsMessage) -> Result<usize, String> {
|
|
||||||
if let Some(sender) = self.get_room(room_id).await {
|
|
||||||
match sender.send(message) {
|
|
||||||
Ok(count) => Ok(count),
|
|
||||||
// No receiver is not exceptional in push scenarios.
|
|
||||||
Err(broadcast::error::SendError(_)) => Ok(0),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RoomManager {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WebSocket manager.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct WebSocketManager {
|
|
||||||
public_room: Arc<RoomManager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WebSocketManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
public_room: Arc::new(RoomManager::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to public room.
|
|
||||||
pub async fn send_to_public(&self, message: WsMessage) -> Result<usize, String> {
|
|
||||||
self.public_room.get_or_create_room("public").await;
|
|
||||||
self.public_room.send_to_room("public", message).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to a dedicated client room.
|
|
||||||
pub async fn send_to_client(&self, client_id: Uuid, message: WsMessage) -> Result<usize, String> {
|
|
||||||
self.public_room
|
|
||||||
.send_to_room(&client_id.to_string(), message)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WebSocketManager {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Public websocket handler.
|
/// Public websocket handler.
|
||||||
pub async fn public_websocket_handler(
|
pub async fn public_websocket_handler(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
State(state): State<crate::AppState>,
|
State(state): State<crate::AppState>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let ws_manager = state.ws_manager.clone();
|
let ws_manager = state.platform.ws_manager.clone();
|
||||||
let app_state = state.clone();
|
let app_state = state.clone();
|
||||||
ws.on_upgrade(move |socket| handle_socket(socket, ws_manager, "public".to_string(), app_state))
|
ws.on_upgrade(move |socket| handle_socket(socket, ws_manager, "public".to_string(), app_state))
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +29,7 @@ pub async fn client_websocket_handler(
|
||||||
Path(client_id): Path<Uuid>,
|
Path(client_id): Path<Uuid>,
|
||||||
State(state): State<crate::AppState>,
|
State(state): State<crate::AppState>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let ws_manager = state.ws_manager.clone();
|
let ws_manager = state.platform.ws_manager.clone();
|
||||||
let room_id = client_id.to_string();
|
let room_id = client_id.to_string();
|
||||||
let app_state = state.clone();
|
let app_state = state.clone();
|
||||||
ws.on_upgrade(move |socket| handle_socket(socket, ws_manager, room_id, app_state))
|
ws.on_upgrade(move |socket| handle_socket(socket, ws_manager, room_id, app_state))
|
||||||
|
|
@ -167,8 +42,7 @@ async fn handle_socket(
|
||||||
room_id: String,
|
room_id: String,
|
||||||
state: crate::AppState,
|
state: crate::AppState,
|
||||||
) {
|
) {
|
||||||
let room_sender = ws_manager.public_room.get_or_create_room(&room_id).await;
|
let mut rx = ws_manager.subscribe_room(&room_id).await;
|
||||||
let mut rx = room_sender.subscribe();
|
|
||||||
let mut can_write = false;
|
let mut can_write = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -198,7 +72,7 @@ async fn handle_socket(
|
||||||
results: vec![],
|
results: vec![],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match state.connection_manager.write_point_values_batch(payload).await {
|
match state.platform.connection_manager.write_point_values_batch(payload).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => crate::connection::BatchSetPointValueRes {
|
Err(e) => crate::connection::BatchSetPointValueRes {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -214,7 +88,6 @@ async fn handle_socket(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Err(e) = ws_manager
|
if let Err(e) = ws_manager
|
||||||
.public_room
|
|
||||||
.send_to_room(&room_id, WsMessage::PointSetValueBatchResult(response))
|
.send_to_room(&room_id, WsMessage::PointSetValueBatchResult(response))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
@ -267,5 +140,5 @@ async fn handle_socket(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ws_manager.public_room.remove_room_if_empty(&room_id).await;
|
ws_manager.remove_room_if_empty(&room_id).await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ axum = { version = "0.8", features = ["ws"] }
|
||||||
tower-http = { version = "0.6", features = ["cors", "fs"] }
|
tower-http = { version = "0.6", features = ["cors", "fs"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
|
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "uuid", "json"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tower = { version = "0.5", features = ["util"] }
|
tower = { version = "0.5", features = ["util"] }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::router::build_router;
|
use crate::router::build_router;
|
||||||
|
use plc_platform_core::platform_context::PlatformContext;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
|
pub database_url: String,
|
||||||
pub server_host: String,
|
pub server_host: String,
|
||||||
pub server_port: u16,
|
pub server_port: u16,
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +11,8 @@ pub struct AppConfig {
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub fn from_env() -> Self {
|
pub fn from_env() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
database_url: std::env::var("DATABASE_URL")
|
||||||
|
.expect("DATABASE_URL must be set"),
|
||||||
server_host: std::env::var("OPS_SERVER_HOST")
|
server_host: std::env::var("OPS_SERVER_HOST")
|
||||||
.unwrap_or_else(|_| "127.0.0.1".to_string()),
|
.unwrap_or_else(|_| "127.0.0.1".to_string()),
|
||||||
server_port: std::env::var("OPS_SERVER_PORT")
|
server_port: std::env::var("OPS_SERVER_PORT")
|
||||||
|
|
@ -19,16 +23,16 @@ impl AppConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub app_name: &'static str,
|
pub app_name: &'static str,
|
||||||
pub config: AppConfig,
|
pub config: AppConfig,
|
||||||
|
pub platform: PlatformContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run() {
|
pub async fn run() {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
plc_platform_core::util::log::init_logger();
|
plc_platform_core::util::log::init_logger();
|
||||||
let _platform = plc_platform_core::bootstrap::bootstrap_platform();
|
|
||||||
let _single_instance =
|
let _single_instance =
|
||||||
match plc_platform_core::util::single_instance::try_acquire("PLCControl.OperationSystem") {
|
match plc_platform_core::util::single_instance::try_acquire("PLCControl.OperationSystem") {
|
||||||
Ok(guard) => guard,
|
Ok(guard) => guard,
|
||||||
|
|
@ -42,9 +46,16 @@ pub async fn run() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let config = AppConfig::from_env();
|
||||||
|
let builder = plc_platform_core::bootstrap::bootstrap_platform(&config.database_url)
|
||||||
|
.await
|
||||||
|
.expect("Failed to bootstrap platform");
|
||||||
|
let platform = builder.build();
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
app_name: "operation-system",
|
app_name: "operation-system",
|
||||||
config: AppConfig::from_env(),
|
config,
|
||||||
|
platform,
|
||||||
};
|
};
|
||||||
let app = build_router(state.clone());
|
let app = build_router(state.clone());
|
||||||
let addr = format!("{}:{}", state.config.server_host, state.config.server_port);
|
let addr = format!("{}:{}", state.config.server_host, state.config.server_port);
|
||||||
|
|
@ -59,11 +70,20 @@ pub async fn run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_state() -> AppState {
|
pub fn test_state() -> AppState {
|
||||||
|
let database_url = "postgres://plc:plc@localhost/plc_control_test".to_string();
|
||||||
|
let pool = sqlx::postgres::PgPoolOptions::new()
|
||||||
|
.connect_lazy(&database_url)
|
||||||
|
.expect("lazy pool should build");
|
||||||
|
let connection_manager = std::sync::Arc::new(plc_platform_core::connection::ConnectionManager::new());
|
||||||
|
let ws_manager = std::sync::Arc::new(plc_platform_core::websocket::WebSocketManager::new());
|
||||||
|
|
||||||
AppState {
|
AppState {
|
||||||
app_name: "operation-system",
|
app_name: "operation-system",
|
||||||
config: AppConfig {
|
config: AppConfig {
|
||||||
|
database_url,
|
||||||
server_host: "127.0.0.1".to_string(),
|
server_host: "127.0.0.1".to_string(),
|
||||||
server_port: 0,
|
server_port: 0,
|
||||||
},
|
},
|
||||||
|
platform: PlatformContext::new(pool, connection_manager, ws_manager),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,37 @@
|
||||||
use crate::platform_context::PlatformContext;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn bootstrap_platform() -> PlatformContext {
|
use crate::connection::ConnectionManager;
|
||||||
PlatformContext::new("bootstrap")
|
use crate::db::init_database;
|
||||||
|
use crate::platform_context::PlatformContext;
|
||||||
|
use crate::websocket::WebSocketManager;
|
||||||
|
|
||||||
|
pub struct PlatformBuilder {
|
||||||
|
pub pool: sqlx::PgPool,
|
||||||
|
pub connection_manager: ConnectionManager,
|
||||||
|
pub ws_manager: Arc<WebSocketManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformBuilder {
|
||||||
|
pub fn build(self) -> PlatformContext {
|
||||||
|
PlatformContext::new(
|
||||||
|
self.pool,
|
||||||
|
Arc::new(self.connection_manager),
|
||||||
|
self.ws_manager,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bootstrap_platform(database_url: &str) -> Result<PlatformBuilder, String> {
|
||||||
|
let pool = init_database(database_url)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to initialize database: {}", e))?;
|
||||||
|
|
||||||
|
let connection_manager = ConnectionManager::new();
|
||||||
|
let ws_manager = Arc::new(WebSocketManager::new());
|
||||||
|
|
||||||
|
Ok(PlatformBuilder {
|
||||||
|
pool,
|
||||||
|
connection_manager,
|
||||||
|
ws_manager,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,25 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::connection::ConnectionManager;
|
||||||
|
use crate::websocket::WebSocketManager;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PlatformContext {
|
pub struct PlatformContext {
|
||||||
pub config_name: Arc<str>,
|
pub pool: sqlx::PgPool,
|
||||||
|
pub connection_manager: Arc<ConnectionManager>,
|
||||||
|
pub ws_manager: Arc<WebSocketManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformContext {
|
impl PlatformContext {
|
||||||
pub fn new(config_name: impl Into<Arc<str>>) -> Self {
|
pub fn new(
|
||||||
|
pool: sqlx::PgPool,
|
||||||
|
connection_manager: Arc<ConnectionManager>,
|
||||||
|
ws_manager: Arc<WebSocketManager>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config_name: config_name.into(),
|
pool,
|
||||||
|
connection_manager,
|
||||||
|
ws_manager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,19 @@ impl WebSocketManager {
|
||||||
.send_to_room(&client_id.to_string(), message)
|
.send_to_room(&client_id.to_string(), message)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn subscribe_room(&self, room_id: &str) -> broadcast::Receiver<WsMessage> {
|
||||||
|
let sender = self.public_room.get_or_create_room(room_id).await;
|
||||||
|
sender.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_to_room(&self, room_id: &str, message: WsMessage) -> Result<usize, String> {
|
||||||
|
self.public_room.send_to_room(room_id, message).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_room_if_empty(&self, room_id: &str) {
|
||||||
|
self.public_room.remove_room_if_empty(room_id).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WebSocketManager {
|
impl Default for WebSocketManager {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
use plc_platform_core::bootstrap::bootstrap_platform;
|
|
||||||
use plc_platform_core::platform_context::PlatformContext;
|
use plc_platform_core::platform_context::PlatformContext;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn platform_context_type_is_public() {
|
fn platform_context_type_is_public() {
|
||||||
let context = bootstrap_platform();
|
|
||||||
assert_eq!(context.config_name.as_ref(), "bootstrap");
|
|
||||||
|
|
||||||
fn assert_send_sync_clone<T: Send + Sync + Clone>() {}
|
fn assert_send_sync_clone<T: Send + Sync + Clone>() {}
|
||||||
assert_send_sync_clone::<PlatformContext>();
|
assert_send_sync_clone::<PlatformContext>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue