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