Added URL shortener

This commit is contained in:
Maurice 2022-11-02 18:49:43 +01:00
parent 1d32fb0365
commit e874e4e473
3 changed files with 104 additions and 46 deletions

@ -5,6 +5,8 @@ Welcome at PASTABBLE!
## About ## About
Pastable is a lightweight and fast pastebin alternative and URL-shortener made by Plabble and written in Rust. 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 ## Environment variables
| Variable | Default | | Variable | Default |
| -------- | ------- | | -------- | ------- |

@ -17,12 +17,19 @@
Get /: Show this page Get /: Show this page
Get /{id}: Get paste by ID and return in plain text 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 /: 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 FROM TERMINAL
Pipe your input into: curl -F 'content=<-' paste.plabble.org -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" 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 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> </pre>
</body> </body>
</html> </html>

@ -1,15 +1,15 @@
use std::{env, fs}; use std::{env, fs};
use chrono::Utc; use chrono::Utc;
use kv::{Config, Store, Json}; use kv::{Config, Store, Json, Bucket, Value};
use paste::Paste; use paste::Paste;
use rand::{Rng, distributions::Alphanumeric}; 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; mod paste;
// Generate random key function // Generate random key function
fn new_key() -> String { fn random_string() -> String {
rand::thread_rng() rand::thread_rng()
.sample_iter(&Alphanumeric) .sample_iter(&Alphanumeric)
.take(5) .take(5)
@ -17,6 +17,18 @@ fn new_key() -> String {
.collect() .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() { fn main() {
// Get settings // Get settings
let port: u16 = env::var("PORT").unwrap_or(String::from("8080")).parse().expect("Failed to parse PORT variable"); 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 store = Store::new(config).expect("Failed to initialize database store");
let about = fs::read_to_string("./about.html").expect("about.html not found!!"); 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")) 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 // Start server
println!("Started PASTABBLE server on port {}", port); println!("Started PASTABBLE server on port {}", port);
@ -37,9 +53,22 @@ fn main() {
(GET) (/) => { (GET) (/) => {
Response::html(&about) 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) (/{id: String}) => {
// Get note from database, if exists // 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) => { Some(paste) => {
Response::text(paste.0.content) Response::text(paste.0.content)
}, },
@ -49,45 +78,65 @@ fn main() {
} }
} }
}, },
(POST) (/{id: String}) => { (POST) (/{id: String}) => {
// Use provided key or generate new one let id = if id.is_empty() { None } else { Some(id) };
let key = if !id.is_empty() && !pastes.contains(&id).unwrap() { register_paste(&pastes, req, id)
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)
}, },
_ => Response::empty_404() _ => 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)
} }