162 lines
4.5 KiB
Rust
162 lines
4.5 KiB
Rust
#![feature(proc_macro_hygiene, decl_macro)]
|
|
|
|
#[macro_use]
|
|
extern crate rocket;
|
|
use rocket::response::content;
|
|
use rocket::State;
|
|
|
|
use core::time::Duration;
|
|
use futures::future::Future;
|
|
|
|
use cuid::slug;
|
|
|
|
use rusoto_core::region::Region;
|
|
use rusoto_credential::{AwsCredentials, EnvironmentProvider, ProvideAwsCredentials};
|
|
use rusoto_s3::util::{PreSignedRequest, PreSignedRequestOption};
|
|
use rusoto_s3::PutObjectRequest;
|
|
|
|
#[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(),
|
|
}
|
|
}
|
|
}
|