From 5b871a33327b36a829e5463702fa7fba6b8101a4 Mon Sep 17 00:00:00 2001 From: Adrian Wozniak Date: Tue, 5 May 2020 08:33:40 +0200 Subject: [PATCH] Add upload image --- Cargo.lock | 562 +++++++++++++++++- jirs-client/Cargo.toml | 5 + jirs-client/js/css/styledImageInput.css | 14 + jirs-client/js/styles.css | 1 + jirs-client/src/lib.rs | 27 +- jirs-client/src/modal/time_tracking.rs | 4 +- jirs-client/src/model.rs | 6 + jirs-client/src/profile.rs | 44 +- jirs-client/src/shared/mod.rs | 2 +- jirs-client/src/shared/styled_image_input.rs | 127 ++++ jirs-client/src/sign_in.rs | 11 +- jirs-client/src/sign_up.rs | 11 +- jirs-data/src/lib.rs | 1 + jirs-server/Cargo.toml | 7 +- jirs-server/src/main.rs | 12 +- jirs-server/src/web/avatar.rs | 73 ++- jirs-server/src/web/mod.rs | 29 + .../tmp/.gitkeep | 0 tmp/.gitkeep | 0 19 files changed, 884 insertions(+), 52 deletions(-) create mode 100644 jirs-client/js/css/styledImageInput.css create mode 100644 jirs-client/src/shared/styled_image_input.rs rename jirs-client/src/shared/styled_file_input.rs => jirs-server/tmp/.gitkeep (100%) create mode 100644 tmp/.gitkeep diff --git a/Cargo.lock b/Cargo.lock index 20895b54..3e3d1155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,26 @@ dependencies = [ "futures 0.3.4", ] +[[package]] +name = "actix-files" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301482841d3d74483a446ead63cb7d362e187d2c8b603f13d91995621ea53c46" +dependencies = [ + "actix-http", + "actix-service", + "actix-web", + "bitflags", + "bytes", + "derive_more", + "futures 0.3.4", + "log 0.4.8", + "mime", + "mime_guess", + "percent-encoding 2.1.0", + "v_htmlescape", +] + [[package]] name = "actix-http" version = "1.0.1" @@ -115,7 +135,7 @@ dependencies = [ "serde_urlencoded", "sha1", "slab", - "time", + "time 0.1.42", ] [[package]] @@ -143,7 +163,7 @@ dependencies = [ "httparse", "log 0.4.8", "mime", - "time", + "time 0.1.42", "twoway", ] @@ -298,7 +318,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "time", + "time 0.1.42", "url 2.1.1", ] @@ -378,6 +398,18 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + [[package]] name = "ascii_utils" version = "0.9.3" @@ -463,6 +495,12 @@ dependencies = [ "libc", ] +[[package]] +name = "base-x" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" + [[package]] name = "base64" version = "0.9.3" @@ -488,6 +526,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "base64" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" + [[package]] name = "bigdecimal" version = "0.1.0" @@ -515,6 +559,17 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.7.3" @@ -616,7 +671,7 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time", + "time 0.1.42", ] [[package]] @@ -670,13 +725,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "cookie" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" dependencies = [ - "time", + "time 0.1.42", "url 1.7.2", ] @@ -732,6 +793,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "dbg" version = "1.0.4" @@ -771,7 +842,7 @@ dependencies = [ "pq-sys", "r2d2", "serde_json", - "time", + "time 0.1.42", "uuid 0.6.5", "uuid 0.8.1", ] @@ -796,6 +867,34 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +dependencies = [ + "cfg-if", + "libc", + "redox_users", + "winapi 0.3.8", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dotenv" version = "0.15.0" @@ -825,7 +924,7 @@ dependencies = [ "encoding", "lazy_static", "rand 0.4.6", - "time", + "time 0.1.42", "version_check 0.1.5", ] @@ -1261,6 +1360,22 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest", +] + [[package]] name = "hostname" version = "0.1.5" @@ -1293,6 +1408,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes", + "http", +] + [[package]] name = "httparse" version = "1.3.4" @@ -1308,6 +1433,43 @@ dependencies = [ "quick-error", ] +[[package]] +name = "hyper" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "log 0.4.8", + "net2", + "pin-project", + "time 0.1.42", + "tokio", + "tower-service", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-tls", +] + [[package]] name = "idna" version = "0.1.5" @@ -1425,6 +1587,7 @@ version = "0.1.0" dependencies = [ "actix", "actix-cors", + "actix-files", "actix-multipart", "actix-rt", "actix-service", @@ -1453,9 +1616,11 @@ dependencies = [ "pretty_env_logger", "quickcheck", "r2d2", + "rusoto_core", + "rusoto_s3", "serde", "serde_json", - "time", + "time 0.1.42", "toml", "url 2.1.1", "uuid 0.8.1", @@ -1545,7 +1710,7 @@ dependencies = [ "email", "lettre", "mime", - "time", + "time 0.1.42", "uuid 0.7.4", ] @@ -1621,6 +1786,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "0.1.11" @@ -1642,6 +1813,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.3.6" @@ -1664,7 +1845,7 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.8", - "miow", + "miow 0.2.1", "net2", "slab", "winapi 0.2.8", @@ -1682,6 +1863,18 @@ dependencies = [ "slab", ] +[[package]] +name = "mio-named-pipes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" +dependencies = [ + "log 0.4.8", + "mio", + "miow 0.3.3", + "winapi 0.3.8", +] + [[package]] name = "mio-uds" version = "0.6.7" @@ -1705,6 +1898,16 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" +dependencies = [ + "socket2", + "winapi 0.3.8", +] + [[package]] name = "native-tls" version = "0.2.4" @@ -2238,6 +2441,17 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "redox_users" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + [[package]] name = "regex" version = "0.1.80" @@ -2294,12 +2508,120 @@ dependencies = [ "quick-error", ] +[[package]] +name = "rusoto_core" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8d624cb48fcaca612329e4dd544380aa329ef338e83d3a90f5b7897e631971" +dependencies = [ + "async-trait", + "base64 0.12.0", + "bytes", + "futures 0.3.4", + "hmac", + "http", + "hyper", + "hyper-tls", + "lazy_static", + "log 0.4.8", + "md5", + "percent-encoding 2.1.0", + "pin-project", + "rusoto_credential", + "rusoto_signature", + "rustc_version", + "serde", + "serde_json", + "sha2", + "tokio", + "xml-rs", +] + +[[package]] +name = "rusoto_credential" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3e7cdf483d7198d9bca7414746d3ba656239e89e467b715d0571912f0b492f" +dependencies = [ + "async-trait", + "chrono", + "dirs", + "futures 0.3.4", + "hyper", + "pin-project", + "regex 1.3.6", + "serde", + "serde_json", + "shlex", + "tokio", + "zeroize", +] + +[[package]] +name = "rusoto_s3" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6bc3221ae5a2c036d5757eee68a2ffb6b7f87b8a83adbf4271c8133fdee01c" +dependencies = [ + "async-trait", + "bytes", + "futures 0.3.4", + "rusoto_core", + "xml-rs", +] + +[[package]] +name = "rusoto_signature" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62940a2bd479900a1bf8935b8f254d3e19368ac3ac4570eb4bd48eb46551a1b7" +dependencies = [ + "base64 0.12.0", + "bytes", + "futures 0.3.4", + "hex", + "hmac", + "http", + "hyper", + "log 0.4.8", + "md5", + "percent-encoding 2.1.0", + "pin-project", + "rusoto_credential", + "rustc_version", + "serde", + "sha2", + "time 0.2.15", + "tokio", +] + +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +dependencies = [ + "base64 0.11.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.3" @@ -2398,6 +2720,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.105" @@ -2459,6 +2796,24 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +[[package]] +name = "sha2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + [[package]] name = "signal-hook-registry" version = "1.2.0" @@ -2493,12 +2848,73 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "standback" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4b8c631c998468961a9ea159f064c5c8499b95b5e4a34b77849d45949d540" + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "syn" version = "1.0.17" @@ -2602,6 +3018,44 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "time" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1330829d2e6c06771eeae476be12ff1aa9eb5e29ca718a431ab27168efde6c1" +dependencies = [ + "cfg-if", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check 0.9.1", + "winapi 0.3.8", +] + +[[package]] +name = "time-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + [[package]] name = "tokio" version = "0.2.13" @@ -2616,13 +3070,36 @@ dependencies = [ "libc", "memchr 2.3.3", "mio", + "mio-named-pipes", "mio-uds", "pin-project-lite", "signal-hook-registry", "slab", + "tokio-macros", "winapi 0.3.8", ] +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.2.0" @@ -2646,6 +3123,12 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" + [[package]] name = "trust-dns-proto" version = "0.18.0-alpha.2" @@ -2685,6 +3168,12 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "try-lock" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" + [[package]] name = "twoway" version = "0.2.1" @@ -2827,6 +3316,37 @@ dependencies = [ "sha1", ] +[[package]] +name = "v_escape" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6" +dependencies = [ + "v_escape_derive", +] + +[[package]] +name = "v_escape_derive" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "v_htmlescape" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41" +dependencies = [ + "cfg-if", + "v_escape", +] + [[package]] name = "vcpkg" version = "0.2.8" @@ -2862,6 +3382,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.8", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3046,3 +3576,15 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "xml-rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" + +[[package]] +name = "zeroize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" diff --git a/jirs-client/Cargo.toml b/jirs-client/Cargo.toml index 96bd9b62..6d191139 100644 --- a/jirs-client/Cargo.toml +++ b/jirs-client/Cargo.toml @@ -50,6 +50,11 @@ features = [ "BinaryType", "Blob", "AddEventListenerOptions", + "File", + "FileList", + "FormData", + "FileReader", + "FileReaderSync", # events "EventTarget", "ErrorEvent", diff --git a/jirs-client/js/css/styledImageInput.css b/jirs-client/js/css/styledImageInput.css new file mode 100644 index 00000000..3d99dca9 --- /dev/null +++ b/jirs-client/js/css/styledImageInput.css @@ -0,0 +1,14 @@ +.styledImageInput { +} + +.styledImageInput > .label > .mask { + display: block; + width: 120px; + height: 120px; + margin: 0 auto; + border-radius: 60px; +} + +.styledImageInput > .input { + display: none; +} diff --git a/jirs-client/js/styles.css b/jirs-client/js/styles.css index 1450a21a..7a4eba39 100644 --- a/jirs-client/js/styles.css +++ b/jirs-client/js/styles.css @@ -13,6 +13,7 @@ @import "./css/styledSelectChild.css"; @import "./css/styledButton.css"; @import "./css/styledInput.css"; +@import "./css/styledImageInput.css"; @import "./css/styledModal.css"; @import "./css/styledTextArea.css"; @import "./css/styledForm.css"; diff --git a/jirs-client/src/lib.rs b/jirs-client/src/lib.rs index e49dcad2..69d20469 100644 --- a/jirs-client/src/lib.rs +++ b/jirs-client/src/lib.rs @@ -1,6 +1,7 @@ use std::sync::RwLock; use seed::{prelude::*, *}; +use web_sys::File; use jirs_data::*; @@ -128,11 +129,13 @@ impl std::fmt::Display for FieldId { UsersFieldId::Username => f.write_str("users-username"), UsersFieldId::Email => f.write_str("users-email"), UsersFieldId::UserRole => f.write_str("users-userRole"), + UsersFieldId::Avatar => f.write_str("users-avatar"), }, FieldId::Profile(sub) => match sub { UsersFieldId::Username => f.write_str("profile-username"), UsersFieldId::Email => f.write_str("profile-email"), UsersFieldId::UserRole => f.write_str("profile-userRole"), + UsersFieldId::Avatar => f.write_str("profile-avatar"), }, } } @@ -169,7 +172,7 @@ pub enum PageChanged { Profile(ProfilePageChange), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Msg { NoOp, GlobalKeyDown { @@ -179,6 +182,11 @@ pub enum Msg { alt: bool, }, PageChanged(PageChanged), + ChangePage(model::Page), + + StyledSelectChanged(FieldId, StyledSelectChange), + InternalFailure(String), + ToggleAboutTooltip, // Auth Token AuthTokenStored, @@ -195,12 +203,6 @@ pub enum Msg { // sign up SignUpRequest, - StyledSelectChanged(FieldId, StyledSelectChange), - - ChangePage(model::Page), - InternalFailure(String), - ToggleAboutTooltip, - // project ProjectAvatarFilterChanged(UserId, AvatarFilterActive), ProjectToggleOnlyMy, @@ -219,6 +221,7 @@ pub enum Msg { // inputs StrInputChanged(FieldId, String), U32InputChanged(FieldId, u32), + FileInputChanged(FieldId, Vec), // issues AddIssue, @@ -228,6 +231,9 @@ pub enum Msg { SaveComment, DeleteComment(CommentId), + // profile + AvatarUpdateFetched(seed::fetch::FetchObject), + // modals ModalOpened(Box), ModalDropped, @@ -237,9 +243,10 @@ pub enum Msg { } fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) { - if msg == Msg::NoOp { - return; - } + match msg { + Msg::NoOp => return, + _ => (), + }; if cfg!(debug_assertions) { log!(msg); } diff --git a/jirs-client/src/modal/time_tracking.rs b/jirs-client/src/modal/time_tracking.rs index 13341707..8ca627fe 100644 --- a/jirs-client/src/modal/time_tracking.rs +++ b/jirs-client/src/modal/time_tracking.rs @@ -8,10 +8,10 @@ use crate::shared::styled_field::StyledField; use crate::shared::styled_input::{StyledInput, StyledInputState}; use crate::shared::styled_modal::StyledModal; use crate::shared::styled_select::{StyledSelect, StyledSelectState}; -use crate::shared::styled_select_child::*; -use crate::shared::tracking_widget::{fibonacci_values, tracking_widget}; use crate::shared::{find_issue, ToChild, ToNode}; use crate::{EditIssueModalSection, FieldId, Msg}; +// use crate::shared::styled_select_child::*; +use crate::shared::tracking_widget::{fibonacci_values, tracking_widget}; pub fn value_for_time_tracking(v: &Option, time_tracking_type: &TimeTracking) -> String { match (time_tracking_type, v.as_ref()) { diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs index a2258948..53a0a029 100644 --- a/jirs-client/src/model.rs +++ b/jirs-client/src/model.rs @@ -8,6 +8,7 @@ use jirs_data::*; use crate::modal::time_tracking::value_for_time_tracking; use crate::shared::styled_checkbox::StyledCheckboxState; use crate::shared::styled_editor::Mode; +use crate::shared::styled_image_input::StyledImageInputState; use crate::shared::styled_input::StyledInputState; use crate::shared::styled_select::StyledSelectState; use crate::{EditIssueModalSection, FieldId, ProjectFieldId, HOST_URL}; @@ -364,6 +365,7 @@ impl Default for UsersPage { pub struct ProfilePage { pub name: StyledInputState, pub email: StyledInputState, + pub avatar: StyledImageInputState, } impl ProfilePage { @@ -377,6 +379,10 @@ impl ProfilePage { FieldId::Profile(UsersFieldId::Email), user.email.as_str(), ), + avatar: StyledImageInputState::new( + FieldId::Profile(UsersFieldId::Avatar), + user.avatar_url.as_ref().cloned(), + ), } } } diff --git a/jirs-client/src/profile.rs b/jirs-client/src/profile.rs index 54ce5422..f40da3ed 100644 --- a/jirs-client/src/profile.rs +++ b/jirs-client/src/profile.rs @@ -1,4 +1,5 @@ use seed::{prelude::*, *}; +use web_sys::FormData; use jirs_data::*; @@ -6,11 +7,12 @@ use crate::api::send_ws_msg; use crate::model::{Model, Page, PageContent, ProfilePage}; use crate::shared::styled_field::StyledField; use crate::shared::styled_form::StyledForm; +use crate::shared::styled_image_input::StyledImageInput; use crate::shared::styled_input::StyledInput; use crate::shared::{inner_layout, ToNode}; -use crate::{FieldId, Msg}; +use crate::{FieldId, Msg, HOST_URL}; -pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orders) { +pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders) { let user = match model.user { Some(ref user) => user, _ => return, @@ -31,6 +33,27 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orde project_page.name.update(&msg); project_page.email.update(&msg); + project_page.avatar.update(&msg); + + match msg { + Msg::FileInputChanged(FieldId::Profile(UsersFieldId::Avatar), ..) => { + let file = match project_page.avatar.file.as_ref() { + Some(f) => f, + _ => return, + }; + let token = match crate::shared::read_auth_token() { + Ok(uuid) => uuid, + _ => return, + }; + let fd = FormData::new().unwrap(); + fd.set_with_str("token", format!("{}", token).as_str()) + .unwrap(); + fd.set_with_blob("avatar", file).unwrap(); + orders.perform_cmd(update_avatar(fd)); + orders.skip(); + } + _ => (), + } } pub fn view(model: &Model) -> Node { @@ -39,6 +62,12 @@ pub fn view(model: &Model) -> Node { _ => return empty![], }; + let avatar = StyledImageInput::build(FieldId::Profile(UsersFieldId::Avatar)) + .add_class("avatar") + .state(&page.avatar) + .build() + .into_node(); + let username = StyledInput::build(FieldId::Profile(UsersFieldId::Username)) .state(&page.name) .valid(true) @@ -65,9 +94,20 @@ pub fn view(model: &Model) -> Node { let content = StyledForm::build() .heading("Profile") + .add_field(avatar) .add_field(username_field) .add_field(email_field) .build() .into_node(); inner_layout(model, "profile", vec![content], empty![]) } + +async fn update_avatar(data: FormData) -> Result { + let host_url = unsafe { HOST_URL.clone() }; + let path = format!("{}/avatar/", host_url); + Request::new(path) + .method(Method::Post) + .body(data.into()) + .fetch_string(Msg::AvatarUpdateFetched) + .await +} diff --git a/jirs-client/src/shared/mod.rs b/jirs-client/src/shared/mod.rs index ee6df9d7..3c350df4 100644 --- a/jirs-client/src/shared/mod.rs +++ b/jirs-client/src/shared/mod.rs @@ -14,9 +14,9 @@ pub mod styled_checkbox; pub mod styled_confirm_modal; pub mod styled_editor; pub mod styled_field; -pub mod styled_file_input; pub mod styled_form; pub mod styled_icon; +pub mod styled_image_input; pub mod styled_input; pub mod styled_link; pub mod styled_modal; diff --git a/jirs-client/src/shared/styled_image_input.rs b/jirs-client/src/shared/styled_image_input.rs new file mode 100644 index 00000000..7eb14d6c --- /dev/null +++ b/jirs-client/src/shared/styled_image_input.rs @@ -0,0 +1,127 @@ +use seed::{prelude::*, *}; +use web_sys::File; + +use crate::shared::ToNode; +use crate::{FieldId, Msg}; + +#[derive(Debug, Clone)] +pub struct StyledImageInputState { + field_id: FieldId, + pub file: Option, + pub url: Option, +} + +impl StyledImageInputState { + pub fn new(field_id: FieldId, url: Option) -> Self { + Self { + field_id, + file: None, + url, + } + } + + pub fn update(&mut self, msg: &Msg) { + match msg { + Msg::FileInputChanged(field_id, files) if field_id == &self.field_id => { + self.file = files.get(0).cloned(); + } + _ => return, + } + } +} + +pub struct StyledImageInput { + id: FieldId, + class_list: Vec, + url: Option, +} + +impl StyledImageInput { + pub fn build(field_id: FieldId) -> StyledInputInputBuilder { + StyledInputInputBuilder { + id: field_id, + class_list: vec![], + url: None, + } + } +} + +impl ToNode for StyledImageInput { + fn into_node(self) -> Node { + render(self) + } +} + +pub struct StyledInputInputBuilder { + id: FieldId, + class_list: Vec, + url: Option, +} + +impl StyledInputInputBuilder { + pub fn add_class(mut self, name: S) -> Self + where + S: Into, + { + self.class_list.push(name.into()); + self + } + + pub fn state(mut self, state: &StyledImageInputState) -> Self { + self.url = state.url.as_ref().cloned(); + self + } + + pub fn build(self) -> StyledImageInput { + StyledImageInput { + id: self.id, + class_list: self.class_list, + url: self.url, + } + } +} + +fn render(values: StyledImageInput) -> Node { + let StyledImageInput { + id, + class_list, + url, + } = values; + + let field_id = id.clone(); + let on_change = ev(Ev::Change, move |ev| { + let target = ev.target().unwrap(); + let input = seed::to_input(&target); + let v = input + .files() + .map(|list| { + let mut v = vec![]; + for i in 0..list.length() { + v.push(list.get(i).unwrap()); + } + v + }) + .unwrap_or_default(); + Msg::FileInputChanged(field_id, v) + }); + let input_id = id.to_string(); + + div![ + class!["styledImageInput"], + attrs![At::Class => class_list.join(" ")], + label![ + class!["label"], + attrs![At::For => input_id], + img![ + class!["mask"], + attrs![At::Src => url.unwrap_or_default()], + " " + ] + ], + input![ + class!["input"], + attrs![At::Type => "file", At::Id => input_id], + on_change + ] + ] +} diff --git a/jirs-client/src/sign_in.rs b/jirs-client/src/sign_in.rs index e3eaedb6..2ce6b209 100644 --- a/jirs-client/src/sign_in.rs +++ b/jirs-client/src/sign_in.rs @@ -22,10 +22,13 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) return; } - if msg == Msg::ChangePage(Page::SignIn) { - model.page_content = PageContent::SignIn(Box::new(SignInPage::default())); - return; - } + match msg { + Msg::ChangePage(Page::SignIn) => { + model.page_content = PageContent::SignIn(Box::new(SignInPage::default())); + return; + } + _ => (), + }; let page = match &mut model.page_content { PageContent::SignIn(page) => page, diff --git a/jirs-client/src/sign_up.rs b/jirs-client/src/sign_up.rs index 53b9d0ca..d3c06067 100644 --- a/jirs-client/src/sign_up.rs +++ b/jirs-client/src/sign_up.rs @@ -19,10 +19,13 @@ pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders return; } - if msg == Msg::ChangePage(Page::SignUp) { - model.page_content = PageContent::SignUp(Box::new(SignUpPage::default())); - return; - } + match msg { + Msg::ChangePage(Page::SignUp) => { + model.page_content = PageContent::SignUp(Box::new(SignUpPage::default())); + return; + } + _ => (), + }; let page = match &mut model.page_content { PageContent::SignUp(page) => page, diff --git a/jirs-data/src/lib.rs b/jirs-data/src/lib.rs index 909a77ed..626be7b3 100644 --- a/jirs-data/src/lib.rs +++ b/jirs-data/src/lib.rs @@ -673,6 +673,7 @@ pub enum UsersFieldId { Username, Email, UserRole, + Avatar, } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] diff --git a/jirs-server/Cargo.toml b/jirs-server/Cargo.toml index 728d1e6d..0e2022d9 100644 --- a/jirs-server/Cargo.toml +++ b/jirs-server/Cargo.toml @@ -21,6 +21,7 @@ actix-service = { version = "*" } actix-rt = "1" actix-web-actors = "*" actix-multipart = { version = "*" } +actix-files = { version = "0.2.1" } dotenv = { version = "*" } byteorder = "1.0" @@ -29,7 +30,7 @@ libc = { version = "0.2.0" } pq-sys = { version = ">=0.3.0, <0.5.0" } quickcheck = { version = "0.4" } serde_json = { version = ">=0.8.0, <2.0" } -toml = "0.5" +toml = "0.5.6" bincode = "1.2.1" time = { version = "0.1" } url = { version = "2.1.0" } @@ -49,6 +50,10 @@ futures = { version = "*" } lettre = { version = "*" } lettre_email = { version = "*" } +# Amazon S3 +rusoto_s3 = "0.43.0" +rusoto_core = "0.43.0" + [dependencies.diesel] version = "1.4.4" features = [ "unstable", "postgres", "numeric", "extras", "uuidv07" ] diff --git a/jirs-server/src/main.rs b/jirs-server/src/main.rs index bacd87c8..63af1af8 100644 --- a/jirs-server/src/main.rs +++ b/jirs-server/src/main.rs @@ -7,6 +7,7 @@ extern crate log; use actix::Actor; use actix_cors::Cors; +use actix_files as fs; use actix_web::{App, HttpServer}; use crate::ws::WsServer; @@ -40,7 +41,8 @@ async fn main() -> Result<(), String> { let ws_server = WsServer::default().start(); HttpServer::new(move || { - App::new() + let web_config = web::Configuration::read(); + let mut app = App::new() .wrap(actix_web::middleware::Logger::default()) .wrap(Cors::default()) .data(ws_server.clone()) @@ -48,6 +50,14 @@ async fn main() -> Result<(), String> { .data(mail_addr.clone()) .data(crate::db::build_pool()) .service(crate::ws::index) + .service(actix_web::web::scope("/avatar").service(crate::web::avatar::upload)); + if let Some(file_system) = web_config.filesystem.as_ref() { + app = app.service(fs::Files::new( + file_system.client_path.as_str(), + file_system.store_path.as_str(), + )); + } + app }) .workers(web_config.concurrency) .bind(web_config.addr()) diff --git a/jirs-server/src/web/avatar.rs b/jirs-server/src/web/avatar.rs index cbb4a259..a2a989ed 100644 --- a/jirs-server/src/web/avatar.rs +++ b/jirs-server/src/web/avatar.rs @@ -1,28 +1,67 @@ -use actix_multipart::Multipart; -use actix_web::{get, post, web, Error, HttpResponse, Responder}; -use futures::{StreamExt, TryStreamExt}; use std::io::Write; +use actix::Addr; +use actix_multipart::{Field, Multipart}; +use actix_web::http::header::ContentDisposition; +use actix_web::web::Data; +use actix_web::{get, post, web, Error, HttpResponse, Responder}; +use futures::{StreamExt, TryStreamExt}; + +use crate::db::DbExecutor; + #[post("/")] -async fn upload(mut payload: Multipart) -> Result { - while let Ok(Some(mut field)) = payload.try_next().await { - let content_type = field.content_disposition().unwrap(); - let filename = content_type.get_filename().unwrap(); - let filepath = format!("./tmp/{}", filename); - // File::create is blocking operation, use threadpool - let mut f = web::block(|| std::fs::File::create(filepath)) - .await - .unwrap(); - // Field in turn is stream of *Bytes* object - while let Some(chunk) = field.next().await { - let data = chunk.unwrap(); - // filesystem operations are blocking, we have to use threadpool - f = web::block(move || f.write_all(&data).map(|_| f)).await?; +pub async fn upload( + mut payload: Multipart, + db: Data>, +) -> Result { + while let Ok(Some(field)) = payload.try_next().await { + let disposition: ContentDisposition = match field.content_disposition() { + Some(d) => d, + _ => continue, + }; + if !disposition.is_form_data() { + return Ok(HttpResponse::BadRequest().finish()); } + let _name = disposition.get_name().as_ref().cloned().unwrap_or_default(); + match disposition.get_name() { + Some("token") => handle_token(field, disposition, db.clone()).await?, + Some("avatar") => handle_image(field, disposition, db.clone()).await?, + _ => continue, + }; } Ok(HttpResponse::Ok().json("")) } +async fn handle_token( + mut field: Field, + _disposition: ContentDisposition, + _db: Data>, +) -> Result<(), Error> { + Ok(()) +} + +async fn handle_image( + mut field: Field, + disposition: ContentDisposition, + _db: Data>, +) -> Result<(), Error> { + let web_config = crate::web::Configuration::read(); + + let filename = disposition.get_filename().unwrap(); + let filepath = format!("./tmp/{}", filename); + // File::create is blocking operation, use threadpool + let mut f = web::block(|| std::fs::File::create(filepath)) + .await + .unwrap(); + // Field in turn is stream of *Bytes* object + while let Some(chunk) = field.next().await { + let data = chunk.unwrap(); + // filesystem operations are blocking, we have to use thread pool + f = web::block(move || f.write_all(&data).map(|_| f)).await?; + } + Ok(()) +} + #[get("/{id}")] async fn download(_id: web::Path) -> impl Responder { HttpResponse::Ok().json("") diff --git a/jirs-server/src/web/mod.rs b/jirs-server/src/web/mod.rs index bcabf619..1f9dcd2b 100644 --- a/jirs-server/src/web/mod.rs +++ b/jirs-server/src/web/mod.rs @@ -40,12 +40,35 @@ pub enum Protocol { Https, } +#[derive(Serialize, Deserialize)] +pub struct FileSystemStorage { + pub store_path: String, + pub client_path: String, +} + +#[derive(Serialize, Deserialize)] +pub struct AmazonS3Storage { + pub access_key_id: String, + pub secret_access_key: String, + pub bucket: String, + pub region: String, +} + +#[derive(Serialize, Deserialize)] +pub enum Storage { + FileSystem, + AmazonS3, +} + #[derive(Serialize, Deserialize)] pub struct Configuration { pub concurrency: usize, pub port: String, pub bind: String, pub ssl: bool, + pub storage: Storage, + pub filesystem: Option, + pub s3: Option, } impl Default for Configuration { @@ -55,6 +78,12 @@ impl Default for Configuration { port: "5000".to_string(), bind: "0.0.0.0".to_string(), ssl: false, + storage: Storage::FileSystem, + filesystem: Some(FileSystemStorage { + store_path: "./tmp".to_string(), + client_path: "/img".to_string(), + }), + s3: None, } } } diff --git a/jirs-client/src/shared/styled_file_input.rs b/jirs-server/tmp/.gitkeep similarity index 100% rename from jirs-client/src/shared/styled_file_input.rs rename to jirs-server/tmp/.gitkeep diff --git a/tmp/.gitkeep b/tmp/.gitkeep new file mode 100644 index 00000000..e69de29b