From 4ad4ac04ce0a474205cbd8c33e3b5704ada2b58b Mon Sep 17 00:00:00 2001 From: mrasheduzzaman Date: Mon, 20 Nov 2023 13:26:22 +0600 Subject: [PATCH 1/3] Add docker compose config for postgres db --- compose.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 compose.yml diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..ecb065e --- /dev/null +++ b/compose.yml @@ -0,0 +1,30 @@ +version: "3.9" + +services: + db: + image: postgres:latest + container_name: postgres + restart: always + user: postgres + secrets: + - db-password + volumes: + - db-data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=demo + - POSTGRES_PASSWORD_FILE=/run/secrets/db-password + expose: + - 5432 + healthcheck: + test: [ "CMD", "pg_isready" ] + interval: 10s + timeout: 5s + retries: 5 + + +volumes: + db-data: + +secrets: + db-password: + file: db/pass.txt From 1e154ed63e55407cfdd783636968685cf7133c34 Mon Sep 17 00:00:00 2001 From: mrasheduzzaman Date: Mon, 20 Nov 2023 13:26:51 +0600 Subject: [PATCH 2/3] Add database password and env file --- .env | 1 + db/pass.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 .env create mode 100644 db/pass.txt diff --git a/.env b/.env new file mode 100644 index 0000000..8c8e6ad --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=postgres://postgres:admin@localhost/demo diff --git a/db/pass.txt b/db/pass.txt new file mode 100644 index 0000000..7fbe952 --- /dev/null +++ b/db/pass.txt @@ -0,0 +1 @@ +admin From de7fe46931d1d4eb704c296fa7464c8720bda314 Mon Sep 17 00:00:00 2001 From: mrasheduzzaman Date: Mon, 20 Nov 2023 23:49:39 +0600 Subject: [PATCH 3/3] Add basic modules --- .env | 4 +- Cargo.lock | 34 +++++++++++++++ Cargo.toml | 6 ++- compose.yml | 4 +- diesel.toml | 2 +- migrations/.keep | 0 .../down.sql | 6 +++ .../up.sql | 36 ++++++++++++++++ migrations/2023-11-20-073822_users/down.sql | 2 + migrations/2023-11-20-073822_users/up.sql | 6 +++ migrations/2023-11-21-063116_users/down.sql | 2 + migrations/2023-11-21-063116_users/up.sql | 6 +++ src/common/database.rs | 10 +---- src/common/mod.rs | 1 + src/controllers/mod.rs | 1 + src/controllers/user_controller.rs | 15 +++++++ src/main.rs | 23 ++++++++++- src/models/mod.rs | 1 + src/models/user.rs | 13 ++++++ src/schema.rs | 9 ++++ src/services/mod.rs | 1 + src/services/user_service.rs | 41 +++++++++++++++++++ 22 files changed, 207 insertions(+), 16 deletions(-) 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/2023-11-20-073822_users/down.sql create mode 100644 migrations/2023-11-20-073822_users/up.sql create mode 100644 migrations/2023-11-21-063116_users/down.sql create mode 100644 migrations/2023-11-21-063116_users/up.sql create mode 100644 src/common/mod.rs create mode 100644 src/controllers/mod.rs create mode 100644 src/controllers/user_controller.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/user.rs create mode 100644 src/schema.rs create mode 100644 src/services/mod.rs create mode 100644 src/services/user_service.rs diff --git a/.env b/.env index 8c8e6ad..8bd2bab 100644 --- a/.env +++ b/.env @@ -1 +1,3 @@ -DATABASE_URL=postgres://postgres:admin@localhost/demo +HOST=127.0.0.1 +PORT=5001 +DATABASE_URL="postgres://postgres:admin@localhost:5432/demo" diff --git a/Cargo.lock b/Cargo.lock index 7d4d3ed..18ea21e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,8 @@ dependencies = [ "diesel_derives", "itoa", "pq-sys", + "r2d2", + "uuid", ] [[package]] @@ -1063,6 +1065,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "rand" version = "0.8.5" @@ -1140,12 +1153,14 @@ dependencies = [ "diesel", "dotenv", "env_logger", + "once_cell", "salvo", "serde", "serde_json", "tokio", "tracing", "tracing-subscriber", + "uuid", ] [[package]] @@ -1298,6 +1313,15 @@ dependencies = [ "syn", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1795,6 +1819,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index bfddff4..9d8a434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,14 @@ edition = "2021" [dependencies] anyhow = "1.0.75" chrono = { version = "0.4.31", features = ["serde"] } -diesel = { version = "2.1.4", features = ["chrono", "postgres"] } +diesel = { version = "2.1.4", features = ["chrono", "postgres", "uuid", "r2d2"] } dotenv = "0.15.0" env_logger = "0.10.1" -salvo = { version = "0.58.3", features = ["cors", "anyhow", "logging"] } +once_cell = "1.18.0" +salvo = { version = "0.58.3", features = ["cors", "anyhow", "logging", "affix"] } serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" tokio = { version = "1.34.0", features = ["macros"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" +uuid = { version = "1.6.1", features = ["serde", "v4"] } diff --git a/compose.yml b/compose.yml index ecb065e..d8bb5bd 100644 --- a/compose.yml +++ b/compose.yml @@ -3,7 +3,7 @@ version: "3.9" services: db: image: postgres:latest - container_name: postgres + container_name: localdb restart: always user: postgres secrets: @@ -15,6 +15,8 @@ services: - POSTGRES_PASSWORD_FILE=/run/secrets/db-password expose: - 5432 + ports: + - "5432:5432" healthcheck: test: [ "CMD", "pg_isready" ] interval: 10s diff --git a/diesel.toml b/diesel.toml index 6968858..92267c8 100644 --- a/diesel.toml +++ b/diesel.toml @@ -2,4 +2,4 @@ # see diesel.rs/guides/configuring-diesel-cli [print_schema] -file = "src/schema/schema.rs" +file = "src/schema.rs" 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/2023-11-20-073822_users/down.sql b/migrations/2023-11-20-073822_users/down.sql new file mode 100644 index 0000000..cf7e736 --- /dev/null +++ b/migrations/2023-11-20-073822_users/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE users diff --git a/migrations/2023-11-20-073822_users/up.sql b/migrations/2023-11-20-073822_users/up.sql new file mode 100644 index 0000000..1369822 --- /dev/null +++ b/migrations/2023-11-20-073822_users/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +CREATE TABLE users ( + id uuid PRIMARY KEY default gen_random_uuid(), + name TEXT NOT NULL, + age INT NOT NULL +) diff --git a/migrations/2023-11-21-063116_users/down.sql b/migrations/2023-11-21-063116_users/down.sql new file mode 100644 index 0000000..cf7e736 --- /dev/null +++ b/migrations/2023-11-21-063116_users/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE users diff --git a/migrations/2023-11-21-063116_users/up.sql b/migrations/2023-11-21-063116_users/up.sql new file mode 100644 index 0000000..1369822 --- /dev/null +++ b/migrations/2023-11-21-063116_users/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +CREATE TABLE users ( + id uuid PRIMARY KEY default gen_random_uuid(), + name TEXT NOT NULL, + age INT NOT NULL +) diff --git a/src/common/database.rs b/src/common/database.rs index 12784e0..f39fcec 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -1,14 +1,9 @@ use diesel::r2d2::{self, ConnectionManager, PooledConnection}; use once_cell::sync::OnceCell; -#[cfg(feature = "database_postgres")] -type DbCon = diesel::PgConnection; - -#[cfg(all(feature = "database_postgres", debug_assertions))] -#[allow(dead_code)] +pub type DbCon = diesel::PgConnection; pub type DieselBackend = diesel::pg::Pg; - pub type Pool = r2d2::Pool>; pub type Connection = PooledConnection>; @@ -36,9 +31,6 @@ impl Database { } fn get_or_init_pool() -> &'static Pool { - #[cfg(debug_assertions)] - crate::load_env_vars(); - static POOL: OnceCell = OnceCell::new(); POOL.get_or_init(|| { diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..8fd0a6b --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1 @@ +pub mod database; diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs new file mode 100644 index 0000000..d3588d5 --- /dev/null +++ b/src/controllers/mod.rs @@ -0,0 +1 @@ +pub mod user_controller; diff --git a/src/controllers/user_controller.rs b/src/controllers/user_controller.rs new file mode 100644 index 0000000..820b696 --- /dev/null +++ b/src/controllers/user_controller.rs @@ -0,0 +1,15 @@ +use crate::services::user_service::UserService; +use salvo::prelude::*; + +use crate::common::database::Database; + +#[handler] +pub async fn all_users( + req: &mut Request, + depot: &mut Depot, + res: &mut Response, +) { + let mut connection = Database::new().get_connection(); + let users = UserService::list(&mut connection).await; + res.render(Json(users)); +} diff --git a/src/main.rs b/src/main.rs index 720342d..4548302 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,13 @@ +mod common; +mod controllers; +mod models; +mod services; +mod schema; + +use controllers::user_controller; +use dotenv::dotenv; use salvo::prelude::*; +use std::env; #[handler] async fn hello() -> &'static str { @@ -7,9 +16,19 @@ async fn hello() -> &'static str { #[tokio::main] async fn main() { + env::set_var("RUST_LOG", "debug"); tracing_subscriber::fmt().init(); - let router = Router::new().get(hello); - let acceptor = TcpListener::new("127.0.0.1:5001").bind().await; + dotenv().ok(); + + let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); + let host = env::var("HOST").expect("HOST is not set in .env file"); + let port = env::var("PORT").expect("PORT is not set in .env file"); + let server_url = format!("{host}:{port}"); + + let router = Router::new() + .get(user_controller::all_users); + let acceptor = TcpListener::new(server_url).bind().await; + Server::new(acceptor).serve(router).await; } diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src/models/user.rs b/src/models/user.rs new file mode 100644 index 0000000..2ec9b79 --- /dev/null +++ b/src/models/user.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; +use diesel::prelude::*; +use uuid::Uuid; + +use crate::schema::users; + +#[derive(Serialize, Deserialize, Queryable, Insertable)] +#[table_name = "users"] +pub struct User { + pub id: Uuid, + pub name: String, + pub age: i32, +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..af823bf --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,9 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + users (id) { + id -> Uuid, + name -> Text, + age -> Int4, + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..4f2070f --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1 @@ +pub mod user_service; diff --git a/src/services/user_service.rs b/src/services/user_service.rs new file mode 100644 index 0000000..b3bd328 --- /dev/null +++ b/src/services/user_service.rs @@ -0,0 +1,41 @@ +use diesel; +use diesel::prelude::*; +use uuid::Uuid; + +use crate::common::database::Connection; +use crate::models::user::User; + +use crate::schema::users; +use crate::schema::users::dsl::users as all_users; + + +pub struct UserService; + +impl UserService { + pub async fn list(connection: &mut Connection) -> Vec { + users::table.load::(connection).unwrap() + } + + pub async fn create(connection: &mut Connection, new_user: &User) -> User { + let data = User { + id: Uuid::new_v4(), + name: new_user.name.clone(), + age: new_user.age.clone() + }; + + let _result = diesel::insert_into(users::table).values(&data).execute(connection); + + users::table + .order(users::id.desc()) + .first(connection) + .unwrap() + } + + pub async fn delete(connection: &mut Connection, user_id: Uuid) -> bool { + diesel::delete(users::table.find(user_id)).execute(connection).is_ok() + } + + pub async fn delete_all(connection: &mut Connection) -> bool { + diesel::delete(all_users).execute(connection).is_ok() + } +}