Refactor code to use diesel-async.

This still depends on unreleased diesel and diesel-async code. As soon
as new diesel and diesel-async code has been released, we can remove
the [patch.crates-io] from Cargo.toml.
This commit is contained in:
Orne Brocaar 2023-11-28 13:12:18 +00:00
parent 3f57609981
commit 8e2eda3d5b
30 changed files with 3384 additions and 4122 deletions

219
Cargo.lock generated
View File

@ -759,9 +759,11 @@ dependencies = [
"chrono",
"clap",
"diesel",
"diesel-async",
"diesel_migrations",
"dotenv",
"futures",
"futures-util",
"gcp_auth",
"geohash",
"handlebars",
@ -796,6 +798,9 @@ dependencies = [
"reqwest",
"rquickjs",
"rust-embed",
"rustls",
"rustls-native-certs",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
@ -804,6 +809,8 @@ dependencies = [
"thiserror",
"tokio",
"tokio-executor-trait",
"tokio-postgres",
"tokio-postgres-rustls",
"tokio-reactor-trait",
"tokio-stream",
"toml",
@ -1172,6 +1179,24 @@ dependencies = [
"generic-array",
]
[[package]]
name = "deadpool"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
dependencies = [
"async-trait",
"deadpool-runtime",
"num_cpus",
"tokio",
]
[[package]]
name = "deadpool-runtime"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
[[package]]
name = "der"
version = "0.7.8"
@ -1204,9 +1229,8 @@ dependencies = [
[[package]]
name = "diesel"
version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8"
version = "2.1.1"
source = "git+https://github.com/diesel-rs/diesel.git?rev=566dcccc6df6adb6ceddef8df5e1806e2a065c40#566dcccc6df6adb6ceddef8df5e1806e2a065c40"
dependencies = [
"bigdecimal",
"bitflags 2.4.1",
@ -1217,19 +1241,31 @@ dependencies = [
"num-bigint",
"num-integer",
"num-traits",
"pq-sys",
"r2d2",
"serde_json",
"uuid",
]
[[package]]
name = "diesel-async"
version = "0.4.1"
source = "git+https://github.com/weiznich/diesel_async.git?rev=017ebe2fb7a2709ab5db92148dea5ce812a35e09#017ebe2fb7a2709ab5db92148dea5ce812a35e09"
dependencies = [
"async-trait",
"deadpool",
"diesel",
"futures-util",
"scoped-futures",
"tokio",
"tokio-postgres",
]
[[package]]
name = "diesel_derives"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44"
version = "2.1.0"
source = "git+https://github.com/diesel-rs/diesel.git?rev=566dcccc6df6adb6ceddef8df5e1806e2a065c40#566dcccc6df6adb6ceddef8df5e1806e2a065c40"
dependencies = [
"diesel_table_macro_syntax",
"dsl_auto_type",
"proc-macro2",
"quote",
"syn 2.0.39",
@ -1249,8 +1285,7 @@ dependencies = [
[[package]]
name = "diesel_table_macro_syntax"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
source = "git+https://github.com/diesel-rs/diesel.git?rev=566dcccc6df6adb6ceddef8df5e1806e2a065c40#566dcccc6df6adb6ceddef8df5e1806e2a065c40"
dependencies = [
"syn 2.0.39",
]
@ -1306,6 +1341,19 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dsl_auto_type"
version = "0.1.0"
source = "git+https://github.com/diesel-rs/diesel.git?rev=566dcccc6df6adb6ceddef8df5e1806e2a065c40#566dcccc6df6adb6ceddef8df5e1806e2a065c40"
dependencies = [
"darling",
"either",
"heck",
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]]
name = "dtoa"
version = "1.0.9"
@ -1453,6 +1501,12 @@ dependencies = [
"async-trait",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fastrand"
version = "1.9.0"
@ -1484,6 +1538,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7"
[[package]]
name = "finl_unicode"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
[[package]]
name = "fixedbitset"
version = "0.4.2"
@ -2410,6 +2470,16 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.6.4"
@ -2968,6 +3038,15 @@ dependencies = [
"indexmap 2.1.0",
]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared 0.11.2",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
@ -2977,6 +3056,15 @@ dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.3"
@ -3095,6 +3183,35 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "postgres-protocol"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520"
dependencies = [
"base64 0.21.5",
"byteorder",
"bytes",
"fallible-iterator",
"hmac",
"md-5",
"memchr",
"rand",
"sha2",
"stringprep",
]
[[package]]
name = "postgres-types"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c"
dependencies = [
"bytes",
"fallible-iterator",
"postgres-protocol",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -3107,15 +3224,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pq-sys"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd"
dependencies = [
"vcpkg",
]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
@ -3733,6 +3841,16 @@ dependencies = [
"parking_lot",
]
[[package]]
name = "scoped-futures"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467"
dependencies = [
"cfg-if",
"pin-utils",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@ -4110,10 +4228,21 @@ dependencies = [
"new_debug_unreachable",
"once_cell",
"parking_lot",
"phf_shared",
"phf_shared 0.10.0",
"precomputed-hash",
]
[[package]]
name = "stringprep"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6"
dependencies = [
"finl_unicode",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -4344,6 +4473,46 @@ dependencies = [
"syn 2.0.39",
]
[[package]]
name = "tokio-postgres"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8"
dependencies = [
"async-trait",
"byteorder",
"bytes",
"fallible-iterator",
"futures-channel",
"futures-util",
"log",
"parking_lot",
"percent-encoding",
"phf",
"pin-project-lite",
"postgres-protocol",
"postgres-types",
"rand",
"socket2 0.5.5",
"tokio",
"tokio-util",
"whoami",
]
[[package]]
name = "tokio-postgres-rustls"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd5831152cb0d3f79ef5523b357319ba154795d64c7078b2daa95a803b54057f"
dependencies = [
"futures",
"ring 0.16.20",
"rustls",
"tokio",
"tokio-postgres",
"tokio-rustls",
]
[[package]]
name = "tokio-reactor-trait"
version = "1.1.0"
@ -4934,6 +5103,16 @@ dependencies = [
"rustix 0.38.25",
]
[[package]]
name = "whoami"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
dependencies = [
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -13,3 +13,7 @@ members = [
opt-level = 'z'
lto = true
codegen-units = 1
[patch.crates-io]
diesel = { git = "https://github.com/diesel-rs/diesel.git", rev = "566dcccc6df6adb6ceddef8df5e1806e2a065c40" }
diesel-async = { git = "https://github.com/weiznich/diesel_async.git", rev = "017ebe2fb7a2709ab5db92148dea5ce812a35e09" }

View File

@ -26,14 +26,16 @@ handlebars = "4.4"
validator = "0.16"
diesel = { version = "2.1", features = [
"chrono",
"postgres",
"r2d2",
"uuid",
"serde_json",
"numeric",
"64-column-tables",
"postgres_backend",
] }
diesel_migrations = { version = "2.1" }
diesel-async = { version = "0.4", features = ["deadpool", "postgres", "async-connection-wrapper"] }
tokio-postgres = "0.7"
tokio-postgres-rustls = "0.10.0"
r2d2 = "0.8"
bigdecimal = "0.4"
redis = { version = "0.23", features = ["r2d2", "cluster", "tls-rustls"] }
@ -84,6 +86,7 @@ warp = { version = "0.3", features = ["tls"], default-features = false }
hyper = "0.14"
tower = "0.4"
futures = "0.3"
futures-util = "0.3"
http = "0.2"
http-body = "0.4"
rust-embed = "8.0"
@ -98,6 +101,9 @@ anyhow = "1.0"
pbkdf2 = { version = "0.12", features = ["simple"] }
rand_core = { version = "0.6", features = ["std"] }
jsonwebtoken = "8.3"
rustls = "0.21"
rustls-native-certs = "0.6"
rustls-pemfile = "1.0"
openssl = { version = "0.10" }
openidconnect = { version = "3.3", features = ["accept-rfc3339-timestamps"] }

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,14 @@ use std::convert::Infallible;
use std::net::SocketAddr;
use anyhow::{Context, Result};
use diesel::RunQueryDsl;
use diesel_async::RunQueryDsl;
use tokio::task;
use tracing::info;
use warp::{http::Response, http::StatusCode, Filter};
use crate::config;
use crate::monitoring::prometheus;
use crate::storage::{get_db_conn, get_redis_conn};
use crate::storage::{get_async_db_conn, get_redis_conn};
pub async fn setup() {
let conf = config::get();
@ -50,17 +50,17 @@ async fn health_handler() -> Result<impl warp::Reply, Infallible> {
}
async fn _health_handler() -> Result<()> {
let mut c = get_async_db_conn().await?;
diesel::sql_query("select 1")
.execute(&mut c)
.await
.context("PostgreSQL connection error")?;
task::spawn_blocking(move || -> Result<()> {
let mut r = get_redis_conn()?;
if !r.check_connection() {
return Err(anyhow!("Redis connection error"));
}
let mut c = get_db_conn()?;
diesel::sql_query("select 1")
.execute(&mut c)
.context("PostgreSQL connection error")?;
Ok(())
})
.await?

View File

@ -41,11 +41,12 @@ pub fn run() {
# PostgreSQL connection pool.
max_open_connections={{ postgresql.max_open_connections }}
# Min idle connections.
# CA certificate (optional).
#
# This sets the min. number of idle connections in the PostgreSQL connection
# pool (0 = equal to max_open_connections).
min_idle_connections={{ postgresql.min_idle_connections }}
# Set this to the path of the CA certificate in case you are using TLS and
# the server-certificate is not signed by a CA in the platform certificate
# store.
ca_cert="{{ postgresql.ca_cert }}"
# Redis configuration.
@ -459,11 +460,12 @@ pub fn run() {
# PostgreSQL connection pool.
max_open_connections={{ integration.postgresql.max_open_connections }}
# Min idle connections.
# CA certificate (optional).
#
# This sets the min. number of idle connections in the PostgreSQL connection
# pool (0 = equal to max_open_connections).
min_idle_connections={{ integration.postgresql.min_idle_connections }}
# Set this to the path of the CA certificate in case you are using TLS and
# the server-certificate is not signed by a CA in the platform certificate
# store.
ca_cert="{{ integration.postgresql.ca_cert }}"
# AMQP / RabbitMQ integration configuration.

View File

@ -54,7 +54,7 @@ impl Default for Logging {
pub struct Postgresql {
pub dsn: String,
pub max_open_connections: u32,
pub min_idle_connections: u32,
pub ca_cert: String,
}
impl Default for Postgresql {
@ -62,7 +62,7 @@ impl Default for Postgresql {
Postgresql {
dsn: "postgresql://chirpstack:chirpstack@localhost/chirpstack?sslmode=disable".into(),
max_open_connections: 10,
min_idle_connections: 0,
ca_cert: "".into(),
}
}
}
@ -307,7 +307,7 @@ impl Default for MqttIntegrationClient {
pub struct PostgresqlIntegration {
pub dsn: String,
pub max_open_connections: u32,
pub min_idle_connections: u32,
pub ca_cert: String,
}
impl Default for PostgresqlIntegration {
@ -315,7 +315,7 @@ impl Default for PostgresqlIntegration {
PostgresqlIntegration {
dsn: "postgresql://chirpstack_integration:chirpstack_integration@localhost/chirpstack_integration?sslmode=disable".into(),
max_open_connections: 10,
min_idle_connections: 0,
ca_cert: "".into(),
}
}
}

View File

@ -56,6 +56,7 @@ pub async fn setup() -> Result<()> {
}
"postgresql" => integrations.push(Box::new(
postgresql::Integration::new(&conf.integration.postgresql)
.await
.context("Setup PostgreSQL integration")?,
)),
"amqp" => integrations.push(Box::new(

View File

@ -1,19 +1,24 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::str::FromStr;
use anyhow::{Context, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::{ConnectionError, ConnectionResult};
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
use diesel_async::pooled_connection::deadpool::{Object as DeadpoolObject, Pool as DeadpoolPool};
use diesel_async::pooled_connection::{AsyncDieselConnectionManager, ManagerConfig};
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use tokio::task;
use tracing::info;
use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use tracing::{error, info};
use uuid::Uuid;
use super::Integration as IntegrationTrait;
use crate::config::PostgresqlIntegration as Config;
use crate::config::{self, PostgresqlIntegration as Config};
use chirpstack_api::integration;
use schema::{
event_ack, event_integration, event_join, event_location, event_log, event_status,
@ -25,7 +30,8 @@ mod schema;
pub const MIGRATIONS: EmbeddedMigrations =
embed_migrations!("./src/integration/postgresql/migrations");
type PgPool = Pool<ConnectionManager<PgConnection>>;
pub type AsyncPgPool = DeadpoolPool<AsyncPgConnection>;
pub type AsyncPgPoolConnection = DeadpoolObject<AsyncPgConnection>;
#[derive(Insertable)]
#[diesel(table_name = event_up)]
@ -189,32 +195,87 @@ struct EventIntegration {
}
pub struct Integration {
pg_pool: PgPool,
pg_pool: AsyncPgPool,
}
impl Integration {
pub fn new(conf: &Config) -> Result<Integration> {
pub async fn new(conf: &Config) -> Result<Integration> {
info!("Initializing PostgreSQL integration");
let pg_pool = PgPool::builder()
.max_size(conf.max_open_connections)
.min_idle(match conf.min_idle_connections {
0 => None,
_ => Some(conf.min_idle_connections),
})
.build(ConnectionManager::new(&conf.dsn))
.context("Setup PostgreSQL connection pool error")?;
let mut db_conn = pg_pool.get()?;
let mut config = ManagerConfig::default();
config.custom_setup = Box::new(pg_establish_connection);
let mgr =
AsyncDieselConnectionManager::<AsyncPgConnection>::new_with_config(&conf.dsn, config);
let pg_pool = DeadpoolPool::builder(mgr)
.max_size(conf.max_open_connections as usize)
.build()?;
let c = pg_pool.get().await?;
let mut c_wrapped: AsyncConnectionWrapper<AsyncPgPoolConnection> =
AsyncConnectionWrapper::from(c);
info!("Applying schema migrations");
db_conn
.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
tokio::task::spawn_blocking(move || -> Result<()> {
c_wrapped
.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
Ok(())
})
.await??;
Ok(Integration { pg_pool })
}
}
// Source:
// https://github.com/weiznich/diesel_async/blob/main/examples/postgres/pooled-with-rustls/src/main.rs
fn pg_establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
let fut = async {
let root_certs =
pg_root_certs().map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
let rustls_config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_certs)
.with_no_client_auth();
let tls = tokio_postgres_rustls::MakeRustlsConnect::new(rustls_config);
let (client, conn) = tokio_postgres::connect(config, tls)
.await
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
tokio::spawn(async move {
if let Err(e) = conn.await {
error!(error = %e, "PostgreSQL connection error");
}
});
AsyncPgConnection::try_from(client).await
};
fut.boxed()
}
fn pg_root_certs() -> Result<rustls::RootCertStore> {
let conf = config::get();
let mut roots = rustls::RootCertStore::empty();
let certs = rustls_native_certs::load_native_certs()?;
let certs: Vec<_> = certs.into_iter().map(|cert| cert.0).collect();
roots.add_parsable_certificates(&certs);
if !conf.postgresql.ca_cert.is_empty() {
let f = File::open(&conf.integration.postgresql.ca_cert).context("Open ca certificate")?;
let mut reader = BufReader::new(f);
let certs = rustls_pemfile::certs(&mut reader)?;
for cert in certs
.into_iter()
.map(rustls::Certificate)
.collect::<Vec<_>>()
{
roots.add(&cert)?;
}
}
Ok(roots)
}
#[async_trait]
impl IntegrationTrait for Integration {
async fn uplink_event(
@ -254,16 +315,12 @@ impl IntegrationTrait for Integration {
rx_info: serde_json::to_value(&pl.rx_info)?,
tx_info: serde_json::to_value(&pl.tx_info)?,
};
let mut c = self.pg_pool.get()?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_up::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
let mut c = self.pg_pool.get().await?;
diesel::insert_into(event_up::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
@ -295,16 +352,12 @@ impl IntegrationTrait for Integration {
tags: serde_json::to_value(&di.tags)?,
dev_addr: pl.dev_addr.clone(),
};
let mut c = self.pg_pool.get()?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_join::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
let mut c = self.pg_pool.get().await?;
diesel::insert_into(event_join::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
@ -338,16 +391,12 @@ impl IntegrationTrait for Integration {
acknowledged: pl.acknowledged,
f_cnt_down: pl.f_cnt_down as i64,
};
let mut c = self.pg_pool.get()?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_ack::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
let mut c = self.pg_pool.get().await?;
diesel::insert_into(event_ack::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
@ -382,16 +431,12 @@ impl IntegrationTrait for Integration {
gateway_id: pl.gateway_id.clone(),
tx_info: serde_json::to_value(&pl.tx_info)?,
};
let mut c = self.pg_pool.get()?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_tx_ack::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
let mut c = self.pg_pool.get().await?;
diesel::insert_into(event_tx_ack::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
@ -425,16 +470,12 @@ impl IntegrationTrait for Integration {
description: pl.description.clone(),
context: serde_json::to_value(&pl.context)?,
};
let mut c = self.pg_pool.get()?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_log::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
let mut c = self.pg_pool.get().await?;
diesel::insert_into(event_log::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
@ -469,15 +510,12 @@ impl IntegrationTrait for Integration {
battery_level_unavailable: pl.battery_level_unavailable,
battery_level: pl.battery_level,
};
let mut c = self.pg_pool.get()?;
let mut c = self.pg_pool.get().await?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_status::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
diesel::insert_into(event_status::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
@ -514,16 +552,12 @@ impl IntegrationTrait for Integration {
source: loc.source.to_string(),
accuracy: loc.accuracy,
};
let mut c = self.pg_pool.get()?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_location::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
let mut c = self.pg_pool.get().await?;
diesel::insert_into(event_location::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
@ -557,16 +591,12 @@ impl IntegrationTrait for Integration {
event_type: pl.event_type.clone(),
object: serde_json::to_value(&pl.object)?,
};
let mut c = self.pg_pool.get()?;
task::spawn_blocking(move || -> Result<()> {
diesel::insert_into(event_integration::table)
.values(&e)
.execute(&mut c)?;
Ok(())
})
.await??;
let mut c = self.pg_pool.get().await?;
diesel::insert_into(event_integration::table)
.values(&e)
.execute(&mut c)
.await?;
Ok(())
}
}

View File

@ -2,13 +2,13 @@ use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::dsl;
use diesel::prelude::*;
use tokio::task;
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
use super::error::Error;
use super::schema::api_key;
use super::{error, get_db_conn};
use super::{error, get_async_db_conn};
#[derive(Queryable, Insertable, PartialEq, Eq, Debug)]
#[diesel(table_name = api_key)]
@ -51,81 +51,61 @@ pub struct Filters {
pub async fn create(ak: ApiKey) -> Result<ApiKey, Error> {
ak.validate()?;
let ak = task::spawn_blocking(move || -> Result<ApiKey, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(api_key::table)
.values(&ak)
.get_result(&mut c)
.map_err(|e| error::Error::from_diesel(e, ak.id.to_string()))
})
.await??;
let mut c = get_async_db_conn().await?;
let ak: ApiKey = diesel::insert_into(api_key::table)
.values(&ak)
.get_result(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, ak.id.to_string()))?;
info!(id = %ak.id, "Api-key created");
Ok(ak)
}
pub async fn delete(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(api_key::dsl::api_key.find(&id)).execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(id = %id, "Api-key deleted");
Ok(())
}
})
.await?
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(api_key::dsl::api_key.find(&id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(id = %id, "Api-key deleted");
Ok(())
}
pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
let mut c = get_async_db_conn().await?;
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = api_key::dsl::api_key
.select(dsl::count_star())
.filter(api_key::dsl::is_admin.eq(filters.is_admin))
.into_boxed();
let mut q = api_key::dsl::api_key
.select(dsl::count_star())
.filter(api_key::dsl::is_admin.eq(filters.is_admin))
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(api_key::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(api_key::dsl::tenant_id.eq(tenant_id));
}
Ok(q.first(&mut c)?)
}
})
.await?
Ok(q.first(&mut c).await?)
}
pub async fn list(limit: i64, offset: i64, filters: &Filters) -> Result<Vec<ApiKey>, Error> {
task::spawn_blocking({
let filters = filters.clone();
let mut c = get_async_db_conn().await?;
move || -> Result<Vec<ApiKey>, Error> {
let mut c = get_db_conn()?;
let mut q = api_key::dsl::api_key
.filter(api_key::dsl::is_admin.eq(filters.is_admin))
.into_boxed();
let mut q = api_key::dsl::api_key
.filter(api_key::dsl::is_admin.eq(filters.is_admin))
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(api_key::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(api_key::dsl::tenant_id.eq(tenant_id));
}
let items = q
.order_by(api_key::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)?;
Ok(items)
}
})
.await?
let items = q
.order_by(api_key::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await?;
Ok(items)
}
#[cfg(test)]
@ -143,18 +123,12 @@ pub mod test {
}
pub async fn get(id: &Uuid) -> Result<ApiKey, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<ApiKey, Error> {
let mut c = get_db_conn()?;
api_key::dsl::api_key
.find(&id)
.first(&mut c)
.map_err(|e| error::Error::from_diesel(e, id.to_string()))
}
})
.await?
let mut c = get_async_db_conn().await?;
api_key::dsl::api_key
.find(&id)
.first(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, id.to_string()))
}
pub async fn create_api_key(is_admin: bool, is_tenant: bool) -> ApiKey {

View File

@ -4,20 +4,22 @@ use std::str::FromStr;
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::backend::Backend;
use diesel::dsl;
use diesel::pg::Pg;
use diesel::prelude::*;
use diesel::sql_types::{Jsonb, Text};
use diesel::{deserialize, serialize};
use diesel::{
backend::Backend,
deserialize, dsl,
pg::Pg,
prelude::*,
serialize,
sql_types::{Jsonb, Text},
};
use diesel_async::RunQueryDsl;
use serde::{Deserialize, Serialize};
use tokio::task;
use tracing::info;
use uuid::Uuid;
use super::error::Error;
use super::schema::{application, application_integration};
use super::{fields, get_db_conn};
use super::{fields, get_async_db_conn};
#[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)]
#[diesel(table_name = application)]
@ -289,77 +291,58 @@ impl Default for Integration {
pub async fn create(a: Application) -> Result<Application, Error> {
a.validate()?;
task::spawn_blocking({
move || -> Result<Application, Error> {
let mut c = get_db_conn()?;
let a: Application = diesel::insert_into(application::table)
.values(&a)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, a.id.to_string()))?;
info!(id = %a.id, "Application created");
let mut c = get_async_db_conn().await?;
let a: Application = diesel::insert_into(application::table)
.values(&a)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, a.id.to_string()))?;
Ok(a)
}
})
.await?
info!(id = %a.id, "Application created");
Ok(a)
}
pub async fn get(id: &Uuid) -> Result<Application, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<Application, Error> {
let mut c = get_db_conn()?;
let a = application::dsl::application
.find(&id)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(a)
}
})
.await?
let mut c = get_async_db_conn().await?;
let a = application::dsl::application
.find(&id)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(a)
}
pub async fn update(a: Application) -> Result<Application, Error> {
a.validate()?;
task::spawn_blocking({
move || -> Result<Application, Error> {
let mut c = get_db_conn()?;
let a: Application = diesel::update(application::dsl::application.find(&a.id))
.set((
application::updated_at.eq(Utc::now()),
application::name.eq(&a.name),
application::description.eq(&a.description),
application::tags.eq(&a.tags),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, a.id.to_string()))?;
let mut c = get_async_db_conn().await?;
let a: Application = diesel::update(application::dsl::application.find(&a.id))
.set((
application::updated_at.eq(Utc::now()),
application::name.eq(&a.name),
application::description.eq(&a.description),
application::tags.eq(&a.tags),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, a.id.to_string()))?;
info!(
application_id = %a.id,
"Application updated"
);
info!(
application_id = %a.id,
"Application updated"
);
Ok(a)
}
})
.await?
Ok(a)
}
pub async fn update_mqtt_cls_cert(id: &Uuid, cert: &[u8]) -> Result<Application, Error> {
let app = task::spawn_blocking({
let id = *id;
let cert = cert.to_vec();
move || -> Result<Application, Error> {
let mut c = get_db_conn()?;
let app: Application = diesel::update(application::dsl::application.find(&id))
.set(application::mqtt_tls_cert.eq(cert))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(app)
}
})
.await??;
let mut c = get_async_db_conn().await?;
let app: Application = diesel::update(application::dsl::application.find(&id))
.set(application::mqtt_tls_cert.eq(cert))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!(
application_id = %id,
@ -370,47 +353,37 @@ pub async fn update_mqtt_cls_cert(id: &Uuid, cert: &[u8]) -> Result<Application,
}
pub async fn delete(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(application::dsl::application.find(&id)).execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(application::dsl::application.find(&id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(
application_id = %id,
"Application deleted"
);
info!(
application_id = %id,
"Application deleted"
);
Ok(())
}
})
.await?
Ok(())
}
pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = application::dsl::application
.select(dsl::count_star())
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = application::dsl::application
.select(dsl::count_star())
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(application::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(application::dsl::tenant_id.eq(tenant_id));
}
if let Some(search) = &filters.search {
q = q.filter(application::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(application::dsl::name.ilike(format!("%{}%", search)));
}
Ok(q.first(&mut c)?)
}
})
.await?
Ok(q.first(&mut c).await?)
}
pub async fn list(
@ -418,156 +391,128 @@ pub async fn list(
offset: i64,
filters: &Filters,
) -> Result<Vec<ApplicationListItem>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<ApplicationListItem>, Error> {
let mut c = get_db_conn()?;
let mut q = application::dsl::application
.select((
application::id,
application::created_at,
application::updated_at,
application::name,
application::description,
))
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = application::dsl::application
.select((
application::id,
application::created_at,
application::updated_at,
application::name,
application::description,
))
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(application::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(application::dsl::tenant_id.eq(tenant_id));
}
if let Some(search) = &filters.search {
q = q.filter(application::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(application::dsl::name.ilike(format!("%{}%", search)));
}
let items = q
.order_by(application::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)?;
Ok(items)
}
})
.await?
let items = q
.order_by(application::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await?;
Ok(items)
}
pub async fn create_integration(i: Integration) -> Result<Integration, Error> {
task::spawn_blocking({
move || -> Result<Integration, Error> {
let mut c = get_db_conn()?;
let i: Integration = diesel::insert_into(application_integration::table)
.values(&i)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, i.kind.to_string()))?;
let mut c = get_async_db_conn().await?;
let i: Integration = diesel::insert_into(application_integration::table)
.values(&i)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, i.kind.to_string()))?;
info!(application_id = %i.application_id, kind = %i.kind, "Integration created");
Ok(i)
}
})
.await?
info!(application_id = %i.application_id, kind = %i.kind, "Integration created");
Ok(i)
}
pub async fn get_integration(
application_id: &Uuid,
kind: IntegrationKind,
) -> Result<Integration, Error> {
task::spawn_blocking({
let application_id = *application_id;
move || -> Result<Integration, Error> {
let mut c = get_db_conn()?;
let mut i: Integration = application_integration::dsl::application_integration
.filter(
application_integration::dsl::application_id
.eq(application_id)
.and(application_integration::dsl::kind.eq(kind)),
)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
let mut c = get_async_db_conn().await?;
let mut i: Integration = application_integration::dsl::application_integration
.filter(
application_integration::dsl::application_id
.eq(application_id)
.and(application_integration::dsl::kind.eq(kind)),
)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
// For backwards compatibiliy
if let IntegrationConfiguration::LoraCloud(conf) = &mut i.configuration {
if conf.modem_geolocation_services.forward_f_ports.is_empty() {
conf.modem_geolocation_services.forward_f_ports = vec![
conf.modem_geolocation_services.modem_port,
conf.modem_geolocation_services.gnss_port,
197,
192,
];
}
}
Ok(i)
// For backwards compatibiliy
if let IntegrationConfiguration::LoraCloud(conf) = &mut i.configuration {
if conf.modem_geolocation_services.forward_f_ports.is_empty() {
conf.modem_geolocation_services.forward_f_ports = vec![
conf.modem_geolocation_services.modem_port,
conf.modem_geolocation_services.gnss_port,
197,
192,
];
}
})
.await?
}
Ok(i)
}
pub async fn update_integration(i: Integration) -> Result<Integration, Error> {
task::spawn_blocking({
move || -> Result<Integration, Error> {
let mut c = get_db_conn()?;
let i: Integration = diesel::update(
application_integration::dsl::application_integration.filter(
application_integration::dsl::application_id
.eq(&i.application_id)
.and(application_integration::dsl::kind.eq(&i.kind)),
),
)
.set((
application_integration::updated_at.eq(Utc::now()),
application_integration::configuration.eq(&i.configuration),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, i.application_id.to_string()))?;
let mut c = get_async_db_conn().await?;
let i: Integration = diesel::update(
application_integration::dsl::application_integration.filter(
application_integration::dsl::application_id
.eq(&i.application_id)
.and(application_integration::dsl::kind.eq(&i.kind)),
),
)
.set((
application_integration::updated_at.eq(Utc::now()),
application_integration::configuration.eq(&i.configuration),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, i.application_id.to_string()))?;
info!(application_id = %i.application_id, kind = %i.kind, "Integration updated");
info!(application_id = %i.application_id, kind = %i.kind, "Integration updated");
Ok(i)
}
})
.await?
Ok(i)
}
pub async fn delete_integration(application_id: &Uuid, kind: IntegrationKind) -> Result<(), Error> {
task::spawn_blocking({
let application_id = *application_id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(
application_integration::dsl::application_integration.filter(
application_integration::dsl::application_id
.eq(&application_id)
.and(application_integration::dsl::kind.eq(&kind)),
),
)
.execute(&mut c)?;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(
application_integration::dsl::application_integration.filter(
application_integration::dsl::application_id
.eq(&application_id)
.and(application_integration::dsl::kind.eq(&kind)),
),
)
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(application_id.to_string()));
}
if ra == 0 {
return Err(Error::NotFound(application_id.to_string()));
}
info!(application_id = %application_id, kind = %kind, "Integration deleted");
Ok(())
}
})
.await?
info!(application_id = %application_id, kind = %kind, "Integration deleted");
Ok(())
}
pub async fn get_integrations_for_application(
application_id: &Uuid,
) -> Result<Vec<Integration>, Error> {
task::spawn_blocking({
let application_id = *application_id;
move || -> Result<Vec<Integration>, Error> {
let mut c = get_db_conn()?;
let items: Vec<Integration> = application_integration::dsl::application_integration
.filter(application_integration::dsl::application_id.eq(&application_id))
.order_by(application_integration::dsl::kind)
.load(&mut c)?;
Ok(items)
}
})
.await?
let mut c = get_async_db_conn().await?;
let items: Vec<Integration> = application_integration::dsl::application_integration
.filter(application_integration::dsl::application_id.eq(&application_id))
.order_by(application_integration::dsl::kind)
.load(&mut c)
.await?;
Ok(items)
}
pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>, Error> {
@ -577,12 +522,9 @@ pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>,
pub key: String,
}
task::spawn_blocking({
let application_id = *application_id;
move || -> Result<Vec<String>, Error> {
let mut c = get_db_conn()?;
let keys: Vec<Measurement> = diesel::sql_query(
r#"
let mut c = get_async_db_conn().await?;
let keys: Vec<Measurement> = diesel::sql_query(
r#"
select
distinct jsonb_object_keys(dp.measurements) as key
from
@ -594,14 +536,12 @@ pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>,
order by
key
"#,
)
.bind::<diesel::sql_types::Uuid, _>(application_id)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
Ok(keys.iter().map(|k| k.key.clone()).collect())
}
})
.await?
)
.bind::<diesel::sql_types::Uuid, _>(application_id)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
Ok(keys.iter().map(|k| k.key.clone()).collect())
}
#[cfg(test)]

View File

@ -6,14 +6,14 @@ use anyhow::{Context, Result};
use bigdecimal::BigDecimal;
use chrono::{DateTime, Duration, Utc};
use diesel::{backend::Backend, deserialize, dsl, prelude::*, serialize, sql_types::Text};
use tokio::task;
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
use lrwn::{DevAddr, EUI64};
use super::schema::{application, device, device_profile, multicast_group_device, tenant};
use super::{error::Error, fields, get_db_conn};
use super::{error::Error, fields, get_async_db_conn};
use crate::config;
#[derive(Debug, Clone, Copy, Eq, PartialEq, AsExpression, FromSqlRow)]
@ -177,11 +177,11 @@ pub struct DevicesDataRate {
}
pub async fn create(d: Device) -> Result<Device, Error> {
d.validate()?;
let d = task::spawn_blocking({
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
c.transaction::<Device, Error, _>(|c| {
let mut c = get_async_db_conn().await?;
let d: Device = c
.build_transaction()
.run::<Device, Error, _>(|c| {
Box::pin(async move {
// use for update to lock the tenant
let t: super::tenant::Tenant = tenant::dsl::tenant
.select((
@ -200,13 +200,15 @@ pub async fn create(d: Device) -> Result<Device, Error> {
.inner_join(application::table)
.filter(application::dsl::id.eq(&d.application_id))
.for_update()
.first(c)?;
.first(c)
.await?;
let dev_count: i64 = device::dsl::device
.select(dsl::count_star())
.inner_join(application::table)
.filter(application::dsl::tenant_id.eq(&t.id))
.first(c)?;
.first(c)
.await?;
if t.max_device_count != 0 && dev_count as i32 >= t.max_device_count {
return Err(Error::NotAllowed(
@ -217,100 +219,77 @@ pub async fn create(d: Device) -> Result<Device, Error> {
diesel::insert_into(device::table)
.values(&d)
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, d.dev_eui.to_string()))
})
}
})
.await??;
})
.await?;
info!(dev_eui = %d.dev_eui, "Device created");
Ok(d)
}
pub async fn get(dev_eui: &EUI64) -> Result<Device, Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
let d = device::dsl::device
.find(&dev_eui)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(d)
}
})
.await?
let mut c = get_async_db_conn().await?;
let d = device::dsl::device
.find(&dev_eui)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(d)
}
pub async fn update(d: Device) -> Result<Device, Error> {
d.validate()?;
let d = task::spawn_blocking({
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
diesel::update(device::dsl::device.find(&d.dev_eui))
.set((
device::updated_at.eq(Utc::now()),
device::application_id.eq(&d.application_id),
device::device_profile_id.eq(&d.device_profile_id),
device::name.eq(&d.name),
device::description.eq(&d.description),
device::skip_fcnt_check.eq(&d.skip_fcnt_check),
device::is_disabled.eq(&d.is_disabled),
device::tags.eq(&d.tags),
device::variables.eq(&d.variables),
device::join_eui.eq(&d.join_eui),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, d.dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let d: Device = diesel::update(device::dsl::device.find(&d.dev_eui))
.set((
device::updated_at.eq(Utc::now()),
device::application_id.eq(&d.application_id),
device::device_profile_id.eq(&d.device_profile_id),
device::name.eq(&d.name),
device::description.eq(&d.description),
device::skip_fcnt_check.eq(&d.skip_fcnt_check),
device::is_disabled.eq(&d.is_disabled),
device::tags.eq(&d.tags),
device::variables.eq(&d.variables),
device::join_eui.eq(&d.join_eui),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, d.dev_eui.to_string()))?;
info!(dev_eui = %d.dev_eui, "Device updated");
Ok(d)
}
pub async fn set_enabled_class(dev_eui: &EUI64, mode: DeviceClass) -> Result<Device, Error> {
let d = task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
diesel::update(device::dsl::device.find(&dev_eui))
.set(device::enabled_class.eq(&mode))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
.set(device::enabled_class.eq(&mode))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(dev_eui = %dev_eui, enabled_class = %mode, "Enabled class updated");
Ok(d)
}
pub async fn set_join_eui(dev_eui: EUI64, join_eui: EUI64) -> Result<Device, Error> {
let d = task::spawn_blocking({
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
diesel::update(device::dsl::device.find(&dev_eui))
.set(device::join_eui.eq(&join_eui))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
.set(device::join_eui.eq(&join_eui))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(dev_eui = %dev_eui, join_eui = %join_eui, "Updated JoinEUI");
Ok(d)
}
pub async fn set_dev_addr(dev_eui: EUI64, dev_addr: DevAddr) -> Result<Device, Error> {
let d = task::spawn_blocking({
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
diesel::update(device::dsl::device.find(&dev_eui))
.set(device::dev_addr.eq(&dev_addr))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
.set(device::dev_addr.eq(&dev_addr))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(dev_eui = %dev_eui, dev_addr = %dev_addr, "Updated DevAddr");
Ok(d)
}
@ -323,34 +302,24 @@ pub async fn set_scheduler_run_after(
dev_eui: &EUI64,
new_ts: Option<DateTime<Utc>>,
) -> Result<Device, Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
diesel::update(device::dsl::device.find(&dev_eui))
.set(device::scheduler_run_after.eq(&new_ts))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await?
let mut c = get_async_db_conn().await?;
diesel::update(device::dsl::device.find(&dev_eui))
.set(device::scheduler_run_after.eq(&new_ts))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
pub async fn set_last_seen_dr(dev_eui: &EUI64, dr: u8) -> Result<Device, Error> {
let d = task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
diesel::update(device::dsl::device.find(&dev_eui))
.set((
device::last_seen_at.eq(Utc::now()),
device::dr.eq(dr as i16),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
.set((
device::last_seen_at.eq(Utc::now()),
device::dr.eq(dr as i16),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(dev_eui = %dev_eui, dr = dr, "Data-rate updated");
Ok(d)
}
@ -361,70 +330,53 @@ pub async fn set_status(
external_power_source: bool,
battery_level: Option<BigDecimal>,
) -> Result<Device, Error> {
let d = task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<Device, Error> {
let mut c = get_db_conn()?;
diesel::update(device::dsl::device.find(&dev_eui))
.set((
device::margin.eq(Some(margin)),
device::external_power_source.eq(external_power_source),
device::battery_level.eq(battery_level),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let d: Device = diesel::update(device::dsl::device.find(&dev_eui))
.set((
device::margin.eq(Some(margin)),
device::external_power_source.eq(external_power_source),
device::battery_level.eq(battery_level),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(dev_eui = %dev_eui, "Device status updated");
Ok(d)
}
pub async fn delete(dev_eui: &EUI64) -> Result<(), Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(device::dsl::device.find(&dev_eui)).execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(dev_eui.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(device::dsl::device.find(&dev_eui))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(dev_eui.to_string()));
}
info!(dev_eui = %dev_eui, "Device deleted");
Ok(())
}
pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = device::dsl::device
.select(dsl::count_star())
.distinct()
.left_join(multicast_group_device::table)
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = device::dsl::device
.select(dsl::count_star())
.distinct()
.left_join(multicast_group_device::table)
.into_boxed();
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
if let Some(search) = &filters.search {
q = q.filter(device::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(device::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q
.filter(multicast_group_device::dsl::multicast_group_id.eq(multicast_group_id));
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q.filter(multicast_group_device::dsl::multicast_group_id.eq(multicast_group_id));
}
Ok(q.first(&mut c)?)
}
})
.await?
Ok(q.first(&mut c).await?)
}
pub async fn list(
@ -432,133 +384,118 @@ pub async fn list(
offset: i64,
filters: &Filters,
) -> Result<Vec<DeviceListItem>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<DeviceListItem>, Error> {
let mut c = get_db_conn()?;
let mut q = device::dsl::device
.inner_join(device_profile::table)
.left_join(multicast_group_device::table)
.select((
device::dev_eui,
device::name,
device::description,
device_profile::id,
device_profile::name,
device::created_at,
device::updated_at,
device::last_seen_at,
device::margin,
device::external_power_source,
device::battery_level,
))
.distinct()
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = device::dsl::device
.inner_join(device_profile::table)
.left_join(multicast_group_device::table)
.select((
device::dev_eui,
device::name,
device::description,
device_profile::id,
device_profile::name,
device::created_at,
device::updated_at,
device::last_seen_at,
device::margin,
device::external_power_source,
device::battery_level,
))
.distinct()
.into_boxed();
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
if let Some(search) = &filters.search {
q = q.filter(device::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(device::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q
.filter(multicast_group_device::dsl::multicast_group_id.eq(multicast_group_id));
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q.filter(multicast_group_device::dsl::multicast_group_id.eq(multicast_group_id));
}
q.order_by(device::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
q.order_by(device::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn get_active_inactive(tenant_id: &Option<Uuid>) -> Result<DevicesActiveInactive, Error> {
task::spawn_blocking({
let tenant_id = *tenant_id;
move || -> Result<DevicesActiveInactive, Error> {
let mut c = get_db_conn()?;
diesel::sql_query(r#"
with device_active_inactive as (
select
make_interval(secs => dp.uplink_interval) * 1.5 as uplink_interval,
d.last_seen_at as last_seen_at
from
device d
inner join device_profile dp
on d.device_profile_id = dp.id
where
$1 is null or dp.tenant_id = $1
)
select
coalesce(sum(case when last_seen_at is null then 1 end), 0) as never_seen_count,
coalesce(sum(case when (now() - uplink_interval) > last_seen_at then 1 end), 0) as inactive_count,
coalesce(sum(case when (now() - uplink_interval) <= last_seen_at then 1 end), 0) as active_count
from
device_active_inactive
"#)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Uuid>, _>(tenant_id)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
let mut c = get_async_db_conn().await?;
diesel::sql_query(r#"
with device_active_inactive as (
select
make_interval(secs => dp.uplink_interval) * 1.5 as uplink_interval,
d.last_seen_at as last_seen_at
from
device d
inner join device_profile dp
on d.device_profile_id = dp.id
where
$1 is null or dp.tenant_id = $1
)
select
coalesce(sum(case when last_seen_at is null then 1 end), 0) as never_seen_count,
coalesce(sum(case when (now() - uplink_interval) > last_seen_at then 1 end), 0) as inactive_count,
coalesce(sum(case when (now() - uplink_interval) <= last_seen_at then 1 end), 0) as active_count
from
device_active_inactive
"#)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Uuid>, _>(tenant_id)
.get_result(&mut c).await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn get_data_rates(tenant_id: &Option<Uuid>) -> Result<Vec<DevicesDataRate>, Error> {
task::spawn_blocking({
let tenant_id = *tenant_id;
move || -> Result<Vec<DevicesDataRate>, Error> {
let mut c = get_db_conn()?;
let mut q = device::dsl::device
.inner_join(device_profile::table)
//.select((device::dr, dsl::count_star()))
.select((
device::dr,
diesel::dsl::sql::<diesel::sql_types::BigInt>("count(1)"),
))
.group_by(device::dr)
.filter(device::dsl::dr.is_not_null())
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = device::dsl::device
.inner_join(device_profile::table)
//.select((device::dr, dsl::count_star()))
.select((
device::dr,
diesel::dsl::sql::<diesel::sql_types::BigInt>("count(1)"),
))
.group_by(device::dr)
.filter(device::dsl::dr.is_not_null())
.into_boxed();
if let Some(id) = &tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(id));
}
if let Some(id) = &tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(id));
}
q.load(&mut c).map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
q.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn get_with_class_b_c_queue_items(limit: usize) -> Result<Vec<Device>> {
task::spawn_blocking(move || -> Result<Vec<Device>> {
let mut c = get_db_conn()?;
c.transaction::<Vec<Device>, Error, _>(|c| {
let conf = config::get();
let mut c = get_async_db_conn().await?;
c.build_transaction()
.run::<Vec<Device>, Error, _>(|c| {
Box::pin(async {
let conf = config::get();
// This query will:
// * Select the devices for which a Class-B or Class-C downlink can be scheduled.
// * Lock the device records for update with skip locked such that other
// ChirpStack instances are able to do the same for the remaining devices.
// * Update the scheduler_run_after for these devices to now() + 2 * scheduler
// interval to avoid concurrency issues (other ChirpStack instance scheduling
// the same queue items).
//
// This way, we do not have to keep the device records locked until the scheduler
// finishes its batch as the same set of devices will not be returned until after
// the updated scheduler_run_after. Only if the scheduler takes more time than 2x the
// interval (the scheduler is still working on processing the batch after 2 x interval)
// this might cause issues.
// The alternative would be to keep the transaction open for a long time + keep
// the device records locked during this time which could case issues as well.
diesel::sql_query(
r#"
// This query will:
// * Select the devices for which a Class-B or Class-C downlink can be scheduled.
// * Lock the device records for update with skip locked such that other
// ChirpStack instances are able to do the same for the remaining devices.
// * Update the scheduler_run_after for these devices to now() + 2 * scheduler
// interval to avoid concurrency issues (other ChirpStack instance scheduling
// the same queue items).
//
// This way, we do not have to keep the device records locked until the scheduler
// finishes its batch as the same set of devices will not be returned until after
// the updated scheduler_run_after. Only if the scheduler takes more time than 2x the
// interval (the scheduler is still working on processing the batch after 2 x interval)
// this might cause issues.
// The alternative would be to keep the transaction open for a long time + keep
// the device records locked during this time which could case issues as well.
diesel::sql_query(
r#"
update
device
set
@ -590,18 +527,19 @@ pub async fn get_with_class_b_c_queue_items(limit: usize) -> Result<Vec<Device>>
)
returning *
"#,
)
.bind::<diesel::sql_types::Integer, _>(limit as i32)
.bind::<diesel::sql_types::Timestamptz, _>(Utc::now())
.bind::<diesel::sql_types::Timestamptz, _>(
Utc::now() + Duration::from_std(2 * conf.network.scheduler.interval).unwrap(),
)
.load(c)
.map_err(|e| Error::from_diesel(e, "".into()))
)
.bind::<diesel::sql_types::Integer, _>(limit as i32)
.bind::<diesel::sql_types::Timestamptz, _>(Utc::now())
.bind::<diesel::sql_types::Timestamptz, _>(
Utc::now() + Duration::from_std(2 * conf.network.scheduler.interval).unwrap(),
)
.load(c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
})
})
.await
.context("Get with Class B/C queue-items transaction")
})
.await?
}
#[cfg(test)]

View File

@ -1,13 +1,13 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use tokio::task;
use diesel_async::RunQueryDsl;
use tracing::info;
use lrwn::{AES128Key, EUI64};
use super::error::Error;
use super::get_db_conn;
use super::get_async_db_conn;
use super::schema::device_keys;
#[derive(Queryable, Insertable, AsChangeset, PartialEq, Eq, Debug, Clone)]
@ -45,16 +45,12 @@ impl Default for DeviceKeys {
}
pub async fn create(dk: DeviceKeys) -> Result<DeviceKeys, Error> {
let dk = task::spawn_blocking({
move || -> Result<DeviceKeys, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(device_keys::table)
.values(&dk)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dk.dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dk: DeviceKeys = diesel::insert_into(device_keys::table)
.values(&dk)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dk.dev_eui.to_string()))?;
info!(
dev_eui = %dk.dev_eui,
"Device-keys created"
@ -63,31 +59,22 @@ pub async fn create(dk: DeviceKeys) -> Result<DeviceKeys, Error> {
}
pub async fn get(dev_eui: &EUI64) -> Result<DeviceKeys, Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<DeviceKeys, Error> {
let mut c = get_db_conn()?;
let dk = device_keys::dsl::device_keys
.find(&dev_eui)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(dk)
}
})
.await?
let mut c = get_async_db_conn().await?;
let dk = device_keys::dsl::device_keys
.find(&dev_eui)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(dk)
}
pub async fn update(dk: DeviceKeys) -> Result<DeviceKeys, Error> {
let dk = task::spawn_blocking({
move || -> Result<DeviceKeys, Error> {
let mut c = get_db_conn()?;
diesel::update(device_keys::dsl::device_keys.find(&dk.dev_eui))
.set(&dk)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dk.dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dk: DeviceKeys = diesel::update(device_keys::dsl::device_keys.find(&dk.dev_eui))
.set(&dk)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dk.dev_eui.to_string()))?;
info!(
dev_eui = %dk.dev_eui,
"Device-keys updated"
@ -96,19 +83,13 @@ pub async fn update(dk: DeviceKeys) -> Result<DeviceKeys, Error> {
}
pub async fn delete(dev_eui: &EUI64) -> Result<(), Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra =
diesel::delete(device_keys::dsl::device_keys.find(&dev_eui)).execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(dev_eui.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(device_keys::dsl::device_keys.find(&dev_eui))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(dev_eui.to_string()));
}
info!(
dev_eui = %dev_eui,
"Device-keys deleted"
@ -117,18 +98,12 @@ pub async fn delete(dev_eui: &EUI64) -> Result<(), Error> {
}
pub async fn set_dev_nonces(dev_eui: &EUI64, nonces: &[i32]) -> Result<DeviceKeys, Error> {
let dk = task::spawn_blocking({
let dev_eui = *dev_eui;
let nonces = nonces.to_vec();
move || -> Result<DeviceKeys, Error> {
let mut c = get_db_conn()?;
diesel::update(device_keys::dsl::device_keys.find(&dev_eui))
.set(device_keys::dev_nonces.eq(&nonces))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dk: DeviceKeys = diesel::update(device_keys::dsl::device_keys.find(dev_eui))
.set(device_keys::dev_nonces.eq(nonces))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(
dev_eui = %dev_eui,
"Dev-nonces updated"
@ -140,15 +115,16 @@ pub async fn validate_incr_join_and_store_dev_nonce(
dev_eui: &EUI64,
dev_nonce: i32,
) -> Result<DeviceKeys, Error> {
let dk = task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<DeviceKeys, Error> {
let mut c = get_db_conn()?;
c.transaction::<DeviceKeys, Error, _>(|c| {
let mut c = get_async_db_conn().await?;
let dk: DeviceKeys = c
.build_transaction()
.run::<DeviceKeys, Error, _>(|c| {
Box::pin(async move {
let mut dk: DeviceKeys = device_keys::dsl::device_keys
.find(&dev_eui)
.for_update()
.first(c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
if dk.dev_nonces.contains(&(Some(dev_nonce))) {
@ -165,11 +141,11 @@ pub async fn validate_incr_join_and_store_dev_nonce(
device_keys::join_nonce.eq(&dk.join_nonce),
))
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
})
}
})
.await??;
})
.await?;
info!(dev_eui = %dev_eui, dev_nonce = dev_nonce, "Device-nonce validated, join-nonce incremented and stored");
Ok(dk)
@ -182,20 +158,16 @@ pub mod test {
use crate::test;
pub async fn reset_nonces(dev_eui: &EUI64) -> Result<DeviceKeys, Error> {
let dk = task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<DeviceKeys, Error> {
let mut c = get_db_conn()?;
diesel::update(device_keys::dsl::device_keys.find(&dev_eui))
.set((
device_keys::dev_nonces.eq::<Vec<i32>>(Vec::new()),
device_keys::join_nonce.eq(0),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dk: DeviceKeys = diesel::update(device_keys::dsl::device_keys.find(&dev_eui))
.set((
device_keys::dev_nonces.eq::<Vec<i32>>(Vec::new()),
device_keys::join_nonce.eq(0),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(
dev_eui = %dev_eui,
"Nonces reset"

View File

@ -2,9 +2,8 @@ use std::collections::HashMap;
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::dsl;
use diesel::prelude::*;
use tokio::task;
use diesel::{dsl, prelude::*};
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
@ -12,7 +11,7 @@ use lrwn::region::{CommonName, MacVersion, Revision};
use super::error::Error;
use super::schema::device_profile;
use super::{error, fields, get_db_conn};
use super::{error, fields, get_async_db_conn};
use crate::api::helpers::ToProto;
use crate::codec::Codec;
use chirpstack_api::internal;
@ -199,170 +198,136 @@ pub struct Filters {
pub async fn create(dp: DeviceProfile) -> Result<DeviceProfile, Error> {
dp.validate()?;
let dp = task::spawn_blocking({
move || -> Result<DeviceProfile, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(device_profile::table)
.values(&dp)
.get_result(&mut c)
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dp: DeviceProfile = diesel::insert_into(device_profile::table)
.values(&dp)
.get_result(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))?;
info!(id = %dp.id, "Device-profile created");
Ok(dp)
}
pub async fn get(id: &Uuid) -> Result<DeviceProfile, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<DeviceProfile, Error> {
let mut c = get_db_conn()?;
let dp = device_profile::dsl::device_profile
.find(&id)
.first(&mut c)
.map_err(|e| error::Error::from_diesel(e, id.to_string()))?;
Ok(dp)
}
})
.await?
let mut c = get_async_db_conn().await?;
let dp = device_profile::dsl::device_profile
.find(&id)
.first(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, id.to_string()))?;
Ok(dp)
}
pub async fn update(dp: DeviceProfile) -> Result<DeviceProfile, Error> {
dp.validate()?;
let dp = task::spawn_blocking({
move || -> Result<DeviceProfile, Error> {
let mut c = get_db_conn()?;
let mut c = get_async_db_conn().await?;
let dp: DeviceProfile = diesel::update(device_profile::dsl::device_profile.find(&dp.id))
.set((
device_profile::updated_at.eq(Utc::now()),
device_profile::name.eq(&dp.name),
device_profile::description.eq(&dp.description),
device_profile::region.eq(&dp.region),
device_profile::mac_version.eq(&dp.mac_version),
device_profile::reg_params_revision.eq(&dp.reg_params_revision),
device_profile::adr_algorithm_id.eq(&dp.adr_algorithm_id),
device_profile::payload_codec_runtime.eq(&dp.payload_codec_runtime),
device_profile::payload_codec_script.eq(&dp.payload_codec_script),
device_profile::flush_queue_on_activate.eq(&dp.flush_queue_on_activate),
device_profile::uplink_interval.eq(&dp.uplink_interval),
device_profile::device_status_req_interval.eq(&dp.device_status_req_interval),
device_profile::supports_otaa.eq(&dp.supports_otaa),
device_profile::supports_class_b.eq(&dp.supports_class_b),
device_profile::supports_class_c.eq(&dp.supports_class_c),
device_profile::class_b_timeout.eq(&dp.class_b_timeout),
device_profile::class_b_ping_slot_nb_k.eq(&dp.class_b_ping_slot_nb_k),
device_profile::class_b_ping_slot_dr.eq(&dp.class_b_ping_slot_dr),
device_profile::class_b_ping_slot_freq.eq(&dp.class_b_ping_slot_freq),
device_profile::class_c_timeout.eq(&dp.class_c_timeout),
device_profile::abp_rx1_delay.eq(&dp.abp_rx1_delay),
device_profile::abp_rx1_dr_offset.eq(&dp.abp_rx1_dr_offset),
device_profile::abp_rx2_dr.eq(&dp.abp_rx2_dr),
device_profile::abp_rx2_freq.eq(&dp.abp_rx2_freq),
device_profile::tags.eq(&dp.tags),
device_profile::measurements.eq(&dp.measurements),
device_profile::auto_detect_measurements.eq(&dp.auto_detect_measurements),
device_profile::region_config_id.eq(&dp.region_config_id),
device_profile::is_relay.eq(&dp.is_relay),
device_profile::is_relay_ed.eq(&dp.is_relay_ed),
device_profile::relay_ed_relay_only.eq(&dp.relay_ed_relay_only),
device_profile::relay_enabled.eq(&dp.relay_enabled),
device_profile::relay_cad_periodicity.eq(&dp.relay_cad_periodicity),
device_profile::relay_default_channel_index.eq(&dp.relay_default_channel_index),
device_profile::relay_second_channel_freq.eq(&dp.relay_second_channel_freq),
device_profile::relay_second_channel_dr.eq(&dp.relay_second_channel_dr),
device_profile::relay_second_channel_ack_offset.eq(&dp.relay_second_channel_ack_offset),
device_profile::relay_ed_activation_mode.eq(&dp.relay_ed_activation_mode),
device_profile::relay_ed_smart_enable_level.eq(&dp.relay_ed_smart_enable_level),
device_profile::relay_ed_back_off.eq(&dp.relay_ed_back_off),
device_profile::relay_ed_uplink_limit_bucket_size
.eq(&dp.relay_ed_uplink_limit_bucket_size),
device_profile::relay_ed_uplink_limit_reload_rate
.eq(&dp.relay_ed_uplink_limit_reload_rate),
device_profile::relay_join_req_limit_reload_rate
.eq(&dp.relay_join_req_limit_reload_rate),
device_profile::relay_notify_limit_reload_rate.eq(&dp.relay_notify_limit_reload_rate),
device_profile::relay_global_uplink_limit_reload_rate
.eq(&dp.relay_global_uplink_limit_reload_rate),
device_profile::relay_overall_limit_reload_rate.eq(&dp.relay_overall_limit_reload_rate),
device_profile::relay_join_req_limit_bucket_size
.eq(&dp.relay_join_req_limit_bucket_size),
device_profile::relay_notify_limit_bucket_size.eq(&dp.relay_notify_limit_bucket_size),
device_profile::relay_global_uplink_limit_bucket_size
.eq(&dp.relay_global_uplink_limit_bucket_size),
device_profile::relay_overall_limit_bucket_size.eq(&dp.relay_overall_limit_bucket_size),
device_profile::allow_roaming.eq(&dp.allow_roaming),
))
.get_result(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))?;
diesel::update(device_profile::dsl::device_profile.find(&dp.id))
.set((
device_profile::updated_at.eq(Utc::now()),
device_profile::name.eq(&dp.name),
device_profile::description.eq(&dp.description),
device_profile::region.eq(&dp.region),
device_profile::mac_version.eq(&dp.mac_version),
device_profile::reg_params_revision.eq(&dp.reg_params_revision),
device_profile::adr_algorithm_id.eq(&dp.adr_algorithm_id),
device_profile::payload_codec_runtime.eq(&dp.payload_codec_runtime),
device_profile::payload_codec_script.eq(&dp.payload_codec_script),
device_profile::flush_queue_on_activate.eq(&dp.flush_queue_on_activate),
device_profile::uplink_interval.eq(&dp.uplink_interval),
device_profile::device_status_req_interval.eq(&dp.device_status_req_interval),
device_profile::supports_otaa.eq(&dp.supports_otaa),
device_profile::supports_class_b.eq(&dp.supports_class_b),
device_profile::supports_class_c.eq(&dp.supports_class_c),
device_profile::class_b_timeout.eq(&dp.class_b_timeout),
device_profile::class_b_ping_slot_nb_k.eq(&dp.class_b_ping_slot_nb_k),
device_profile::class_b_ping_slot_dr.eq(&dp.class_b_ping_slot_dr),
device_profile::class_b_ping_slot_freq.eq(&dp.class_b_ping_slot_freq),
device_profile::class_c_timeout.eq(&dp.class_c_timeout),
device_profile::abp_rx1_delay.eq(&dp.abp_rx1_delay),
device_profile::abp_rx1_dr_offset.eq(&dp.abp_rx1_dr_offset),
device_profile::abp_rx2_dr.eq(&dp.abp_rx2_dr),
device_profile::abp_rx2_freq.eq(&dp.abp_rx2_freq),
device_profile::tags.eq(&dp.tags),
device_profile::measurements.eq(&dp.measurements),
device_profile::auto_detect_measurements.eq(&dp.auto_detect_measurements),
device_profile::region_config_id.eq(&dp.region_config_id),
device_profile::is_relay.eq(&dp.is_relay),
device_profile::is_relay_ed.eq(&dp.is_relay_ed),
device_profile::relay_ed_relay_only.eq(&dp.relay_ed_relay_only),
device_profile::relay_enabled.eq(&dp.relay_enabled),
device_profile::relay_cad_periodicity.eq(&dp.relay_cad_periodicity),
device_profile::relay_default_channel_index.eq(&dp.relay_default_channel_index),
device_profile::relay_second_channel_freq.eq(&dp.relay_second_channel_freq),
device_profile::relay_second_channel_dr.eq(&dp.relay_second_channel_dr),
device_profile::relay_second_channel_ack_offset
.eq(&dp.relay_second_channel_ack_offset),
device_profile::relay_ed_activation_mode.eq(&dp.relay_ed_activation_mode),
device_profile::relay_ed_smart_enable_level.eq(&dp.relay_ed_smart_enable_level),
device_profile::relay_ed_back_off.eq(&dp.relay_ed_back_off),
device_profile::relay_ed_uplink_limit_bucket_size
.eq(&dp.relay_ed_uplink_limit_bucket_size),
device_profile::relay_ed_uplink_limit_reload_rate
.eq(&dp.relay_ed_uplink_limit_reload_rate),
device_profile::relay_join_req_limit_reload_rate
.eq(&dp.relay_join_req_limit_reload_rate),
device_profile::relay_notify_limit_reload_rate
.eq(&dp.relay_notify_limit_reload_rate),
device_profile::relay_global_uplink_limit_reload_rate
.eq(&dp.relay_global_uplink_limit_reload_rate),
device_profile::relay_overall_limit_reload_rate
.eq(&dp.relay_overall_limit_reload_rate),
device_profile::relay_join_req_limit_bucket_size
.eq(&dp.relay_join_req_limit_bucket_size),
device_profile::relay_notify_limit_bucket_size
.eq(&dp.relay_notify_limit_bucket_size),
device_profile::relay_global_uplink_limit_bucket_size
.eq(&dp.relay_global_uplink_limit_bucket_size),
device_profile::relay_overall_limit_bucket_size
.eq(&dp.relay_overall_limit_bucket_size),
device_profile::allow_roaming.eq(&dp.allow_roaming),
))
.get_result(&mut c)
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))
}
})
.await??;
info!(id = %dp.id, "Device-profile updated");
Ok(dp)
}
pub async fn set_measurements(id: Uuid, m: &fields::Measurements) -> Result<DeviceProfile, Error> {
let dp = task::spawn_blocking({
let m = m.clone();
move || -> Result<DeviceProfile, Error> {
let mut c = get_db_conn()?;
diesel::update(device_profile::dsl::device_profile.find(&id))
.set(device_profile::measurements.eq(m))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dp: DeviceProfile = diesel::update(device_profile::dsl::device_profile.find(&id))
.set(device_profile::measurements.eq(m))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!(id = %id, "Device-profile measurements updated");
Ok(dp)
}
pub async fn delete(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra =
diesel::delete(device_profile::dsl::device_profile.find(&id)).execute(&mut c)?;
if ra == 0 {
return Err(error::Error::NotFound(id.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(device_profile::dsl::device_profile.find(&id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(error::Error::NotFound(id.to_string()));
}
info!(id = %id, "Device-profile deleted");
Ok(())
}
pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = device_profile::dsl::device_profile
.select(dsl::count_star())
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = device_profile::dsl::device_profile
.select(dsl::count_star())
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(tenant_id));
}
if let Some(search) = &filters.search {
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search)));
}
Ok(q.first(&mut c)?)
}
})
.await?
Ok(q.first(&mut c).await?)
}
pub async fn list(
@ -370,42 +335,37 @@ pub async fn list(
offset: i64,
filters: &Filters,
) -> Result<Vec<DeviceProfileListItem>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<DeviceProfileListItem>, Error> {
let mut c = get_db_conn()?;
let mut q = device_profile::dsl::device_profile
.select((
device_profile::id,
device_profile::created_at,
device_profile::updated_at,
device_profile::name,
device_profile::region,
device_profile::mac_version,
device_profile::reg_params_revision,
device_profile::supports_otaa,
device_profile::supports_class_b,
device_profile::supports_class_c,
))
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = device_profile::dsl::device_profile
.select((
device_profile::id,
device_profile::created_at,
device_profile::updated_at,
device_profile::name,
device_profile::region,
device_profile::mac_version,
device_profile::reg_params_revision,
device_profile::supports_otaa,
device_profile::supports_class_b,
device_profile::supports_class_c,
))
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(tenant_id));
}
if let Some(search) = &filters.search {
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search)));
}
let items = q
.order_by(device_profile::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)?;
Ok(items)
}
})
.await?
let items = q
.order_by(device_profile::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await?;
Ok(items)
}
#[cfg(test)]

View File

@ -2,17 +2,16 @@ use std::collections::HashMap;
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::dsl;
use diesel::prelude::*;
use diesel::{dsl, prelude::*};
use diesel_async::RunQueryDsl;
use regex::Regex;
use tokio::task;
use tracing::info;
use lrwn::region::{CommonName, MacVersion, Revision};
use super::error::Error;
use super::schema::device_profile_template;
use super::{error, fields, get_db_conn};
use super::{error, fields, get_async_db_conn};
use crate::codec::Codec;
#[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq)]
@ -134,197 +133,164 @@ pub struct DeviceProfileTemplateListItem {
pub async fn create(dp: DeviceProfileTemplate) -> Result<DeviceProfileTemplate, Error> {
dp.validate()?;
let dp = task::spawn_blocking({
move || -> Result<DeviceProfileTemplate, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(device_profile_template::table)
.values(&dp)
.get_result(&mut c)
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dp: DeviceProfileTemplate = diesel::insert_into(device_profile_template::table)
.values(&dp)
.get_result(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))?;
info!(id = %dp.id, "Device-profile template created");
Ok(dp)
}
pub async fn upsert(dp: DeviceProfileTemplate) -> Result<DeviceProfileTemplate, Error> {
dp.validate()?;
let dp = task::spawn_blocking({
move || -> Result<DeviceProfileTemplate, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(device_profile_template::table)
.values(&dp)
.on_conflict(device_profile_template::id)
.do_update()
.set((
device_profile_template::updated_at.eq(Utc::now()),
device_profile_template::name.eq(&dp.name),
device_profile_template::description.eq(&dp.description),
device_profile_template::vendor.eq(&dp.vendor),
device_profile_template::firmware.eq(&dp.firmware),
device_profile_template::region.eq(&dp.region),
device_profile_template::mac_version.eq(&dp.mac_version),
device_profile_template::reg_params_revision.eq(&dp.reg_params_revision),
device_profile_template::adr_algorithm_id.eq(&dp.adr_algorithm_id),
device_profile_template::payload_codec_runtime.eq(&dp.payload_codec_runtime),
device_profile_template::payload_codec_script.eq(&dp.payload_codec_script),
device_profile_template::uplink_interval.eq(&dp.uplink_interval),
device_profile_template::device_status_req_interval
.eq(&dp.device_status_req_interval),
device_profile_template::flush_queue_on_activate
.eq(&dp.flush_queue_on_activate),
device_profile_template::supports_otaa.eq(&dp.supports_otaa),
device_profile_template::supports_class_b.eq(&dp.supports_class_b),
device_profile_template::supports_class_c.eq(&dp.supports_class_c),
device_profile_template::class_b_timeout.eq(&dp.class_b_timeout),
device_profile_template::class_b_ping_slot_nb_k.eq(&dp.class_b_ping_slot_nb_k),
device_profile_template::class_b_ping_slot_dr.eq(&dp.class_b_ping_slot_dr),
device_profile_template::class_b_ping_slot_freq.eq(&dp.class_b_ping_slot_freq),
device_profile_template::class_c_timeout.eq(&dp.class_c_timeout),
device_profile_template::abp_rx1_delay.eq(&dp.abp_rx1_delay),
device_profile_template::abp_rx1_dr_offset.eq(&dp.abp_rx1_dr_offset),
device_profile_template::abp_rx2_dr.eq(&dp.abp_rx2_dr),
device_profile_template::abp_rx2_freq.eq(&dp.abp_rx2_freq),
device_profile_template::tags.eq(&dp.tags),
device_profile_template::measurements.eq(&dp.measurements),
device_profile_template::auto_detect_measurements
.eq(&dp.auto_detect_measurements),
))
.get_result(&mut c)
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let dp: DeviceProfileTemplate = diesel::insert_into(device_profile_template::table)
.values(&dp)
.on_conflict(device_profile_template::id)
.do_update()
.set((
device_profile_template::updated_at.eq(Utc::now()),
device_profile_template::name.eq(&dp.name),
device_profile_template::description.eq(&dp.description),
device_profile_template::vendor.eq(&dp.vendor),
device_profile_template::firmware.eq(&dp.firmware),
device_profile_template::region.eq(&dp.region),
device_profile_template::mac_version.eq(&dp.mac_version),
device_profile_template::reg_params_revision.eq(&dp.reg_params_revision),
device_profile_template::adr_algorithm_id.eq(&dp.adr_algorithm_id),
device_profile_template::payload_codec_runtime.eq(&dp.payload_codec_runtime),
device_profile_template::payload_codec_script.eq(&dp.payload_codec_script),
device_profile_template::uplink_interval.eq(&dp.uplink_interval),
device_profile_template::device_status_req_interval.eq(&dp.device_status_req_interval),
device_profile_template::flush_queue_on_activate.eq(&dp.flush_queue_on_activate),
device_profile_template::supports_otaa.eq(&dp.supports_otaa),
device_profile_template::supports_class_b.eq(&dp.supports_class_b),
device_profile_template::supports_class_c.eq(&dp.supports_class_c),
device_profile_template::class_b_timeout.eq(&dp.class_b_timeout),
device_profile_template::class_b_ping_slot_nb_k.eq(&dp.class_b_ping_slot_nb_k),
device_profile_template::class_b_ping_slot_dr.eq(&dp.class_b_ping_slot_dr),
device_profile_template::class_b_ping_slot_freq.eq(&dp.class_b_ping_slot_freq),
device_profile_template::class_c_timeout.eq(&dp.class_c_timeout),
device_profile_template::abp_rx1_delay.eq(&dp.abp_rx1_delay),
device_profile_template::abp_rx1_dr_offset.eq(&dp.abp_rx1_dr_offset),
device_profile_template::abp_rx2_dr.eq(&dp.abp_rx2_dr),
device_profile_template::abp_rx2_freq.eq(&dp.abp_rx2_freq),
device_profile_template::tags.eq(&dp.tags),
device_profile_template::measurements.eq(&dp.measurements),
device_profile_template::auto_detect_measurements.eq(&dp.auto_detect_measurements),
))
.get_result(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, dp.id.to_string()))?;
info!(id = %dp.id, "Device-profile template upserted");
Ok(dp)
}
pub async fn get(id: &str) -> Result<DeviceProfileTemplate, Error> {
task::spawn_blocking({
let id = id.to_string();
move || -> Result<DeviceProfileTemplate, Error> {
let mut c = get_db_conn()?;
let dp = device_profile_template::dsl::device_profile_template
.find(&id)
.first(&mut c)
.map_err(|e| error::Error::from_diesel(e, id.clone()))?;
Ok(dp)
}
})
.await?
let id = id.to_string();
let mut c = get_async_db_conn().await?;
let dp = device_profile_template::dsl::device_profile_template
.find(&id)
.first(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, id.clone()))?;
Ok(dp)
}
pub async fn update(dp: DeviceProfileTemplate) -> Result<DeviceProfileTemplate, Error> {
dp.validate()?;
let dp = task::spawn_blocking({
move || -> Result<DeviceProfileTemplate, Error> {
let mut c = get_db_conn()?;
let mut c = get_async_db_conn().await?;
diesel::update(device_profile_template::dsl::device_profile_template.find(&dp.id))
.set((
device_profile_template::updated_at.eq(Utc::now()),
device_profile_template::name.eq(&dp.name),
device_profile_template::description.eq(&dp.description),
device_profile_template::vendor.eq(&dp.vendor),
device_profile_template::firmware.eq(&dp.firmware),
device_profile_template::region.eq(&dp.region),
device_profile_template::mac_version.eq(&dp.mac_version),
device_profile_template::reg_params_revision.eq(&dp.reg_params_revision),
device_profile_template::adr_algorithm_id.eq(&dp.adr_algorithm_id),
device_profile_template::payload_codec_runtime.eq(&dp.payload_codec_runtime),
device_profile_template::payload_codec_script.eq(&dp.payload_codec_script),
device_profile_template::uplink_interval.eq(&dp.uplink_interval),
device_profile_template::device_status_req_interval
.eq(&dp.device_status_req_interval),
device_profile_template::flush_queue_on_activate
.eq(&dp.flush_queue_on_activate),
device_profile_template::supports_otaa.eq(&dp.supports_otaa),
device_profile_template::supports_class_b.eq(&dp.supports_class_b),
device_profile_template::supports_class_c.eq(&dp.supports_class_c),
device_profile_template::class_b_timeout.eq(&dp.class_b_timeout),
device_profile_template::class_b_ping_slot_nb_k.eq(&dp.class_b_ping_slot_nb_k),
device_profile_template::class_b_ping_slot_dr.eq(&dp.class_b_ping_slot_dr),
device_profile_template::class_b_ping_slot_freq.eq(&dp.class_b_ping_slot_freq),
device_profile_template::class_c_timeout.eq(&dp.class_c_timeout),
device_profile_template::abp_rx1_delay.eq(&dp.abp_rx1_delay),
device_profile_template::abp_rx1_dr_offset.eq(&dp.abp_rx1_dr_offset),
device_profile_template::abp_rx2_dr.eq(&dp.abp_rx2_dr),
device_profile_template::abp_rx2_freq.eq(&dp.abp_rx2_freq),
device_profile_template::tags.eq(&dp.tags),
))
.get_result(&mut c)
.map_err(|e| error::Error::from_diesel(e, dp.id.clone()))
}
})
.await??;
let dp: DeviceProfileTemplate =
diesel::update(device_profile_template::dsl::device_profile_template.find(&dp.id))
.set((
device_profile_template::updated_at.eq(Utc::now()),
device_profile_template::name.eq(&dp.name),
device_profile_template::description.eq(&dp.description),
device_profile_template::vendor.eq(&dp.vendor),
device_profile_template::firmware.eq(&dp.firmware),
device_profile_template::region.eq(&dp.region),
device_profile_template::mac_version.eq(&dp.mac_version),
device_profile_template::reg_params_revision.eq(&dp.reg_params_revision),
device_profile_template::adr_algorithm_id.eq(&dp.adr_algorithm_id),
device_profile_template::payload_codec_runtime.eq(&dp.payload_codec_runtime),
device_profile_template::payload_codec_script.eq(&dp.payload_codec_script),
device_profile_template::uplink_interval.eq(&dp.uplink_interval),
device_profile_template::device_status_req_interval
.eq(&dp.device_status_req_interval),
device_profile_template::flush_queue_on_activate.eq(&dp.flush_queue_on_activate),
device_profile_template::supports_otaa.eq(&dp.supports_otaa),
device_profile_template::supports_class_b.eq(&dp.supports_class_b),
device_profile_template::supports_class_c.eq(&dp.supports_class_c),
device_profile_template::class_b_timeout.eq(&dp.class_b_timeout),
device_profile_template::class_b_ping_slot_nb_k.eq(&dp.class_b_ping_slot_nb_k),
device_profile_template::class_b_ping_slot_dr.eq(&dp.class_b_ping_slot_dr),
device_profile_template::class_b_ping_slot_freq.eq(&dp.class_b_ping_slot_freq),
device_profile_template::class_c_timeout.eq(&dp.class_c_timeout),
device_profile_template::abp_rx1_delay.eq(&dp.abp_rx1_delay),
device_profile_template::abp_rx1_dr_offset.eq(&dp.abp_rx1_dr_offset),
device_profile_template::abp_rx2_dr.eq(&dp.abp_rx2_dr),
device_profile_template::abp_rx2_freq.eq(&dp.abp_rx2_freq),
device_profile_template::tags.eq(&dp.tags),
))
.get_result(&mut c)
.await
.map_err(|e| error::Error::from_diesel(e, dp.id.clone()))?;
info!(id = %dp.id, "Device-profile template updated");
Ok(dp)
}
pub async fn delete(id: &str) -> Result<(), Error> {
task::spawn_blocking({
let id = id.to_string();
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra =
diesel::delete(device_profile_template::dsl::device_profile_template.find(&id))
.execute(&mut c)?;
if ra == 0 {
return Err(error::Error::NotFound(id));
}
Ok(())
}
})
.await??;
let id = id.to_string();
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(device_profile_template::dsl::device_profile_template.find(&id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(error::Error::NotFound(id));
}
info!(id = %id, "Device-profile template deleted");
Ok(())
}
pub async fn get_count() -> Result<i64, Error> {
task::spawn_blocking({
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
Ok(device_profile_template::dsl::device_profile_template
.select(dsl::count_star())
.first(&mut c)?)
}
})
.await?
let mut c = get_async_db_conn().await?;
Ok(device_profile_template::dsl::device_profile_template
.select(dsl::count_star())
.first(&mut c)
.await?)
}
pub async fn list(limit: i64, offset: i64) -> Result<Vec<DeviceProfileTemplateListItem>, Error> {
task::spawn_blocking({
move || -> Result<Vec<DeviceProfileTemplateListItem>, Error> {
let mut c = get_db_conn()?;
let items = device_profile_template::dsl::device_profile_template
.select((
device_profile_template::id,
device_profile_template::created_at,
device_profile_template::updated_at,
device_profile_template::name,
device_profile_template::vendor,
device_profile_template::firmware,
device_profile_template::region,
device_profile_template::mac_version,
device_profile_template::reg_params_revision,
device_profile_template::supports_otaa,
device_profile_template::supports_class_b,
device_profile_template::supports_class_c,
))
.order_by((
device_profile_template::dsl::vendor,
device_profile_template::dsl::name,
device_profile_template::dsl::firmware,
device_profile_template::dsl::region,
))
.limit(limit)
.offset(offset)
.load(&mut c)?;
Ok(items)
}
})
.await?
let mut c = get_async_db_conn().await?;
let items = device_profile_template::dsl::device_profile_template
.select((
device_profile_template::id,
device_profile_template::created_at,
device_profile_template::updated_at,
device_profile_template::name,
device_profile_template::vendor,
device_profile_template::firmware,
device_profile_template::region,
device_profile_template::mac_version,
device_profile_template::reg_params_revision,
device_profile_template::supports_otaa,
device_profile_template::supports_class_b,
device_profile_template::supports_class_c,
))
.order_by((
device_profile_template::dsl::vendor,
device_profile_template::dsl::name,
device_profile_template::dsl::firmware,
device_profile_template::dsl::region,
))
.limit(limit)
.offset(offset)
.load(&mut c)
.await?;
Ok(items)
}
#[cfg(test)]

View File

@ -1,12 +1,12 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::{dsl, prelude::*};
use tokio::task;
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
use super::error::Error;
use super::get_db_conn;
use super::get_async_db_conn;
use super::schema::device_queue_item;
use lrwn::EUI64;
@ -64,172 +64,128 @@ impl Default for DeviceQueueItem {
pub async fn enqueue_item(qi: DeviceQueueItem) -> Result<DeviceQueueItem, Error> {
qi.validate()?;
let qi = task::spawn_blocking({
move || -> Result<DeviceQueueItem, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(device_queue_item::table)
.values(&qi)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, qi.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let qi: DeviceQueueItem = diesel::insert_into(device_queue_item::table)
.values(&qi)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, qi.id.to_string()))?;
info!(id = %qi.id, dev_eui = %qi.dev_eui, "Device queue-item enqueued");
Ok(qi)
}
pub async fn get_item(id: &Uuid) -> Result<DeviceQueueItem, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<DeviceQueueItem, Error> {
let mut c = get_db_conn()?;
let qi = device_queue_item::dsl::device_queue_item
.find(&id)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(qi)
}
})
.await?
let mut c = get_async_db_conn().await?;
let qi = device_queue_item::dsl::device_queue_item
.find(id)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(qi)
}
pub async fn update_item(qi: DeviceQueueItem) -> Result<DeviceQueueItem, Error> {
let qi = task::spawn_blocking({
move || -> Result<DeviceQueueItem, Error> {
let mut c = get_db_conn()?;
diesel::update(device_queue_item::dsl::device_queue_item.find(&qi.id))
.set((
device_queue_item::is_pending.eq(&qi.is_pending),
device_queue_item::f_cnt_down.eq(&qi.f_cnt_down),
device_queue_item::timeout_after.eq(&qi.timeout_after),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, qi.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let qi: DeviceQueueItem =
diesel::update(device_queue_item::dsl::device_queue_item.find(&qi.id))
.set((
device_queue_item::is_pending.eq(&qi.is_pending),
device_queue_item::f_cnt_down.eq(&qi.f_cnt_down),
device_queue_item::timeout_after.eq(&qi.timeout_after),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, qi.id.to_string()))?;
info!(id = %qi.id, dev_eui = %qi.dev_eui, "Device queue-item updated");
Ok(qi)
}
pub async fn delete_item(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(device_queue_item::dsl::device_queue_item.find(&id))
.execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(device_queue_item::dsl::device_queue_item.find(&id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(id = %id, "Device queue-item deleted");
Ok(())
}
/// It returns the device queue-item and a bool indicating if there are more items in the queue.
pub async fn get_next_for_dev_eui(dev_eui: &EUI64) -> Result<(DeviceQueueItem, bool), Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<(DeviceQueueItem, bool), Error> {
let mut c = get_db_conn()?;
let items: Vec<DeviceQueueItem> = device_queue_item::dsl::device_queue_item
.filter(device_queue_item::dev_eui.eq(&dev_eui))
.order_by(device_queue_item::created_at)
.limit(2)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
let mut c = get_async_db_conn().await?;
let items: Vec<DeviceQueueItem> = device_queue_item::dsl::device_queue_item
.filter(device_queue_item::dev_eui.eq(&dev_eui))
.order_by(device_queue_item::created_at)
.limit(2)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
// Return NotFound on empty Vec.
if items.is_empty() {
// Return NotFound on empty Vec.
if items.is_empty() {
return Err(Error::NotFound(dev_eui.to_string()));
}
// In case the transmission is pending and hasn't timed-out yet, do not
// return it.
if items[0].is_pending {
if let Some(timeout_after) = &items[0].timeout_after {
if timeout_after > &Utc::now() {
return Err(Error::NotFound(dev_eui.to_string()));
}
// In case the transmission is pending and hasn't timed-out yet, do not
// return it.
if items[0].is_pending {
if let Some(timeout_after) = &items[0].timeout_after {
if timeout_after > &Utc::now() {
return Err(Error::NotFound(dev_eui.to_string()));
}
}
}
// Return first item and bool indicating if there are more items in the queue.
Ok((items[0].clone(), items.len() > 1))
}
})
.await?
}
// Return first item and bool indicating if there are more items in the queue.
Ok((items[0].clone(), items.len() > 1))
}
pub async fn get_for_dev_eui(dev_eui: &EUI64) -> Result<Vec<DeviceQueueItem>, Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<Vec<DeviceQueueItem>, Error> {
let mut c = get_db_conn()?;
let items = device_queue_item::dsl::device_queue_item
.filter(device_queue_item::dev_eui.eq(&dev_eui))
.order_by(device_queue_item::created_at)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(items)
}
})
.await?
let mut c = get_async_db_conn().await?;
let items = device_queue_item::dsl::device_queue_item
.filter(device_queue_item::dev_eui.eq(&dev_eui))
.order_by(device_queue_item::created_at)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(items)
}
pub async fn flush_for_dev_eui(dev_eui: &EUI64) -> Result<(), Error> {
let count = task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<usize, Error> {
let mut c = get_db_conn()?;
diesel::delete(
device_queue_item::dsl::device_queue_item
.filter(device_queue_item::dev_eui.eq(&dev_eui)),
)
.execute(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let count: usize = diesel::delete(
device_queue_item::dsl::device_queue_item.filter(device_queue_item::dev_eui.eq(&dev_eui)),
)
.execute(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
info!(dev_eui = %dev_eui, count = count, "Device queue flushed");
Ok(())
}
pub async fn get_pending_for_dev_eui(dev_eui: &EUI64) -> Result<DeviceQueueItem, Error> {
task::spawn_blocking({
let dev_eui = *dev_eui;
move || -> Result<DeviceQueueItem, Error> {
let mut c = get_db_conn()?;
let qi = device_queue_item::dsl::device_queue_item
.filter(
device_queue_item::dev_eui
.eq(&dev_eui)
.and(device_queue_item::is_pending.eq(true)),
)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(qi)
}
})
.await?
let mut c = get_async_db_conn().await?;
let qi = device_queue_item::dsl::device_queue_item
.filter(
device_queue_item::dev_eui
.eq(&dev_eui)
.and(device_queue_item::is_pending.eq(true)),
)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(qi)
}
pub async fn get_max_f_cnt_down(dev_eui: EUI64) -> Result<Option<i64>, Error> {
task::spawn_blocking({
move || -> Result<Option<i64>, Error> {
let mut c = get_db_conn()?;
Ok(device_queue_item::dsl::device_queue_item
.select(dsl::max(device_queue_item::f_cnt_down))
.filter(device_queue_item::dsl::dev_eui.eq(dev_eui))
.first(&mut c)?)
}
})
.await?
let mut c = get_async_db_conn().await?;
Ok(device_queue_item::dsl::device_queue_item
.select(dsl::max(device_queue_item::f_cnt_down))
.filter(device_queue_item::dsl::dev_eui.eq(dev_eui))
.first(&mut c)
.await?)
}
#[cfg(test)]
@ -281,7 +237,7 @@ pub mod test {
// get for dev eui
let queue = get_for_dev_eui(&d.dev_eui).await.unwrap();
assert_eq!(&qi, queue.first().unwrap());
assert_eq!(qi, queue[0]);
// next next queue item for dev eui
let resp = get_next_for_dev_eui(&d.dev_eui).await.unwrap();

View File

@ -2,16 +2,15 @@ use std::collections::HashMap;
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::dsl;
use diesel::prelude::*;
use tokio::task;
use diesel::{dsl, prelude::*};
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
use lrwn::EUI64;
use super::schema::{gateway, multicast_group_gateway, tenant};
use super::{error::Error, fields, get_db_conn};
use super::{error::Error, fields, get_async_db_conn};
#[derive(Queryable, Insertable, PartialEq, Debug)]
#[diesel(table_name = gateway)]
@ -110,15 +109,17 @@ impl Default for Gateway {
pub async fn create(gw: Gateway) -> Result<Gateway, Error> {
gw.validate()?;
let gw = task::spawn_blocking({
move || -> Result<Gateway, Error> {
let mut c = get_db_conn()?;
c.transaction::<Gateway, Error, _>(|c| {
let mut c = get_async_db_conn().await?;
let gw: Gateway = c
.build_transaction()
.run::<Gateway, Error, _>(|c| {
Box::pin(async move {
// use for_update to lock the tenant.
let t: super::tenant::Tenant = tenant::dsl::tenant
.find(&gw.tenant_id)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, gw.tenant_id.to_string()))?;
if !t.can_have_gateways {
@ -128,7 +129,8 @@ pub async fn create(gw: Gateway) -> Result<Gateway, Error> {
let gw_count: i64 = gateway::dsl::gateway
.select(dsl::count_star())
.filter(gateway::dsl::tenant_id.eq(&gw.tenant_id))
.first(c)?;
.first(c)
.await?;
if t.max_gateway_count != 0 && gw_count as i32 >= t.max_gateway_count {
return Err(Error::NotAllowed(
@ -139,11 +141,11 @@ pub async fn create(gw: Gateway) -> Result<Gateway, Error> {
diesel::insert_into(gateway::table)
.values(&gw)
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, gw.gateway_id.to_string()))
})
}
})
.await??;
})
.await?;
info!(
gateway_id = %gw.gateway_id,
"Gateway created"
@ -152,41 +154,32 @@ pub async fn create(gw: Gateway) -> Result<Gateway, Error> {
}
pub async fn get(gateway_id: &EUI64) -> Result<Gateway, Error> {
task::spawn_blocking({
let gateway_id = *gateway_id;
move || -> Result<Gateway, Error> {
let mut c = get_db_conn()?;
let gw = gateway::dsl::gateway
.find(&gateway_id)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, gateway_id.to_string()))?;
Ok(gw)
}
})
.await?
let mut c = get_async_db_conn().await?;
let gw = gateway::dsl::gateway
.find(&gateway_id)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, gateway_id.to_string()))?;
Ok(gw)
}
pub async fn update(gw: Gateway) -> Result<Gateway, Error> {
gw.validate()?;
let gw = task::spawn_blocking({
move || -> Result<Gateway, Error> {
let mut c = get_db_conn()?;
diesel::update(gateway::dsl::gateway.find(&gw.gateway_id))
.set((
gateway::updated_at.eq(Utc::now()),
gateway::name.eq(&gw.name),
gateway::description.eq(&gw.description),
gateway::latitude.eq(&gw.latitude),
gateway::longitude.eq(&gw.longitude),
gateway::altitude.eq(&gw.altitude),
gateway::stats_interval_secs.eq(&gw.stats_interval_secs),
gateway::tags.eq(&gw.tags),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, gw.gateway_id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let gw: Gateway = diesel::update(gateway::dsl::gateway.find(&gw.gateway_id))
.set((
gateway::updated_at.eq(Utc::now()),
gateway::name.eq(&gw.name),
gateway::description.eq(&gw.description),
gateway::latitude.eq(&gw.latitude),
gateway::longitude.eq(&gw.longitude),
gateway::altitude.eq(&gw.altitude),
gateway::stats_interval_secs.eq(&gw.stats_interval_secs),
gateway::tags.eq(&gw.tags),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, gw.gateway_id.to_string()))?;
info!(
gateway_id = %gw.gateway_id,
"Gateway updated"
@ -195,23 +188,16 @@ pub async fn update(gw: Gateway) -> Result<Gateway, Error> {
}
pub async fn update_state(id: &EUI64, props: &HashMap<String, String>) -> Result<Gateway, Error> {
let gw = task::spawn_blocking({
let id = *id;
let props = fields::KeyValue::new(props.clone());
move || -> Result<Gateway, Error> {
let mut c = get_db_conn()?;
let gw: Gateway = diesel::update(gateway::dsl::gateway.find(&id))
.set((
gateway::last_seen_at.eq(Some(Utc::now())),
gateway::properties.eq(props),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(gw)
}
})
.await??;
let props = fields::KeyValue::new(props.clone());
let mut c = get_async_db_conn().await?;
let gw: Gateway = diesel::update(gateway::dsl::gateway.find(&id))
.set((
gateway::last_seen_at.eq(Some(Utc::now())),
gateway::properties.eq(props),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!(
gateway_id = %id,
@ -228,26 +214,19 @@ pub async fn update_state_and_loc(
alt: f32,
props: &HashMap<String, String>,
) -> Result<Gateway, Error> {
let gw = task::spawn_blocking({
let id = *id;
let props = fields::KeyValue::new(props.clone());
move || -> Result<Gateway, Error> {
let mut c = get_db_conn()?;
let gw: Gateway = diesel::update(gateway::dsl::gateway.find(&id))
.set((
gateway::last_seen_at.eq(Some(Utc::now())),
gateway::latitude.eq(lat),
gateway::longitude.eq(lon),
gateway::altitude.eq(alt),
gateway::properties.eq(props),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(gw)
}
})
.await??;
let props = fields::KeyValue::new(props.clone());
let mut c = get_async_db_conn().await?;
let gw: Gateway = diesel::update(gateway::dsl::gateway.find(&id))
.set((
gateway::last_seen_at.eq(Some(Utc::now())),
gateway::latitude.eq(lat),
gateway::longitude.eq(lon),
gateway::altitude.eq(alt),
gateway::properties.eq(props),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!(
gateway_id = %id,
@ -258,20 +237,12 @@ pub async fn update_state_and_loc(
}
pub async fn update_tls_cert(id: &EUI64, cert: &[u8]) -> Result<Gateway, Error> {
let gw = task::spawn_blocking({
let id = *id;
let cert = cert.to_vec();
move || -> Result<Gateway, Error> {
let mut c = get_db_conn()?;
let gw: Gateway = diesel::update(gateway::dsl::gateway.find(&id))
.set(gateway::tls_certificate.eq(cert))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(gw)
}
})
.await??;
let mut c = get_async_db_conn().await?;
let gw: Gateway = diesel::update(gateway::dsl::gateway.find(&id))
.set(gateway::tls_certificate.eq(cert))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!(
gateway_id = %id,
"Gateway tls certificate updated"
@ -281,18 +252,13 @@ pub async fn update_tls_cert(id: &EUI64, cert: &[u8]) -> Result<Gateway, Error>
}
pub async fn delete(gateway_id: &EUI64) -> Result<(), Error> {
task::spawn_blocking({
let gateway_id = *gateway_id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(gateway::dsl::gateway.find(&gateway_id)).execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(gateway_id.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(gateway::dsl::gateway.find(&gateway_id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(gateway_id.to_string()));
}
info!(
gateway_id = %gateway_id,
"Gateway deleted"
@ -301,34 +267,26 @@ pub async fn delete(gateway_id: &EUI64) -> Result<(), Error> {
}
pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = gateway::dsl::gateway
.select(dsl::count_star())
.distinct()
.left_join(multicast_group_gateway::table)
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = gateway::dsl::gateway
.select(dsl::count_star())
.distinct()
.left_join(multicast_group_gateway::table)
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(gateway::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(gateway::dsl::tenant_id.eq(tenant_id));
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q.filter(
multicast_group_gateway::dsl::multicast_group_id.eq(multicast_group_id),
);
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q.filter(multicast_group_gateway::dsl::multicast_group_id.eq(multicast_group_id));
}
if let Some(search) = &filters.search {
q = q.filter(gateway::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(gateway::dsl::name.ilike(format!("%{}%", search)));
}
Ok(q.first(&mut c)?)
}
})
.await?
Ok(q.first(&mut c).await?)
}
pub async fn list(
@ -336,98 +294,80 @@ pub async fn list(
offset: i64,
filters: &Filters,
) -> Result<Vec<GatewayListItem>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<GatewayListItem>, Error> {
let mut c = get_db_conn()?;
let mut q = gateway::dsl::gateway
.left_join(multicast_group_gateway::table)
.select((
gateway::tenant_id,
gateway::gateway_id,
gateway::name,
gateway::description,
gateway::created_at,
gateway::updated_at,
gateway::last_seen_at,
gateway::latitude,
gateway::longitude,
gateway::altitude,
gateway::properties,
gateway::stats_interval_secs,
))
.distinct()
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = gateway::dsl::gateway
.left_join(multicast_group_gateway::table)
.select((
gateway::tenant_id,
gateway::gateway_id,
gateway::name,
gateway::description,
gateway::created_at,
gateway::updated_at,
gateway::last_seen_at,
gateway::latitude,
gateway::longitude,
gateway::altitude,
gateway::properties,
gateway::stats_interval_secs,
))
.distinct()
.into_boxed();
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(gateway::dsl::tenant_id.eq(tenant_id));
}
if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(gateway::dsl::tenant_id.eq(tenant_id));
}
if let Some(search) = &filters.search {
q = q.filter(gateway::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(gateway::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q.filter(
multicast_group_gateway::dsl::multicast_group_id.eq(multicast_group_id),
);
}
if let Some(multicast_group_id) = &filters.multicast_group_id {
q = q.filter(multicast_group_gateway::dsl::multicast_group_id.eq(multicast_group_id));
}
let items = q
.order_by(gateway::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)?;
Ok(items)
}
})
.await?
let items = q
.order_by(gateway::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await?;
Ok(items)
}
pub async fn get_meta(gateway_id: &EUI64) -> Result<GatewayMeta, Error> {
task::spawn_blocking({
let gateway_id = *gateway_id;
move || -> Result<GatewayMeta, Error> {
let mut c = get_db_conn()?;
let meta = gateway::dsl::gateway
.inner_join(tenant::table)
.select((
gateway::gateway_id,
gateway::tenant_id,
gateway::latitude,
gateway::longitude,
gateway::altitude,
tenant::private_gateways_up,
tenant::private_gateways_down,
))
.filter(gateway::dsl::gateway_id.eq(&gateway_id))
.first(&mut c)
.map_err(|e| Error::from_diesel(e, gateway_id.to_string()))?;
Ok(meta)
}
})
.await?
let mut c = get_async_db_conn().await?;
let meta = gateway::dsl::gateway
.inner_join(tenant::table)
.select((
gateway::gateway_id,
gateway::tenant_id,
gateway::latitude,
gateway::longitude,
gateway::altitude,
tenant::private_gateways_up,
tenant::private_gateways_down,
))
.filter(gateway::dsl::gateway_id.eq(&gateway_id))
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, gateway_id.to_string()))?;
Ok(meta)
}
pub async fn get_counts_by_state(tenant_id: &Option<Uuid>) -> Result<GatewayCountsByState, Error> {
task::spawn_blocking({
let tenant_id = *tenant_id;
move || -> Result<GatewayCountsByState, Error> {
let mut c = get_db_conn()?;
let counts: GatewayCountsByState = diesel::sql_query(r#"
select
coalesce(sum(case when last_seen_at is null then 1 end), 0) as never_seen_count,
coalesce(sum(case when (now() - make_interval(secs => stats_interval_secs * 2)) > last_seen_at then 1 end), 0) as offline_count,
coalesce(sum(case when (now() - make_interval(secs => stats_interval_secs * 2)) <= last_seen_at then 1 end), 0) as online_count
from
gateway
where
$1 is null or tenant_id = $1
"#).bind::<diesel::sql_types::Nullable<diesel::sql_types::Uuid>, _>(tenant_id).get_result(&mut c)?;
Ok(counts)
}
}).await?
let mut c = get_async_db_conn().await?;
let counts: GatewayCountsByState = diesel::sql_query(r#"
select
coalesce(sum(case when last_seen_at is null then 1 end), 0) as never_seen_count,
coalesce(sum(case when (now() - make_interval(secs => stats_interval_secs * 2)) > last_seen_at then 1 end), 0) as offline_count,
coalesce(sum(case when (now() - make_interval(secs => stats_interval_secs * 2)) <= last_seen_at then 1 end), 0) as online_count
from
gateway
where
$1 is null or tenant_id = $1
"#).bind::<diesel::sql_types::Nullable<diesel::sql_types::Uuid>, _>(tenant_id).get_result(&mut c).await?;
Ok(counts)
}
#[cfg(test)]

View File

@ -1,28 +1,24 @@
use diesel::prelude::*;
use tokio::task;
use diesel_async::RunQueryDsl;
use super::schema::{application, device, device_profile, tenant};
use super::{
application::Application, device::Device, device_profile::DeviceProfile, tenant::Tenant,
};
use super::{error::Error, get_db_conn};
use super::{error::Error, get_async_db_conn};
use lrwn::EUI64;
pub async fn get_all_device_data(
dev_eui: EUI64,
) -> Result<(Device, Application, Tenant, DeviceProfile), Error> {
task::spawn_blocking({
move || -> Result<(Device, Application, Tenant, DeviceProfile), Error> {
let mut c = get_db_conn()?;
let res = device::table
.inner_join(application::table)
.inner_join(tenant::table.on(application::dsl::tenant_id.eq(tenant::dsl::id)))
.inner_join(device_profile::table)
.filter(device::dsl::dev_eui.eq(&dev_eui))
.first::<(Device, Application, Tenant, DeviceProfile)>(&mut c)
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(res)
}
})
.await?
let mut c = get_async_db_conn().await?;
let res = device::table
.inner_join(application::table)
.inner_join(tenant::table.on(application::dsl::tenant_id.eq(tenant::dsl::id)))
.inner_join(device_profile::table)
.filter(device::dsl::dev_eui.eq(&dev_eui))
.first::<(Device, Application, Tenant, DeviceProfile)>(&mut c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
Ok(res)
}

View File

@ -1,12 +1,21 @@
use std::fs::File;
use std::io::BufReader;
use std::ops::{Deref, DerefMut};
use std::sync::RwLock;
use anyhow::Context;
use anyhow::Result;
use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use diesel::{ConnectionError, ConnectionResult};
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
use diesel_async::pooled_connection::deadpool::{Object as DeadpoolObject, Pool as DeadpoolPool};
use diesel_async::pooled_connection::{AsyncDieselConnectionManager, ManagerConfig};
use diesel_async::AsyncPgConnection;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use tracing::info;
use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use r2d2::{Pool, PooledConnection};
use tokio::task;
use tracing::{error, info};
use crate::config;
@ -34,11 +43,11 @@ pub mod search;
pub mod tenant;
pub mod user;
pub type PgPool = Pool<ConnectionManager<PgConnection>>;
pub type PgPoolConnection = PooledConnection<ConnectionManager<PgConnection>>;
pub type AsyncPgPool = DeadpoolPool<AsyncPgConnection>;
pub type AsyncPgPoolConnection = DeadpoolObject<AsyncPgConnection>;
lazy_static! {
static ref PG_POOL: RwLock<Option<PgPool>> = RwLock::new(None);
static ref ASYNC_PG_POOL: RwLock<Option<AsyncPgPool>> = RwLock::new(None);
static ref REDIS_POOL: RwLock<Option<RedisPool>> = RwLock::new(None);
static ref REDIS_PREFIX: RwLock<String> = RwLock::new("".to_string());
}
@ -170,21 +179,18 @@ pub async fn setup() -> Result<()> {
let conf = config::get();
info!("Setting up PostgreSQL connection pool");
let pg_pool = PgPool::builder()
.max_size(conf.postgresql.max_open_connections)
.min_idle(match conf.postgresql.min_idle_connections {
0 => None,
_ => Some(conf.postgresql.min_idle_connections),
})
.build(ConnectionManager::new(&conf.postgresql.dsn))
.context("Setup PostgreSQL connection pool error")?;
set_db_pool(pg_pool);
let mut pg_conn = get_db_conn()?;
let mut config = ManagerConfig::default();
config.custom_setup = Box::new(pg_establish_connection);
info!("Applying schema migrations");
pg_conn
.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
let mgr = AsyncDieselConnectionManager::<AsyncPgConnection>::new_with_config(
&conf.postgresql.dsn,
config,
);
let pool = DeadpoolPool::builder(mgr)
.max_size(conf.postgresql.max_open_connections as usize)
.build()?;
set_async_db_pool(pool);
run_db_migrations().await?;
info!("Setting up Redis client");
if conf.redis.cluster {
@ -221,18 +227,66 @@ pub async fn setup() -> Result<()> {
Ok(())
}
pub fn get_db_pool() -> Result<PgPool> {
let pool_r = PG_POOL.read().unwrap();
let pool = pool_r
// Source:
// https://github.com/weiznich/diesel_async/blob/main/examples/postgres/pooled-with-rustls/src/main.rs
fn pg_establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
let fut = async {
let root_certs =
pg_root_certs().map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
let rustls_config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_certs)
.with_no_client_auth();
let tls = tokio_postgres_rustls::MakeRustlsConnect::new(rustls_config);
let (client, conn) = tokio_postgres::connect(config, tls)
.await
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
tokio::spawn(async move {
if let Err(e) = conn.await {
error!(error = %e, "PostgreSQL connection error");
}
});
AsyncPgConnection::try_from(client).await
};
fut.boxed()
}
fn pg_root_certs() -> Result<rustls::RootCertStore> {
let conf = config::get();
let mut roots = rustls::RootCertStore::empty();
let certs = rustls_native_certs::load_native_certs()?;
let certs: Vec<_> = certs.into_iter().map(|cert| cert.0).collect();
roots.add_parsable_certificates(&certs);
if !conf.postgresql.ca_cert.is_empty() {
let f = File::open(&conf.postgresql.ca_cert).context("Open ca certificate")?;
let mut reader = BufReader::new(f);
let certs = rustls_pemfile::certs(&mut reader)?;
for cert in certs
.into_iter()
.map(rustls::Certificate)
.collect::<Vec<_>>()
{
roots.add(&cert)?;
}
}
Ok(roots)
}
pub fn get_async_db_pool() -> Result<AsyncPgPool> {
let pool_r = ASYNC_PG_POOL.read().unwrap();
let pool: AsyncPgPool = pool_r
.as_ref()
.ok_or_else(|| anyhow!("PostgreSQL connection pool is not initialized (yet)"))?
.ok_or_else(|| anyhow!("PostgreSQL connection pool is not initialized"))?
.clone();
Ok(pool)
}
pub fn get_db_conn() -> Result<PgPoolConnection> {
let pool = get_db_pool()?;
Ok(pool.get()?)
pub async fn get_async_db_conn() -> Result<AsyncPgPoolConnection> {
let pool = get_async_db_pool()?;
Ok(pool.get().await?)
}
pub fn get_redis_conn() -> Result<RedisPoolConnection> {
@ -246,11 +300,28 @@ pub fn get_redis_conn() -> Result<RedisPoolConnection> {
})
}
pub fn set_db_pool(p: PgPool) {
let mut pool_w = PG_POOL.write().unwrap();
pub fn set_async_db_pool(p: AsyncPgPool) {
let mut pool_w = ASYNC_PG_POOL.write().unwrap();
*pool_w = Some(p);
}
pub async fn run_db_migrations() -> Result<()> {
info!("Applying schema migrations");
let c = get_async_db_conn().await?;
let mut c_wrapped: AsyncConnectionWrapper<AsyncPgPoolConnection> =
AsyncConnectionWrapper::from(c);
task::spawn_blocking(move || -> Result<()> {
c_wrapped
.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
Ok(())
})
.await?
}
pub fn set_redis_pool(p: RedisPool) {
let mut pool_w = REDIS_POOL.write().unwrap();
*pool_w = Some(p);
@ -262,14 +333,22 @@ pub fn redis_key(s: String) -> String {
}
#[cfg(test)]
pub fn reset_db() -> Result<()> {
let mut conn = get_db_conn()?;
conn.revert_all_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
conn.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
pub async fn reset_db() -> Result<()> {
let c = get_async_db_conn().await?;
let mut c_wrapped: AsyncConnectionWrapper<AsyncPgPoolConnection> =
AsyncConnectionWrapper::from(c);
Ok(())
tokio::task::spawn_blocking(move || -> Result<()> {
c_wrapped
.revert_all_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
c_wrapped
.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!("{}", e))?;
Ok(())
})
.await?
}
#[cfg(test)]

View File

@ -1,8 +1,7 @@
use anyhow::{Context, Result};
use chrono::{DateTime, Duration, Utc};
use diesel::dsl;
use diesel::prelude::*;
use tokio::task;
use diesel::{dsl, prelude::*};
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
@ -14,7 +13,7 @@ use super::schema::{
application, device, gateway, multicast_group, multicast_group_device, multicast_group_gateway,
multicast_group_queue_item,
};
use super::{fields, get_db_conn};
use super::{fields, get_async_db_conn};
use crate::downlink::classb;
use crate::{config, gpstime::ToDateTime, gpstime::ToGpsTime};
@ -133,104 +132,79 @@ impl Default for MulticastGroupQueueItem {
pub async fn create(mg: MulticastGroup) -> Result<MulticastGroup, Error> {
mg.validate()?;
let mg = task::spawn_blocking({
move || -> Result<MulticastGroup, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(multicast_group::table)
.values(&mg)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, mg.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let mg: MulticastGroup = diesel::insert_into(multicast_group::table)
.values(&mg)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, mg.id.to_string()))?;
info!(id = %mg.id, "Multicast-group created");
Ok(mg)
}
pub async fn get(id: &Uuid) -> Result<MulticastGroup, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<MulticastGroup, Error> {
let mut c = get_db_conn()?;
multicast_group::dsl::multicast_group
.find(&id)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))
}
})
.await?
let mut c = get_async_db_conn().await?;
multicast_group::dsl::multicast_group
.find(&id)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))
}
pub async fn update(mg: MulticastGroup) -> Result<MulticastGroup, Error> {
mg.validate()?;
let mg = task::spawn_blocking({
move || -> Result<MulticastGroup, Error> {
let mut c = get_db_conn()?;
diesel::update(multicast_group::dsl::multicast_group.find(&mg.id))
.set((
multicast_group::updated_at.eq(Utc::now()),
multicast_group::name.eq(&mg.name),
multicast_group::region.eq(&mg.region),
multicast_group::mc_addr.eq(&mg.mc_addr),
multicast_group::mc_nwk_s_key.eq(&mg.mc_nwk_s_key),
multicast_group::mc_app_s_key.eq(&mg.mc_app_s_key),
multicast_group::f_cnt.eq(&mg.f_cnt),
multicast_group::group_type.eq(&mg.group_type),
multicast_group::dr.eq(&mg.dr),
multicast_group::frequency.eq(&mg.frequency),
multicast_group::class_b_ping_slot_period.eq(&mg.class_b_ping_slot_period),
multicast_group::class_c_scheduling_type.eq(&mg.class_c_scheduling_type),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, mg.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let mg: MulticastGroup = diesel::update(multicast_group::dsl::multicast_group.find(&mg.id))
.set((
multicast_group::updated_at.eq(Utc::now()),
multicast_group::name.eq(&mg.name),
multicast_group::region.eq(&mg.region),
multicast_group::mc_addr.eq(&mg.mc_addr),
multicast_group::mc_nwk_s_key.eq(&mg.mc_nwk_s_key),
multicast_group::mc_app_s_key.eq(&mg.mc_app_s_key),
multicast_group::f_cnt.eq(&mg.f_cnt),
multicast_group::group_type.eq(&mg.group_type),
multicast_group::dr.eq(&mg.dr),
multicast_group::frequency.eq(&mg.frequency),
multicast_group::class_b_ping_slot_period.eq(&mg.class_b_ping_slot_period),
multicast_group::class_c_scheduling_type.eq(&mg.class_c_scheduling_type),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, mg.id.to_string()))?;
info!(id = %mg.id, "Multicast-group updated");
Ok(mg)
}
pub async fn delete(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra =
diesel::delete(multicast_group::dsl::multicast_group.find(&id)).execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(multicast_group::dsl::multicast_group.find(&id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(id = %id, "Multicast-group deleted");
Ok(())
}
pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = multicast_group::dsl::multicast_group
.select(dsl::count_star())
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = multicast_group::dsl::multicast_group
.select(dsl::count_star())
.into_boxed();
if let Some(application_id) = &filters.application_id {
q = q.filter(multicast_group::dsl::application_id.eq(application_id));
}
if let Some(application_id) = &filters.application_id {
q = q.filter(multicast_group::dsl::application_id.eq(application_id));
}
if let Some(search) = &filters.search {
q = q.filter(multicast_group::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(multicast_group::dsl::name.ilike(format!("%{}%", search)));
}
q.first(&mut c)
.map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
q.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn list(
@ -238,56 +212,51 @@ pub async fn list(
offset: i64,
filters: &Filters,
) -> Result<Vec<MulticastGroupListItem>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<MulticastGroupListItem>, Error> {
let mut c = get_db_conn()?;
let mut q = multicast_group::dsl::multicast_group
.select((
multicast_group::id,
multicast_group::created_at,
multicast_group::updated_at,
multicast_group::name,
multicast_group::region,
multicast_group::group_type,
))
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = multicast_group::dsl::multicast_group
.select((
multicast_group::id,
multicast_group::created_at,
multicast_group::updated_at,
multicast_group::name,
multicast_group::region,
multicast_group::group_type,
))
.into_boxed();
if let Some(application_id) = &filters.application_id {
q = q.filter(multicast_group::dsl::application_id.eq(application_id));
}
if let Some(application_id) = &filters.application_id {
q = q.filter(multicast_group::dsl::application_id.eq(application_id));
}
if let Some(search) = &filters.search {
q = q.filter(multicast_group::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(multicast_group::dsl::name.ilike(format!("%{}%", search)));
}
q.order_by(multicast_group::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
q.order_by(multicast_group::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn add_device(group_id: &Uuid, dev_eui: &EUI64) -> Result<(), Error> {
task::spawn_blocking({
let group_id = *group_id;
let dev_eui = *dev_eui;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
c.transaction::<(), Error, _>(|c| {
let mut c = get_async_db_conn().await?;
c.build_transaction()
.run::<(), Error, _>(|c| {
Box::pin(async move {
let d: super::device::Device = device::dsl::device
.find(&dev_eui)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
let mg: MulticastGroup = multicast_group::dsl::multicast_group
.find(&group_id)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, group_id.to_string()))?;
if d.application_id != mg.application_id {
@ -302,65 +271,59 @@ pub async fn add_device(group_id: &Uuid, dev_eui: &EUI64) -> Result<(), Error> {
multicast_group_device::created_at.eq(Utc::now()),
))
.execute(c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))?;
Ok(())
})
}
})
.await??;
})
.await?;
info!(multicast_group_id = %group_id, dev_eui = %dev_eui, "Device added to multicast-group");
Ok(())
}
pub async fn remove_device(group_id: &Uuid, dev_eui: &EUI64) -> Result<(), Error> {
task::spawn_blocking({
let group_id = *group_id;
let dev_eui = *dev_eui;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(
multicast_group_device::dsl::multicast_group_device
.filter(multicast_group_device::multicast_group_id.eq(&group_id))
.filter(multicast_group_device::dev_eui.eq(&dev_eui)),
)
.execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(format!(
"multicast-group: {}, device: {}",
group_id, dev_eui
)));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(
multicast_group_device::dsl::multicast_group_device
.filter(multicast_group_device::multicast_group_id.eq(&group_id))
.filter(multicast_group_device::dev_eui.eq(&dev_eui)),
)
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(format!(
"multicast-group: {}, device: {}",
group_id, dev_eui
)));
}
info!(multicast_group_id = %group_id, dev_eui = %dev_eui, "Device removed from multicast-group");
Ok(())
}
pub async fn add_gateway(group_id: &Uuid, gateway_id: &EUI64) -> Result<(), Error> {
task::spawn_blocking({
let group_id = *group_id;
let gateway_id = *gateway_id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
c.transaction::<(), Error, _>(|c| {
let mut c = get_async_db_conn().await?;
c.build_transaction()
.run::<(), Error, _>(|c| {
Box::pin(async move {
let gw: super::gateway::Gateway = gateway::dsl::gateway
.find(&gateway_id)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, gateway_id.to_string()))?;
let mg: MulticastGroup = multicast_group::dsl::multicast_group
.find(&group_id)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, group_id.to_string()))?;
let a: super::application::Application = application::dsl::application
.find(&mg.application_id)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, mg.application_id.to_string()))?;
if a.tenant_id != gw.tenant_id {
@ -375,70 +338,53 @@ pub async fn add_gateway(group_id: &Uuid, gateway_id: &EUI64) -> Result<(), Erro
multicast_group_gateway::created_at.eq(Utc::now()),
))
.execute(c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))?;
Ok(())
})
}
})
.await??;
})
.await?;
info!(multicast_group_id = %group_id, gateway_id = %gateway_id, "Gateway added to multicast-group");
Ok(())
}
pub async fn remove_gateway(group_id: &Uuid, gateway_id: &EUI64) -> Result<(), Error> {
task::spawn_blocking({
let group_id = *group_id;
let gateway_id = *gateway_id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(
multicast_group_gateway::dsl::multicast_group_gateway
.filter(multicast_group_gateway::multicast_group_id.eq(&group_id))
.filter(multicast_group_gateway::gateway_id.eq(&gateway_id)),
)
.execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(format!(
"multicast-group: {}, gateway: {}",
group_id, gateway_id
)));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(
multicast_group_gateway::dsl::multicast_group_gateway
.filter(multicast_group_gateway::multicast_group_id.eq(&group_id))
.filter(multicast_group_gateway::gateway_id.eq(&gateway_id)),
)
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(format!(
"multicast-group: {}, gateway: {}",
group_id, gateway_id
)));
}
info!(multicast_group_id = %group_id, gateway_id = %gateway_id, "Gateway removed from multicast-group");
Ok(())
}
pub async fn get_dev_euis(group_id: &Uuid) -> Result<Vec<EUI64>, Error> {
task::spawn_blocking({
let group_id = *group_id;
move || -> Result<Vec<EUI64>, Error> {
let mut c = get_db_conn()?;
multicast_group_device::dsl::multicast_group_device
.select(multicast_group_device::dev_eui)
.filter(multicast_group_device::dsl::multicast_group_id.eq(&group_id))
.load(&mut c)
.map_err(|e| Error::from_diesel(e, group_id.to_string()))
}
})
.await?
let mut c = get_async_db_conn().await?;
multicast_group_device::dsl::multicast_group_device
.select(multicast_group_device::dev_eui)
.filter(multicast_group_device::dsl::multicast_group_id.eq(&group_id))
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, group_id.to_string()))
}
pub async fn get_gateway_ids(group_id: &Uuid) -> Result<Vec<EUI64>, Error> {
task::spawn_blocking({
let group_id = *group_id;
move || -> Result<Vec<EUI64>, Error> {
let mut c = get_db_conn()?;
multicast_group_gateway::dsl::multicast_group_gateway
.select(multicast_group_gateway::gateway_id)
.filter(multicast_group_gateway::dsl::multicast_group_id.eq(&group_id))
.load(&mut c)
.map_err(|e| Error::from_diesel(e, group_id.to_string()))
}
})
.await?
let mut c = get_async_db_conn().await?;
multicast_group_gateway::dsl::multicast_group_gateway
.select(multicast_group_gateway::gateway_id)
.filter(multicast_group_gateway::dsl::multicast_group_id.eq(&group_id))
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, group_id.to_string()))
}
// This enqueues a multicast-group queue item for the given gateways and returns the frame-counter
@ -451,17 +397,18 @@ pub async fn enqueue(
) -> Result<(Vec<Uuid>, u32), Error> {
qi.validate()?;
let (ids, f_cnt) = task::spawn_blocking({
let gateway_ids = gateway_ids.to_vec();
move || -> Result<(Vec<Uuid>, u32), Error> {
let mut c = get_db_conn()?;
let conf = config::get();
c.transaction::<(Vec<Uuid>, u32), Error, _>(|c| {
let mut c = get_async_db_conn().await?;
let conf = config::get();
let (ids, f_cnt) = c
.build_transaction()
.run::<(Vec<Uuid>, u32), Error, _>(|c| {
Box::pin(async move {
let mut ids: Vec<Uuid> = Vec::new();
let mg: MulticastGroup = multicast_group::dsl::multicast_group
.find(&qi.multicast_group_id)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, qi.multicast_group_id.to_string()))?;
match mg.group_type.as_ref() {
@ -483,7 +430,8 @@ pub async fn enqueue(
multicast_group_queue_item::dsl::multicast_group_id
.eq(&qi.multicast_group_id),
)
.first(c)?;
.first(c)
.await?;
// Get timestamp after which we must generate the next ping-slot.
let ping_slot_after_gps_time = match res {
@ -505,7 +453,7 @@ pub async fn enqueue(
let scheduler_run_after_ts = emit_at_time_since_gps_epoch.to_date_time()
- Duration::from_std(2 * conf.network.scheduler.interval).unwrap();
for gateway_id in &gateway_ids {
for gateway_id in gateway_ids {
let qi = MulticastGroupQueueItem {
scheduler_run_after: scheduler_run_after_ts,
multicast_group_id: mg.id,
@ -523,6 +471,7 @@ pub async fn enqueue(
diesel::insert_into(multicast_group_queue_item::table)
.values(&qi)
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, mg.id.to_string()))?;
ids.push(qi.id);
}
@ -538,7 +487,8 @@ pub async fn enqueue(
multicast_group_queue_item::dsl::multicast_group_id
.eq(&qi.multicast_group_id),
)
.first(c)?;
.first(c)
.await?;
let mut scheduler_run_after_ts = match res {
Some(v) => {
@ -563,7 +513,7 @@ pub async fn enqueue(
None
};
for gateway_id in &gateway_ids {
for gateway_id in gateway_ids {
let qi = MulticastGroupQueueItem {
scheduler_run_after: scheduler_run_after_ts,
multicast_group_id: mg.id,
@ -579,6 +529,7 @@ pub async fn enqueue(
diesel::insert_into(multicast_group_queue_item::table)
.values(&qi)
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, mg.id.to_string()))?;
ids.push(qi.id);
@ -604,77 +555,58 @@ pub async fn enqueue(
diesel::update(multicast_group::dsl::multicast_group.find(&qi.multicast_group_id))
.set(multicast_group::f_cnt.eq(mg.f_cnt + 1))
.execute(c)
.await
.map_err(|e| Error::from_diesel(e, qi.multicast_group_id.to_string()))?;
// Return value before it was incremented
Ok((ids, mg.f_cnt as u32))
})
}
})
.await??;
})
.await?;
info!(multicast_group_id = %qi.multicast_group_id, f_cnt = f_cnt, "Multicast-group queue item created");
Ok((ids, f_cnt))
}
pub async fn delete_queue_item(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(
multicast_group_queue_item::dsl::multicast_group_queue_item.find(&id),
)
.execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(multicast_group_queue_item::dsl::multicast_group_queue_item.find(&id))
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(id = %id, "Multicast-group queue item deleted");
Ok(())
}
pub async fn flush_queue(multicast_group_id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let multicast_group_id = *multicast_group_id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let _ = diesel::delete(
multicast_group_queue_item::dsl::multicast_group_queue_item
.filter(multicast_group_queue_item::multicast_group_id.eq(&multicast_group_id)),
)
.execute(&mut c)
.map_err(|e| Error::from_diesel(e, multicast_group_id.to_string()))?;
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let _ = diesel::delete(
multicast_group_queue_item::dsl::multicast_group_queue_item
.filter(multicast_group_queue_item::multicast_group_id.eq(&multicast_group_id)),
)
.execute(&mut c)
.await
.map_err(|e| Error::from_diesel(e, multicast_group_id.to_string()))?;
info!(multicast_group_id = %multicast_group_id, "Multicast-group queue flushed");
Ok(())
}
pub async fn get_queue(multicast_group_id: &Uuid) -> Result<Vec<MulticastGroupQueueItem>, Error> {
task::spawn_blocking({
let multicast_group_id = *multicast_group_id;
move || -> Result<Vec<MulticastGroupQueueItem>, Error> {
let mut c = get_db_conn()?;
multicast_group_queue_item::dsl::multicast_group_queue_item
.filter(multicast_group_queue_item::dsl::multicast_group_id.eq(&multicast_group_id))
.order_by(multicast_group_queue_item::created_at)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, multicast_group_id.to_string()))
}
})
.await?
let mut c = get_async_db_conn().await?;
multicast_group_queue_item::dsl::multicast_group_queue_item
.filter(multicast_group_queue_item::dsl::multicast_group_id.eq(&multicast_group_id))
.order_by(multicast_group_queue_item::created_at)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, multicast_group_id.to_string()))
}
pub async fn get_schedulable_queue_items(limit: usize) -> Result<Vec<MulticastGroupQueueItem>> {
task::spawn_blocking({
move || -> Result<Vec<MulticastGroupQueueItem>> {
let mut c = get_db_conn()?;
c.transaction::<Vec<MulticastGroupQueueItem>, Error, _>(|c| {
let mut c = get_async_db_conn().await?;
c.build_transaction()
.run::<Vec<MulticastGroupQueueItem>, Error, _>(|c| {
Box::pin(async move {
let conf = config::get();
diesel::sql_query(
r#"
@ -703,12 +635,12 @@ pub async fn get_schedulable_queue_items(limit: usize) -> Result<Vec<MulticastGr
Utc::now() + Duration::from_std(2 * conf.network.scheduler.interval).unwrap(),
)
.load(c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
})
.context("Get schedulable multicast-group queue items")
}
})
.await?
})
.await
.context("Get schedulable multicast-group queue items")
}
#[cfg(test)]
@ -718,17 +650,12 @@ pub mod test {
use crate::test;
pub async fn get_queue_item(id: &Uuid) -> Result<MulticastGroupQueueItem, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<MulticastGroupQueueItem, Error> {
let mut c = get_db_conn()?;
multicast_group_queue_item::dsl::multicast_group_queue_item
.find(&id)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))
}
})
.await?
let mut c = get_async_db_conn().await?;
multicast_group_queue_item::dsl::multicast_group_queue_item
.find(&id)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))
}
struct FilterTest<'a> {

View File

@ -1,15 +1,14 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::dsl;
use diesel::prelude::*;
use tokio::task;
use diesel::{dsl, prelude::*};
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
use lrwn::{DevAddr, EUI64};
use super::schema::{device, device_profile, relay_device};
use super::{device::Device, error::Error, get_db_conn};
use super::{device::Device, error::Error, get_async_db_conn};
// This is set to 15, because the FilterList must contain a "catch-all" record to filter all
// uplinks that do not match the remaining records. This means that we can use 16 - 1 FilterList
@ -44,24 +43,18 @@ pub struct DeviceListItem {
}
pub async fn get_relay_count(filters: &RelayFilters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = device::dsl::device
.select(dsl::count_star())
.inner_join(device_profile::table)
.filter(device_profile::dsl::is_relay.eq(true))
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = device::dsl::device
.select(dsl::count_star())
.inner_join(device_profile::table)
.filter(device_profile::dsl::is_relay.eq(true))
.into_boxed();
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
Ok(q.first(&mut c)?)
}
})
.await?
Ok(q.first(&mut c).await?)
}
pub async fn list_relays(
@ -69,48 +62,38 @@ pub async fn list_relays(
offset: i64,
filters: &RelayFilters,
) -> Result<Vec<RelayListItem>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<RelayListItem>, Error> {
let mut c = get_db_conn()?;
let mut q = device::dsl::device
.inner_join(device_profile::table)
.select((device::dev_eui, device::name))
.filter(device_profile::dsl::is_relay.eq(true))
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = device::dsl::device
.inner_join(device_profile::table)
.select((device::dev_eui, device::name))
.filter(device_profile::dsl::is_relay.eq(true))
.into_boxed();
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id));
}
q.order_by(device::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
q.order_by(device::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn get_device_count(filters: &DeviceFilters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = relay_device::dsl::relay_device
.select(dsl::count_star())
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = relay_device::dsl::relay_device
.select(dsl::count_star())
.into_boxed();
if let Some(relay_dev_eui) = &filters.relay_dev_eui {
q = q.filter(relay_device::dsl::relay_dev_eui.eq(relay_dev_eui));
}
if let Some(relay_dev_eui) = &filters.relay_dev_eui {
q = q.filter(relay_device::dsl::relay_dev_eui.eq(relay_dev_eui));
}
q.first(&mut c)
.map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
q.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn list_devices(
@ -118,57 +101,53 @@ pub async fn list_devices(
offset: i64,
filters: &DeviceFilters,
) -> Result<Vec<DeviceListItem>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<DeviceListItem>, Error> {
let mut c = get_db_conn()?;
let mut q = relay_device::dsl::relay_device
.inner_join(device::table.on(relay_device::dsl::dev_eui.eq(device::dsl::dev_eui)))
.inner_join(
device_profile::table
.on(device::dsl::device_profile_id.eq(device_profile::dsl::id)),
)
.select((
relay_device::dev_eui,
device::join_eui,
device::dev_addr,
relay_device::created_at,
device::name,
device_profile::relay_ed_uplink_limit_bucket_size,
device_profile::relay_ed_uplink_limit_reload_rate,
))
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = relay_device::dsl::relay_device
.inner_join(device::table.on(relay_device::dsl::dev_eui.eq(device::dsl::dev_eui)))
.inner_join(
device_profile::table.on(device::dsl::device_profile_id.eq(device_profile::dsl::id)),
)
.select((
relay_device::dev_eui,
device::join_eui,
device::dev_addr,
relay_device::created_at,
device::name,
device_profile::relay_ed_uplink_limit_bucket_size,
device_profile::relay_ed_uplink_limit_reload_rate,
))
.into_boxed();
if let Some(relay_dev_eui) = &filters.relay_dev_eui {
q = q.filter(relay_device::dsl::relay_dev_eui.eq(relay_dev_eui));
}
if let Some(relay_dev_eui) = &filters.relay_dev_eui {
q = q.filter(relay_device::dsl::relay_dev_eui.eq(relay_dev_eui));
}
q.order_by(device::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.map_err(|e| Error::from_diesel(e, "".into()))
}
})
.await?
q.order_by(device::dsl::name)
.limit(limit)
.offset(offset)
.load(&mut c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn add_device(relay_dev_eui: EUI64, device_dev_eui: EUI64) -> Result<(), Error> {
task::spawn_blocking({
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
c.transaction::<(), Error, _>(|c| {
let mut c = get_async_db_conn().await?;
c.build_transaction()
.run::<(), Error, _>(|c| {
Box::pin(async move {
// We lock the relay device to avoid race-conditions in the validation.
let rd: Device = device::dsl::device
.find(&relay_dev_eui)
.for_update()
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, relay_dev_eui.to_string()))?;
// Is the given relay_dev_eui a Relay?
let rdp: super::device_profile::DeviceProfile = device_profile::dsl::device_profile
.find(&rd.device_profile_id)
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, rd.device_profile_id.to_string()))?;
if !rdp.is_relay {
return Err(Error::Validation("Device is not a relay".to_string()));
@ -178,6 +157,7 @@ pub async fn add_device(relay_dev_eui: EUI64, device_dev_eui: EUI64) -> Result<(
let d: Device = device::dsl::device
.find(&device_dev_eui)
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, device_dev_eui.to_string()))?;
if rd.application_id != d.application_id {
@ -190,6 +170,7 @@ pub async fn add_device(relay_dev_eui: EUI64, device_dev_eui: EUI64) -> Result<(
let dp: super::device_profile::DeviceProfile = device_profile::dsl::device_profile
.find(&d.device_profile_id)
.get_result(c)
.await
.map_err(|e| Error::from_diesel(e, d.device_profile_id.to_string()))?;
if rdp.region != dp.region {
return Err(Error::Validation(
@ -207,6 +188,7 @@ pub async fn add_device(relay_dev_eui: EUI64, device_dev_eui: EUI64) -> Result<(
.select(dsl::count_star())
.filter(relay_device::dsl::relay_dev_eui.eq(&relay_dev_eui))
.first(c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))?;
if count > RELAY_MAX_DEVICES {
@ -223,13 +205,13 @@ pub async fn add_device(relay_dev_eui: EUI64, device_dev_eui: EUI64) -> Result<(
relay_device::created_at.eq(Utc::now()),
))
.execute(c)
.await
.map_err(|e| Error::from_diesel(e, "".into()))?;
Ok(())
})
}
})
.await??;
})
.await?;
info!(relay_dev_eui = %relay_dev_eui, device_dev_eui = %device_dev_eui, "Device added to relay");
@ -237,25 +219,20 @@ pub async fn add_device(relay_dev_eui: EUI64, device_dev_eui: EUI64) -> Result<(
}
pub async fn remove_device(relay_dev_eui: EUI64, device_dev_eui: EUI64) -> Result<(), Error> {
task::spawn_blocking({
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(
relay_device::dsl::relay_device
.filter(relay_device::relay_dev_eui.eq(&relay_dev_eui))
.filter(relay_device::dev_eui.eq(&device_dev_eui)),
)
.execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(format!(
"relay_dev_eui: {}, device_dev_eui: {}",
relay_dev_eui, device_dev_eui
)));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(
relay_device::dsl::relay_device
.filter(relay_device::relay_dev_eui.eq(&relay_dev_eui))
.filter(relay_device::dev_eui.eq(&device_dev_eui)),
)
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(format!(
"relay_dev_eui: {}, device_dev_eui: {}",
relay_dev_eui, device_dev_eui
)));
}
info!(relay_dev_eui = %relay_dev_eui, device_dev_eui = %device_dev_eui, "Device removed from relay");

View File

@ -1,13 +1,12 @@
use std::collections::HashMap;
use crate::diesel::RunQueryDsl;
use anyhow::{Context, Result};
use diesel_async::RunQueryDsl;
use regex::Regex;
use tokio::task;
use uuid::Uuid;
use super::error::Error;
use super::get_db_conn;
use super::get_async_db_conn;
use lrwn::EUI64;
lazy_static! {
@ -45,117 +44,112 @@ pub async fn global_search(
limit: usize,
offset: usize,
) -> Result<Vec<SearchResult>, Error> {
task::spawn_blocking({
let user_id = *user_id;
let search = search.to_string();
let (query, tags) = parse_search_query(&search);
let query = format!("%{}%", query);
let tags = serde_json::to_value(tags).context("To serde_json value")?;
let (query, tags) = parse_search_query(search);
let query = format!("%{}%", query);
let tags = serde_json::to_value(tags).context("To serde_json value")?;
move || -> Result<Vec<SearchResult>, Error> {
let mut c = get_db_conn()?;
let res = diesel::sql_query(
r#"
-- device
select
'device' as kind,
greatest(similarity(d.name, $1), similarity(encode(d.dev_eui, 'hex'), $1), similarity(encode(d.dev_addr, 'hex'), $1)) as score,
t.id as tenant_id,
t.name as tenant_name,
a.id as application_id,
a.name as application_name,
d.dev_eui as device_dev_eui,
d.name as device_name,
null as gateway_id,
null as gateway_name
from device d
inner join application a
on a.id = d.application_id
inner join tenant t
on t.id = a.tenant_id
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and (d.name ilike $2 or encode(d.dev_eui, 'hex') ilike $2 or encode(d.dev_addr, 'hex') ilike $2 or ($7 != '{}'::jsonb and d.tags @> $7))
-- gateway
union
select
'gateway' as kind,
greatest(similarity(g.name, $1), similarity(encode(g.gateway_id, 'hex'), $1)) as score,
t.id as tenant_id,
t.name as tenant_name,
null as application_id,
null as application_name,
null as device_dev_eui,
null as device_name,
g.gateway_id as gateway_id,
g.name as gateway_name
from
gateway g
inner join tenant t
on t.id = g.tenant_id
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and (g.name ilike $2 or encode(g.gateway_id, 'hex') ilike $2 or ($7 != '{}'::jsonb and g.tags @> $7))
-- tenant
union
select
'tenant' as kind,
similarity(t.name, $1) as score,
t.id as tenant_id,
t.name as tenant_name,
null as application_id,
null as application_name,
null as device_dev_eui,
null as device_name,
null as gateway_id,
null as gateway_name
from
tenant t
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and t.name ilike $2
-- application
union
select
'application' as kind,
similarity(a.name, $1) as score,
t.id as tenant_id,
t.name as tenant_name,
a.id as application_id,
a.name as application_name,
null as device_dev_eui,
null as device_name,
null as gateway_id,
null as gateway_name
from
application a
inner join tenant t
on t.id = a.tenant_id
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and a.name ilike $2
order by
score desc
limit $5
offset $6
"#,
)
let mut c = get_async_db_conn().await?;
let res: Vec<SearchResult> = diesel::sql_query(
r#"
-- device
select
'device' as kind,
greatest(similarity(d.name, $1), similarity(encode(d.dev_eui, 'hex'), $1), similarity(encode(d.dev_addr, 'hex'), $1)) as score,
t.id as tenant_id,
t.name as tenant_name,
a.id as application_id,
a.name as application_name,
d.dev_eui as device_dev_eui,
d.name as device_name,
null as gateway_id,
null as gateway_name
from device d
inner join application a
on a.id = d.application_id
inner join tenant t
on t.id = a.tenant_id
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and (d.name ilike $2 or encode(d.dev_eui, 'hex') ilike $2 or encode(d.dev_addr, 'hex') ilike $2 or ($7 != '{}'::jsonb and d.tags @> $7))
-- gateway
union
select
'gateway' as kind,
greatest(similarity(g.name, $1), similarity(encode(g.gateway_id, 'hex'), $1)) as score,
t.id as tenant_id,
t.name as tenant_name,
null as application_id,
null as application_name,
null as device_dev_eui,
null as device_name,
g.gateway_id as gateway_id,
g.name as gateway_name
from
gateway g
inner join tenant t
on t.id = g.tenant_id
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and (g.name ilike $2 or encode(g.gateway_id, 'hex') ilike $2 or ($7 != '{}'::jsonb and g.tags @> $7))
-- tenant
union
select
'tenant' as kind,
similarity(t.name, $1) as score,
t.id as tenant_id,
t.name as tenant_name,
null as application_id,
null as application_name,
null as device_dev_eui,
null as device_name,
null as gateway_id,
null as gateway_name
from
tenant t
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and t.name ilike $2
-- application
union
select
'application' as kind,
similarity(a.name, $1) as score,
t.id as tenant_id,
t.name as tenant_name,
a.id as application_id,
a.name as application_name,
null as device_dev_eui,
null as device_name,
null as gateway_id,
null as gateway_name
from
application a
inner join tenant t
on t.id = a.tenant_id
left join tenant_user tu
on tu.tenant_id = t.id
left join "user" u
on u.id = tu.user_id
where
($3 = true or u.id = $4)
and a.name ilike $2
order by
score desc
limit $5
offset $6
"#)
.bind::<diesel::sql_types::Text, _>(&search)
.bind::<diesel::sql_types::Text, _>(&query)
.bind::<diesel::sql_types::Bool, _>(global_admin)
@ -163,12 +157,9 @@ pub async fn global_search(
.bind::<diesel::sql_types::BigInt, _>(limit as i64)
.bind::<diesel::sql_types::BigInt, _>(offset as i64)
.bind::<diesel::sql_types::Jsonb, _>(tags)
.load(&mut c)?;
.load(&mut c).await?;
Ok(res)
}
})
.await?
Ok(res)
}
fn parse_search_query(q: &str) -> (String, HashMap<String, String>) {

View File

@ -2,15 +2,14 @@ use std::collections::HashMap;
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::dsl;
use diesel::prelude::*;
use tokio::task;
use diesel::{dsl, prelude::*};
use diesel_async::RunQueryDsl;
use tracing::info;
use uuid::Uuid;
use super::error::Error;
use super::schema::{tenant, tenant_user, user};
use super::{fields, get_db_conn};
use super::{fields, get_async_db_conn};
#[derive(Queryable, Insertable, PartialEq, Eq, Debug, Clone)]
#[diesel(table_name = tenant)]
@ -105,148 +104,113 @@ pub struct Filters {
pub async fn create(t: Tenant) -> Result<Tenant, Error> {
t.validate()?;
let t = task::spawn_blocking({
move || -> Result<Tenant, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(tenant::table)
.values(&t)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, t.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let t: Tenant = diesel::insert_into(tenant::table)
.values(&t)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, t.id.to_string()))?;
info!(id = %t.id, "Tenant created");
Ok(t)
}
pub async fn get(id: &Uuid) -> Result<Tenant, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<Tenant, Error> {
let mut c = get_db_conn()?;
let t = tenant::dsl::tenant
.find(&id)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(t)
}
})
.await?
let mut c = get_async_db_conn().await?;
let t = tenant::dsl::tenant
.find(&id)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(t)
}
pub async fn update(t: Tenant) -> Result<Tenant, Error> {
t.validate()?;
let t = task::spawn_blocking({
move || -> Result<Tenant, Error> {
let mut c = get_db_conn()?;
diesel::update(tenant::dsl::tenant.find(&t.id))
.set((
tenant::updated_at.eq(Utc::now()),
tenant::name.eq(&t.name),
tenant::description.eq(&t.description),
tenant::can_have_gateways.eq(&t.can_have_gateways),
tenant::max_device_count.eq(&t.max_device_count),
tenant::max_gateway_count.eq(&t.max_gateway_count),
tenant::private_gateways_up.eq(&t.private_gateways_up),
tenant::private_gateways_down.eq(&t.private_gateways_down),
tenant::tags.eq(&t.tags),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, t.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let t: Tenant = diesel::update(tenant::dsl::tenant.find(&t.id))
.set((
tenant::updated_at.eq(Utc::now()),
tenant::name.eq(&t.name),
tenant::description.eq(&t.description),
tenant::can_have_gateways.eq(&t.can_have_gateways),
tenant::max_device_count.eq(&t.max_device_count),
tenant::max_gateway_count.eq(&t.max_gateway_count),
tenant::private_gateways_up.eq(&t.private_gateways_up),
tenant::private_gateways_down.eq(&t.private_gateways_down),
tenant::tags.eq(&t.tags),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, t.id.to_string()))?;
info!(id = %t.id, "Tenant updated");
Ok(t)
}
pub async fn delete(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(tenant::dsl::tenant.find(&id))
.execute(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(tenant::dsl::tenant.find(&id))
.execute(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
Ok(())
}
})
.await??;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(id = %id, "Tenant deleted");
Ok(())
}
pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let mut q = tenant::dsl::tenant
.left_join(tenant_user::table)
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = tenant::dsl::tenant
.left_join(tenant_user::table)
.into_boxed();
if let Some(user_id) = &filters.user_id {
q = q.filter(tenant_user::dsl::user_id.eq(user_id));
}
if let Some(user_id) = &filters.user_id {
q = q.filter(tenant_user::dsl::user_id.eq(user_id));
}
if let Some(search) = &filters.search {
q = q.filter(tenant::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(tenant::dsl::name.ilike(format!("%{}%", search)));
}
Ok(
q.select(dsl::sql::<diesel::sql_types::BigInt>("count(distinct id)"))
.first(&mut c)?,
)
}
})
.await?
Ok(
q.select(dsl::sql::<diesel::sql_types::BigInt>("count(distinct id)"))
.first(&mut c)
.await?,
)
}
pub async fn list(limit: i64, offset: i64, filters: &Filters) -> Result<Vec<Tenant>, Error> {
task::spawn_blocking({
let filters = filters.clone();
move || -> Result<Vec<Tenant>, Error> {
let mut c = get_db_conn()?;
let mut q = tenant::dsl::tenant
.left_join(tenant_user::table)
.select(tenant::all_columns)
.group_by(tenant::dsl::id)
.order_by(tenant::dsl::name)
.limit(limit)
.offset(offset)
.into_boxed();
let mut c = get_async_db_conn().await?;
let mut q = tenant::dsl::tenant
.left_join(tenant_user::table)
.select(tenant::all_columns)
.group_by(tenant::dsl::id)
.order_by(tenant::dsl::name)
.limit(limit)
.offset(offset)
.into_boxed();
if let Some(user_id) = &filters.user_id {
q = q.filter(tenant_user::dsl::user_id.eq(user_id));
}
if let Some(user_id) = &filters.user_id {
q = q.filter(tenant_user::dsl::user_id.eq(user_id));
}
if let Some(search) = &filters.search {
q = q.filter(tenant::dsl::name.ilike(format!("%{}%", search)));
}
if let Some(search) = &filters.search {
q = q.filter(tenant::dsl::name.ilike(format!("%{}%", search)));
}
let items = q.load(&mut c)?;
Ok(items)
}
})
.await?
let items = q.load(&mut c).await?;
Ok(items)
}
pub async fn add_user(tu: TenantUser) -> Result<TenantUser, Error> {
let tu = task::spawn_blocking({
move || -> Result<TenantUser, Error> {
let mut c = get_db_conn()?;
diesel::insert_into(tenant_user::table)
.values(&tu)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, tu.user_id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let tu: TenantUser = diesel::insert_into(tenant_user::table)
.values(&tu)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, tu.user_id.to_string()))?;
info!(
tenant_id = %tu.tenant_id,
user_id = %tu.user_id,
@ -256,20 +220,16 @@ pub async fn add_user(tu: TenantUser) -> Result<TenantUser, Error> {
}
pub async fn update_user(tu: TenantUser) -> Result<TenantUser, Error> {
let tu = task::spawn_blocking({
move || -> Result<TenantUser, Error> {
let mut c = get_db_conn()?;
diesel::update(
tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::tenant_id.eq(&tu.tenant_id))
.filter(tenant_user::dsl::user_id.eq(&tu.user_id)),
)
.set(&tu)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, tu.user_id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let tu: TenantUser = diesel::update(
tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::tenant_id.eq(&tu.tenant_id))
.filter(tenant_user::dsl::user_id.eq(&tu.user_id)),
)
.set(&tu)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, tu.user_id.to_string()))?;
info!(
tenant_id = %tu.tenant_id,
user_id = %tu.user_id,
@ -279,35 +239,24 @@ pub async fn update_user(tu: TenantUser) -> Result<TenantUser, Error> {
}
pub async fn get_user(tenant_id: &Uuid, user_id: &Uuid) -> Result<TenantUser, Error> {
task::spawn_blocking({
let tenant_id = *tenant_id;
let user_id = *user_id;
move || -> Result<TenantUser, Error> {
let mut c = get_db_conn()?;
let tu: TenantUser = tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.filter(tenant_user::dsl::user_id.eq(&user_id))
.first(&mut c)
.map_err(|e| Error::from_diesel(e, user_id.to_string()))?;
Ok(tu)
}
})
.await?
let mut c = get_async_db_conn().await?;
let tu: TenantUser = tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.filter(tenant_user::dsl::user_id.eq(&user_id))
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, user_id.to_string()))?;
Ok(tu)
}
pub async fn get_user_count(tenant_id: &Uuid) -> Result<i64, Error> {
task::spawn_blocking({
let tenant_id = *tenant_id;
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let count = tenant_user::dsl::tenant_user
.select(dsl::count_star())
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.first(&mut c)?;
Ok(count)
}
})
.await?
let mut c = get_async_db_conn().await?;
let count = tenant_user::dsl::tenant_user
.select(dsl::count_star())
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.first(&mut c)
.await?;
Ok(count)
}
pub async fn get_users(
@ -315,53 +264,41 @@ pub async fn get_users(
limit: i64,
offset: i64,
) -> Result<Vec<TenantUserListItem>, Error> {
task::spawn_blocking({
let tenant_id = *tenant_id;
move || -> Result<Vec<TenantUserListItem>, Error> {
let mut c = get_db_conn()?;
let items = tenant_user::dsl::tenant_user
.inner_join(user::table)
.select((
tenant_user::dsl::tenant_id,
tenant_user::dsl::user_id,
tenant_user::dsl::created_at,
tenant_user::dsl::updated_at,
user::dsl::email,
tenant_user::dsl::is_admin,
tenant_user::dsl::is_device_admin,
tenant_user::dsl::is_gateway_admin,
))
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.order_by(user::dsl::email)
.limit(limit)
.offset(offset)
.load(&mut c)?;
let mut c = get_async_db_conn().await?;
let items = tenant_user::dsl::tenant_user
.inner_join(user::table)
.select((
tenant_user::dsl::tenant_id,
tenant_user::dsl::user_id,
tenant_user::dsl::created_at,
tenant_user::dsl::updated_at,
user::dsl::email,
tenant_user::dsl::is_admin,
tenant_user::dsl::is_device_admin,
tenant_user::dsl::is_gateway_admin,
))
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.order_by(user::dsl::email)
.limit(limit)
.offset(offset)
.load(&mut c)
.await?;
Ok(items)
}
})
.await?
Ok(items)
}
pub async fn delete_user(tenant_id: &Uuid, user_id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let tenant_id = *tenant_id;
let user_id = *user_id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(
tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.filter(tenant_user::dsl::user_id.eq(&user_id)),
)
.execute(&mut c)?;
if ra == 0 {
return Err(Error::NotFound(user_id.to_string()));
}
Ok(())
}
})
.await??;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(
tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::tenant_id.eq(&tenant_id))
.filter(tenant_user::dsl::user_id.eq(&user_id)),
)
.execute(&mut c)
.await?;
if ra == 0 {
return Err(Error::NotFound(user_id.to_string()));
}
info!(
tenant_id = %tenant_id,
user_id = %user_id,
@ -371,17 +308,12 @@ pub async fn delete_user(tenant_id: &Uuid, user_id: &Uuid) -> Result<(), Error>
}
pub async fn get_tenant_users_for_user(user_id: &Uuid) -> Result<Vec<TenantUser>, Error> {
task::spawn_blocking({
let user_id = *user_id;
move || -> Result<Vec<TenantUser>, Error> {
let mut c = get_db_conn()?;
let items = tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::user_id.eq(&user_id))
.load(&mut c)?;
Ok(items)
}
})
.await?
let mut c = get_async_db_conn().await?;
let items = tenant_user::dsl::tenant_user
.filter(tenant_user::dsl::user_id.eq(&user_id))
.load(&mut c)
.await?;
Ok(items)
}
#[cfg(test)]

View File

@ -1,19 +1,18 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use diesel::dsl;
use diesel::prelude::*;
use diesel::{dsl, prelude::*};
use diesel_async::RunQueryDsl;
use pbkdf2::{
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Algorithm, Pbkdf2,
};
use rand_core::OsRng;
use tokio::task;
use tracing::info;
use uuid::Uuid;
use validator::validate_email;
use super::error::Error;
use super::get_db_conn;
use super::get_async_db_conn;
use super::schema::user;
#[derive(Queryable, Insertable, PartialEq, Eq, Debug, Clone)]
@ -67,181 +66,134 @@ impl User {
pub async fn create(u: User) -> Result<User, Error> {
u.validate()?;
let u = task::spawn_blocking({
move || -> Result<User, Error> {
let mut c = get_db_conn()?;
let mut c = get_async_db_conn().await?;
diesel::insert_into(user::table)
.values(&u)
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, u.id.to_string()))
}
})
.await??;
let u: User = diesel::insert_into(user::table)
.values(&u)
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, u.id.to_string()))?;
info!(id = %u.id, "User created");
Ok(u)
}
pub async fn get(id: &Uuid) -> Result<User, Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<User, Error> {
let mut c = get_db_conn()?;
let u = user::dsl::user
.find(&id)
.first(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(u)
}
})
.await?
let mut c = get_async_db_conn().await?;
let u = user::dsl::user
.find(&id)
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
Ok(u)
}
pub async fn get_by_email(email: &str) -> Result<User, Error> {
task::spawn_blocking({
let email = email.to_string();
move || -> Result<User, Error> {
let mut c = get_db_conn()?;
let u = user::dsl::user
.filter(user::dsl::email.eq(&email))
.first(&mut c)
.map_err(|e| Error::from_diesel(e, email))?;
Ok(u)
}
})
.await?
let mut c = get_async_db_conn().await?;
let u = user::dsl::user
.filter(user::dsl::email.eq(email))
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, email.to_string()))?;
Ok(u)
}
pub async fn get_by_external_id(external_id: &str) -> Result<User, Error> {
task::spawn_blocking({
let external_id = external_id.to_string();
move || -> Result<User, Error> {
let mut c = get_db_conn()?;
let u = user::dsl::user
.filter(user::dsl::external_id.eq(&external_id))
.first(&mut c)
.map_err(|e| Error::from_diesel(e, external_id))?;
Ok(u)
}
})
.await?
let mut c = get_async_db_conn().await?;
let u = user::dsl::user
.filter(user::dsl::external_id.eq(external_id))
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, external_id.to_string()))?;
Ok(u)
}
pub async fn get_by_email_and_pw(email: &str, pw: &str) -> Result<User, Error> {
task::spawn_blocking({
let email = email.to_string();
let pw = pw.to_string();
move || -> Result<User, Error> {
let mut c = get_db_conn()?;
let u: User = match user::dsl::user
.filter(user::dsl::email.eq(&email))
.first(&mut c)
.map_err(|e| Error::from_diesel(e, email))
{
Ok(v) => v,
Err(Error::NotFound(_)) => {
return Err(Error::InvalidUsernameOrPassword);
}
Err(v) => {
return Err(v);
}
};
if verify_password(&pw, &u.password_hash) {
return Ok(u);
}
Err(Error::InvalidUsernameOrPassword)
let mut c = get_async_db_conn().await?;
let u: User = match user::dsl::user
.filter(user::dsl::email.eq(email))
.first(&mut c)
.await
.map_err(|e| Error::from_diesel(e, email.to_string()))
{
Ok(v) => v,
Err(Error::NotFound(_)) => {
return Err(Error::InvalidUsernameOrPassword);
}
})
.await?
Err(v) => {
return Err(v);
}
};
if verify_password(&pw, &u.password_hash) {
return Ok(u);
}
Err(Error::InvalidUsernameOrPassword)
}
pub async fn update(u: User) -> Result<User, Error> {
u.validate()?;
let u = task::spawn_blocking({
move || -> Result<User, Error> {
let mut c = get_db_conn()?;
diesel::update(user::dsl::user.find(&u.id))
.set((
user::updated_at.eq(Utc::now()),
user::is_admin.eq(&u.is_admin),
user::is_active.eq(&u.is_active),
user::email.eq(&u.email),
user::email_verified.eq(&u.email_verified),
user::note.eq(&u.note),
user::external_id.eq(&u.external_id),
))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, u.id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let u: User = diesel::update(user::dsl::user.find(&u.id))
.set((
user::updated_at.eq(Utc::now()),
user::is_admin.eq(&u.is_admin),
user::is_active.eq(&u.is_active),
user::email.eq(&u.email),
user::email_verified.eq(&u.email_verified),
user::note.eq(&u.note),
user::external_id.eq(&u.external_id),
))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, u.id.to_string()))?;
info!(user_id = %u.id, "User updated");
Ok(u)
}
pub async fn set_password_hash(id: &Uuid, hash: &str) -> Result<User, Error> {
let u = task::spawn_blocking({
let id = *id;
let hash = hash.to_string();
move || -> Result<User, Error> {
let mut c = get_db_conn()?;
diesel::update(user::dsl::user.find(&id))
.set(user::password_hash.eq(&hash))
.get_result(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))
}
})
.await??;
let mut c = get_async_db_conn().await?;
let u: User = diesel::update(user::dsl::user.find(&id))
.set(user::password_hash.eq(&hash))
.get_result(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!(id = %id, "Password set");
Ok(u)
}
pub async fn delete(id: &Uuid) -> Result<(), Error> {
task::spawn_blocking({
let id = *id;
move || -> Result<(), Error> {
let mut c = get_db_conn()?;
let ra = diesel::delete(user::dsl::user.find(&id))
.execute(&mut c)
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
let mut c = get_async_db_conn().await?;
let ra = diesel::delete(user::dsl::user.find(&id))
.execute(&mut c)
.await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
Ok(())
}
})
.await??;
if ra == 0 {
return Err(Error::NotFound(id.to_string()));
}
info!(user_id = %id, "User deleted");
Ok(())
}
pub async fn get_count() -> Result<i64, Error> {
task::spawn_blocking({
move || -> Result<i64, Error> {
let mut c = get_db_conn()?;
let count = user::dsl::user.select(dsl::count_star()).first(&mut c)?;
Ok(count)
}
})
.await?
let mut c = get_async_db_conn().await?;
let count = user::dsl::user
.select(dsl::count_star())
.first(&mut c)
.await?;
Ok(count)
}
pub async fn list(limit: i64, offset: i64) -> Result<Vec<User>, Error> {
task::spawn_blocking({
move || -> Result<Vec<User>, Error> {
let mut c = get_db_conn()?;
let items = user::dsl::user
.order_by(user::dsl::email)
.limit(limit)
.offset(offset)
.load(&mut c)?;
Ok(items)
}
})
.await?
let mut c = get_async_db_conn().await?;
let items = user::dsl::user
.order_by(user::dsl::email)
.limit(limit)
.offset(offset)
.load(&mut c)
.await?;
Ok(items)
}
// The output format is documented here:

View File

@ -80,7 +80,7 @@ pub async fn prepare<'a>() -> std::sync::MutexGuard<'a, ()> {
storage::setup().await.unwrap();
// reset db
storage::reset_db().unwrap();
storage::reset_db().await.unwrap();
// flush redis db
storage::reset_redis().await.unwrap();

View File

@ -1,17 +1,13 @@
FROM ghcr.io/cross-rs/aarch64-unknown-linux-musl:latest
ENV ZLIB_VERSION=1.3
ENV POSTGRESQL_VERSION=11.21
ENV OPENSSL_VERSION=3.1.2
ENV OPENSSL_TARGET=linux-aarch64
ENV MUSL_PREFIX=aarch64-linux-musl
ENV POSTGRESQL_HOST=aarch64-unknown-linux-musl
RUN apt-get update && \
apt-get --assume-yes install \
protobuf-compiler \
libprotobuf-dev \
binutils-aarch64-linux-gnu
libprotobuf-dev
RUN echo "Building OpenSSL" && \
cd /tmp && \
@ -23,24 +19,4 @@ RUN echo "Building OpenSSL" && \
make install_sw && \
rm -r /tmp/*
RUN echo "Building zlib" && \
cd /tmp && \
curl -fLO "https://zlib.net/zlib-$ZLIB_VERSION.tar.gz" && \
tar xzf "zlib-$ZLIB_VERSION.tar.gz" && cd "zlib-$ZLIB_VERSION" && \
CC=$MUSL_PREFIX-gcc ./configure --static --prefix=/usr/local/$MUSL_PREFIX-target && \
make && make install && \
rm -r /tmp/*
RUN echo "Building libpq" && \
cd /tmp && \
curl -fLO "https://ftp.postgresql.org/pub/source/v$POSTGRESQL_VERSION/postgresql-$POSTGRESQL_VERSION.tar.gz" && \
tar xzf "postgresql-$POSTGRESQL_VERSION.tar.gz" && cd "postgresql-$POSTGRESQL_VERSION" && \
CC=$MUSL_PREFIX-gcc CPPFLAGS="-I/usr/local/$MUSL_PREFIX/include -I/usr/local/$MUSL_PREFIX-target/include" LDFLAGS="-L/usr/local/$MUSL_PREFIX/lib -L/usr/local/$MUSL_PREFIX-target/lib" ./configure --with-openssl --without-readline --prefix=/usr/local/$MUSL_PREFIX-target --host $POSTGRESQL_HOST && \
cd src/interfaces/libpq && make all-static-lib && make install-lib-static && \
rm -r /tmp/*
# Workaround for re-defined unicode_to_utf8 which also exists in quickjs library.
RUN /usr/aarch64-linux-gnu/bin/objcopy --redefine-sym unicode_to_utf8=unicode_to_utf8_rename /usr/local/$MUSL_PREFIX-target/lib/libpq.a
ENV PKG_CONFIG_PATH=/usr/local/$MUSL_PREFIX-target/lib/pkgconfig

View File

@ -1,17 +1,13 @@
FROM ghcr.io/cross-rs/armv7-unknown-linux-musleabihf:latest
ENV ZLIB_VERSION=1.3
ENV POSTGRESQL_VERSION=11.21
ENV OPENSSL_VERSION=3.1.2
ENV OPENSSL_TARGET=linux-generic32
ENV MUSL_PREFIX=arm-linux-musleabihf
ENV POSTGRESQL_HOST=armv7-unknown-linux-musleabihf
RUN apt-get update && \
apt-get --assume-yes install \
protobuf-compiler \
libprotobuf-dev \
binutils-arm-linux-gnueabihf
libprotobuf-dev
RUN echo "Building OpenSSL" && \
cd /tmp && \
@ -23,24 +19,4 @@ RUN echo "Building OpenSSL" && \
make install_sw && \
rm -r /tmp/*
RUN echo "Building zlib" && \
cd /tmp && \
curl -fLO "https://zlib.net/zlib-$ZLIB_VERSION.tar.gz" && \
tar xzf "zlib-$ZLIB_VERSION.tar.gz" && cd "zlib-$ZLIB_VERSION" && \
CC=$MUSL_PREFIX-gcc ./configure --static --prefix=/usr/local/$MUSL_PREFIX-target && \
make && make install && \
rm -r /tmp/*
RUN echo "Building libpq" && \
cd /tmp && \
curl -fLO "https://ftp.postgresql.org/pub/source/v$POSTGRESQL_VERSION/postgresql-$POSTGRESQL_VERSION.tar.gz" && \
tar xzf "postgresql-$POSTGRESQL_VERSION.tar.gz" && cd "postgresql-$POSTGRESQL_VERSION" && \
CC=$MUSL_PREFIX-gcc CPPFLAGS="-I/usr/local/$MUSL_PREFIX/include -I/usr/local/$MUSL_PREFIX-target/include" LDFLAGS="-L/usr/local/$MUSL_PREFIX/lib -L/usr/local/$MUSL_PREFIX-target/lib" ./configure --with-openssl --without-readline --prefix=/usr/local/$MUSL_PREFIX-target --host $POSTGRESQL_HOST && \
cd src/interfaces/libpq && make all-static-lib && make install-lib-static && \
rm -r /tmp/*
# Workaround for re-defined unicode_to_utf8 which also exists in quickjs library.
RUN /usr/bin/arm-linux-gnueabihf-objcopy --redefine-sym unicode_to_utf8=unicode_to_utf8_rename /usr/local/$MUSL_PREFIX-target/lib/libpq.a
ENV PKG_CONFIG_PATH=/usr/local/$MUSL_PREFIX-target/lib/pkgconfig

View File

@ -1,7 +1,5 @@
FROM ghcr.io/cross-rs/x86_64-unknown-linux-musl:latest
ENV ZLIB_VERSION=1.3
ENV POSTGRESQL_VERSION=11.21
ENV OPENSSL_VERSION=3.1.2
ENV OPENSSL_TARGET=linux-x86_64
ENV MUSL_PREFIX=x86_64-linux-musl
@ -21,24 +19,4 @@ RUN echo "Building OpenSSL" && \
make install_sw && \
rm -r /tmp/*
RUN echo "Building zlib" && \
cd /tmp && \
curl -fLO "https://zlib.net/zlib-$ZLIB_VERSION.tar.gz" && \
tar xzf "zlib-$ZLIB_VERSION.tar.gz" && cd "zlib-$ZLIB_VERSION" && \
CC=$MUSL_PREFIX-gcc ./configure --static --prefix=/usr/local/$MUSL_PREFIX-target && \
make && make install && \
rm -r /tmp/*
RUN echo "Building libpq" && \
cd /tmp && \
curl -fLO "https://ftp.postgresql.org/pub/source/v$POSTGRESQL_VERSION/postgresql-$POSTGRESQL_VERSION.tar.gz" && \
tar xzf "postgresql-$POSTGRESQL_VERSION.tar.gz" && cd "postgresql-$POSTGRESQL_VERSION" && \
CC=$MUSL_PREFIX-gcc CPPFLAGS="-I/usr/local/$MUSL_PREFIX/include -I/usr/local/$MUSL_PREFIX-target/include" LDFLAGS="-L/usr/local/$MUSL_PREFIX/lib -L/usr/local/$MUSL_PREFIX-target/lib -L/usr/local/$MUSL_PREFIX-target/lib64" ./configure --with-openssl --without-readline --prefix=/usr/local/$MUSL_PREFIX-target && \
cd src/interfaces/libpq && make all-static-lib && make install-lib-static && \
rm -r /tmp/*
# Workaround for re-defined unicode_to_utf8 which also exists in quickjs library.
RUN objcopy --redefine-sym unicode_to_utf8=unicode_to_utf8_rename /usr/local/$MUSL_PREFIX-target/lib/libpq.a
ENV PKG_CONFIG_PATH=/usr/local/$MUSL_PREFIX-target/lib/pkgconfig:/usr/local/$MUSL_PREFIX-target/lib64/pkgconfig

View File

@ -16,7 +16,7 @@ hex = "0.4"
cmac = { version = "0.7", optional = true }
aes = { version = "0.8", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
diesel = { version = "2.1", features = ["postgres"], optional = true }
diesel = { version = "2.1", features = ["postgres_backend"], optional = true }
# Error handling
thiserror = "1.0"

View File

@ -11,7 +11,6 @@ pkgs.mkShell {
pkgs.perl
pkgs.cmake
pkgs.clang
pkgs.postgresql
pkgs.openssl
];
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";