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.
This commit is contained in:
Orne Brocaar 2025-02-10 15:21:58 +00:00
parent de7e0c619d
commit 1d76fabdb0
4 changed files with 254 additions and 2 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1899,6 +1899,69 @@ impl ApplicationService for Application {
Ok(resp)
}
async fn list_device_profiles(
&self,
request: Request<api::ListApplicationDeviceProfilesRequest>,
) -> Result<Response<api::ListApplicationDeviceProfilesResponse>, 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<api::ListApplicationDeviceTagsRequest>,
) -> Result<Response<api::ListApplicationDeviceTagsResponse>, 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)]

View File

@ -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<Vec<String>,
Ok(keys.iter().map(|k| k.key.clone()).collect())
}
pub async fn get_device_profiles(
application_id: Uuid,
) -> Result<Vec<(fields::Uuid, String)>, 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<BTreeMap<String, Vec<String>>, Error> {
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
let items: Vec<DeviceTags> = 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::sql_types::Uuid, _>(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<BTreeMap<String, Vec<String>>, Error> {
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
let items: Vec<DeviceTags> = 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::sql_types::Uuid, _>(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::*;