mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-01-29 15:13:52 +00:00
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:
parent
3f57609981
commit
8e2eda3d5b
219
Cargo.lock
generated
219
Cargo.lock
generated
@ -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"
|
||||
|
@ -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" }
|
||||
|
@ -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
@ -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?
|
||||
|
@ -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.
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)]
|
||||
|
@ -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)]
|
||||
|
@ -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"
|
||||
|
@ -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)]
|
||||
|
@ -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)]
|
||||
|
@ -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();
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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> {
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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>) {
|
||||
|
@ -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)]
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user