Forráskód Böngészése

react-rust-postgres: add connection to postgres with migrations

Signed-off-by: Jérémie Drouet <jeremie.drouet@gmail.com>
Jérémie Drouet 5 éve
szülő
commit
b73e252639

+ 6 - 1
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"]

+ 7 - 0
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

+ 5 - 0
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"

+ 0 - 0
react-rust-postgres/backend/migrations/.gitkeep


+ 6 - 0
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();

+ 36 - 0
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;

+ 1 - 0
react-rust-postgres/backend/migrations/2020-03-26-084736_create_people/down.sql

@@ -0,0 +1 @@
+DROP TABLE users;

+ 4 - 0
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
+);

+ 74 - 3
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<HelloMessage> {
+fn index(conn: MyDBConn) -> Json<HelloMessage> {
+    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::<String>(Ok(String::from("localhost")))
+        .unwrap();
+
+    let env_mode = env::var("ROCKET_ENV")
+        .or(Ok(String::from("development")))
+        .and_then(|value| value.parse::<Environment>())
+        .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<rocket::Rocket, rocket::Rocket> {
+    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();
 }

+ 6 - 0
react-rust-postgres/backend/src/schema.rs

@@ -0,0 +1,6 @@
+table! {
+    users (id) {
+        id -> Int4,
+        login -> Text,
+    }
+}

+ 18 - 0
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<Vec<User>> {
+        users::table.load::<User>(&*connection)
+    }
+}

+ 28 - 1
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: {}