From 645284d348451237702c6c0ff008e9f2f954153c Mon Sep 17 00:00:00 2001 From: Mads Mogensen Date: Fri, 5 Apr 2024 10:43:47 +0200 Subject: [PATCH] backend: Use newtypes for ids Ids are now typed to avoid type confusion Rerun actions --- backend/Cargo.lock | 35 +++++++++++++++++++++++++++++++ backend/Cargo.toml | 1 + backend/src/data_model.rs | 1 + backend/src/data_model/account.rs | 6 ++++++ backend/src/data_model/device.rs | 11 ++++++++-- backend/src/data_model/event.rs | 10 ++++++--- backend/src/data_model/task.rs | 15 ++++++++++--- backend/src/data_model/time.rs | 2 +- backend/src/extractors/auth.rs | 10 +++++---- backend/src/handlers/accounts.rs | 8 +++---- backend/src/handlers/devices.rs | 18 +++++----------- backend/src/handlers/tasks.rs | 9 ++++---- backend/src/main.rs | 6 +++--- backend/src/protocol/devices.rs | 4 +++- backend/src/protocol/tasks.rs | 9 +++++--- 15 files changed, 103 insertions(+), 42 deletions(-) create mode 100644 backend/src/data_model/account.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index e5f1f011..8f1a7c48 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -268,6 +268,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -379,6 +385,19 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -1226,6 +1245,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.32" @@ -1258,6 +1286,7 @@ dependencies = [ "argon2", "axum", "chrono", + "derive_more", "dotenv", "http-body-util", "serde", @@ -1275,6 +1304,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" version = "1.0.197" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 05e3c744..b4e3ce99 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -16,6 +16,7 @@ serde = "1.0" serde_with = "3.7" dotenv = "0.15" argon2 = { version = "0.5", features = ["std"] } +derive_more = "0.99" [dev-dependencies] tower = { version = "0.4", features = ["util"] } diff --git a/backend/src/data_model.rs b/backend/src/data_model.rs index 46d85634..7e89e72c 100644 --- a/backend/src/data_model.rs +++ b/backend/src/data_model.rs @@ -1,3 +1,4 @@ +pub mod account; pub mod device; mod event; pub mod task; diff --git a/backend/src/data_model/account.rs b/backend/src/data_model/account.rs new file mode 100644 index 00000000..56020619 --- /dev/null +++ b/backend/src/data_model/account.rs @@ -0,0 +1,6 @@ +use derive_more::{From, Into}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug, sqlx::Type, PartialEq, Eq, From, Into)] +#[sqlx(transparent)] +pub struct AccountId(i64); diff --git a/backend/src/data_model/device.rs b/backend/src/data_model/device.rs index 22c33991..10c33222 100644 --- a/backend/src/data_model/device.rs +++ b/backend/src/data_model/device.rs @@ -1,8 +1,15 @@ +use derive_more::{From, Into}; use serde::{Deserialize, Serialize}; +use super::account::AccountId; + +#[derive(Deserialize, Serialize, Debug, sqlx::Type, PartialEq, Eq, From, Into, Clone, Copy)] +#[sqlx(transparent)] +pub struct DeviceId(i64); + #[derive(Serialize, Deserialize)] pub struct Device { - pub id: i64, + pub id: DeviceId, pub effect: f64, - pub account_id: i64, + pub account_id: AccountId, } diff --git a/backend/src/data_model/event.rs b/backend/src/data_model/event.rs index dcdb39ef..4f616547 100644 --- a/backend/src/data_model/event.rs +++ b/backend/src/data_model/event.rs @@ -1,11 +1,15 @@ use serde::{Deserialize, Serialize}; -use super::time::DateTimeUtc; +use super::{device::DeviceId, time::DateTimeUtc}; + +#[derive(Deserialize, Serialize, Debug, sqlx::Type, PartialEq, Eq)] +#[sqlx(transparent)] +struct EventId(i64); #[derive(Serialize, Deserialize)] struct Event { - id: i64, - device_id: i64, + id: EventId, + device_id: DeviceId, version_nr: i64, start_time: DateTimeUtc, } diff --git a/backend/src/data_model/task.rs b/backend/src/data_model/task.rs index 3c2ac217..8e2d7cad 100644 --- a/backend/src/data_model/task.rs +++ b/backend/src/data_model/task.rs @@ -1,11 +1,20 @@ use serde::{Deserialize, Serialize}; -use super::time::{Milliseconds, Timespan}; +use super::{ + device::DeviceId, + time::{Milliseconds, Timespan}, +}; + +use derive_more::{From, Into}; + +#[derive(Deserialize, Serialize, Debug, sqlx::Type, PartialEq, Eq, From, Into, Clone, Copy)] +#[sqlx(transparent)] +pub struct TaskId(i64); #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct Task { - pub id: i64, + pub id: TaskId, pub timespan: Timespan, pub duration: Milliseconds, - pub device_id: i64, + pub device_id: DeviceId, } diff --git a/backend/src/data_model/time.rs b/backend/src/data_model/time.rs index c0e3ced8..ccef1280 100644 --- a/backend/src/data_model/time.rs +++ b/backend/src/data_model/time.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; pub type DateTimeUtc = DateTime; -#[derive(Serialize, Deserialize, sqlx::Type, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, sqlx::Type, Debug, PartialEq, Eq, Clone, Copy)] #[sqlx(transparent)] pub struct Milliseconds(i64); diff --git a/backend/src/extractors/auth.rs b/backend/src/extractors/auth.rs index 25027b62..e92d9a56 100644 --- a/backend/src/extractors/auth.rs +++ b/backend/src/extractors/auth.rs @@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize}; use sqlx::SqlitePool; use uuid::Uuid; +use crate::data_model::account::AccountId; + #[derive(Deserialize, Serialize, sqlx::Type)] #[sqlx(transparent)] pub struct AuthToken(Uuid); @@ -23,7 +25,7 @@ impl AuthToken { } // Account id -pub struct Authentication(pub i64); +pub struct Authentication(pub AccountId); #[async_trait] impl FromRequestParts for Authentication { @@ -57,10 +59,10 @@ fn get_auth_token(headers: &HeaderMap) -> Option { AuthToken::try_parse(string).ok() } -async fn get_account_id_from_token(token: AuthToken, pool: &SqlitePool) -> Option { +async fn get_account_id_from_token(token: AuthToken, pool: &SqlitePool) -> Option { sqlx::query_scalar!( r#" - SELECT account_id + SELECT account_id as "id: AccountId" FROM AuthTokens WHERE id = ? "#, @@ -72,7 +74,7 @@ async fn get_account_id_from_token(token: AuthToken, pool: &SqlitePool) -> Optio } pub async fn create_auth_token( - account_id: i64, + account_id: AccountId, pool: &SqlitePool, ) -> Result { let auth_token = AuthToken::new(); diff --git a/backend/src/handlers/accounts.rs b/backend/src/handlers/accounts.rs index 673ec11a..04e01a01 100644 --- a/backend/src/handlers/accounts.rs +++ b/backend/src/handlers/accounts.rs @@ -6,9 +6,7 @@ use axum::{debug_handler, extract::State, http::StatusCode, Json}; use sqlx::SqlitePool; use crate::{ - extractors::auth::create_auth_token, - handlers::util::internal_error, - protocol::accounts::{RegisterOrLoginRequest, RegisterOrLoginResponse}, + data_model::account::AccountId, extractors::auth::create_auth_token, handlers::util::internal_error, protocol::accounts::{RegisterOrLoginRequest, RegisterOrLoginResponse} }; #[debug_handler] @@ -31,7 +29,7 @@ pub async fn register_account( r#" INSERT INTO Accounts (username, password_hash) VALUES (?, ?) - RETURNING id + RETURNING id as "id: AccountId" "#, register_request.username, password_hash @@ -54,7 +52,7 @@ pub async fn login_to_account( ) -> Result, (StatusCode, String)> { let account = sqlx::query!( r#" - SELECT id, password_hash + SELECT id as "id: AccountId", password_hash FROM Accounts WHERE username = ? "#, diff --git a/backend/src/handlers/devices.rs b/backend/src/handlers/devices.rs index 372fff5c..98296e38 100644 --- a/backend/src/handlers/devices.rs +++ b/backend/src/handlers/devices.rs @@ -2,7 +2,7 @@ use axum::{debug_handler, extract::State, http::StatusCode, Json}; use sqlx::SqlitePool; use crate::{ - data_model::device::Device, extractors::auth::Authentication, handlers::util::internal_error, + data_model::device::{Device, DeviceId}, extractors::auth::Authentication, handlers::util::internal_error, protocol::devices::CreateDeviceRequest, }; @@ -11,7 +11,8 @@ pub async fn get_devices( State(pool): State, Authentication(account_id): Authentication, ) -> Result>, (StatusCode, String)> { - let devices = sqlx::query!( + let devices = sqlx::query_as!( + Device, r#" SELECT id, effect, account_id FROM Devices @@ -23,16 +24,7 @@ pub async fn get_devices( .await .map_err(internal_error)?; - Ok(Json( - devices - .iter() - .map(|d| Device { - id: d.id, - effect: d.effect, - account_id: d.account_id, - }) - .collect(), - )) + Ok(Json(devices)) } #[debug_handler] @@ -45,7 +37,7 @@ pub async fn create_device( r#" INSERT INTO Devices (effect, account_id) VALUES (?, ?) - RETURNING id + RETURNING id as "id: DeviceId" "#, create_device_request.effect, account_id diff --git a/backend/src/handlers/tasks.rs b/backend/src/handlers/tasks.rs index e011a8d3..45ecb8c8 100644 --- a/backend/src/handlers/tasks.rs +++ b/backend/src/handlers/tasks.rs @@ -2,7 +2,7 @@ use axum::{debug_handler, extract::State, http::StatusCode, Json}; use sqlx::SqlitePool; use crate::{ - data_model::{task::Task, time::Timespan}, + data_model::{device::DeviceId, task::{Task, TaskId}, time::{Milliseconds, Timespan}}, extractors::auth::Authentication, handlers::util::internal_error, protocol::tasks::{CreateTaskRequest, DeleteTaskRequest}, @@ -15,7 +15,7 @@ pub async fn get_tasks( ) -> Result>, (StatusCode, String)> { let tasks = sqlx::query!( r#" - SELECT Tasks.id, Tasks.timespan_start, Tasks.timespan_end, Tasks.duration, Tasks.device_id + SELECT Tasks.id as "id: TaskId", Tasks.timespan_start, Tasks.timespan_end, Tasks.duration as "duration: Milliseconds", Tasks.device_id as "device_id: DeviceId" FROM Tasks JOIN Devices ON Tasks.device_id == Devices.id WHERE Devices.account_id = ? @@ -26,12 +26,13 @@ pub async fn get_tasks( .await .map_err(internal_error)?; + let my_tasks = tasks .iter() .map(|t| Task { id: t.id, timespan: Timespan::new_from_naive(t.timespan_start, t.timespan_end), - duration: t.duration.into(), + duration: t.duration, device_id: t.device_id, }) .collect(); @@ -49,7 +50,7 @@ pub async fn create_task( r#" INSERT INTO Tasks (timespan_start, timespan_end, duration, device_id) VALUES (?, ?, ?, ?) - RETURNING id + RETURNING id as "id: TaskId" "#, create_task_request.timespan.start, create_task_request.timespan.end, diff --git a/backend/src/main.rs b/backend/src/main.rs index 75592436..0114d79d 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -216,7 +216,7 @@ mod tests { .header("X-Auth-Token", auth_token.clone()) .body(Body::from( serde_json::to_vec(&Task { - id: -1, + id: (-1).into(), timespan: Timespan::new( Utc::now(), Utc::now().checked_add_days(Days::new(1)).unwrap(), @@ -244,7 +244,7 @@ mod tests { let body = response.into_body().collect().await.unwrap().to_bytes(); let response: Task = serde_json::from_slice(&body).unwrap(); - assert_ne!(response.id, -1); + assert_ne!(response.id, (-1).into()); assert_eq!(response.duration, 3600.into()); } @@ -264,7 +264,7 @@ mod tests { .header("X-Auth-Token", auth_token.clone()) .body(Body::from( serde_json::to_vec(&Task { - id: -1, + id: (-1).into(), timespan: Timespan::new( Utc::now(), Utc::now().checked_add_days(Days::new(1)).unwrap(), diff --git a/backend/src/protocol/devices.rs b/backend/src/protocol/devices.rs index 1443346e..535e75a2 100644 --- a/backend/src/protocol/devices.rs +++ b/backend/src/protocol/devices.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use crate::data_model::device::DeviceId; + #[derive(Deserialize, Serialize)] pub struct CreateDeviceRequest { pub effect: f64, @@ -7,5 +9,5 @@ pub struct CreateDeviceRequest { #[derive(Deserialize, Serialize)] pub struct DeleteDeviceRequest { - pub id: i64, + pub id: DeviceId, } diff --git a/backend/src/protocol/tasks.rs b/backend/src/protocol/tasks.rs index a716093a..4fcf47d3 100644 --- a/backend/src/protocol/tasks.rs +++ b/backend/src/protocol/tasks.rs @@ -1,15 +1,18 @@ use serde::{Deserialize, Serialize}; -use crate::data_model::time::{Milliseconds, Timespan}; +use crate::data_model::{ + device::DeviceId, + time::{Milliseconds, Timespan}, +}; #[derive(Deserialize, Serialize)] pub struct CreateTaskRequest { pub timespan: Timespan, pub duration: Milliseconds, - pub device_id: i64, + pub device_id: DeviceId, } #[derive(Deserialize, Serialize)] pub struct DeleteTaskRequest { - pub id: i64, + pub id: DeviceId, }