From d47bc85b23990bf18bf819fd0a9d8b3a4823ae86 Mon Sep 17 00:00:00 2001 From: Nicolas del Valle Date: Sun, 8 Sep 2024 13:48:26 +0200 Subject: [PATCH] Setup diesel initial implementation --- Cargo.lock | 223 +++++++++++++++- Cargo.toml | 4 +- config/default.json | 6 +- diesel.toml | 9 + migrations/.keep | 0 .../down.sql | 6 + .../up.sql | 36 +++ .../2024-07-14-055806_create_users/down.sql | 1 + .../2024-07-14-055806_create_users/up.sql | 11 + .../2024-07-14-134620_create_cats/down.sql | 1 + .../2024-07-14-134620_create_cats/up.sql | 9 + src/database.rs | 40 +-- src/errors.rs | 5 + src/main.rs | 1 + src/models/cat.rs | 4 +- src/models/mod.rs | 7 +- src/models/user.rs | 99 +++++--- src/routes/cat.rs | 1 - src/routes/user.rs | 7 +- src/schema.rs | 34 +++ src/utils/models.rs | 238 ++++-------------- 21 files changed, 476 insertions(+), 266 deletions(-) create mode 100644 diesel.toml create mode 100644 migrations/.keep create mode 100644 migrations/00000000000000_diesel_initial_setup/down.sql create mode 100644 migrations/00000000000000_diesel_initial_setup/up.sql create mode 100644 migrations/2024-07-14-055806_create_users/down.sql create mode 100644 migrations/2024-07-14-055806_create_users/up.sql create mode 100644 migrations/2024-07-14-134620_create_cats/down.sql create mode 100644 migrations/2024-07-14-134620_create_cats/up.sql create mode 100644 src/schema.rs diff --git a/Cargo.lock b/Cargo.lock index 8827128..bce990b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,6 +344,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.0", ] @@ -565,6 +566,23 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "deadpool" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6541a3916932fe57768d4be0b1ffb5ec7cbf74ca8c903fdfd5c0fe8aa958f0ed" +dependencies = [ + "deadpool-runtime", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" + [[package]] name = "derivative" version = "2.2.0" @@ -589,6 +607,56 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diesel" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf97ee7261bb708fa3402fa9c17a54b70e90e3cb98afb3dc8999d5512cb03f94" +dependencies = [ + "bitflags 2.3.3", + "byteorder", + "chrono", + "diesel_derives", + "itoa", +] + +[[package]] +name = "diesel-async" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb799bb6f8ca6a794462125d7b8983b0c86e6c93a33a9c55934a4a5de4409d3" +dependencies = [ + "async-trait", + "deadpool", + "diesel", + "futures-util", + "scoped-futures", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "diesel_derives" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ff2be1e7312c858b2ef974f5c7089833ae57b5311b334b30923af58e5718d8" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn 2.0.48", +] + [[package]] name = "diff" version = "0.1.13" @@ -615,6 +683,26 @@ dependencies = [ "const-random", ] +[[package]] +name = "dsl_auto_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +dependencies = [ + "darling 0.20.8", + "either", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -630,7 +718,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -663,6 +751,12 @@ dependencies = [ "libc", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "1.9.0" @@ -886,6 +980,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.2" @@ -1560,7 +1660,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets 0.48.1", ] @@ -1639,6 +1739,24 @@ dependencies = [ "sha2", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.2" @@ -1677,6 +1795,35 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "postgres-protocol" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" +dependencies = [ + "base64 0.21.2", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1786,6 +1933,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.9.1" @@ -1916,6 +2072,8 @@ dependencies = [ "bytes", "chrono", "config", + "diesel", + "diesel-async", "futures", "jsonwebtoken", "mime", @@ -2040,6 +2198,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-futures" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467" +dependencies = [ + "cfg-if", + "pin-utils", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2257,6 +2425,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.8" @@ -2396,7 +2570,7 @@ dependencies = [ "autocfg", "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", "windows-sys", ] @@ -2522,6 +2696,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-postgres" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2 0.5.5", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -2881,6 +3081,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.87" @@ -2976,6 +3182,17 @@ dependencies = [ "webpki", ] +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", + "web-sys", +] + [[package]] name = "widestring" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index d49cbc4..a7a050a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ tower-http = { version = "0.4.4", features = [ "sensitive-headers", "cors", ] } -chrono = "0.4.38" +chrono = { version = "0.4", features = ["serde"] } async-trait = "0.1.81" # Investigate if wither::bson can be used instead and activate this feature. bson = { version = "2.10.0", features = ["serde_with", "chrono-0_4"] } @@ -35,6 +35,8 @@ bcrypt = "0.15.1" validator = { version = "0.18.1", features = ["derive"] } mime = "0.3.17" bytes = "1.6.0" +diesel = { version = "2.1", features = ["chrono"] } +diesel-async = { version = "0.5", features = ["postgres", "deadpool"] } [dev-dependencies] assert-json-diff = "2.0.2" diff --git a/config/default.json b/config/default.json index 12e57db..ebfdb49 100644 --- a/config/default.json +++ b/config/default.json @@ -1,19 +1,15 @@ { "environment": "development", - "server": { "port": 8080 }, - "database": { - "uri": "mongodb://localhost:27017", + "uri": "postgresql://localhost:5432", "name": "rustapi" }, - "auth": { "secret": "secret" }, - "logger": { "level": "debug" } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..5d7f0cc --- /dev/null +++ b/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "/Users/ndelvalle/Code/ndelvalle/rustapi/migrations" diff --git a/migrations/.keep b/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/2024-07-14-055806_create_users/down.sql b/migrations/2024-07-14-055806_create_users/down.sql new file mode 100644 index 0000000..9951735 --- /dev/null +++ b/migrations/2024-07-14-055806_create_users/down.sql @@ -0,0 +1 @@ +DROP TABLE users diff --git a/migrations/2024-07-14-055806_create_users/up.sql b/migrations/2024-07-14-055806_create_users/up.sql new file mode 100644 index 0000000..9bf3bad --- /dev/null +++ b/migrations/2024-07-14-055806_create_users/up.sql @@ -0,0 +1,11 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + locked_at TIMESTAMP +); + +SELECT diesel_manage_updated_at('users'); diff --git a/migrations/2024-07-14-134620_create_cats/down.sql b/migrations/2024-07-14-134620_create_cats/down.sql new file mode 100644 index 0000000..978669e --- /dev/null +++ b/migrations/2024-07-14-134620_create_cats/down.sql @@ -0,0 +1 @@ +DROP TABLE cats diff --git a/migrations/2024-07-14-134620_create_cats/up.sql b/migrations/2024-07-14-134620_create_cats/up.sql new file mode 100644 index 0000000..f6262a9 --- /dev/null +++ b/migrations/2024-07-14-134620_create_cats/up.sql @@ -0,0 +1,9 @@ +CREATE TABLE cats ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +SELECT diesel_manage_updated_at('cats '); diff --git a/src/database.rs b/src/database.rs index 48c5ede..6c93ebe 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,21 +1,33 @@ -use mongodb::Database; -use tokio::sync::OnceCell; -use wither::mongodb; +use diesel_async::pooled_connection::deadpool::{self, Pool}; +use diesel_async::pooled_connection::AsyncDieselConnectionManager; +use diesel_async::AsyncPgConnection; +use once_cell::sync::OnceCell; use crate::settings::SETTINGS; -static CONNECTION: OnceCell = OnceCell::const_new(); +type PooledConnection = deadpool::Object; -pub async fn connection() -> &'static Database { - CONNECTION - .get_or_init(|| async { - let db_uri = SETTINGS.database.uri.as_str(); - let db_name = SETTINGS.database.name.as_str(); +static CONNECTION: OnceCell> = OnceCell::new(); - mongodb::Client::with_uri_str(db_uri) - .await - .expect("Failed to initialize MongoDB connection") - .database(db_name) - }) +pub async fn get_connection() -> PooledConnection { + let conn = get_connection_pool() .await + .get() + .await + .expect("Failed to get connection from the Pool"); + + conn +} + +async fn get_connection_pool() -> &'static Pool { + CONNECTION.get_or_init(|| { + let db_uri = SETTINGS.database.uri.as_str(); + let db_name = SETTINGS.database.name.as_str(); + let db_url = format!("{}/{}", db_uri, db_name); + + let config = AsyncDieselConnectionManager::::new(db_url); + Pool::builder(config) + .build() + .expect("Failed to initialize PostgreSQL connection pool") + }) } diff --git a/src/errors.rs b/src/errors.rs index 3859941..deb2f21 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -2,6 +2,7 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; use bcrypt::BcryptError; +use diesel::result::Error as DieselError; use serde_json::json; use tokio::task::JoinError; use wither::bson; @@ -14,6 +15,9 @@ pub enum Error { #[error("{0}")] Wither(#[from] WitherError), + #[error("{0}")] + Diesel(#[from] DieselError), + #[error("{0}")] Mongo(#[from] MongoError), @@ -59,6 +63,7 @@ impl Error { (StatusCode::INTERNAL_SERVER_ERROR, 5001) } Error::Wither(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5002), + Error::Diesel(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5002), Error::Mongo(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5003), Error::SerializeMongoResponse(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5004), Error::RunSyncTask(_) => (StatusCode::INTERNAL_SERVER_ERROR, 5005), diff --git a/src/main.rs b/src/main.rs index 42ea311..d56dbea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod errors; mod logger; mod models; mod routes; +mod schema; mod settings; mod utils; diff --git a/src/models/cat.rs b/src/models/cat.rs index 660f4cd..424d2ad 100644 --- a/src/models/cat.rs +++ b/src/models/cat.rs @@ -7,9 +7,9 @@ use wither::Model as WitherModel; use crate::utils::date; use crate::utils::date::Date; -use crate::utils::models::ModelExt; +// use crate::utils::models::ModelExt; -impl ModelExt for Cat {} +// impl ModelExt for Cat {} #[derive(Debug, Clone, Serialize, Deserialize, WitherModel, Validate)] #[model(index(keys = r#"doc!{ "user": 1, "created_at": 1 }"#))] diff --git a/src/models/mod.rs b/src/models/mod.rs index 0ace409..e7db1e5 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,12 +1,11 @@ pub mod cat; pub mod user; -use crate::utils::models::ModelExt; +use crate::utils::models::QueryModelExt; use crate::Error; pub async fn sync_indexes() -> Result<(), Error> { - user::User::sync_indexes().await?; - cat::Cat::sync_indexes().await?; - + // user::User::sync_indexes().await?; + // cat::Cat::sync_indexes().await?; Ok(()) } diff --git a/src/models/user.rs b/src/models/user.rs index dba8baa..28f1d0d 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,50 +1,70 @@ -use bson::serde_helpers::bson_datetime_as_rfc3339_string; -use bson::serde_helpers::serialize_object_id_as_hex_string; +use chrono::{NaiveDateTime, Utc}; +use diesel::associations::Identifiable; +use diesel::prelude::{Insertable, Queryable, Selectable}; +use diesel::query_dsl::methods::FilterDsl; +use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; use tokio::task; use validator::Validate; -use wither::bson::{doc, oid::ObjectId}; -use wither::Model as WitherModel; +use crate::database; use crate::errors::Error; -use crate::utils::date; -use crate::utils::date::Date; -use crate::utils::models::ModelExt; +use crate::schema::users; -impl ModelExt for User {} - -#[derive(Debug, Clone, Serialize, Deserialize, WitherModel, Validate)] -#[model(index(keys = r#"doc!{ "email": 1 }"#, options = r#"doc!{ "unique": true }"#))] +#[derive(Debug, Clone, Serialize, Deserialize, Identifiable, Queryable, Selectable)] +#[diesel(table_name = crate::schema::users)] +#[diesel(check_for_backend(diesel::pg::Pg))] pub struct User { - #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] - pub id: Option, - #[validate(length(min = 1))] + pub id: i32, pub name: String, - #[validate(email)] pub email: String, pub password: String, - pub updated_at: Date, - pub created_at: Date, - pub locked_at: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub locked_at: Option, +} + +#[derive(Debug, Insertable, Validate)] +#[diesel(table_name = crate::schema::users)] +pub struct NewUser<'a> { + #[validate(length(min = 5))] + pub name: &'a str, + #[validate(email)] + pub email: &'a str, + pub password: &'a str, } impl User { - pub fn new(name: A, email: B, password_hash: C) -> Self - where - A: Into, - B: Into, - C: Into, - { - let now = date::now(); - Self { - id: None, - name: name.into(), - email: email.into(), - password: password_hash.into(), - updated_at: now, - created_at: now, - locked_at: None, - } + pub async fn create(name: &str, email: &str, password_hash: &str) -> User { + let now = Utc::now().naive_utc(); + let new_user = NewUser { + name, + email, + password: password_hash, + }; + + let mut conn = database::get_connection().await; + let user = diesel::insert_into(users::table) + .values(&new_user) + .get_result::(&mut conn) + .await + .expect("Error saving new post"); + + user + } + + pub async fn find_by_email(value: &str) -> Result, Error> { + use crate::schema::users::dsl::*; + use diesel::ExpressionMethods; + use diesel::OptionalExtension; + + let mut conn = database::get_connection().await; + users + .filter(email.eq(value)) + .first::(&mut conn) + .await + .optional() + .map_err(Error::Diesel) } pub fn is_password_match(&self, password: &str) -> bool { @@ -54,20 +74,17 @@ impl User { #[derive(Debug, Serialize, Deserialize)] pub struct PublicUser { - #[serde(alias = "_id", serialize_with = "serialize_object_id_as_hex_string")] - pub id: ObjectId, + pub id: i32, pub name: String, pub email: String, - #[serde(with = "bson_datetime_as_rfc3339_string")] - pub updated_at: Date, - #[serde(with = "bson_datetime_as_rfc3339_string")] - pub created_at: Date, + pub updated_at: NaiveDateTime, + pub created_at: NaiveDateTime, } impl From for PublicUser { fn from(user: User) -> Self { Self { - id: user.id.unwrap(), + id: user.id, name: user.name.clone(), email: user.email.clone(), updated_at: user.updated_at, diff --git a/src/routes/cat.rs b/src/routes/cat.rs index c9b6cf1..707877a 100644 --- a/src/routes/cat.rs +++ b/src/routes/cat.rs @@ -13,7 +13,6 @@ use crate::errors::Error; use crate::models::cat::{Cat, PublicCat}; use crate::utils::custom_response::CustomResponseResult as Response; use crate::utils::custom_response::{CustomResponse, CustomResponseBuilder, ResponsePagination}; -use crate::utils::models::ModelExt; use crate::utils::pagination::Pagination; use crate::utils::to_object_id::to_object_id; use crate::utils::token::TokenUser; diff --git a/src/routes/user.rs b/src/routes/user.rs index 4b74bb3..c6066a8 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -9,7 +9,6 @@ use crate::models::user; use crate::models::user::{PublicUser, User}; use crate::settings::SETTINGS; use crate::utils::custom_response::{CustomResponse, CustomResponseBuilder}; -use crate::utils::models::ModelExt; use crate::utils::token; pub fn create_route() -> Router { @@ -20,8 +19,8 @@ pub fn create_route() -> Router { async fn create_user(Json(body): Json) -> Result, Error> { let password_hash = user::hash_password(body.password).await?; - let user = User::new(body.name, body.email, password_hash); - let user = User::create(user).await?; + // let user = User::new(body.name, body.email, password_hash); + let user = User::create(&body.name, &body.email, &password_hash).await; let res = PublicUser::from(user); let res = CustomResponseBuilder::new() @@ -48,7 +47,7 @@ async fn authenticate_user( return Err(Error::bad_request()); } - let user = User::find_one(doc! { "email": email }, None).await?; + let user = User::find_by_email(email).await?; let user = match user { Some(user) => user, diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..47e8fe1 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,34 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + cats (id) { + id -> Int4, + user_id -> Int4, + #[max_length = 255] + name -> Varchar, + created_at -> Timestamp, + updated_at -> Timestamp, + } +} + +diesel::table! { + users (id) { + id -> Int4, + #[max_length = 255] + name -> Varchar, + #[max_length = 255] + email -> Varchar, + #[max_length = 255] + password -> Varchar, + created_at -> Timestamp, + updated_at -> Timestamp, + locked_at -> Nullable, + } +} + +diesel::joinable!(cats -> users (user_id)); + +diesel::allow_tables_to_appear_in_same_query!( + cats, + users, +); diff --git a/src/utils/models.rs b/src/utils/models.rs index 557d45d..175ccf0 100644 --- a/src/utils/models.rs +++ b/src/utils/models.rs @@ -1,202 +1,58 @@ -#![allow(dead_code)] - use async_trait::async_trait; -use futures::stream::TryStreamExt; -use serde::{de::DeserializeOwned, ser::Serialize}; -use validator::Validate; -use wither::bson::doc; -use wither::bson::from_bson; -use wither::bson::Bson; -use wither::bson::Document; -use wither::bson::{self, oid::ObjectId}; -use wither::mongodb::options::FindOneAndUpdateOptions; -use wither::mongodb::options::FindOneOptions; -use wither::mongodb::options::FindOptions; -use wither::mongodb::options::ReturnDocument; -use wither::mongodb::options::UpdateOptions; -use wither::mongodb::results::DeleteResult; -use wither::mongodb::results::UpdateResult; -use wither::Model as WitherModel; -use wither::ModelCursor; + +use diesel::pg::Pg; + +use diesel::associations::HasTable; +use diesel::prelude::Queryable; +use diesel::prelude::Table; +use diesel_async::RunQueryDsl; use crate::database; use crate::errors::Error; -// This is the Model trait. All models that have a MongoDB collection should -// implement this and therefore inherit theses methods. #[async_trait] -pub trait ModelExt +pub trait QueryModelExt where - Self: WitherModel + Validate, + Self: Queryable + HasTable + Send + Sync, + T: Table + Send + Sync, { - async fn create(mut model: Self) -> Result { - let connection = database::connection().await; - model.validate().map_err(|_error| Error::bad_request())?; - model.save(connection, None).await.map_err(Error::Wither)?; - - Ok(model) - } - - async fn find_by_id(id: &ObjectId) -> Result, Error> { - let connection = database::connection().await; - ::find_one(connection, doc! { "_id": id }, None) - .await - .map_err(Error::Wither) - } - - async fn find_one(query: Document, options: O) -> Result, Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - ::find_one(connection, query, options) - .await - .map_err(Error::Wither) - } - - async fn find(query: Document, options: O) -> Result, Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - ::find(connection, query, options) - .await - .map_err(Error::Wither)? - .try_collect::>() - .await - .map_err(Error::Wither) - } - - async fn find_and_count(query: Document, options: O) -> Result<(Vec, u64), Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - - let count = Self::collection(connection) - .count_documents(query.clone(), None) - .await - .map_err(Error::Mongo)?; - - let items = ::find(connection, query, options.into()) - .await - .map_err(Error::Wither)? - .try_collect::>() - .await - .map_err(Error::Wither)?; - - Ok((items, count)) - } - - async fn cursor(query: Document, options: O) -> Result, Error> - where - O: Into> + Send, - { - let connection = database::connection().await; - ::find(connection, query, options) - .await - .map_err(Error::Wither) - } - - async fn find_one_and_update(query: Document, update: Document) -> Result, Error> { - let connection = database::connection().await; - let options = FindOneAndUpdateOptions::builder() - .return_document(ReturnDocument::After) - .build(); - - ::find_one_and_update(connection, query, update, options) - .await - .map_err(Error::Wither) - } - - async fn update_one( - query: Document, - update: Document, - options: O, - ) -> Result - where - O: Into> + Send, - { - let connection = database::connection().await; - Self::collection(connection) - .update_one(query, update, options) - .await - .map_err(Error::Mongo) - } - - async fn update_many( - query: Document, - update: Document, - options: O, - ) -> Result - where - O: Into> + Send, - { - let connection = database::connection().await; - Self::collection(connection) - .update_many(query, update, options) - .await - .map_err(Error::Mongo) - } - - async fn delete_many(query: Document) -> Result { - let connection = database::connection().await; - ::delete_many(connection, query, None) - .await - .map_err(Error::Wither) - } - - async fn delete_one(query: Document) -> Result { - let connection = database::connection().await; - Self::collection(connection) - .delete_one(query, None) - .await - .map_err(Error::Mongo) - } - - async fn count(query: Document) -> Result { - let connection = database::connection().await; - Self::collection(connection) - .count_documents(query, None) - .await - .map_err(Error::Mongo) - } - - async fn exists(query: Document) -> Result { - let connection = database::connection().await; - let count = Self::collection(connection) - .count_documents(query, None) - .await - .map_err(Error::Mongo)?; - - Ok(count > 0) - } - - async fn aggregate(pipeline: Vec) -> Result, Error> - where - A: Serialize + DeserializeOwned, - { - let connection = database::connection().await; - - let documents = Self::collection(connection) - .aggregate(pipeline, None) - .await - .map_err(Error::Mongo)? - .try_collect::>() - .await - .map_err(Error::Mongo)?; - - let documents = documents - .into_iter() - .map(|document| from_bson::(Bson::Document(document))) - .collect::, bson::de::Error>>() - .map_err(Error::SerializeMongoResponse)?; - - Ok(documents) + async fn find_by_id(id: i32) -> Result, Error> { + let conn = database::get_connection().await; + use diesel::prelude::*; + Self::table().find(id).first::(conn).optional() } +} - async fn sync_indexes() -> Result<(), Error> { - let connection = database::connection().await; - Self::sync(connection).await.map_err(Error::Wither) - } +impl QueryModelExt for U +where + U: Queryable + HasTable
+ Send + Sync, + T: Table + Send + Sync, +{ } + +// #[async_trait] +// pub trait QueryModelExt: Sized { +// type Table: Table; +// +// async fn find_by_id(id: i32) -> Result, Error> +// where +// Self: Queryable, +// Self::Table: FindDsl, +// { +// let mut conn = database::get_connection().await; +// Self::Table::table() +// .find(id) +// .first::(&mut conn) +// .await +// .optional() +// .map_err(Error::Diesel) +// } +// } +// +// impl QueryModelExt for T +// where +// T: HasTable + Queryable + Send + Sync, +// T::Table: Table + FindDsl + Send + Sync, +// { +// type Table = T::Table; +// }