Browse Source

Initial commit

master
Dylan Baker 5 years ago
commit
d3803a38b5

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
1
+/target
2
+**/*.rs.bk
3
+.env

+ 1449
- 0
Cargo.lock
File diff suppressed because it is too large
View File


+ 20
- 0
Cargo.toml View File

@@ -0,0 +1,20 @@
1
+[package]
2
+name = "gist-server"
3
+version = "0.1.0"
4
+authors = ["Dylan Baker"]
5
+edition = "2018"
6
+
7
+[dependencies]
8
+diesel = { version = "1.0.0", features = ["postgres"] }
9
+dotenv = "0.9.0"
10
+r2d2 = "0.8.3"
11
+r2d2-diesel = "1.0.0"
12
+rocket = "0.4.0"
13
+serde = "1.0.82"
14
+serde_derive = "1.0.82"
15
+serde_json = "1.0.33"
16
+
17
+[dependencies.rocket_contrib]
18
+version = "0.4.0"
19
+default-features = false
20
+features = ["json", "tera_templates"]

+ 5
- 0
diesel.toml View File

@@ -0,0 +1,5 @@
1
+# For documentation on how to configure this file,
2
+# see diesel.rs/guides/configuring-diesel-cli
3
+
4
+[print_schema]
5
+file = "src/schema.rs"

+ 0
- 0
migrations/.gitkeep View File


+ 6
- 0
migrations/00000000000000_diesel_initial_setup/down.sql View File

@@ -0,0 +1,6 @@
1
+-- This file was automatically created by Diesel to setup helper functions
2
+-- and other internal bookkeeping. This file is safe to edit, any future
3
+-- changes will be added to existing projects as new migrations.
4
+
5
+DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
6
+DROP FUNCTION IF EXISTS diesel_set_updated_at();

+ 36
- 0
migrations/00000000000000_diesel_initial_setup/up.sql View File

@@ -0,0 +1,36 @@
1
+-- This file was automatically created by Diesel to setup helper functions
2
+-- and other internal bookkeeping. This file is safe to edit, any future
3
+-- changes will be added to existing projects as new migrations.
4
+
5
+
6
+
7
+
8
+-- Sets up a trigger for the given table to automatically set a column called
9
+-- `updated_at` whenever the row is modified (unless `updated_at` was included
10
+-- in the modified columns)
11
+--
12
+-- # Example
13
+--
14
+-- ```sql
15
+-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
16
+--
17
+-- SELECT diesel_manage_updated_at('users');
18
+-- ```
19
+CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
20
+BEGIN
21
+    EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
22
+                    FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
23
+END;
24
+$$ LANGUAGE plpgsql;
25
+
26
+CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
27
+BEGIN
28
+    IF (
29
+        NEW IS DISTINCT FROM OLD AND
30
+        NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
31
+    ) THEN
32
+        NEW.updated_at := current_timestamp;
33
+    END IF;
34
+    RETURN NEW;
35
+END;
36
+$$ LANGUAGE plpgsql;

+ 1
- 0
migrations/2019-04-30-203312_create_gists/down.sql View File

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

+ 5
- 0
migrations/2019-04-30-203312_create_gists/up.sql View File

@@ -0,0 +1,5 @@
1
+CREATE TABLE gists (
2
+  id SERIAL PRIMARY KEY,
3
+  title VARCHAR NOT NULL,
4
+  body TEXT NOT NULL
5
+)

+ 41
- 0
src/connection.rs View File

@@ -0,0 +1,41 @@
1
+use diesel::pg::PgConnection;
2
+use r2d2;
3
+use r2d2_diesel::ConnectionManager;
4
+use rocket::http::Status;
5
+use rocket::request::{self, FromRequest};
6
+use rocket::{Outcome, Request, State};
7
+use std::env;
8
+use std::ops::Deref;
9
+
10
+type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
11
+
12
+pub fn init_pool() -> Pool {
13
+    let manager = ConnectionManager::<PgConnection>::new(database_url());
14
+    Pool::new(manager).expect("db pool")
15
+}
16
+
17
+fn database_url() -> String {
18
+    env::var("DATABASE_URL").expect("DATABASE_URL must be set")
19
+}
20
+
21
+pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<PgConnection>>);
22
+
23
+impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
24
+    type Error = ();
25
+
26
+    fn from_request(request: &'a Request<'r>) -> request::Outcome<DbConn, Self::Error> {
27
+        let pool = request.guard::<State<Pool>>()?;
28
+        match pool.get() {
29
+            Ok(conn) => Outcome::Success(DbConn(conn)),
30
+            Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
31
+        }
32
+    }
33
+}
34
+
35
+impl Deref for DbConn {
36
+    type Target = PgConnection;
37
+
38
+    fn deref(&self) -> &Self::Target {
39
+        &self.0
40
+    }
41
+}

+ 19
- 0
src/gists/mod.rs View File

@@ -0,0 +1,19 @@
1
+use diesel::pg::PgConnection as PGC;
2
+use diesel::prelude::*;
3
+
4
+use crate::schema::gists;
5
+
6
+#[derive(Queryable, AsChangeset, Serialize, Deserialize)]
7
+pub struct Gist {
8
+    pub id: i32,
9
+    pub title: String,
10
+    pub body: String,
11
+}
12
+
13
+pub fn all(connection: &PGC) -> QueryResult<Vec<Gist>> {
14
+    gists::table.load::<Gist>(&*connection)
15
+}
16
+
17
+pub fn get(connection: &PGC, id: i32) -> QueryResult<Gist> {
18
+    gists::table.find(id).get_result::<Gist>(connection)
19
+}

+ 32
- 0
src/main.rs View File

@@ -0,0 +1,32 @@
1
+#![feature(proc_macro_hygiene, decl_macro)]
2
+
3
+#[macro_use]
4
+extern crate diesel;
5
+extern crate dotenv;
6
+extern crate r2d2;
7
+extern crate r2d2_diesel;
8
+#[macro_use]
9
+extern crate rocket;
10
+extern crate rocket_contrib;
11
+#[macro_use]
12
+extern crate serde_derive;
13
+
14
+use dotenv::dotenv;
15
+use rocket_contrib::templates::Template;
16
+
17
+mod connection;
18
+mod gists;
19
+mod routes;
20
+mod schema;
21
+
22
+use crate::routes::static_rocket_route_info_for_index;
23
+use crate::routes::static_rocket_route_info_for_show_gist;
24
+
25
+fn main() {
26
+    dotenv().ok();
27
+    rocket::ignite()
28
+        .attach(Template::fairing())
29
+        .manage(connection::init_pool())
30
+        .mount("/", routes![index, show_gist])
31
+        .launch();
32
+}

+ 34
- 0
src/routes.rs View File

@@ -0,0 +1,34 @@
1
+use diesel::result::Error;
2
+use rocket::http::Status;
3
+use rocket_contrib::json::Json;
4
+use rocket_contrib::templates::Template;
5
+use std::collections::HashMap;
6
+
7
+use crate::connection::DbConn;
8
+use crate::gists;
9
+use crate::gists::Gist;
10
+
11
+fn error_status(error: Error) -> Status {
12
+    match error {
13
+        Error::NotFound => Status::NotFound,
14
+        _ => Status::InternalServerError,
15
+    }
16
+}
17
+
18
+#[get("/")]
19
+pub fn index(connection: DbConn) -> Result<Json<Vec<Gist>>, Status> {
20
+    gists::all(&connection)
21
+        .map(|gists| Json(gists))
22
+        .map_err(|error| error_status(error))
23
+}
24
+
25
+#[get("/gists/<id>")]
26
+pub fn show_gist(connection: DbConn, id: i32) -> Template {
27
+    let gist = match gists::get(&connection, id) {
28
+        Ok(gist) => Some(gist),
29
+        Err(_) => None,
30
+    };
31
+    let mut context = HashMap::new();
32
+    context.insert("gist", gist);
33
+    Template::render("gists/show", &context)
34
+}

+ 7
- 0
src/schema.rs View File

@@ -0,0 +1,7 @@
1
+table! {
2
+    gists (id) {
3
+        id -> Int4,
4
+        title -> Varchar,
5
+        body -> Text,
6
+    }
7
+}

+ 17
- 0
templates/gists/show.tera View File

@@ -0,0 +1,17 @@
1
+<html>
2
+  <head>
3
+    <title>{{ gist.title }}</title>
4
+  </head>
5
+  <body>
6
+    {% if gist %}
7
+      <h3>{{ gist.title }}</h3>
8
+      <div>
9
+        <p>
10
+          {{ gist.body }}
11
+        </p>
12
+      </div>
13
+    {% else %}
14
+      <h3>Gist not found</h3>
15
+    {% endif %}
16
+  </body>
17
+</html>

Loading…
Cancel
Save