Database User & Session repository
This commit is contained in:
3
3/coursework/src/.gitignore
vendored
Normal file
3
3/coursework/src/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target/
|
||||
/**/scrapyard/
|
||||
/database/data/
|
5354
3/coursework/src/Cargo.lock
generated
Normal file
5354
3/coursework/src/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
3/coursework/src/Cargo.toml
Normal file
3
3/coursework/src/Cargo.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["app/*", "libs/*", "database"]
|
19
3/coursework/src/README.md
Normal file
19
3/coursework/src/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Stuff that helped:
|
||||
|
||||
* Architecture:
|
||||
- [How to apply hexagonal architecture to Rust](https://www.barrage.net/blog/technology/how-to-apply-hexagonal-architecture-to-rust)
|
||||
- [Implementing onion architecture using Rust](https://mathias-vandaele.dev/implementing-onion-architecture-using-rust)
|
||||
* Design:
|
||||
- [Rust Data Modelling Without Classes](https://www.youtube.com/watch?v=z-0-bbc80JM)
|
||||
- ["Making Impossible States Impossible" by Richard Feldman](https://www.youtube.com/watch?v=IcgmSRJHu_8)
|
||||
- [Pretty State Machine Patterns in Rust](https://hoverbear.org/blog/rust-state-machine-pattern/)
|
||||
* How to Iced:
|
||||
- [Building a simple text editor with iced, a cross-platform GUI library for Rust](https://www.youtube.com/watch?v=gcBJ7cPSALo)
|
||||
- [Unofficial Iced Guide](https://jl710.github.io/iced-guide/)
|
||||
- [icebreaker](https://github.com/hecrj/icebreaker)
|
||||
|
||||
---
|
||||
|
||||
> _The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise._
|
||||
|
||||
— Edsger W. Dijkstra
|
20
3/coursework/src/database/Cargo.toml
Normal file
20
3/coursework/src/database/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "database"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "2.0.11"
|
||||
derive_more = { version = "1.0.0", features = ["deref", "deref_mut", "from", "into"] }
|
||||
garde = { version = "0.22.0", features = ["email", "url", "derive"] }
|
||||
|
||||
chrono = { version = "0.4.39", default-features = false, features = [
|
||||
"std",
|
||||
"now",
|
||||
] }
|
||||
sqlx = { version = "0.8.3", default-features = false, features = [
|
||||
"mysql",
|
||||
"macros",
|
||||
"chrono",
|
||||
"runtime-tokio",
|
||||
] }
|
11
3/coursework/src/database/compose.yaml
Normal file
11
3/coursework/src/database/compose.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
services:
|
||||
database:
|
||||
image: mysql:9.0
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:3306:3306"
|
||||
volumes:
|
||||
- ./data:/var/lib/mysql
|
||||
- ./init:/docker-entrypoint-initdb.d/:ro
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password # yes, I know
|
177
3/coursework/src/database/init/init.sql
Normal file
177
3/coursework/src/database/init/init.sql
Normal file
@ -0,0 +1,177 @@
|
||||
-- DROP DATABASE IF EXISTS repository;
|
||||
CREATE DATABASE repository;
|
||||
USE repository;
|
||||
|
||||
-- Required info for an account
|
||||
CREATE TABLE Users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(31) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
|
||||
last_used TIMESTAMP NULL,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Enables multiple packages to have the same base yet different components
|
||||
CREATE TABLE PackageBases ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(127) UNIQUE NOT NULL,
|
||||
description VARCHAR(510) NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- User roles for working on packages: flagger, packager, submitter, maintainer, etc.
|
||||
CREATE TABLE PackageBaseRoles ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(31) UNIQUE NOT NULL,
|
||||
description VARCHAR(255) NULL
|
||||
);
|
||||
-- Roles that a user has for a package
|
||||
CREATE TABLE PackageBaseUserRoles (
|
||||
base_id INT UNSIGNED,
|
||||
user_id INT UNSIGNED,
|
||||
role_id TINYINT UNSIGNED,
|
||||
|
||||
comment VARCHAR(255) NULL,
|
||||
|
||||
PRIMARY KEY (base_id, user_id, role_id), -- composite key
|
||||
FOREIGN KEY (base_id) REFERENCES PackageBases(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (role_id) REFERENCES PackageBaseRoles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Information about the actual packages
|
||||
CREATE TABLE Packages ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
package_base INT UNSIGNED NOT NULL,
|
||||
name VARCHAR(127) UNIQUE NOT NULL,
|
||||
version VARCHAR(127) NOT NULL,
|
||||
description VARCHAR(255) NULL,
|
||||
url VARCHAR(510) NULL,
|
||||
|
||||
flagged_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL,
|
||||
|
||||
FOREIGN KEY (package_base) REFERENCES PackageBases (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- depends, makedepends, optdepends, etc.
|
||||
CREATE TABLE DependencyTypes ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(31) UNIQUE NOT NULL
|
||||
);
|
||||
INSERT INTO DependencyTypes (id, name) VALUES
|
||||
(1, 'depends'),
|
||||
(2, 'makedepends'),
|
||||
(3, 'checkdepends'),
|
||||
(4, 'optdepends');
|
||||
|
||||
-- Track which dependencies a package has
|
||||
CREATE TABLE PackageDependencies ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
arch VARCHAR(63) NULL,
|
||||
condition VARCHAR(255) NULL,
|
||||
description VARCHAR(127) NULL,
|
||||
|
||||
package INT UNSIGNED NOT NULL,
|
||||
dependency_type TINYINT UNSIGNED NOT NULL,
|
||||
dependency_package_name VARCHAR(127) NOT NULL, -- Not an actual package, but an an alias. Allows for package substitution.
|
||||
|
||||
FOREIGN KEY (package) REFERENCES Packages (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (dependency_type) REFERENCES DependencyTypes (id)
|
||||
);
|
||||
|
||||
-- conflicts, provides, replaces, etc.
|
||||
CREATE TABLE RelationTypes ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(31) UNIQUE NOT NULL
|
||||
);
|
||||
INSERT INTO RelationTypes (id, name) VALUES
|
||||
(1, 'conflicts'),
|
||||
(2, 'provides'),
|
||||
(3, 'replaces');
|
||||
|
||||
-- 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,
|
||||
|
||||
package INT UNSIGNED NOT NULL,
|
||||
relation_type TINYINT UNSIGNED NOT NULL,
|
||||
relation_package_name VARCHAR(127) NOT NULL,
|
||||
|
||||
FOREIGN KEY (package) REFERENCES Packages (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (relation_type) REFERENCES RelationTypes (id)
|
||||
);
|
||||
|
||||
-- Public user profile
|
||||
/* CREATE TABLE UserProfiles ( user_id INT UNSIGNED PRIMARY KEY,
|
||||
real_name VARCHAR(63) NULL,
|
||||
homepage TEXT NULL, -- bio / description / whatever
|
||||
irc_nick VARCHAR(31) NULL,
|
||||
pgp_key CHAR(40) NULL,
|
||||
language VARCHAR(31) NULL, -- only for display
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE
|
||||
); */
|
||||
|
||||
-- Settings for the User
|
||||
/* CREATE TABLE UserPreferences ( user_id INT UNSIGNED PRIMARY KEY,
|
||||
inactive BOOLEAN DEFAULT 0 NOT NULL, -- user is no longer active
|
||||
show_email BOOLEAN DEFAULT 0 NOT NULL, -- on public profile page
|
||||
utc_timezone TINYINT DEFAULT 0 NOT NULL, -- adjust timestamps shown
|
||||
backup_email VARCHAR(127) NULL, -- to restore the account
|
||||
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE
|
||||
); */
|
||||
|
||||
-- Levels of access to the repository
|
||||
/* CREATE TABLE AccessRoles ( id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(31) UNIQUE NOT NULL,
|
||||
description VARCHAR(255) NULL
|
||||
); */
|
||||
-- Roles that a user has
|
||||
/* CREATE TABLE UserAccessRoles (
|
||||
user_id INT UNSIGNED,
|
||||
role_id TINYINT UNSIGNED,
|
||||
|
||||
PRIMARY KEY (user_id, role_id), -- composite key
|
||||
FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (role_id) REFERENCES AccessRoles(id) ON DELETE CASCADE
|
||||
); */
|
||||
|
||||
-- Votes
|
||||
/* CREATE TABLE PackageBaseUserVotes (
|
||||
package_base INT UNSIGNED,
|
||||
user INT UNSIGNED,
|
||||
score TINYINT UNSIGNED DEFAULT 0 NOT NULL CHECK (score <= 10),
|
||||
|
||||
comment VARCHAR(255) NULL,
|
||||
log TEXT NULL, -- error logs, etc.
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL,
|
||||
|
||||
PRIMARY KEY (package_base, user), -- composite key
|
||||
FOREIGN KEY (package_base) REFERENCES PackageBases (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user) REFERENCES Users (id) ON DELETE CASCADE
|
||||
); */
|
||||
|
||||
-- Information about licenses
|
||||
/* CREATE TABLE Licenses ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(127) UNIQUE NOT NULL,
|
||||
description TEXT NULL
|
||||
); */
|
||||
-- Information about licenses
|
||||
/* CREATE TABLE PackageLicenses (
|
||||
package INT UNSIGNED,
|
||||
license INT UNSIGNED,
|
||||
|
||||
PRIMARY KEY (package, license), -- composite key
|
||||
FOREIGN KEY (package) REFERENCES Packages (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (license) REFERENCES Licenses (id) ON DELETE CASCADE
|
||||
); */
|
40
3/coursework/src/database/src/atomic.rs
Normal file
40
3/coursework/src/database/src/atomic.rs
Normal file
@ -0,0 +1,40 @@
|
||||
type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait Atomic {
|
||||
type Transaction<'a>;
|
||||
|
||||
async fn start_transaction(&mut self) -> Result<Self::Transaction<'_>>;
|
||||
async fn abort_transaction(transaction: Self::Transaction<'_>) -> Result;
|
||||
async fn commit_transaction(transaction: Self::Transaction<'_>) -> Result;
|
||||
}
|
||||
|
||||
use sqlx::Connection;
|
||||
|
||||
impl Atomic for sqlx::MySqlPool {
|
||||
type Transaction<'a> = sqlx::MySqlTransaction<'a>;
|
||||
|
||||
async fn start_transaction(&mut self) -> Result<Self::Transaction<'_>> {
|
||||
self.begin().await.map_err(Box::from)
|
||||
}
|
||||
async fn abort_transaction(transaction: Self::Transaction<'_>) -> Result {
|
||||
transaction.rollback().await.map_err(Box::from)
|
||||
}
|
||||
async fn commit_transaction(transaction: Self::Transaction<'_>) -> Result {
|
||||
transaction.commit().await.map_err(Box::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl Atomic for sqlx::MySqlConnection {
|
||||
type Transaction<'a> = sqlx::MySqlTransaction<'a>;
|
||||
|
||||
async fn start_transaction(&mut self) -> Result<Self::Transaction<'_>> {
|
||||
self.begin().await.map_err(Box::from)
|
||||
}
|
||||
async fn abort_transaction(transaction: Self::Transaction<'_>) -> Result {
|
||||
transaction.rollback().await.map_err(Box::from)
|
||||
}
|
||||
async fn commit_transaction(transaction: Self::Transaction<'_>) -> Result {
|
||||
transaction.commit().await.map_err(Box::from)
|
||||
}
|
||||
}
|
44
3/coursework/src/database/src/connect.rs
Normal file
44
3/coursework/src/database/src/connect.rs
Normal file
@ -0,0 +1,44 @@
|
||||
type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait Connect {
|
||||
type Connection;
|
||||
|
||||
async fn open_connection(&self) -> Result<Self::Connection>;
|
||||
async fn close_connection(connection: Self::Connection) -> Result;
|
||||
}
|
||||
|
||||
use sqlx::Connection;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MySqlPool {
|
||||
pool: sqlx::MySqlPool,
|
||||
}
|
||||
impl Connect for MySqlPool {
|
||||
type Connection = sqlx::MySqlPool;
|
||||
|
||||
async fn open_connection(&self) -> Result<Self::Connection> {
|
||||
Ok(self.pool.clone())
|
||||
}
|
||||
async fn close_connection(_: Self::Connection) -> Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MySqlConnection {
|
||||
link: String,
|
||||
}
|
||||
|
||||
impl Connect for MySqlConnection {
|
||||
type Connection = sqlx::MySqlConnection;
|
||||
|
||||
async fn open_connection(&self) -> Result<Self::Connection> {
|
||||
sqlx::MySqlConnection::connect(&self.link)
|
||||
.await
|
||||
.map_err(Box::from)
|
||||
}
|
||||
async fn close_connection(connection: Self::Connection) -> Result {
|
||||
connection.close().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
16
3/coursework/src/database/src/lib.rs
Normal file
16
3/coursework/src/database/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use garde::{Report, Unvalidated, Valid, Validate};
|
||||
|
||||
pub trait IntoValid: Validate {
|
||||
fn into_valid(self) -> Result<Valid<Self>, Report>
|
||||
where
|
||||
Self: Sized,
|
||||
<Self as Validate>::Context: Default,
|
||||
{
|
||||
Unvalidated::new(self).validate()
|
||||
}
|
||||
}
|
||||
impl<T: garde::Validate> IntoValid for T {}
|
||||
|
||||
pub mod atomic;
|
||||
pub mod connect;
|
||||
pub mod repository;
|
2
3/coursework/src/database/src/repository.rs
Normal file
2
3/coursework/src/database/src/repository.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod session;
|
||||
pub mod user;
|
51
3/coursework/src/database/src/repository/session.rs
Normal file
51
3/coursework/src/database/src/repository/session.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use super::user::User;
|
||||
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait SessionRepository<C> {
|
||||
async fn start(connection: &mut C, user: User) -> Result<Session>;
|
||||
async fn end(connection: &mut C, session: Session) -> Result<User>;
|
||||
}
|
||||
|
||||
#[derive(DerefMut, Deref)]
|
||||
pub struct Session {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
user: User,
|
||||
|
||||
start: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub const fn start(&self) -> DateTime<Utc> {
|
||||
self.start
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SessionAdapter;
|
||||
|
||||
impl<E> SessionRepository<E> for SessionAdapter
|
||||
where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
async fn start(connection: &mut E, user: User) -> Result<Session> {
|
||||
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<User> {
|
||||
Ok(session.user)
|
||||
}
|
||||
}
|
164
3/coursework/src/database/src/repository/user.rs
Normal file
164
3/coursework/src/database/src/repository/user.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use derive_more::{Deref, From, Into};
|
||||
use garde::{Valid, Validate};
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
pub type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait UserRepository<C> {
|
||||
async fn get_by_id(connection: &C, id: Id) -> Result<Option<User>>;
|
||||
async fn get_by_name(connection: &C, name: &Valid<Username>) -> Result<Option<User>>;
|
||||
async fn get_by_email(connection: &C, email: &Valid<Email>) -> Result<Option<User>>;
|
||||
|
||||
async fn change_name(connection: &mut C, user: &mut User, name: Valid<Username>) -> Result;
|
||||
async fn change_email(connection: &mut C, user: &mut User, email: Valid<Email>) -> Result;
|
||||
async fn change_password(
|
||||
connection: &mut C,
|
||||
user: &mut User,
|
||||
password: Valid<Password>,
|
||||
) -> Result;
|
||||
|
||||
async fn create(connection: &mut C, user: Valid<UserData>) -> Result<User>;
|
||||
}
|
||||
|
||||
#[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<QueryUser> 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<E> UserRepository<E> for UserAdapter
|
||||
where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
async fn get_by_id(connection: &E, id: Id) -> Result<Option<User>> {
|
||||
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<Username>) -> Result<Option<User>> {
|
||||
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<Email>) -> Result<Option<User>> {
|
||||
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<Username>) -> 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<Email>) -> 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<Password>,
|
||||
) -> 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<UserData>) -> Result<User> {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user