From 1d76fabdb09026cfbedd0b4cf7721adaa03830df Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 10 Feb 2025 15:21:58 +0000 Subject: [PATCH] Add APIs + functions to get app. device-profiles and tags. These API methods can be used to given an application id, retrieve the list of used device-profiles and device tags. --- api/proto/api/application.proto | 50 ++++++++++ .../proto/chirpstack/api/application.proto | 50 ++++++++++ chirpstack/src/api/application.rs | 63 +++++++++++++ chirpstack/src/storage/application.rs | 93 ++++++++++++++++++- 4 files changed, 254 insertions(+), 2 deletions(-) diff --git a/api/proto/api/application.proto b/api/proto/api/application.proto index e2d51eda..d009c700 100644 --- a/api/proto/api/application.proto +++ b/api/proto/api/application.proto @@ -427,6 +427,20 @@ service ApplicationService { post : "/api/applications/{application_id}/integrations/mqtt/certificate" }; } + + // List device-profiles used within the given application. + rpc ListDeviceProfiles(ListApplicationDeviceProfilesRequest) returns (ListApplicationDeviceProfilesResponse) { + option (google.api.http) = { + get: "/api/applications/{application_id}/device-profiles" + }; + } + + // List device tags used within the given application. + rpc ListDeviceTags(ListApplicationDeviceTagsRequest) returns (ListApplicationDeviceTagsResponse) { + option (google.api.http) = { + get: "/api/applications/{application_id}/device-tags" + }; + } } enum Encoding { @@ -1099,3 +1113,39 @@ message GenerateMqttIntegrationClientCertificateResponse { // Expires at defines the expiration date of the certificate. google.protobuf.Timestamp expires_at = 4; } + +message ApplicationDeviceProfileListItem { + // Device-profile ID (UUID). + string id = 1; + + // Name. + string name = 2; +} + +message ListApplicationDeviceProfilesRequest { + // Application ID (UUID). + string application_id = 1; +}; + +message ListApplicationDeviceProfilesResponse { + // Device-profiles. + repeated ApplicationDeviceProfileListItem result = 1; +} + +message ApplicationDeviceTagListItem { + // Tag key. + string key = 1; + + // Used values. + repeated string values = 2; +} + +message ListApplicationDeviceTagsRequest { + // Application ID (UUID). + string application_id = 1; +} + +message ListApplicationDeviceTagsResponse { + // Device tags. + repeated ApplicationDeviceTagListItem result = 1; +} diff --git a/api/rust/proto/chirpstack/api/application.proto b/api/rust/proto/chirpstack/api/application.proto index e2d51eda..d009c700 100644 --- a/api/rust/proto/chirpstack/api/application.proto +++ b/api/rust/proto/chirpstack/api/application.proto @@ -427,6 +427,20 @@ service ApplicationService { post : "/api/applications/{application_id}/integrations/mqtt/certificate" }; } + + // List device-profiles used within the given application. + rpc ListDeviceProfiles(ListApplicationDeviceProfilesRequest) returns (ListApplicationDeviceProfilesResponse) { + option (google.api.http) = { + get: "/api/applications/{application_id}/device-profiles" + }; + } + + // List device tags used within the given application. + rpc ListDeviceTags(ListApplicationDeviceTagsRequest) returns (ListApplicationDeviceTagsResponse) { + option (google.api.http) = { + get: "/api/applications/{application_id}/device-tags" + }; + } } enum Encoding { @@ -1099,3 +1113,39 @@ message GenerateMqttIntegrationClientCertificateResponse { // Expires at defines the expiration date of the certificate. google.protobuf.Timestamp expires_at = 4; } + +message ApplicationDeviceProfileListItem { + // Device-profile ID (UUID). + string id = 1; + + // Name. + string name = 2; +} + +message ListApplicationDeviceProfilesRequest { + // Application ID (UUID). + string application_id = 1; +}; + +message ListApplicationDeviceProfilesResponse { + // Device-profiles. + repeated ApplicationDeviceProfileListItem result = 1; +} + +message ApplicationDeviceTagListItem { + // Tag key. + string key = 1; + + // Used values. + repeated string values = 2; +} + +message ListApplicationDeviceTagsRequest { + // Application ID (UUID). + string application_id = 1; +} + +message ListApplicationDeviceTagsResponse { + // Device tags. + repeated ApplicationDeviceTagListItem result = 1; +} diff --git a/chirpstack/src/api/application.rs b/chirpstack/src/api/application.rs index 24a496b7..950dfd19 100644 --- a/chirpstack/src/api/application.rs +++ b/chirpstack/src/api/application.rs @@ -1899,6 +1899,69 @@ impl ApplicationService for Application { Ok(resp) } + + async fn list_device_profiles( + &self, + request: Request, + ) -> Result, Status> { + let req = request.get_ref(); + let app_id = Uuid::from_str(&req.application_id).map_err(|e| e.status())?; + + self.validator + .validate( + request.extensions(), + validator::ValidateApplicationAccess::new(validator::Flag::Read, app_id), + ) + .await?; + + let dp_items = application::get_device_profiles(app_id) + .await + .map_err(|e| e.status())?; + + let mut resp = Response::new(api::ListApplicationDeviceProfilesResponse { + result: dp_items + .iter() + .map(|v| api::ApplicationDeviceProfileListItem { + id: v.0.to_string(), + name: v.1.clone(), + }) + .collect(), + }); + resp.metadata_mut() + .insert("x-log-application_id", req.application_id.parse().unwrap()); + + Ok(resp) + } + + async fn list_device_tags( + &self, + request: Request, + ) -> Result, Status> { + let req = request.get_ref(); + let app_id = Uuid::from_str(&req.application_id).map_err(|e| e.status())?; + + self.validator + .validate( + request.extensions(), + validator::ValidateApplicationAccess::new(validator::Flag::Read, app_id), + ) + .await?; + + let tags = application::get_device_tags(app_id) + .await + .map_err(|e| e.status())?; + + let mut resp = Response::new(api::ListApplicationDeviceTagsResponse { + result: tags + .into_iter() + .map(|(k, v)| api::ApplicationDeviceTagListItem { key: k, values: v }) + .collect(), + }); + resp.metadata_mut() + .insert("x-log-application_id", req.application_id.parse().unwrap()); + + Ok(resp) + } } #[cfg(test)] diff --git a/chirpstack/src/storage/application.rs b/chirpstack/src/storage/application.rs index f9a88e66..063d987f 100644 --- a/chirpstack/src/storage/application.rs +++ b/chirpstack/src/storage/application.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt; use std::str::FromStr; @@ -15,7 +15,7 @@ use tracing::info; use uuid::Uuid; use super::error::Error; -use super::schema::{application, application_integration}; +use super::schema::{application, application_integration, device, device_profile}; use super::{fields, get_async_db_conn}; #[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)] @@ -594,6 +594,95 @@ pub async fn get_measurement_keys(application_id: &Uuid) -> Result, Ok(keys.iter().map(|k| k.key.clone()).collect()) } +pub async fn get_device_profiles( + application_id: Uuid, +) -> Result, Error> { + let result: Vec<(fields::Uuid, String)> = device_profile::dsl::device_profile + .select((device_profile::dsl::id, device_profile::dsl::name)) + .distinct() + .inner_join(device::table.on(device::dsl::device_profile_id.eq(device_profile::dsl::id))) + .filter(device::dsl::application_id.eq(fields::Uuid::from(application_id))) + .order_by(device_profile::dsl::name) + .load(&mut get_async_db_conn().await?) + .await?; + + Ok(result) +} + +#[derive(QueryableByName)] +struct DeviceTags { + #[diesel(sql_type = diesel::sql_types::Text)] + key: String, + #[diesel(sql_type = diesel::sql_types::Text)] + value: String, +} + +#[cfg(feature = "postgres")] +pub async fn get_device_tags(application_id: Uuid) -> Result>, Error> { + let mut out: BTreeMap> = BTreeMap::new(); + + let items: Vec = diesel::sql_query( + r#" + select + distinct + t.key, + t.value + from device d + join lateral jsonb_each_text(d.tags) t + on true + where + d.application_id = $1 + order by + t.key, + t.value + "#, + ) + .bind::(fields::Uuid::from(application_id)) + .load(&mut get_async_db_conn().await?) + .await + .map_err(|e| Error::from_diesel(e, application_id.to_string()))?; + + for item in &items { + let entry = out.entry(item.key.clone()).or_default(); + entry.push(item.value.clone()); + } + + Ok(out) +} + +#[cfg(feature = "sqlite")] +pub async fn get_device_tags(application_id: Uuid) -> Result>, Error> { + let mut out: BTreeMap> = BTreeMap::new(); + + let items: Vec = diesel::sql_query( + r#" + select + distinct + t.key, + t.value + from + device d, + json_each(d.tags) as t + where + d.application_id = ?1 + order by + t.key, + t.value + "#, + ) + .bind::(fields::Uuid::from(application_id)) + .load(&mut get_async_db_conn().await?) + .await + .map_err(|e| Error::from_diesel(e, application_id.to_string()))?; + + for item in &items { + let entry = out.entry(item.key.clone()).or_default(); + entry.push(item.value.clone()); + } + + Ok(out) +} + #[cfg(test)] pub mod test { use super::*;