summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2020-06-06 18:59:03 -0400
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2020-06-06 19:03:31 -0400
commit8e89d43bdf35b2a1f69f3f7e3d87c57fb1e3f58a (patch)
treee7152a060dce2ef8a1dbf41cc6202339c5337a97
parent0f0d4477a4eab8ec0b99c134154ce0fedb4d6400 (diff)
Implement file uploading.
-rw-r--r--Cargo.lock349
-rw-r--r--Cargo.toml2
-rw-r--r--README.md6
-rw-r--r--src/birka-web.rs (renamed from src/main.rs)139
-rw-r--r--static/css/style.css10
-rw-r--r--templates/upload.hbs22
6 files changed, 477 insertions, 51 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1492cbe..27a846a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -46,6 +46,12 @@ dependencies = [
[[package]]
name = "autocfg"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
+
+[[package]]
+name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
@@ -90,7 +96,9 @@ dependencies = [
"blake2",
"dirs",
"image",
+ "rand 0.7.3",
"rocket",
+ "rocket-multipart-form-data",
"rocket_contrib",
"rusqlite",
"serde",
@@ -149,6 +157,16 @@ dependencies = [
]
[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+dependencies = [
+ "memchr",
+ "safemem",
+]
+
+[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -179,6 +197,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
name = "color_quant"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -228,7 +255,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
"cfg-if",
"crossbeam-utils",
"lazy_static",
@@ -253,7 +280,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
"cfg-if",
"lazy_static",
]
@@ -342,6 +369,17 @@ dependencies = [
]
[[package]]
+name = "educe"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d27c760a73a13abb1ddf417c06bc2b8d14b0607dd19097316e0ca9412a971088"
+dependencies = [
+ "proc-macro2 1.0.17",
+ "quote 1.0.6",
+ "syn 1.0.25",
+]
+
+[[package]]
name = "either"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -397,6 +435,12 @@ dependencies = [
]
[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
+[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -490,7 +534,7 @@ dependencies = [
"httparse",
"language-tags",
"log 0.3.9",
- "mime",
+ "mime 0.2.6",
"num_cpus",
"time",
"traitobject",
@@ -534,7 +578,7 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
]
[[package]]
@@ -704,7 +748,7 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
]
[[package]]
@@ -717,6 +761,24 @@ dependencies = [
]
[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "1.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3"
+dependencies = [
+ "mime 0.2.6",
+ "phf",
+ "phf_codegen",
+ "unicase",
+]
+
+[[package]]
name = "miniz_oxide"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -769,6 +831,24 @@ dependencies = [
]
[[package]]
+name = "multipart"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136eed74cadb9edd2651ffba732b19a450316b680e4f48d6c79e905799e19d01"
+dependencies = [
+ "buf_redux",
+ "httparse",
+ "log 0.4.8",
+ "mime 0.2.6",
+ "mime_guess",
+ "quick-error",
+ "rand 0.6.5",
+ "safemem",
+ "tempfile",
+ "twoway",
+]
+
+[[package]]
name = "net2"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -803,7 +883,7 @@ version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
"num-traits",
]
@@ -813,7 +893,7 @@ version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
"num-integer",
"num-traits",
]
@@ -824,7 +904,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
"num-integer",
"num-traits",
]
@@ -835,7 +915,7 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
]
[[package]]
@@ -926,6 +1006,45 @@ dependencies = [
]
[[package]]
+name = "phf"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
+dependencies = [
+ "phf_shared",
+ "rand 0.6.5",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
+dependencies = [
+ "siphasher",
+ "unicase",
+]
+
+[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -944,6 +1063,12 @@ dependencies = [
]
[[package]]
+name = "ppv-lite86"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
+
+[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -986,6 +1111,153 @@ dependencies = [
]
[[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+dependencies = [
+ "autocfg 0.1.7",
+ "libc",
+ "rand_chacha 0.1.1",
+ "rand_core 0.4.2",
+ "rand_hc 0.1.0",
+ "rand_isaac",
+ "rand_jitter",
+ "rand_os",
+ "rand_pcg",
+ "rand_xorshift",
+ "winapi 0.3.8",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+dependencies = [
+ "autocfg 0.1.7",
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+dependencies = [
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
+dependencies = [
+ "libc",
+ "rand_core 0.4.2",
+ "winapi 0.3.8",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+dependencies = [
+ "cloudabi",
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.4.2",
+ "rdrand",
+ "winapi 0.3.8",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+dependencies = [
+ "autocfg 0.1.7",
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
name = "rayon"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1010,6 +1282,15 @@ dependencies = [
]
[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1045,6 +1326,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
[[package]]
+name = "remove_dir_all"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
+dependencies = [
+ "winapi 0.3.8",
+]
+
+[[package]]
name = "ring"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1078,6 +1368,18 @@ dependencies = [
]
[[package]]
+name = "rocket-multipart-form-data"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c38ffbd5126f8df80346e7945028d5dd5ea81f6cf0e4b6310942ce6d7e5db691"
+dependencies = [
+ "educe",
+ "mime 0.3.16",
+ "multipart",
+ "rocket",
+]
+
+[[package]]
name = "rocket_codegen"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1225,6 +1527,12 @@ dependencies = [
]
[[package]]
+name = "siphasher"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
+
+[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1271,6 +1579,20 @@ dependencies = [
]
[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand 0.7.3",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi 0.3.8",
+]
+
+[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1316,6 +1638,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
[[package]]
+name = "twoway"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "typeable"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 6087545..a4a707f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,8 @@ rusqlite = "0.21.0"
# Until RFC #2887 is merged, image and rocket are deps of the CLI tool as well.
image = "0.23"
+multipart = { package = "rocket-multipart-form-data", version = "0.9" }
+rand = "0.7"
rocket = "0.4"
rocket_contrib = { version = "0.4", default-features = false, features = ["handlebars_templates", "json", "serve"] }
serde = "1.0"
diff --git a/README.md b/README.md
index c18c66f..2cf8a74 100644
--- a/README.md
+++ b/README.md
@@ -108,9 +108,9 @@ GET /api/posts/[id]
ImageResult
```
-To upload an image to the database, the blob of data may be POSTED to the
-following endpoint, where `filename` is the desired filename of the image (a
-string):
+To upload an image to the database, post a multipart/form-data encoded message
+to the following endpoint with the following fields: `tags`, a comma-separated
+list of tags for the files, and `image`, the file to be uploaded.
```
POST /api/posts/[filename]
diff --git a/src/main.rs b/src/birka-web.rs
index 2a5b440..22ace80 100644
--- a/src/main.rs
+++ b/src/birka-web.rs
@@ -24,20 +24,27 @@ extern crate image;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
+extern crate rusqlite;
+#[macro_use]
extern crate serde_derive;
mod database;
use anyhow::Result;
-use database::{hash_file, Query, TagDatabase};
+use database::{Image, Query, TagDatabase};
use image::ImageFormat;
+use multipart::{MultipartFormData, MultipartFormDataField, MultipartFormDataOptions};
+use rand::distributions::Alphanumeric;
+use rand::{thread_rng, Rng};
+use rocket::http::ContentType;
use rocket::{Data, State};
use rocket_contrib::json::Json;
use rocket_contrib::serve::StaticFiles;
use rocket_contrib::templates::Template;
-use std::env;
+
use std::fs;
use std::fs::File;
+use std::io::Write;
use std::os::unix::fs::symlink;
use std::path::Path;
use std::sync::Mutex;
@@ -48,7 +55,7 @@ const RESULTS_PER_QUERY: i64 = 50;
/// Shorthand for the state that is passed to all handlers.
type SiteState<'a> = State<'a, Mutex<DatabaseConnection>>;
-/// Collection of data necessary to serve images from the tag database.
+/// Everything necessary to serve images from the tag database.
struct DatabaseConnection {
tb: TagDatabase,
image_dir: String,
@@ -56,17 +63,18 @@ struct DatabaseConnection {
impl DatabaseConnection {
fn new() -> Result<Self> {
- let bin = env::current_exe()?.canonicalize()?;
+ // FIXME: It is somewhat of an arbitrary choice that the directory
+ // containing symbolic links and thumbnails be relative to the binary.
+ let bin = std::env::current_exe()?.canonicalize()?;
let bin_dir = bin.parent().unwrap();
- let image_dir = String::from(bin_dir.join("image/").to_str().unwrap());
- let tb_path = bin_dir.join("birka.db");
- let tb = TagDatabase::new(tb_path.to_str().unwrap())?;
+ let image_dir = bin_dir.join("image/").to_str().unwrap().to_string();
+ let tb = TagDatabase::new(&bin_dir.join("birka.db"))?;
Ok(DatabaseConnection { tb, image_dir })
}
}
/// Information about an image, as returned by the 'posts' endpoint.
-#[derive(Serialize)]
+#[derive(Debug, Serialize)]
struct ImageResult {
id: i64,
filename: String,
@@ -75,6 +83,7 @@ struct ImageResult {
tags: Vec<String>,
}
+/// Return the store file name of the image at `filename`.
fn image_store_filename(id: i64, filename: &str) -> String {
if let Some(n) = filename.rfind('.') {
format!("{id}.{ext}", id = id, ext = &filename[n + 1..])
@@ -83,7 +92,7 @@ fn image_store_filename(id: i64, filename: &str) -> String {
}
}
-/// Return the file name of the thumbnail for the image at `filename`.
+/// Return the store file name of the thumbnail for the image at `filename`.
fn thumb_filename(id: i64, filename: &str) -> String {
if let Some(n) = filename.rfind('.') {
format!("{id}_thumb.{ext}", id = id, ext = &filename[n + 1..])
@@ -103,12 +112,9 @@ fn get_posts(conn: SiteState, tags: String, last: Option<i64>) -> Json<Vec<Image
} else {
vec![]
};
+
let results = tb
- .query(Query {
- tags: &tags[..],
- last_id: last,
- limit: Some(RESULTS_PER_QUERY),
- })
+ .query(Query::new(&tags[..], last, Some(RESULTS_PER_QUERY)))
.unwrap();
Json(
@@ -139,16 +145,46 @@ fn get_post(conn: SiteState, id: i64) -> Json<ImageResult> {
})
}
-#[post("/posts/<filename>", data = "<data>")]
-fn put_post(conn: SiteState, filename: String, data: Data) -> Json<&str> {
+#[post("/posts", data = "<data>")]
+fn put_post(conn: SiteState, content_type: &ContentType, data: Data) -> Json<String> {
let conn = conn.inner().lock().unwrap();
- let path = Path::new(&conn.image_dir).join(filename);
- data.stream_to_file(&path).unwrap();
- conn.tb
- .import_image(&path, &hash_file(&path).unwrap())
- .unwrap();
+ let options = MultipartFormDataOptions::with_multipart_form_data_fields(vec![
+ MultipartFormDataField::text("tags"),
+ MultipartFormDataField::raw("image")
+ .size_limit(32 * 1024 * 1024)
+ .content_type_by_string(Some(multipart::mime::IMAGE_STAR))
+ .unwrap(),
+ ]);
- Json("uploaded!")
+ let mut data = MultipartFormData::parse(content_type, data, options).unwrap();
+ let raw = data.raw.remove("image").unwrap().remove(0);
+
+ let filename = raw
+ .file_name
+ .unwrap_or(thread_rng().sample_iter(&Alphanumeric).take(30).collect());
+ let path = Path::new(&conn.image_dir).join(&filename);
+ let mut f = File::create(&path).unwrap();
+ f.write_all(&raw.raw).unwrap();
+
+ let hash = database::hash_file(&path).unwrap();
+ let id = conn.tb.import_image(&path, &hash).unwrap();
+ add_to_store(
+ &Image {
+ id,
+ blake2: hash,
+ filename: filename.clone(),
+ orig_dir: conn.image_dir.clone(),
+ },
+ &conn.image_dir,
+ );
+
+ let tags = data.texts.remove("tags").unwrap().remove(0).text;
+ for tag in tags.split(",") {
+ conn.tb.add_tag(tag).unwrap();
+ conn.tb.tag_image(id, tag).unwrap();
+ }
+
+ Json("Uploaded!".into())
}
#[get("/")]
@@ -156,6 +192,11 @@ fn index(conn: SiteState) -> Template {
display_posts(conn, String::from(""), None)
}
+#[get("/upload")]
+fn upload() -> Template {
+ Template::render("upload", None::<()>)
+}
+
#[get("/posts?<tags>&<last>")]
fn display_posts(conn: SiteState, tags: String, last: Option<i64>) -> Template {
#[derive(Serialize)]
@@ -203,28 +244,48 @@ fn display_posts(conn: SiteState, tags: String, last: Option<i64>) -> Template {
#[get("/posts/<id>")]
fn display_post(conn: SiteState, id: i64) -> Template {
- Template::render("image", get_post(conn, id).into_inner())
+ #[derive(Serialize)]
+ struct Context {
+ tags: Vec<String>,
+ filename: String,
+ orig_filename: String,
+ }
+
+ let tb = &conn.inner().lock().unwrap().tb;
+ let image = tb.image_by_id(id).unwrap();
+
+ Template::render(
+ "image",
+ Context {
+ tags: tb.tags_for_image(image.id).unwrap(),
+ filename: image_store_filename(image.id, &image.filename),
+ orig_filename: image.filename,
+ },
+ )
+}
+
+/// Create a thumbnail for `image` in the store at `image_dir`.
+fn add_to_store(image: &Image, image_dir: &str) {
+ let orig_path = Path::new(&image.orig_dir).join(&image.filename);
+ let store_path = image_store_filename(image.id, &image.filename);
+ let store_path = Path::new(image_dir).join(store_path);
+ symlink(orig_path, &store_path).ok();
+ let thumb_path = thumb_filename(image.id, &image.filename);
+ let thumb_path = Path::new(image_dir).join(&thumb_path);
+ if !thumb_path.exists() {
+ let im = image::open(store_path).unwrap();
+ let out = &mut File::create(&thumb_path).unwrap();
+ im.thumbnail(100, 100)
+ .write_to(out, ImageFormat::Png)
+ .unwrap();
+ }
}
/// Maybe initialize the directory for storing image symlinks and thumbnails.
fn create_image_directory(conn: &DatabaseConnection) {
fs::create_dir(&conn.image_dir).ok();
-
for image in conn.tb.images_by_tags(&vec![][..]).unwrap() {
- let src = Path::new(&image.orig_dir).join(&image.filename);
- let dst = image_store_filename(image.id, &image.filename);
- let dst = Path::new(&conn.image_dir).join(dst);
- symlink(&src, &dst).ok();
-
- let thumb_path = thumb_filename(image.id, &image.filename);
- let thumb_path = Path::new(&conn.image_dir).join(&thumb_path);
- if !thumb_path.exists() {
- let im = image::open(src).unwrap();
- let out = &mut File::create(&thumb_path).unwrap();
- im.thumbnail(100, 100)
- .write_to(out, ImageFormat::Png)
- .unwrap();
- }
+ add_to_store(&image, &conn.image_dir);
}
}
@@ -234,7 +295,7 @@ fn main() {
rocket::ignite()
.mount("/public", StaticFiles::from("./static/"))
.mount("/image", StaticFiles::from(&conn.image_dir))
- .mount("/", routes![index, display_posts, display_post])
+ .mount("/", routes![index, upload, display_posts, display_post])
.mount("/api", routes![get_posts, get_post, put_post])
.attach(Template::fairing())
.manage(Mutex::new(conn))
diff --git a/static/css/style.css b/static/css/style.css
index b32823e..017622a 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -18,3 +18,13 @@ aside {
main {
grid-column: 2 / 6;
grid-row: 2
+}
+
+#upload {
+ width: 20%;
+}
+
+#upload input {
+ min-width: 100%;
+ margin-bottom: 16px;
+}
diff --git a/templates/upload.hbs b/templates/upload.hbs
new file mode 100644
index 0000000..ca57d4f
--- /dev/null
+++ b/templates/upload.hbs
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>бирка-тян - Upload File</title>
+ <link rel="stylesheet" type="text/css" href="/public/css/style.css">
+ <meta charset="UTF-8">
+</head>
+<body>
+ <header>
+ <h1><a href="/">бирка-тян</a></h1>
+ </header>
+ <main>
+ <h2>Upload a file</h2>
+ <form id="upload" method="post" action="/api/posts" enctype="multipart/form-data">
+ <input id="image" name="image" type="file" accept="image/*">
+ <label for="tags">Tags:</label>
+ <input id="tags" name="tags" type="text">
+ <button type="submit">Upload</button>
+ </form>
+ </main>
+</body>
+</html>