🏡 index : old_projects/url_shortener.git

author yaqubroli <walchuk2018@icloud.com> 2022-12-18 21:57:59.0 -08:00:00
committer yaqubroli <walchuk2018@icloud.com> 2022-12-18 21:57:59.0 -08:00:00
commit
db28093f4edf6196ce536184dea1cfe24f70a698 [patch]
tree
e12b8a0df983f45399d580d7bf2916a26291f649
parent
295a2af79521a1d9199db5b68c0b4e72c8a70112
download
db28093f4edf6196ce536184dea1cfe24f70a698.tar.gz

Refactored database, added templating, added shortener



Diff

 Cargo.lock                   |   8 ++++----
 Cargo.toml                   |   6 ++++--
 config_defaults.toml         |   1 +
 index.html                   |   2 ++
 results.html                 |  12 ++++++++++++
 src/database.rs              | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
 src/endpoints.rs             |  66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
 src/main.rs                  |  67 ++++++++++++++++++++++++++++++++++++++++++++++++-------------------
 src/settings.rs              |  11 +++++++++--
 src/shortener.rs             |  39 +++++++++++++++++++++++++++++++++++++++
 src/templating.rs            |  22 ++++++++++++++++++++++
 src/url.rs                   |   8 ++++++++
 static/style.css             |   5 +++++
 target/.rustc_info.json      |   2 +-
 target/debug/url_shortener   |   0 
 target/debug/url_shortener.d |   2 +-
 16 files changed, 283 insertions(+), 180 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 6f17a54..b0a1473 100644
--- a/Cargo.lock
+++ a/Cargo.lock
@@ -2071,18 +2071,18 @@

[[package]]
name = "thiserror"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
 "proc-macro2",
 "quote",
diff --git a/Cargo.toml b/Cargo.toml
index c18c07f..ec982a0 100644
--- a/Cargo.toml
+++ a/Cargo.toml
@@ -11,8 +11,10 @@
[dependencies]
# diesel = { version = "2.0.2", features = ["mysql"] }
actix-web = "4"
serde = { version = "1.0", features = ["derive"] }
actix-files = "0.6.2"
actix-settings = "0.6.0"
serde = { version = "1.0", features = ["derive"] }
mysql = "23.0.0"
actix-files = "0.6.2"
# 
# base64 = "0.20.0"
# log = "0.4"
diff --git a/config_defaults.toml b/config_defaults.toml
index 9599a8e..5185ce6 100644
--- a/config_defaults.toml
+++ a/config_defaults.toml
@@ -1,7 +1,8 @@
[actix]
# For more info, see: https://docs.rs/actix-web/4/actix_web/struct.HttpServer.html.

hosts = [
    # @ts-ignore
    ["0.0.0.0", 9000]      # This should work for both development and deployment...
    #                      # ... but other entries are possible, as well.
]
diff --git a/index.html b/index.html
index d4db8c6..2563b5a 100644
--- a/index.html
+++ a/index.html
@@ -1,7 +1,9 @@
<!DOCTYPE html>
<html>
<head>
    <title>URL submission form</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="static/style.css" />
</head>
<body>
    <h1>URL shortener</h1>
diff --git a/results.html b/results.html
new file mode 100644
index 0000000..61ed301 100644
--- /dev/null
+++ a/results.html
@@ -1,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
    <title>URL shortened</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="static/style.css" />
</head>
<body>
    <h1>URL shortened successfully</h1>
    <p>Your url is accessible at {{domain}}/{{shortened}}</p>
</body>
</html>
diff --git a/src/database.rs b/src/database.rs
index d95b144..ebeefb7 100644
--- a/src/database.rs
+++ a/src/database.rs
@@ -1,142 +1,98 @@
use actix_settings::BasicSettings;
use crate::settings;

use mysql::prelude::*;
use mysql::*;

//use crate::config::{Config};

/* pub fn init(config: Config) -> Pool {
    let url = format!(
        "mysql://{}:{}@{}:{}/{}",
        config.db.user, config.db.password, config.db.host, config.db.port, config.db.database
    );
    println!("Connecting to database at {}.", url);
    let pool = Pool::new(url.as_str()).expect("Unable to connect to database.");
    if !does_table_exist(&pool) {
        println!("Table does not exist. Creating them.");
        create_table(&pool);
#[derive(Debug)]
pub struct RetrievedUrl {
    pub url: String,
    pub success: bool
}

#[derive(Debug)]
pub struct SubmittedUrl {
    pub shortened: String,
    pub success: bool
}

// Description: This function takes in a settings struct and returns a mysql connection pool
pub async fn init (settings: &BasicSettings<settings::AppSettings>) -> Pool {
    let database_settings = &settings.application.database;
    let url = format!("mysql://{}:{}@{}:{}/{}", database_settings.username, database_settings.password, database_settings.host, database_settings.port, database_settings.database);
    let pool = Pool::new(url.as_str()).unwrap();
    // create the table if it doesn't exist
    let mut connection = pool.get_conn().unwrap();
    if create_table(&mut connection).await {
        println!("Created table `urls`");
    } else {
        println!("Table `urls` already exists");
    }
    pool
} */

pub fn does_table_exist(pool: &Pool) -> bool {
    let mut conn = pool.get_conn().unwrap();
    let result: Vec<String> = conn
        .exec_map(
            r"
        SELECT table_name FROM information_schema.tables WHERE table_schema = :database
    ",
            params! {
                "database" => "url_shortener"
            },
            |table_name| table_name,
        )
        .unwrap();
    result.len() > 0
}

pub fn create_table(pool: &Pool) {
    let mut conn = pool.get_conn().unwrap();
    conn.query_drop(
        r"
        CREATE TABLE IF NOT EXISTS `urls` (
            `id` INT NOT NULL AUTO_INCREMENT,
            `url` VARCHAR(255) NOT NULL,
            `shortened` VARCHAR(255) NOT NULL,
            PRIMARY KEY (`id`)
        ) ENGINE=InnoDB;
    ",
    )
    .unwrap();
    println!("Table created.");
}

pub fn insert_url(pool: &Pool, url: &str, shortened: &str) {
    let mut conn = pool.get_conn().unwrap();
    conn.exec_drop(
        r"
        INSERT INTO `urls` (`url`, `shortened`) VALUES (:url, :shortened)
    ",
        params! {
            "url" => url,
            "shortened" => shortened
        },
    )
    .unwrap();
// Description: This function takes in a connection and a shortened url and returns a RetrievedUrl struct, where `url` is the url column and `success` is true if the shortened url exists in the database
pub async fn retrieve_url (connection: &mut PooledConn, shortened: &str) -> RetrievedUrl {
    let mut result = connection.exec_iter("SELECT url FROM urls WHERE shortened = :shortened", params! {
        "shortened" => shortened
    }).unwrap();
    let row = result.next().unwrap();
    let url = row.unwrap().get::<String, _>("url").unwrap();
    RetrievedUrl {
        url,
        success: true
    }
}

pub fn get_url(pool: &Pool, shortened: &str) -> Option<String> {
    let mut conn = pool.get_conn().unwrap();
    let result: Vec<String> = conn
        .exec_map(
            r"
        SELECT `url` FROM `urls` WHERE `shortened` = :shortened
    ",
            params! {
// Description: This function takes in a connection and a url and a shortened url and returns a SubmittedUrl struct, where `shortened` is the shortened url column and `success` is true if the shortened url does not exist in the database
pub async fn submit_url (connection: &mut PooledConn, url: &str, shortened: &str) -> SubmittedUrl {
    let row = connection.exec_iter("SELECT shortened FROM urls WHERE shortened = :shortened", params! {
        "shortened" => shortened
    }).unwrap().next();
    if row.is_some() {
        SubmittedUrl {
            shortened: shortened.to_string(),
            success: false
        }
    } else {
        let row = connection.exec_iter("SELECT shortened FROM urls WHERE url = :url", params! {
            "url" => url
        }).unwrap().next();
        if row.is_some() {
            let shortened = row.unwrap().unwrap().get::<String, _>("shortened").unwrap();
            SubmittedUrl {
                shortened,
                success: true
            }
        } else {
            connection.exec_drop("INSERT INTO urls (url, shortened) VALUES (:url, :shortened)", params! {
                "url" => url,
                "shortened" => shortened
            },
            |url| url,
        )
        .unwrap();
    result.into_iter().next()
}

pub fn get_shortened(pool: &Pool, url: &str) -> Option<String> {
    let mut conn = pool.get_conn().unwrap();
    let result: Vec<String> = conn
        .exec_map(
            r"
        SELECT `shortened` FROM `urls` WHERE `url` = :url
    ",
            params! {
                "url" => url
            },
            |shortened| shortened,
        )
        .unwrap();
    result.into_iter().next()
}

pub fn url_exists(pool: &Pool, url: &str) -> bool {
    let mut conn = pool.get_conn().unwrap();
    let result: Vec<String> = conn
        .exec_map(
            r"
        SELECT `url` FROM `urls` WHERE `url` = :url
    ",
            params! {
                "url" => url
            },
            |url| url,
        )
        .unwrap();
    result.len() > 0
            }).unwrap();
            SubmittedUrl {
                shortened: shortened.to_string(),
                success: true
            }
        }
    }
}

pub fn shortened_exists(pool: &Pool, shortened: &str) -> bool {
    let mut conn = pool.get_conn().unwrap();
    let result: Vec<String> = conn
        .exec_map(
            r"
        SELECT `shortened` FROM `urls` WHERE `shortened` = :shortened
    ",
            params! {
                "shortened" => shortened
            },
            |shortened| shortened,
        )
        .unwrap();
    result.len() > 0
// Description: This function takes in a connection and returns a u64 which is the number of rows in the table
pub async fn count_urls (connection: &mut PooledConn) -> u64 {
    let mut result = connection.exec_iter("SELECT COUNT(*) FROM urls", ()).unwrap();
    let row = result.next().unwrap();
    let count = row.unwrap().get::<u64, _>("COUNT(*)").unwrap();
    count
}

// Description: This function takes in a connection and returns a bool which is true if the table was created
pub async fn create_table (connection: &mut PooledConn) -> bool {
    let mut result = connection.exec_iter("CREATE TABLE IF NOT EXISTS urls (url VARCHAR(255) NOT NULL, shortened VARCHAR(255) NOT NULL, PRIMARY KEY (shortened))", ()).unwrap();
    let row = result.next();
    if row.is_some() {
        true
    } else {
        false
    }
}

pub fn count_urls(pool: &Pool) -> u64 {
    let mut conn = pool.get_conn().unwrap();
    let result: Vec<u64> = conn
        .exec_map(
            r"
        SELECT COUNT(*) FROM `urls`
    ",
            (),
            |count| count,
        )
        .unwrap();
    result.into_iter().next().unwrap()
}
diff --git a/src/endpoints.rs b/src/endpoints.rs
index 78e1a02..e16a1a0 100644
--- a/src/endpoints.rs
+++ a/src/endpoints.rs
@@ -1,29 +1,61 @@
use actix_files::NamedFile;
use std::path::PathBuf;
use actix_web::{web, HttpResponse, HttpRequest, Responder};
use actix_files::NamedFile;
use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};

use crate::{database::{self, SubmittedUrl}, shortener::{self, base64}, url, templating};

#[derive(Deserialize, Serialize, Debug)]
pub struct Submission {
    url: String
}

pub async fn file(req: HttpRequest) -> Result<NamedFile, actix_web::Error> {
    let path_string = req.match_info().query("filename");
    let path: PathBuf = if path_string != "" {
        PathBuf::from(path_string)
    } else {
        PathBuf::from("index.html")
    };
    Ok(NamedFile::open(path)?)
// write a version of the above function, but without using NamedFile and using standard rust fs libraries instead
pub async fn static_file(path: web::Path<String>) -> impl Responder {
    let path_string = path.into_inner();
    println!("Accessing file {:?}", path_string);
    let file = std::fs::read_to_string(PathBuf::from(format!("static/{}", path_string))).unwrap();
    HttpResponse::Ok().body(file)
}

pub async fn hello(req: HttpRequest) -> impl Responder {
    //println!("HTTP Host Address: {:?}", req.app_data::<AppData>().unwrap().config.http.host);
    HttpResponse::Ok().body("Hello world!")
/* -> Result<NamedFile, actix_web::Error> {
    let path_string = path.into_inner();
    println!("Accessing file {:?}", path_string);
    Ok(NamedFile::open(PathBuf::from(format!("static/{}", path_string)))?)
} */

pub async fn index() -> Result<NamedFile, actix_web::Error> {
    Ok(NamedFile::open("index.html")?)
}
pub async fn submit_url(form: web::Form<Submission>, app_data: web::Data<crate::AppData>) -> impl Responder {
    let url = url::format_url(form.url.clone());
    let mut connection = app_data.database.get_conn().unwrap();
    let count = database::count_urls(&mut connection).await;
    for n in 0..3{
        let shortened = shortener::base64(count);
        let submitted_url = database::submit_url(&mut connection, &url, &shortened).await;
        if submitted_url.success {
            return HttpResponse::Ok().body(
                templating::read_and_apply_templates(
                    PathBuf::from("results.html"),
                    templating::TemplateSchema {
                        url: url,
                        shortened: submitted_url.shortened,
                        domain: app_data.config.application.templating.domain.clone(),
                        count: count.to_string()
                    }
                )
            );
        }
    }
    HttpResponse::InternalServerError().body("An error occured while submitting your URL")
}

pub async fn submit_url(form: web::Form<Submission>) -> impl Responder {
    println!("Received submission: {:?}", form);
    HttpResponse::Ok().body(format!("Received submission: {:?}", form))
// Takes a shortened URL and redirects to the original URL
pub async fn redirect_url(path: web::Path<(String)>, app_data: web::Data<crate::AppData>) -> impl Responder {
    println!("Redirect request recieved to {:?}", path);
    let (shortened) = path.into_inner();
    let mut connection = app_data.database.get_conn().unwrap();
    let retrieved_url = database::retrieve_url(&mut connection, &shortened).await;
    println!("Redirecting to {:?}", retrieved_url.url);
    HttpResponse::Found().header("Location", retrieved_url.url).finish()
}
diff --git a/src/main.rs b/src/main.rs
index a18b191..222801b 100644
--- a/src/main.rs
+++ a/src/main.rs
@@ -1,41 +1,58 @@
use actix_settings::{BasicSettings, ApplySettings};
use actix_web::{web, App, HttpServer, middleware::Condition, middleware::Logger, middleware::Compress};
// use mysql::Pool;
use mysql::Pool;

pub mod database;
pub mod endpoints;
pub mod settings;
pub mod shortener;
pub mod url;
pub mod templating;

/*

TODO:
 * - make config accessible universally, or somehow transfer config data to how functions work
 * - make database accessible universally
 * - make shortened url generation
 * - make shortened url redirect
 * - clean up code
 * - add HTML templates
 * - implement other functions (pastebin maybe?)
*/

#[derive(Clone)]
pub struct AppData {
    config: BasicSettings<settings::AppSettings>,
    database: Pool
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Importing settings...");
    let settings = settings::init();
    HttpServer::new(move || {
        let settings = settings.clone();
        App::new()
            // Grab compression settings from config.toml
            .wrap(Condition::new(
                settings.actix.enable_compression,
                Compress::default(),
            ))
            // Grab logger settings from config.toml
            .wrap(Condition::new(
                settings.actix.enable_log,
                Logger::default(),
            ))
            .app_data(web::Data::new(settings.clone()))
            .route("/", web::get().to(endpoints::hello))
            .route("/{filename:.*}", web::get().to(endpoints::file))
            .route("/", web::post().to(endpoints::submit_url))
    println!("Starting server...");
    let server = HttpServer::new({
        let app_data = AppData {
            config: settings.clone(),
            database: database::init(&settings).await
        };
        move || {
            App::new()
                // Grab compression settings from config.toml
                .wrap(Condition::new(
                    settings.actix.enable_compression,
                    Compress::default(),
                ))
                // Grab logger settings from config.toml
                .wrap(Condition::new(
                    settings.actix.enable_log,
                    Logger::default(),
                ))
                .app_data(web::Data::new(app_data.clone()))
                .route("/", web::get().to(endpoints::index))
                .route("/static/{filename}", web::get().to(endpoints::static_file))
                .route("/{shortened}", web::get().to(endpoints::redirect_url))
                .route("/", web::post().to(endpoints::submit_url))
        }
    })
    .bind(("127.0.0.1", 4000))?
    .run()
    .await
    .apply_settings(&settings)
    .run();
    println!("Server started!");
    server.await
}
diff --git a/src/settings.rs b/src/settings.rs
index bc54768..b195b88 100644
--- a/src/settings.rs
+++ a/src/settings.rs
@@ -1,9 +1,10 @@
use actix_settings::{ApplySettings as _, Settings, BasicSettings};
use actix_settings::{BasicSettings};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AppSettings {
    pub database: DatabaseSettings
    pub database: DatabaseSettings,
    pub templating: TemplatingSettings
}

#[derive(Clone, Debug, Deserialize, Serialize)]
@@ -13,6 +14,12 @@
    pub username: String,
    pub password: String,
    pub database: String
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TemplatingSettings {
    pub enabled: bool,
    pub domain: String
}

pub fn init () -> BasicSettings<AppSettings> {
diff --git a/src/shortener.rs b/src/shortener.rs
new file mode 100644
index 0000000..2e90ea5 100644
--- /dev/null
+++ a/src/shortener.rs
@@ -1,0 +1,39 @@
// Dangling library of functions to create as short of a shortened string as humanly possible.

const BASE64_CHARS: [char; 64] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
    'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
    'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
    'v', 'w', 'x', 'y', 'z', '-', '_',
];

pub fn base64(num: u64) -> String {
    let mut result = String::new();
    let mut num = num;
    while num > 0 {
        let remainder = num % 64;
        num = num / 64;
        result.push(BASE64_CHARS[remainder as usize]);
    }
    result
}

// creates a string of random base64 characters, initial length is 4, but if the total amount of strings exceeds 64^4, it will increase the length of the string by 1
/*

pub fn random_base64(num: u64) -> String {
    let mut result = String::new();
    let mut num = num;
    let mut length = 4;
    while num > 0 {
        let remainder = num % 64;
        num = num / 64;
        result.push(BASE64_CHARS[remainder as usize]);
        if num > 0 {
            length += 1;
        }
    }
    while result.len() < length {
        result.push(BASE64_CHARS[rand::random::<usize>() % 64]);
    }
    result
} */
diff --git a/src/templating.rs b/src/templating.rs
new file mode 100644
index 0000000..da40319 100644
--- /dev/null
+++ a/src/templating.rs
@@ -1,0 +1,22 @@
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;

pub struct TemplateSchema {
    pub url: String,
    pub shortened: String,
    pub domain: String,
    pub count: String
}

pub fn read_and_apply_templates(path: PathBuf, schema: TemplateSchema) -> String {
    let mut file = File::open(path).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    // Hardcoded templates, will change this if/when the amount of templates increases
    contents
        .replace("{{url}}", &schema.url)
        .replace("{{shortened}}", &schema.shortened)
        .replace("{{domain}}", &schema.domain)
        .replace("{{count}}", &schema.count)
}
diff --git a/src/url.rs b/src/url.rs
new file mode 100644
index 0000000..486f045 100644
--- /dev/null
+++ a/src/url.rs
@@ -1,0 +1,8 @@
// checks if string could possibly be a valid URL, and if it isn't, formats it into a valid URL
pub fn format_url(url: String) -> String {
    if url.contains("://") {
        url
    } else {
        format!("http://{}", url)
    }
}
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..359f792 100644
--- /dev/null
+++ a/static/style.css
@@ -1,0 +1,5 @@
body {
    font-family: sans-serif;
    background-color: #f0f0f0;
    text-align: center;
}
diff --git a/target/.rustc_info.json b/target/.rustc_info.json
index 03f47ec..5d51843 100644
--- a/target/.rustc_info.json
+++ a/target/.rustc_info.json
@@ -1,1 +1,1 @@
{"rustc_fingerprint":13098977256995595289,"outputs":{"15697416045686424142":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""},"10376369925670944939":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/opt/homebrew/Cellar/rust/1.65.0\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.65.0\nbinary: rustc\ncommit-hash: unknown\ncommit-date: unknown\nhost: aarch64-apple-darwin\nrelease: 1.65.0\nLLVM version: 15.0.0\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":13098977256995595289,"outputs":{"10376369925670944939":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/opt/homebrew/Cellar/rust/1.65.0\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"15697416045686424142":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.65.0\nbinary: rustc\ncommit-hash: unknown\ncommit-date: unknown\nhost: aarch64-apple-darwin\nrelease: 1.65.0\nLLVM version: 15.0.0\n","stderr":""}},"successes":{}}
diff --git a/target/debug/url_shortener b/target/debug/url_shortener
index f650a3f7de1ee2e49f753d57a2a7d053c2580946..7858f6b0913859e465e41578426dfb769e3c9e8f 100755
Binary files a/target/debug/url_shortener and a/target/debug/url_shortener differdiff --git a/target/debug/url_shortener.d b/target/debug/url_shortener.d
index 5cefc33..e49228f 100644
--- a/target/debug/url_shortener.d
+++ a/target/debug/url_shortener.d
@@ -1,1 +1,1 @@
/Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/target/debug/url_shortener: /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/build.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/database.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/endpoints.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/main.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/settings.rs
/Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/target/debug/url_shortener: /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/build.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/database.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/endpoints.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/main.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/settings.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/shortener.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/templating.rs /Users/yaqub/OneDrive\ -\ University\ of\ St\ Andrews/dev/rust/url_shortener/src/url.rs