summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <vpzomtrrfrt@gmail.com>2020-10-08 09:44:02 -0600
committerColin Reeder <vpzomtrrfrt@gmail.com>2020-10-08 09:44:02 -0600
commit3d0f120023526c507e4d97f70fb633288401f260 (patch)
tree733b2ecb7451f30ef5d041ffe650307ab2044f15
parent1c2477ed05dde6e54fd445fef82a642f39a159e7 (diff)
Add image upload to New Post UI
-rw-r--r--Cargo.lock135
-rw-r--r--Cargo.toml1
-rw-r--r--res/lang/en.ftl3
-rw-r--r--src/resp_types.rs5
-rw-r--r--src/routes/communities.rs144
5 files changed, 271 insertions, 17 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 38abccf..68b2e2c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,15 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
+name = "aho-corasick"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "ammonia"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -79,12 +88,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
[[package]]
+name = "derive_more"
+version = "0.99.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "dtoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
[[package]]
+name = "encoding_rs"
+version = "0.8.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -178,12 +207,28 @@ dependencies = [
]
[[package]]
+name = "futures"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
name = "futures-channel"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
dependencies = [
"futures-core",
+ "futures-sink",
]
[[package]]
@@ -193,6 +238,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
[[package]]
+name = "futures-executor"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c"
+
+[[package]]
name = "futures-macro"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -225,9 +287,13 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
dependencies = [
+ "futures-channel",
"futures-core",
+ "futures-io",
"futures-macro",
+ "futures-sink",
"futures-task",
+ "memchr",
"pin-project",
"pin-utils",
"proc-macro-hack",
@@ -299,6 +365,7 @@ dependencies = [
"hyper",
"hyper-tls",
"lazy_static",
+ "multer",
"render",
"serde",
"serde_derive",
@@ -535,6 +602,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
name = "mio"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -566,6 +639,25 @@ dependencies = [
]
[[package]]
+name = "multer"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99851e6ad01b0fbe086dda2dea00d68bb84fc7d7eae2c39ca7313da9197f4d31"
+dependencies = [
+ "bytes",
+ "derive_more",
+ "encoding_rs",
+ "futures",
+ "http",
+ "httparse",
+ "lazy_static",
+ "log",
+ "mime",
+ "regex",
+ "twoway",
+]
+
+[[package]]
name = "native-tls"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -882,6 +974,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
+name = "regex"
+version = "1.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
+
+[[package]]
name = "remove_dir_all"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1138,6 +1248,15 @@ dependencies = [
]
[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1229,6 +1348,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
[[package]]
+name = "twoway"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
+dependencies = [
+ "memchr",
+ "unchecked-index",
+]
+
+[[package]]
name = "type-map"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1238,6 +1367,12 @@ dependencies = [
]
[[package]]
+name = "unchecked-index"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
+
+[[package]]
name = "unic-langid"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 839422e..cc7c4c5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,3 +32,4 @@ lazy_static = "1.4.0"
unic-langid = { version = "0.9.0", features = ["macros"] }
futures-util = "0.3.5"
hitide_icons = { path = "./icons" }
+multer = "1.2.2"
diff --git a/res/lang/en.ftl b/res/lang/en.ftl
index 41371d9..ae90509 100644
--- a/res/lang/en.ftl
+++ b/res/lang/en.ftl
@@ -62,6 +62,9 @@ post_delete_question = Delete this post?
post_delete_title = Delete Post
post_likes_nothing = Looks like nobody has liked this post yet.
post_new = New Post
+post_new_href_conflict = Cannot specify both URL and Image
+post_new_missing_content_type = Missing Content-Type for image upload
+post_new_image_prompt = Image:
post_not_approved = This post has not been approved by the community.
preview = Preview
register = Register
diff --git a/src/resp_types.rs b/src/resp_types.rs
index 279c924..ec5673e 100644
--- a/src/resp_types.rs
+++ b/src/resp_types.rs
@@ -166,6 +166,11 @@ pub struct JustID {
}
#[derive(Deserialize, Debug)]
+pub struct JustStringID<'a> {
+ pub id: &'a str,
+}
+
+#[derive(Deserialize, Debug)]
pub struct RespYourFollow {
pub accepted: bool,
}
diff --git a/src/routes/communities.rs b/src/routes/communities.rs
index 526c89b..909ab4c 100644
--- a/src/routes/communities.rs
+++ b/src/routes/communities.rs
@@ -1,13 +1,14 @@
use crate::components::{CommunityLink, HTPage, MaybeFillInput, MaybeFillTextArea, PostItem};
use crate::resp_types::{
- JustContentHTML, RespCommunityInfoMaybeYour, RespMinimalAuthorInfo, RespMinimalCommunityInfo,
- RespPostListPost, RespYourFollow,
+ JustContentHTML, JustStringID, RespCommunityInfoMaybeYour, RespMinimalAuthorInfo,
+ RespMinimalCommunityInfo, RespPostListPost, RespYourFollow,
};
use crate::routes::{
fetch_base_data, for_client, get_cookie_map_for_headers, get_cookie_map_for_req, html_response,
res_to_error, CookieMap,
};
use serde_derive::Deserialize;
+use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
@@ -722,7 +723,7 @@ async fn page_community_new_post_inner(
cookies: &CookieMap<'_>,
ctx: Arc<crate::RouteContext>,
display_error: Option<String>,
- prev_values: Option<&HashMap<&str, serde_json::Value>>,
+ prev_values: Option<&HashMap<Cow<'_, str>, serde_json::Value>>,
display_preview: Option<&str>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, &cookies).await?;
@@ -744,7 +745,7 @@ async fn page_community_new_post_inner(
}
})
}
- <form method={"POST"} action={&submit_url}>
+ <form method={"POST"} action={&submit_url} enctype={"multipart/form-data"}>
<table>
<tr>
<td>
@@ -762,6 +763,14 @@ async fn page_community_new_post_inner(
<MaybeFillInput values={&prev_values} r#type={"text"} name={"href"} required={false} id={"input_url"} />
</td>
</tr>
+ <tr>
+ <td>
+ <label for={"input_image"}>{lang.tr("post_new_image_prompt", None)}</label>
+ </td>
+ <td>
+ <input id={"input_image"} type={"file"} accept={"image/*"} name={"href_media"} />
+ </td>
+ </tr>
</table>
<label>
{lang.tr("text_with_markdown", None)}{":"}
@@ -792,12 +801,113 @@ async fn handler_communities_new_post_submit(
let (community_id,) = params;
let (req_parts, body) = req.into_parts();
+ let lang = crate::get_lang_for_headers(&req_parts.headers);
let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
- let body = hyper::body::to_bytes(body).await?;
- let mut body: HashMap<&str, serde_json::Value> = serde_urlencoded::from_bytes(&body)?;
- if body.contains_key("preview") {
- let md = body
+ let content_type = req_parts
+ .headers
+ .get(hyper::header::CONTENT_TYPE)
+ .ok_or_else(|| {
+ crate::Error::InternalStr("missing content-type header in form submission".to_owned())
+ })?;
+ let content_type = std::str::from_utf8(content_type.as_ref())?;
+
+ let boundary = multer::parse_boundary(&content_type)?;
+
+ let mut multipart = multer::Multipart::new(body, boundary);
+
+ let mut body_values: HashMap<Cow<'_, str>, serde_json::Value> = HashMap::new();
+ {
+ let mut error = None;
+
+ loop {
+ let field = multipart.next_field().await?;
+ let field = match field {
+ None => break,
+ Some(field) => field,
+ };
+
+ if field.name().is_none() {
+ continue;
+ }
+
+ if field.name().unwrap() == "href_media" {
+ if body_values.contains_key("href") && body_values["href"] != "" {
+ error = Some(lang.tr("post_new_href_conflict", None).into_owned());
+ } else {
+ match field.content_type() {
+ None => {
+ error =
+ Some(lang.tr("post_new_missing_content_type", None).into_owned());
+ }
+ Some(mime) => {
+ println!("will upload media");
+ let res = res_to_error(
+ ctx.http_client
+ .request(for_client(
+ hyper::Request::post(format!(
+ "{}/api/unstable/media",
+ ctx.backend_host,
+ ))
+ .header(hyper::header::CONTENT_TYPE, mime.as_ref())
+ .body(hyper::Body::wrap_stream(field))?,
+ &req_parts.headers,
+ &cookies,
+ )?)
+ .await?,
+ )
+ .await;
+
+ match res {
+ Err(crate::Error::RemoteError((_, message))) => {
+ error = Some(message);
+ }
+ Err(other) => {
+ return Err(other);
+ }
+ Ok(res) => {
+ let res = hyper::body::to_bytes(res.into_body()).await?;
+ let res: JustStringID = serde_json::from_slice(&res)?;
+
+ body_values.insert(
+ "href".into(),
+ format!("local-media://{}", res.id).into(),
+ );
+ }
+ }
+
+ println!("finished media upload");
+ }
+ }
+ }
+ } else {
+ let name = field.name().unwrap();
+ if name == "href" && body_values.contains_key("href") && body_values["href"] != "" {
+ error = Some(lang.tr("post_new_href_conflict", None).into_owned());
+ } else {
+ let name = name.to_owned();
+ let value = field.text().await?;
+ body_values.insert(name.into(), value.into());
+ }
+ }
+ }
+
+ if let Some(error) = error {
+ return page_community_new_post_inner(
+ community_id,
+ &req_parts.headers,
+ &cookies,
+ ctx,
+ Some(error),
+ Some(&body_values),
+ None,
+ )
+ .await;
+ }
+ }
+
+ if body_values.contains_key("preview") {
+ let md = body_values
.get("content_markdown")
.and_then(|x| x.as_str())
.unwrap_or("");
@@ -828,7 +938,7 @@ async fn handler_communities_new_post_submit(
&cookies,
ctx,
None,
- Some(&body),
+ Some(&body_values),
Some(&preview_res.content_html),
)
.await
@@ -840,7 +950,7 @@ async fn handler_communities_new_post_submit(
&cookies,
ctx,
Some(message),
- Some(&body),
+ Some(&body_values),
None,
)
.await
@@ -849,19 +959,19 @@ async fn handler_communities_new_post_submit(
};
}
- body.insert("community", community_id.into());
- if body.get("content_markdown").and_then(|x| x.as_str()) == Some("") {
- body.remove("content_markdown");
+ body_values.insert("community".into(), community_id.into());
+ if body_values.get("content_markdown").and_then(|x| x.as_str()) == Some("") {
+ body_values.remove("content_markdown");
}
- if body.get("href").and_then(|x| x.as_str()) == Some("") {
- body.remove("href");
+ if body_values.get("href").and_then(|x| x.as_str()) == Some("") {
+ body_values.remove("href");
}
let api_res = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::post(format!("{}/api/unstable/posts", ctx.backend_host))
- .body(serde_json::to_vec(&body)?.into())?,
+ .body(serde_json::to_vec(&body_values)?.into())?,
&req_parts.headers,
&cookies,
)?)
@@ -891,7 +1001,7 @@ async fn handler_communities_new_post_submit(
&cookies,
ctx,
Some(message),
- Some(&body),
+ Some(&body_values),
None,
)
.await