summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs116
-rw-r--r--src/schema.rs6
-rw-r--r--src/types.rs22
3 files changed, 115 insertions, 29 deletions
diff --git a/src/main.rs b/src/main.rs
index 8e5e1fa..ab4c044 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,59 +1,117 @@
-extern crate actix_web;
-extern crate env_logger;
 #[macro_use] extern crate serde_derive;
+#[macro_use] extern crate diesel;
 
-use actix_web::{middleware, web, HttpServer, App, HttpResponse, HttpRequest};
-use listenfd::ListenFd;
-use actix_web::web::Json;
+mod schema;
+use schema::links;
 
-#[derive(Debug, Deserialize)]
-struct UserData {
-    url: String
+mod types;
+use types::*;
+
+use actix_web::{middleware, web, HttpServer, App, HttpResponse, Result};
+use actix_web::web::{Json, Path, Data};
+use diesel::{PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, QueryResult};
+use dotenv::dotenv;
+use diesel::r2d2::{ConnectionManager, Pool};
+
+fn establish_connection() -> Pool<ConnectionManager<PgConnection>> {
+    dotenv().ok();
+    let database_url = std::env::var("DATABASE_URL").expect("Cannot find DATABASE_URL. Check .env file.");
+    let manager = ConnectionManager::<PgConnection>::new(database_url);
+    Pool::builder().max_size(4).build(manager).expect("Failed to create pool.")
+}
+
+fn make_url(url_to_check: &str) -> Result<String, ()> {
+    let url_object = match url::Url::parse(url_to_check) {
+        Ok(result_url) => result_url,
+        Err(_) => return Err(())
+    };
+    if !url_object.cannot_be_a_base() && url_object.has_host() && url_object.domain().is_some() {
+        Ok(format!("https://{}{}{}",
+                   url_object.domain().unwrap(),
+                   url_object.path(),
+                   url_object.query().map_or("".to_owned(), |q| format!("?{}", q)))
+        )
+    } else {
+        Err(())
+    }
+}
+
+fn add_entry_to_database(entry: Entry, connection: &PgConnection) -> QueryResult<Entry> {
+    diesel::insert_into(links::table).values(&entry).get_result::<Entry>(connection)
+}
+
+fn get_url_from_database(url_hash: &String, connection: &PgConnection) -> Result<String, diesel::result::Error> {
+    links::table.filter(links::hash.eq(url_hash)).select(links::url).first(connection)
 }
 
-#[derive(Debug, Serialize)]
-struct UserResponse {
-    hash: String,
-    key: String
+fn get_hash_from_string(to_hash: &String) -> String {
+    let mut hasher = crc32fast::Hasher::new();
+    hasher.update(to_hash.as_bytes());
+    base64::encode_config(hasher.finalize().to_ne_bytes(), base64::URL_SAFE_NO_PAD).chars().take(3).collect()
+}
+
+fn add_to_database_safely(mut hash: String, user_url: String, connection: &PgConnection) -> String {
+    match get_url_from_database(&hash, connection) {
+        Ok(other_url) => {
+            if user_url != other_url {
+                hash = add_to_database_safely(get_hash_from_string(&hash), user_url, connection);
+            }
+        }
+        Err(_) => {
+            if add_entry_to_database(Entry { hash: hash.clone() , url: user_url }, connection).is_err() {
+
+            }
+        }
+    };
+    hash
 }
 
 async fn root() -> HttpResponse {
-    HttpResponse::Ok().body("Please make a POST request to / with the URL you want to shorten!")
+    HttpResponse::Ok().body("Please make a POST request to / in the format {'url': '<your_url>'} with the URL you want to shorten!")
 }
 
-async fn shorten(req: HttpRequest, params: Json<UserData>) -> HttpResponse {
-    //println!("{:?}", req.head());
-    println!("{:?}", params.url);
-    HttpResponse::Ok().body("Right back at you!")
+async fn shorten(params: Json<UserData>, state: Data<PoolState>) -> HttpResponse {
+    let user_url = match make_url(&params.url) {
+        Ok(parse_result) => parse_result,
+        Err(_) => {
+            return HttpResponse::BadRequest().body("The URL you entered does not follow the proper URL format.");
+        },
+    };
+    let hash= add_to_database_safely(get_hash_from_string(&user_url), user_url, &state.get().expect("Could not get a connection from pool"));
+
+    HttpResponse::Ok().json(UserResponse{ hash })
 }
 
-async fn redirect() -> HttpResponse {
-    HttpResponse::TemporaryRedirect().header("Location", "http://google.com/").finish()
+async fn redirect(info: Path<String>, state: Data<PoolState>) -> HttpResponse {
+    match get_url_from_database(&info, &state.get().expect("Could not get a connection from pool")) {
+        Ok(url) => HttpResponse::TemporaryRedirect().header("Location", url).finish(),
+        Err(_) => HttpResponse::NotFound().body("The URL you specified could not be found.")
+    }
 }
 
 #[actix_rt::main]
 async fn main() -> std::io::Result<()> {
-    let mut lfd = ListenFd::from_env();
     std::env::set_var("RUST_LOG", "actix_web=info");
     env_logger::init();
-    let mut server = HttpServer::new(|| {
+
+    let pool = establish_connection();
+
+    HttpServer::new(move || {
         App::new()
             .wrap(middleware::Logger::default())
+            .data(pool.clone())
             .service(
             web::resource("/")
                 .route(web::get().to(root))
                 .route(web::post().to(shorten))
             )
             .service(
-                web::resource("/{hash}/{key}")
+                web::resource("/{hash}")
                     .route(web::get().to(redirect))
             )
-    });
-    server = if let Some(l) = lfd.take_tcp_listener(0).unwrap() {
-        server.listen(l)?
-    } else {
-        server.bind("localhost:3000")?
-    };
+    })
+        .bind("localhost:3000")?
+        .run()
+        .await
 
-    server.run().await
 }
diff --git a/src/schema.rs b/src/schema.rs
index e69de29..5d823c0 100644
--- a/src/schema.rs
+++ b/src/schema.rs
@@ -0,0 +1,6 @@
+table! {
+    links (hash) {
+        hash -> Text,
+        url -> Text,
+    }
+}
\ No newline at end of file
diff --git a/src/types.rs b/src/types.rs
index e69de29..d068043 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -0,0 +1,22 @@
+use diesel::PgConnection;
+use diesel::r2d2::{ConnectionManager, Pool};
+use crate::schema::links;
+
+#[derive(Debug, Deserialize)]
+pub struct UserData {
+    pub url: String
+}
+
+#[derive(Debug, Serialize)]
+pub struct UserResponse {
+    pub hash: String,
+}
+
+#[derive(Queryable, Insertable)]
+#[table_name="links"]
+pub struct Entry {
+    pub hash: String,
+    pub url: String
+}
+
+pub type PoolState = Pool<ConnectionManager<PgConnection>>;
\ No newline at end of file