From b73e252639a71fca35cc9ce4bbb3ac9fc1140213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Drouet?= Date: Thu, 26 Mar 2020 10:39:45 +0100 Subject: [PATCH] react-rust-postgres: add connection to postgres with migrations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérémie Drouet --- react-rust-postgres/backend/Cargo.toml | 7 +- react-rust-postgres/backend/Dockerfile | 7 ++ react-rust-postgres/backend/diesel.toml | 5 ++ .../backend/migrations/.gitkeep | 0 .../down.sql | 6 ++ .../up.sql | 36 +++++++++ .../2020-03-26-084736_create_people/down.sql | 1 + .../2020-03-26-084736_create_people/up.sql | 4 + react-rust-postgres/backend/src/main.rs | 77 ++++++++++++++++++- react-rust-postgres/backend/src/schema.rs | 6 ++ react-rust-postgres/backend/src/user.rs | 18 +++++ react-rust-postgres/docker-compose.yaml | 29 ++++++- 12 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 react-rust-postgres/backend/diesel.toml create mode 100644 react-rust-postgres/backend/migrations/.gitkeep create mode 100644 react-rust-postgres/backend/migrations/00000000000000_diesel_initial_setup/down.sql create mode 100644 react-rust-postgres/backend/migrations/00000000000000_diesel_initial_setup/up.sql create mode 100644 react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/down.sql create mode 100644 react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/up.sql create mode 100644 react-rust-postgres/backend/src/schema.rs create mode 100644 react-rust-postgres/backend/src/user.rs diff --git a/react-rust-postgres/backend/Cargo.toml b/react-rust-postgres/backend/Cargo.toml index 99c6eb4..36d1526 100644 --- a/react-rust-postgres/backend/Cargo.toml +++ b/react-rust-postgres/backend/Cargo.toml @@ -7,12 +7,17 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +diesel_migrations = "1.4.0" rocket = "0.4.4" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" +[dependencies.diesel] +version = "1.0.0" +features = ["postgres"] + [dependencies.rocket_contrib] version = "0.4.4" default-features = false -features = ["json"] +features = ["json", "diesel_postgres_pool"] diff --git a/react-rust-postgres/backend/Dockerfile b/react-rust-postgres/backend/Dockerfile index 5424f3e..2f75c1a 100644 --- a/react-rust-postgres/backend/Dockerfile +++ b/react-rust-postgres/backend/Dockerfile @@ -1,7 +1,12 @@ FROM jdrouet/rust-nightly:buster-slim AS base +RUN apt-get update \ + && apt-get install -y libpq-dev \ + && rm -rf /var/lib/apt/lists/* + ENV USER=root ENV ROCKET_ADDRESS=0.0.0.0 +ENV ROCKET_ENV=development WORKDIR /code RUN cargo init @@ -21,6 +26,8 @@ RUN cargo build --release --offline FROM debian:buster-slim +ENV ROCKET_ENV=production + EXPOSE 8000 COPY --from=builder /code/target/release/react-rust-postgres /react-rust-postgres diff --git a/react-rust-postgres/backend/diesel.toml b/react-rust-postgres/backend/diesel.toml new file mode 100644 index 0000000..92267c8 --- /dev/null +++ b/react-rust-postgres/backend/diesel.toml @@ -0,0 +1,5 @@ +# For documentation on how to configure this file, +# see diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" diff --git a/react-rust-postgres/backend/migrations/.gitkeep b/react-rust-postgres/backend/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/react-rust-postgres/backend/migrations/00000000000000_diesel_initial_setup/down.sql b/react-rust-postgres/backend/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/react-rust-postgres/backend/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/react-rust-postgres/backend/migrations/00000000000000_diesel_initial_setup/up.sql b/react-rust-postgres/backend/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/react-rust-postgres/backend/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/react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/down.sql b/react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/down.sql new file mode 100644 index 0000000..cc1f647 --- /dev/null +++ b/react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/down.sql @@ -0,0 +1 @@ +DROP TABLE users; diff --git a/react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/up.sql b/react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/up.sql new file mode 100644 index 0000000..48e8330 --- /dev/null +++ b/react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/up.sql @@ -0,0 +1,4 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + login TEXT UNIQUE NOT NULL +); diff --git a/react-rust-postgres/backend/src/main.rs b/react-rust-postgres/backend/src/main.rs index 8b08ae7..c474595 100644 --- a/react-rust-postgres/backend/src/main.rs +++ b/react-rust-postgres/backend/src/main.rs @@ -1,12 +1,32 @@ #![feature(proc_macro_hygiene, decl_macro)] +#[macro_use] +extern crate diesel; +#[macro_use] +extern crate diesel_migrations; #[macro_use] extern crate rocket; #[macro_use] extern crate serde_derive; +#[macro_use] extern crate rocket_contrib; +mod schema; +mod user; + +use rocket::config::{Config, Environment, Value}; +use rocket::fairing::AdHoc; use rocket_contrib::json::Json; +use std::collections::HashMap; +use std::env; + +// This macro from `diesel_migrations` defines an `embedded_migrations` module +// containing a function named `run`. This allows the example to be run and +// tested without any outside setup of the database. +embed_migrations!(); + +#[database("my_db")] +struct MyDBConn(diesel::PgConnection); #[derive(Serialize)] struct HelloMessage { @@ -14,12 +34,63 @@ struct HelloMessage { } #[get("/")] -fn index() -> Json { +fn index(conn: MyDBConn) -> Json { + let result = match user::User::all(&*conn) { + Ok(res) => res.len(), + Err(_) => 0, + }; + Json(HelloMessage { - message: String::from("Hello, world"), + message: format!("Hello with {} users", result), }) } +fn get_config() -> Config { + let mut database_config = HashMap::new(); + let mut databases = HashMap::new(); + + let env_address = env::var("ROCKET_ADDRESS") + .or::(Ok(String::from("localhost"))) + .unwrap(); + + let env_mode = env::var("ROCKET_ENV") + .or(Ok(String::from("development"))) + .and_then(|value| value.parse::()) + .unwrap(); + + let database_url = match env::var("DATABASE_URL") { + Ok(value) => value, + Err(_) => String::from("postgres://localhost/postgres"), + }; + + database_config.insert("url", Value::from(database_url)); + databases.insert("my_db", Value::from(database_config)); + + let config = Config::build(env_mode) + .address(env_address) + .extra("databases", databases) + .finalize() + .unwrap(); + + config +} + +fn run_db_migrations(r: rocket::Rocket) -> Result { + let conn = MyDBConn::get_one(&r).expect("database connection"); + match embedded_migrations::run(&*conn) { + Ok(()) => Ok(r), + Err(e) => { + println!("Failed to run database migrations: {:?}", e); + Err(r) + } + } +} + fn main() { - rocket::ignite().mount("/", routes![index]).launch(); + let config = get_config(); + rocket::custom(config) + .attach(MyDBConn::fairing()) + .attach(AdHoc::on_attach("Database Migrations", run_db_migrations)) + .mount("/", routes![index]) + .launch(); } diff --git a/react-rust-postgres/backend/src/schema.rs b/react-rust-postgres/backend/src/schema.rs new file mode 100644 index 0000000..4087ca1 --- /dev/null +++ b/react-rust-postgres/backend/src/schema.rs @@ -0,0 +1,6 @@ +table! { + users (id) { + id -> Int4, + login -> Text, + } +} diff --git a/react-rust-postgres/backend/src/user.rs b/react-rust-postgres/backend/src/user.rs new file mode 100644 index 0000000..6ea71b9 --- /dev/null +++ b/react-rust-postgres/backend/src/user.rs @@ -0,0 +1,18 @@ +#![allow(proc_macro_derive_resolution_fallback)] + +use diesel; +use diesel::prelude::*; +use super::schema::users; + +#[derive(Queryable, AsChangeset, Serialize, Deserialize)] +#[table_name = "users"] +pub struct User { + pub id: i32, + pub login: String, +} + +impl User { + pub fn all(connection: &PgConnection) -> QueryResult> { + users::table.load::(&*connection) + } +} diff --git a/react-rust-postgres/docker-compose.yaml b/react-rust-postgres/docker-compose.yaml index 14824eb..263ed2b 100644 --- a/react-rust-postgres/docker-compose.yaml +++ b/react-rust-postgres/docker-compose.yaml @@ -1,16 +1,43 @@ version: "3.7" services: + frontend: + build: + context: frontend + target: development + networks: + - client-side + ports: + - 3000:3000 + volumes: + - ./frontend/src:/code/src:ro backend: build: context: backend target: development + environment: + - DATABASE_URL=postgres://postgres:mysecretpassword@db/postgres networks: + - client-side - server-side volumes: - - ./backend/src:/code/src:ro + - ./backend/src:/code/src - backend-cache:/code/target + depends_on: + - db + db: + image: postgres:12-alpine + restart: always + environment: + - POSTGRES_PASSWORD=mysecretpassword + networks: + - server-side + ports: + - 5432:5432 + volumes: + - db-data:/var/lib/postgresql/data networks: client-side: {} server-side: {} volumes: backend-cache: {} + db-data: {}