Initial commit

This commit is contained in:
Maurice 2022-11-02 15:46:41 +01:00
commit 1d32fb0365
9 changed files with 1343 additions and 0 deletions

2
.gitignore vendored Normal file

@ -0,0 +1,2 @@
/target
/data

1161
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

13
Cargo.toml Normal file

@ -0,0 +1,13 @@
[package]
name = "pastabble"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
kv = { version = "0.24.0", features = ["json-value"] }
serde = { version = "1.0.145", features = ["derive"] }
chrono = { version = "0.4.22", features = ["serde"] }
rand = "0.8.5"
rouille = "3.6.1"

7
Dockerfile Normal file

@ -0,0 +1,7 @@
FROM alpine:latest
WORKDIR /app
COPY ./target/x86_64-unknown-linux-musl/release/ /app
COPY ./about.html /app/about.html
ENTRYPOINT [ "./pastabble" ]

21
README.md Normal file

@ -0,0 +1,21 @@
# PASTABBLE
Welcome at PASTABBLE!
## About
Pastable is a lightweight and fast pastebin alternative and URL-shortener made by Plabble and written in Rust.
## Environment variables
| Variable | Default |
| -------- | ------- |
| PORT | 8080 |
| DATA_DIR | ./data |
## Setup
Place the about.html file from this folder in the same folder as the binary
## Requirements
Rust and musl toolchain should be installed
```
rustup target add x86_64-unknown-linux-musl
```

28
about.html Normal file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About</title>
</head>
<body>
<pre>
Welcome at PASTABBLE!
ABOUT
Pastable is a lightweight and fast pastebin alternative, and URL-shortener made by Plabble and written in Rust.
USAGE
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
FROM TERMINAL
Pipe your input into: curl -F 'content=&lt;-' 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
</pre>
</body>
</html>

5
build.sh Executable file

@ -0,0 +1,5 @@
#!/bin/sh
RUNNER="podman"
cargo build --target x86_64-unknown-linux-musl --release
$RUNNER build -t pastabble:latest .

93
src/main.rs Normal file

@ -0,0 +1,93 @@
use std::{env, fs};
use chrono::Utc;
use kv::{Config, Store, Json};
use paste::Paste;
use rand::{Rng, distributions::Alphanumeric};
use rouille::{Response, router, try_or_400, post_input};
mod paste;
// Generate random key function
fn new_key() -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(5)
.map(char::from)
.collect()
}
fn main() {
// Get settings
let port: u16 = env::var("PORT").unwrap_or(String::from("8080")).parse().expect("Failed to parse PORT variable");
let data_dir: String = env::var("DATA_DIR").unwrap_or(String::from("./data"));
let config = Config::new(data_dir);
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)
let pastes = store.bucket::<String, Json<Paste>>(Some("pastes"))
.expect("Failed to open default bucket");
// Start server
println!("Started PASTABBLE server on port {}", port);
rouille::start_server(format!("0.0.0.0:{}", port), move |req| {
router!(req,
(GET) (/) => {
Response::html(&about)
},
(GET) (/{id: String}) => {
// Get note from database, if exists
match pastes.get(&id).expect("Failed to access DB") {
Some(paste) => {
Response::text(paste.0.content)
},
None => {
Response::text(format!("Note with ID '{}' not found", &id))
.with_status_code(404)
}
}
},
(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)
},
_ => Response::empty_404()
)
});
}

13
src/paste.rs Normal file

@ -0,0 +1,13 @@
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use chrono::serde::ts_seconds_option;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Paste {
pub content: String,
pub language: Option<String>,
#[serde(with = "ts_seconds_option")]
pub expires: Option<DateTime<Utc>>,
pub created: DateTime<Utc>
}