Initial commit
commit
657143849b
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "paste"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Ambrose Chua <ambrose@chua.family>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.1"
|
||||||
|
rocket = "0.4"
|
||||||
|
cuid = "0.1"
|
||||||
|
rusoto_s3 = "0.40"
|
||||||
|
rusoto_core = "0.40"
|
||||||
|
rusoto_credential = "0.40"
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
|
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
use rocket::State;
|
||||||
|
use rocket::response::content;
|
||||||
|
|
||||||
|
use core::time::Duration;
|
||||||
|
use futures::future::Future;
|
||||||
|
|
||||||
|
use cuid::slug;
|
||||||
|
|
||||||
|
use rusoto_core::region::Region;
|
||||||
|
use rusoto_credential::AwsCredentials;
|
||||||
|
use rusoto_credential::EnvironmentProvider;
|
||||||
|
use rusoto_credential::ProvideAwsCredentials;
|
||||||
|
use rusoto_s3::PutObjectRequest;
|
||||||
|
use rusoto_s3::util::PreSignedRequestOption;
|
||||||
|
use rusoto_s3::util::PreSignedRequest;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> content::Html<&'static str> {
|
||||||
|
content::Html("
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Cousine&display=swap\">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont,\"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
padding: 0.25em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input[type=submit], textarea {
|
||||||
|
border: none;
|
||||||
|
font: inherit;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
input[type=submit] {
|
||||||
|
background: #8f8;
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-family: \"Cousine\", monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<form method=\"get\">
|
||||||
|
<div class=\"toolbar\">
|
||||||
|
<input type=\"submit\" value=\"Create new paste\">
|
||||||
|
</div>
|
||||||
|
<textarea placeholder=\"Paste content here\" rows=\"10\" required></textarea>
|
||||||
|
</form>
|
||||||
|
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/autosize.js/4.0.2/autosize.min.js\"></script>
|
||||||
|
<script>
|
||||||
|
autosize(document.querySelectorAll('textarea'));
|
||||||
|
const form = document.querySelector('form');
|
||||||
|
const textarea = form.querySelector('textarea');
|
||||||
|
const submit = form.querySelector('input[type=submit]');
|
||||||
|
textarea.focus();
|
||||||
|
form.addEventListener('submit', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
submit.value = 'Creating paste...';
|
||||||
|
submit.disabled = true;
|
||||||
|
textarea.disabled = true;
|
||||||
|
|
||||||
|
fetch('/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(url => Promise.all([
|
||||||
|
url,
|
||||||
|
fetch(url, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: textarea.value,
|
||||||
|
}),
|
||||||
|
]))
|
||||||
|
.then(res => {
|
||||||
|
const url = new URL(res[0]);
|
||||||
|
textarea.value = '';
|
||||||
|
window.location = url.origin + url.pathname;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/upload")]
|
||||||
|
fn upload(api: State<Api>) -> String {
|
||||||
|
api.presigned_put_object()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rocket::ignite().manage(Api::new()).mount("/", routes![index, upload]).launch();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Api {
|
||||||
|
region: Region,
|
||||||
|
credentials: AwsCredentials,
|
||||||
|
bucket: String,
|
||||||
|
expires_in: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Api {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let credentials = EnvironmentProvider::default().credentials().wait().unwrap();
|
||||||
|
let region = Region::Custom {
|
||||||
|
name: Self::read_region(),
|
||||||
|
endpoint: Self::read_region_endpoint(),
|
||||||
|
};
|
||||||
|
let bucket = "paste".to_string();
|
||||||
|
let expires_in = Duration::new(1 * 60 * 60, 0);
|
||||||
|
Self {
|
||||||
|
region,
|
||||||
|
credentials,
|
||||||
|
bucket,
|
||||||
|
expires_in,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn presigned_put_object(&self) -> String {
|
||||||
|
let mut put_object_request = PutObjectRequest::default();
|
||||||
|
put_object_request.bucket = self.bucket.clone();
|
||||||
|
put_object_request.key = self.generate_id();
|
||||||
|
let option = PreSignedRequestOption {
|
||||||
|
expires_in: self.expires_in,
|
||||||
|
};
|
||||||
|
put_object_request.get_presigned_url(&self.region, &self.credentials, &option)
|
||||||
|
}
|
||||||
|
fn generate_id(&self) -> String {
|
||||||
|
slug().unwrap()
|
||||||
|
}
|
||||||
|
fn read_region() -> String {
|
||||||
|
std::env::var("AWS_DEFAULT_REGION").or_else(|_| std::env::var("AWS_REGION")).map_err(|_| RegionError::new("No AWS_DEFAULT_REGION in environment")).unwrap()
|
||||||
|
}
|
||||||
|
fn read_region_endpoint() -> String {
|
||||||
|
std::env::var("AWS_BASE_URL").map_err(|_| RegionError::new("No AWS_BASE_URL in environment")).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RegionError {
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegionError {
|
||||||
|
pub fn new<S>(message: S) -> Self where S: ToString {
|
||||||
|
RegionError {
|
||||||
|
message: message.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue