2026-07-02 05:38:01 +08:00
|
|
|
|
//! 设备管理 API — CRUD + 详情 + 级联下拉
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! 提供柜子的增删改查、详情查询、安全码重新生成,
|
|
|
|
|
|
//! 以及柜子/仓板/仓体的级联下拉选项。
|
|
|
|
|
|
|
|
|
|
|
|
use axum::extract::{Path, Query, State};
|
|
|
|
|
|
use axum::Json;
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
|
use sqlx::MySqlPool;
|
|
|
|
|
|
|
|
|
|
|
|
use super::organizations::{
|
|
|
|
|
|
generate_abstract_id, generate_auth_str,
|
|
|
|
|
|
CabinetDetail, CabinetRow, CabinBoardRow, CabinBoardWithCompartments,
|
|
|
|
|
|
CompartmentRow, CreateCabinetBody, UpdateCabinetBody,
|
|
|
|
|
|
};
|
|
|
|
|
|
use crate::error::AppError;
|
|
|
|
|
|
use crate::middleware::auth::{self, CurrentUser};
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// 项目访问权限验证
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/// 验证用户是否有权访问指定项目
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 总管理员(role_level >= 2)可访问所有项目;
|
|
|
|
|
|
/// 企业管理员只能访问本组织下的项目。
|
|
|
|
|
|
async fn check_project_access(
|
|
|
|
|
|
user: &CurrentUser,
|
|
|
|
|
|
pool: &MySqlPool,
|
|
|
|
|
|
project_id: i64,
|
|
|
|
|
|
) -> Result<(), AppError> {
|
|
|
|
|
|
// 总管理员可访问所有项目
|
|
|
|
|
|
if user.role_level >= 2 {
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(org_id) = user.organization_id {
|
|
|
|
|
|
// 验证项目是否属于用户的组织
|
|
|
|
|
|
let project: Option<(i64,)> = sqlx::query_as(
|
|
|
|
|
|
"SELECT id FROM projects WHERE id = ? AND organization_id = ?",
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(project_id)
|
|
|
|
|
|
.bind(org_id)
|
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
if project.is_none() {
|
|
|
|
|
|
return Err(AppError::Forbidden("无权访问该项目".into()));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return Err(AppError::Forbidden("无组织关联".into()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// 设备 CRUD
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/// GET /api/projects/:project_id/cabinets — 按项目查询柜子列表
|
|
|
|
|
|
pub async fn list_cabinets(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Path(project_id): Path<i64>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:view")?;
|
|
|
|
|
|
check_project_access(&user, &state.mysql, project_id).await?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
let rows = sqlx::query_as::<_, CabinetRow>(
|
|
|
|
|
|
r#"SELECT id, project_id, abstract_id, imei, name, status
|
|
|
|
|
|
FROM cabinets WHERE project_id = ? ORDER BY id"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(project_id)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!(rows)))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// POST /api/cabinets — 批量添加柜子
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 每个 IMEI 创建一个柜子,同时生成默认仓控板和 6 个仓体。
|
|
|
|
|
|
pub async fn create_cabinet(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Json(body): Json<CreateCabinetBody>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:create")?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
if body.imeis.is_empty() {
|
|
|
|
|
|
return Err(AppError::BadRequest("IMEI 列表不能为空".into()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut unique_imeis: Vec<String> = Vec::new();
|
|
|
|
|
|
for imei in &body.imeis {
|
|
|
|
|
|
let trimmed = imei.trim().to_string();
|
|
|
|
|
|
if trimmed.len() != 15 {
|
|
|
|
|
|
return Err(AppError::BadRequest(
|
|
|
|
|
|
format!("IMEI 必须为15位数字: {}", trimmed),
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
if !unique_imeis.contains(&trimmed) {
|
|
|
|
|
|
unique_imeis.push(trimmed);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut created_ids: Vec<i64> = Vec::new();
|
|
|
|
|
|
|
|
|
|
|
|
for imei in &unique_imeis {
|
|
|
|
|
|
let abstract_id = generate_abstract_id(imei);
|
|
|
|
|
|
let auth_str = generate_auth_str();
|
|
|
|
|
|
|
|
|
|
|
|
let mut tx = db.begin().await.map_err(AppError::Database)?;
|
|
|
|
|
|
|
|
|
|
|
|
let result = sqlx::query(
|
|
|
|
|
|
r#"INSERT INTO cabinets (project_id, abstract_id, imei, auth_str)
|
|
|
|
|
|
VALUES (?, ?, ?, ?)"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(body.project_id)
|
|
|
|
|
|
.bind(&abstract_id)
|
|
|
|
|
|
.bind(imei)
|
|
|
|
|
|
.bind(&auth_str)
|
|
|
|
|
|
.execute(&mut *tx)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
let cabinet_id = result.last_insert_id() as i64;
|
|
|
|
|
|
|
|
|
|
|
|
let board_result = sqlx::query(
|
|
|
|
|
|
r#"INSERT INTO cabin_boards (cabinet_id, board_id) VALUES (?, 1)"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(cabinet_id)
|
|
|
|
|
|
.execute(&mut *tx)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
let board_pk = board_result.last_insert_id() as i64;
|
|
|
|
|
|
|
|
|
|
|
|
for ch in 1..=6 {
|
|
|
|
|
|
sqlx::query(
|
|
|
|
|
|
r#"INSERT INTO compartments (cabin_board_id, channel_id) VALUES (?, ?)"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(board_pk)
|
|
|
|
|
|
.bind(ch)
|
|
|
|
|
|
.execute(&mut *tx)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tx.commit().await.map_err(AppError::Database)?;
|
|
|
|
|
|
created_ids.push(cabinet_id);
|
|
|
|
|
|
tracing::info!("创建柜子 id={} abstract_id={} imei={}", cabinet_id, abstract_id, imei);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!({ "ids": created_ids })))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// PUT /api/cabinets/:id — 更新柜子(划拨项目等)
|
|
|
|
|
|
pub async fn update_cabinet(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Path(id): Path<i64>,
|
|
|
|
|
|
Json(body): Json<UpdateCabinetBody>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:edit")?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
let rows = sqlx::query(
|
|
|
|
|
|
r#"UPDATE cabinets SET
|
|
|
|
|
|
project_id = COALESCE(?, project_id),
|
|
|
|
|
|
name = COALESCE(?, name)
|
|
|
|
|
|
WHERE id = ?"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(body.project_id)
|
|
|
|
|
|
.bind(&body.name)
|
|
|
|
|
|
.bind(id)
|
|
|
|
|
|
.execute(db)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
.rows_affected();
|
|
|
|
|
|
|
|
|
|
|
|
if rows == 0 {
|
|
|
|
|
|
return Err(AppError::NotFound(format!("柜子 {} 不存在", id)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tracing::info!("更新柜子 id={}", id);
|
|
|
|
|
|
Ok(Json(json!({})))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// DELETE /api/cabinets/:id — 删除柜子(级联删除仓控板和仓体)
|
|
|
|
|
|
pub async fn delete_cabinet(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Path(id): Path<i64>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:delete")?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
let mut tx = db.begin().await.map_err(AppError::Database)?;
|
|
|
|
|
|
|
|
|
|
|
|
sqlx::query(
|
|
|
|
|
|
r#"DELETE compartments FROM compartments
|
|
|
|
|
|
INNER JOIN cabin_boards ON compartments.cabin_board_id = cabin_boards.id
|
|
|
|
|
|
WHERE cabin_boards.cabinet_id = ?"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(id)
|
|
|
|
|
|
.execute(&mut *tx)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
sqlx::query(r#"DELETE FROM cabin_boards WHERE cabinet_id = ?"#)
|
|
|
|
|
|
.bind(id)
|
|
|
|
|
|
.execute(&mut *tx)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
let rows = sqlx::query(r#"DELETE FROM cabinets WHERE id = ?"#)
|
|
|
|
|
|
.bind(id)
|
|
|
|
|
|
.execute(&mut *tx)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
.rows_affected();
|
|
|
|
|
|
|
|
|
|
|
|
tx.commit().await.map_err(AppError::Database)?;
|
|
|
|
|
|
|
|
|
|
|
|
if rows == 0 {
|
|
|
|
|
|
return Err(AppError::NotFound(format!("柜子 {} 不存在", id)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tracing::info!("删除柜子 id={}", id);
|
|
|
|
|
|
Ok(Json(json!({})))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// POST /api/cabinets/:id/regenerate-auth — 重新生成安全码
|
|
|
|
|
|
pub async fn regenerate_auth(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Path(id): Path<i64>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:edit")?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
let auth_str = generate_auth_str();
|
|
|
|
|
|
|
|
|
|
|
|
let rows = sqlx::query(r#"UPDATE cabinets SET auth_str = ? WHERE id = ?"#)
|
|
|
|
|
|
.bind(&auth_str)
|
|
|
|
|
|
.bind(id)
|
|
|
|
|
|
.execute(db)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
.rows_affected();
|
|
|
|
|
|
|
|
|
|
|
|
if rows == 0 {
|
|
|
|
|
|
return Err(AppError::NotFound(format!("柜子 {} 不存在", id)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tracing::info!("重新生成 auth_str id={}", id);
|
|
|
|
|
|
Ok(Json(json!({ "auth_str": auth_str })))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// 柜子详情(含仓控板 + 仓体)
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/// GET /api/cabinets/:id — 柜子详情,包含仓控板和仓体
|
|
|
|
|
|
pub async fn get_cabinet_detail(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Path(id): Path<i64>,
|
|
|
|
|
|
) -> Result<Json<CabinetDetail>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:view")?;
|
|
|
|
|
|
|
|
|
|
|
|
let db = &state.mysql;
|
|
|
|
|
|
let cabinet = sqlx::query_as::<_, CabinetRow>(
|
|
|
|
|
|
r#"SELECT id, project_id, abstract_id, imei, name, status
|
|
|
|
|
|
FROM cabinets WHERE id = ?"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(id)
|
|
|
|
|
|
.fetch_optional(db)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
.ok_or_else(|| AppError::NotFound(format!("柜子 {} 不存在", id)))?;
|
|
|
|
|
|
|
|
|
|
|
|
// 验证用户是否有权访问该柜子所属的项目
|
|
|
|
|
|
if let Some(pid) = cabinet.project_id {
|
|
|
|
|
|
check_project_access(&user, db, pid).await?;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let boards: Vec<CabinBoardRow> = sqlx::query_as(
|
|
|
|
|
|
r#"SELECT id, cabinet_id, board_id, status
|
|
|
|
|
|
FROM cabin_boards WHERE cabinet_id = ? ORDER BY board_id"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(id)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
let mut cabin_boards = Vec::new();
|
|
|
|
|
|
for board in boards {
|
|
|
|
|
|
let compartments: Vec<CompartmentRow> = sqlx::query_as(
|
|
|
|
|
|
r#"SELECT id, cabin_board_id, channel_id, status
|
|
|
|
|
|
FROM compartments WHERE cabin_board_id = ? ORDER BY channel_id"#,
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(board.id)
|
|
|
|
|
|
.fetch_all(db)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
cabin_boards.push(CabinBoardWithCompartments {
|
|
|
|
|
|
id: board.id,
|
|
|
|
|
|
board_id: board.board_id,
|
|
|
|
|
|
status: board.status,
|
|
|
|
|
|
compartments,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Json(CabinetDetail {
|
|
|
|
|
|
id: cabinet.id,
|
|
|
|
|
|
project_id: cabinet.project_id,
|
|
|
|
|
|
abstract_id: cabinet.abstract_id,
|
|
|
|
|
|
imei: cabinet.imei,
|
|
|
|
|
|
name: cabinet.name,
|
|
|
|
|
|
status: cabinet.status,
|
|
|
|
|
|
cabin_boards,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// 级联下拉选项
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/// 柜子下拉查询参数
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
|
pub struct CabinetListQuery {
|
|
|
|
|
|
pub project_id: Option<i64>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 仓板下拉查询参数
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
|
pub struct CabinBoardListQuery {
|
|
|
|
|
|
pub cabinet_id: i64,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 仓体下拉查询参数
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
|
pub struct CompartmentListQuery {
|
|
|
|
|
|
pub cabin_board_id: i64,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 柜子下拉选项
|
|
|
|
|
|
#[derive(Debug, Serialize, sqlx::FromRow)]
|
|
|
|
|
|
pub struct CabinetOption {
|
|
|
|
|
|
pub id: i64,
|
|
|
|
|
|
pub name: Option<String>,
|
|
|
|
|
|
pub abstract_id: String,
|
2026-07-02 20:54:37 +08:00
|
|
|
|
pub imei: Option<String>,
|
|
|
|
|
|
pub iccid: Option<String>,
|
|
|
|
|
|
pub address: Option<String>,
|
|
|
|
|
|
pub status: i8,
|
2026-07-02 05:38:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 仓板下拉选项
|
|
|
|
|
|
#[derive(Debug, Serialize, sqlx::FromRow)]
|
|
|
|
|
|
pub struct CabinBoardOption {
|
|
|
|
|
|
pub id: i64,
|
|
|
|
|
|
pub board_id: i8,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 仓体下拉选项
|
|
|
|
|
|
#[derive(Debug, Serialize, sqlx::FromRow)]
|
|
|
|
|
|
pub struct CompartmentOption {
|
|
|
|
|
|
pub id: i64,
|
|
|
|
|
|
pub channel_id: i8,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// GET /api/cabinets/options — 柜子下拉列表
|
|
|
|
|
|
pub async fn list_cabinet_options(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Query(params): Query<CabinetListQuery>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:view")?;
|
|
|
|
|
|
|
|
|
|
|
|
let rows = if let Some(pid) = params.project_id {
|
|
|
|
|
|
sqlx::query_as::<_, CabinetOption>(
|
2026-07-02 20:54:37 +08:00
|
|
|
|
"SELECT id, name, abstract_id, imei, iccid, address, status FROM cabinets WHERE project_id = ? ORDER BY id"
|
2026-07-02 05:38:01 +08:00
|
|
|
|
)
|
|
|
|
|
|
.bind(pid)
|
|
|
|
|
|
.fetch_all(&state.mysql)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sqlx::query_as::<_, CabinetOption>(
|
2026-07-02 20:54:37 +08:00
|
|
|
|
"SELECT id, name, abstract_id, imei, iccid, address, status FROM cabinets ORDER BY id"
|
2026-07-02 05:38:01 +08:00
|
|
|
|
)
|
|
|
|
|
|
.fetch_all(&state.mysql)
|
|
|
|
|
|
.await?
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!({ "code": 0, "data": rows, "message": "ok" })))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// GET /api/cabin-boards/options — 仓板下拉列表
|
|
|
|
|
|
pub async fn list_cabin_board_options(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Query(params): Query<CabinBoardListQuery>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:view")?;
|
|
|
|
|
|
|
|
|
|
|
|
let rows = sqlx::query_as::<_, CabinBoardOption>(
|
|
|
|
|
|
"SELECT id, board_id FROM cabin_boards WHERE cabinet_id = ? ORDER BY board_id"
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(params.cabinet_id)
|
|
|
|
|
|
.fetch_all(&state.mysql)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!({ "code": 0, "data": rows, "message": "ok" })))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// GET /api/compartments/options — 仓体下拉列表
|
|
|
|
|
|
pub async fn list_compartment_options(
|
|
|
|
|
|
user: CurrentUser,
|
|
|
|
|
|
State(state): State<super::AppState>,
|
|
|
|
|
|
Query(params): Query<CompartmentListQuery>,
|
|
|
|
|
|
) -> Result<Json<Value>, AppError> {
|
|
|
|
|
|
auth::check_permission(&user, "device:view")?;
|
|
|
|
|
|
|
|
|
|
|
|
let rows = sqlx::query_as::<_, CompartmentOption>(
|
|
|
|
|
|
"SELECT id, channel_id FROM compartments WHERE cabin_board_id = ? ORDER BY channel_id"
|
|
|
|
|
|
)
|
|
|
|
|
|
.bind(params.cabin_board_id)
|
|
|
|
|
|
.fetch_all(&state.mysql)
|
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!({ "code": 0, "data": rows, "message": "ok" })))
|
|
|
|
|
|
}
|