From d799da520ed8586127f5a441e9edb63ea6d5ab23 Mon Sep 17 00:00:00 2001 From: Anton Bilous Date: Thu, 30 Jan 2025 13:48:25 +0200 Subject: [PATCH] Separate ports & adaptors --- 3/coursework/src/database/init/init.sql | 4 +- 3/coursework/src/database/src/adapter.rs | 1 + .../src/database/src/adapter/mysql.rs | 2 + .../src/database/src/adapter/mysql/base.rs | 98 ++++++++++ .../src/database/src/adapter/mysql/user.rs | 125 +++++++++++++ 3/coursework/src/database/src/lib.rs | 3 +- 3/coursework/src/database/src/main.rs | 18 ++ 3/coursework/src/database/src/port.rs | 21 +++ 3/coursework/src/database/src/port/base.rs | 79 ++++++++ 3/coursework/src/database/src/port/package.rs | 168 ++++++++++++++++++ 3/coursework/src/database/src/port/user.rs | 105 +++++++++++ 3/coursework/src/database/src/repository.rs | 2 - .../src/database/src/repository/session.rs | 51 ------ .../src/database/src/repository/user.rs | 164 ----------------- 14 files changed, 621 insertions(+), 220 deletions(-) create mode 100644 3/coursework/src/database/src/adapter.rs create mode 100644 3/coursework/src/database/src/adapter/mysql.rs create mode 100644 3/coursework/src/database/src/adapter/mysql/base.rs create mode 100644 3/coursework/src/database/src/adapter/mysql/user.rs create mode 100644 3/coursework/src/database/src/main.rs create mode 100644 3/coursework/src/database/src/port.rs create mode 100644 3/coursework/src/database/src/port/base.rs create mode 100644 3/coursework/src/database/src/port/package.rs create mode 100644 3/coursework/src/database/src/port/user.rs delete mode 100644 3/coursework/src/database/src/repository.rs delete mode 100644 3/coursework/src/database/src/repository/session.rs delete mode 100644 3/coursework/src/database/src/repository/user.rs diff --git a/3/coursework/src/database/init/init.sql b/3/coursework/src/database/init/init.sql index 8341d89..66dfbb0 100644 --- a/3/coursework/src/database/init/init.sql +++ b/3/coursework/src/database/init/init.sql @@ -70,7 +70,7 @@ INSERT INTO DependencyTypes (id, name) VALUES -- Track which dependencies a package has CREATE TABLE PackageDependencies ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, arch VARCHAR(63) NULL, - condition VARCHAR(255) NULL, + requirement VARCHAR(255) NULL, description VARCHAR(127) NULL, package INT UNSIGNED NOT NULL, @@ -93,7 +93,7 @@ INSERT INTO RelationTypes (id, name) VALUES -- Track which conflicts, provides and replaces a package has CREATE TABLE PackageRelations ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, arch VARCHAR(63) NULL, - condition VARCHAR(255) NULL, + requiremen VARCHAR(255) NULL, package INT UNSIGNED NOT NULL, relation_type TINYINT UNSIGNED NOT NULL, diff --git a/3/coursework/src/database/src/adapter.rs b/3/coursework/src/database/src/adapter.rs new file mode 100644 index 0000000..4fb2dd9 --- /dev/null +++ b/3/coursework/src/database/src/adapter.rs @@ -0,0 +1 @@ +pub mod mysql; diff --git a/3/coursework/src/database/src/adapter/mysql.rs b/3/coursework/src/database/src/adapter/mysql.rs new file mode 100644 index 0000000..5e21c1d --- /dev/null +++ b/3/coursework/src/database/src/adapter/mysql.rs @@ -0,0 +1,2 @@ +pub mod user; +pub mod base; diff --git a/3/coursework/src/database/src/adapter/mysql/base.rs b/3/coursework/src/database/src/adapter/mysql/base.rs new file mode 100644 index 0000000..559c6e3 --- /dev/null +++ b/3/coursework/src/database/src/adapter/mysql/base.rs @@ -0,0 +1,98 @@ +pub use crate::port::base::*; + +use sqlx::{Executor, MySql}; + +pub struct BaseAdapter; + +struct DatabaseBase { + id: u64, + name: String, + description: Option, + created_at: DateTime, + updated_at: DateTime, +} +impl From for Base { + fn from(value: DatabaseBase) -> Self { + Self { + id: Id(value.id), + name: value.name.into(), + description: value.description.into(), + created_at: value.created_at, + updated_at: value.updated_at, + } + } +} + +impl BaseRepository for BaseAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl crate::port::CRUD for BaseAdapter +where + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + type Create = New; + type Read = Id; + type Update = Field; + type Delete = Id; + type Existing = Base; + type Id = Id; + + async fn create(connection: &mut E, data: Self::Create) -> Result { + Ok(Id(sqlx::query!( + "INSERT INTO PackageBases (name, description) VALUES (?, ?)", + data.name.0, + data.description.0, + ) + .execute(&*connection) + .await? + .last_insert_id())) + } + + async fn read(connection: &E, data: Self::Read) -> Result> { + Ok(sqlx::query_as!( + DatabaseBase, + "SELECT * FROM PackageBases WHERE id = ?", + data.0 + ) + .fetch_optional(connection) + .await? + .map(Into::into)) + } + + async fn update(connection: &mut E, id: Self::Id, data: Self::Update) -> Result { + match data { + Field::Name(valid) => { + sqlx::query!( + "UPDATE PackageBases SET name = ? WHERE id = ?", + valid.0, + id.0 + ) + } + Field::Description(valid) => { + sqlx::query!( + "UPDATE PackageBases SET description = ? WHERE id = ?", + valid.0, + id.0 + ) + } + Field::CreatedAt(date_time) => sqlx::query!( + "UPDATE PackageBases SET created_at = ? WHERE id = ?", + date_time, + id.0 + ), + Field::UpdatedAt(date_time) => sqlx::query!( + "UPDATE PackageBases SET updated_at = ? WHERE id = ?", + date_time, + id.0 + ), + } + .execute(&*connection) + .await?; + Ok(()) + } + + async fn delete(connection: &mut E, data: Self::Delete) -> Result { + sqlx::query!("DELETE FROM PackageBases WHERE id = ?", data.0) + .execute(&*connection) + .await?; + Ok(()) + } +} diff --git a/3/coursework/src/database/src/adapter/mysql/user.rs b/3/coursework/src/database/src/adapter/mysql/user.rs new file mode 100644 index 0000000..0dada5f --- /dev/null +++ b/3/coursework/src/database/src/adapter/mysql/user.rs @@ -0,0 +1,125 @@ +pub use crate::port::user::*; + +use sqlx::{Executor, MySql}; + +pub struct UserAdapter; + +struct DatabaseUser { + id: u64, + name: String, + email: String, + password: String, + last_used: Option>, + created_at: DateTime, + updated_at: DateTime, +} + +impl From for User { + fn from(value: DatabaseUser) -> Self { + Self { + id: Id(value.id), + name: value.name.into(), + email: value.email.into(), + password: value.password.into(), + last_used: value.last_used, + created_at: value.created_at, + updated_at: value.updated_at, + } + } +} + +impl UserRepository for UserAdapter where for<'a> &'a E: Executor<'a, Database = MySql> {} +impl crate::port::CRUD for UserAdapter +where + for<'a> &'a E: Executor<'a, Database = MySql>, +{ + type Create = New; + type Read = Unique; + type Update = Field; + type Delete = Unique; + type Existing = User; + type Id = Id; + + async fn create(connection: &mut E, data: Self::Create) -> Result { + Ok(Id(sqlx::query!( + "INSERT INTO Users (name, email, password, last_used) VALUES (?, ?, ?, ?)", + data.name.0, + data.email.0, + data.password.0, + data.last_used, + ) + .execute(&*connection) + .await? + .last_insert_id())) + } + + async fn read(connection: &E, data: Self::Read) -> Result> { + Ok(match data { + Unique::Id(id) => { + sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE id = ?", id.0) + .fetch_optional(connection) + .await + } + Unique::Name(name) => { + sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE name = ?", name.0) + .fetch_optional(connection) + .await + } + Unique::Email(email) => { + sqlx::query_as!(DatabaseUser, "SELECT * FROM Users WHERE email = ?", email.0) + .fetch_optional(connection) + .await + } + }? + .map(Into::into)) + } + + async fn update(connection: &mut E, id: Self::Id, data: Self::Update) -> Result { + match data { + Field::Name(valid) => { + sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", valid.0, id.0) + } + Field::Email(valid) => { + sqlx::query!("UPDATE Users SET email = ? WHERE id = ?", valid.0, id.0) + } + Field::Password(valid) => { + sqlx::query!("UPDATE Users SET password = ? WHERE id = ?", valid.0, id.0) + } + Field::LastUsed(date_time) => { + sqlx::query!( + "UPDATE Users SET last_used = ? WHERE id = ?", + date_time, + id.0 + ) + } + Field::CreatedAt(date_time) => sqlx::query!( + "UPDATE Users SET created_at = ? WHERE id = ?", + date_time, + id.0 + ), + Field::UpdatedAt(date_time) => sqlx::query!( + "UPDATE Users SET updated_at = ? WHERE id = ?", + date_time, + id.0 + ), + } + .execute(&*connection) + .await?; + Ok(()) + } + + async fn delete(connection: &mut E, data: Self::Delete) -> Result { + match data { + Unique::Id(id) => sqlx::query!("DELETE FROM Users WHERE id = ?", id.0), + Unique::Name(name) => { + sqlx::query!("DELETE FROM Users WHERE name = ?", name.0) + } + Unique::Email(email) => { + sqlx::query!("DELETE FROM Users WHERE email = ?", email.0) + } + } + .execute(&*connection) + .await?; + Ok(()) + } +} diff --git a/3/coursework/src/database/src/lib.rs b/3/coursework/src/database/src/lib.rs index d36d96d..da8cf3a 100644 --- a/3/coursework/src/database/src/lib.rs +++ b/3/coursework/src/database/src/lib.rs @@ -13,4 +13,5 @@ impl IntoValid for T {} pub mod atomic; pub mod connect; -pub mod repository; +pub mod port; +pub mod adapter; diff --git a/3/coursework/src/database/src/main.rs b/3/coursework/src/database/src/main.rs new file mode 100644 index 0000000..eb43d13 --- /dev/null +++ b/3/coursework/src/database/src/main.rs @@ -0,0 +1,18 @@ +use garde::{Valid, Validate}; + +#[derive(Validate)] +enum Data { + Struct { + #[garde(range(min=-10, max=10))] + field: i32, + }, + Tuple(#[garde(ascii)] String), +} + +fn main() { + let data = Data::Struct { field: 100 }; + let data = Data::Tuple("🧱".into()); + if let Err(e) = data.validate() { + println!("invalid data: {e}"); + } +} diff --git a/3/coursework/src/database/src/port.rs b/3/coursework/src/database/src/port.rs new file mode 100644 index 0000000..c0b5d0c --- /dev/null +++ b/3/coursework/src/database/src/port.rs @@ -0,0 +1,21 @@ +pub type Result = std::result::Result>; + +#[allow(async_fn_in_trait)] +pub trait CRUD { + type Create; + type Read; + type Update; + type Delete; + type Existing; + type Id; + + async fn create(connection: &mut C, data: Self::Create) -> Result; + async fn read(connection: &C, data: Self::Read) -> Result>; + async fn update(connection: &mut C, id: Self::Id, data: Self::Update) -> Result; + async fn delete(connection: &mut C, data: Self::Delete) -> Result; +} + +pub mod user; +pub mod base; +// pub mod package; +// pub mod session; diff --git a/3/coursework/src/database/src/port/base.rs b/3/coursework/src/database/src/port/base.rs new file mode 100644 index 0000000..f241124 --- /dev/null +++ b/3/coursework/src/database/src/port/base.rs @@ -0,0 +1,79 @@ +pub use super::{CRUD, Result}; + +pub use chrono::{DateTime, Utc}; +use derive_more::{Deref, From, Into}; +use garde::{Valid, Validate}; + +#[allow(async_fn_in_trait)] +pub trait BaseRepository: + super::CRUD< + C, + Create = New, + Read = Id, + Update = Field, + Delete = Id, + Existing = Base, + Id = Id, + > +{ + async fn update_base(connection: &mut C, base: &mut Base, data: Self::Update) -> Result { + Self::update(connection, base.id, data.clone()).await?; + match data { + Field::Name(valid) => base.name = valid.into_inner(), + Field::Description(valid) => base.description = valid.into_inner(), + Field::CreatedAt(date_time) => base.created_at = date_time, + Field::UpdatedAt(date_time) => base.updated_at = date_time, + } + Ok(()) + } +} + +#[derive(Deref, Into, Clone, Copy)] +pub struct Id(pub(crate) u64); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Name(#[garde(alphanumeric, length(min = 2, max = 127))] pub String); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Description(#[garde(length(max = 510))] pub Option); + +pub struct New { + pub name: Name, + pub description: Description, +} + +#[derive(Clone)] +pub enum Field { + Name(Valid), + Description(Valid), + CreatedAt(DateTime), + UpdatedAt(DateTime), +} + +pub struct Base { + pub(crate) id: Id, + pub(crate) name: Name, + pub(crate) description: Description, + pub(crate) created_at: DateTime, + pub(crate) updated_at: DateTime, +} + +impl Base { + pub fn id(&self) -> Id { + self.id + } + pub fn name(&self) -> &Name { + &self.name + } + pub fn description(&self) -> &Description { + &self.description + } + pub fn created_at(&self) -> DateTime { + self.created_at + } + pub fn updated_at(&self) -> DateTime { + self.updated_at + } +} diff --git a/3/coursework/src/database/src/port/package.rs b/3/coursework/src/database/src/port/package.rs new file mode 100644 index 0000000..72de830 --- /dev/null +++ b/3/coursework/src/database/src/port/package.rs @@ -0,0 +1,168 @@ +use super::{Result, package_base::Base}; + +use derive_more::{Deref, From, Into}; +use garde::{Valid, Validate}; +use sqlx::{Executor, MySql}; + +// pub enum GetBy + +#[allow(async_fn_in_trait)] +pub trait PackageRepository { + async fn get_by_id(connection: &C, id: Id) -> Result>; + async fn get_by_name(connection: &C, name: &Valid) -> Result>; + + async fn change_name(connection: &mut C, package: &mut Package, name: Valid) -> Result; + async fn change_base(connection: &mut C, package: &mut Package, base: &Base) -> Result; + async fn change_version(connection: &mut C, package: &mut Package, version: Valid) -> Result; + + async fn create(connection: &mut C, data: Valid) -> Result; +} + +#[derive(Deref, Into, Clone, Copy)] +pub struct Id(u32); + +pub type BaseId = super::package_base::Id; + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Name(#[garde(alphanumeric, length(min = 2, max = 127))] pub String); + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Version(#[garde(alphanumeric, length(min = 1, max = 127))] pub String); + +#[derive(Validate, Deref)] +#[garde(transparent)] +pub struct Description(#[garde(ascii, length(max = 255))] pub Option); + +#[derive(Validate)] +#[garde(transparent)] +pub struct URL(#[garde(url, length(max = 510))] pub Option); + +#[derive(Validate)] +pub struct PackageData { + #[garde(dive)] + pub name: Name, + #[garde(dive)] + pub description: Description, +} + +#[derive(Deref)] +pub struct Package { + id: Id, + #[deref] + data: PackageData, +} +impl Package { + pub const fn id(&self) -> Id { + self.id + } +} + +// pub struct UserAdapter; +// +// struct QueryUser { +// id: u32, +// name: String, +// email: String, +// password: String, +// } +// impl From for Package { +// fn from(value: QueryUser) -> Self { +// Self { +// id: Id(value.id), +// data: PackageData { +// name: Name(value.name), +// description: Description(value.email), +// }, +// } +// } +// } +// +// impl PackageRepository for UserAdapter +// where +// for<'a> &'a E: Executor<'a, Database = MySql>, +// { +// async fn get_by_id(connection: &E, id: Id) -> Result> { +// Ok(sqlx::query_as!( +// QueryUser, +// "SELECT id, name, email, password FROM Users WHERE id = ?", +// id.0 +// ) +// .fetch_optional(connection) +// .await? +// .map(Into::into)) +// } +// async fn get_by_name(connection: &E, name: &Valid) -> Result> { +// Ok(sqlx::query_as!( +// QueryUser, +// "SELECT id, name, email, password FROM Users WHERE name = ?", +// name.0 +// ) +// .fetch_optional(connection) +// .await? +// .map(Into::into)) +// } +// async fn get_by_email(connection: &E, email: &Valid) -> Result> { +// Ok(sqlx::query_as!( +// QueryUser, +// "SELECT id, name, email, password FROM Users WHERE email = ?", +// email.0 +// ) +// .fetch_optional(connection) +// .await? +// .map(Into::into)) +// } +// +// async fn change_name(connection: &mut E, user: &mut Package, name: Valid) -> Result { +// sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", name.0, user.id.0) +// .execute(&*connection) +// .await?; +// Ok(()) +// } +// async fn change_email( +// connection: &mut E, +// user: &mut Package, +// email: Valid, +// ) -> Result { +// sqlx::query!( +// "UPDATE Users SET email = ? WHERE id = ?", +// email.0, +// user.id.0 +// ) +// .execute(&*connection) +// .await?; +// Ok(()) +// } +// async fn change_password( +// connection: &mut E, +// user: &mut Package, +// password: Valid, +// ) -> Result { +// sqlx::query!( +// "UPDATE Users SET password = ? WHERE id = ?", +// password.0, +// user.id.0 +// ) +// .execute(&*connection) +// .await?; +// Ok(()) +// } +// +// async fn create(connection: &mut E, data: Valid) -> Result { +// let id = sqlx::query!( +// "INSERT INTO Users (name, email, password) VALUES (?, ?, ?)", +// data.name.0, +// data.description.0, +// data.password.0 +// ) +// .execute(&*connection) +// .await? +// .last_insert_id() as u32; +// +// Ok(Package { +// id: Id(id), +// data: data.into_inner(), +// }) +// } +// } diff --git a/3/coursework/src/database/src/port/user.rs b/3/coursework/src/database/src/port/user.rs new file mode 100644 index 0000000..afa448b --- /dev/null +++ b/3/coursework/src/database/src/port/user.rs @@ -0,0 +1,105 @@ +pub use super::{CRUD, Result}; + +pub use chrono::{DateTime, Utc}; +use derive_more::{Deref, From, Into}; +use garde::{Valid, Validate}; + +#[allow(async_fn_in_trait)] +pub trait UserRepository: + super::CRUD< + C, + Create = New, + Read = Unique, + Update = Field, + Delete = Unique, + Existing = User, + Id = Id, + > +{ + async fn update_user(connection: &mut C, user: &mut User, data: Self::Update) -> Result { + Self::update(connection, user.id, data.clone()).await?; + match data { + Field::Name(valid) => user.name = valid.into_inner(), + Field::Email(valid) => user.email = valid.into_inner(), + Field::Password(valid) => user.password = valid.into_inner(), + Field::LastUsed(date_time) => user.last_used = date_time, + Field::CreatedAt(date_time) => user.created_at = date_time, + Field::UpdatedAt(date_time) => user.updated_at = date_time, + } + Ok(()) + } +} + +#[derive(Deref, Into, Clone, Copy)] +pub struct Id(pub(crate) u64); + +// TODO: is this the right layer for requirements (email) validatoin? + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Name(#[garde(alphanumeric, length(min = 2, max = 31))] pub String); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Email(#[garde(email, length(max = 255))] pub String); + +#[derive(Validate, Deref, From, Clone)] +#[garde(transparent)] +pub struct Password(#[garde(ascii, length(max = 255))] pub String); + +pub struct New { + pub name: Valid, + pub email: Valid, + pub password: Valid, + pub last_used: Option>, +} + +pub enum Unique { + Id(Id), + Name(Valid), + Email(Valid), +} + +#[derive(Clone)] +pub enum Field { + Name(Valid), + Email(Valid), + Password(Valid), + LastUsed(Option>), + CreatedAt(DateTime), + UpdatedAt(DateTime), +} + +pub struct User { + pub(crate) id: Id, + pub(crate) name: Name, + pub(crate) email: Email, + pub(crate) password: Password, + pub(crate) last_used: Option>, + pub(crate) created_at: DateTime, + pub(crate) updated_at: DateTime, +} + +impl User { + pub const fn id(&self) -> Id { + self.id + } + pub const fn name(&self) -> &Name { + &self.name + } + pub const fn email(&self) -> &Email { + &self.email + } + pub const fn password(&self) -> &Password { + &self.password + } + pub const fn last_used(&self) -> Option> { + self.last_used + } + pub const fn created_at(&self) -> DateTime { + self.created_at + } + pub const fn updated_at(&self) -> DateTime { + self.updated_at + } +} diff --git a/3/coursework/src/database/src/repository.rs b/3/coursework/src/database/src/repository.rs deleted file mode 100644 index f950c0f..0000000 --- a/3/coursework/src/database/src/repository.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod session; -pub mod user; diff --git a/3/coursework/src/database/src/repository/session.rs b/3/coursework/src/database/src/repository/session.rs deleted file mode 100644 index a670d60..0000000 --- a/3/coursework/src/database/src/repository/session.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::user::User; -pub type Result = std::result::Result>; - -use chrono::{DateTime, Utc}; -use derive_more::{Deref, DerefMut}; -use sqlx::{Executor, MySql}; - -#[allow(async_fn_in_trait)] -pub trait SessionRepository { - async fn start(connection: &mut C, user: User) -> Result; - async fn end(connection: &mut C, session: Session) -> Result; -} - -#[derive(DerefMut, Deref)] -pub struct Session { - #[deref] - #[deref_mut] - user: User, - - start: DateTime, -} - -impl Session { - pub const fn start(&self) -> DateTime { - self.start - } -} - -#[derive(Debug)] -pub struct SessionAdapter; - -impl SessionRepository for SessionAdapter -where - for<'a> &'a E: Executor<'a, Database = MySql>, -{ - async fn start(connection: &mut E, user: User) -> Result { - let start = Utc::now(); - sqlx::query!( - "UPDATE Users SET last_used = ? WHERE id = ?", - start, - *user.id() - ) - .execute(&*connection) - .await?; - Ok(Session { user, start }) - } - - async fn end(_: &mut E, session: Session) -> Result { - Ok(session.user) - } -} diff --git a/3/coursework/src/database/src/repository/user.rs b/3/coursework/src/database/src/repository/user.rs deleted file mode 100644 index a0a6566..0000000 --- a/3/coursework/src/database/src/repository/user.rs +++ /dev/null @@ -1,164 +0,0 @@ -use derive_more::{Deref, From, Into}; -use garde::{Valid, Validate}; -use sqlx::{Executor, MySql}; - -pub type Result = std::result::Result>; - -#[allow(async_fn_in_trait)] -pub trait UserRepository { - async fn get_by_id(connection: &C, id: Id) -> Result>; - async fn get_by_name(connection: &C, name: &Valid) -> Result>; - async fn get_by_email(connection: &C, email: &Valid) -> Result>; - - async fn change_name(connection: &mut C, user: &mut User, name: Valid) -> Result; - async fn change_email(connection: &mut C, user: &mut User, email: Valid) -> Result; - async fn change_password( - connection: &mut C, - user: &mut User, - password: Valid, - ) -> Result; - - async fn create(connection: &mut C, user: Valid) -> Result; -} - -#[derive(Deref, Into, Clone, Copy)] -pub struct Id(u32); - -#[derive(Validate, Deref)] -#[garde(transparent)] -pub struct Username(#[garde(alphanumeric, length(min = 2, max = 31))] pub String); - -#[derive(Validate, Deref)] -#[garde(transparent)] -pub struct Email(#[garde(email, length(max = 255))] pub String); - -#[derive(Validate, Deref)] -#[garde(transparent)] -pub struct Password(#[garde(ascii, length(max = 255))] pub String); - -#[derive(Validate)] -pub struct UserData { - #[garde(dive)] - pub name: Username, - #[garde(dive)] - pub email: Email, - #[garde(dive)] - pub password: Password, -} - -#[derive(Deref)] -pub struct User { - id: Id, - #[deref] - data: UserData, -} -impl User { - pub const fn id(&self) -> Id { - self.id - } -} - -pub struct UserAdapter; - -struct QueryUser { - id: u32, - name: String, - email: String, - password: String, -} -impl From for User { - fn from(value: QueryUser) -> Self { - Self { - id: Id(value.id), - data: UserData { - name: Username(value.name), - email: Email(value.email), - password: Password(value.password), - }, - } - } -} - -impl UserRepository for UserAdapter -where - for<'a> &'a E: Executor<'a, Database = MySql>, -{ - async fn get_by_id(connection: &E, id: Id) -> Result> { - Ok(sqlx::query_as!( - QueryUser, - "SELECT id, name, email, password FROM Users WHERE id = ?", - id.0 - ) - .fetch_optional(connection) - .await? - .map(Into::into)) - } - async fn get_by_name(connection: &E, name: &Valid) -> Result> { - Ok(sqlx::query_as!( - QueryUser, - "SELECT id, name, email, password FROM Users WHERE name = ?", - name.0 - ) - .fetch_optional(connection) - .await? - .map(Into::into)) - } - async fn get_by_email(connection: &E, email: &Valid) -> Result> { - Ok(sqlx::query_as!( - QueryUser, - "SELECT id, name, email, password FROM Users WHERE email = ?", - email.0 - ) - .fetch_optional(connection) - .await? - .map(Into::into)) - } - - async fn change_name(connection: &mut E, user: &mut User, name: Valid) -> Result { - sqlx::query!("UPDATE Users SET name = ? WHERE id = ?", name.0, user.id.0) - .execute(&*connection) - .await?; - Ok(()) - } - async fn change_email(connection: &mut E, user: &mut User, email: Valid) -> Result { - sqlx::query!( - "UPDATE Users SET email = ? WHERE id = ?", - email.0, - user.id.0 - ) - .execute(&*connection) - .await?; - Ok(()) - } - async fn change_password( - connection: &mut E, - user: &mut User, - password: Valid, - ) -> Result { - sqlx::query!( - "UPDATE Users SET password = ? WHERE id = ?", - password.0, - user.id.0 - ) - .execute(&*connection) - .await?; - Ok(()) - } - - async fn create(connection: &mut E, user: Valid) -> Result { - let id = sqlx::query!( - "INSERT INTO Users (name, email, password) VALUES (?, ?, ?)", - user.name.0, - user.email.0, - user.password.0 - ) - .execute(&*connection) - .await? - .last_insert_id() as u32; - - Ok(User { - id: Id(id), - data: user.into_inner(), - }) - } -}