Implement support for SQLite database backend. (#418) (#500)

This feature makes it possible to select between PostgreSQL and SQLite as database backend using a compile feature-flag. It is not possible to enable both at the same time.

---------

Co-authored-by: Momo Bel <plopyomomo@gmail.com>
This commit is contained in:
Orne Brocaar 2024-08-26 13:22:35 +01:00 committed by GitHub
parent 800d7d0efe
commit e63296573b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
137 changed files with 5036 additions and 2482 deletions

View File

@ -13,6 +13,13 @@ env:
jobs: jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
database:
- postgres
- sqlite
env:
DATABASE: ${{ matrix.database }}
steps: steps:
- -
name: Checkout name: Checkout
@ -32,7 +39,7 @@ jobs:
~/.cargo/registry/cache/ ~/.cargo/registry/cache/
~/.cargo/git/db/ ~/.cargo/git/db/
target/ target/
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-test-${{ matrix.database }}-${{ hashFiles('**/Cargo.lock') }}
- -
name: Start dependency services name: Start dependency services
run: docker compose up -d run: docker compose up -d

4
.gitignore vendored
View File

@ -11,8 +11,12 @@
# Binary packages # Binary packages
/dist /dist
# SQLite databases
*.sqlite
# Rust target directory # Rust target directory
**/target **/target
**/target-sqlite
# Certificates # Certificates
/chirpstack/configuration/certs/* /chirpstack/configuration/certs/*

24
Cargo.lock generated
View File

@ -876,6 +876,7 @@ dependencies = [
"rustls 0.23.12", "rustls 0.23.12",
"rustls-native-certs", "rustls-native-certs",
"rustls-pemfile", "rustls-pemfile",
"scoped-futures",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
@ -907,7 +908,6 @@ dependencies = [
name = "chirpstack_api" name = "chirpstack_api"
version = "4.9.0" version = "4.9.0"
dependencies = [ dependencies = [
"diesel",
"hex", "hex",
"pbjson", "pbjson",
"pbjson-build", "pbjson-build",
@ -1303,10 +1303,12 @@ dependencies = [
"chrono", "chrono",
"diesel_derives", "diesel_derives",
"itoa", "itoa",
"libsqlite3-sys",
"num-bigint", "num-bigint",
"num-integer", "num-integer",
"num-traits", "num-traits",
"serde_json", "serde_json",
"time",
"uuid", "uuid",
] ]
@ -2165,9 +2167,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@ -2466,6 +2468,16 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "libsqlite3-sys"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4588d65215825ee71ebff9e1c9982067833b1355d7546845ffdb3165cbd7456"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.8" version = "0.3.8"
@ -5019,6 +5031,12 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"

View File

@ -8,7 +8,7 @@ dist:
# Install dev dependencies # Install dev dependencies
dev-dependencies: dev-dependencies:
cargo install cross --version 0.2.5 cargo install cross --version 0.2.5
cargo install diesel_cli --version 2.2.1 --no-default-features --features postgres cargo install diesel_cli --version 2.2.1 --no-default-features --features postgres,sqlite
cargo install cargo-deb --version 1.43.1 cargo install cargo-deb --version 1.43.1
cargo install cargo-generate-rpm --version 0.12.1 cargo install cargo-generate-rpm --version 0.12.1

View File

@ -84,7 +84,11 @@ docker compose up -d
Run the following command to run the ChirpStack tests: Run the following command to run the ChirpStack tests:
```bash ```bash
# Test (with PostgresQL database backend)
make test make test
# Test with SQLite database backend
DATABASE=sqlite make test
``` ```
### Building ChirpStack binaries ### Building ChirpStack binaries
@ -109,6 +113,34 @@ make release-amd64
make dist make dist
``` ```
By default the above commands will build ChirpStack with the PostgresQL database
database backend. Set the `DATABASE=sqlite` env. variable to compile ChirpStack
with the SQLite database backend.
### Database migrations
To create a new database migration, execute:
```
make migration-generate NAME=test-migration
```
To apply migrations, execute:
```
make migration-run
```
To revert a migration, execute:
```
make migration-revert
```
By default the above commands will execute the migration commands using the
PostgresQL database backend. To execute migration commands for the SQLite
database backend, set the `DATABASE=sqlite` env. variable.
## License ## License
ChirpStack Network Server is distributed under the MIT license. See also ChirpStack Network Server is distributed under the MIT license. See also

2
api/rust/Cargo.toml vendored
View File

@ -12,7 +12,6 @@
default = ["api", "json"] default = ["api", "json"]
api = ["tonic/transport", "tonic-build/transport", "tokio"] api = ["tonic/transport", "tonic-build/transport", "tokio"]
json = ["pbjson", "pbjson-types", "serde"] json = ["pbjson", "pbjson-types", "serde"]
diesel = ["dep:diesel"]
internal = [] internal = []
[dependencies] [dependencies]
@ -29,7 +28,6 @@
pbjson = { version = "0.7", optional = true } pbjson = { version = "0.7", optional = true }
pbjson-types = { version = "0.7", optional = true } pbjson-types = { version = "0.7", optional = true }
serde = { version = "1.0", optional = true } serde = { version = "1.0", optional = true }
diesel = { version = "2.2", features = ["postgres_backend"], optional = true }
[build-dependencies] [build-dependencies]
tonic-build = { version = "0.12", features = [ tonic-build = { version = "0.12", features = [

View File

@ -2,13 +2,6 @@ include!(concat!(env!("OUT_DIR"), "/internal/internal.rs"));
#[cfg(feature = "json")] #[cfg(feature = "json")]
include!(concat!(env!("OUT_DIR"), "/internal/internal.serde.rs")); include!(concat!(env!("OUT_DIR"), "/internal/internal.serde.rs"));
#[cfg(feature = "diesel")]
use diesel::{backend::Backend, deserialize, serialize, sql_types::Binary};
#[cfg(feature = "diesel")]
use prost::Message;
#[cfg(feature = "diesel")]
use std::io::Cursor;
impl DeviceSession { impl DeviceSession {
pub fn get_a_f_cnt_down(&self) -> u32 { pub fn get_a_f_cnt_down(&self) -> u32 {
if self.mac_version().to_string().starts_with("1.0") { if self.mac_version().to_string().starts_with("1.0") {
@ -30,28 +23,3 @@ impl DeviceSession {
} }
} }
} }
#[cfg(feature = "diesel")]
impl<ST, DB> deserialize::FromSql<ST, DB> for DeviceSession
where
DB: Backend,
*const [u8]: deserialize::FromSql<ST, DB>,
{
fn from_sql(value: DB::RawValue<'_>) -> deserialize::Result<Self> {
let bytes = <Vec<u8> as deserialize::FromSql<ST, DB>>::from_sql(value)?;
Ok(DeviceSession::decode(&mut Cursor::new(bytes))?)
}
}
#[cfg(feature = "diesel")]
impl serialize::ToSql<Binary, diesel::pg::Pg> for DeviceSession
where
[u8]: serialize::ToSql<Binary, diesel::pg::Pg>,
{
fn to_sql(&self, out: &mut serialize::Output<'_, '_, diesel::pg::Pg>) -> serialize::Result {
<[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql(
&self.encode_to_vec(),
&mut out.reborrow(),
)
}
}

View File

@ -26,20 +26,16 @@
email_address = "0.2" email_address = "0.2"
diesel = { version = "2.2", features = [ diesel = { version = "2.2", features = [
"chrono", "chrono",
"uuid",
"serde_json",
"numeric", "numeric",
"64-column-tables", "64-column-tables",
"postgres_backend",
] } ] }
diesel_migrations = { version = "2.2" } diesel_migrations = { version = "2.2" }
diesel-async = { version = "0.5", features = [ diesel-async = { version = "0.5", features = [
"deadpool", "deadpool",
"postgres",
"async-connection-wrapper", "async-connection-wrapper",
] } ] }
tokio-postgres = "0.7" tokio-postgres = { version = "0.7", optional = true }
tokio-postgres-rustls = "0.12" tokio-postgres-rustls = { version = "0.12", optional = true }
bigdecimal = "0.4" bigdecimal = "0.4"
redis = { version = "0.26", features = ["tls-rustls", "tokio-rustls-comp"] } redis = { version = "0.26", features = ["tls-rustls", "tokio-rustls-comp"] }
deadpool-redis = { version = "0.16", features = ["cluster"] } deadpool-redis = { version = "0.16", features = ["cluster"] }
@ -53,11 +49,7 @@
], default-features = true } ], default-features = true }
# ChirpStack API definitions # ChirpStack API definitions
chirpstack_api = { path = "../api/rust", features = [ chirpstack_api = { path = "../api/rust", features = ["default", "internal"] }
"default",
"internal",
"diesel",
] }
lrwn = { path = "../lrwn", features = [ lrwn = { path = "../lrwn", features = [
"serde", "serde",
"diesel", "diesel",
@ -161,6 +153,7 @@
petgraph = "0.6" petgraph = "0.6"
prometheus-client = "0.22" prometheus-client = "0.22"
pin-project = "1.1" pin-project = "1.1"
scoped-futures = { version = "0.1", features = ["std"] }
signal-hook = "0.3" signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
@ -171,6 +164,23 @@
dotenv = "0.15" dotenv = "0.15"
[features] [features]
default = ["postgres"]
postgres = [
"tokio-postgres",
"tokio-postgres-rustls",
"diesel/postgres_backend",
"diesel/serde_json",
"diesel/uuid",
"diesel-async/postgres",
"lrwn/postgres",
]
sqlite = [
"diesel/sqlite",
"diesel/returning_clauses_for_sqlite_3_35",
"lrwn/sqlite",
"diesel-async/sync-connection-wrapper",
"diesel-async/sqlite",
]
test-all-integrations = [ test-all-integrations = [
"test-integration-amqp", "test-integration-amqp",
"test-integration-kafka", "test-integration-kafka",

View File

@ -1,20 +1,21 @@
.PHONY: dist .PHONY: dist
PKG_VERSION := $(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') PKG_VERSION := $(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
DATABASE ?= postgres
debug-amd64: debug-amd64:
cross build --target x86_64-unknown-linux-musl cross build --target x86_64-unknown-linux-musl --no-default-features --features="$(DATABASE)"
release-amd64: release-amd64:
cross build --target x86_64-unknown-linux-musl --release cross build --target x86_64-unknown-linux-musl --release --no-default-features --features="$(DATABASE)"
dist: dist:
# Keep these in this order, as aarch64 is based on Debian Buster (older), # Keep these in this order, as aarch64 is based on Debian Buster (older),
# the others on Bullseye. For some build scripts we want to build against # the others on Bullseye. For some build scripts we want to build against
# least recent LIBC. # least recent LIBC.
cross build --target aarch64-unknown-linux-musl --release cross build --target aarch64-unknown-linux-musl --release --no-default-features --features="$(DATABASE)"
cross build --target x86_64-unknown-linux-musl --release cross build --target x86_64-unknown-linux-musl --release --no-default-features --features="$(DATABASE)"
cross build --target armv7-unknown-linux-musleabihf --release cross build --target armv7-unknown-linux-musleabihf --release --no-default-features --features="$(DATABASE)"
cargo deb --target x86_64-unknown-linux-musl --no-build --no-strip cargo deb --target x86_64-unknown-linux-musl --no-build --no-strip
cargo deb --target armv7-unknown-linux-musleabihf --no-build --no-strip cargo deb --target armv7-unknown-linux-musleabihf --no-build --no-strip
@ -40,10 +41,38 @@ dist:
test: test:
cargo fmt --check cargo fmt --check
cargo clippy --no-deps cargo clippy --no-deps --no-default-features --features="$(DATABASE)"
TZ=UTC cargo test TZ=UTC cargo test --no-default-features --features="$(DATABASE)"
test-all: test-all:
cargo fmt --check cargo fmt --check
cargo clippy --no-deps cargo clippy --no-deps --no-default-features --features="$(DATABASE)"
TZ=UTC cargo test --features test-all-integrations TZ=UTC cargo test --no-default-features --features="$(DATABASE),test-all-integrations"
migration-generate:
ifeq ($(NAME),)
@echo "You must provide a NAME parameter, e.g. make migration-generate NAME=test-migration"
else
diesel --config-file diesel_$(DATABASE).toml migration --migration-dir migrations_$(DATABASE) generate $(NAME)
endif
migration-run: chirpstack_test.sqlite
ifeq ($(DATABASE),postgres)
diesel --config-file diesel_postgres.toml migration --migration-dir migrations_postgres run
endif
ifeq ($(DATABASE),sqlite)
DATABASE_URL="chirpstack_test.sqlite" diesel --config-file diesel_sqlite.toml migration --migration-dir migrations_sqlite run
sed -i 's/Timestamp/TimestamptzSqlite/g' src/storage/schema_sqlite.rs
endif
migration-revert: chirpstack_test.sqlite
ifeq ($(DATABASE),postgres)
diesel --config-file diesel_postgres.toml migration --migration-dir migrations_postgres revert
endif
ifeq ($(DATABASE),sqlite)
DATABASE_URL="chirpstack_test.sqlite" diesel --config-file diesel_sqlite.toml migration --migration-dir migrations_sqlite revert
sed -i 's/Timestamp/TimestamptzSqlite/g' src/storage/schema_sqlite.rs
endif
chirpstack_test.sqlite:
DATABASE_URL=chirpstack_test.sqlite diesel --config-file diesel_sqlite.toml setup --migration-dir migrations_sqlite

View File

@ -2,4 +2,4 @@
# see diesel.rs/guides/configuring-diesel-cli # see diesel.rs/guides/configuring-diesel-cli
[print_schema] [print_schema]
file = "src/storage/schema.rs" file = "src/storage/schema_postgres.rs"

View File

@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/storage/schema_sqlite.rs"

View File

@ -0,0 +1,18 @@
drop table relay_gateway;
drop table multicast_group_gateway;
drop table multicast_group_queue_item;
drop table multicast_group_device;
drop table multicast_group;
drop table device_queue_item;
drop table device_keys;
drop table device;
drop table device_profile;
drop table api_key;
drop table application_integration;
drop table application;
drop table gateway;
drop table tenant_user;
drop table tenant;
drop table "user";
drop table relay_device;
drop table device_profile_template;

View File

@ -0,0 +1,392 @@
-- user
create table "user" (
id text not null primary key,
external_id text null,
created_at datetime not null,
updated_at datetime not null,
is_admin boolean not null,
is_active boolean not null,
email text not null,
email_verified boolean not null,
password_hash varchar(200) not null,
note text not null
);
create unique index idx_user_email on "user"(email);
create unique index idx_user_external_id on "user"(external_id);
insert into "user" (
id,
created_at,
updated_at,
is_admin,
is_active,
email,
email_verified,
password_hash,
note
) values (
'05244f12-6daf-4e1f-8315-c66783a0ab56',
datetime('now'),
datetime('now'),
TRUE,
TRUE,
'admin',
FALSE,
'$pbkdf2-sha512$i=1,l=64$l8zGKtxRESq3PA2kFhHRWA$H3lGMxOt55wjwoc+myeOoABofJY9oDpldJa7fhqdjbh700V6FLPML75UmBOt9J5VFNjAL1AvqCozA1HJM0QVGA',
''
);
-- tenant
create table tenant (
id text not null primary key,
created_at datetime not null,
updated_at datetime not null,
name varchar(100) not null,
description text not null,
can_have_gateways boolean not null,
max_device_count integer not null,
max_gateway_count integer not null,
private_gateways_up boolean not null,
private_gateways_down boolean not null default FALSE,
tags text not null default '{}'
);
-- sqlite has advanced text search with https://www.sqlite.org/fts5.html
-- but looks like it is for a full table and not specific per column, to investigate
create index idx_tenant_name_trgm on "tenant"(name);
insert into "tenant" (
id,
created_at,
updated_at,
name,
description,
can_have_gateways,
max_device_count,
max_gateway_count,
private_gateways_up
) values (
'52f14cd4-c6f1-4fbd-8f87-4025e1d49242',
datetime('now'),
datetime('now'),
'ChirpStack',
'',
TRUE,
0,
0,
FALSE
);
-- tenant user
create table tenant_user (
tenant_id text not null references tenant on delete cascade,
user_id text not null references "user" on delete cascade,
created_at datetime not null,
updated_at datetime not null,
is_admin boolean not null,
is_device_admin boolean not null,
is_gateway_admin boolean not null,
primary key (tenant_id, user_id)
);
create index idx_tenant_user_user_id on tenant_user (user_id);
-- gateway
create table gateway (
gateway_id blob not null primary key,
tenant_id text not null references tenant on delete cascade,
created_at datetime not null,
updated_at datetime not null,
last_seen_at datetime,
name varchar(100) not null,
description text not null,
latitude double precision not null,
longitude double precision not null,
altitude real not null,
stats_interval_secs integer not null,
tls_certificate blob,
tags text not null,
properties text not null
);
create index idx_gateway_tenant_id on gateway (tenant_id);
create index idx_gateway_name_trgm on gateway (name);
create index idx_gateway_id_trgm on gateway (hex(gateway_id));
create index idx_gateway_tags on gateway (tags);
-- application
create table application (
id text not null primary key,
tenant_id text not null references tenant on delete cascade,
created_at datetime not null,
updated_at datetime not null,
name varchar(100) not null,
description text not null,
mqtt_tls_cert blob,
tags text not null default '{}'
);
create index idx_application_tenant_id on application (tenant_id);
create index idx_application_name_trgm on application (name);
-- application integration
create table application_integration (
application_id text not null references application on delete cascade,
kind varchar(20) not null,
created_at datetime not null,
updated_at datetime not null,
configuration text not null,
primary key (application_id, kind)
);
-- api-key
create table api_key (
id text not null primary key,
created_at datetime not null,
name varchar(100) not null,
is_admin boolean not null,
tenant_id text null references tenant on delete cascade
);
create index idx_api_key_tenant_id on api_key (tenant_id);
-- device-profile
create table device_profile (
id text not null primary key,
tenant_id text not null references tenant on delete cascade,
created_at datetime not null,
updated_at datetime not null,
name varchar(100) not null,
region varchar(10) not null,
mac_version varchar(10) not null,
reg_params_revision varchar(20) not null,
adr_algorithm_id varchar(100) not null,
payload_codec_runtime varchar(20) not null,
uplink_interval integer not null,
device_status_req_interval integer not null,
supports_otaa boolean not null,
supports_class_b boolean not null,
supports_class_c boolean not null,
class_b_timeout integer not null,
class_b_ping_slot_nb_k integer not null,
class_b_ping_slot_dr smallint not null,
class_b_ping_slot_freq bigint not null,
class_c_timeout integer not null,
abp_rx1_delay smallint not null,
abp_rx1_dr_offset smallint not null,
abp_rx2_dr smallint not null,
abp_rx2_freq bigint not null,
tags text not null,
payload_codec_script text not null default '',
flush_queue_on_activate boolean not null default FALSE,
description text not null default '',
measurements text not null default '{}',
auto_detect_measurements boolean not null default TRUE,
region_config_id varchar(100) null,
is_relay boolean not null default FALSE,
is_relay_ed boolean not null default FALSE,
relay_ed_relay_only boolean not null default FALSE,
relay_enabled boolean not null default FALSE,
relay_cad_periodicity smallint not null default 0,
relay_default_channel_index smallint not null default 0,
relay_second_channel_freq bigint not null default 0,
relay_second_channel_dr smallint not null default 0,
relay_second_channel_ack_offset smallint not null default 0,
relay_ed_activation_mode smallint not null default 0,
relay_ed_smart_enable_level smallint not null default 0,
relay_ed_back_off smallint not null default 0,
relay_ed_uplink_limit_bucket_size smallint not null default 0,
relay_ed_uplink_limit_reload_rate smallint not null default 0,
relay_join_req_limit_reload_rate smallint not null default 0,
relay_notify_limit_reload_rate smallint not null default 0,
relay_global_uplink_limit_reload_rate smallint not null default 0,
relay_overall_limit_reload_rate smallint not null default 0,
relay_join_req_limit_bucket_size smallint not null default 0,
relay_notify_limit_bucket_size smallint not null default 0,
relay_global_uplink_limit_bucket_size smallint not null default 0,
relay_overall_limit_bucket_size smallint not null default 0,
allow_roaming boolean not null default TRUE,
rx1_delay smallint not null default 0
);
create index idx_device_profile_tenant_id on device_profile (tenant_id);
create index idx_device_profile_name_trgm on device_profile (name);
create index idx_device_profile_tags on device_profile (tags);
-- device
create table device (
dev_eui blob not null primary key,
application_id text not null references application on delete cascade,
device_profile_id text not null references device_profile on delete cascade,
created_at datetime not null,
updated_at datetime not null,
last_seen_at datetime,
scheduler_run_after datetime,
name varchar(100) not null,
description text not null,
external_power_source boolean not null,
battery_level numeric(5, 2),
margin int,
dr smallint,
latitude double precision,
longitude double precision,
altitude real,
dev_addr blob,
enabled_class char(1) not null,
skip_fcnt_check boolean not null,
is_disabled boolean not null,
tags text not null,
variables text not null,
join_eui blob not null default x'00000000',
secondary_dev_addr blob,
device_session blob
);
create index idx_device_application_id on device (application_id);
create index idx_device_device_profile_id on device (device_profile_id);
create index idx_device_name_trgm on device (name);
create index idx_device_dev_eui_trgm on device (hex(dev_eui));
create index idx_device_dev_addr_trgm on device (hex(dev_addr));
create index idx_device_tags on device (tags);
create table device_keys (
dev_eui blob not null primary key references device on delete cascade,
created_at datetime not null,
updated_at datetime not null,
nwk_key blob not null,
app_key blob not null,
dev_nonces text not null,
join_nonce int not null
);
create table device_queue_item (
id text not null primary key,
dev_eui blob references device on delete cascade not null,
created_at datetime not null,
f_port smallint not null,
confirmed boolean not null,
data blob not null,
is_pending boolean not null,
f_cnt_down bigint null,
timeout_after datetime,
is_encrypted boolean default FALSE not null
);
create index idx_device_queue_item_dev_eui on device_queue_item (dev_eui);
create index idx_device_queue_item_created_at on device_queue_item (created_at);
create index idx_device_queue_item_timeout_after on device_queue_item (timeout_after);
-- multicast groups
create table multicast_group (
id text not null primary key,
application_id text not null references application on delete cascade,
created_at datetime not null,
updated_at datetime not null,
name varchar(100) not null,
region varchar(10) not null,
mc_addr blob not null,
mc_nwk_s_key blob not null,
mc_app_s_key blob not null,
f_cnt bigint not null,
group_type char(1) not null,
dr smallint not null,
frequency bigint not null,
class_b_ping_slot_nb_k smallint not null,
class_c_scheduling_type varchar(20) not null default 'delay'
);
create index idx_multicast_group_application_id on multicast_group (application_id);
create index idx_multicast_group_name_trgm on multicast_group (name);
create table multicast_group_device (
multicast_group_id text not null references multicast_group on delete cascade,
dev_eui blob not null references device on delete cascade,
created_at datetime not null,
primary key (multicast_group_id, dev_eui)
);
create table multicast_group_queue_item (
id text not null primary key,
created_at datetime not null,
scheduler_run_after datetime not null,
multicast_group_id text not null references multicast_group on delete cascade,
gateway_id blob not null references gateway on delete cascade,
f_cnt bigint not null,
f_port smallint not null,
data blob not null,
emit_at_time_since_gps_epoch bigint
);
create index idx_multicast_group_queue_item_multicast_group_id on multicast_group_queue_item (multicast_group_id);
create index idx_multicast_group_queue_item_scheduler_run_after on multicast_group_queue_item (scheduler_run_after);
create table device_profile_template (
id text not null primary key,
created_at datetime not null,
updated_at datetime not null,
name varchar(100) not null,
description text not null,
vendor varchar(100) not null,
firmware varchar(100) not null,
region varchar(10) not null,
mac_version varchar(10) not null,
reg_params_revision varchar(20) not null,
adr_algorithm_id varchar(100) not null,
payload_codec_runtime varchar(20) not null,
payload_codec_script text not null,
uplink_interval integer not null,
device_status_req_interval integer not null,
flush_queue_on_activate boolean not null,
supports_otaa boolean not null,
supports_class_b boolean not null,
supports_class_c boolean not null,
class_b_timeout integer not null,
class_b_ping_slot_nb_k integer not null,
class_b_ping_slot_dr smallint not null,
class_b_ping_slot_freq bigint not null,
class_c_timeout integer not null,
abp_rx1_delay smallint not null,
abp_rx1_dr_offset smallint not null,
abp_rx2_dr smallint not null,
abp_rx2_freq bigint not null,
tags text not null,
measurements text not null default '{}',
auto_detect_measurements boolean not null default TRUE
);
create table multicast_group_gateway (
multicast_group_id text not null references multicast_group on delete cascade,
gateway_id blob not null references gateway on delete cascade,
created_at datetime not null,
primary key (multicast_group_id, gateway_id)
);
create table relay_device (
relay_dev_eui blob not null references device on delete cascade,
dev_eui blob not null references device on delete cascade,
created_at datetime not null,
primary key (relay_dev_eui, dev_eui)
);
create index idx_tenant_tags on tenant (tags);
create index idx_application_tags on application (tags);
create index idx_device_dev_addr on device (dev_addr);
create index idx_device_secondary_dev_addr on device (secondary_dev_addr);
-- relay gateway
create table relay_gateway (
tenant_id text not null references tenant on delete cascade,
relay_id blob not null,
created_at datetime not null,
updated_at datetime not null,
last_seen_at datetime,
name varchar(100) not null,
description text not null,
stats_interval_secs integer not null,
region_config_id varchar(100) not null,
primary key (tenant_id, relay_id)
);

View File

@ -44,7 +44,7 @@ impl ApplicationService for Application {
.await?; .await?;
let a = application::Application { let a = application::Application {
tenant_id, tenant_id: tenant_id.into(),
name: req_app.name.clone(), name: req_app.name.clone(),
description: req_app.description.clone(), description: req_app.description.clone(),
tags: fields::KeyValue::new(req_app.tags.clone()), tags: fields::KeyValue::new(req_app.tags.clone()),
@ -119,7 +119,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update(application::Application { let _ = application::update(application::Application {
id: app_id, id: app_id.into(),
name: req_app.name.to_string(), name: req_app.name.to_string(),
description: req_app.description.to_string(), description: req_app.description.to_string(),
tags: fields::KeyValue::new(req_app.tags.clone()), tags: fields::KeyValue::new(req_app.tags.clone()),
@ -279,7 +279,7 @@ impl ApplicationService for Application {
.await?; .await?;
let i = application::Integration { let i = application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::Http, kind: application::IntegrationKind::Http,
configuration: application::IntegrationConfiguration::Http( configuration: application::IntegrationConfiguration::Http(
application::HttpConfiguration { application::HttpConfiguration {
@ -367,7 +367,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::Http, kind: application::IntegrationKind::Http,
configuration: application::IntegrationConfiguration::Http( configuration: application::IntegrationConfiguration::Http(
application::HttpConfiguration { application::HttpConfiguration {
@ -438,7 +438,7 @@ impl ApplicationService for Application {
.await?; .await?;
let i = application::Integration { let i = application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::InfluxDb, kind: application::IntegrationKind::InfluxDb,
configuration: application::IntegrationConfiguration::InfluxDb( configuration: application::IntegrationConfiguration::InfluxDb(
application::InfluxDbConfiguration { application::InfluxDbConfiguration {
@ -535,7 +535,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::InfluxDb, kind: application::IntegrationKind::InfluxDb,
configuration: application::IntegrationConfiguration::InfluxDb( configuration: application::IntegrationConfiguration::InfluxDb(
application::InfluxDbConfiguration { application::InfluxDbConfiguration {
@ -610,7 +610,7 @@ impl ApplicationService for Application {
.await?; .await?;
let i = application::Integration { let i = application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::ThingsBoard, kind: application::IntegrationKind::ThingsBoard,
configuration: application::IntegrationConfiguration::ThingsBoard( configuration: application::IntegrationConfiguration::ThingsBoard(
application::ThingsBoardConfiguration { application::ThingsBoardConfiguration {
@ -689,7 +689,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::ThingsBoard, kind: application::IntegrationKind::ThingsBoard,
configuration: application::IntegrationConfiguration::ThingsBoard( configuration: application::IntegrationConfiguration::ThingsBoard(
application::ThingsBoardConfiguration { application::ThingsBoardConfiguration {
@ -755,7 +755,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::create_integration(application::Integration { let _ = application::create_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::MyDevices, kind: application::IntegrationKind::MyDevices,
configuration: application::IntegrationConfiguration::MyDevices( configuration: application::IntegrationConfiguration::MyDevices(
application::MyDevicesConfiguration { application::MyDevicesConfiguration {
@ -832,7 +832,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::MyDevices, kind: application::IntegrationKind::MyDevices,
configuration: application::IntegrationConfiguration::MyDevices( configuration: application::IntegrationConfiguration::MyDevices(
application::MyDevicesConfiguration { application::MyDevicesConfiguration {
@ -907,7 +907,7 @@ impl ApplicationService for Application {
}; };
let _ = application::create_integration(application::Integration { let _ = application::create_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::LoraCloud, kind: application::IntegrationKind::LoraCloud,
configuration: application::IntegrationConfiguration::LoraCloud( configuration: application::IntegrationConfiguration::LoraCloud(
application::LoraCloudConfiguration { application::LoraCloudConfiguration {
@ -1032,7 +1032,7 @@ impl ApplicationService for Application {
}; };
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::LoraCloud, kind: application::IntegrationKind::LoraCloud,
configuration: application::IntegrationConfiguration::LoraCloud( configuration: application::IntegrationConfiguration::LoraCloud(
application::LoraCloudConfiguration { application::LoraCloudConfiguration {
@ -1119,7 +1119,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::create_integration(application::Integration { let _ = application::create_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::GcpPubSub, kind: application::IntegrationKind::GcpPubSub,
configuration: application::IntegrationConfiguration::GcpPubSub( configuration: application::IntegrationConfiguration::GcpPubSub(
application::GcpPubSubConfiguration { application::GcpPubSubConfiguration {
@ -1202,7 +1202,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::GcpPubSub, kind: application::IntegrationKind::GcpPubSub,
configuration: application::IntegrationConfiguration::GcpPubSub( configuration: application::IntegrationConfiguration::GcpPubSub(
application::GcpPubSubConfiguration { application::GcpPubSubConfiguration {
@ -1271,7 +1271,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::create_integration(application::Integration { let _ = application::create_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::AwsSns, kind: application::IntegrationKind::AwsSns,
configuration: application::IntegrationConfiguration::AwsSns( configuration: application::IntegrationConfiguration::AwsSns(
application::AwsSnsConfiguration { application::AwsSnsConfiguration {
@ -1354,7 +1354,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::AwsSns, kind: application::IntegrationKind::AwsSns,
configuration: application::IntegrationConfiguration::AwsSns( configuration: application::IntegrationConfiguration::AwsSns(
application::AwsSnsConfiguration { application::AwsSnsConfiguration {
@ -1424,7 +1424,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::create_integration(application::Integration { let _ = application::create_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::AzureServiceBus, kind: application::IntegrationKind::AzureServiceBus,
configuration: application::IntegrationConfiguration::AzureServiceBus( configuration: application::IntegrationConfiguration::AzureServiceBus(
application::AzureServiceBusConfiguration { application::AzureServiceBusConfiguration {
@ -1506,7 +1506,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::AzureServiceBus, kind: application::IntegrationKind::AzureServiceBus,
configuration: application::IntegrationConfiguration::AzureServiceBus( configuration: application::IntegrationConfiguration::AzureServiceBus(
application::AzureServiceBusConfiguration { application::AzureServiceBusConfiguration {
@ -1574,7 +1574,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::create_integration(application::Integration { let _ = application::create_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::PilotThings, kind: application::IntegrationKind::PilotThings,
configuration: application::IntegrationConfiguration::PilotThings( configuration: application::IntegrationConfiguration::PilotThings(
application::PilotThingsConfiguration { application::PilotThingsConfiguration {
@ -1653,7 +1653,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::PilotThings, kind: application::IntegrationKind::PilotThings,
configuration: application::IntegrationConfiguration::PilotThings( configuration: application::IntegrationConfiguration::PilotThings(
application::PilotThingsConfiguration { application::PilotThingsConfiguration {
@ -1730,7 +1730,7 @@ impl ApplicationService for Application {
} }
let _ = application::create_integration(application::Integration { let _ = application::create_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::Ifttt, kind: application::IntegrationKind::Ifttt,
configuration: application::IntegrationConfiguration::Ifttt( configuration: application::IntegrationConfiguration::Ifttt(
application::IftttConfiguration { application::IftttConfiguration {
@ -1814,7 +1814,7 @@ impl ApplicationService for Application {
.await?; .await?;
let _ = application::update_integration(application::Integration { let _ = application::update_integration(application::Integration {
application_id: app_id, application_id: app_id.into(),
kind: application::IntegrationKind::Ifttt, kind: application::IntegrationKind::Ifttt,
configuration: application::IntegrationConfiguration::Ifttt( configuration: application::IntegrationConfiguration::Ifttt(
application::IftttConfiguration { application::IftttConfiguration {
@ -1945,7 +1945,9 @@ pub mod test {
}), }),
}; };
let mut create_req = Request::new(create_req); let mut create_req = Request::new(create_req);
create_req.extensions_mut().insert(AuthID::User(u.id)); create_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let create_resp = service.create(create_req).await.unwrap(); let create_resp = service.create(create_req).await.unwrap();
let create_resp = create_resp.get_ref(); let create_resp = create_resp.get_ref();
@ -1954,7 +1956,9 @@ pub mod test {
id: create_resp.id.clone(), id: create_resp.id.clone(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::Application { Some(api::Application {
@ -1976,7 +1980,9 @@ pub mod test {
}), }),
}; };
let mut up_req = Request::new(up_req); let mut up_req = Request::new(up_req);
up_req.extensions_mut().insert(AuthID::User(u.id)); up_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.update(up_req).await.unwrap(); let _ = service.update(up_req).await.unwrap();
//get //get
@ -1984,7 +1990,9 @@ pub mod test {
id: create_resp.id.clone(), id: create_resp.id.clone(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::Application { Some(api::Application {
@ -2004,7 +2012,9 @@ pub mod test {
offset: 0, offset: 0,
}; };
let mut list_req = Request::new(list_req); let mut list_req = Request::new(list_req);
list_req.extensions_mut().insert(AuthID::User(u.id)); list_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let list_resp = service.list(list_req).await.unwrap(); let list_resp = service.list(list_req).await.unwrap();
assert_eq!(1, list_resp.get_ref().total_count); assert_eq!(1, list_resp.get_ref().total_count);
assert_eq!(1, list_resp.get_ref().result.len()); assert_eq!(1, list_resp.get_ref().result.len());
@ -2014,14 +2024,18 @@ pub mod test {
id: create_resp.id.clone(), id: create_resp.id.clone(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.delete(del_req).await.unwrap(); let _ = service.delete(del_req).await.unwrap();
let del_req = api::DeleteApplicationRequest { let del_req = api::DeleteApplicationRequest {
id: create_resp.id.clone(), id: create_resp.id.clone(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let del_resp = service.delete(del_req).await; let del_resp = service.delete(del_req).await;
assert!(del_resp.is_err()); assert!(del_resp.is_err());
} }

File diff suppressed because it is too large Load Diff

View File

@ -64,8 +64,8 @@ impl DeviceService for Device {
let d = device::Device { let d = device::Device {
dev_eui, dev_eui,
application_id: app_id, application_id: app_id.into(),
device_profile_id: dp_id, device_profile_id: dp_id.into(),
name: req_d.name.clone(), name: req_d.name.clone(),
description: req_d.description.clone(), description: req_d.description.clone(),
skip_fcnt_check: req_d.skip_fcnt_check, skip_fcnt_check: req_d.skip_fcnt_check,
@ -191,8 +191,8 @@ impl DeviceService for Device {
// update // update
let _ = device::update(device::Device { let _ = device::update(device::Device {
dev_eui, dev_eui,
application_id: app_id, application_id: app_id.into(),
device_profile_id: dp_id, device_profile_id: dp_id.into(),
name: req_d.name.clone(), name: req_d.name.clone(),
description: req_d.description.clone(), description: req_d.description.clone(),
skip_fcnt_check: req_d.skip_fcnt_check, skip_fcnt_check: req_d.skip_fcnt_check,
@ -533,7 +533,7 @@ impl DeviceService for Device {
dp.reset_session_to_boot_params(&mut ds); dp.reset_session_to_boot_params(&mut ds);
let mut device_changeset = device::DeviceChangeset { let mut device_changeset = device::DeviceChangeset {
device_session: Some(Some(ds)), device_session: Some(Some(ds.into())),
dev_addr: Some(Some(dev_addr)), dev_addr: Some(Some(dev_addr)),
secondary_dev_addr: Some(None), secondary_dev_addr: Some(None),
..Default::default() ..Default::default()
@ -1085,7 +1085,7 @@ impl DeviceService for Device {
} }
let qi = device_queue::DeviceQueueItem { let qi = device_queue::DeviceQueueItem {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
dev_eui, dev_eui,
f_port: req_qi.f_port as i16, f_port: req_qi.f_port as i16,
confirmed: req_qi.confirmed, confirmed: req_qi.confirmed,
@ -1539,11 +1539,14 @@ pub mod test {
dev.dev_eui, dev.dev_eui,
&device::DeviceChangeset { &device::DeviceChangeset {
dev_addr: Some(Some(DevAddr::from_be_bytes([1, 2, 3, 4]))), dev_addr: Some(Some(DevAddr::from_be_bytes([1, 2, 3, 4]))),
device_session: Some(Some(internal::DeviceSession { device_session: Some(Some(
dev_addr: vec![1, 2, 3, 4], internal::DeviceSession {
js_session_key_id: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4],
..Default::default() js_session_key_id: vec![8, 7, 6, 5, 4, 3, 2, 1],
})), ..Default::default()
}
.into(),
)),
..Default::default() ..Default::default()
}, },
) )
@ -1568,14 +1571,17 @@ pub mod test {
device::partial_update( device::partial_update(
dev.dev_eui, dev.dev_eui,
&device::DeviceChangeset { &device::DeviceChangeset {
device_session: Some(Some(internal::DeviceSession { device_session: Some(Some(
dev_addr: vec![1, 2, 3, 4], internal::DeviceSession {
app_s_key: Some(common::KeyEnvelope { dev_addr: vec![1, 2, 3, 4],
kek_label: "test-key".into(), app_s_key: Some(common::KeyEnvelope {
aes_key: vec![8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1], kek_label: "test-key".into(),
}), aes_key: vec![8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1],
..Default::default() }),
})), ..Default::default()
}
.into(),
)),
..Default::default() ..Default::default()
}, },
) )

View File

@ -45,7 +45,7 @@ impl DeviceProfileService for DeviceProfile {
.await?; .await?;
let mut dp = device_profile::DeviceProfile { let mut dp = device_profile::DeviceProfile {
tenant_id, tenant_id: tenant_id.into(),
name: req_dp.name.clone(), name: req_dp.name.clone(),
description: req_dp.description.clone(), description: req_dp.description.clone(),
region: req_dp.region().from_proto(), region: req_dp.region().from_proto(),
@ -247,7 +247,7 @@ impl DeviceProfileService for DeviceProfile {
// update // update
let _ = device_profile::update(device_profile::DeviceProfile { let _ = device_profile::update(device_profile::DeviceProfile {
id: dp_id, id: dp_id.into(),
name: req_dp.name.clone(), name: req_dp.name.clone(),
description: req_dp.description.clone(), description: req_dp.description.clone(),
region: req_dp.region().from_proto(), region: req_dp.region().from_proto(),

View File

@ -58,7 +58,7 @@ impl GatewayService for Gateway {
let gw = gateway::Gateway { let gw = gateway::Gateway {
gateway_id: EUI64::from_str(&req_gw.gateway_id).map_err(|e| e.status())?, gateway_id: EUI64::from_str(&req_gw.gateway_id).map_err(|e| e.status())?,
tenant_id, tenant_id: tenant_id.into(),
name: req_gw.name.clone(), name: req_gw.name.clone(),
description: req_gw.description.clone(), description: req_gw.description.clone(),
latitude: lat, latitude: lat,
@ -851,8 +851,8 @@ impl GatewayService for Gateway {
.await?; .await?;
let _ = gateway::update_relay_gateway(gateway::RelayGateway { let _ = gateway::update_relay_gateway(gateway::RelayGateway {
tenant_id,
relay_id, relay_id,
tenant_id: tenant_id.into(),
name: req_relay.name.clone(), name: req_relay.name.clone(),
description: req_relay.description.clone(), description: req_relay.description.clone(),
stats_interval_secs: req_relay.stats_interval as i32, stats_interval_secs: req_relay.stats_interval as i32,
@ -1028,7 +1028,9 @@ pub mod test {
}), }),
}; };
let mut create_req = Request::new(create_req); let mut create_req = Request::new(create_req);
create_req.extensions_mut().insert(AuthID::User(u.id)); create_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.create(create_req).await.unwrap(); let _ = service.create(create_req).await.unwrap();
// get // get
@ -1036,7 +1038,9 @@ pub mod test {
gateway_id: "0102030405060708".into(), gateway_id: "0102030405060708".into(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::Gateway { Some(api::Gateway {
@ -1070,7 +1074,9 @@ pub mod test {
}), }),
}; };
let mut up_req = Request::new(up_req); let mut up_req = Request::new(up_req);
up_req.extensions_mut().insert(AuthID::User(u.id)); up_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.update(up_req).await.unwrap(); let _ = service.update(up_req).await.unwrap();
// get // get
@ -1078,7 +1084,9 @@ pub mod test {
gateway_id: "0102030405060708".into(), gateway_id: "0102030405060708".into(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::Gateway { Some(api::Gateway {
@ -1105,7 +1113,9 @@ pub mod test {
..Default::default() ..Default::default()
}; };
let mut list_req = Request::new(list_req); let mut list_req = Request::new(list_req);
list_req.extensions_mut().insert(AuthID::User(u.id)); list_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let list_resp = service.list(list_req).await.unwrap(); let list_resp = service.list(list_req).await.unwrap();
assert_eq!(1, list_resp.get_ref().total_count); assert_eq!(1, list_resp.get_ref().total_count);
assert_eq!(1, list_resp.get_ref().result.len()); assert_eq!(1, list_resp.get_ref().result.len());
@ -1115,14 +1125,18 @@ pub mod test {
gateway_id: "0102030405060708".into(), gateway_id: "0102030405060708".into(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.delete(del_req).await.unwrap(); let _ = service.delete(del_req).await.unwrap();
let del_req = api::DeleteGatewayRequest { let del_req = api::DeleteGatewayRequest {
gateway_id: "0102030405060708".into(), gateway_id: "0102030405060708".into(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let del_resp = service.delete(del_req).await; let del_resp = service.delete(del_req).await;
assert!(del_resp.is_err()); assert!(del_resp.is_err());
} }
@ -1198,7 +1212,9 @@ pub mod test {
aggregation: common::Aggregation::Day.into(), aggregation: common::Aggregation::Day.into(),
}; };
let mut stats_req = Request::new(stats_req); let mut stats_req = Request::new(stats_req);
stats_req.extensions_mut().insert(AuthID::User(u.id)); stats_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let stats_resp = service.get_metrics(stats_req).await.unwrap(); let stats_resp = service.get_metrics(stats_req).await.unwrap();
let stats_resp = stats_resp.get_ref(); let stats_resp = stats_resp.get_ref();
assert_eq!( assert_eq!(
@ -1289,7 +1305,7 @@ pub mod test {
end: Some(now_st.into()), end: Some(now_st.into()),
}; };
let mut stats_req = Request::new(stats_req); let mut stats_req = Request::new(stats_req);
stats_req.extensions_mut().insert(AuthID::User(u.id)); stats_req.extensions_mut().insert(AuthID::User(u.id.into()));
let stats_resp = service.get_duty_cycle_metrics(stats_req).await.unwrap(); let stats_resp = service.get_duty_cycle_metrics(stats_req).await.unwrap();
let stats_resp = stats_resp.get_ref(); let stats_resp = stats_resp.get_ref();
assert_eq!( assert_eq!(
@ -1363,7 +1379,9 @@ pub mod test {
relay_id: "01020304".into(), relay_id: "01020304".into(),
}; };
let mut get_relay_req = Request::new(get_relay_req); let mut get_relay_req = Request::new(get_relay_req);
get_relay_req.extensions_mut().insert(AuthID::User(u.id)); get_relay_req
.extensions_mut()
.insert(AuthID::User(u.id.into()));
let get_relay_resp = service.get_relay_gateway(get_relay_req).await.unwrap(); let get_relay_resp = service.get_relay_gateway(get_relay_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::RelayGateway { Some(api::RelayGateway {
@ -1389,7 +1407,9 @@ pub mod test {
}), }),
}; };
let mut up_relay_req = Request::new(up_relay_req); let mut up_relay_req = Request::new(up_relay_req);
up_relay_req.extensions_mut().insert(AuthID::User(u.id)); up_relay_req
.extensions_mut()
.insert(AuthID::User(u.id.into()));
let _ = service.update_relay_gateway(up_relay_req).await.unwrap(); let _ = service.update_relay_gateway(up_relay_req).await.unwrap();
// get relay gateway // get relay gateway
@ -1398,7 +1418,9 @@ pub mod test {
relay_id: "01020304".into(), relay_id: "01020304".into(),
}; };
let mut get_relay_req = Request::new(get_relay_req); let mut get_relay_req = Request::new(get_relay_req);
get_relay_req.extensions_mut().insert(AuthID::User(u.id)); get_relay_req
.extensions_mut()
.insert(AuthID::User(u.id.into()));
let get_relay_resp = service.get_relay_gateway(get_relay_req).await.unwrap(); let get_relay_resp = service.get_relay_gateway(get_relay_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::RelayGateway { Some(api::RelayGateway {
@ -1419,7 +1441,9 @@ pub mod test {
offset: 0, offset: 0,
}; };
let mut list_relay_req = Request::new(list_relay_req); let mut list_relay_req = Request::new(list_relay_req);
list_relay_req.extensions_mut().insert(AuthID::User(u.id)); list_relay_req
.extensions_mut()
.insert(AuthID::User(u.id.into()));
let list_relay_resp = service.list_relay_gateways(list_relay_req).await.unwrap(); let list_relay_resp = service.list_relay_gateways(list_relay_req).await.unwrap();
assert_eq!(1, list_relay_resp.get_ref().total_count); assert_eq!(1, list_relay_resp.get_ref().total_count);
assert_eq!(1, list_relay_resp.get_ref().result.len()); assert_eq!(1, list_relay_resp.get_ref().result.len());
@ -1430,7 +1454,9 @@ pub mod test {
relay_id: "01020304".into(), relay_id: "01020304".into(),
}; };
let mut del_relay_req = Request::new(del_relay_req); let mut del_relay_req = Request::new(del_relay_req);
del_relay_req.extensions_mut().insert(AuthID::User(u.id)); del_relay_req
.extensions_mut()
.insert(AuthID::User(u.id.into()));
let del_relay_resp = service.delete_relay_gateway(del_relay_req).await; let del_relay_resp = service.delete_relay_gateway(del_relay_req).await;
assert!(del_relay_resp.is_ok()); assert!(del_relay_resp.is_ok());
@ -1439,7 +1465,9 @@ pub mod test {
relay_id: "01020304".into(), relay_id: "01020304".into(),
}; };
let mut del_relay_req = Request::new(del_relay_req); let mut del_relay_req = Request::new(del_relay_req);
del_relay_req.extensions_mut().insert(AuthID::User(u.id)); del_relay_req
.extensions_mut()
.insert(AuthID::User(u.id.into()));
let del_relay_resp = service.delete_relay_gateway(del_relay_req).await; let del_relay_resp = service.delete_relay_gateway(del_relay_req).await;
assert!(del_relay_resp.is_err()); assert!(del_relay_resp.is_err());
} }

View File

@ -287,7 +287,11 @@ impl InternalService for Internal {
let tenant_id = if req_key.tenant_id.is_empty() { let tenant_id = if req_key.tenant_id.is_empty() {
None None
} else { } else {
Some(Uuid::from_str(&req_key.tenant_id).map_err(|e| e.status())?) Some(
Uuid::from_str(&req_key.tenant_id)
.map_err(|e| e.status())?
.into(),
)
}; };
if req_key.is_admin && tenant_id.is_some() { if req_key.is_admin && tenant_id.is_some() {
@ -312,7 +316,7 @@ impl InternalService for Internal {
let ak = api_key::ApiKey { let ak = api_key::ApiKey {
name: req_key.name.clone(), name: req_key.name.clone(),
is_admin: req_key.is_admin, is_admin: req_key.is_admin,
tenant_id, tenant_id: tenant_id.map(|u| u.into()),
..Default::default() ..Default::default()
}; };

View File

@ -47,7 +47,7 @@ impl MulticastGroupService for MulticastGroup {
.await?; .await?;
let mg = multicast::MulticastGroup { let mg = multicast::MulticastGroup {
application_id: app_id, application_id: app_id.into(),
name: req_mg.name.clone(), name: req_mg.name.clone(),
region: req_mg.region().from_proto(), region: req_mg.region().from_proto(),
mc_addr: DevAddr::from_str(&req_mg.mc_addr).map_err(|e| e.status())?, mc_addr: DevAddr::from_str(&req_mg.mc_addr).map_err(|e| e.status())?,
@ -154,7 +154,7 @@ impl MulticastGroupService for MulticastGroup {
.await?; .await?;
let _ = multicast::update(multicast::MulticastGroup { let _ = multicast::update(multicast::MulticastGroup {
id: mg_id, id: mg_id.into(),
name: req_mg.name.clone(), name: req_mg.name.clone(),
region: req_mg.region().from_proto(), region: req_mg.region().from_proto(),
mc_addr: DevAddr::from_str(&req_mg.mc_addr).map_err(|e| e.status())?, mc_addr: DevAddr::from_str(&req_mg.mc_addr).map_err(|e| e.status())?,
@ -408,7 +408,7 @@ impl MulticastGroupService for MulticastGroup {
.await?; .await?;
let f_cnt = downlink::multicast::enqueue(multicast::MulticastGroupQueueItem { let f_cnt = downlink::multicast::enqueue(multicast::MulticastGroupQueueItem {
multicast_group_id: mg_id, multicast_group_id: mg_id.into(),
f_port: req_enq.f_port as i16, f_port: req_enq.f_port as i16,
data: req_enq.data.clone(), data: req_enq.data.clone(),
..Default::default() ..Default::default()

View File

@ -122,7 +122,7 @@ impl TenantService for Tenant {
// update // update
let _ = tenant::update(tenant::Tenant { let _ = tenant::update(tenant::Tenant {
id: tenant_id, id: tenant_id.into(),
name: req_tenant.name.clone(), name: req_tenant.name.clone(),
description: req_tenant.description.clone(), description: req_tenant.description.clone(),
can_have_gateways: req_tenant.can_have_gateways, can_have_gateways: req_tenant.can_have_gateways,
@ -190,7 +190,7 @@ impl TenantService for Tenant {
let u = user::get(id).await.map_err(|e| e.status())?; let u = user::get(id).await.map_err(|e| e.status())?;
if !u.is_admin { if !u.is_admin {
filters.user_id = Some(u.id); filters.user_id = Some(u.id.into());
} }
} }
AuthID::Key(_) => { AuthID::Key(_) => {
@ -258,8 +258,8 @@ impl TenantService for Tenant {
.await?; .await?;
let _ = tenant::add_user(tenant::TenantUser { let _ = tenant::add_user(tenant::TenantUser {
tenant_id, tenant_id: tenant_id.into(),
user_id, user_id: user_id.into(),
is_admin: req_user.is_admin, is_admin: req_user.is_admin,
is_device_admin: req_user.is_device_admin, is_device_admin: req_user.is_device_admin,
is_gateway_admin: req_user.is_gateway_admin, is_gateway_admin: req_user.is_gateway_admin,
@ -342,8 +342,8 @@ impl TenantService for Tenant {
.await?; .await?;
tenant::update_user(tenant::TenantUser { tenant::update_user(tenant::TenantUser {
tenant_id, tenant_id: tenant_id.into(),
user_id, user_id: user_id.into(),
is_admin: req_user.is_admin, is_admin: req_user.is_admin,
is_device_admin: req_user.is_device_admin, is_device_admin: req_user.is_device_admin,
is_gateway_admin: req_user.is_gateway_admin, is_gateway_admin: req_user.is_gateway_admin,
@ -482,7 +482,9 @@ pub mod test {
}), }),
}; };
let mut create_req = Request::new(create_req); let mut create_req = Request::new(create_req);
create_req.extensions_mut().insert(AuthID::User(u.id)); create_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let create_resp = service.create(create_req).await.unwrap(); let create_resp = service.create(create_req).await.unwrap();
// get // get
@ -490,7 +492,9 @@ pub mod test {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::Tenant { Some(api::Tenant {
@ -518,7 +522,9 @@ pub mod test {
}), }),
}; };
let mut up_req = Request::new(up_req); let mut up_req = Request::new(up_req);
up_req.extensions_mut().insert(AuthID::User(u.id)); up_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.update(up_req).await.unwrap(); let _ = service.update(up_req).await.unwrap();
// get // get
@ -526,7 +532,9 @@ pub mod test {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::Tenant { Some(api::Tenant {
@ -549,7 +557,9 @@ pub mod test {
user_id: "".into(), user_id: "".into(),
}; };
let mut list_req = Request::new(list_req); let mut list_req = Request::new(list_req);
list_req.extensions_mut().insert(AuthID::User(u.id)); list_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let list_resp = service.list(list_req).await.unwrap(); let list_resp = service.list(list_req).await.unwrap();
assert_eq!(1, list_resp.get_ref().total_count); assert_eq!(1, list_resp.get_ref().total_count);
assert_eq!(1, list_resp.get_ref().result.len()); assert_eq!(1, list_resp.get_ref().result.len());
@ -559,14 +569,18 @@ pub mod test {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.delete(del_req).await.unwrap(); let _ = service.delete(del_req).await.unwrap();
let del_req = api::DeleteTenantRequest { let del_req = api::DeleteTenantRequest {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let del_resp = service.delete(del_req).await; let del_resp = service.delete(del_req).await;
assert!(del_resp.is_err()); assert!(del_resp.is_err());
} }

View File

@ -64,8 +64,8 @@ impl UserService for User {
let tenant_id = Uuid::from_str(&tu.tenant_id).map_err(|e| e.status())?; let tenant_id = Uuid::from_str(&tu.tenant_id).map_err(|e| e.status())?;
tenant::add_user(tenant::TenantUser { tenant::add_user(tenant::TenantUser {
tenant_id, tenant_id: tenant_id.into(),
user_id: u.id, user_id: u.id.into(),
is_admin: tu.is_admin, is_admin: tu.is_admin,
is_device_admin: tu.is_device_admin, is_device_admin: tu.is_device_admin,
is_gateway_admin: tu.is_gateway_admin, is_gateway_admin: tu.is_gateway_admin,
@ -138,7 +138,7 @@ impl UserService for User {
// update // update
let _ = user::update(user::User { let _ = user::update(user::User {
id: user_id, id: user_id.into(),
is_admin: req_user.is_admin, is_admin: req_user.is_admin,
is_active: req_user.is_active, is_active: req_user.is_active,
email: req_user.email.clone(), email: req_user.email.clone(),
@ -292,7 +292,9 @@ pub mod test {
}), }),
}; };
let mut create_req = Request::new(create_req); let mut create_req = Request::new(create_req);
create_req.extensions_mut().insert(AuthID::User(u.id)); create_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let create_resp = service.create(create_req).await.unwrap(); let create_resp = service.create(create_req).await.unwrap();
// get // get
@ -300,7 +302,9 @@ pub mod test {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::User { Some(api::User {
@ -326,7 +330,9 @@ pub mod test {
}), }),
}; };
let mut up_req = Request::new(up_req); let mut up_req = Request::new(up_req);
up_req.extensions_mut().insert(AuthID::User(u.id)); up_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.update(up_req).await.unwrap(); let _ = service.update(up_req).await.unwrap();
// get // get
@ -334,7 +340,9 @@ pub mod test {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut get_req = Request::new(get_req); let mut get_req = Request::new(get_req);
get_req.extensions_mut().insert(AuthID::User(u.id)); get_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let get_resp = service.get(get_req).await.unwrap(); let get_resp = service.get(get_req).await.unwrap();
assert_eq!( assert_eq!(
Some(api::User { Some(api::User {
@ -354,7 +362,9 @@ pub mod test {
password: "newpassword".into(), password: "newpassword".into(),
}; };
let mut up_req = Request::new(up_req); let mut up_req = Request::new(up_req);
up_req.extensions_mut().insert(AuthID::User(u.id)); up_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.update_password(up_req).await.unwrap(); let _ = service.update_password(up_req).await.unwrap();
// list // list
@ -363,7 +373,9 @@ pub mod test {
limit: 10, limit: 10,
}; };
let mut list_req = Request::new(list_req); let mut list_req = Request::new(list_req);
list_req.extensions_mut().insert(AuthID::User(u.id)); list_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let list_resp = service.list(list_req).await.unwrap(); let list_resp = service.list(list_req).await.unwrap();
// * Admin from migrations // * Admin from migrations
// * User that we created for auth // * User that we created for auth
@ -376,14 +388,18 @@ pub mod test {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let _ = service.delete(del_req).await.unwrap(); let _ = service.delete(del_req).await.unwrap();
let del_req = api::DeleteUserRequest { let del_req = api::DeleteUserRequest {
id: create_resp.get_ref().id.clone(), id: create_resp.get_ref().id.clone(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let del_resp = service.delete(del_req).await; let del_resp = service.delete(del_req).await;
assert!(del_resp.is_err()); assert!(del_resp.is_err());
@ -391,7 +407,9 @@ pub mod test {
id: u.id.to_string(), id: u.id.to_string(),
}; };
let mut del_req = Request::new(del_req); let mut del_req = Request::new(del_req);
del_req.extensions_mut().insert(AuthID::User(u.id)); del_req
.extensions_mut()
.insert(AuthID::User(Into::<uuid::Uuid>::into(u.id).clone()));
let del_resp = service.delete(del_req).await; let del_resp = service.delete(del_req).await;
assert!(del_resp.is_err()); assert!(del_resp.is_err());
} }

View File

@ -3,7 +3,8 @@ use handlebars::{no_escape, Handlebars};
use super::super::config; use super::super::config;
pub fn run() { pub fn run() {
let template = r#" let template = vec![
r#"
# Logging configuration # Logging configuration
[logging] [logging]
@ -20,7 +21,9 @@ pub fn run() {
# Log as JSON. # Log as JSON.
json={{ logging.json }} json={{ logging.json }}
"#,
#[cfg(feature = "postgres")]
r#"
# PostgreSQL configuration. # PostgreSQL configuration.
[postgresql] [postgresql]
@ -46,8 +49,36 @@ pub fn run() {
# the server-certificate is not signed by a CA in the platform certificate # the server-certificate is not signed by a CA in the platform certificate
# store. # store.
ca_cert="{{ postgresql.ca_cert }}" ca_cert="{{ postgresql.ca_cert }}"
"#,
#[cfg(feature = "sqlite")]
r#"
# SQLite configuration.
[sqlite]
# Sqlite DB path.
#
# Format example: sqlite:///<DATABASE>.
#
path="{{ sqlite.path }}"
# Max open connections.
#
# This sets the max. number of open connections that are allowed in the
# SQLite connection pool.
max_open_connections={{ sqlite.max_open_connections }}
# PRAGMAs.
#
# This configures the list of PRAGMAs that are executed to prepare the
# SQLite library. For a full list of available PRAGMAs see:
# https://www.sqlite.org/pragma.html
pragmas=[
{{#each sqlite.pragmas}}
"{{this}}",
{{/each}}
]
"#,
r#"
# Redis configuration. # Redis configuration.
[redis] [redis]
@ -944,6 +975,7 @@ pub fn run() {
kek="{{ this.kek }}" kek="{{ this.kek }}"
{{/each}} {{/each}}
# UI configuration. # UI configuration.
[ui] [ui]
# Tileserver URL. # Tileserver URL.
@ -958,14 +990,14 @@ pub fn run() {
# default tileserver_url (OSM). If you configure a different tile-server, you # default tileserver_url (OSM). If you configure a different tile-server, you
# might need to update the map_attribution. # might need to update the map_attribution.
map_attribution="{{ui.map_attribution}}" map_attribution="{{ui.map_attribution}}"
"#; "#].join("\n");
let mut reg = Handlebars::new(); let mut reg = Handlebars::new();
reg.register_escape_fn(no_escape); reg.register_escape_fn(no_escape);
let conf = config::get(); let conf = config::get();
println!( println!(
"{}", "{}",
reg.render_template(template, &conf) reg.render_template(&template, &conf)
.expect("render configfile error") .expect("render configfile error")
); );
} }

View File

@ -43,7 +43,7 @@ pub async fn run() -> Result<()> {
*dev_eui, *dev_eui,
&storage::device::DeviceChangeset { &storage::device::DeviceChangeset {
dev_addr: Some(Some(DevAddr::from_slice(&ds.dev_addr)?)), dev_addr: Some(Some(DevAddr::from_slice(&ds.dev_addr)?)),
device_session: Some(Some(ds)), device_session: Some(Some(ds.into())),
..Default::default() ..Default::default()
}, },
) )

View File

@ -5,8 +5,11 @@ use std::str::FromStr;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::backend::Backend; use diesel::backend::Backend;
#[cfg(feature = "postgres")]
use diesel::pg::Pg; use diesel::pg::Pg;
use diesel::sql_types::Text; use diesel::sql_types::Text;
#[cfg(feature = "sqlite")]
use diesel::sqlite::Sqlite;
use diesel::{deserialize, serialize}; use diesel::{deserialize, serialize};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -40,6 +43,7 @@ where
} }
} }
#[cfg(feature = "postgres")]
impl serialize::ToSql<Text, Pg> for Codec impl serialize::ToSql<Text, Pg> for Codec
where where
str: serialize::ToSql<Text, Pg>, str: serialize::ToSql<Text, Pg>,
@ -49,6 +53,14 @@ where
} }
} }
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, Sqlite> for Codec {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Sqlite>) -> serialize::Result {
out.set_value(self.to_string());
Ok(serialize::IsNull::No)
}
}
impl FromStr for Codec { impl FromStr for Codec {
type Err = anyhow::Error; type Err = anyhow::Error;

View File

@ -19,6 +19,7 @@ pub struct Configuration {
pub logging: Logging, pub logging: Logging,
pub postgresql: Postgresql, pub postgresql: Postgresql,
pub redis: Redis, pub redis: Redis,
pub sqlite: Sqlite,
pub api: Api, pub api: Api,
pub gateway: Gateway, pub gateway: Gateway,
pub network: Network, pub network: Network,
@ -90,6 +91,29 @@ impl Default for Redis {
} }
} }
#[derive(Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Sqlite {
pub path: String,
pub pragmas: Vec<String>,
pub max_open_connections: u32,
}
impl Default for Sqlite {
fn default() -> Self {
Sqlite {
path: "sqlite://chirpstack.sqlite".into(),
pragmas: vec![
// Set busy_timeout to avoid manually managing transaction business/contention
"busy_timeout = 1000".to_string(),
// Enable foreign-keys since it is off by default
"foreign_keys = ON".to_string(),
],
max_open_connections: 4,
}
}
}
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
#[serde(default)] #[serde(default)]
pub struct Api { pub struct Api {

View File

@ -363,7 +363,7 @@ impl Data {
trace!("Selecting downlink gateway"); trace!("Selecting downlink gateway");
let gw_down = helpers::select_downlink_gateway( let gw_down = helpers::select_downlink_gateway(
Some(self.tenant.id), Some(self.tenant.id.into()),
&self.device.get_device_session()?.region_config_id, &self.device.get_device_session()?.region_config_id,
self.network_conf.gateway_prefer_min_margin, self.network_conf.gateway_prefer_min_margin,
self.device_gateway_rx_info.as_mut().unwrap(), self.device_gateway_rx_info.as_mut().unwrap(),
@ -519,7 +519,8 @@ impl Data {
}, },
}; };
integration::ack_event(self.application.id, &self.device.variables, &pl).await; integration::ack_event(self.application.id.into(), &self.device.variables, &pl)
.await;
warn!(dev_eui = %self.device.dev_eui, device_queue_item_id = %qi.id, "Device queue-item discarded because of timeout"); warn!(dev_eui = %self.device.dev_eui, device_queue_item_id = %qi.id, "Device queue-item discarded because of timeout");
continue; continue;
@ -549,7 +550,8 @@ impl Data {
.collect(), .collect(),
}; };
integration::log_event(self.application.id, &self.device.variables, &pl).await; integration::log_event(self.application.id.into(), &self.device.variables, &pl)
.await;
warn!(dev_eui = %self.device.dev_eui, device_queue_item_id = %qi.id, "Device queue-item discarded because of max. payload size"); warn!(dev_eui = %self.device.dev_eui, device_queue_item_id = %qi.id, "Device queue-item discarded because of max. payload size");
continue; continue;
@ -585,7 +587,8 @@ impl Data {
.collect(), .collect(),
}; };
integration::log_event(self.application.id, &self.device.variables, &pl).await; integration::log_event(self.application.id.into(), &self.device.variables, &pl)
.await;
warn!(dev_eui = %self.device.dev_eui, device_queue_item_id = %qi.id, "Device queue-item discarded because of invalid frame-counter"); warn!(dev_eui = %self.device.dev_eui, device_queue_item_id = %qi.id, "Device queue-item discarded because of invalid frame-counter");
continue; continue;
@ -2728,7 +2731,7 @@ mod test {
name: "max payload size error".into(), name: "max payload size error".into(),
max_payload_size: 10, max_payload_size: 10,
queue_items: vec![device_queue::DeviceQueueItem { queue_items: vec![device_queue::DeviceQueueItem {
id: qi_id, id: qi_id.into(),
dev_eui: d.dev_eui, dev_eui: d.dev_eui,
f_port: 1, f_port: 1,
data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
@ -2768,7 +2771,7 @@ mod test {
name: "is pending".into(), name: "is pending".into(),
max_payload_size: 10, max_payload_size: 10,
queue_items: vec![device_queue::DeviceQueueItem { queue_items: vec![device_queue::DeviceQueueItem {
id: qi_id, id: qi_id.into(),
dev_eui: d.dev_eui, dev_eui: d.dev_eui,
f_port: 1, f_port: 1,
f_cnt_down: Some(10), f_cnt_down: Some(10),
@ -2800,7 +2803,7 @@ mod test {
name: "invalid frame-counter".into(), name: "invalid frame-counter".into(),
max_payload_size: 10, max_payload_size: 10,
queue_items: vec![device_queue::DeviceQueueItem { queue_items: vec![device_queue::DeviceQueueItem {
id: qi_id, id: qi_id.into(),
dev_eui: d.dev_eui, dev_eui: d.dev_eui,
f_port: 1, f_port: 1,
data: vec![1, 2, 3], data: vec![1, 2, 3],
@ -2841,14 +2844,14 @@ mod test {
name: "valid payload".into(), name: "valid payload".into(),
max_payload_size: 10, max_payload_size: 10,
queue_items: vec![device_queue::DeviceQueueItem { queue_items: vec![device_queue::DeviceQueueItem {
id: qi_id, id: qi_id.into(),
dev_eui: d.dev_eui, dev_eui: d.dev_eui,
f_port: 1, f_port: 1,
data: vec![1, 2, 3], data: vec![1, 2, 3],
..Default::default() ..Default::default()
}], }],
expected_queue_item: Some(device_queue::DeviceQueueItem { expected_queue_item: Some(device_queue::DeviceQueueItem {
id: qi_id, id: qi_id.into(),
dev_eui: d.dev_eui, dev_eui: d.dev_eui,
f_port: 1, f_port: 1,
data: vec![1, 2, 3], data: vec![1, 2, 3],
@ -2874,7 +2877,7 @@ mod test {
let d = device::partial_update( let d = device::partial_update(
d.dev_eui, d.dev_eui,
&device::DeviceChangeset { &device::DeviceChangeset {
device_session: Some(Some(ds.clone())), device_session: Some(Some(ds.clone().into())),
..Default::default() ..Default::default()
}, },
) )
@ -3418,11 +3421,14 @@ mod test {
dev_addr: Some(*dev_addr), dev_addr: Some(*dev_addr),
application_id: app.id, application_id: app.id,
device_profile_id: dp_ed.id, device_profile_id: dp_ed.id,
device_session: Some(internal::DeviceSession { device_session: Some(
dev_addr: dev_addr.to_vec(), internal::DeviceSession {
nwk_s_enc_key: vec![0; 16], dev_addr: dev_addr.to_vec(),
..Default::default() nwk_s_enc_key: vec![0; 16],
}), ..Default::default()
}
.into(),
),
..Default::default() ..Default::default()
}) })
.await .await
@ -3435,7 +3441,7 @@ mod test {
let d_relay = device::partial_update( let d_relay = device::partial_update(
d_relay.dev_eui, d_relay.dev_eui,
&device::DeviceChangeset { &device::DeviceChangeset {
device_session: Some(Some(test.device_session.clone())), device_session: Some(Some(test.device_session.clone().into())),
..Default::default() ..Default::default()
}, },
) )
@ -3884,7 +3890,7 @@ mod test {
let d_relay = device::partial_update( let d_relay = device::partial_update(
d_relay.dev_eui, d_relay.dev_eui,
&device::DeviceChangeset { &device::DeviceChangeset {
device_session: Some(Some(test.device_session.clone())), device_session: Some(Some(test.device_session.clone().into())),
..Default::default() ..Default::default()
}, },
) )
@ -4015,7 +4021,7 @@ mod test {
application: application::Application::default(), application: application::Application::default(),
device_profile: test.device_profile.clone(), device_profile: test.device_profile.clone(),
device: device::Device { device: device::Device {
device_session: Some(test.device_session.clone()), device_session: Some(test.device_session.clone().into()),
..Default::default() ..Default::default()
}, },
network_conf: config::get_region_network("eu868").unwrap(), network_conf: config::get_region_network("eu868").unwrap(),
@ -4126,7 +4132,7 @@ mod test {
application: application::Application::default(), application: application::Application::default(),
device_profile: test.device_profile.clone(), device_profile: test.device_profile.clone(),
device: device::Device { device: device::Device {
device_session: Some(test.device_session.clone()), device_session: Some(test.device_session.clone().into()),
..Default::default() ..Default::default()
}, },
network_conf: config::get_region_network("eu868").unwrap(), network_conf: config::get_region_network("eu868").unwrap(),
@ -4247,7 +4253,7 @@ mod test {
application: application::Application::default(), application: application::Application::default(),
device_profile: test.device_profile.clone(), device_profile: test.device_profile.clone(),
device: device::Device { device: device::Device {
device_session: Some(test.device_session.clone()), device_session: Some(test.device_session.clone().into()),
..Default::default() ..Default::default()
}, },
network_conf: config::get_region_network("eu868").unwrap(), network_conf: config::get_region_network("eu868").unwrap(),
@ -4504,7 +4510,7 @@ mod test {
let d_relay = device::partial_update( let d_relay = device::partial_update(
d_relay.dev_eui, d_relay.dev_eui,
&device::DeviceChangeset { &device::DeviceChangeset {
device_session: Some(Some(test.device_session.clone())), device_session: Some(Some(test.device_session.clone().into())),
..Default::default() ..Default::default()
}, },
) )

View File

@ -239,7 +239,7 @@ mod tests {
}, },
// is_private_down is set, first gateway matches tenant. // is_private_down is set, first gateway matches tenant.
Test { Test {
tenant_id: Some(t.id), tenant_id: Some(t.id.into()),
min_snr_margin: 0.0, min_snr_margin: 0.0,
rx_info: internal::DeviceGatewayRxInfo { rx_info: internal::DeviceGatewayRxInfo {
items: vec![ items: vec![
@ -262,7 +262,7 @@ mod tests {
}, },
// is_private_down is set, second gateway matches tenant. // is_private_down is set, second gateway matches tenant.
Test { Test {
tenant_id: Some(t.id), tenant_id: Some(t.id.into()),
min_snr_margin: 0.0, min_snr_margin: 0.0,
rx_info: internal::DeviceGatewayRxInfo { rx_info: internal::DeviceGatewayRxInfo {
items: vec![ items: vec![

View File

@ -182,7 +182,7 @@ impl JoinAccept<'_> {
trace!("Select downlink gateway"); trace!("Select downlink gateway");
let gw_down = helpers::select_downlink_gateway( let gw_down = helpers::select_downlink_gateway(
Some(self.tenant.id), Some(self.tenant.id.into()),
&self.uplink_frame_set.region_config_id, &self.uplink_frame_set.region_config_id,
self.network_conf.gateway_prefer_min_margin, self.network_conf.gateway_prefer_min_margin,
self.device_gateway_rx_info.as_mut().unwrap(), self.device_gateway_rx_info.as_mut().unwrap(),

View File

@ -434,7 +434,7 @@ impl TxAck {
..Default::default() ..Default::default()
}; };
integration::log_event(app.id, &dev.variables, &pl).await; integration::log_event(app.id.into(), &dev.variables, &pl).await;
Ok(()) Ok(())
} }
@ -483,7 +483,7 @@ impl TxAck {
tx_info: self.downlink_frame_item.as_ref().unwrap().tx_info.clone(), tx_info: self.downlink_frame_item.as_ref().unwrap().tx_info.clone(),
}; };
integration::txack_event(app.id, &dev.variables, &pl).await; integration::txack_event(app.id.into(), &dev.variables, &pl).await;
Ok(()) Ok(())
} }
@ -532,7 +532,7 @@ impl TxAck {
tx_info: self.downlink_frame_item.as_ref().unwrap().tx_info.clone(), tx_info: self.downlink_frame_item.as_ref().unwrap().tx_info.clone(),
}; };
integration::txack_event(app.id, &dev.variables, &pl).await; integration::txack_event(app.id.into(), &dev.variables, &pl).await;
Ok(()) Ok(())
} }

View File

@ -28,6 +28,7 @@ pub mod mock;
mod mqtt; mod mqtt;
mod mydevices; mod mydevices;
mod pilot_things; mod pilot_things;
#[cfg(feature = "postgres")]
mod postgresql; mod postgresql;
mod redis; mod redis;
mod thingsboard; mod thingsboard;
@ -54,6 +55,7 @@ pub async fn setup() -> Result<()> {
.context("Setup MQTT integration")?, .context("Setup MQTT integration")?,
)); ));
} }
#[cfg(feature = "postgres")]
"postgresql" => integrations.push(Box::new( "postgresql" => integrations.push(Box::new(
postgresql::Integration::new(&conf.integration.postgresql) postgresql::Integration::new(&conf.integration.postgresql)
.await .await
@ -533,7 +535,7 @@ async fn handle_down_command(application_id: String, pl: integration::DownlinkCo
// Validate that the application_id from the topic is indeed the application ID to which // Validate that the application_id from the topic is indeed the application ID to which
// the device belongs. // the device belongs.
let dev = device::get(&dev_eui).await?; let dev = device::get(&dev_eui).await?;
if dev.application_id != app_id { if Into::<Uuid>::into(dev.application_id) != app_id {
return Err(anyhow!( return Err(anyhow!(
"Application ID from topic does not match application ID from device" "Application ID from topic does not match application ID from device"
)); ));
@ -555,8 +557,8 @@ async fn handle_down_command(application_id: String, pl: integration::DownlinkCo
let qi = device_queue::DeviceQueueItem { let qi = device_queue::DeviceQueueItem {
id: match pl.id.is_empty() { id: match pl.id.is_empty() {
true => Uuid::new_v4(), true => Uuid::new_v4().into(),
false => Uuid::from_str(&pl.id)?, false => Uuid::from_str(&pl.id)?.into(),
}, },
f_port: pl.f_port as i16, f_port: pl.f_port as i16,
confirmed: pl.confirmed, confirmed: pl.confirmed,

View File

@ -118,7 +118,7 @@ mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle( let resp = handle(

View File

@ -277,7 +277,7 @@ mod test {
device::partial_update( device::partial_update(
dev.dev_eui, dev.dev_eui,
&device::DeviceChangeset { &device::DeviceChangeset {
device_session: Some(Some(tst.device_session_ed.clone())), device_session: Some(Some(tst.device_session_ed.clone().into())),
..Default::default() ..Default::default()
}, },
) )
@ -285,7 +285,7 @@ mod test {
.unwrap(); .unwrap();
let mut relay_dev = device::Device { let mut relay_dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };

View File

@ -1,11 +1,10 @@
use anyhow::Result; use anyhow::Result;
use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use tracing::info; use tracing::info;
use crate::api::helpers::ToProto; use crate::api::helpers::ToProto;
use crate::integration; use crate::integration;
use crate::storage::{application, device, device_profile, tenant}; use crate::storage::{application, device, device_profile, fields, tenant};
use crate::uplink::{helpers, UplinkFrameSet}; use crate::uplink::{helpers, UplinkFrameSet};
use chirpstack_api::integration as integration_pb; use chirpstack_api::integration as integration_pb;
@ -29,8 +28,8 @@ pub async fn handle(
margin: Some(pl.margin as i32), margin: Some(pl.margin as i32),
external_power_source: Some(pl.battery == 0), external_power_source: Some(pl.battery == 0),
battery_level: Some(if pl.battery > 0 && pl.battery < 255 { battery_level: Some(if pl.battery > 0 && pl.battery < 255 {
let v: BigDecimal = ((pl.battery as f32) / 254.0 * 100.0).try_into()?; let v: fields::BigDecimal = ((pl.battery as f32) / 254.0 * 100.0).try_into()?;
Some(v.with_scale(2)) Some(v.with_scale(2).into())
} else { } else {
None None
}), }),
@ -47,7 +46,7 @@ pub async fn handle(
helpers::get_rx_timestamp(&uplink_frame_set.rx_info_set).into(); helpers::get_rx_timestamp(&uplink_frame_set.rx_info_set).into();
integration::status_event( integration::status_event(
app.id, app.id.into(),
&dev.variables, &dev.variables,
&integration_pb::StatusEvent { &integration_pb::StatusEvent {
deduplication_id: uplink_frame_set.uplink_set_id.to_string(), deduplication_id: uplink_frame_set.uplink_set_id.to_string(),
@ -203,7 +202,7 @@ pub mod test {
assert_eq!(Some(10), d.margin); assert_eq!(Some(10), d.margin);
assert!(!d.external_power_source); assert!(!d.external_power_source);
assert_eq!( assert_eq!(
Some(BigDecimal::from_str("100.00").unwrap()), Some(bigdecimal::BigDecimal::from_str("100.00").unwrap().into()),
d.battery_level d.battery_level
); );
} }

View File

@ -178,7 +178,7 @@ mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle( let resp = handle(

View File

@ -216,7 +216,7 @@ mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle(&mut dev, &tst.filter_list_ans, tst.filter_list_req.as_ref()); let resp = handle(&mut dev, &tst.filter_list_ans, tst.filter_list_req.as_ref());

View File

@ -361,7 +361,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
dev_eui: lrwn::EUI64::from_str("0102030405060708").unwrap(), dev_eui: lrwn::EUI64::from_str("0102030405060708").unwrap(),
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let block = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRAns( let block = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRAns(

View File

@ -207,9 +207,12 @@ pub mod test {
let dp: device_profile::DeviceProfile = Default::default(); let dp: device_profile::DeviceProfile = Default::default();
let mut dev = device::Device { let mut dev = device::Device {
dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
device_session: Some(internal::DeviceSession { device_session: Some(
..Default::default() internal::DeviceSession {
}), ..Default::default()
}
.into(),
),
..Default::default() ..Default::default()
}; };

View File

@ -472,7 +472,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };

View File

@ -65,7 +65,7 @@ pub async fn handle(
.collect(), .collect(),
}; };
integration::log_event(app.id, &dev.variables, &log_event).await; integration::log_event(app.id.into(), &dev.variables, &log_event).await;
Ok(None) Ok(None)
} }
@ -88,19 +88,19 @@ mod test {
integration::set_mock().await; integration::set_mock().await;
let t = tenant::Tenant { let t = tenant::Tenant {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
name: "tenant".to_string(), name: "tenant".to_string(),
..Default::default() ..Default::default()
}; };
let app = application::Application { let app = application::Application {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
name: "app".to_string(), name: "app".to_string(),
..Default::default() ..Default::default()
}; };
let dp = device_profile::DeviceProfile { let dp = device_profile::DeviceProfile {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
name: "dp".to_string(), name: "dp".to_string(),
tags: fields::KeyValue::new( tags: fields::KeyValue::new(
[("dp_tag".to_string(), "dp_value".to_string())] [("dp_tag".to_string(), "dp_value".to_string())]

View File

@ -183,7 +183,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle( let resp = handle(

View File

@ -37,7 +37,7 @@ pub mod test {
#[test] #[test]
fn test_handle() { fn test_handle() {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(internal::DeviceSession::default()), device_session: Some(internal::DeviceSession::default().into()),
..Default::default() ..Default::default()
}; };
let block = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::PingSlotInfoReq( let block = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::PingSlotInfoReq(

View File

@ -161,7 +161,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle( let resp = handle(

View File

@ -180,7 +180,7 @@ mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle(&mut dev, &tst.relay_conf_ans, tst.relay_conf_req.as_ref()); let resp = handle(&mut dev, &tst.relay_conf_ans, tst.relay_conf_req.as_ref());

View File

@ -48,21 +48,24 @@ pub mod test {
#[test] #[test]
fn test_handle() { fn test_handle() {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(internal::DeviceSession { device_session: Some(
tx_power_index: 3, internal::DeviceSession {
min_supported_tx_power_index: 1, tx_power_index: 3,
max_supported_tx_power_index: 5, min_supported_tx_power_index: 1,
extra_uplink_channels: [(3, Default::default())].iter().cloned().collect(), max_supported_tx_power_index: 5,
rx1_delay: 3, extra_uplink_channels: [(3, Default::default())].iter().cloned().collect(),
rx1_dr_offset: 1, rx1_delay: 3,
rx2_dr: 5, rx1_dr_offset: 1,
rx2_frequency: 868900000, rx2_dr: 5,
enabled_uplink_channel_indices: vec![0, 1], rx2_frequency: 868900000,
class_b_ping_slot_dr: 3, enabled_uplink_channel_indices: vec![0, 1],
class_b_ping_slot_freq: 868100000, class_b_ping_slot_dr: 3,
nb_trans: 3, class_b_ping_slot_freq: 868100000,
..Default::default() nb_trans: 3,
}), ..Default::default()
}
.into(),
),
..Default::default() ..Default::default()
}; };
let dp = device_profile::DeviceProfile { let dp = device_profile::DeviceProfile {

View File

@ -184,7 +184,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle( let resp = handle(

View File

@ -103,7 +103,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle( let resp = handle(

View File

@ -139,7 +139,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };
let resp = handle( let resp = handle(

View File

@ -126,7 +126,7 @@ pub mod test {
for tst in &tests { for tst in &tests {
let mut dev = device::Device { let mut dev = device::Device {
device_session: Some(tst.device_session.clone()), device_session: Some(tst.device_session.clone().into()),
..Default::default() ..Default::default()
}; };

View File

@ -8,16 +8,16 @@ use uuid::Uuid;
use super::error::Error; use super::error::Error;
use super::schema::api_key; use super::schema::api_key;
use super::{error, get_async_db_conn}; use super::{error, fields, get_async_db_conn};
#[derive(Queryable, Insertable, PartialEq, Eq, Debug)] #[derive(Queryable, Insertable, PartialEq, Eq, Debug)]
#[diesel(table_name = api_key)] #[diesel(table_name = api_key)]
pub struct ApiKey { pub struct ApiKey {
pub id: Uuid, pub id: fields::Uuid,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub name: String, pub name: String,
pub is_admin: bool, pub is_admin: bool,
pub tenant_id: Option<Uuid>, pub tenant_id: Option<fields::Uuid>,
} }
impl ApiKey { impl ApiKey {
@ -33,7 +33,7 @@ impl ApiKey {
impl Default for ApiKey { impl Default for ApiKey {
fn default() -> Self { fn default() -> Self {
ApiKey { ApiKey {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
created_at: Utc::now(), created_at: Utc::now(),
name: "".into(), name: "".into(),
is_admin: false, is_admin: false,
@ -61,7 +61,7 @@ pub async fn create(ak: ApiKey) -> Result<ApiKey, Error> {
} }
pub async fn delete(id: &Uuid) -> Result<(), Error> { pub async fn delete(id: &Uuid) -> Result<(), Error> {
let ra = diesel::delete(api_key::dsl::api_key.find(&id)) let ra = diesel::delete(api_key::dsl::api_key.find(fields::Uuid::from(id)))
.execute(&mut get_async_db_conn().await?) .execute(&mut get_async_db_conn().await?)
.await?; .await?;
if ra == 0 { if ra == 0 {
@ -78,7 +78,7 @@ pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
.into_boxed(); .into_boxed();
if let Some(tenant_id) = &filters.tenant_id { if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(api_key::dsl::tenant_id.eq(tenant_id)); q = q.filter(api_key::dsl::tenant_id.eq(fields::Uuid::from(tenant_id)));
} }
Ok(q.first(&mut get_async_db_conn().await?).await?) Ok(q.first(&mut get_async_db_conn().await?).await?)
@ -90,7 +90,7 @@ pub async fn list(limit: i64, offset: i64, filters: &Filters) -> Result<Vec<ApiK
.into_boxed(); .into_boxed();
if let Some(tenant_id) = &filters.tenant_id { if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(api_key::dsl::tenant_id.eq(tenant_id)); q = q.filter(api_key::dsl::tenant_id.eq(fields::Uuid::from(tenant_id)));
} }
let items = q let items = q
@ -118,7 +118,7 @@ pub mod test {
pub async fn get(id: &Uuid) -> Result<ApiKey, Error> { pub async fn get(id: &Uuid) -> Result<ApiKey, Error> {
api_key::dsl::api_key api_key::dsl::api_key
.find(&id) .find(fields::Uuid::from(id))
.first(&mut get_async_db_conn().await?) .first(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| error::Error::from_diesel(e, id.to_string())) .map_err(|e| error::Error::from_diesel(e, id.to_string()))
@ -162,7 +162,7 @@ pub mod test {
}, },
FilterTest { FilterTest {
filters: Filters { filters: Filters {
tenant_id: ak_tenant.tenant_id, tenant_id: ak_tenant.tenant_id.map(|u| u.into()),
is_admin: false, is_admin: false,
}, },
keys: vec![&ak_tenant], keys: vec![&ak_tenant],

View File

@ -4,14 +4,11 @@ use std::str::FromStr;
use anyhow::Result; use anyhow::Result;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::{ #[cfg(feature = "sqlite")]
backend::Backend, use diesel::sqlite::Sqlite;
deserialize, dsl, use diesel::{backend::Backend, deserialize, dsl, prelude::*, serialize, sql_types::Text};
pg::Pg, #[cfg(feature = "postgres")]
prelude::*, use diesel::{pg::Pg, sql_types::Jsonb};
serialize,
sql_types::{Jsonb, Text},
};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::info; use tracing::info;
@ -24,8 +21,8 @@ use super::{fields, get_async_db_conn};
#[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)] #[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)]
#[diesel(table_name = application)] #[diesel(table_name = application)]
pub struct Application { pub struct Application {
pub id: Uuid, pub id: fields::Uuid,
pub tenant_id: Uuid, pub tenant_id: fields::Uuid,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub name: String, pub name: String,
@ -48,8 +45,8 @@ impl Default for Application {
let now = Utc::now(); let now = Utc::now();
Application { Application {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
tenant_id: Uuid::nil(), tenant_id: Uuid::nil().into(),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
name: "".into(), name: "".into(),
@ -68,7 +65,7 @@ pub struct Filters {
#[derive(Queryable, PartialEq, Eq, Debug)] #[derive(Queryable, PartialEq, Eq, Debug)]
pub struct ApplicationListItem { pub struct ApplicationListItem {
pub id: Uuid, pub id: fields::Uuid,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub name: String, pub name: String,
@ -129,6 +126,7 @@ where
} }
} }
#[cfg(feature = "postgres")]
impl serialize::ToSql<Text, Pg> for IntegrationKind impl serialize::ToSql<Text, Pg> for IntegrationKind
where where
str: serialize::ToSql<Text, Pg>, str: serialize::ToSql<Text, Pg>,
@ -138,8 +136,16 @@ where
} }
} }
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, Sqlite> for IntegrationKind {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Sqlite>) -> serialize::Result {
out.set_value(self.to_string());
Ok(serialize::IsNull::No)
}
}
#[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[diesel(sql_type = Jsonb)] #[diesel(sql_type = fields::sql_types::JsonT)]
pub enum IntegrationConfiguration { pub enum IntegrationConfiguration {
None, None,
Http(HttpConfiguration), Http(HttpConfiguration),
@ -154,6 +160,7 @@ pub enum IntegrationConfiguration {
Ifttt(IftttConfiguration), Ifttt(IftttConfiguration),
} }
#[cfg(feature = "postgres")]
impl deserialize::FromSql<Jsonb, Pg> for IntegrationConfiguration { impl deserialize::FromSql<Jsonb, Pg> for IntegrationConfiguration {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> { fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = <serde_json::Value as deserialize::FromSql<Jsonb, Pg>>::from_sql(value)?; let value = <serde_json::Value as deserialize::FromSql<Jsonb, Pg>>::from_sql(value)?;
@ -161,6 +168,7 @@ impl deserialize::FromSql<Jsonb, Pg> for IntegrationConfiguration {
} }
} }
#[cfg(feature = "postgres")]
impl serialize::ToSql<Jsonb, Pg> for IntegrationConfiguration { impl serialize::ToSql<Jsonb, Pg> for IntegrationConfiguration {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Pg>) -> serialize::Result { fn to_sql(&self, out: &mut serialize::Output<'_, '_, Pg>) -> serialize::Result {
let value = serde_json::to_value(self)?; let value = serde_json::to_value(self)?;
@ -168,6 +176,23 @@ impl serialize::ToSql<Jsonb, Pg> for IntegrationConfiguration {
} }
} }
#[cfg(feature = "sqlite")]
impl deserialize::FromSql<Text, Sqlite> for IntegrationConfiguration {
fn from_sql(value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let s =
<*const str as deserialize::FromSql<diesel::sql_types::Text, Sqlite>>::from_sql(value)?;
Ok(serde_json::from_str(unsafe { &*s })?)
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, Sqlite> for IntegrationConfiguration {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Sqlite>) -> serialize::Result {
out.set_value(serde_json::to_string(self)?);
Ok(serialize::IsNull::No)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HttpConfiguration { pub struct HttpConfiguration {
pub headers: HashMap<String, String>, pub headers: HashMap<String, String>,
@ -268,7 +293,7 @@ pub struct IftttConfiguration {
#[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)] #[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)]
#[diesel(table_name = application_integration)] #[diesel(table_name = application_integration)]
pub struct Integration { pub struct Integration {
pub application_id: Uuid, pub application_id: fields::Uuid,
pub kind: IntegrationKind, pub kind: IntegrationKind,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
@ -280,7 +305,7 @@ impl Default for Integration {
let now = Utc::now(); let now = Utc::now();
Integration { Integration {
application_id: Uuid::nil(), application_id: Uuid::nil().into(),
kind: IntegrationKind::Http, kind: IntegrationKind::Http,
created_at: now, created_at: now,
updated_at: now, updated_at: now,
@ -305,7 +330,7 @@ pub async fn create(a: Application) -> Result<Application, Error> {
pub async fn get(id: &Uuid) -> Result<Application, Error> { pub async fn get(id: &Uuid) -> Result<Application, Error> {
let a = application::dsl::application let a = application::dsl::application
.find(&id) .find(fields::Uuid::from(id))
.first(&mut get_async_db_conn().await?) .first(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| Error::from_diesel(e, id.to_string()))?; .map_err(|e| Error::from_diesel(e, id.to_string()))?;
@ -335,11 +360,12 @@ pub async fn update(a: Application) -> Result<Application, Error> {
} }
pub async fn update_mqtt_cls_cert(id: &Uuid, cert: &[u8]) -> Result<Application, Error> { pub async fn update_mqtt_cls_cert(id: &Uuid, cert: &[u8]) -> Result<Application, Error> {
let app: Application = diesel::update(application::dsl::application.find(&id)) let app: Application =
.set(application::mqtt_tls_cert.eq(cert)) diesel::update(application::dsl::application.find(fields::Uuid::from(id)))
.get_result(&mut get_async_db_conn().await?) .set(application::mqtt_tls_cert.eq(cert))
.await .get_result(&mut get_async_db_conn().await?)
.map_err(|e| Error::from_diesel(e, id.to_string()))?; .await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!( info!(
application_id = %id, application_id = %id,
@ -350,7 +376,7 @@ pub async fn update_mqtt_cls_cert(id: &Uuid, cert: &[u8]) -> Result<Application,
} }
pub async fn delete(id: &Uuid) -> Result<(), Error> { pub async fn delete(id: &Uuid) -> Result<(), Error> {
let ra = diesel::delete(application::dsl::application.find(&id)) let ra = diesel::delete(application::dsl::application.find(fields::Uuid::from(id)))
.execute(&mut get_async_db_conn().await?) .execute(&mut get_async_db_conn().await?)
.await?; .await?;
if ra == 0 { if ra == 0 {
@ -371,11 +397,18 @@ pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
.into_boxed(); .into_boxed();
if let Some(tenant_id) = &filters.tenant_id { if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(application::dsl::tenant_id.eq(tenant_id)); q = q.filter(application::dsl::tenant_id.eq(fields::Uuid::from(tenant_id)));
} }
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
q = q.filter(application::dsl::name.ilike(format!("%{}%", search))); #[cfg(feature = "postgres")]
{
q = q.filter(application::dsl::name.ilike(format!("%{}%", search)));
}
#[cfg(feature = "sqlite")]
{
q = q.filter(application::dsl::name.like(format!("%{}%", search)));
}
} }
Ok(q.first(&mut get_async_db_conn().await?).await?) Ok(q.first(&mut get_async_db_conn().await?).await?)
@ -397,11 +430,18 @@ pub async fn list(
.into_boxed(); .into_boxed();
if let Some(tenant_id) = &filters.tenant_id { if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(application::dsl::tenant_id.eq(tenant_id)); q = q.filter(application::dsl::tenant_id.eq(fields::Uuid::from(tenant_id)));
} }
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
q = q.filter(application::dsl::name.ilike(format!("%{}%", search))); #[cfg(feature = "postgres")]
{
q = q.filter(application::dsl::name.ilike(format!("%{}%", search)));
}
#[cfg(feature = "sqlite")]
{
q = q.filter(application::dsl::name.like(format!("%{}%", search)));
}
} }
let items = q let items = q
@ -431,7 +471,7 @@ pub async fn get_integration(
let mut i: Integration = application_integration::dsl::application_integration let mut i: Integration = application_integration::dsl::application_integration
.filter( .filter(
application_integration::dsl::application_id application_integration::dsl::application_id
.eq(application_id) .eq(fields::Uuid::from(application_id))
.and(application_integration::dsl::kind.eq(kind)), .and(application_integration::dsl::kind.eq(kind)),
) )
.first(&mut get_async_db_conn().await?) .first(&mut get_async_db_conn().await?)
@ -478,7 +518,7 @@ pub async fn delete_integration(application_id: &Uuid, kind: IntegrationKind) ->
let ra = diesel::delete( let ra = diesel::delete(
application_integration::dsl::application_integration.filter( application_integration::dsl::application_integration.filter(
application_integration::dsl::application_id application_integration::dsl::application_id
.eq(&application_id) .eq(fields::Uuid::from(application_id))
.and(application_integration::dsl::kind.eq(&kind)), .and(application_integration::dsl::kind.eq(&kind)),
), ),
) )
@ -497,20 +537,21 @@ pub async fn get_integrations_for_application(
application_id: &Uuid, application_id: &Uuid,
) -> Result<Vec<Integration>, Error> { ) -> Result<Vec<Integration>, Error> {
let items: Vec<Integration> = application_integration::dsl::application_integration let items: Vec<Integration> = application_integration::dsl::application_integration
.filter(application_integration::dsl::application_id.eq(&application_id)) .filter(application_integration::dsl::application_id.eq(fields::Uuid::from(application_id)))
.order_by(application_integration::dsl::kind) .order_by(application_integration::dsl::kind)
.load(&mut get_async_db_conn().await?) .load(&mut get_async_db_conn().await?)
.await?; .await?;
Ok(items) Ok(items)
} }
pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>, Error> { #[derive(QueryableByName)]
#[derive(QueryableByName)] struct Measurement {
struct Measurement { #[diesel(sql_type = diesel::sql_types::Text)]
#[diesel(sql_type = diesel::sql_types::Text)] pub key: String,
pub key: String, }
}
#[cfg(feature = "postgres")]
pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>, Error> {
let keys: Vec<Measurement> = diesel::sql_query( let keys: Vec<Measurement> = diesel::sql_query(
r#" r#"
select select
@ -525,7 +566,28 @@ pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>,
key key
"#, "#,
) )
.bind::<diesel::sql_types::Uuid, _>(application_id) .bind::<fields::sql_types::Uuid, _>(fields::Uuid::from(application_id))
.load(&mut get_async_db_conn().await?)
.await
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
Ok(keys.iter().map(|k| k.key.clone()).collect())
}
#[cfg(feature = "sqlite")]
pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>, Error> {
let keys: Vec<Measurement> = diesel::sql_query(
r#"
select distinct json_each.key as key
from device_profile dp, json_each(dp.measurements)
inner join device d
on d.device_profile_id = dp.id
where
d.application_id = ?
order by
key
"#,
)
.bind::<fields::sql_types::Uuid, _>(fields::Uuid::from(application_id))
.load(&mut get_async_db_conn().await?) .load(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?; .map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
@ -548,7 +610,7 @@ pub mod test {
pub async fn create_application(tenant_id: Option<Uuid>) -> Application { pub async fn create_application(tenant_id: Option<Uuid>) -> Application {
let tenant_id = match tenant_id { let tenant_id = match tenant_id {
Some(v) => v, Some(v) => v.into(),
None => { None => {
let t = storage::tenant::test::create_tenant().await; let t = storage::tenant::test::create_tenant().await;
t.id t.id
@ -623,7 +685,7 @@ pub mod test {
}, },
FilterTest { FilterTest {
filters: Filters { filters: Filters {
tenant_id: Some(app.tenant_id), tenant_id: Some(app.tenant_id.into()),
search: None, search: None,
}, },
apps: vec![&app], apps: vec![&app],

View File

@ -1,9 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::ops::{Deref, DerefMut};
use std::str::FromStr; use std::str::FromStr;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use bigdecimal::BigDecimal;
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use diesel::{backend::Backend, deserialize, dsl, prelude::*, serialize, sql_types::Text}; use diesel::{backend::Backend, deserialize, dsl, prelude::*, serialize, sql_types::Text};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
@ -14,7 +14,7 @@ use chirpstack_api::internal;
use lrwn::{DevAddr, EUI64}; use lrwn::{DevAddr, EUI64};
use super::schema::{application, device, device_profile, multicast_group_device, tenant}; use super::schema::{application, device, device_profile, multicast_group_device, tenant};
use super::{error::Error, fields, get_async_db_conn}; use super::{db_transaction, error::Error, fields, get_async_db_conn};
use crate::api::helpers::FromProto; use crate::api::helpers::FromProto;
use crate::config; use crate::config;
@ -62,6 +62,7 @@ where
} }
} }
#[cfg(feature = "postgres")]
impl serialize::ToSql<Text, diesel::pg::Pg> for DeviceClass impl serialize::ToSql<Text, diesel::pg::Pg> for DeviceClass
where where
str: serialize::ToSql<Text, diesel::pg::Pg>, str: serialize::ToSql<Text, diesel::pg::Pg>,
@ -77,12 +78,23 @@ where
} }
} }
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, diesel::sqlite::Sqlite> for DeviceClass {
fn to_sql(
&self,
out: &mut serialize::Output<'_, '_, diesel::sqlite::Sqlite>,
) -> serialize::Result {
out.set_value(self.to_string());
Ok(serialize::IsNull::No)
}
}
#[derive(Queryable, QueryableByName, Insertable, PartialEq, Debug, Clone)] #[derive(Queryable, QueryableByName, Insertable, PartialEq, Debug, Clone)]
#[diesel(table_name = device)] #[diesel(table_name = device)]
pub struct Device { pub struct Device {
pub dev_eui: EUI64, pub dev_eui: EUI64,
pub application_id: Uuid, pub application_id: fields::Uuid,
pub device_profile_id: Uuid, pub device_profile_id: fields::Uuid,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub last_seen_at: Option<DateTime<Utc>>, pub last_seen_at: Option<DateTime<Utc>>,
@ -90,7 +102,7 @@ pub struct Device {
pub name: String, pub name: String,
pub description: String, pub description: String,
pub external_power_source: bool, pub external_power_source: bool,
pub battery_level: Option<BigDecimal>, pub battery_level: Option<fields::BigDecimal>,
pub margin: Option<i32>, pub margin: Option<i32>,
pub dr: Option<i16>, pub dr: Option<i16>,
pub latitude: Option<f64>, pub latitude: Option<f64>,
@ -104,7 +116,7 @@ pub struct Device {
pub variables: fields::KeyValue, pub variables: fields::KeyValue,
pub join_eui: EUI64, pub join_eui: EUI64,
pub secondary_dev_addr: Option<DevAddr>, pub secondary_dev_addr: Option<DevAddr>,
pub device_session: Option<internal::DeviceSession>, pub device_session: Option<fields::DeviceSession>,
} }
#[derive(AsChangeset, Debug, Clone, Default)] #[derive(AsChangeset, Debug, Clone, Default)]
@ -116,10 +128,10 @@ pub struct DeviceChangeset {
pub enabled_class: Option<DeviceClass>, pub enabled_class: Option<DeviceClass>,
pub join_eui: Option<EUI64>, pub join_eui: Option<EUI64>,
pub secondary_dev_addr: Option<Option<DevAddr>>, pub secondary_dev_addr: Option<Option<DevAddr>>,
pub device_session: Option<Option<internal::DeviceSession>>, pub device_session: Option<Option<fields::DeviceSession>>,
pub margin: Option<i32>, pub margin: Option<i32>,
pub external_power_source: Option<bool>, pub external_power_source: Option<bool>,
pub battery_level: Option<Option<BigDecimal>>, pub battery_level: Option<Option<fields::BigDecimal>>,
pub scheduler_run_after: Option<Option<DateTime<Utc>>>, pub scheduler_run_after: Option<Option<DateTime<Utc>>>,
pub is_disabled: Option<bool>, pub is_disabled: Option<bool>,
} }
@ -135,12 +147,14 @@ impl Device {
pub fn get_device_session(&self) -> Result<&internal::DeviceSession, Error> { pub fn get_device_session(&self) -> Result<&internal::DeviceSession, Error> {
self.device_session self.device_session
.as_ref() .as_ref()
.map(|ds| ds.deref())
.ok_or_else(|| Error::NotFound(self.dev_eui.to_string())) .ok_or_else(|| Error::NotFound(self.dev_eui.to_string()))
} }
pub fn get_device_session_mut(&mut self) -> Result<&mut internal::DeviceSession, Error> { pub fn get_device_session_mut(&mut self) -> Result<&mut internal::DeviceSession, Error> {
self.device_session self.device_session
.as_mut() .as_mut()
.map(|ds| ds.deref_mut())
.ok_or_else(|| Error::NotFound(self.dev_eui.to_string())) .ok_or_else(|| Error::NotFound(self.dev_eui.to_string()))
} }
@ -155,8 +169,8 @@ impl Default for Device {
Device { Device {
dev_eui: EUI64::default(), dev_eui: EUI64::default(),
application_id: Uuid::nil(), application_id: Uuid::nil().into(),
device_profile_id: Uuid::nil(), device_profile_id: Uuid::nil().into(),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
last_seen_at: None, last_seen_at: None,
@ -188,14 +202,14 @@ pub struct DeviceListItem {
pub dev_eui: EUI64, pub dev_eui: EUI64,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub device_profile_id: Uuid, pub device_profile_id: fields::Uuid,
pub device_profile_name: String, pub device_profile_name: String,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub last_seen_at: Option<DateTime<Utc>>, pub last_seen_at: Option<DateTime<Utc>>,
pub margin: Option<i32>, pub margin: Option<i32>,
pub external_power_source: bool, pub external_power_source: bool,
pub battery_level: Option<BigDecimal>, pub battery_level: Option<fields::BigDecimal>,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
@ -223,52 +237,50 @@ pub struct DevicesDataRate {
pub async fn create(d: Device) -> Result<Device, Error> { pub async fn create(d: Device) -> Result<Device, Error> {
let mut c = get_async_db_conn().await?; let mut c = get_async_db_conn().await?;
let d: Device = c let d: Device = db_transaction::<Device, Error, _>(&mut c, |c| {
.build_transaction() Box::pin(async move {
.run::<Device, Error, _>(|c| { let query = tenant::dsl::tenant
Box::pin(async move { .select((
// use for update to lock the tenant tenant::dsl::id,
let t: super::tenant::Tenant = tenant::dsl::tenant tenant::dsl::created_at,
.select(( tenant::dsl::updated_at,
tenant::dsl::id, tenant::dsl::name,
tenant::dsl::created_at, tenant::dsl::description,
tenant::dsl::updated_at, tenant::dsl::can_have_gateways,
tenant::dsl::name, tenant::dsl::max_device_count,
tenant::dsl::description, tenant::dsl::max_gateway_count,
tenant::dsl::can_have_gateways, tenant::dsl::private_gateways_up,
tenant::dsl::max_device_count, tenant::dsl::private_gateways_down,
tenant::dsl::max_gateway_count, tenant::dsl::tags,
tenant::dsl::private_gateways_up, ))
tenant::dsl::private_gateways_down, .inner_join(application::table)
tenant::dsl::tags, .filter(application::dsl::id.eq(&d.application_id));
)) // use for update to lock the tenant
.inner_join(application::table) #[cfg(feature = "postgres")]
.filter(application::dsl::id.eq(&d.application_id)) let query = query.for_update();
.for_update() let t: super::tenant::Tenant = query.first(c).await?;
.first(c)
.await?;
let dev_count: i64 = device::dsl::device let dev_count: i64 = device::dsl::device
.select(dsl::count_star()) .select(dsl::count_star())
.inner_join(application::table) .inner_join(application::table)
.filter(application::dsl::tenant_id.eq(&t.id)) .filter(application::dsl::tenant_id.eq(&t.id))
.first(c) .first(c)
.await?; .await?;
if t.max_device_count != 0 && dev_count as i32 >= t.max_device_count { if t.max_device_count != 0 && dev_count as i32 >= t.max_device_count {
return Err(Error::NotAllowed( return Err(Error::NotAllowed(
"Max number of devices exceeded for tenant".into(), "Max number of devices exceeded for tenant".into(),
)); ));
} }
diesel::insert_into(device::table) diesel::insert_into(device::table)
.values(&d) .values(&d)
.get_result(c) .get_result(c)
.await .await
.map_err(|e| Error::from_diesel(e, d.dev_eui.to_string())) .map_err(|e| Error::from_diesel(e, d.dev_eui.to_string()))
})
}) })
.await?; })
.await?;
info!(dev_eui = %d.dev_eui, "Device created"); info!(dev_eui = %d.dev_eui, "Device created");
Ok(d) Ok(d)
} }
@ -304,130 +316,129 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
let mut c = get_async_db_conn().await?; let mut c = get_async_db_conn().await?;
c.build_transaction() db_transaction::<ValidationStatus, Error, _>(&mut c, |c| {
.run::<ValidationStatus, Error, _>(|c| { Box::pin(async move {
Box::pin(async move { let query = device::dsl::device
let mut devices: Vec<Device> = device::dsl::device .filter(
.filter( device::dsl::dev_addr
device::dsl::dev_addr .eq(&dev_addr)
.eq(&dev_addr) .or(device::dsl::secondary_dev_addr.eq(&dev_addr)),
.or(device::dsl::secondary_dev_addr.eq(&dev_addr)), )
) .filter(device::dsl::is_disabled.eq(false));
.filter(device::dsl::is_disabled.eq(false)) #[cfg(feature = "postgres")]
.for_update() let query = query.for_update();
.load(c) let mut devices: Vec<Device> = query.load(c).await?;
.await?;
if devices.is_empty() { if devices.is_empty() {
return Err(Error::NotFound(dev_addr.to_string())); return Err(Error::NotFound(dev_addr.to_string()));
}
for d in &mut devices {
let mut sessions = vec![];
if let Some(ds) = &d.device_session {
sessions.push(ds.clone());
if let Some(ds) = &ds.pending_rejoin_device_session {
sessions.push(ds.as_ref().into());
}
} }
for d in &mut devices { for ds in &mut sessions {
let mut sessions = vec![]; if ds.dev_addr != dev_addr.to_vec() {
continue;
if let Some(ds) = &d.device_session {
sessions.push(ds.clone());
if let Some(ds) = &ds.pending_rejoin_device_session {
sessions.push(*ds.clone());
}
} }
for ds in &mut sessions { // Get the full 32bit frame-counter.
if ds.dev_addr != dev_addr.to_vec() { let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig);
continue; let f_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.f_nwk_s_int_key)?;
let s_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.s_nwk_s_int_key)?;
// Check both the full frame-counter and the received frame-counter
// truncated to the 16LSB.
// The latter is needed in case of a frame-counter reset as the
// GetFullFCntUp will think the 16LSB has rolled over and will
// increment the 16MSB bit.
let mut mic_ok = false;
for f_cnt in [full_f_cnt, f_cnt_orig] {
// Set the full f_cnt.
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
pl.fhdr.f_cnt = f_cnt;
} }
// Get the full 32bit frame-counter. mic_ok = phy
let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig); .validate_uplink_data_mic(
let f_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.f_nwk_s_int_key)?; ds.mac_version().from_proto(),
let s_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.s_nwk_s_int_key)?; ds.conf_f_cnt,
tx_dr,
// Check both the full frame-counter and the received frame-counter tx_ch,
// truncated to the 16LSB. &f_nwk_s_int_key,
// The latter is needed in case of a frame-counter reset as the &s_nwk_s_int_key,
// GetFullFCntUp will think the 16LSB has rolled over and will )
// increment the 16MSB bit. .context("Validate MIC")?;
let mut mic_ok = false;
for f_cnt in [full_f_cnt, f_cnt_orig] {
// Set the full f_cnt.
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
pl.fhdr.f_cnt = f_cnt;
}
mic_ok = phy
.validate_uplink_data_mic(
ds.mac_version().from_proto(),
ds.conf_f_cnt,
tx_dr,
tx_ch,
&f_nwk_s_int_key,
&s_nwk_s_int_key,
)
.context("Validate MIC")?;
if mic_ok {
break;
}
}
if mic_ok { if mic_ok {
let full_f_cnt = if let lrwn::Payload::MACPayload(pl) = &phy.payload { break;
pl.fhdr.f_cnt
} else {
0
};
if let Some(relay) = &ds.relay {
if !relayed && relay.ed_relay_only {
info!(
dev_eui = %d.dev_eui,
"Only communication through relay is allowed"
);
return Err(Error::NotFound(dev_addr.to_string()));
}
}
if full_f_cnt >= ds.f_cnt_up {
// We immediately save the device-session to make sure that concurrent calls for
// the same uplink will fail on the frame-counter validation.
let ds_f_cnt_up = ds.f_cnt_up;
ds.f_cnt_up = full_f_cnt + 1;
let _ = diesel::update(device::dsl::device.find(d.dev_eui))
.set(device::device_session.eq(&ds.clone()))
.execute(c)
.await?;
// We do return the device-session with original frame-counter
ds.f_cnt_up = ds_f_cnt_up;
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Ok(full_f_cnt, d.clone()));
} else if ds.skip_f_cnt_check {
// re-transmission or frame-counter reset
ds.f_cnt_up = 0;
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Ok(full_f_cnt, d.clone()));
} else if full_f_cnt == (ds.f_cnt_up - 1) {
// re-transmission, the frame-counter did not increment
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Retransmission(full_f_cnt, d.clone()));
} else {
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Reset(full_f_cnt, d.clone()));
}
}
// Restore the original f_cnt.
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
pl.fhdr.f_cnt = f_cnt_orig;
} }
} }
}
Err(Error::InvalidMIC) if mic_ok {
}) let full_f_cnt = if let lrwn::Payload::MACPayload(pl) = &phy.payload {
pl.fhdr.f_cnt
} else {
0
};
if let Some(relay) = &ds.relay {
if !relayed && relay.ed_relay_only {
info!(
dev_eui = %d.dev_eui,
"Only communication through relay is allowed"
);
return Err(Error::NotFound(dev_addr.to_string()));
}
}
if full_f_cnt >= ds.f_cnt_up {
// We immediately save the device-session to make sure that concurrent calls for
// the same uplink will fail on the frame-counter validation.
let ds_f_cnt_up = ds.f_cnt_up;
ds.f_cnt_up = full_f_cnt + 1;
let _ = diesel::update(device::dsl::device.find(d.dev_eui))
.set(device::device_session.eq(&ds.clone()))
.execute(c)
.await?;
// We do return the device-session with original frame-counter
ds.f_cnt_up = ds_f_cnt_up;
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Ok(full_f_cnt, d.clone()));
} else if ds.skip_f_cnt_check {
// re-transmission or frame-counter reset
ds.f_cnt_up = 0;
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Ok(full_f_cnt, d.clone()));
} else if full_f_cnt == (ds.f_cnt_up - 1) {
// re-transmission, the frame-counter did not increment
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Retransmission(full_f_cnt, d.clone()));
} else {
d.device_session = Some(ds.clone());
return Ok(ValidationStatus::Reset(full_f_cnt, d.clone()));
}
}
// Restore the original f_cnt.
if let lrwn::Payload::MACPayload(pl) = &mut phy.payload {
pl.fhdr.f_cnt = f_cnt_orig;
}
}
}
Err(Error::InvalidMIC)
}) })
.await })
.await
} }
pub async fn get_for_phypayload( pub async fn get_for_phypayload(
@ -462,7 +473,7 @@ pub async fn get_for_phypayload(
if let Some(ds) = &d.device_session { if let Some(ds) = &d.device_session {
sessions.push(ds.clone()); sessions.push(ds.clone());
if let Some(ds) = &ds.pending_rejoin_device_session { if let Some(ds) = &ds.pending_rejoin_device_session {
sessions.push(*ds.clone()); sessions.push(ds.as_ref().into());
} }
} }
@ -559,15 +570,25 @@ pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
.into_boxed(); .into_boxed();
if let Some(application_id) = &filters.application_id { if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id)); q = q.filter(device::dsl::application_id.eq(fields::Uuid::from(application_id)));
} }
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
q = q.filter(device::dsl::name.ilike(format!("%{}%", search))); #[cfg(feature = "postgres")]
{
q = q.filter(device::dsl::name.ilike(format!("%{}%", search)));
}
#[cfg(feature = "sqlite")]
{
q = q.filter(device::dsl::name.like(format!("%{}%", search)));
}
} }
if let Some(multicast_group_id) = &filters.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 = q.filter(
multicast_group_device::dsl::multicast_group_id
.eq(fields::Uuid::from(multicast_group_id)),
);
} }
Ok(q.first(&mut get_async_db_conn().await?).await?) Ok(q.first(&mut get_async_db_conn().await?).await?)
@ -598,15 +619,25 @@ pub async fn list(
.into_boxed(); .into_boxed();
if let Some(application_id) = &filters.application_id { if let Some(application_id) = &filters.application_id {
q = q.filter(device::dsl::application_id.eq(application_id)); q = q.filter(device::dsl::application_id.eq(fields::Uuid::from(application_id)));
} }
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
q = q.filter(device::dsl::name.ilike(format!("%{}%", search))); #[cfg(feature = "postgres")]
{
q = q.filter(device::dsl::name.ilike(format!("%{}%", search)));
}
#[cfg(feature = "sqlite")]
{
q = q.filter(device::dsl::name.like(format!("%{}%", search)));
}
} }
if let Some(multicast_group_id) = &filters.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 = q.filter(
multicast_group_device::dsl::multicast_group_id
.eq(fields::Uuid::from(multicast_group_id)),
);
} }
q.order_by(device::dsl::name) q.order_by(device::dsl::name)
@ -617,6 +648,7 @@ pub async fn list(
.map_err(|e| Error::from_diesel(e, "".into())) .map_err(|e| Error::from_diesel(e, "".into()))
} }
#[cfg(feature = "postgres")]
pub async fn get_active_inactive(tenant_id: &Option<Uuid>) -> Result<DevicesActiveInactive, Error> { pub async fn get_active_inactive(tenant_id: &Option<Uuid>) -> Result<DevicesActiveInactive, Error> {
diesel::sql_query(r#" diesel::sql_query(r#"
with device_active_inactive as ( with device_active_inactive as (
@ -637,11 +669,43 @@ pub async fn get_active_inactive(tenant_id: &Option<Uuid>) -> Result<DevicesActi
from from
device_active_inactive device_active_inactive
"#) "#)
.bind::<diesel::sql_types::Nullable<diesel::sql_types::Uuid>, _>(tenant_id) .bind::<diesel::sql_types::Nullable<fields::sql_types::Uuid>, _>(tenant_id.map(fields::Uuid::from))
.get_result(&mut get_async_db_conn().await?).await .get_result(&mut get_async_db_conn().await?).await
.map_err(|e| Error::from_diesel(e, "".into())) .map_err(|e| Error::from_diesel(e, "".into()))
} }
#[cfg(feature = "sqlite")]
pub async fn get_active_inactive(tenant_id: &Option<Uuid>) -> Result<DevicesActiveInactive, Error> {
diesel::sql_query(
r#"
with device_active_inactive as (
select
dp.uplink_interval * 1.5 as uplink_interval,
d.last_seen_at as last_seen_at,
(unixepoch('now') - unixepoch(last_seen_at)) as not_seen_duration
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 not_seen_duration > uplink_interval then 1 end), 0) as inactive_count,
coalesce(sum(case when not_seen_duration <= uplink_interval then 1 end), 0) as active_count
from
device_active_inactive
"#,
)
.bind::<diesel::sql_types::Nullable<fields::sql_types::Uuid>, _>(
tenant_id.map(fields::Uuid::from),
)
.get_result(&mut get_async_db_conn().await?)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}
pub async fn get_data_rates(tenant_id: &Option<Uuid>) -> Result<Vec<DevicesDataRate>, Error> { pub async fn get_data_rates(tenant_id: &Option<Uuid>) -> Result<Vec<DevicesDataRate>, Error> {
let mut q = device::dsl::device let mut q = device::dsl::device
.inner_join(device_profile::table) .inner_join(device_profile::table)
@ -655,7 +719,7 @@ pub async fn get_data_rates(tenant_id: &Option<Uuid>) -> Result<Vec<DevicesDataR
.into_boxed(); .into_boxed();
if let Some(id) = &tenant_id { if let Some(id) = &tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(id)); q = q.filter(device_profile::dsl::tenant_id.eq(fields::Uuid::from(id)));
} }
q.load(&mut get_async_db_conn().await?) q.load(&mut get_async_db_conn().await?)
@ -665,28 +729,60 @@ pub async fn get_data_rates(tenant_id: &Option<Uuid>) -> Result<Vec<DevicesDataR
pub async fn get_with_class_b_c_queue_items(limit: usize) -> Result<Vec<Device>> { pub async fn get_with_class_b_c_queue_items(limit: usize) -> Result<Vec<Device>> {
let mut c = get_async_db_conn().await?; let mut c = get_async_db_conn().await?;
c.build_transaction() db_transaction::<Vec<Device>, Error, _>(&mut c, |c| {
.run::<Vec<Device>, Error, _>(|c| { Box::pin(async {
Box::pin(async { let conf = config::get();
let conf = config::get();
// This query will: // This query will:
// * Select the devices for which a Class-B or Class-C downlink can be scheduled. // * 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 // * Lock the device records for update with skip locked such that other
// ChirpStack instances are able to do the same for the remaining devices. // ChirpStack instances are able to do the same for the remaining devices.
// * Update the scheduler_run_after for these devices to now() + 2 * scheduler // * Update the scheduler_run_after for these devices to now() + 2 * scheduler
// interval to avoid concurrency issues (other ChirpStack instance scheduling // interval to avoid concurrency issues (other ChirpStack instance scheduling
// the same queue items). // the same queue items).
// //
// This way, we do not have to keep the device records locked until the scheduler // 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 // 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 // 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) // interval (the scheduler is still working on processing the batch after 2 x interval)
// this might cause issues. // this might cause issues.
// The alternative would be to keep the transaction open for a long time + keep // 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. // the device records locked during this time which could case issues as well.
diesel::sql_query( diesel::sql_query(if cfg!(feature = "sqlite") {
r#" r#"
update
device
set
scheduler_run_after = ?3
where
dev_eui in (
select
d.dev_eui
from
device d
where
d.enabled_class in ('B', 'C')
and (d.scheduler_run_after is null or d.scheduler_run_after < ?2)
and d.is_disabled = FALSE
and exists (
select
1
from
device_queue_item dq
where
dq.dev_eui = d.dev_eui
and not (
-- pending queue-item with timeout_after in the future
(dq.is_pending = true and dq.timeout_after > ?2)
)
)
order by d.dev_eui
limit ?1
)
returning *
"#
} else {
r#"
update update
device device
set set
@ -718,20 +814,20 @@ pub async fn get_with_class_b_c_queue_items(limit: usize) -> Result<Vec<Device>>
for update skip locked for update skip locked
) )
returning * 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)
.await
.map_err(|e| Error::from_diesel(e, "".into()))
}) })
.bind::<diesel::sql_types::Integer, _>(limit as i32)
.bind::<fields::sql_types::Timestamptz, _>(Utc::now())
.bind::<fields::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
.context("Get with Class B/C queue-items transaction")
} }
// GetFullFCntUp returns the full 32bit frame-counter, given the fCntUp which // GetFullFCntUp returns the full 32bit frame-counter, given the fCntUp which
@ -786,9 +882,10 @@ pub mod test {
}; };
let application_id = match application_id { let application_id = match application_id {
Some(v) => v, Some(v) => v.into(),
None => { None => {
let a = storage::application::test::create_application(Some(tenant_id)).await; let a =
storage::application::test::create_application(Some(tenant_id.into())).await;
a.id a.id
} }
}; };
@ -797,7 +894,7 @@ pub mod test {
name: "test-dev".into(), name: "test-dev".into(),
dev_eui, dev_eui,
application_id, application_id,
device_profile_id, device_profile_id: device_profile_id.into(),
..Default::default() ..Default::default()
}; };
@ -808,8 +905,12 @@ pub mod test {
async fn test_device() { async fn test_device() {
let _guard = test::prepare().await; let _guard = test::prepare().await;
let dp = storage::device_profile::test::create_device_profile(None).await; let dp = storage::device_profile::test::create_device_profile(None).await;
let mut d = let mut d = create_device(
create_device(EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), dp.id, None).await; EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
dp.id.into(),
None,
)
.await;
// get // get
let d_get = get(&d.dev_eui).await.unwrap(); let d_get = get(&d.dev_eui).await.unwrap();
@ -858,7 +959,7 @@ pub mod test {
}, },
FilterTest { FilterTest {
filters: Filters { filters: Filters {
application_id: Some(d.application_id), application_id: Some(d.application_id.into()),
multicast_group_id: None, multicast_group_id: None,
search: None, search: None,
}, },
@ -906,7 +1007,12 @@ pub mod test {
async fn test_get_with_class_b_c_queue_items() { async fn test_get_with_class_b_c_queue_items() {
let _guard = test::prepare().await; let _guard = test::prepare().await;
let dp = storage::device_profile::test::create_device_profile(None).await; let dp = storage::device_profile::test::create_device_profile(None).await;
let d = create_device(EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), dp.id, None).await; let d = create_device(
EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
dp.id.into(),
None,
)
.await;
// nothing in the queue // nothing in the queue
let res = get_with_class_b_c_queue_items(10).await.unwrap(); let res = get_with_class_b_c_queue_items(10).await.unwrap();
@ -1057,24 +1163,27 @@ pub mod test {
name: "0101010101010101".into(), name: "0101010101010101".into(),
dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]), dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]),
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
device_session: Some(internal::DeviceSession { device_session: Some(
dev_addr: vec![0x01, 0x02, 0x03, 0x04], internal::DeviceSession {
s_nwk_s_int_key: vec![ dev_addr: vec![0x01, 0x02, 0x03, 0x04],
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, s_nwk_s_int_key: vec![
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
], 0x01, 0x01, 0x01, 0x01,
f_nwk_s_int_key: vec![ ],
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, f_nwk_s_int_key: vec![
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
], 0x01, 0x01, 0x01, 0x01,
nwk_s_enc_key: vec![ ],
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, nwk_s_enc_key: vec![
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
], 0x01, 0x01, 0x01, 0x01,
f_cnt_up: 100, ],
skip_f_cnt_check: true, f_cnt_up: 100,
..Default::default() skip_f_cnt_check: true,
}), ..Default::default()
}
.into(),
),
..Default::default() ..Default::default()
}, },
Device { Device {
@ -1083,23 +1192,26 @@ pub mod test {
name: "0202020202020202".into(), name: "0202020202020202".into(),
dev_eui: EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]), dev_eui: EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]),
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
device_session: Some(internal::DeviceSession { device_session: Some(
dev_addr: vec![0x01, 0x02, 0x03, 0x04], internal::DeviceSession {
s_nwk_s_int_key: vec![ dev_addr: vec![0x01, 0x02, 0x03, 0x04],
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, s_nwk_s_int_key: vec![
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
], 0x02, 0x02, 0x02, 0x02,
f_nwk_s_int_key: vec![ ],
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, f_nwk_s_int_key: vec![
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
], 0x02, 0x02, 0x02, 0x02,
nwk_s_enc_key: vec![ ],
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, nwk_s_enc_key: vec![
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
], 0x02, 0x02, 0x02, 0x02,
f_cnt_up: 200, ],
..Default::default() f_cnt_up: 200,
}), ..Default::default()
}
.into(),
),
..Default::default() ..Default::default()
}, },
Device { Device {
@ -1109,40 +1221,43 @@ pub mod test {
dev_eui: EUI64::from_be_bytes([3, 3, 3, 3, 3, 3, 3, 3]), dev_eui: EUI64::from_be_bytes([3, 3, 3, 3, 3, 3, 3, 3]),
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
secondary_dev_addr: Some(DevAddr::from_be_bytes([4, 3, 2, 1])), secondary_dev_addr: Some(DevAddr::from_be_bytes([4, 3, 2, 1])),
device_session: Some(internal::DeviceSession { device_session: Some(
dev_addr: vec![0x01, 0x02, 0x03, 0x04], internal::DeviceSession {
s_nwk_s_int_key: vec![ dev_addr: vec![0x01, 0x02, 0x03, 0x04],
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03,
],
f_nwk_s_int_key: vec![
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03,
],
nwk_s_enc_key: vec![
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03,
],
f_cnt_up: 300,
pending_rejoin_device_session: Some(Box::new(internal::DeviceSession {
dev_addr: vec![0x04, 0x03, 0x02, 0x01],
s_nwk_s_int_key: vec![ s_nwk_s_int_key: vec![
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03,
], ],
f_nwk_s_int_key: vec![ f_nwk_s_int_key: vec![
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03,
], ],
nwk_s_enc_key: vec![ nwk_s_enc_key: vec![
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03,
], ],
f_cnt_up: 0, f_cnt_up: 300,
pending_rejoin_device_session: Some(Box::new(internal::DeviceSession {
dev_addr: vec![0x04, 0x03, 0x02, 0x01],
s_nwk_s_int_key: vec![
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04,
],
f_nwk_s_int_key: vec![
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04,
],
nwk_s_enc_key: vec![
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04,
],
f_cnt_up: 0,
..Default::default()
})),
..Default::default() ..Default::default()
})), }
..Default::default() .into(),
}), ),
..Default::default() ..Default::default()
}, },
Device { Device {
@ -1151,23 +1266,26 @@ pub mod test {
name: "0505050505050505".into(), name: "0505050505050505".into(),
dev_eui: EUI64::from_be_bytes([5, 5, 5, 5, 5, 5, 5, 5]), dev_eui: EUI64::from_be_bytes([5, 5, 5, 5, 5, 5, 5, 5]),
dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])),
device_session: Some(internal::DeviceSession { device_session: Some(
dev_addr: vec![0x01, 0x02, 0x03, 0x04], internal::DeviceSession {
s_nwk_s_int_key: vec![ dev_addr: vec![0x01, 0x02, 0x03, 0x04],
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, s_nwk_s_int_key: vec![
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
], 0x05, 0x05, 0x05, 0x05,
f_nwk_s_int_key: vec![ ],
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, f_nwk_s_int_key: vec![
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
], 0x05, 0x05, 0x05, 0x05,
nwk_s_enc_key: vec![ ],
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, nwk_s_enc_key: vec![
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
], 0x05, 0x05, 0x05, 0x05,
f_cnt_up: (1 << 16) + 1, ],
..Default::default() f_cnt_up: (1 << 16) + 1,
}), ..Default::default()
}
.into(),
),
..Default::default() ..Default::default()
}, },
]; ];

View File

@ -7,8 +7,8 @@ use tracing::info;
use lrwn::{AES128Key, EUI64}; use lrwn::{AES128Key, EUI64};
use super::error::Error; use super::error::Error;
use super::get_async_db_conn;
use super::schema::device_keys; use super::schema::device_keys;
use super::{db_transaction, fields, get_async_db_conn};
#[derive(Queryable, Insertable, AsChangeset, PartialEq, Eq, Debug, Clone)] #[derive(Queryable, Insertable, AsChangeset, PartialEq, Eq, Debug, Clone)]
#[diesel(table_name = device_keys)] #[diesel(table_name = device_keys)]
@ -18,7 +18,7 @@ pub struct DeviceKeys {
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub nwk_key: AES128Key, pub nwk_key: AES128Key,
pub app_key: AES128Key, pub app_key: AES128Key,
pub dev_nonces: Vec<Option<i32>>, pub dev_nonces: fields::DevNonces,
pub join_nonce: i32, pub join_nonce: i32,
} }
@ -38,7 +38,7 @@ impl Default for DeviceKeys {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
]), ]),
dev_nonces: Vec::new(), dev_nonces: Vec::new().into(),
join_nonce: 0, join_nonce: 0,
} }
} }
@ -94,8 +94,9 @@ pub async fn delete(dev_eui: &EUI64) -> Result<(), Error> {
} }
pub async fn set_dev_nonces(dev_eui: &EUI64, nonces: &[i32]) -> Result<DeviceKeys, Error> { pub async fn set_dev_nonces(dev_eui: &EUI64, nonces: &[i32]) -> Result<DeviceKeys, Error> {
let nonces: Vec<Option<i32>> = nonces.iter().map(|v| Some(*v)).collect();
let dk: DeviceKeys = diesel::update(device_keys::dsl::device_keys.find(dev_eui)) let dk: DeviceKeys = diesel::update(device_keys::dsl::device_keys.find(dev_eui))
.set(device_keys::dev_nonces.eq(nonces)) .set(device_keys::dev_nonces.eq(fields::DevNonces::from(nonces)))
.get_result(&mut get_async_db_conn().await?) .get_result(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?; .map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
@ -111,36 +112,35 @@ pub async fn validate_incr_join_and_store_dev_nonce(
dev_nonce: i32, dev_nonce: i32,
) -> Result<DeviceKeys, Error> { ) -> Result<DeviceKeys, Error> {
let mut c = get_async_db_conn().await?; let mut c = get_async_db_conn().await?;
let dk: DeviceKeys = c let dk: DeviceKeys = db_transaction::<DeviceKeys, Error, _>(&mut c, |c| {
.build_transaction() Box::pin(async move {
.run::<DeviceKeys, Error, _>(|c| { let query = device_keys::dsl::device_keys.find(&dev_eui);
Box::pin(async move { #[cfg(feature = "postgres")]
let mut dk: DeviceKeys = device_keys::dsl::device_keys let query = query.for_update();
.find(&dev_eui) let mut dk: DeviceKeys = query
.for_update() .first(c)
.first(c) .await
.await .map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?;
if dk.dev_nonces.contains(&(Some(dev_nonce))) { if dk.dev_nonces.contains(&(Some(dev_nonce))) {
return Err(Error::InvalidDevNonce); return Err(Error::InvalidDevNonce);
} }
dk.dev_nonces.push(Some(dev_nonce)); dk.dev_nonces.push(Some(dev_nonce));
dk.join_nonce += 1; dk.join_nonce += 1;
diesel::update(device_keys::dsl::device_keys.find(&dev_eui)) diesel::update(device_keys::dsl::device_keys.find(&dev_eui))
.set(( .set((
device_keys::updated_at.eq(Utc::now()), device_keys::updated_at.eq(Utc::now()),
device_keys::dev_nonces.eq(&dk.dev_nonces), device_keys::dev_nonces.eq(&dk.dev_nonces),
device_keys::join_nonce.eq(&dk.join_nonce), device_keys::join_nonce.eq(&dk.join_nonce),
)) ))
.get_result(c) .get_result(c)
.await .await
.map_err(|e| Error::from_diesel(e, dev_eui.to_string())) .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"); info!(dev_eui = %dev_eui, dev_nonce = dev_nonce, "Device-nonce validated, join-nonce incremented and stored");
Ok(dk) Ok(dk)
@ -155,7 +155,7 @@ pub mod test {
pub async fn reset_nonces(dev_eui: &EUI64) -> Result<DeviceKeys, Error> { pub async fn reset_nonces(dev_eui: &EUI64) -> Result<DeviceKeys, Error> {
let dk: DeviceKeys = diesel::update(device_keys::dsl::device_keys.find(&dev_eui)) let dk: DeviceKeys = diesel::update(device_keys::dsl::device_keys.find(&dev_eui))
.set(( .set((
device_keys::dev_nonces.eq::<Vec<i32>>(Vec::new()), device_keys::dev_nonces.eq(fields::DevNonces::from(Vec::new())),
device_keys::join_nonce.eq(0), device_keys::join_nonce.eq(0),
)) ))
.get_result(&mut get_async_db_conn().await?) .get_result(&mut get_async_db_conn().await?)

View File

@ -19,8 +19,8 @@ use chirpstack_api::internal;
#[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq)] #[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq)]
#[diesel(table_name = device_profile)] #[diesel(table_name = device_profile)]
pub struct DeviceProfile { pub struct DeviceProfile {
pub id: Uuid, pub id: fields::Uuid,
pub tenant_id: Uuid, pub tenant_id: fields::Uuid,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub name: String, pub name: String,
@ -95,8 +95,8 @@ impl Default for DeviceProfile {
let now = Utc::now(); let now = Utc::now();
DeviceProfile { DeviceProfile {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
tenant_id: Uuid::nil(), tenant_id: Uuid::nil().into(),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
name: "".into(), name: "".into(),
@ -185,7 +185,7 @@ impl DeviceProfile {
#[derive(Queryable, PartialEq, Eq, Debug)] #[derive(Queryable, PartialEq, Eq, Debug)]
pub struct DeviceProfileListItem { pub struct DeviceProfileListItem {
pub id: Uuid, pub id: fields::Uuid,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub name: String, pub name: String,
@ -217,7 +217,7 @@ pub async fn create(dp: DeviceProfile) -> Result<DeviceProfile, Error> {
pub async fn get(id: &Uuid) -> Result<DeviceProfile, Error> { pub async fn get(id: &Uuid) -> Result<DeviceProfile, Error> {
let dp = device_profile::dsl::device_profile let dp = device_profile::dsl::device_profile
.find(&id) .find(&fields::Uuid::from(id))
.first(&mut get_async_db_conn().await?) .first(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| error::Error::from_diesel(e, id.to_string()))?; .map_err(|e| error::Error::from_diesel(e, id.to_string()))?;
@ -297,17 +297,18 @@ pub async fn update(dp: DeviceProfile) -> Result<DeviceProfile, Error> {
} }
pub async fn set_measurements(id: Uuid, m: &fields::Measurements) -> Result<DeviceProfile, Error> { pub async fn set_measurements(id: Uuid, m: &fields::Measurements) -> Result<DeviceProfile, Error> {
let dp: DeviceProfile = diesel::update(device_profile::dsl::device_profile.find(&id)) let dp: DeviceProfile =
.set(device_profile::measurements.eq(m)) diesel::update(device_profile::dsl::device_profile.find(&fields::Uuid::from(id)))
.get_result(&mut get_async_db_conn().await?) .set(device_profile::measurements.eq(m))
.await .get_result(&mut get_async_db_conn().await?)
.map_err(|e| Error::from_diesel(e, id.to_string()))?; .await
.map_err(|e| Error::from_diesel(e, id.to_string()))?;
info!(id = %id, "Device-profile measurements updated"); info!(id = %id, "Device-profile measurements updated");
Ok(dp) Ok(dp)
} }
pub async fn delete(id: &Uuid) -> Result<(), Error> { pub async fn delete(id: &Uuid) -> Result<(), Error> {
let ra = diesel::delete(device_profile::dsl::device_profile.find(&id)) let ra = diesel::delete(device_profile::dsl::device_profile.find(&fields::Uuid::from(id)))
.execute(&mut get_async_db_conn().await?) .execute(&mut get_async_db_conn().await?)
.await?; .await?;
if ra == 0 { if ra == 0 {
@ -323,11 +324,18 @@ pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
.into_boxed(); .into_boxed();
if let Some(tenant_id) = &filters.tenant_id { if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(tenant_id)); q = q.filter(device_profile::dsl::tenant_id.eq(fields::Uuid::from(tenant_id)));
} }
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search))); #[cfg(feature = "postgres")]
{
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search)));
}
#[cfg(feature = "sqlite")]
{
q = q.filter(device_profile::dsl::name.like(format!("%{}%", search)));
}
} }
Ok(q.first(&mut get_async_db_conn().await?).await?) Ok(q.first(&mut get_async_db_conn().await?).await?)
@ -354,11 +362,18 @@ pub async fn list(
.into_boxed(); .into_boxed();
if let Some(tenant_id) = &filters.tenant_id { if let Some(tenant_id) = &filters.tenant_id {
q = q.filter(device_profile::dsl::tenant_id.eq(tenant_id)); q = q.filter(device_profile::dsl::tenant_id.eq(fields::Uuid::from(tenant_id)));
} }
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search))); #[cfg(feature = "postgres")]
{
q = q.filter(device_profile::dsl::name.ilike(format!("%{}%", search)));
}
#[cfg(feature = "sqlite")]
{
q = q.filter(device_profile::dsl::name.like(format!("%{}%", search)));
}
} }
let items = q let items = q
@ -386,7 +401,7 @@ pub mod test {
pub async fn create_device_profile(tenant_id: Option<Uuid>) -> DeviceProfile { pub async fn create_device_profile(tenant_id: Option<Uuid>) -> DeviceProfile {
let tenant_id = match tenant_id { let tenant_id = match tenant_id {
Some(v) => v, Some(v) => v.into(),
None => { None => {
let t = storage::tenant::test::create_tenant().await; let t = storage::tenant::test::create_tenant().await;
t.id t.id
@ -462,7 +477,7 @@ pub mod test {
}, },
FilterTest { FilterTest {
filters: Filters { filters: Filters {
tenant_id: Some(dp.tenant_id), tenant_id: Some(dp.tenant_id.into()),
search: None, search: None,
}, },
dps: vec![&dp], dps: vec![&dp],

View File

@ -5,15 +5,14 @@ use diesel_async::RunQueryDsl;
use tracing::info; use tracing::info;
use uuid::Uuid; use uuid::Uuid;
use super::error::Error;
use super::get_async_db_conn;
use super::schema::device_queue_item; use super::schema::device_queue_item;
use super::{error::Error, fields, get_async_db_conn};
use lrwn::EUI64; use lrwn::EUI64;
#[derive(Queryable, Insertable, PartialEq, Eq, Debug, Clone)] #[derive(Queryable, Insertable, PartialEq, Eq, Debug, Clone)]
#[diesel(table_name = device_queue_item)] #[diesel(table_name = device_queue_item)]
pub struct DeviceQueueItem { pub struct DeviceQueueItem {
pub id: Uuid, pub id: fields::Uuid,
pub dev_eui: EUI64, pub dev_eui: EUI64,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub f_port: i16, pub f_port: i16,
@ -48,7 +47,7 @@ impl Default for DeviceQueueItem {
let now = Utc::now(); let now = Utc::now();
DeviceQueueItem { DeviceQueueItem {
id: Uuid::new_v4(), id: Uuid::new_v4().into(),
dev_eui: EUI64::from_be_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), dev_eui: EUI64::from_be_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
created_at: now, created_at: now,
f_port: 0, f_port: 0,
@ -76,7 +75,7 @@ pub async fn enqueue_item(qi: DeviceQueueItem) -> Result<DeviceQueueItem, Error>
pub async fn get_item(id: &Uuid) -> Result<DeviceQueueItem, Error> { pub async fn get_item(id: &Uuid) -> Result<DeviceQueueItem, Error> {
let qi = device_queue_item::dsl::device_queue_item let qi = device_queue_item::dsl::device_queue_item
.find(id) .find(&fields::Uuid::from(id))
.first(&mut get_async_db_conn().await?) .first(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| Error::from_diesel(e, id.to_string()))?; .map_err(|e| Error::from_diesel(e, id.to_string()))?;
@ -99,9 +98,10 @@ pub async fn update_item(qi: DeviceQueueItem) -> Result<DeviceQueueItem, Error>
} }
pub async fn delete_item(id: &Uuid) -> Result<(), Error> { pub async fn delete_item(id: &Uuid) -> Result<(), Error> {
let ra = diesel::delete(device_queue_item::dsl::device_queue_item.find(&id)) let ra =
.execute(&mut get_async_db_conn().await?) diesel::delete(device_queue_item::dsl::device_queue_item.find(&fields::Uuid::from(id)))
.await?; .execute(&mut get_async_db_conn().await?)
.await?;
if ra == 0 { if ra == 0 {
return Err(Error::NotFound(id.to_string())); return Err(Error::NotFound(id.to_string()));
} }
@ -192,7 +192,7 @@ pub mod test {
let dp = storage::device_profile::test::create_device_profile(None).await; let dp = storage::device_profile::test::create_device_profile(None).await;
let d = storage::device::test::create_device( let d = storage::device::test::create_device(
EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
dp.id, dp.id.into(),
None, None,
) )
.await; .await;
@ -253,7 +253,7 @@ pub mod test {
let dp = storage::device_profile::test::create_device_profile(None).await; let dp = storage::device_profile::test::create_device_profile(None).await;
let d = storage::device::test::create_device( let d = storage::device::test::create_device(
EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
dp.id, dp.id.into(),
None, None,
) )
.await; .await;
@ -278,7 +278,7 @@ pub mod test {
let dp = storage::device_profile::test::create_device_profile(None).await; let dp = storage::device_profile::test::create_device_profile(None).await;
let d = storage::device::test::create_device( let d = storage::device::test::create_device(
EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
dp.id, dp.id.into(),
None, None,
) )
.await; .await;

View File

@ -1,177 +0,0 @@
use std::collections::HashMap;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use diesel::backend::Backend;
use diesel::pg::Pg;
use diesel::sql_types::{Jsonb, Text};
use diesel::{deserialize, serialize};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow)]
#[diesel(sql_type = Jsonb)]
pub struct KeyValue(HashMap<String, String>);
impl KeyValue {
pub fn new(m: HashMap<String, String>) -> Self {
KeyValue(m)
}
#[allow(clippy::wrong_self_convention)]
pub fn into_hashmap(&self) -> HashMap<String, String> {
self.0.clone()
}
}
impl Deref for KeyValue {
type Target = HashMap<String, String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for KeyValue {
fn deref_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.0
}
}
impl deserialize::FromSql<Jsonb, Pg> for KeyValue {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = <serde_json::Value as deserialize::FromSql<Jsonb, Pg>>::from_sql(value)?;
let kv: HashMap<String, String> = serde_json::from_value(value)?;
Ok(KeyValue(kv))
}
}
impl serialize::ToSql<Jsonb, Pg> for KeyValue {
fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
let value = serde_json::to_value(&self.0)?;
<serde_json::Value as serialize::ToSql<Jsonb, Pg>>::to_sql(&value, &mut out.reborrow())
}
}
#[derive(Debug, Clone, AsExpression, FromSqlRow, PartialEq, Eq)]
#[diesel(sql_type = Jsonb)]
pub struct Measurements(HashMap<String, Measurement>);
impl Measurements {
pub fn new(m: HashMap<String, Measurement>) -> Self {
Measurements(m)
}
#[allow(clippy::wrong_self_convention)]
pub fn into_hashmap(&self) -> HashMap<String, Measurement> {
self.0.clone()
}
}
impl Deref for Measurements {
type Target = HashMap<String, Measurement>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Measurements {
fn deref_mut(&mut self) -> &mut HashMap<String, Measurement> {
&mut self.0
}
}
impl deserialize::FromSql<Jsonb, Pg> for Measurements {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = <serde_json::Value as deserialize::FromSql<Jsonb, Pg>>::from_sql(value)?;
let kv: HashMap<String, Measurement> = serde_json::from_value(value)?;
Ok(Measurements::new(kv))
}
}
impl serialize::ToSql<Jsonb, Pg> for Measurements {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Pg>) -> serialize::Result {
let value = serde_json::to_value(&self.0)?;
<serde_json::Value as serialize::ToSql<Jsonb, Pg>>::to_sql(&value, &mut out.reborrow())
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Measurement {
pub name: String,
pub kind: MeasurementKind,
}
#[allow(clippy::upper_case_acronyms)]
#[allow(non_camel_case_types)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum MeasurementKind {
// Unknown.
UNKNOWN,
// Incrementing counters which are not reset on each reporting.
COUNTER,
// Counters that do get reset upon reading.
ABSOLUTE,
// E.g. a temperature value.
GAUGE,
// E.g. a firmware version, true / false value.
STRING,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, AsExpression, FromSqlRow)]
#[allow(clippy::upper_case_acronyms)]
#[allow(non_camel_case_types)]
#[diesel(sql_type = diesel::sql_types::Text)]
pub enum MulticastGroupSchedulingType {
// Delay.
DELAY,
// GPS time.
GPS_TIME,
}
impl fmt::Display for MulticastGroupSchedulingType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl<DB> deserialize::FromSql<Text, DB> for MulticastGroupSchedulingType
where
DB: Backend,
*const str: deserialize::FromSql<Text, DB>,
{
fn from_sql(value: <DB as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let string = <*const str>::from_sql(value)?;
Ok(Self::from_str(unsafe { &*string })?)
}
}
impl serialize::ToSql<Text, diesel::pg::Pg> for MulticastGroupSchedulingType
where
str: serialize::ToSql<Text, diesel::pg::Pg>,
{
fn to_sql<'b>(
&'b self,
out: &mut serialize::Output<'b, '_, diesel::pg::Pg>,
) -> serialize::Result {
<str as serialize::ToSql<Text, diesel::pg::Pg>>::to_sql(
&self.to_string(),
&mut out.reborrow(),
)
}
}
impl FromStr for MulticastGroupSchedulingType {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(match s {
"DELAY" => MulticastGroupSchedulingType::DELAY,
"GPS_TIME" => MulticastGroupSchedulingType::GPS_TIME,
_ => {
return Err(anyhow!("Unexpected MulticastGroupSchedulingType: {}", s));
}
})
}
}

View File

@ -0,0 +1,91 @@
use diesel::{
backend::Backend,
{deserialize, serialize},
};
#[cfg(feature = "postgres")]
use diesel::{pg::Pg, sql_types::Numeric};
#[cfg(feature = "sqlite")]
use diesel::{sql_types::Double, sqlite::Sqlite};
#[derive(Clone, Debug, Eq, PartialEq, AsExpression, FromSqlRow)]
#[cfg_attr(feature="postgres", diesel(sql_type = Numeric))]
#[cfg_attr(feature="sqlite", diesel(sql_type = Double))]
pub struct BigDecimal(bigdecimal::BigDecimal);
impl std::convert::AsRef<bigdecimal::BigDecimal> for BigDecimal {
fn as_ref(&self) -> &bigdecimal::BigDecimal {
&self.0
}
}
impl std::convert::From<bigdecimal::BigDecimal> for BigDecimal {
fn from(value: bigdecimal::BigDecimal) -> Self {
Self(value)
}
}
impl std::convert::TryFrom<f32> for BigDecimal {
type Error = <bigdecimal::BigDecimal as TryFrom<f32>>::Error;
fn try_from(value: f32) -> Result<Self, Self::Error> {
bigdecimal::BigDecimal::try_from(value).map(|bd| bd.into())
}
}
impl std::ops::Deref for BigDecimal {
type Target = bigdecimal::BigDecimal;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for BigDecimal {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "postgres")]
impl deserialize::FromSql<Numeric, Pg> for BigDecimal {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let u = <bigdecimal::BigDecimal>::from_sql(value)?;
Ok(BigDecimal(u))
}
}
#[cfg(feature = "postgres")]
impl serialize::ToSql<Numeric, Pg> for BigDecimal {
fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
<bigdecimal::BigDecimal as serialize::ToSql<Numeric, Pg>>::to_sql(
&self.0,
&mut out.reborrow(),
)
}
}
#[cfg(feature = "sqlite")]
impl deserialize::FromSql<Double, Sqlite> for BigDecimal
where
f64: deserialize::FromSql<Double, Sqlite>,
{
fn from_sql(value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
use bigdecimal::FromPrimitive;
let bd_val =
<f64 as deserialize::FromSql<diesel::sql_types::Double, Sqlite>>::from_sql(value)?;
let bd = bigdecimal::BigDecimal::from_f64(bd_val)
.ok_or_else(|| format!("Unrepresentable BigDecimal from f64 value"))?;
Ok(BigDecimal(bd))
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Double, Sqlite> for BigDecimal {
fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, Sqlite>) -> serialize::Result {
use bigdecimal::ToPrimitive;
let value = self
.0
.to_f64()
.ok_or_else(|| format!("Unrepresentable f64 value as BigDecimal"))?;
out.set_value(value);
Ok(serialize::IsNull::No)
}
}

View File

@ -0,0 +1,92 @@
use diesel::backend::Backend;
use diesel::{deserialize, serialize};
#[cfg(feature = "postgres")]
use diesel::{
pg::Pg,
sql_types::{Array, Int4, Nullable},
};
#[cfg(feature = "sqlite")]
use diesel::{sql_types::Text, sqlite::Sqlite};
use serde::{Deserialize, Serialize};
#[cfg(feature = "postgres")]
type DevNoncesPgType = Array<Nullable<Int4>>;
// Sqlite has no native array type so use text
#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, AsExpression, FromSqlRow)]
#[serde(transparent)]
#[cfg_attr(feature = "postgres", diesel(sql_type = DevNoncesPgType))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = Text))]
pub struct DevNonces(DevNoncesInner);
pub type DevNoncesInner = Vec<Option<i32>>;
impl std::default::Default for DevNonces {
fn default() -> Self {
Self(Vec::new())
}
}
impl std::convert::AsRef<DevNoncesInner> for DevNonces {
fn as_ref(&self) -> &DevNoncesInner {
&self.0
}
}
impl std::convert::From<DevNoncesInner> for DevNonces {
fn from(value: DevNoncesInner) -> Self {
Self(value)
}
}
impl std::ops::Deref for DevNonces {
type Target = DevNoncesInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for DevNonces {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "postgres")]
impl deserialize::FromSql<DevNoncesPgType, Pg> for DevNonces {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let sql_val = <DevNoncesInner>::from_sql(value)?;
Ok(DevNonces(sql_val))
}
}
#[cfg(feature = "postgres")]
impl serialize::ToSql<DevNoncesPgType, Pg> for DevNonces {
fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
<DevNoncesInner as serialize::ToSql<DevNoncesPgType, Pg>>::to_sql(
&self.0,
&mut out.reborrow(),
)
}
}
#[cfg(feature = "sqlite")]
impl deserialize::FromSql<Text, Sqlite> for DevNonces
where
*const str: deserialize::FromSql<Text, Sqlite>,
{
fn from_sql(value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let s =
<*const str as deserialize::FromSql<diesel::sql_types::Text, Sqlite>>::from_sql(value)?;
let nonces = serde_json::from_str::<DevNonces>(unsafe { &*s })?;
Ok(nonces)
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, Sqlite> for DevNonces {
fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(serde_json::to_string(self)?);
Ok(serialize::IsNull::No)
}
}

View File

@ -0,0 +1,83 @@
use std::io::Cursor;
use std::ops::{Deref, DerefMut};
use diesel::backend::Backend;
#[cfg(feature = "postgres")]
use diesel::pg::Pg;
use diesel::sql_types::Binary;
#[cfg(feature = "sqlite")]
use diesel::sqlite::Sqlite;
use diesel::{deserialize, serialize};
use prost::Message;
use chirpstack_api::internal;
#[derive(Debug, Clone, PartialEq, AsExpression, FromSqlRow)]
#[diesel(sql_type = diesel::sql_types::Binary)]
pub struct DeviceSession(internal::DeviceSession);
impl DeviceSession {
pub fn new(m: internal::DeviceSession) -> Self {
DeviceSession(m)
}
}
impl std::convert::From<internal::DeviceSession> for DeviceSession {
fn from(u: internal::DeviceSession) -> Self {
Self(u)
}
}
impl std::convert::From<&internal::DeviceSession> for DeviceSession {
fn from(u: &internal::DeviceSession) -> Self {
Self::from(u.clone())
}
}
impl std::convert::Into<internal::DeviceSession> for DeviceSession {
fn into(self) -> internal::DeviceSession {
self.0
}
}
impl Deref for DeviceSession {
type Target = internal::DeviceSession;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DeviceSession {
fn deref_mut(&mut self) -> &mut internal::DeviceSession {
&mut self.0
}
}
impl<DB> deserialize::FromSql<Binary, DB> for DeviceSession
where
DB: Backend,
*const [u8]: deserialize::FromSql<Binary, DB>,
{
fn from_sql(value: <DB as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let bindata = <*const [u8] as deserialize::FromSql<Binary, DB>>::from_sql(value)?;
let ds = internal::DeviceSession::decode(&mut Cursor::new(unsafe { &*bindata }))?;
Ok(DeviceSession(ds))
}
}
#[cfg(feature = "postgres")]
impl serialize::ToSql<Binary, Pg> for DeviceSession {
fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
let encoded = self.encode_to_vec();
<Vec<u8> as serialize::ToSql<Binary, Pg>>::to_sql(&encoded, &mut out.reborrow())
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Binary, Sqlite> for DeviceSession {
fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(self.encode_to_vec());
Ok(serialize::IsNull::No)
}
}

View File

@ -0,0 +1,78 @@
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use diesel::backend::Backend;
use diesel::{deserialize, serialize};
#[cfg(feature = "postgres")]
use diesel::{pg::Pg, sql_types::Jsonb};
#[cfg(feature = "sqlite")]
use diesel::{sql_types::Text, sqlite::Sqlite};
#[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow)]
#[cfg_attr(feature = "postgres", diesel(sql_type = Jsonb))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = Text))]
pub struct KeyValue(HashMap<String, String>);
impl KeyValue {
pub fn new(m: HashMap<String, String>) -> Self {
KeyValue(m)
}
#[allow(clippy::wrong_self_convention)]
pub fn into_hashmap(&self) -> HashMap<String, String> {
self.0.clone()
}
}
impl Deref for KeyValue {
type Target = HashMap<String, String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for KeyValue {
fn deref_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.0
}
}
#[cfg(feature = "postgres")]
impl deserialize::FromSql<Jsonb, Pg> for KeyValue {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = <serde_json::Value as deserialize::FromSql<Jsonb, Pg>>::from_sql(value)?;
let kv: HashMap<String, String> = serde_json::from_value(value)?;
Ok(KeyValue(kv))
}
}
#[cfg(feature = "postgres")]
impl serialize::ToSql<Jsonb, Pg> for KeyValue {
fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
let value = serde_json::to_value(&self.0)?;
<serde_json::Value as serialize::ToSql<Jsonb, Pg>>::to_sql(&value, &mut out.reborrow())
}
}
#[cfg(feature = "sqlite")]
impl deserialize::FromSql<Text, Sqlite> for KeyValue
where
*const str: deserialize::FromSql<Text, Sqlite>,
{
fn from_sql(value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let s =
<*const str as deserialize::FromSql<diesel::sql_types::Text, Sqlite>>::from_sql(value)?;
let kv: HashMap<String, String> = serde_json::from_str(unsafe { &*s })?;
Ok(KeyValue(kv))
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, Sqlite> for KeyValue {
fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(serde_json::to_string(&self.0)?);
Ok(serialize::IsNull::No)
}
}

View File

@ -0,0 +1,101 @@
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use diesel::backend::Backend;
use diesel::{deserialize, serialize};
#[cfg(feature = "postgres")]
use diesel::{pg::Pg, sql_types::Jsonb};
#[cfg(feature = "sqlite")]
use diesel::{sql_types::Text, sqlite::Sqlite};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Measurement {
pub name: String,
pub kind: MeasurementKind,
}
#[allow(clippy::upper_case_acronyms)]
#[allow(non_camel_case_types)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum MeasurementKind {
// Unknown.
UNKNOWN,
// Incrementing counters which are not reset on each reporting.
COUNTER,
// Counters that do get reset upon reading.
ABSOLUTE,
// E.g. a temperature value.
GAUGE,
// E.g. a firmware version, true / false value.
STRING,
}
#[derive(Debug, Clone, AsExpression, FromSqlRow, PartialEq, Eq)]
#[cfg_attr(feature = "postgres", diesel(sql_type = Jsonb))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = Text))]
pub struct Measurements(HashMap<String, Measurement>);
impl Measurements {
pub fn new(m: HashMap<String, Measurement>) -> Self {
Measurements(m)
}
#[allow(clippy::wrong_self_convention)]
pub fn into_hashmap(&self) -> HashMap<String, Measurement> {
self.0.clone()
}
}
impl Deref for Measurements {
type Target = HashMap<String, Measurement>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Measurements {
fn deref_mut(&mut self) -> &mut HashMap<String, Measurement> {
&mut self.0
}
}
#[cfg(feature = "postgres")]
impl deserialize::FromSql<Jsonb, Pg> for Measurements {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = <serde_json::Value as deserialize::FromSql<Jsonb, Pg>>::from_sql(value)?;
let kv: HashMap<String, Measurement> = serde_json::from_value(value)?;
Ok(Measurements::new(kv))
}
}
#[cfg(feature = "postgres")]
impl serialize::ToSql<Jsonb, Pg> for Measurements {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Pg>) -> serialize::Result {
let value = serde_json::to_value(&self.0)?;
<serde_json::Value as serialize::ToSql<Jsonb, Pg>>::to_sql(&value, &mut out.reborrow())
}
}
#[cfg(feature = "sqlite")]
impl deserialize::FromSql<Text, Sqlite> for Measurements
where
*const str: deserialize::FromSql<Text, Sqlite>,
{
fn from_sql(value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let s =
<*const str as deserialize::FromSql<diesel::sql_types::Text, Sqlite>>::from_sql(value)?;
let kv: HashMap<String, Measurement> = serde_json::from_str(unsafe { &*s })?;
Ok(Measurements::new(kv))
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, Sqlite> for Measurements {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Sqlite>) -> serialize::Result {
let value = serde_json::to_string(&self.0)?;
out.set_value(value);
Ok(serialize::IsNull::No)
}
}

View File

@ -0,0 +1,37 @@
mod big_decimal;
mod dev_nonces;
mod device_session;
mod key_value;
mod measurements;
mod multicast_group_scheduling_type;
mod uuid;
pub use big_decimal::BigDecimal;
pub use dev_nonces::*;
pub use device_session::DeviceSession;
pub use key_value::KeyValue;
pub use measurements::*;
pub use multicast_group_scheduling_type::MulticastGroupSchedulingType;
pub use uuid::Uuid;
#[cfg(feature = "postgres")]
pub mod sql_types {
pub type Timestamptz = diesel::sql_types::Timestamptz;
pub type JsonT = diesel::sql_types::Jsonb;
pub type Uuid = diesel::sql_types::Uuid;
}
#[cfg(feature = "sqlite")]
pub mod sql_types {
pub type Timestamptz = diesel::sql_types::TimestamptzSqlite;
// TODO: sqlite is adding "jsonb" support, different from postgres
// So we may switch the column to blob?
// see https://sqlite.org/draft/jsonb.html
pub type JsonT = diesel::sql_types::Text;
// Sqlite has no native json type so use text
pub type Uuid = diesel::sql_types::Text;
}

View File

@ -0,0 +1,75 @@
use std::fmt;
use std::str::FromStr;
use diesel::backend::Backend;
use diesel::sql_types::Text;
#[cfg(feature = "sqlite")]
use diesel::sqlite::Sqlite;
use diesel::{deserialize, serialize};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, AsExpression, FromSqlRow)]
#[allow(clippy::upper_case_acronyms)]
#[allow(non_camel_case_types)]
#[diesel(sql_type = diesel::sql_types::Text)]
pub enum MulticastGroupSchedulingType {
// Delay.
DELAY,
// GPS time.
GPS_TIME,
}
impl fmt::Display for MulticastGroupSchedulingType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl<DB> deserialize::FromSql<Text, DB> for MulticastGroupSchedulingType
where
DB: Backend,
*const str: deserialize::FromSql<Text, DB>,
{
fn from_sql(value: <DB as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let string = <*const str>::from_sql(value)?;
Ok(Self::from_str(unsafe { &*string })?)
}
}
#[cfg(feature = "postgres")]
impl serialize::ToSql<Text, diesel::pg::Pg> for MulticastGroupSchedulingType
where
str: serialize::ToSql<Text, diesel::pg::Pg>,
{
fn to_sql<'b>(
&'b self,
out: &mut serialize::Output<'b, '_, diesel::pg::Pg>,
) -> serialize::Result {
<str as serialize::ToSql<Text, diesel::pg::Pg>>::to_sql(
&self.to_string(),
&mut out.reborrow(),
)
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<Text, Sqlite> for MulticastGroupSchedulingType {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Sqlite>) -> serialize::Result {
out.set_value(self.to_string());
Ok(serialize::IsNull::No)
}
}
impl FromStr for MulticastGroupSchedulingType {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(match s {
"DELAY" => MulticastGroupSchedulingType::DELAY,
"GPS_TIME" => MulticastGroupSchedulingType::GPS_TIME,
_ => {
return Err(anyhow!("Unexpected MulticastGroupSchedulingType: {}", s));
}
})
}
}

View File

@ -0,0 +1,87 @@
use diesel::backend::Backend;
#[cfg(feature = "postgres")]
use diesel::pg::Pg;
#[cfg(feature = "sqlite")]
use diesel::sqlite::Sqlite;
use diesel::{deserialize, serialize};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Eq, PartialEq, AsExpression, FromSqlRow)]
#[serde(transparent)]
#[cfg_attr(feature = "postgres", diesel(sql_type = diesel::sql_types::Uuid))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = diesel::sql_types::Text))]
pub struct Uuid(uuid::Uuid);
impl std::convert::From<uuid::Uuid> for Uuid {
fn from(u: uuid::Uuid) -> Self {
Self(u)
}
}
impl std::convert::From<&uuid::Uuid> for Uuid {
fn from(u: &uuid::Uuid) -> Self {
Self::from(u.clone())
}
}
impl std::convert::Into<uuid::Uuid> for Uuid {
fn into(self) -> uuid::Uuid {
self.0
}
}
impl std::ops::Deref for Uuid {
type Target = uuid::Uuid;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for Uuid {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::fmt::Display for Uuid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
#[cfg(feature = "postgres")]
impl deserialize::FromSql<diesel::sql_types::Uuid, Pg> for Uuid {
fn from_sql(value: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let u = <uuid::Uuid>::from_sql(value)?;
Ok(Uuid(u))
}
}
#[cfg(feature = "postgres")]
impl serialize::ToSql<diesel::sql_types::Uuid, Pg> for Uuid {
fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, Pg>) -> serialize::Result {
<uuid::Uuid as serialize::ToSql<diesel::sql_types::Uuid, Pg>>::to_sql(
&self.0,
&mut out.reborrow(),
)
}
}
#[cfg(feature = "sqlite")]
impl deserialize::FromSql<diesel::sql_types::Text, Sqlite> for Uuid {
fn from_sql(value: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let s =
<*const str as deserialize::FromSql<diesel::sql_types::Text, Sqlite>>::from_sql(value)?;
let u = uuid::Uuid::try_parse(unsafe { &*s })?;
Ok(Uuid(u))
}
}
#[cfg(feature = "sqlite")]
impl serialize::ToSql<diesel::sql_types::Text, Sqlite> for Uuid {
fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(self.0.to_string());
Ok(serialize::IsNull::No)
}
}

Some files were not shown because too many files have changed in this diff Show More