diff --git a/Cargo.lock b/Cargo.lock index aaf857d..7d4d54d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,14 +43,14 @@ dependencies = [ "http 0.2.12", "httparse", "httpdate", - "itoa", + "itoa 1.0.11", "language-tags", "local-channel", "mime", "percent-encoding", "pin-project-lite", "rand", - "sha1", + "sha1 0.10.6", "smallvec", "tokio", "tokio-util", @@ -155,7 +155,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "itoa", + "itoa 1.0.11", "language-tags", "log", "mime", @@ -448,6 +448,17 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -481,6 +492,19 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bcrypt" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" +dependencies = [ + "base64", + "blowfish", + "getrandom", + "subtle", + "zeroize", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -515,6 +539,16 @@ dependencies = [ "piper", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "brotli" version = "6.0.0" @@ -617,6 +651,16 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "cmake" version = "0.1.50" @@ -632,6 +676,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -796,7 +850,7 @@ dependencies = [ "byteorder", "chrono", "diesel_derives", - "itoa", + "itoa 1.0.11", "pq-sys", "r2d2", ] @@ -885,6 +939,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + [[package]] name = "either" version = "1.12.0" @@ -1247,7 +1307,7 @@ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.11", ] [[package]] @@ -1258,7 +1318,7 @@ checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.11", ] [[package]] @@ -1315,7 +1375,7 @@ dependencies = [ "http 1.1.0", "http-body", "httparse", - "itoa", + "itoa 1.0.11", "pin-project-lite", "smallvec", "tokio", @@ -1505,6 +1565,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "1.0.0" @@ -1527,6 +1597,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -1559,6 +1638,12 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.11" @@ -2060,6 +2145,16 @@ dependencies = [ "scheduled-thread-pool", ] +[[package]] +name = "r2d2_redis" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "182473b876b0b93e353682ec58e207dd1cb4a62278bbe0045fe52b86b74363bb" +dependencies = [ + "r2d2", + "redis 0.20.2", +] + [[package]] name = "rand" version = "0.8.5" @@ -2090,6 +2185,37 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redis" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4f0ceb2ec0dd769483ecd283f6615aa83dcd0be556d5294c6e659caefe7cc54" +dependencies = [ + "async-trait", + "combine", + "dtoa", + "itoa 0.4.8", + "percent-encoding", + "sha1 0.6.1", + "url", +] + +[[package]] +name = "redis" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec" +dependencies = [ + "combine", + "itoa 1.0.11", + "percent-encoding", + "r2d2", + "ryu", + "sha1_smol", + "socket2 0.5.7", + "url", +] + [[package]] name = "redox_syscall" version = "0.5.1" @@ -2259,6 +2385,7 @@ dependencies = [ "anyhow", "async-std", "base64", + "bcrypt", "chrono", "chrono-tz", "confy", @@ -2269,11 +2396,15 @@ dependencies = [ "once_cell", "paho-mqtt", "paho-mqtt-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2_redis", + "rand", + "redis 0.25.4", "reqwest", "serde", "serde_derive", "serde_json", "tokio", + "validator", ] [[package]] @@ -2355,7 +2486,7 @@ version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ - "itoa", + "itoa 1.0.11", "ryu", "serde", ] @@ -2367,7 +2498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa", + "itoa 1.0.11", "ryu", "serde", ] @@ -2379,12 +2510,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", - "itoa", + "itoa 1.0.11", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2396,6 +2536,12 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2458,6 +2604,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -2557,7 +2709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", - "itoa", + "itoa 1.0.11", "num-conv", "powerfmt", "serde", @@ -2591,6 +2743,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.38.0" @@ -2703,12 +2870,27 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -2722,7 +2904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", - "idna", + "idna 1.0.0", "percent-encoding", ] @@ -2744,6 +2926,21 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "validator" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" +dependencies = [ + "idna 0.5.0", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", +] + [[package]] name = "value-bag" version = "1.9.0" @@ -3116,6 +3313,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.2" diff --git a/Cargo.toml b/Cargo.toml index 1c62152..e8cd554 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,8 @@ chrono-tz = "0.9.0" reqwest = "0.12.3" base64 = "0.22.0" once_cell = "1.19.0" +validator = "0.18.1" +bcrypt = "0.15.1" +redis = { version = "0.25.4", features = ["r2d2"] } +r2d2_redis = "0.14.0" +rand = "0.8.5" diff --git a/docker-compose.yml b/docker-compose.yml index ab5672e..7cc8908 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,14 @@ services: - ./docker/db-store:/var/lib/postgresql/data networks: - sakura-internal + redis: + image: "redis:latest" + ports: + - "6379:6379" + volumes: + - "./docker/redis:/data" + networks: + - sakura-internal main: build: context: . @@ -28,10 +36,12 @@ services: - .config.yaml:/app/Sakura-API depends_on: - db + - redis ports: - 8080:8080 environment: DATABASE_URL: postgres://sakura:password@db:5432/sakura + REDIS_URL: redis://redis:6379 networks: - sakura-internal - sakura-external @@ -45,4 +55,4 @@ networks: external: false sakura-external: name: sakura-network - external: true \ No newline at end of file + external: true diff --git a/migrations/2024-07-18-084702_create_ahth/down.sql b/migrations/2024-07-18-084702_create_ahth/down.sql new file mode 100644 index 0000000..3b70f7b --- /dev/null +++ b/migrations/2024-07-18-084702_create_ahth/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE auth; diff --git a/migrations/2024-07-18-084702_create_ahth/up.sql b/migrations/2024-07-18-084702_create_ahth/up.sql new file mode 100644 index 0000000..0614e68 --- /dev/null +++ b/migrations/2024-07-18-084702_create_ahth/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE auth ( + id bigserial NOT NULL PRIMARY KEY, + email varchar(100) NOT NULL UNIQUE, + password varchar(255) NOT NULL, + created_at TIMESTAMPTZ NOT NULL +); diff --git a/migrations/2024-07-21-082625_add_auth_id_to_account/down.sql b/migrations/2024-07-21-082625_add_auth_id_to_account/down.sql new file mode 100644 index 0000000..21f1e3c --- /dev/null +++ b/migrations/2024-07-21-082625_add_auth_id_to_account/down.sql @@ -0,0 +1,7 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE account +DROP CONSTRAINT fk_auth; + +ALTER TABLE account +DROP COLUMN auth_id; + diff --git a/migrations/2024-07-21-082625_add_auth_id_to_account/up.sql b/migrations/2024-07-21-082625_add_auth_id_to_account/up.sql new file mode 100644 index 0000000..388c943 --- /dev/null +++ b/migrations/2024-07-21-082625_add_auth_id_to_account/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here +ALTER TABLE account +ADD COLUMN auth_id BIGINT NOT NULL; + +ALTER TABLE account +ADD CONSTRAINT fk_auth +FOREIGN KEY (auth_id) REFERENCES auth(id); + diff --git a/src/domain/object/account.rs b/src/domain/object/account.rs index e20af18..c5652c5 100644 --- a/src/domain/object/account.rs +++ b/src/domain/object/account.rs @@ -1,4 +1,4 @@ -use super::Id; +use super::{auth::AuthId, Id}; use crate::utils::time::create_time; use chrono::NaiveDateTime; @@ -7,6 +7,7 @@ pub type AccountId = Id; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Account { pub id: AccountId, + pub auth_id: AuthId, pub username: String, pub grade: i32, pub expiration_date: NaiveDateTime, @@ -14,9 +15,15 @@ pub struct Account { } impl Account { - pub fn new(username: String, grade: i32, expiration_date: NaiveDateTime) -> Self { + pub fn new( + auth_id: AuthId, + username: String, + grade: i32, + expiration_date: NaiveDateTime, + ) -> Self { Self { id: Default::default(), + auth_id, username, grade, expiration_date, @@ -32,14 +39,16 @@ mod tests { #[test] fn test_create_account() { + let auth_id = AuthId::new(1); let username = "test_user".to_string(); let grade = 4; let current_time = create_time(); let expiration_date = current_time + Duration::hours(1); - let account = Account::new(username.clone(), grade, expiration_date); + let account = Account::new(auth_id, username.clone(), grade, expiration_date); assert_eq!(account.id.get(), 0); + assert_eq!(account.auth_id.get(), 1); assert_eq!(account.username, username); assert_eq!(account.expiration_date, expiration_date); } diff --git a/src/domain/object/auth.rs b/src/domain/object/auth.rs new file mode 100644 index 0000000..5922b8d --- /dev/null +++ b/src/domain/object/auth.rs @@ -0,0 +1,48 @@ +use super::Id; +use crate::utils::time::create_time; +use bcrypt::{hash, BcryptError, DEFAULT_COST}; +use chrono::NaiveDateTime; +use validator::{ValidateEmail, ValidationError, ValidationErrors}; + +pub type AuthId = Id; + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Auth { + pub id: AuthId, + pub email: String, + pub password: String, + pub created_at: NaiveDateTime, +} + +impl Auth { + pub fn new(email: &str, password: &str) -> Self { + Self { + id: Default::default(), + email: email.to_string(), + password: password.to_string(), + created_at: create_time(), + } + } + + pub fn validate(email: &str, password: &str) -> Result<(), ValidationErrors> { + let mut errors = ValidationErrors::new(); + if !ValidateEmail::validate_email(&email) { + errors.add("email", ValidationError::new("Invalid email format")); + } + if password.len() < 8 { + errors.add( + "password", + ValidationError::new("Password must be at least 8 characters long"), + ); + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } + + pub fn hash_password(password: &str) -> Result { + hash(password, DEFAULT_COST) + } +} diff --git a/src/domain/object/mod.rs b/src/domain/object/mod.rs index eee8d11..0f25025 100644 --- a/src/domain/object/mod.rs +++ b/src/domain/object/mod.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use serde::{Deserialize, Deserializer, Serialize}; pub mod account; +pub mod auth; pub mod card; pub mod door; pub mod mqtt; diff --git a/src/domain/repository/auth.rs b/src/domain/repository/auth.rs new file mode 100644 index 0000000..74afeef --- /dev/null +++ b/src/domain/repository/auth.rs @@ -0,0 +1,7 @@ +use super::super::object::auth::Auth; +use anyhow; + +pub trait AuthRepository { + // fn signup(&self, auth: &Auth) -> anyhow::Result<()>; + fn exist_by_email(&self, email: &str) -> anyhow::Result; +} diff --git a/src/domain/repository/mod.rs b/src/domain/repository/mod.rs index 547713b..b520561 100644 --- a/src/domain/repository/mod.rs +++ b/src/domain/repository/mod.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod auth; pub mod card; pub mod door; pub mod mqtt; diff --git a/src/infrastructures/database/models.rs b/src/infrastructures/database/models.rs index d1f1ade..a7f791a 100644 --- a/src/infrastructures/database/models.rs +++ b/src/infrastructures/database/models.rs @@ -9,6 +9,7 @@ pub struct NewAccountEntity { pub grade: i32, pub expiration_date: NaiveDateTime, pub created_at: NaiveDateTime, + pub auth_id: i64, } #[derive(Debug, Queryable, Identifiable, AsChangeset)] @@ -19,6 +20,7 @@ pub struct AccountEntity { pub grade: i32, pub expiration_date: NaiveDateTime, pub created_at: NaiveDateTime, + pub auth_id: i64, } #[derive(Debug, Insertable)] @@ -55,3 +57,20 @@ pub struct DoorEntity { pub door_state: bool, pub door_switch_state: bool, } + +#[derive(Debug, Insertable)] +#[table_name = "auth"] +pub struct NewAuthEntity { + pub email: String, + pub password: String, + pub created_at: NaiveDateTime, +} + +#[derive(Debug, Queryable, Identifiable, AsChangeset)] +#[table_name = "auth"] +pub struct AuthEntity { + pub id: i64, + pub email: String, + pub password: String, + pub created_at: NaiveDateTime, +} diff --git a/src/infrastructures/database/schema.rs b/src/infrastructures/database/schema.rs index d2eb882..76b2d1f 100644 --- a/src/infrastructures/database/schema.rs +++ b/src/infrastructures/database/schema.rs @@ -8,6 +8,18 @@ diesel::table! { grade -> Int4, expiration_date -> Timestamptz, created_at -> Timestamptz, + auth_id -> Int8, + } +} + +diesel::table! { + auth (id) { + id -> Int8, + #[max_length = 100] + email -> Varchar, + #[max_length = 255] + password -> Varchar, + created_at -> Timestamptz, } } @@ -31,6 +43,11 @@ diesel::table! { } } +diesel::joinable!(account -> auth (auth_id)); diesel::joinable!(card -> account (account_id)); -diesel::allow_tables_to_appear_in_same_query!(account, card, door,); +diesel::allow_tables_to_appear_in_same_query!( + account, + auth, + card, + door,); diff --git a/src/infrastructures/repository/account.rs b/src/infrastructures/repository/account.rs index da009a4..69b81f5 100644 --- a/src/infrastructures/repository/account.rs +++ b/src/infrastructures/repository/account.rs @@ -1,5 +1,6 @@ use super::super::database::models::{AccountEntity, NewAccountEntity}; use crate::domain::object::account::{Account, AccountId}; +use crate::domain::object::auth::AuthId; use crate::domain::repository::account::AccountRepository; use anyhow; use chrono::NaiveDateTime; @@ -8,12 +9,14 @@ use diesel::r2d2::{ConnectionManager, Pool}; impl NewAccountEntity { pub fn new( + auth_id: AuthId, username: String, grade: i32, expiration_date: NaiveDateTime, created_at: NaiveDateTime, ) -> Self { Self { + auth_id: auth_id.get().to_owned(), username, grade, expiration_date, @@ -23,6 +26,7 @@ impl NewAccountEntity { fn from(model: &Account) -> NewAccountEntity { NewAccountEntity { + auth_id: model.auth_id.get().to_owned(), username: model.username.to_owned(), grade: model.grade.to_owned(), expiration_date: model.expiration_date.to_owned(), @@ -35,6 +39,7 @@ impl AccountEntity { fn from(model: &Account) -> AccountEntity { AccountEntity { id: model.id.get(), + auth_id: model.auth_id.get().to_owned(), username: model.username.to_owned(), grade: model.grade.to_owned(), expiration_date: model.expiration_date.to_owned(), @@ -44,6 +49,7 @@ impl AccountEntity { fn of(&self) -> Account { Account { id: AccountId::new(self.id), + auth_id: AuthId::new(self.auth_id), username: self.username.to_owned(), grade: self.grade.to_owned(), expiration_date: self.expiration_date.to_owned(), diff --git a/src/infrastructures/repository/auth.rs b/src/infrastructures/repository/auth.rs new file mode 100644 index 0000000..98c2603 --- /dev/null +++ b/src/infrastructures/repository/auth.rs @@ -0,0 +1,72 @@ +use super::super::database::models::{AuthEntity, NewAuthEntity}; +use crate::domain::object::auth::{Auth, AuthId}; +use crate::domain::repository::auth::AuthRepository; +use anyhow; +use chrono::NaiveDateTime; +use diesel::prelude::*; +use diesel::r2d2::{ConnectionManager, Pool}; + +impl NewAuthEntity { + pub fn new(email: String, password: String, created_at: NaiveDateTime) -> Self { + Self { + email, + password, + created_at, + } + } + + fn from(model: &Auth) -> NewAuthEntity { + NewAuthEntity { + email: model.email.to_owned(), + password: model.password.to_owned(), + created_at: model.created_at.to_owned(), + } + } +} + +impl AuthEntity { + fn from(model: &Auth) -> AuthEntity { + AuthEntity { + id: model.id.get(), + email: model.email.to_owned(), + password: model.password.to_owned(), + created_at: model.created_at.to_owned(), + } + } + + fn of(&self) -> Auth { + Auth { + id: AuthId::new(self.id), + email: self.email.to_owned(), + password: self.password.to_owned(), + created_at: self.created_at.to_owned(), + } + } +} + +pub struct AuthRepositoryImpl { + pub pool: Box>>, +} + +impl AuthRepository for AuthRepositoryImpl { + // fn signup(&self, auth: &Auth) -> anyhow::Result<()> { + // use super::super::database::schema::auth::dsl; + + // let entity = NewAuthEntity::from(auth); + // let mut conn = self.pool.get()?; + // diesel:: + + // } + fn exist_by_email(&self, email: &str) -> anyhow::Result { + use super::super::database::schema::auth::dsl; + + let mut conn = self.pool.get()?; + let query = dsl::auth.filter(dsl::email.eq(email)); + + match query.first::(&mut conn) { + Ok(_) => Ok(true), + Err(diesel::result::Error::NotFound) => Ok(false), + Err(err) => Err(anyhow::Error::new(err)), + } + } +} diff --git a/src/infrastructures/repository/mod.rs b/src/infrastructures/repository/mod.rs index 6e4e93e..b0f3364 100644 --- a/src/infrastructures/repository/mod.rs +++ b/src/infrastructures/repository/mod.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod auth; pub mod card; pub mod door; pub mod register; diff --git a/src/server/connection.rs b/src/server/connection.rs index 314361d..6b10a39 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1,52 +1,72 @@ use std::env; use diesel::prelude::*; -use diesel::r2d2::{ConnectionManager, Pool}; +use diesel::r2d2::ConnectionManager; +use r2d2_redis::RedisConnectionManager; + use dotenv::dotenv; use crate::domain::repository::account::AccountRepository; +use crate::domain::repository::auth::AuthRepository; use crate::domain::repository::card::CardRepository; use crate::domain::repository::door::DoorRepository; use crate::domain::repository::register::RegisterRepository; use crate::infrastructures::repository::account::AccountRepositoryImpl; +use crate::infrastructures::repository::auth::AuthRepositoryImpl; use crate::infrastructures::repository::card::CardRepositoryImpl; use crate::infrastructures::repository::door::DoorRepositoryImpl; use crate::infrastructures::repository::register::RegisterRepositoryImpl; pub struct RequestContext { - pool: Pool>, + db_pool: diesel::r2d2::Pool>, + redis_pool: r2d2_redis::r2d2::Pool, } impl RequestContext { pub fn new() -> RequestContext { dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL i not set"); - let manager = ConnectionManager::::new(db_url); - let pool = Pool::builder() - .build(manager) + let db_manager = ConnectionManager::::new(db_url); + let db_pool = diesel::r2d2::Pool::builder() + .build(db_manager) .expect("Failed to create DB connection pool."); - RequestContext { pool } + let redis_url = env::var("REDIS_URL").expect("REDIS_URL is not set"); + let redis_manager = RedisConnectionManager::new(redis_url).unwrap(); + let redis_pool = r2d2_redis::r2d2::Pool::builder() + .build(redis_manager) + .expect("Failed to create Redis connection pool."); + + RequestContext { + db_pool, + redis_pool, + } } pub fn account_repository(&self) -> impl AccountRepository { AccountRepositoryImpl { - pool: Box::new(self.pool.to_owned()), + pool: Box::new(self.db_pool.to_owned()), } } pub fn card_repository(&self) -> impl CardRepository { CardRepositoryImpl { - pool: Box::new(self.pool.to_owned()), + pool: Box::new(self.db_pool.to_owned()), } } pub fn door_repository(&self) -> impl DoorRepository { DoorRepositoryImpl { - pool: Box::new(self.pool.to_owned()), + pool: Box::new(self.db_pool.to_owned()), } } pub fn register_repository(&self) -> impl RegisterRepository { RegisterRepositoryImpl {} } + + pub fn auth_repository(&self) -> impl AuthRepository { + AuthRepositoryImpl { + pool: Box::new(self.db_pool.to_owned()), + } + } } diff --git a/src/server/handler/auth.rs b/src/server/handler/auth.rs new file mode 100644 index 0000000..d91ce7a --- /dev/null +++ b/src/server/handler/auth.rs @@ -0,0 +1,14 @@ +use crate::server::connection::RequestContext; +use crate::server::request::auth::AuthRequest; +use crate::usecase; +use actix_web::{post, web, web::Json, HttpResponse, Responder}; + +#[post("/auth/signup")] +async fn signup(data: web::Data, request: Json) -> impl Responder { + match usecase::auth::signup(&data.auth_repository(), &request) { + Ok(_) => HttpResponse::Ok().finish(), + Err(err) => { + HttpResponse::InternalServerError().json(format!("Internal Server Error {}", err)) + } + } +} diff --git a/src/server/handler/mod.rs b/src/server/handler/mod.rs index 03b252e..0a6a9e9 100644 --- a/src/server/handler/mod.rs +++ b/src/server/handler/mod.rs @@ -1,5 +1,5 @@ pub mod account; +pub mod auth; pub mod card; - pub mod mqtt_listener; pub mod register; diff --git a/src/server/request/account.rs b/src/server/request/account.rs index f0e11bf..65b1ddc 100644 --- a/src/server/request/account.rs +++ b/src/server/request/account.rs @@ -1,9 +1,11 @@ use crate::domain::object::account::{Account, AccountId}; +use crate::domain::object::auth::AuthId; use chrono::NaiveDateTime; use serde::Deserialize; #[derive(Debug, Default, Deserialize)] pub struct AccountRequest { + pub auth_id: AuthId, pub username: String, pub grade: i32, pub expiration_date: NaiveDateTime, @@ -17,6 +19,7 @@ pub struct AccountIdRequest { impl AccountRequest { pub fn of(&self) -> Account { Account::new( + self.auth_id.to_owned(), self.username.to_owned(), self.grade.to_owned(), self.expiration_date.to_owned(), @@ -25,6 +28,7 @@ impl AccountRequest { pub fn model(&self, account_id: AccountId, created_at: NaiveDateTime) -> Account { Account { id: account_id, + auth_id: self.auth_id.to_owned(), username: self.username.to_owned(), grade: self.grade.to_owned(), expiration_date: self.expiration_date.to_owned(), diff --git a/src/server/request/auth.rs b/src/server/request/auth.rs new file mode 100644 index 0000000..d2f4ff6 --- /dev/null +++ b/src/server/request/auth.rs @@ -0,0 +1,17 @@ +use chrono::NaiveDateTime; +use serde::Deserialize; + +#[derive(Debug, Default, Deserialize)] +pub struct AuthRequest { + pub email: String, + pub password: String, +} + +#[derive(Debug, Default, Deserialize)] +pub struct AuthAccountRequest { + pub username: String, + pub grade: i32, + pub expiration_date: NaiveDateTime, + pub email: String, + pub password: String, +} diff --git a/src/server/request/mod.rs b/src/server/request/mod.rs index 03e0d18..50d7401 100644 --- a/src/server/request/mod.rs +++ b/src/server/request/mod.rs @@ -1,2 +1,3 @@ pub mod account; +pub mod auth; pub mod card; diff --git a/src/usecase/account.rs b/src/usecase/account.rs index c0a2707..8f1ba4c 100644 --- a/src/usecase/account.rs +++ b/src/usecase/account.rs @@ -41,6 +41,7 @@ pub fn delete_account( mod tests { use super::*; use crate::domain::object::account::AccountId; + use crate::domain::object::auth::AuthId; use crate::domain::object::card::{Card, CardId}; use crate::domain::repository::card::CardRepository; use crate::tests::mock_account_repository::MockAccountRepository; @@ -57,6 +58,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -75,6 +77,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -83,6 +86,7 @@ mod tests { let test_account2 = Account { id: AccountId::new(2), + auth_id: AuthId::new(2), username: "test_user2".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -106,6 +110,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -121,6 +126,7 @@ mod tests { let retrieved_account = result.unwrap(); assert_eq!(retrieved_account.id.get(), test_account.id.get()); + assert_eq!(retrieved_account.auth_id.get(), test_account.auth_id.get()); assert_eq!(retrieved_account.username, test_account.username); assert_eq!(retrieved_account.grade, test_account.grade); assert_eq!( @@ -138,6 +144,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -159,6 +166,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -166,6 +174,7 @@ mod tests { }; let update_account = AccountRequest { + auth_id: AuthId::new(1), username: "update_user".to_string(), grade: 3, expiration_date: Local::now().naive_local() + Duration::hours(2), @@ -184,6 +193,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -208,6 +218,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), diff --git a/src/usecase/auth.rs b/src/usecase/auth.rs new file mode 100644 index 0000000..bf5abb4 --- /dev/null +++ b/src/usecase/auth.rs @@ -0,0 +1,14 @@ +use crate::domain::object::auth::Auth; +use crate::domain::repository::auth::AuthRepository; +use crate::server::request::auth::AuthRequest; +use anyhow; + +pub fn signup(repository: &impl AuthRepository, request: &AuthRequest) -> anyhow::Result<()> { + Auth::validate(&request.email, &request.email)?; + + let hash_password = Auth::hash_password(&request.password)?; + + let auth = Auth::new(&request.email, &hash_password); + + repository.signup(&auth) +} diff --git a/src/usecase/card.rs b/src/usecase/card.rs index 6d02168..23540f6 100644 --- a/src/usecase/card.rs +++ b/src/usecase/card.rs @@ -70,6 +70,7 @@ mod tests { use super::*; use crate::domain::object::{ account::{Account, AccountId}, + auth::AuthId, card::{Card, CardId}, }; use crate::tests::{ @@ -92,6 +93,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -125,6 +127,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -170,6 +173,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -217,6 +221,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -251,6 +256,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), @@ -292,6 +298,7 @@ mod tests { let test_account = Account { id: AccountId::new(1), + auth_id: AuthId::new(1), username: "test_user".to_string(), grade: 4, expiration_date: Local::now().naive_local() + Duration::hours(1), diff --git a/src/usecase/mod.rs b/src/usecase/mod.rs index 21fdfbd..9d4aebd 100644 --- a/src/usecase/mod.rs +++ b/src/usecase/mod.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod auth; pub mod card; pub mod mqtt; pub mod register; diff --git a/src/utils/kvs.rs b/src/utils/kvs.rs new file mode 100644 index 0000000..990cb2a --- /dev/null +++ b/src/utils/kvs.rs @@ -0,0 +1,3 @@ +pub fn join_string(parts: &[&str], delimiter: &str) -> String { + parts.join(delimiter) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 077885d..87e27d3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ +pub mod kvs; pub mod time;