diff --git a/Cargo.lock b/Cargo.lock index 36d94d0..3e852f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -984,6 +984,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "iotishnik-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "bstr", + "bytes", + "chrono", + "clap", + "derive_more", + "dotenvy", + "fred", + "heapless", + "hex", + "hifitime", + "lazy_static", + "nom", + "ntex", + "phf", + "regex", + "rust_decimal", + "serde", + "serde_json", + "serde_qs", + "serde_with", + "smallstr", + "thiserror", + "tokio", + "ufmt", +] + [[package]] name = "is-terminal" version = "0.4.7" @@ -1156,37 +1187,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -[[package]] -name = "narodmon-server" -version = "0.1.0" -dependencies = [ - "anyhow", - "bstr", - "bytes", - "chrono", - "clap", - "derive_more", - "dotenvy", - "fred", - "heapless", - "hex", - "hifitime", - "lazy_static", - "nom", - "ntex", - "phf", - "regex", - "rust_decimal", - "serde", - "serde_json", - "serde_qs", - "serde_with", - "smallstr", - "thiserror", - "tokio", - "ufmt", -] - [[package]] name = "nom" version = "7.1.3" diff --git a/Cargo.toml b/Cargo.toml index a909712..e76204a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "narodmon-server" +name = "iotishnik-server" version = "0.1.0" edition = "2021" @@ -15,7 +15,7 @@ derive_more = "0.99.17" dotenvy = "0.15.7" fred = { version = "6.3.0", features = ["nom"] } heapless = { version = "0.7.16", features = ["ufmt-impl"] } -hex = { version = "0.4.3", default-features = false } +hex = { version = "0.4.3", default-features = false, features = ["std", "alloc"] } hifitime = "3.8.2" lazy_static = "1.4.0" nom = { version = "7.1.3", default-features = false, features = ["std", "alloc"] } @@ -30,4 +30,4 @@ serde_with = { version = "3.6.1", features = ["hex"] } smallstr = { version = "0.3.0", features = ["std", "union"] } thiserror = "1.0.40" tokio = { version = "1.28.2", features = ["full"] } -ufmt = "0.2.0" +ufmt = { version = "0.2.0", features = ["std"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..c0e2290 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# IoTishnik +Платформа для обработки данных окружающей среды с IoT устройств. + +## Описание идеи +> ### Что есть сейчас: +> +> Есть куча девайсов, которые сейчас со своих сенсоров, для публичного и в некоторых случаях частного использования выкладывают на сервер narodmon.ru данные. +> +> ### В чём проблема narodmon.ru: +> +>1. Там всё такоееее легаси, что ты не представляешь. Речь идёт о cgi скриптах написанных на bash. +>2. Разрабы ищут помощь в виде лиц, в которых можно плевать без последствий сколько угодно. +>3. Отсутствие тех поддержки для обычных пользователей от слову совсем. +>4. Ужасное API которое ужасное не только из-за своей кривизны. В нём нужно отправлять телеметрию устройств, админ панель разработчика считай отсутствует. О OAuth2 вообще говорить не стоит. И если ты отправишь неправильные запросы с клиента своего, твой "api ключ" приложения заблокируют, и пофиг на то, что его можно спиздить с чужих приложений. +>5. Интерфейс просто ужас. Можешь сам зайти и посмотреть, даже писать про это не буду. Даже сам UX в изоляции от UI ужасный. +>6. Документация API не соблюдается. Даже когда мы с Андреем написали, что, мол, у вас поля изменились, им было плевать. +>7. Работа с приватными датчиками ужастная и требует денег для того, чтобы оно вообще работало. +>8. Некоторые очень важные API для устройств (такие как MQTT, на которых работают большое количество готовых продуктов не заточенные под narodmon.ru) доступны тоже только по подписке разрабам. +> +> ### Наше решение: +> +> Сделать с нуля своё решение, которое будет горизонтально масштабируемое, с поддержкой старых API для поддержки устройств сделанных под narodmon.ru . Выдать разрабам которые хотят новое и мощное API это API вместе с SDK в виде либ под ардуинку и т.п. Сделать поддержку OAuth для входа используя чужие сервисы и не только. И многое другое что я мб забыл упомянуть \ No newline at end of file diff --git a/docs/kv_db_arch.md b/docs/kv_db_arch.md index c468b02..74a3cff 100644 --- a/docs/kv_db_arch.md +++ b/docs/kv_db_arch.md @@ -8,6 +8,8 @@ - Поля - Вся информация о девайсе - `devices_{device_id}_{tai_timestamp}_{sensor_id}` + - Только значение + - `devices_{device_id}` - Поля - - `value` - - `unit` + - `exists`: bool + - `unit`: str diff --git a/src/ingest_protocol/mod.rs b/src/ingest_protocol/mod.rs index 7ea31e5..627fa9f 100644 --- a/src/ingest_protocol/mod.rs +++ b/src/ingest_protocol/mod.rs @@ -1,3 +1,5 @@ +//! Модуль для парсинга всего, что связанно с данными от устройств. + pub mod error; mod packet_types; pub mod parser; diff --git a/src/ingest_protocol/packet_types.rs b/src/ingest_protocol/packet_types.rs index 902964b..e1c07b5 100644 --- a/src/ingest_protocol/packet_types.rs +++ b/src/ingest_protocol/packet_types.rs @@ -1,16 +1,34 @@ -use crate::hashes::SupportedUnit; +//! Сборник типов для внутренней обработки данных с датчиков. +//! +//! Предполагается, что struct-ы будут совместимы с JSON API для передачи данных датчиков. + +// Не забывайте про: +// #[serde(rename = "...")] +// #[serde(alias = "...")] + +use crate::utils::SupportedUnit; use crate::ingest_protocol::error::Error; use crate::ingest_protocol::parser::parse_mac_address; use hifitime::Epoch; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; -use serde_with::formats::Separator; use serde_with::serde_as; use std::collections::HashSet; use std::hash::{Hash, Hasher}; +/// Данные с одного датчика. +/// +/// Основной идентификатор - поле `mac`. В случае с [crate::web_server::old_device_sensor_api], +/// `mac` может быть не полем, а названием ключа в параметрах. Из-за двусмысленности документации +/// NarodMon, `mac` может означать EUI-48 совместимый MAC адрес, или же просто +/// уникальный идентификатор. +/// +/// Парсинг этих данных отличается в разных транспортных протоколах. +/// Для HTTP /post или /get: см. [crate::web_server::old_device_sensor_api] +/// Для TCP/UDP: TODO +/// Для MQTT: TODO #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct SensorValue { pub mac: String, @@ -26,14 +44,9 @@ impl Hash for SensorValue { } } -pub struct DashSeparator {} - -impl Separator for DashSeparator { - fn separator() -> &'static str { - "-" - } -} - +/// Функция-помощник для [MacAsArray], предназначенный для использования с [serde_with]. +/// +/// Преобразует MAC-адрес. fn mac_as_array(value: &str) -> Result<[u8; 6], Error<&str>> { Ok(parse_mac_address(value)?.1) } diff --git a/src/ingest_protocol/parser.rs b/src/ingest_protocol/parser.rs index 2b4fdfe..ffc4133 100644 --- a/src/ingest_protocol/parser.rs +++ b/src/ingest_protocol/parser.rs @@ -3,7 +3,6 @@ use nom::bytes::complete::tag; use nom::bytes::complete::take_until1; use nom::bytes::complete::{take, take_while, take_while1}; use nom::character::complete::hex_digit1; -use nom::{Parser}; use std::str::FromStr; use crate::ingest_protocol::error::Error; diff --git a/src/main.rs b/src/main.rs index 12c99ed..5539a55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,69 +1,13 @@ -/* -Три датчика реалтайм: -#26-94-1D-75-C2-F8 -#T1#6.93 -#H1#21 -#P1#700.91 -## +#![doc = include_str!("../README.md")] -Формат пакета данных: -#MAC[#NAME]\n -#mac1#value1[#time1][#name1]\n -... -#macN#valueN[#timeN][#nameN]\n -## - -Загрузка истории показаний: -#26-94-1D-75-C2-F8 -#T1#6.93#1687006667 -#T1#10.17#1687006067 -#T1#27.26#1687005467 -## - -C названием и координатами: -#26-94-1D-75-C2-F8#Метео -#OWNER#nm17 -#T1#6.93#Улица -#T2#27.26#Дом -#P1#700.91#Барометр -#LAT#54.308997 -#LON#48.395861 -#ALT#233 -## - */ #![feature(try_blocks)] extern crate core; -mod hashes; +mod utils; mod ingest_protocol; mod web_server; - - - - - - - - - - - - - use crate::web_server::server_main; - -/*fn parse_sensor_value(input: Vec<&str>) -> MyIError, NarodMonValues> { - Ok( - (input, NarodMonValues { - mac: Default::default(), - value: Default::default(), - time: None, - name: None, - }) - ) -}*/ - struct Params {} #[ntex::main] diff --git a/src/hashes.rs b/src/utils.rs similarity index 78% rename from src/hashes.rs rename to src/utils.rs index 77fdb8c..0f4e19a 100644 --- a/src/hashes.rs +++ b/src/utils.rs @@ -1,11 +1,20 @@ +//! Глобальный модуль для вспомогательных типов и утилит. +//! + + use phf::phf_map; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Cow; + +/// Поддерживаемые типы. +/// +/// TODO: Решить необходимо ли к данным прикреплять единицы измерения. +/// TODO: Сейчас вообще сомнительно оставлять ли это или нет. #[derive(Debug, Clone, Hash, PartialEq, Eq)] #[repr(u64)] pub enum SupportedUnit { - Celsius, // Needs verification > 273.15 + Celsius, // Needs verification > -273.15 Percentage, // Needs verification >= 100 && <= 0 MillimeterHg, // Needs verification UVIndex, // Needs verification @@ -56,6 +65,7 @@ impl<'de> Deserialize<'de> for SupportedUnit { } } +/// Таблица преобразования текстового представления единиц в значения [SupportedUnit]. static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! { "C" => SupportedUnit::Celsius, "%" => SupportedUnit::Percentage, diff --git a/src/web_server/app_error.rs b/src/web_server/app_error.rs index 1ff7276..38bcad5 100644 --- a/src/web_server/app_error.rs +++ b/src/web_server/app_error.rs @@ -1,22 +1,22 @@ use derive_more::Display; use fred::prelude::*; -use ntex::http::{StatusCode}; +use ntex::http::StatusCode; use ntex::web; use ntex::web::{HttpRequest, HttpResponse}; - - use thiserror::Error; - use crate::insert_header; -use crate::web_server::old_devices_api::QSParserError; +use crate::web_server::old_device_sensor_api::qs_parser::QSParserError; use rust_decimal::Decimal; use serde_json::json; +/// Главный объект ошибки [std::error::Error] для всего Web API. +/// +/// В целом, все Result у Web сервера должны использовать этот Error. #[derive(Debug, Error, Display)] pub enum AppError { #[display(fmt = "IDK")] @@ -48,6 +48,9 @@ pub enum AppError { json_err: Option, query_error: Option, }, + + #[display(fmt = "IDK")] + DeviceNotFound(String) } impl web::error::WebResponseError for AppError { @@ -61,6 +64,7 @@ impl web::error::WebResponseError for AppError { AppError::ApiKeyInvalid { .. } => StatusCode::BAD_REQUEST, AppError::UnknownBody { .. } => StatusCode::BAD_REQUEST, AppError::QSError(..) => StatusCode::BAD_REQUEST, + AppError::DeviceNotFound(..) => StatusCode::BAD_REQUEST } } @@ -76,6 +80,7 @@ impl web::error::WebResponseError for AppError { "Can't figure out where and in what encoding the main data is" } AppError::QSError(..) => "UrlEncoded body or query params are incorrect", + AppError::DeviceNotFound(..) => "Device not found", }; let status_code = self.status_code(); diff --git a/src/web_server/mod.rs b/src/web_server/mod.rs index e6a12d5..8e591d0 100644 --- a/src/web_server/mod.rs +++ b/src/web_server/mod.rs @@ -1,12 +1,19 @@ -use crate::web_server::old_app_api::old_api_handler; +//! Модуль веб сервера. +//! +//! Все модули отвечают только за Web сторону. Такие вещи как +//! [crate::web_server::old_device_sensor_api] отвечают только за веб версию. +//! +//! TODO: Начать работу над TCP/UDP и MQTT сервером + +use old_app_api::old_api_handler; use fred::bytes_utils::Str; use fred::prelude::*; -pub(crate) mod app_error; +pub mod app_error; pub mod old_app_api; -mod old_devices_api; +pub mod old_device_sensor_api; pub mod utils; #[derive(Clone)] @@ -14,7 +21,7 @@ pub struct NMAppState { pub redis_client: RedisClient, } -use crate::web_server::old_devices_api::device_handler; +use crate::web_server::old_device_sensor_api::device_handler; use ntex::web; diff --git a/src/web_server/old_app_api/config_app.rs b/src/web_server/old_app_api/config_app.rs deleted file mode 100644 index 961c9b9..0000000 --- a/src/web_server/old_app_api/config_app.rs +++ /dev/null @@ -1,45 +0,0 @@ - - -use crate::web_server::app_error::AppError; -use crate::web_server::old_app_api::handlers::{app_init, version}; -use crate::web_server::old_app_api::types::{AppInitRequest, MandatoryParams}; -use crate::web_server::utils::redis::is_api_key_valid; -use crate::web_server::NMAppState; -use nom::AsBytes; -use ntex::util::Bytes; -use ntex::web; -use ntex::web::types::State; - - -pub async fn old_api_handler( - app_state: State, - body_bytes: Bytes, -) -> Result { - if body_bytes.len() > 10 * 1024 { - // 10 KiB - return Err(AppError::RequestTooLarge); - } - - let body_bytes = body_bytes.as_bytes(); - - let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes)?; // TODO: Simd-JSON - - // Ignore clippy singlematch - match mandatory_params.cmd.as_ref() { - "version" => return version((), &app_state).await, - _ => {} - } - - is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?; - - match mandatory_params.cmd.as_ref() { - "appInit" => { - let body: AppInitRequest = serde_json::from_slice(body_bytes)?; - - app_init(body, &app_state).await - } - _ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())), - } - - //Ok("fuck") -} diff --git a/src/web_server/old_app_api/handlers/methods.rs b/src/web_server/old_app_api/handlers/methods.rs deleted file mode 100644 index e1a9893..0000000 --- a/src/web_server/old_app_api/handlers/methods.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::web_server::app_error::AppError; -use crate::web_server::old_app_api::types::AppInitRequest; -use crate::web_server::NMAppState; - -use serde_json::{json}; - - -use crate::insert_header; -use fred::interfaces::KeysInterface; -use ntex::http::StatusCode; -use ntex::web; - - - -pub async fn app_init( - _body: AppInitRequest<'_>, - app_state: &NMAppState, -) -> Result { - let _: () = app_state - .redis_client - .set("test", 123, None, None, true) - .await?; - - Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!")) -} - -pub async fn version(_body: (), _app_state: &NMAppState) -> Result { - let mut resp = web::HttpResponse::build(StatusCode::OK).json(&json!({ - "version": "indev", - "iotishnik": true - })); - - insert_header!(resp.headers_mut(), "Cache-Control", "no-cache"); - - Ok(resp) -} diff --git a/src/web_server/old_app_api/handlers/mod.rs b/src/web_server/old_app_api/handlers/mod.rs index 9cb9400..e1a9893 100644 --- a/src/web_server/old_app_api/handlers/mod.rs +++ b/src/web_server/old_app_api/handlers/mod.rs @@ -1,7 +1,36 @@ -mod methods; +use crate::web_server::app_error::AppError; +use crate::web_server::old_app_api::types::AppInitRequest; +use crate::web_server::NMAppState; + +use serde_json::{json}; -pub use methods::*; +use crate::insert_header; +use fred::interfaces::KeysInterface; +use ntex::http::StatusCode; +use ntex::web; +pub async fn app_init( + _body: AppInitRequest<'_>, + app_state: &NMAppState, +) -> Result { + let _: () = app_state + .redis_client + .set("test", 123, None, None, true) + .await?; + + Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!")) +} + +pub async fn version(_body: (), _app_state: &NMAppState) -> Result { + let mut resp = web::HttpResponse::build(StatusCode::OK).json(&json!({ + "version": "indev", + "iotishnik": true + })); + + insert_header!(resp.headers_mut(), "Cache-Control", "no-cache"); + + Ok(resp) +} diff --git a/src/web_server/old_app_api/mod.rs b/src/web_server/old_app_api/mod.rs index c1572dd..6abc3f3 100644 --- a/src/web_server/old_app_api/mod.rs +++ b/src/web_server/old_app_api/mod.rs @@ -1,5 +1,53 @@ -mod config_app; +//! Модуль обработки данных для приложений конечных пользователей, которые используют старый API. + mod handlers; mod types; -pub use config_app::old_api_handler; +use ntex::web::types::State; +use ntex::util::Bytes; +use ntex::web; +use nom::AsBytes; +use crate::web_server::app_error::AppError; +use crate::web_server::NMAppState; +use crate::web_server::old_app_api::handlers::{app_init, version}; +use crate::web_server::old_app_api::types::{AppInitRequest, MandatoryParams}; +use crate::web_server::utils::redis::is_api_key_valid; + + +/// Обработчик запросов от приложений. +/// +/// Отвечает за разделение на функции по `cmd`. +/// +/// Вызывается напрямую из ntex приложения. +pub async fn old_api_handler( + app_state: State, + body_bytes: Bytes, +) -> Result { + if body_bytes.len() > 10 * 1024 { + // 10 KiB + return Err(AppError::RequestTooLarge); + } + + let body_bytes = body_bytes.as_bytes(); + + let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes)?; // TODO: Simd-JSON + + // Ignore clippy singlematch + match mandatory_params.cmd.as_ref() { + "version" => return version((), &app_state).await, + _ => {} + } + + is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?; + + match mandatory_params.cmd.as_ref() { + "appInit" => { + let body: AppInitRequest = serde_json::from_slice(body_bytes)?; + + app_init(body, &app_state).await + } + _ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())), + } + + //Ok("fuck") +} diff --git a/src/web_server/old_app_api/types/mod.rs b/src/web_server/old_app_api/types/mod.rs index 31d83e3..de9b9bb 100644 --- a/src/web_server/old_app_api/types/mod.rs +++ b/src/web_server/old_app_api/types/mod.rs @@ -1,4 +1,3 @@ - use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -24,6 +23,11 @@ pub struct AddLikeRequest { pub version: u64, } +/// Обязательные параметры у JSON app API. +/// +/// При обработке входящих данных производиться два запроса [serde_json::from_str]. Один вызов пытается +/// получить [MandatoryParams], другой в зависимости от `cmd`. Это позволяет не добвалять поля из +/// этой структуры в каждом специфичном типе. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MandatoryParams<'a> { #[serde(borrow)] @@ -32,6 +36,11 @@ pub struct MandatoryParams<'a> { #[serde(borrow)] pub lang: Cow<'a, str>, + /// Уникальный ID клиента. + /// + /// Используется на подобии как куки PHPSESSID в php. + /// + /// См. также: #[serde(borrow)] pub uuid: Cow<'a, str>, diff --git a/src/web_server/old_device_sensor_api/mod.rs b/src/web_server/old_device_sensor_api/mod.rs new file mode 100644 index 0000000..9a140df --- /dev/null +++ b/src/web_server/old_device_sensor_api/mod.rs @@ -0,0 +1,124 @@ +//! Модуль обработки данных с устройств, которые используют старый API. + +pub mod qs_parser; + +use crate::ingest_protocol::{NMDeviceDataPacket, NMJsonPacket}; +use crate::web_server::app_error::AppError; + + +use fred::types::RedisMap; +use ntex::http::{HttpMessage, StatusCode}; +use ntex::util::{Bytes, HashMap}; +use ntex::{http, web}; +use fred::prelude::*; +use hifitime::Epoch; +use ntex::web::types::State; +use thiserror::Error; +use ufmt::uwrite; +use crate::web_server::NMAppState; +use crate::web_server::old_device_sensor_api::qs_parser::QSParserError; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Device not found")] + DeviceNotFound(String), + #[error("Time sent with the device is way too behind now")] + TimeIsLongBehindNow, + #[error("{0}")] + QSParserError(#[from] QSParserError), +} + + +/// Обработчик данных датчиков с устройств. +/// +/// Слушает /post и /get. +/// Для того чтобы пользователям было легче, на оба пути можно отправлять и POST и GET. +/// +/// На POST можно отправлять JSON или url-encoded тело, на GET - только через Query String. +pub async fn device_handler<'a>( + request: web::HttpRequest, + body: Bytes, + app_state: State, +) -> Result { + let mut real_body: Option = None; + let mut json_error = None; + let mut query_error = None; + + dbg!(&request.content_type()); + dbg!(&request.query_string()); + + if request.method() == http::Method::POST { + match request.content_type() { + "application/json" => match serde_json::from_slice::(body.as_ref()) { + Ok(json_body) => real_body = Some(json_body), + Err(error) => json_error = Some(error), + }, + "application/x-www-form-urlencoded" => { + match serde_qs::from_bytes::(body.as_ref()) { + Ok(qs_body) => { + real_body = Some(NMJsonPacket { + devices: Vec::from([qs_body]), + }) + } + Err(error) => query_error = Some(error), + } + } + _ => {} + } + } else if request.method() == http::Method::GET { + match serde_qs::from_str::(request.query_string()) { + Ok(qs_body) => { + real_body = Some(NMJsonPacket { + devices: Vec::from([qs_body]), + }) + } + Err(error) => query_error = Some(error), + } + } + + if let Some(real_body) = real_body { + for device in real_body.devices { + let mut device_key_str = String::new(); + + let now = Epoch::now().unwrap(); + let mut device_time = device.time + .unwrap_or(now); + + // TODO: Добавить гистерезис + // Отчёт совместимости: отсутствует + if device_time > now { + device_time = now; + } + + let device_tai_timestamp = device_time + .to_duration_since_j1900() + .to_seconds(); + + uwrite!(device_key_str, "devices_{}", hex::encode(device.mac)); + + let device_exists: bool = app_state.redis_client.hget(device_key_str.as_str(), "exists").await?; + + if !device_exists { + return Err(AppError::DeviceNotFound(hex::encode(&device.mac))); + } + + // devices_{device_id}_{tai_timestamp}_{sensor_id} + for sensor in device.values { + let mut device_report_key_str = String::new(); + uwrite!(&mut device_report_key_str, "devices_{}_{}_{}", hex::encode(device.mac), device_tai_timestamp.to_string(), sensor.mac); + + app_state.redis_client.set( + device_key_str.as_str(), sensor.value.to_string(), None, None, false, + ).await?; + } + } + } else { + return Err(AppError::UnknownBody { + json_err: json_error, + query_error, + }); + } + + Ok(web::HttpResponseBuilder::new(StatusCode::OK) + .finish()) +} diff --git a/src/web_server/old_devices_api/mod.rs b/src/web_server/old_device_sensor_api/qs_parser.rs similarity index 61% rename from src/web_server/old_devices_api/mod.rs rename to src/web_server/old_device_sensor_api/qs_parser.rs index 1410e13..1bf1e3e 100644 --- a/src/web_server/old_devices_api/mod.rs +++ b/src/web_server/old_device_sensor_api/qs_parser.rs @@ -1,21 +1,14 @@ use std::collections::HashSet; use std::num::ParseFloatError; use std::str::FromStr; -use crate::ingest_protocol::error::Error; -use crate::ingest_protocol::parser::parse_mac_address; -use crate::ingest_protocol::{NMDeviceDataPacket, NMJsonPacket, SensorValue}; -use crate::web_server::app_error::AppError; - - -use ntex::http::{HttpMessage, StatusCode}; -use ntex::util::{Bytes, HashMap}; -use ntex::{http, web}; use std::sync::Arc; -use fred::bytes_utils::Str; -use hifitime::efmt::consts::ISO8601; use hifitime::Epoch; +use ntex::util::HashMap; use rust_decimal::Decimal; use thiserror::Error; +use crate::ingest_protocol::error::Error; +use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue}; +use crate::ingest_protocol::parser::parse_mac_address; /// В иделае было бы хорошо сделать всё как у [serde_json::Error], но это слишком большая морока #[derive(Error, Clone, Debug)] @@ -47,6 +40,11 @@ impl From for QSParserError { } } +/// Преобразование оставшихся параметров в urlencoded теле или query string в данные с датчиков +/// [SensorValue]. +/// +/// Формат: `=`. +/// Других данных на подобии названия и времени нет. pub fn qs_rest_to_values(parsed: HashMap) -> Result, QSParserError> { let mut hashset = HashSet::new(); @@ -118,56 +116,4 @@ pub async fn parse_nm_qs_format(input: &str) -> Result( - request: web::HttpRequest, - body: Bytes, -) -> Result { - let mut real_body: Option = None; - let mut json_error = None; - let mut query_error = None; - - dbg!(&request.content_type()); - dbg!(&request.query_string()); - - if request.method() == http::Method::POST { - match request.content_type() { - "application/json" => match serde_json::from_slice::(body.as_ref()) { - Ok(json_body) => real_body = Some(json_body), - Err(error) => json_error = Some(error), - }, - "application/x-www-form-urlencoded" => { - match serde_qs::from_bytes::(body.as_ref()) { - Ok(qs_body) => { - real_body = Some(NMJsonPacket { - devices: Vec::from([qs_body]), - }) - } - Err(error) => query_error = Some(error), - } - } - _ => {} - } - } else if request.method() == http::Method::GET { - match serde_qs::from_str::(request.query_string()) { - Ok(qs_body) => { - real_body = Some(NMJsonPacket { - devices: Vec::from([qs_body]), - }) - } - Err(error) => query_error = Some(error), - } - } - - if let Some(_body) = real_body { - // TODO - } else { - return Err(AppError::UnknownBody { - json_err: json_error, - query_error, - }); - } - - Ok(web::HttpResponse::build(StatusCode::OK).finish()) -} +} \ No newline at end of file diff --git a/src/web_server/utils/mod.rs b/src/web_server/utils/mod.rs index 5fed630..672afe1 100644 --- a/src/web_server/utils/mod.rs +++ b/src/web_server/utils/mod.rs @@ -1,3 +1,6 @@ +//! Сборник полезных функций которые используются в многих местах одновременно или слишком +//! неспециализированные. + pub mod redis; #[macro_export] diff --git a/src/web_server/utils/redis.rs b/src/web_server/utils/redis.rs index fe6a9da..cbb6a46 100644 --- a/src/web_server/utils/redis.rs +++ b/src/web_server/utils/redis.rs @@ -1,3 +1,5 @@ +//! Сборник утилит для работы с Redis. + use crate::web_server::app_error::AppError; use fred::prelude::*; use heapless::String as HeaplessString; @@ -6,13 +8,17 @@ use regex::Regex; use ufmt::uwrite; lazy_static! { + /// Разрешённые знаки для API ключа. static ref ALLOWED_API_KEY_CHARACTERS: Regex = Regex::new("[a-zA-Z0-9]{13}").unwrap(); } +/// Описание полей в KV DB у `apikey_{}`. pub struct ApiKeyDescription { + /// ID владельца API ключа. apikey_owner: i64, } +/// Проверка API ключа на валидность. pub async fn is_api_key_valid( client: &RedisClient, api_key: &str,