From e874e4e47390c8da2768ed72f4556264b9acd882 Mon Sep 17 00:00:00 2001 From: Maurice <mauricegolverdingen@gmail.com> Date: Wed, 2 Nov 2022 18:49:43 +0100 Subject: [PATCH] Added URL shortener --- README.md | 2 + about.html | 13 +++-- src/main.rs | 135 +++++++++++++++++++++++++++++++++++----------------- 3 files changed, 104 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 8b2feb9..387fcfc 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Welcome at PASTABBLE! ## About Pastable is a lightweight and fast pastebin alternative and URL-shortener made by Plabble and written in Rust. +For usage, see `about.html` + ## Environment variables | Variable | Default | | -------- | ------- | diff --git a/about.html b/about.html index 02a6dbd..82bda4b 100644 --- a/about.html +++ b/about.html @@ -17,12 +17,19 @@ Get /: Show this page Get /{id}: Get paste by ID and return in plain text Post /: Create new paste (plain text raw body or form parameter 'content') and return random generated key - Post /{id}: Create new paste (plain text raw body) and return requested key if available, else random generated key + Post /{id}: Create new paste (plain text raw body) and return requested key if available, else random generated key + + Get /to/{id}: Navigate to shortcut link (browser redirect) + Post /to: Create new link (plain text raw body or form parameter 'link') and return random generated key + Post /to/{id}: Create new link (plain text raw body) and return requested key if available, else random generated key FROM TERMINAL - Pipe your input into: curl -F 'content=<-' paste.plabble.org -w "\n" - Or post it as raw data like: curl paste.plabble.org -H "Content-Type: text/plain" -d @- -w "\n" + Pipe your input into: curl -F 'content=<-' paste.plabble.org -w '\n' + Or post it as raw data like: curl paste.plabble.org -H 'Content-Type: text/plain' -d @- -w '\n' You can create an alias for this! To get the result: curl paste.plabble.org/YOURID + + For links: + Pipe your input into: curl -F 'link=<-' paste.plabble.org/to -w '\n' </pre> </body> </html> diff --git a/src/main.rs b/src/main.rs index 79142a3..7e148b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,15 @@ use std::{env, fs}; use chrono::Utc; -use kv::{Config, Store, Json}; +use kv::{Config, Store, Json, Bucket, Value}; use paste::Paste; use rand::{Rng, distributions::Alphanumeric}; -use rouille::{Response, router, try_or_400, post_input}; +use rouille::{Response, router, try_or_400, post_input, Request}; mod paste; // Generate random key function -fn new_key() -> String { +fn random_string() -> String { rand::thread_rng() .sample_iter(&Alphanumeric) .take(5) @@ -17,6 +17,18 @@ fn new_key() -> String { .collect() } +// Generate key using provided key or generate new one +fn generate_key<T : Value>(key: Option<String>, store: &Bucket<String, T>) -> String { + let mut new_key = key.unwrap_or(random_string()); + + loop { + if !store.contains(&new_key).unwrap() { + break new_key; + } + new_key = random_string(); + } +} + fn main() { // Get settings let port: u16 = env::var("PORT").unwrap_or(String::from("8080")).parse().expect("Failed to parse PORT variable"); @@ -26,9 +38,13 @@ fn main() { let store = Store::new(config).expect("Failed to initialize database store"); let about = fs::read_to_string("./about.html").expect("about.html not found!!"); - // Get pastes bucket (default) + // Get pastes bucket let pastes = store.bucket::<String, Json<Paste>>(Some("pastes")) - .expect("Failed to open default bucket"); + .expect("Failed to open pastes bucket"); + + // Get links bucket + let links = store.bucket::<String, String>(Some("links")) + .expect("Failed to open links bucket"); // Start server println!("Started PASTABBLE server on port {}", port); @@ -37,9 +53,22 @@ fn main() { (GET) (/) => { Response::html(&about) }, + (GET) (/to/{id: String}) => { + match links.get(&id).expect("Failed to access links DB") { + Some(lnk) => { + Response::redirect_301(lnk) + }, + None => { + Response::text(format!("Redirect with ID '{}' not found", &id)) + .with_status_code(404) + } + } + }, + (POST) (/to/{id: String}) => { register_link(&links, req, Some(id)) }, + (POST) (/to) => { register_link(&links, req, None) }, (GET) (/{id: String}) => { // Get note from database, if exists - match pastes.get(&id).expect("Failed to access DB") { + match pastes.get(&id).expect("Failed to access pastes DB") { Some(paste) => { Response::text(paste.0.content) }, @@ -49,45 +78,65 @@ fn main() { } } }, - (POST) (/{id: String}) => { - // Use provided key or generate new one - let key = if !id.is_empty() && !pastes.contains(&id).unwrap() { - id - } else { - loop { - let new_key = new_key(); - if !pastes.contains(&new_key).expect("Could not access store") { - break new_key; - } - } - }; - - // Try read body from form data, and if not present from request body - let body = match post_input!(&req, { - content: String - }) { - Ok(f) => { - f.content - }, - Err(_) => { - try_or_400!(rouille::input::plain_text_body(&req)) - } - }; - - // Create and save new note - let paste = Json(Paste { - content: body, - expires: None, - language: None, - created: Utc::now() - }); - - pastes.set(&key, &paste).expect("Failed to save note"); - - // Return key - Response::text(key) + (POST) (/{id: String}) => { + let id = if id.is_empty() { None } else { Some(id) }; + register_paste(&pastes, req, id) }, _ => Response::empty_404() ) }); +} + +// Register paste handler +fn register_paste(pastes: &Bucket<String, Json<Paste>>, req: &Request, id: Option<String>) -> Response { + + // Try read body from form data, and if not present from request body + let body = match post_input!(req, { + content: String + }) { + Ok(f) => { + f.content + }, + Err(_) => { + try_or_400!(rouille::input::plain_text_body(req)) + } + }; + + // Create and save new note + let paste = Json(Paste { + content: body, + expires: None, + language: None, + created: Utc::now() + }); + + let key = generate_key(id, &pastes); + pastes.set(&key, &paste).expect("Failed to save note"); + pastes.flush().expect("Failed to svae paste to database"); + + // Return key + Response::text(key) +} + +// Register link route handler +fn register_link(links: &Bucket<String,String>, req: &Request, id: Option<String>) -> Response { + + // Try read body from form data, and if not present from request body + let link = match post_input!(req, { + link: String + }) { + Ok(f) => { + f.link + }, + Err(_) => { + try_or_400!(rouille::input::plain_text_body(req)) + } + }.trim().to_string(); + + let key = generate_key(id, &links); + links.set(&key, &link).expect("Failed to save link"); + links.flush().expect("Failed to save link to database"); + + // Return key + Response::text(key) } \ No newline at end of file