forked from neri/datatrash
Add curl instructions, extract copy into own file
This commit is contained in:
parent
83ea1a15c7
commit
12544034af
43
src/main.rs
43
src/main.rs
|
@ -6,7 +6,7 @@ use actix_files::{Files, NamedFile};
|
||||||
use actix_multipart::Multipart;
|
use actix_multipart::Multipart;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
error,
|
error,
|
||||||
http::header::{ContentDisposition, DispositionParam, DispositionType},
|
http::header::{ContentDisposition, DispositionParam, DispositionType, ACCEPT},
|
||||||
middleware,
|
middleware,
|
||||||
web::{self, Bytes},
|
web::{self, Bytes},
|
||||||
App, Error, FromRequest, HttpRequest, HttpResponse, HttpServer,
|
App, Error, FromRequest, HttpRequest, HttpResponse, HttpServer,
|
||||||
|
@ -24,16 +24,23 @@ use sqlx::{
|
||||||
};
|
};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
const INDEX_HTML: &str = include_str!("../template/index.html");
|
||||||
const UPLOAD_HTML: &str = include_str!("../template/upload.html");
|
const UPLOAD_HTML: &str = include_str!("../template/upload.html");
|
||||||
const VIEW_HTML: &str = include_str!("../template/view.html");
|
const VIEW_HTML: &str = include_str!("../template/view.html");
|
||||||
|
|
||||||
async fn index() -> Result<NamedFile, Error> {
|
async fn index(req: web::HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
Ok(NamedFile::open("./static/index.html")
|
let upload_url = format!("{}/upload", get_host_url(&req));
|
||||||
.map_err(|_| error::ErrorNotFound(""))?
|
let index_html = INDEX_HTML.replace("{upload_url}", upload_url.as_str());
|
||||||
.disable_content_disposition())
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(index_html))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// multipart data
|
||||||
|
// required: either 'file' or 'text'
|
||||||
|
// optional: 'keep_for' default to 30 minutes
|
||||||
async fn upload(
|
async fn upload(
|
||||||
|
req: web::HttpRequest,
|
||||||
payload: Multipart,
|
payload: Multipart,
|
||||||
db: web::Data<PgPool>,
|
db: web::Data<PgPool>,
|
||||||
expiry_watch_sender: web::Data<Sender<()>>,
|
expiry_watch_sender: web::Data<Sender<()>>,
|
||||||
|
@ -88,24 +95,34 @@ async fn upload(
|
||||||
expiry_watch_sender.send(()).await;
|
expiry_watch_sender.send(()).await;
|
||||||
|
|
||||||
let redirect = if kind == FileKind::BINARY && original_name.is_some() {
|
let redirect = if kind == FileKind::BINARY && original_name.is_some() {
|
||||||
format!("/upload/{}/{}", file_id, original_name.unwrap())
|
format!("/upload/{}/{}", file_id, original_name.as_ref().unwrap())
|
||||||
} else {
|
} else {
|
||||||
format!("/upload/{}", file_id)
|
format!("/upload/{}", file_id)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let url = get_file_url(&req, &file_id, original_name.as_deref());
|
||||||
Ok(HttpResponse::SeeOther()
|
Ok(HttpResponse::SeeOther()
|
||||||
.header("location", redirect)
|
.header("location", redirect)
|
||||||
.finish())
|
.body(format!("{}\n", url)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_host_url(req: &web::HttpRequest) -> String {
|
||||||
|
let conn = req.connection_info();
|
||||||
|
format!("{}://{}", conn.scheme(), conn.host())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_file_url(req: &web::HttpRequest, id: &str, name: Option<&str>) -> String {
|
||||||
|
if let Some(name) = name {
|
||||||
|
format!("{}/file/{}/{}", get_host_url(req), id, name)
|
||||||
|
} else {
|
||||||
|
format!("{}/file/{}", get_host_url(req), id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn uploaded(req: web::HttpRequest) -> Result<HttpResponse, Error> {
|
async fn uploaded(req: web::HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
let id = req.match_info().query("id");
|
let id = req.match_info().query("id");
|
||||||
let name = req.match_info().get("name");
|
let name = req.match_info().get("name");
|
||||||
let conn = req.connection_info();
|
let url = get_file_url(&req, id, name);
|
||||||
let url = if let Some(name) = name {
|
|
||||||
format!("{}://{}/file/{}/{}", conn.scheme(), conn.host(), id, name)
|
|
||||||
} else {
|
|
||||||
format!("{}://{}/file/{}", conn.scheme(), conn.host(), id)
|
|
||||||
};
|
|
||||||
let upload_html = UPLOAD_HTML.replace("{url}", url.as_str());
|
let upload_html = UPLOAD_HTML.replace("{url}", url.as_str());
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
|
|
|
@ -11,17 +11,17 @@ pub(crate) async fn parse_multipart(
|
||||||
filename: &Path,
|
filename: &Path,
|
||||||
) -> Result<(Option<String>, DateTime<Local>, FileKind), error::Error> {
|
) -> Result<(Option<String>, DateTime<Local>, FileKind), error::Error> {
|
||||||
let mut original_name: Option<String> = None;
|
let mut original_name: Option<String> = None;
|
||||||
let mut timeout: Option<String> = None;
|
let mut keep_for: Option<String> = None;
|
||||||
let mut kind: Option<FileKind> = None;
|
let mut kind: Option<FileKind> = None;
|
||||||
|
|
||||||
while let Ok(Some(field)) = payload.try_next().await {
|
while let Ok(Some(field)) = payload.try_next().await {
|
||||||
let name = get_field_name(&field)?;
|
let name = get_field_name(&field)?;
|
||||||
let name = name.as_str();
|
let name = name.as_str();
|
||||||
match name {
|
match name {
|
||||||
"validity_secs" => {
|
"keep_for" => {
|
||||||
timeout = Some(parse_string(name, field).await?);
|
keep_for = Some(parse_string(name, field).await?);
|
||||||
}
|
}
|
||||||
"content" => {
|
"file" => {
|
||||||
let file_original_name = get_original_filename(&field);
|
let file_original_name = get_original_filename(&field);
|
||||||
if file_original_name == None || file_original_name.as_deref() == Some("") {
|
if file_original_name == None || file_original_name.as_deref() == Some("") {
|
||||||
continue;
|
continue;
|
||||||
|
@ -35,7 +35,7 @@ pub(crate) async fn parse_multipart(
|
||||||
.await
|
.await
|
||||||
.map_err(|_| error::ErrorInternalServerError("could not write file"))?;
|
.map_err(|_| error::ErrorInternalServerError("could not write file"))?;
|
||||||
}
|
}
|
||||||
"text_content" => {
|
"text" => {
|
||||||
if original_name.is_some() {
|
if original_name.is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,11 @@ pub(crate) async fn parse_multipart(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let validity_secs = timeout
|
let validity_secs = keep_for
|
||||||
.ok_or_else(|| error::ErrorBadRequest("field validity_secs not set"))?
|
.map(|timeout| timeout.parse())
|
||||||
.parse()
|
.transpose()
|
||||||
.map_err(|e| {
|
.map_err(|e| error::ErrorBadRequest(format!("field validity_secs is not a number: {}", e)))?
|
||||||
error::ErrorBadRequest(format!("field validity_secs is not a number: {}", e))
|
.unwrap_or(1800); // default to 30 minutes
|
||||||
})?;
|
|
||||||
let max_validity_secs = Duration::days(31).num_seconds();
|
let max_validity_secs = Duration::days(31).num_seconds();
|
||||||
if validity_secs > max_validity_secs {
|
if validity_secs > max_validity_secs {
|
||||||
return Err(error::ErrorBadRequest(format!(
|
return Err(error::ErrorBadRequest(format!(
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
const button = document.getElementById("copy");
|
||||||
|
button.onclick = () => {
|
||||||
|
if (!navigator.clipboard) {
|
||||||
|
button.innerText = "nicht unterstützt";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const content = document.getElementsByClassName("copy-content")[0];
|
||||||
|
navigator.clipboard.writeText(content.textContent).then(
|
||||||
|
(_) => {
|
||||||
|
button.innerText = "kopiert!";
|
||||||
|
},
|
||||||
|
(_) => {
|
||||||
|
button.innerText = "nicht unterstützt";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
|
@ -71,3 +71,7 @@ a.button:visited {
|
||||||
.button.main:hover {
|
.button.main:hover {
|
||||||
background-color: forestgreen;
|
background-color: forestgreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usage {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
|
@ -13,19 +13,14 @@
|
||||||
<form action="/upload" method="POST" enctype="multipart/form-data">
|
<form action="/upload" method="POST" enctype="multipart/form-data">
|
||||||
<label for="file-upload">datei</label>
|
<label for="file-upload">datei</label>
|
||||||
<br />
|
<br />
|
||||||
<input id="file-upload" type="file" name="content" />
|
<input id="file-upload" type="file" name="file" />
|
||||||
<br />
|
<br />
|
||||||
<label for="text-upload">oder asciitrash</label>
|
<label for="text-upload">oder asciitrash</label>
|
||||||
<br />
|
<br />
|
||||||
<textarea
|
<textarea id="text-upload" name="text" rows="20" cols="120"></textarea>
|
||||||
id="text-upload"
|
|
||||||
name="text_content"
|
|
||||||
rows="20"
|
|
||||||
cols="120"
|
|
||||||
></textarea>
|
|
||||||
<br />
|
<br />
|
||||||
<label for="validity_secs">gültig für</label>
|
<label for="keep_for">gültig für</label>
|
||||||
<select id="validity_secs" name="validity_secs">
|
<select id="keep_for" name="keep_for">
|
||||||
<option value="1800">30 minuten</option>
|
<option value="1800">30 minuten</option>
|
||||||
<option value="3600">60 minuten</option>
|
<option value="3600">60 minuten</option>
|
||||||
<option value="43200">12 stunden</option>
|
<option value="43200">12 stunden</option>
|
||||||
|
@ -36,6 +31,16 @@
|
||||||
<br />
|
<br />
|
||||||
<input class="main button" type="submit" value="hochladen" />
|
<input class="main button" type="submit" value="hochladen" />
|
||||||
</form>
|
</form>
|
||||||
|
<section class="usage">
|
||||||
|
<pre>
|
||||||
|
file upload
|
||||||
|
curl -F 'file=@yourfile.rs' {upload_url}
|
||||||
|
text upload
|
||||||
|
curl -F 'text=your text' {upload_url}
|
||||||
|
including time
|
||||||
|
curl -F 'text=your text' -F 'keep_for=1800' {upload_url}
|
||||||
|
</pre>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -11,27 +11,10 @@
|
||||||
<h1><a href="/">datatrash</a></h1>
|
<h1><a href="/">datatrash</a></h1>
|
||||||
<p>
|
<p>
|
||||||
datei-link:
|
datei-link:
|
||||||
<a id="link" href="{url}">
|
<a id="link" class="copy-content" href="{url}">{url}</a>
|
||||||
{url}
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
<button id="copy" class="main button" onclick="copyToClipboard()">
|
<button id="copy" class="main button">link kopieren</button>
|
||||||
link kopieren
|
|
||||||
</button>
|
|
||||||
</main>
|
</main>
|
||||||
<script lang="javascript">
|
<script src="/static/copy.js" lang="javascript"></script>
|
||||||
function copyToClipboard() {
|
|
||||||
const button = document.getElementById("copy");
|
|
||||||
if (!navigator.clipboard) {
|
|
||||||
button.innerText = "nicht unterstützt";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const anchor = document.getElementById("link");
|
|
||||||
navigator.clipboard.writeText(anchor.href).then(
|
|
||||||
_ => { button.innerText = "kopiert!"; },
|
|
||||||
_ => { button.innerText = "nicht unterstützt"; },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -9,26 +9,13 @@
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<h1><a href="/">datatrash</a></h1>
|
<h1><a href="/">datatrash</a></h1>
|
||||||
<textarea id="text" rows="20" cols="120" readonly>{text}</textarea>
|
<textarea id="text" rows="20" cols="120" class="copy-content" readonly>
|
||||||
|
{text}</textarea
|
||||||
|
>
|
||||||
<br />
|
<br />
|
||||||
<a class="main button" href="?raw">herunterladen</a>
|
<a class="main button" href="?raw">herunterladen</a>
|
||||||
<button id="copy" class="button" onclick="copyToClipboard()">
|
<button id="copy" class="button">text kopieren</button>
|
||||||
text kopieren
|
|
||||||
</button>
|
|
||||||
</main>
|
</main>
|
||||||
<script lang="javascript">
|
<script src="/static/copy.js" lang="javascript"></script>
|
||||||
function copyToClipboard() {
|
|
||||||
const button = document.getElementById("copy");
|
|
||||||
if (!navigator.clipboard) {
|
|
||||||
button.innerText = "nicht unterstützt";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const textarea = document.getElementById("text");
|
|
||||||
navigator.clipboard.writeText(textarea.value).then(
|
|
||||||
_ => { button.innerText = "kopiert!"; },
|
|
||||||
_ => { button.innerText = "nicht unterstützt"; },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue