From 2f870a5a0a4bdd573a5e7488b6293e747668d544 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?=
Date: Fri, 17 Apr 2020 14:10:05 +0200
Subject: [PATCH] Reduce update issue payload. Create config files
---
.env | 6 +-
.gitignore | 3 +
Cargo.lock | 468 ++++++++++++++++++++++++-
jirs-client/src/lib.rs | 123 ++++---
jirs-client/src/modal/add_issue.rs | 58 +--
jirs-client/src/modal/issue_details.rs | 403 ++++++++++++---------
jirs-client/src/modal/time_tracking.rs | 36 +-
jirs-client/src/model.rs | 30 +-
jirs-client/src/project.rs | 4 +-
jirs-client/src/project_settings.rs | 44 ++-
jirs-client/src/ws/issue.rs | 27 +-
jirs-data/src/lib.rs | 51 ++-
jirs-server/Cargo.toml | 3 +
jirs-server/src/db/authorize_user.rs | 2 +-
jirs-server/src/db/comments.rs | 8 +-
jirs-server/src/db/issue_assignees.rs | 2 +-
jirs-server/src/db/issues.rs | 12 +-
jirs-server/src/db/mod.rs | 52 ++-
jirs-server/src/db/projects.rs | 4 +-
jirs-server/src/db/tokens.rs | 4 +-
jirs-server/src/db/users.rs | 13 +-
jirs-server/src/mail/mod.rs | 92 +++++
jirs-server/src/mail/welcome.rs | 60 ++++
jirs-server/src/main.rs | 29 +-
jirs-server/src/routes/comments.rs | 77 ----
jirs-server/src/routes/mod.rs | 32 --
jirs-server/src/routes/users.rs | 23 --
jirs-server/src/web/mod.rs | 79 +++++
jirs-server/src/ws/auth.rs | 39 ++-
jirs-server/src/ws/issues.rs | 65 ++--
jirs-server/src/ws/mod.rs | 132 +++----
31 files changed, 1375 insertions(+), 606 deletions(-)
create mode 100644 jirs-server/src/mail/mod.rs
create mode 100644 jirs-server/src/mail/welcome.rs
delete mode 100644 jirs-server/src/routes/comments.rs
delete mode 100644 jirs-server/src/routes/mod.rs
delete mode 100644 jirs-server/src/routes/users.rs
create mode 100644 jirs-server/src/web/mod.rs
diff --git a/.env b/.env
index ea5f638f..69a1c147 100644
--- a/.env
+++ b/.env
@@ -1,8 +1,8 @@
-DATABASE_URL=postgres://postgres@localhost:5432/jirs
+DEBUG=true
+NODE_ENV=development
RUST_LOG=debug
JIRS_CLIENT_PORT=7000
JIRS_CLIENT_BIND=0.0.0.0
+DATABASE_URL=postgres://postgres@localhost:5432/jirs
JIRS_SERVER_PORT=5000
JIRS_SERVER_BIND=0.0.0.0
-NODE_ENV=development
-DEBUG=true
diff --git a/.gitignore b/.gitignore
index ea8c4bf7..8c6cbac2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
/target
+mail.toml
+web.toml
+db.toml
diff --git a/Cargo.lock b/Cargo.lock
index e0503ca4..929ff717 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -83,7 +83,7 @@ dependencies = [
"actix-service",
"actix-threadpool",
"actix-utils",
- "base64",
+ "base64 0.11.0",
"bitflags",
"brotli2",
"bytes",
@@ -359,6 +359,12 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
+[[package]]
+name = "ascii_utils"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
+
[[package]]
name = "async-trait"
version = "0.1.26"
@@ -381,6 +387,12 @@ dependencies = [
"winapi 0.3.8",
]
+[[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"
@@ -397,7 +409,7 @@ dependencies = [
"actix-http",
"actix-rt",
"actix-service",
- "base64",
+ "base64 0.11.0",
"bytes",
"derive_more",
"futures-core",
@@ -432,6 +444,25 @@ dependencies = [
"libc",
]
+[[package]]
+name = "base64"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
+dependencies = [
+ "byteorder",
+ "safemem",
+]
+
+[[package]]
+name = "base64"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
+dependencies = [
+ "byteorder",
+]
+
[[package]]
name = "base64"
version = "0.11.0"
@@ -506,6 +537,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "bufstream"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
+
[[package]]
name = "bumpalo"
version = "3.2.1"
@@ -630,6 +667,22 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127"
+[[package]]
+name = "core-foundation"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
+
[[package]]
name = "crc32fast"
version = "1.2.0"
@@ -655,7 +708,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",
]
@@ -742,12 +795,91 @@ version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
+[[package]]
+name = "email"
+version = "0.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4"
+dependencies = [
+ "base64 0.9.3",
+ "chrono",
+ "encoding",
+ "lazy_static",
+ "rand 0.4.6",
+ "time",
+ "version_check 0.1.5",
+]
+
[[package]]
name = "enclose"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357"
+[[package]]
+name = "encoding"
+version = "0.2.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
+dependencies = [
+ "encoding-index-japanese",
+ "encoding-index-korean",
+ "encoding-index-simpchinese",
+ "encoding-index-singlebyte",
+ "encoding-index-tradchinese",
+]
+
+[[package]]
+name = "encoding-index-japanese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-korean"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-simpchinese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-singlebyte"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-tradchinese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding_index_tests"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
+
[[package]]
name = "encoding_rs"
version = "0.8.22"
@@ -826,6 +958,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+[[package]]
+name = "fast_chemail"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
+dependencies = [
+ "ascii_utils",
+]
+
[[package]]
name = "flate2"
version = "1.0.14"
@@ -844,6 +985,21 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@@ -1049,6 +1205,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "hostname"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
+dependencies = [
+ "libc",
+ "winutil",
+]
+
[[package]]
name = "hostname"
version = "0.3.1"
@@ -1114,7 +1280,7 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
]
[[package]]
@@ -1207,6 +1373,8 @@ dependencies = [
"futures 0.3.4",
"ipnetwork",
"jirs-data",
+ "lettre",
+ "lettre_email",
"libc",
"log 0.4.8",
"num-bigint",
@@ -1220,6 +1388,7 @@ dependencies = [
"serde",
"serde_json",
"time",
+ "toml",
"url 2.1.1",
"uuid 0.8.1",
]
@@ -1255,6 +1424,38 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+[[package]]
+name = "lettre"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66afaa5dfadbb81d4e00fd1d1ab057c7cd4c799c5a44e0009386d553587e728"
+dependencies = [
+ "base64 0.10.1",
+ "bufstream",
+ "fast_chemail",
+ "hostname 0.1.5",
+ "log 0.4.8",
+ "native-tls",
+ "nom",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "lettre_email"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb68ca999042d965476e47bbdbacd52db0927348b6f8062c44dd04a3b1fd43b"
+dependencies = [
+ "base64 0.10.1",
+ "email",
+ "lettre",
+ "mime",
+ "time",
+ "uuid 0.7.4",
+]
+
[[package]]
name = "libc"
version = "0.2.68"
@@ -1399,6 +1600,24 @@ dependencies = [
"ws2_32-sys",
]
+[[package]]
+name = "native-tls"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log 0.4.8",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
[[package]]
name = "net2"
version = "0.2.33"
@@ -1410,13 +1629,23 @@ dependencies = [
"winapi 0.3.8",
]
+[[package]]
+name = "nom"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
+dependencies = [
+ "memchr 2.3.3",
+ "version_check 0.1.5",
+]
+
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
"num-integer",
"num-traits",
]
@@ -1427,7 +1656,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",
]
@@ -1437,7 +1666,7 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
dependencies = [
- "autocfg",
+ "autocfg 1.0.0",
]
[[package]]
@@ -1456,6 +1685,39 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+[[package]]
+name = "openssl"
+version = "0.10.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "lazy_static",
+ "libc",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7717097d810a0f2e2323f9e5d11e71608355e24828410b55b9d4f18aa5f9a5d8"
+dependencies = [
+ "autocfg 1.0.0",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
[[package]]
name = "parking_lot"
version = "0.10.0"
@@ -1567,6 +1829,12 @@ version = "0.1.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
+[[package]]
+name = "pkg-config"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+
[[package]]
name = "ppv-lite86"
version = "0.2.6"
@@ -1685,6 +1953,25 @@ dependencies = [
"winapi 0.3.8",
]
+[[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"
@@ -1693,9 +1980,19 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
- "rand_chacha",
+ "rand_chacha 0.2.2",
"rand_core 0.5.1",
- "rand_hc",
+ "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]]
@@ -1732,6 +2029,15 @@ 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"
@@ -1741,6 +2047,59 @@ 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 = "rdrand"
version = "0.4.0"
@@ -1793,13 +2152,22 @@ version = "0.6.17"
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 = "resolv-conf"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a"
dependencies = [
- "hostname",
+ "hostname 0.3.1",
"quick-error",
]
@@ -1815,6 +2183,22 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76"
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "schannel"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19"
+dependencies = [
+ "lazy_static",
+ "winapi 0.3.8",
+]
+
[[package]]
name = "scheduled-thread-pool"
version = "0.2.4"
@@ -1830,6 +2214,29 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+[[package]]
+name = "security-framework"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "seed"
version = "0.6.0"
@@ -1977,6 +2384,20 @@ dependencies = [
"unicode-xid",
]
+[[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 = "termcolor"
version = "1.1.0"
@@ -2078,6 +2499,15 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "toml"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "trust-dns-proto"
version = "0.18.0-alpha.2"
@@ -2239,6 +2669,15 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "uuid"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
+dependencies = [
+ "rand 0.6.5",
+]
+
[[package]]
name = "uuid"
version = "0.8.1"
@@ -2416,6 +2855,15 @@ dependencies = [
"winapi 0.3.8",
]
+[[package]]
+name = "winutil"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e"
+dependencies = [
+ "winapi 0.3.8",
+]
+
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
diff --git a/jirs-client/src/lib.rs b/jirs-client/src/lib.rs
index 4244e7d0..a9c81653 100644
--- a/jirs-client/src/lib.rs
+++ b/jirs-client/src/lib.rs
@@ -23,90 +23,85 @@ pub type AvatarFilterActive = bool;
pub type AppType = App>;
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
-pub enum EditIssueModalFieldId {
- IssueType,
- Title,
- Description,
- Status,
- Assignees,
- Reporter,
- Priority,
- Estimate,
- TimeSpend,
- TimeRemaining,
- // comment
- CommentBody,
-}
-
-#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
-pub enum AddIssueModalFieldId {
- IssueType,
- Summary,
- Description,
- Reporter,
- Assignees,
- Priority,
-}
-
-#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
-pub enum ProjectSettingsFieldId {
- Name,
- Url,
- Description,
- Category,
-}
-
-#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
-pub enum LoginFieldId {
- Username,
- Email,
- Token,
+pub enum EditIssueModalSection {
+ Issue(IssueFieldId),
+ Comment(CommentFieldId),
}
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
pub enum FieldId {
Login(LoginFieldId),
// issue
- AddIssueModal(AddIssueModalFieldId),
- EditIssueModal(EditIssueModalFieldId),
+ AddIssueModal(IssueFieldId),
+ EditIssueModal(EditIssueModalSection),
// project boards
TextFilterBoard,
CopyButtonLabel,
- ProjectSettings(ProjectSettingsFieldId),
+ ProjectSettings(ProjectFieldId),
}
impl std::fmt::Display for FieldId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FieldId::EditIssueModal(sub) => match sub {
- EditIssueModalFieldId::IssueType => f.write_str("issueTypeEditModalTop"),
- EditIssueModalFieldId::Title => f.write_str("titleIssueEditModal"),
- EditIssueModalFieldId::Description => f.write_str("descriptionIssueEditModal"),
- EditIssueModalFieldId::Status => f.write_str("statusIssueEditModal"),
- EditIssueModalFieldId::Assignees => f.write_str("assigneesIssueEditModal"),
- EditIssueModalFieldId::Reporter => f.write_str("reporterIssueEditModal"),
- EditIssueModalFieldId::Priority => f.write_str("priorityIssueEditModal"),
- EditIssueModalFieldId::Estimate => f.write_str("estimateIssueEditModal"),
- EditIssueModalFieldId::TimeSpend => f.write_str("timeSpendIssueEditModal"),
- EditIssueModalFieldId::TimeRemaining => f.write_str("timeRemainingIssueEditModal"),
- EditIssueModalFieldId::CommentBody => f.write_str("editIssue-commentBody"),
+ EditIssueModalSection::Issue(IssueFieldId::Type) => {
+ f.write_str("issueTypeEditModalTop")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::Title) => {
+ f.write_str("titleIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::Description) => {
+ f.write_str("descriptionIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::Status) => {
+ f.write_str("statusIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::Assignees) => {
+ f.write_str("assigneesIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::Reporter) => {
+ f.write_str("reporterIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::Priority) => {
+ f.write_str("priorityIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::Estimate) => {
+ f.write_str("estimateIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::TimeSpend) => {
+ f.write_str("timeSpendIssueEditModal")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::TimeRemaining) => {
+ f.write_str("timeRemainingIssueEditModal")
+ }
+ EditIssueModalSection::Comment(CommentFieldId::Body) => {
+ f.write_str("editIssue-commentBody")
+ }
+ EditIssueModalSection::Issue(IssueFieldId::ListPosition) => {
+ f.write_str("editIssue-listPosition")
+ }
},
FieldId::AddIssueModal(sub) => match sub {
- AddIssueModalFieldId::IssueType => f.write_str("issueTypeAddIssueModal"),
- AddIssueModalFieldId::Summary => f.write_str("summaryAddIssueModal"),
- AddIssueModalFieldId::Description => f.write_str("descriptionAddIssueModal"),
- AddIssueModalFieldId::Reporter => f.write_str("reporterAddIssueModal"),
- AddIssueModalFieldId::Assignees => f.write_str("assigneesAddIssueModal"),
- AddIssueModalFieldId::Priority => f.write_str("issuePriorityAddIssueModal"),
+ IssueFieldId::Type => f.write_str("issueTypeAddIssueModal"),
+ IssueFieldId::Title => f.write_str("summaryAddIssueModal"),
+ IssueFieldId::Description => f.write_str("descriptionAddIssueModal"),
+ IssueFieldId::Reporter => f.write_str("reporterAddIssueModal"),
+ IssueFieldId::Assignees => f.write_str("assigneesAddIssueModal"),
+ IssueFieldId::Priority => f.write_str("issuePriorityAddIssueModal"),
+ IssueFieldId::Status => f.write_str("addIssueModal-status"),
+ IssueFieldId::Estimate => f.write_str("addIssueModal-estimate"),
+ IssueFieldId::TimeSpend => f.write_str("addIssueModal-timeSpend"),
+ IssueFieldId::TimeRemaining => f.write_str("addIssueModal-timeRemaining"),
+ IssueFieldId::ListPosition => f.write_str("addIssueModal-listPosition"),
},
FieldId::TextFilterBoard => f.write_str("textFilterBoard"),
FieldId::CopyButtonLabel => f.write_str("copyButtonLabel"),
FieldId::ProjectSettings(sub) => match sub {
- ProjectSettingsFieldId::Name => f.write_str("projectSettings-name"),
- ProjectSettingsFieldId::Url => f.write_str("projectSettings-url"),
- ProjectSettingsFieldId::Description => f.write_str("projectSettings-description"),
- ProjectSettingsFieldId::Category => f.write_str("projectSettings-category"),
+ ProjectFieldId::Name => f.write_str("projectSettings-name"),
+ ProjectFieldId::Url => f.write_str("projectSettings-url"),
+ ProjectFieldId::Description => f.write_str("projectSettings-description"),
+ ProjectFieldId::Category => f.write_str("projectSettings-category"),
},
FieldId::Login(sub) => match sub {
LoginFieldId::Email => f.write_str("login-email"),
@@ -332,8 +327,8 @@ pub fn render() {
#[inline]
fn authorize_or_redirect() {
match crate::shared::read_auth_token() {
- Ok(uuid) => {
- send_ws_msg(WsMsg::AuthorizeRequest(uuid));
+ Ok(token) => {
+ send_ws_msg(WsMsg::AuthorizeRequest(token));
}
Err(..) => {
let pathname = seed::document().location().unwrap().pathname().unwrap();
diff --git a/jirs-client/src/modal/add_issue.rs b/jirs-client/src/modal/add_issue.rs
index 2e620f80..aac5bc82 100644
--- a/jirs-client/src/modal/add_issue.rs
+++ b/jirs-client/src/modal/add_issue.rs
@@ -1,5 +1,6 @@
use seed::{prelude::*, *};
+use jirs_data::IssueFieldId;
use jirs_data::{IssuePriority, IssueType, ToVec};
use crate::api::send_ws_msg;
@@ -14,7 +15,7 @@ use crate::shared::styled_select::StyledSelectChange;
use crate::shared::styled_select_child::ToStyledSelectChild;
use crate::shared::styled_textarea::StyledTextarea;
use crate::shared::ToNode;
-use crate::{AddIssueModalFieldId, FieldId, Msg};
+use crate::{FieldId, Msg};
pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders) {
let modal = model.modals.iter_mut().find(|modal| match modal {
@@ -57,16 +58,16 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
orders.skip().send_msg(Msg::ModalDropped);
}
- Msg::InputChanged(FieldId::AddIssueModal(AddIssueModalFieldId::Description), value) => {
+ Msg::InputChanged(FieldId::AddIssueModal(IssueFieldId::Description), value) => {
modal.description = Some(value.clone());
}
- Msg::InputChanged(FieldId::AddIssueModal(AddIssueModalFieldId::Summary), value) => {
+ Msg::InputChanged(FieldId::AddIssueModal(IssueFieldId::Title), value) => {
modal.title = value.clone();
}
// IssueTypeAddIssueModal
Msg::StyledSelectChanged(
- FieldId::AddIssueModal(AddIssueModalFieldId::IssueType),
+ FieldId::AddIssueModal(IssueFieldId::Type),
StyledSelectChange::Changed(id),
) => {
modal.issue_type = (*id).into();
@@ -74,7 +75,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
// ReporterAddIssueModal
Msg::StyledSelectChanged(
- FieldId::AddIssueModal(AddIssueModalFieldId::Reporter),
+ FieldId::AddIssueModal(IssueFieldId::Reporter),
StyledSelectChange::Changed(id),
) => {
modal.reporter_id = Some(*id as i32);
@@ -82,7 +83,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
// AssigneesAddIssueModal
Msg::StyledSelectChanged(
- FieldId::AddIssueModal(AddIssueModalFieldId::Assignees),
+ FieldId::AddIssueModal(IssueFieldId::Assignees),
StyledSelectChange::Changed(id),
) => {
let id = *id as i32;
@@ -91,7 +92,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
}
}
Msg::StyledSelectChanged(
- FieldId::AddIssueModal(AddIssueModalFieldId::Assignees),
+ FieldId::AddIssueModal(IssueFieldId::Assignees),
StyledSelectChange::RemoveMulti(id),
) => {
let id = *id as i32;
@@ -106,7 +107,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
// IssuePriorityAddIssueModal
Msg::StyledSelectChanged(
- FieldId::AddIssueModal(AddIssueModalFieldId::Priority),
+ FieldId::AddIssueModal(IssueFieldId::Priority),
StyledSelectChange::Changed(id),
) => {
modal.priority = (*id).into();
@@ -117,7 +118,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
}
pub fn view(model: &Model, modal: &AddIssueModal) -> Node {
- let select_type = StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::IssueType))
+ let select_type = StyledSelect::build(FieldId::AddIssueModal(IssueFieldId::Type))
.name("type")
.normal()
.text_filter(modal.type_state.text_filter.as_str())
@@ -139,7 +140,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node {
.build()
.into_node();
- let short_summary = StyledInput::build(FieldId::AddIssueModal(AddIssueModalFieldId::Summary))
+ let short_summary = StyledInput::build(FieldId::AddIssueModal(IssueFieldId::Title))
.valid(true)
.build()
.into_node();
@@ -152,7 +153,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node {
let description = StyledTextarea::build()
.height(110)
- .build(FieldId::AddIssueModal(AddIssueModalFieldId::Description))
+ .build(FieldId::AddIssueModal(IssueFieldId::Description))
.into_node();
let description_field = StyledField::build()
.label("Description")
@@ -165,7 +166,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node {
.reporter_id
.or_else(|| model.user.as_ref().map(|u| u.id))
.unwrap_or_default();
- let reporter = StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::Reporter))
+ let reporter = StyledSelect::build(FieldId::AddIssueModal(IssueFieldId::Reporter))
.normal()
.text_filter(modal.reporter_state.text_filter.as_str())
.opened(modal.reporter_state.opened)
@@ -199,7 +200,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node {
.build()
.into_node();
- let assignees = StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::Assignees))
+ let assignees = StyledSelect::build(FieldId::AddIssueModal(IssueFieldId::Assignees))
.normal()
.multi()
.text_filter(modal.assignees_state.text_filter.as_str())
@@ -234,22 +235,21 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node {
.build()
.into_node();
- let select_priority =
- StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::Priority))
- .name("priority")
- .normal()
- .text_filter(modal.priority_state.text_filter.as_str())
- .opened(modal.priority_state.opened)
- .valid(true)
- .options(
- IssuePriority::ordered()
- .iter()
- .map(|p| p.to_select_child().name("priority"))
- .collect(),
- )
- .selected(vec![modal.priority.to_select_child().name("priority")])
- .build()
- .into_node();
+ let select_priority = StyledSelect::build(FieldId::AddIssueModal(IssueFieldId::Priority))
+ .name("priority")
+ .normal()
+ .text_filter(modal.priority_state.text_filter.as_str())
+ .opened(modal.priority_state.opened)
+ .valid(true)
+ .options(
+ IssuePriority::ordered()
+ .iter()
+ .map(|p| p.to_select_child().name("priority"))
+ .collect(),
+ )
+ .selected(vec![modal.priority.to_select_child().name("priority")])
+ .build()
+ .into_node();
let issue_priority_field = StyledField::build()
.label("Issue Type")
.tip("Priority in relation to other issues.")
diff --git a/jirs-client/src/modal/issue_details.rs b/jirs-client/src/modal/issue_details.rs
index 3765ba87..7c1078a0 100644
--- a/jirs-client/src/modal/issue_details.rs
+++ b/jirs-client/src/modal/issue_details.rs
@@ -15,7 +15,7 @@ use crate::shared::styled_select_child::ToStyledSelectChild;
use crate::shared::styled_textarea::StyledTextarea;
use crate::shared::tracking_widget::tracking_link;
use crate::shared::ToNode;
-use crate::{EditIssueModalFieldId, FieldChange, FieldId, Msg};
+use crate::{EditIssueModalSection, FieldChange, FieldId, Msg};
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) {
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
@@ -33,35 +33,51 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) {
modal.payload = issue.clone().into();
}
Msg::StyledSelectChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::IssueType),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
StyledSelectChange::Changed(value),
) => {
modal.payload.issue_type = (*value).into();
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Type,
+ PayloadVariant::IssueType(modal.payload.issue_type.clone()),
+ ));
}
Msg::StyledSelectChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::Status),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Status)),
StyledSelectChange::Changed(value),
) => {
modal.payload.status = (*value).into();
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Status,
+ PayloadVariant::IssueStatus(modal.payload.status.clone()),
+ ));
}
Msg::StyledSelectChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::Reporter),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Reporter)),
StyledSelectChange::Changed(value),
) => {
modal.payload.reporter_id = *value as i32;
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Reporter,
+ PayloadVariant::I32(modal.payload.reporter_id),
+ ));
}
Msg::StyledSelectChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::Assignees),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)),
StyledSelectChange::Changed(value),
) => {
modal.payload.user_ids.push(*value as i32);
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Assignees,
+ PayloadVariant::VecI32(modal.payload.user_ids.clone()),
+ ));
}
Msg::StyledSelectChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::Assignees),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)),
StyledSelectChange::RemoveMulti(value),
) => {
let mut old = vec![];
@@ -72,40 +88,83 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) {
modal.payload.user_ids.push(id);
}
}
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Assignees,
+ PayloadVariant::VecI32(modal.payload.user_ids.clone()),
+ ));
}
Msg::StyledSelectChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::Priority),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)),
StyledSelectChange::Changed(value),
) => {
modal.payload.priority = (*value).into();
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Priority,
+ PayloadVariant::IssuePriority(modal.payload.priority),
+ ));
}
- Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::Title), value) => {
+ Msg::InputChanged(
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Title)),
+ value,
+ ) => {
modal.payload.title = value.clone();
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Title,
+ PayloadVariant::String(modal.payload.title.clone()),
+ ));
}
- Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::Description), value) => {
+ Msg::InputChanged(
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
+ value,
+ ) => {
modal.payload.description = Some(value.clone());
modal.payload.description_text = Some(value.clone());
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::Description,
+ PayloadVariant::String(
+ modal
+ .payload
+ .description
+ .as_ref()
+ .cloned()
+ .unwrap_or_default(),
+ ),
+ ));
}
- Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::TimeSpend), value) => {
+ Msg::InputChanged(
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeSpend)),
+ value,
+ ) => {
modal.payload.time_spent = value.parse::().ok();
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::TimeSpend,
+ PayloadVariant::OptionI32(modal.payload.time_spent.clone()),
+ ));
}
- Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::TimeRemaining), value) => {
+ Msg::InputChanged(
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeRemaining)),
+ value,
+ ) => {
modal.payload.time_remaining = value.parse::().ok();
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::TimeRemaining,
+ PayloadVariant::OptionI32(modal.payload.time_remaining.clone()),
+ ));
}
Msg::ModalChanged(FieldChange::TabChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::Description),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
mode,
)) => {
modal.description_editor_mode = mode.clone();
}
Msg::ModalChanged(FieldChange::ToggleCommentForm(
- FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
+ FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
flag,
)) => {
modal.comment_form.creating = *flag;
@@ -115,22 +174,34 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) {
}
}
// comments
- Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody), text) => {
+ Msg::InputChanged(
+ FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
+ text,
+ ) => {
modal.comment_form.body = text.clone();
}
- Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::Estimate), value) => {
- match value.parse::() {
- Ok(n) if !value.is_empty() => {
- modal.payload.estimate = Some(n);
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
- }
- _ if value.is_empty() => {
- modal.payload.estimate = None;
- send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
- }
- _ => {}
+ Msg::InputChanged(
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Estimate)),
+ value,
+ ) => match value.parse::() {
+ Ok(n) if !value.is_empty() => {
+ modal.payload.estimate = Some(n);
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::TimeRemaining,
+ PayloadVariant::OptionI32(modal.payload.estimate.clone()),
+ ));
}
- }
+ _ if value.is_empty() => {
+ modal.payload.estimate = None;
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ modal.id,
+ IssueFieldId::TimeRemaining,
+ PayloadVariant::OptionI32(modal.payload.estimate.clone()),
+ ));
+ }
+ _ => {}
+ },
Msg::SaveComment => {
let msg = match modal.comment_form.id {
Some(id) => WsMsg::UpdateComment(UpdateCommentPayload {
@@ -147,12 +218,12 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) {
orders
.skip()
.send_msg(Msg::ModalChanged(FieldChange::ToggleCommentForm(
- FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
+ FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
false,
)));
}
Msg::ModalChanged(FieldChange::EditComment(
- FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
+ FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
comment_id,
)) => {
let id = *comment_id;
@@ -176,7 +247,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) {
orders
.skip()
.send_msg(Msg::ModalChanged(FieldChange::ToggleCommentForm(
- FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
+ FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
true,
)));
}
@@ -256,29 +327,30 @@ fn top_modal_row(_model: &Model, modal: &EditIssueModal) -> Node {
.build()
.into_node();
- let issue_type_select =
- StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::IssueType))
- .dropdown_width(150)
+ let issue_type_select = StyledSelect::build(FieldId::EditIssueModal(
+ EditIssueModalSection::Issue(IssueFieldId::Type),
+ ))
+ .dropdown_width(150)
+ .name("type")
+ .text_filter(top_type_state.text_filter.as_str())
+ .opened(top_type_state.opened)
+ .valid(true)
+ .options(
+ IssueType::ordered()
+ .into_iter()
+ .map(|t| t.to_select_child().name("type"))
+ .collect(),
+ )
+ .selected(vec![{
+ let id = modal.id;
+ let issue_type = &payload.issue_type;
+ issue_type
+ .to_select_child()
.name("type")
- .text_filter(top_type_state.text_filter.as_str())
- .opened(top_type_state.opened)
- .valid(true)
- .options(
- IssueType::ordered()
- .into_iter()
- .map(|t| t.to_select_child().name("type"))
- .collect(),
- )
- .selected(vec![{
- let id = modal.id;
- let issue_type = &payload.issue_type;
- issue_type
- .to_select_child()
- .name("type")
- .text(format!("{} - {}", issue_type, id))
- }])
- .build()
- .into_node();
+ .text(format!("{} - {}", issue_type, id))
+ }])
+ .build()
+ .into_node();
div![
attrs![At::Class => "topActions"],
@@ -305,17 +377,20 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node {
.add_class("textarea")
.max_height(48)
.height(0)
- .build(FieldId::EditIssueModal(EditIssueModalFieldId::Title))
+ .build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::Title,
+ )))
.into_node();
let description_text = payload.description.as_ref().cloned().unwrap_or_default();
- let description =
- StyledEditor::build(FieldId::EditIssueModal(EditIssueModalFieldId::Description))
- .text(description_text)
- .mode(description_editor_mode.clone())
- .update_on(Ev::Change)
- .build()
- .into_node();
+ let description = StyledEditor::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::Description,
+ )))
+ .text(description_text)
+ .mode(description_editor_mode.clone())
+ .update_on(Ev::Change)
+ .build()
+ .into_node();
let description_field = StyledField::build().input(description).build().into_node();
let user_avatar = StyledAvatar::build()
@@ -338,7 +413,7 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node {
let handler = mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
Msg::ModalChanged(FieldChange::ToggleCommentForm(
- FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
+ FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
!creating_comment,
))
});
@@ -388,7 +463,7 @@ fn build_comment_form(form: &CommentForm) -> Vec> {
let close_comment_form = mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
Msg::ModalChanged(FieldChange::ToggleCommentForm(
- FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
+ FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
false,
))
});
@@ -396,7 +471,9 @@ fn build_comment_form(form: &CommentForm) -> Vec> {
let text_area = StyledTextarea::build()
.value(form.body.as_str())
.placeholder("Add a comment...")
- .build(FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody))
+ .build(FieldId::EditIssueModal(EditIssueModalSection::Comment(
+ CommentFieldId::Body,
+ )))
.into_node();
let submit = StyledButton::build()
@@ -439,7 +516,7 @@ fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Option Node {
..
} = modal;
- let status = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Status))
- .name("status")
- .opened(status_state.opened)
- .normal()
- .text_filter(status_state.text_filter.as_str())
- .options(
- IssueStatus::ordered()
- .into_iter()
- .map(|opt| opt.to_select_child().name("status"))
- .collect(),
- )
- .selected(vec![payload.status.to_select_child().name("status")])
- .valid(true)
- .build()
- .into_node();
+ let status = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::Status,
+ )))
+ .name("status")
+ .opened(status_state.opened)
+ .normal()
+ .text_filter(status_state.text_filter.as_str())
+ .options(
+ IssueStatus::ordered()
+ .into_iter()
+ .map(|opt| opt.to_select_child().name("status"))
+ .collect(),
+ )
+ .selected(vec![payload.status.to_select_child().name("status")])
+ .valid(true)
+ .build()
+ .into_node();
let status_field = StyledField::build()
.input(status)
.label("Status")
.build()
.into_node();
- let assignees = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Assignees))
- .name("assignees")
- .opened(assignees_state.opened)
- .empty()
- .multi()
- .text_filter(assignees_state.text_filter.as_str())
- .options(
- model
- .users
- .iter()
- .map(|user| user.to_select_child().name("assignees"))
- .collect(),
- )
- .selected(
- model
- .users
- .iter()
- .filter(|user| payload.user_ids.contains(&user.id))
- .map(|user| user.to_select_child().name("assignees"))
- .collect(),
- )
- .build()
- .into_node();
+ let assignees = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::Assignees,
+ )))
+ .name("assignees")
+ .opened(assignees_state.opened)
+ .empty()
+ .multi()
+ .text_filter(assignees_state.text_filter.as_str())
+ .options(
+ model
+ .users
+ .iter()
+ .map(|user| user.to_select_child().name("assignees"))
+ .collect(),
+ )
+ .selected(
+ model
+ .users
+ .iter()
+ .filter(|user| payload.user_ids.contains(&user.id))
+ .map(|user| user.to_select_child().name("assignees"))
+ .collect(),
+ )
+ .build()
+ .into_node();
let assignees_field = StyledField::build()
.input(assignees)
.label("Assignees")
.build()
.into_node();
- let reporter = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Reporter))
- .name("reporter")
- .opened(reporter_state.opened)
- .empty()
- .text_filter(reporter_state.text_filter.as_str())
- .options(
- model
- .users
- .iter()
- .map(|user| user.to_select_child().name("reporter"))
- .collect(),
- )
- .selected(
- model
- .users
- .iter()
- .filter(|user| payload.reporter_id == user.id)
- .map(|user| user.to_select_child().name("reporter"))
- .collect(),
- )
- .build()
- .into_node();
+ let reporter = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::Reporter,
+ )))
+ .name("reporter")
+ .opened(reporter_state.opened)
+ .empty()
+ .text_filter(reporter_state.text_filter.as_str())
+ .options(
+ model
+ .users
+ .iter()
+ .map(|user| user.to_select_child().name("reporter"))
+ .collect(),
+ )
+ .selected(
+ model
+ .users
+ .iter()
+ .filter(|user| payload.reporter_id == user.id)
+ .map(|user| user.to_select_child().name("reporter"))
+ .collect(),
+ )
+ .build()
+ .into_node();
let reporter_field = StyledField::build()
.input(reporter)
.label("Reporter")
.build()
.into_node();
- let priority = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Priority))
- .name("priority")
- .opened(priority_state.opened)
- .empty()
- .text_filter(priority_state.text_filter.as_str())
- .options(
- IssuePriority::ordered()
- .into_iter()
- .map(|p| p.to_select_child().name("priority"))
- .collect(),
- )
- .selected(vec![payload.priority.to_select_child().name("priority")])
- .build()
- .into_node();
+ let priority = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::Priority,
+ )))
+ .name("priority")
+ .opened(priority_state.opened)
+ .empty()
+ .text_filter(priority_state.text_filter.as_str())
+ .options(
+ IssuePriority::ordered()
+ .into_iter()
+ .map(|p| p.to_select_child().name("priority"))
+ .collect(),
+ )
+ .selected(vec![payload.priority.to_select_child().name("priority")])
+ .build()
+ .into_node();
let priority_field = StyledField::build()
.input(priority)
.label("Priority")
.build()
.into_node();
- let estimate = StyledInput::build(FieldId::EditIssueModal(EditIssueModalFieldId::Estimate))
- .valid(true)
- .value(
- payload
- .estimate
- .as_ref()
- .map(|n| n.to_string())
- .unwrap_or_default(),
- )
- .build()
- .into_node();
+ let estimate = StyledInput::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::Estimate,
+ )))
+ .valid(true)
+ .value(
+ payload
+ .estimate
+ .as_ref()
+ .map(|n| n.to_string())
+ .unwrap_or_default(),
+ )
+ .build()
+ .into_node();
let estimate_field = StyledField::build()
.input(estimate)
.label("Original Estimate (hours)")
diff --git a/jirs-client/src/modal/time_tracking.rs b/jirs-client/src/modal/time_tracking.rs
index 129eed5e..b11a2f94 100644
--- a/jirs-client/src/modal/time_tracking.rs
+++ b/jirs-client/src/modal/time_tracking.rs
@@ -1,6 +1,6 @@
use seed::{prelude::*, *};
-use jirs_data::IssueId;
+use jirs_data::{IssueFieldId, IssueId};
use crate::model::{ModalType, Model};
use crate::shared::styled_button::StyledButton;
@@ -9,7 +9,7 @@ use crate::shared::styled_input::StyledInput;
use crate::shared::styled_modal::StyledModal;
use crate::shared::tracking_widget::tracking_widget;
use crate::shared::{find_issue, ToNode};
-use crate::{EditIssueModalFieldId, FieldId, Msg};
+use crate::{EditIssueModalSection, FieldId, Msg};
pub fn view(model: &Model, issue_id: IssueId) -> Node {
let _issue = match find_issue(model, issue_id) {
@@ -26,26 +26,28 @@ pub fn view(model: &Model, issue_id: IssueId) -> Node {
let tracking = tracking_widget(model, edit_issue_modal);
- let time_spent = StyledInput::build(FieldId::EditIssueModal(EditIssueModalFieldId::TimeSpend))
- .value(
- edit_issue_modal
- .payload
- .time_spent
- .as_ref()
- .map(|n| n.to_string())
- .unwrap_or_default(),
- )
- .valid(true)
- .build()
- .into_node();
+ let time_spent = StyledInput::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::TimeSpend,
+ )))
+ .value(
+ edit_issue_modal
+ .payload
+ .time_spent
+ .as_ref()
+ .map(|n| n.to_string())
+ .unwrap_or_default(),
+ )
+ .valid(true)
+ .build()
+ .into_node();
let time_spent_field = StyledField::build()
.input(time_spent)
.label("Time spent")
.build()
.into_node();
- let time_remaining = StyledInput::build(FieldId::EditIssueModal(
- EditIssueModalFieldId::TimeRemaining,
- ))
+ let time_remaining = StyledInput::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
+ IssueFieldId::TimeRemaining,
+ )))
.value(
edit_issue_modal
.payload
diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs
index db3593b1..5bbf7a55 100644
--- a/jirs-client/src/model.rs
+++ b/jirs-client/src/model.rs
@@ -7,9 +7,7 @@ use jirs_data::*;
use crate::shared::styled_editor::Mode;
use crate::shared::styled_select::StyledSelectState;
-use crate::{
- AddIssueModalFieldId, EditIssueModalFieldId, FieldId, ProjectSettingsFieldId, HOST_URL,
-};
+use crate::{EditIssueModalSection, FieldId, ProjectFieldId, HOST_URL};
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
pub enum ModalType {
@@ -65,19 +63,19 @@ impl EditIssueModal {
user_ids: issue.user_ids.clone(),
},
top_type_state: StyledSelectState::new(FieldId::EditIssueModal(
- EditIssueModalFieldId::IssueType,
+ EditIssueModalSection::Issue(IssueFieldId::Type),
)),
status_state: StyledSelectState::new(FieldId::EditIssueModal(
- EditIssueModalFieldId::Status,
+ EditIssueModalSection::Issue(IssueFieldId::Status),
)),
reporter_state: StyledSelectState::new(FieldId::EditIssueModal(
- EditIssueModalFieldId::Reporter,
+ EditIssueModalSection::Issue(IssueFieldId::Reporter),
)),
assignees_state: StyledSelectState::new(FieldId::EditIssueModal(
- EditIssueModalFieldId::Assignees,
+ EditIssueModalSection::Issue(IssueFieldId::Assignees),
)),
priority_state: StyledSelectState::new(FieldId::EditIssueModal(
- EditIssueModalFieldId::Priority,
+ EditIssueModalSection::Issue(IssueFieldId::Priority),
)),
description_editor_mode: Mode::View,
comment_form: CommentForm {
@@ -126,18 +124,12 @@ impl Default for AddIssueModal {
project_id: Default::default(),
user_ids: Default::default(),
reporter_id: Default::default(),
- type_state: StyledSelectState::new(FieldId::AddIssueModal(
- AddIssueModalFieldId::IssueType,
- )),
- reporter_state: StyledSelectState::new(FieldId::AddIssueModal(
- AddIssueModalFieldId::Reporter,
- )),
+ type_state: StyledSelectState::new(FieldId::AddIssueModal(IssueFieldId::Type)),
+ reporter_state: StyledSelectState::new(FieldId::AddIssueModal(IssueFieldId::Reporter)),
assignees_state: StyledSelectState::new(FieldId::AddIssueModal(
- AddIssueModalFieldId::Assignees,
- )),
- priority_state: StyledSelectState::new(FieldId::AddIssueModal(
- AddIssueModalFieldId::Priority,
+ IssueFieldId::Assignees,
)),
+ priority_state: StyledSelectState::new(FieldId::AddIssueModal(IssueFieldId::Priority)),
}
}
}
@@ -220,7 +212,7 @@ impl ProjectSettingsPage {
},
description_mode: EditorMode::View,
project_category_state: StyledSelectState::new(FieldId::ProjectSettings(
- ProjectSettingsFieldId::Category,
+ ProjectFieldId::Category,
)),
}
}
diff --git a/jirs-client/src/project.rs b/jirs-client/src/project.rs
index a1c7dc31..a4bbf13d 100644
--- a/jirs-client/src/project.rs
+++ b/jirs-client/src/project.rs
@@ -11,7 +11,7 @@ use crate::shared::styled_icon::{Icon, StyledIcon};
use crate::shared::styled_input::StyledInput;
use crate::shared::styled_select::StyledSelectChange;
use crate::shared::{drag_ev, inner_layout, ToNode};
-use crate::{EditIssueModalFieldId, FieldId, Msg};
+use crate::{EditIssueModalSection, FieldId, Msg};
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders) {
if model.user.is_none() {
@@ -63,7 +63,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
orders.skip().send_msg(Msg::ModalDropped);
}
Msg::StyledSelectChanged(
- FieldId::EditIssueModal(EditIssueModalFieldId::IssueType),
+ FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
StyledSelectChange::Text(text),
) => {
let modal = model
diff --git a/jirs-client/src/project_settings.rs b/jirs-client/src/project_settings.rs
index 5d9804a0..59ae1e93 100644
--- a/jirs-client/src/project_settings.rs
+++ b/jirs-client/src/project_settings.rs
@@ -13,7 +13,7 @@ use crate::shared::styled_select_child::ToStyledSelectChild;
use crate::shared::styled_textarea::StyledTextarea;
use crate::shared::{inner_layout, ToNode};
use crate::FieldChange::TabChanged;
-use crate::{model, FieldId, Msg, ProjectSettingsFieldId};
+use crate::{model, FieldId, Msg, ProjectFieldId};
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) {
if model.user.is_none() {
@@ -45,24 +45,24 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders)
page.project_category_state.update(&msg, orders);
match msg {
Msg::ProjectSaveChanges => send_ws_msg(WsMsg::ProjectUpdateRequest(page.payload.clone())),
- Msg::InputChanged(FieldId::ProjectSettings(ProjectSettingsFieldId::Name), text) => {
+ Msg::InputChanged(FieldId::ProjectSettings(ProjectFieldId::Name), text) => {
page.payload.name = Some(text);
}
- Msg::InputChanged(FieldId::ProjectSettings(ProjectSettingsFieldId::Url), text) => {
+ Msg::InputChanged(FieldId::ProjectSettings(ProjectFieldId::Url), text) => {
page.payload.url = Some(text);
}
- Msg::InputChanged(FieldId::ProjectSettings(ProjectSettingsFieldId::Description), text) => {
+ Msg::InputChanged(FieldId::ProjectSettings(ProjectFieldId::Description), text) => {
page.payload.description = Some(text);
}
Msg::StyledSelectChanged(
- FieldId::ProjectSettings(ProjectSettingsFieldId::Category),
+ FieldId::ProjectSettings(ProjectFieldId::Category),
StyledSelectChange::Changed(value),
) => {
let category = value.into();
page.payload.category = Some(category);
}
Msg::ModalChanged(TabChanged(
- FieldId::ProjectSettings(ProjectSettingsFieldId::Description),
+ FieldId::ProjectSettings(ProjectFieldId::Description),
mode,
)) => {
page.description_mode = mode;
@@ -89,7 +89,7 @@ pub fn view(model: &model::Model) -> Node {
.height(39)
.max_height(39)
.disable_auto_resize()
- .build(FieldId::ProjectSettings(ProjectSettingsFieldId::Name))
+ .build(FieldId::ProjectSettings(ProjectFieldId::Name))
.into_node();
let name_field = StyledField::build()
.label("Name")
@@ -103,7 +103,7 @@ pub fn view(model: &model::Model) -> Node {
.max_height(39)
.disable_auto_resize()
.value(page.payload.url.as_ref().cloned().unwrap_or_default())
- .build(FieldId::ProjectSettings(ProjectSettingsFieldId::Url))
+ .build(FieldId::ProjectSettings(ProjectFieldId::Url))
.into_node();
let url_field = StyledField::build()
.label("Url")
@@ -112,20 +112,18 @@ pub fn view(model: &model::Model) -> Node {
.build()
.into_node();
- let description = StyledEditor::build(FieldId::ProjectSettings(
- ProjectSettingsFieldId::Description,
- ))
- .text(
- page.payload
- .description
- .as_ref()
- .cloned()
- .unwrap_or_default(),
- )
- .update_on(Ev::Change)
- .mode(page.description_mode.clone())
- .build()
- .into_node();
+ let description = StyledEditor::build(FieldId::ProjectSettings(ProjectFieldId::Description))
+ .text(
+ page.payload
+ .description
+ .as_ref()
+ .cloned()
+ .unwrap_or_default(),
+ )
+ .update_on(Ev::Change)
+ .mode(page.description_mode.clone())
+ .build()
+ .into_node();
let description_field = StyledField::build()
.input(description)
.label("Description")
@@ -133,7 +131,7 @@ pub fn view(model: &model::Model) -> Node {
.build()
.into_node();
- let category = StyledSelect::build(FieldId::ProjectSettings(ProjectSettingsFieldId::Category))
+ let category = StyledSelect::build(FieldId::ProjectSettings(ProjectFieldId::Category))
.opened(page.project_category_state.opened)
.text_filter(page.project_category_state.text_filter.as_str())
.valid(true)
diff --git a/jirs-client/src/ws/issue.rs b/jirs-client/src/ws/issue.rs
index f5486442..bb080634 100644
--- a/jirs-client/src/ws/issue.rs
+++ b/jirs-client/src/ws/issue.rs
@@ -93,23 +93,16 @@ pub fn sync(model: &mut Model) {
continue;
}
- let payload = UpdateIssuePayload {
- title: issue.title.clone(),
- issue_type: issue.issue_type.clone(),
- status: issue.status.clone(),
- priority: issue.priority.clone(),
- list_position: issue.list_position,
- description: issue.description.clone(),
- description_text: issue.description_text.clone(),
- estimate: issue.estimate,
- time_spent: issue.time_spent,
- time_remaining: issue.time_remaining,
- project_id: issue.project_id,
- reporter_id: issue.reporter_id,
- user_ids: issue.user_ids.clone(),
- };
-
- send_ws_msg(WsMsg::IssueUpdateRequest(issue.id, payload));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ issue.id,
+ IssueFieldId::Status,
+ PayloadVariant::IssueStatus(issue.status.clone()),
+ ));
+ send_ws_msg(WsMsg::IssueUpdateRequest(
+ issue.id,
+ IssueFieldId::ListPosition,
+ PayloadVariant::I32(issue.list_position),
+ ));
}
project_page.dragged_issue_id = None;
project_page.last_drag_exchange_id = None;
diff --git a/jirs-data/src/lib.rs b/jirs-data/src/lib.rs
index 37eeb14c..592f41b5 100644
--- a/jirs-data/src/lib.rs
+++ b/jirs-data/src/lib.rs
@@ -168,7 +168,7 @@ impl IssueStatus {
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
#[cfg_attr(feature = "backend", sql_type = "IssuePriorityType")]
-#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
+#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
pub enum IssuePriority {
Highest,
High,
@@ -463,6 +463,53 @@ pub struct UpdateProjectPayload {
pub category: Option,
}
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub enum PayloadVariant {
+ OptionI32(Option),
+ VecI32(Vec),
+ I32(i32),
+ String(String),
+ IssueType(IssueType),
+ IssueStatus(IssueStatus),
+ IssuePriority(IssuePriority),
+ ProjectCategory(ProjectCategory),
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
+pub enum ProjectFieldId {
+ Name,
+ Url,
+ Description,
+ Category,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
+pub enum LoginFieldId {
+ Username,
+ Email,
+ Token,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
+pub enum CommentFieldId {
+ Body,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)]
+pub enum IssueFieldId {
+ Type,
+ Title,
+ Description,
+ Status,
+ ListPosition,
+ Assignees,
+ Reporter,
+ Priority,
+ Estimate,
+ TimeSpend,
+ TimeRemaining,
+}
+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum WsMsg {
Ping,
@@ -488,7 +535,7 @@ pub enum WsMsg {
ProjectUpdateRequest(UpdateProjectPayload),
// issue
- IssueUpdateRequest(IssueId, UpdateIssuePayload),
+ IssueUpdateRequest(IssueId, IssueFieldId, PayloadVariant),
IssueUpdated(Issue),
IssueDeleteRequest(IssueId),
IssueDeleted(IssueId),
diff --git a/jirs-server/Cargo.toml b/jirs-server/Cargo.toml
index f06cba64..2edd99c6 100644
--- a/jirs-server/Cargo.toml
+++ b/jirs-server/Cargo.toml
@@ -28,6 +28,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"
bincode = "1.2.1"
time = { version = "0.1" }
url = { version = "2.1.0" }
@@ -44,6 +45,8 @@ log = "0.4"
pretty_env_logger = "0.4"
env_logger = "0.7"
futures = { version = "*" }
+lettre = { version = "*" }
+lettre_email = { version = "*" }
[dependencies.diesel]
version = "1.4.4"
diff --git a/jirs-server/src/db/authorize_user.rs b/jirs-server/src/db/authorize_user.rs
index fe09711b..ce7c244e 100644
--- a/jirs-server/src/db/authorize_user.rs
+++ b/jirs-server/src/db/authorize_user.rs
@@ -23,7 +23,7 @@ impl Handler for DbExecutor {
use crate::schema::users::dsl::{id, users};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let token = tokens
diff --git a/jirs-server/src/db/comments.rs b/jirs-server/src/db/comments.rs
index 391dfb7d..f1e2bff6 100644
--- a/jirs-server/src/db/comments.rs
+++ b/jirs-server/src/db/comments.rs
@@ -22,7 +22,7 @@ impl Handler for DbExecutor {
use crate::schema::comments::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let rows: Vec = comments
@@ -59,7 +59,7 @@ impl Handler for DbExecutor {
};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let row: Comment = diesel::insert_into(comments)
@@ -88,7 +88,7 @@ impl Handler for DbExecutor {
use crate::schema::comments::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let query = diesel::update(
@@ -122,7 +122,7 @@ impl Handler for DbExecutor {
use crate::schema::comments::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
diesel::delete(
diff --git a/jirs-server/src/db/issue_assignees.rs b/jirs-server/src/db/issue_assignees.rs
index 2e89f21a..715852bc 100644
--- a/jirs-server/src/db/issue_assignees.rs
+++ b/jirs-server/src/db/issue_assignees.rs
@@ -22,7 +22,7 @@ impl Handler for DbExecutor {
use crate::schema::issue_assignees::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
issue_assignees
diff --git a/jirs-server/src/db/issues.rs b/jirs-server/src/db/issues.rs
index 4ddb08c7..ea7a1fd7 100644
--- a/jirs-server/src/db/issues.rs
+++ b/jirs-server/src/db/issues.rs
@@ -27,7 +27,7 @@ impl Handler for DbExecutor {
fn handle(&mut self, msg: LoadIssue, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::issues::dsl::{id, issues};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let record = issues
@@ -54,7 +54,7 @@ impl Handler for DbExecutor {
fn handle(&mut self, msg: LoadProjectIssues, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::issues::dsl::{issues, project_id};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let chain = issues.filter(project_id.eq(msg.project_id)).distinct();
@@ -69,7 +69,7 @@ impl Handler for DbExecutor {
}
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Default)]
pub struct UpdateIssue {
pub issue_id: i32,
pub title: Option,
@@ -97,7 +97,7 @@ impl Handler for DbExecutor {
fn handle(&mut self, msg: UpdateIssue, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::issues::dsl::{self, issues};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
@@ -187,7 +187,7 @@ impl Handler for DbExecutor {
use crate::schema::issues::dsl::issues;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
@@ -229,7 +229,7 @@ impl Handler for DbExecutor {
use crate::schema::issues::dsl::{issues, status};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
diff --git a/jirs-server/src/db/mod.rs b/jirs-server/src/db/mod.rs
index da75f843..4b0f6612 100644
--- a/jirs-server/src/db/mod.rs
+++ b/jirs-server/src/db/mod.rs
@@ -1,7 +1,10 @@
+use std::fs::*;
+
use actix::{Actor, SyncContext};
#[cfg(not(debug_assertions))]
use diesel::pg::PgConnection;
use diesel::r2d2::{self, ConnectionManager};
+use serde::{Deserialize, Serialize};
#[cfg(debug_assertions)]
use crate::db::dev::VerboseConnection;
@@ -19,7 +22,10 @@ pub type DbPool = r2d2::Pool>;
#[cfg(not(debug_assertions))]
pub type DbPool = r2d2::Pool>;
-pub struct DbExecutor(pub DbPool);
+pub struct DbExecutor {
+ pub pool: DbPool,
+ pub config: Configuration,
+}
impl Actor for DbExecutor {
type Context = SyncContext;
@@ -27,7 +33,10 @@ impl Actor for DbExecutor {
impl Default for DbExecutor {
fn default() -> Self {
- Self(build_pool())
+ Self {
+ pool: build_pool(),
+ config: Configuration::read(),
+ }
}
}
@@ -126,3 +135,42 @@ pub mod dev {
}
}
}
+
+#[derive(Serialize, Deserialize)]
+pub struct Configuration {
+ pub concurrency: usize,
+ pub database_url: String,
+}
+
+impl Default for Configuration {
+ fn default() -> Self {
+ Self {
+ concurrency: 2,
+ database_url: "postgres://postgres@localhost:5432/jirs".to_string(),
+ }
+ }
+}
+
+impl Configuration {
+ pub fn read() -> Self {
+ let contents: String = read_to_string(Self::config_file()).unwrap_or_default();
+ match toml::from_str(contents.as_str()) {
+ Ok(config) => config,
+ _ => {
+ let config = Configuration::default();
+ config.write().unwrap_or_else(|e| panic!(e));
+ config
+ }
+ }
+ }
+
+ pub fn write(&self) -> Result<(), String> {
+ let s = toml::to_string(self).map_err(|e| e.to_string())?;
+ write(Self::config_file(), s.as_str()).map_err(|e| e.to_string())?;
+ Ok(())
+ }
+
+ pub fn config_file() -> &'static str {
+ "db.toml"
+ }
+}
diff --git a/jirs-server/src/db/projects.rs b/jirs-server/src/db/projects.rs
index c5a130cd..7b4b05b8 100644
--- a/jirs-server/src/db/projects.rs
+++ b/jirs-server/src/db/projects.rs
@@ -23,7 +23,7 @@ impl Handler for DbExecutor {
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::projects::dsl::{id, projects};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
@@ -59,7 +59,7 @@ impl Handler for DbExecutor {
fn handle(&mut self, msg: UpdateProject, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::projects::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
diff --git a/jirs-server/src/db/tokens.rs b/jirs-server/src/db/tokens.rs
index 26f45310..45c20656 100644
--- a/jirs-server/src/db/tokens.rs
+++ b/jirs-server/src/db/tokens.rs
@@ -24,7 +24,7 @@ impl Handler for DbExecutor {
fn handle(&mut self, msg: FindBindToken, _: &mut Self::Context) -> Self::Result {
use crate::schema::tokens::dsl::{bind_token, tokens};
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
@@ -58,7 +58,7 @@ impl Handler for DbExecutor {
fn handle(&mut self, msg: CreateBindToken, _: &mut Self::Context) -> Self::Result {
use crate::schema::tokens::dsl::tokens;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
diff --git a/jirs-server/src/db/users.rs b/jirs-server/src/db/users.rs
index a300ee72..a49ee7a1 100644
--- a/jirs-server/src/db/users.rs
+++ b/jirs-server/src/db/users.rs
@@ -1,10 +1,11 @@
-use crate::db::DbExecutor;
-use crate::errors::ServiceErrors;
-use crate::models::{IssueAssignee, User};
use actix::{Handler, Message};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
+use crate::db::DbExecutor;
+use crate::errors::ServiceErrors;
+use crate::models::{IssueAssignee, User};
+
#[derive(Serialize, Deserialize, Debug)]
pub struct FindUser {
pub name: String,
@@ -22,7 +23,7 @@ impl Handler for DbExecutor {
use crate::schema::users::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let row: User = users
@@ -53,7 +54,7 @@ impl Handler for DbExecutor {
use crate::schema::users::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let rows: Vec = users
@@ -82,7 +83,7 @@ impl Handler for DbExecutor {
use crate::schema::users::dsl::*;
let conn = &self
- .0
+ .pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let rows: Vec<(User, IssueAssignee)> = users
diff --git a/jirs-server/src/mail/mod.rs b/jirs-server/src/mail/mod.rs
new file mode 100644
index 00000000..cf931979
--- /dev/null
+++ b/jirs-server/src/mail/mod.rs
@@ -0,0 +1,92 @@
+use std::fs::*;
+
+use actix::{Actor, SyncContext};
+use lettre;
+use serde::{Deserialize, Serialize};
+
+pub mod welcome;
+
+pub type MailTransport = lettre::SmtpTransport;
+
+pub struct MailExecutor {
+ pub transport: MailTransport,
+ pub config: Configuration,
+}
+
+impl Actor for MailExecutor {
+ type Context = SyncContext;
+}
+
+impl Default for MailExecutor {
+ fn default() -> Self {
+ let config = Configuration::read();
+ Self {
+ transport: mail_transport(&config),
+ config,
+ }
+ }
+}
+
+fn mail_client(config: &Configuration) -> lettre::SmtpClient {
+ let mail_user = config.user.as_str();
+ let mail_pass = config.pass.as_str();
+ let mail_host = config.host.as_str();
+
+ lettre::SmtpClient::new_simple(mail_host)
+ .expect("Failed to init SMTP client")
+ .credentials(lettre::smtp::authentication::Credentials::new(
+ mail_user.to_string(),
+ mail_pass.to_string(),
+ ))
+ .connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited)
+ .smtp_utf8(true)
+}
+
+fn mail_transport(config: &Configuration) -> MailTransport {
+ mail_client(config).transport()
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Configuration {
+ pub concurrency: usize,
+ pub user: String,
+ pub pass: String,
+ pub host: String,
+ pub from: String,
+}
+
+impl Default for Configuration {
+ fn default() -> Self {
+ Self {
+ concurrency: 2,
+ user: "apikey".to_string(),
+ pass: "YOUR-TOKEN".to_string(),
+ host: "smtp.sendgrid.net".to_string(),
+ from: "contact@jirs.pl".to_string(),
+ }
+ }
+}
+
+impl Configuration {
+ pub fn read() -> Self {
+ let contents: String = read_to_string(Self::config_file()).unwrap_or_default();
+ match toml::from_str(contents.as_str()) {
+ Ok(config) => config,
+ _ => {
+ let config = Configuration::default();
+ config.write().unwrap_or_else(|e| panic!(e));
+ config
+ }
+ }
+ }
+
+ pub fn write(&self) -> Result<(), String> {
+ let s = toml::to_string(self).map_err(|e| e.to_string())?;
+ write(Self::config_file(), s.as_str()).map_err(|e| e.to_string())?;
+ Ok(())
+ }
+
+ fn config_file() -> &'static str {
+ "mail.toml"
+ }
+}
diff --git a/jirs-server/src/mail/welcome.rs b/jirs-server/src/mail/welcome.rs
new file mode 100644
index 00000000..7c64bfc9
--- /dev/null
+++ b/jirs-server/src/mail/welcome.rs
@@ -0,0 +1,60 @@
+use actix::{Handler, Message};
+use lettre;
+use lettre_email;
+use uuid::Uuid;
+
+use crate::mail::MailExecutor;
+
+#[derive(Debug)]
+pub struct Welcome {
+ pub bind_token: Uuid,
+ pub email: String,
+}
+
+impl Message for Welcome {
+ type Result = Result<(), String>;
+}
+
+impl Handler for MailExecutor {
+ type Result = Result<(), String>;
+
+ fn handle(&mut self, msg: Welcome, _ctx: &mut Self::Context) -> Self::Result {
+ use lettre::Transport;
+ let transport = &mut self.transport;
+ let from = self.config.from.as_str();
+
+ let html = format!(
+ r#"
+
+
+
+
+ Welcome in JIRS!
+
+
+
+ Please copy this code to sign-in single use token field:
{bind_token}
+
+
+ Notice: This token is single use and will be removed from system once you use it.
+
+
+
+ "#,
+ bind_token = msg.bind_token,
+ );
+
+ let email = lettre_email::Email::builder()
+ .from(from.clone())
+ .to(msg.email.as_str())
+ .html(html.as_str())
+ .subject("Welcome to JIRS")
+ .build()
+ .map_err(|_| "Email is not valid".to_string())?;
+
+ transport
+ .send(email.into())
+ .and_then(|_| Ok(()))
+ .map_err(|e| format!("Mailer: {}", e))
+ }
+}
diff --git a/jirs-server/src/main.rs b/jirs-server/src/main.rs
index a4f59e25..6f86f400 100644
--- a/jirs-server/src/main.rs
+++ b/jirs-server/src/main.rs
@@ -6,14 +6,15 @@ extern crate diesel;
extern crate log;
use actix_cors::Cors;
-use actix_web::{web, App, HttpServer};
+use actix_web::{App, HttpServer};
pub mod db;
pub mod errors;
+pub mod mail;
pub mod middleware;
pub mod models;
-pub mod routes;
pub mod schema;
+pub mod web;
pub mod ws;
#[actix_rt::main]
@@ -21,28 +22,28 @@ async fn main() -> Result<(), String> {
dotenv::dotenv().ok();
pretty_env_logger::init();
- let port = std::env::var("JIRS_SERVER_PORT").unwrap_or_else(|_| "3000".to_string());
- let bind = std::env::var("JIRS_SERVER_BIND").unwrap_or_else(|_| "0.0.0.0".to_string());
- let addr = format!("{}:{}", bind, port);
+ let web_config = web::Configuration::read();
- let db_addr = actix::SyncArbiter::start(4, crate::db::DbExecutor::default);
+ let db_addr = actix::SyncArbiter::start(
+ crate::db::Configuration::read().concurrency,
+ crate::db::DbExecutor::default,
+ );
+ let mail_addr = actix::SyncArbiter::start(
+ crate::mail::Configuration::read().concurrency,
+ crate::mail::MailExecutor::default,
+ );
HttpServer::new(move || {
App::new()
.wrap(actix_web::middleware::Logger::default())
.wrap(Cors::default())
.data(db_addr.clone())
+ .data(mail_addr.clone())
.data(crate::db::build_pool())
.service(crate::ws::index)
- .service(
- web::scope("/comments")
- .service(crate::routes::comments::create)
- .service(crate::routes::comments::update)
- .service(crate::routes::comments::delete),
- )
- .service(web::scope("/currentUser").service(crate::routes::users::current_user))
})
- .bind(addr)
+ .workers(web_config.concurrency)
+ .bind(web_config.addr())
.map_err(|e| format!("{}", e))?
.run()
.await
diff --git a/jirs-server/src/routes/comments.rs b/jirs-server/src/routes/comments.rs
deleted file mode 100644
index 9980ca96..00000000
--- a/jirs-server/src/routes/comments.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use actix::Addr;
-use actix_web::web::{Data, Json, Path};
-use actix_web::{delete, post, put, HttpRequest, HttpResponse};
-
-use crate::db::comments::{CreateComment, DeleteComment, UpdateComment};
-use crate::db::DbExecutor;
-use crate::routes::user_from_request;
-
-#[post("")]
-pub async fn create(
- req: HttpRequest,
- payload: Json,
- db: Data>,
-) -> HttpResponse {
- let user = match user_from_request(req, &db).await {
- Ok(user) => user,
- Err(response) => return response,
- };
-
- let msg = CreateComment {
- body: payload.body.clone(),
- issue_id: payload.issue_id,
- user_id: user.id,
- };
- let comment = match db.send(msg).await {
- Ok(Ok(comment)) => comment,
- _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
- };
-
- HttpResponse::Ok().json(comment)
-}
-
-#[put("/{id}")]
-pub async fn update(
- req: HttpRequest,
- path: Path,
- db: Data>,
- payload: Json,
-) -> HttpResponse {
- let user = match user_from_request(req, &db).await {
- Ok(user) => user,
- Err(response) => return response,
- };
- let comment_id = path.into_inner();
- let body = payload.body.clone();
-
- let msg = UpdateComment {
- comment_id,
- body,
- user_id: user.id,
- };
- let comment = match db.send(msg).await {
- Ok(Ok(comment)) => comment,
- Ok(Err(e)) => return e.into_http_response(),
- _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
- };
- HttpResponse::Ok().json(comment)
-}
-
-#[delete("/{id}")]
-pub async fn delete(req: HttpRequest, path: Path, db: Data>) -> HttpResponse {
- let user = match user_from_request(req, &db).await {
- Ok(user) => user,
- Err(response) => return response,
- };
- let comment_id = path.into_inner();
- let msg = DeleteComment {
- user_id: user.id,
- comment_id,
- };
- match db.send(msg).await {
- Ok(Ok(_)) => (),
- Ok(Err(_)) => (),
- _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
- };
- HttpResponse::NoContent().body("")
-}
diff --git a/jirs-server/src/routes/mod.rs b/jirs-server/src/routes/mod.rs
deleted file mode 100644
index ed3d62ba..00000000
--- a/jirs-server/src/routes/mod.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use actix::Addr;
-use actix_web::web::Data;
-use actix_web::{HttpRequest, HttpResponse};
-
-use crate::db::authorize_user::AuthorizeUser;
-use crate::db::DbExecutor;
-use crate::errors::ServiceErrors;
-use crate::middleware::authorize::token_from_headers;
-use crate::models::User;
-
-pub mod comments;
-pub mod users;
-
-pub async fn user_from_request(
- req: HttpRequest,
- db: &Data>,
-) -> Result {
- let token = match token_from_headers(req.headers()) {
- Ok(uuid) => uuid,
- _ => return Err(ServiceErrors::Unauthorized.into_http_response()),
- };
- match db
- .send(AuthorizeUser {
- access_token: token,
- })
- .await
- {
- Ok(Ok(user)) => Ok(user),
- Ok(Err(e)) => Err(e.into_http_response()),
- _ => Err(ServiceErrors::Unauthorized.into_http_response()),
- }
-}
diff --git a/jirs-server/src/routes/users.rs b/jirs-server/src/routes/users.rs
deleted file mode 100644
index 6a1791d1..00000000
--- a/jirs-server/src/routes/users.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use crate::db::authorize_user::AuthorizeUser;
-use crate::db::DbExecutor;
-use crate::middleware::authorize::token_from_headers;
-use actix::Addr;
-use actix_web::web::Data;
-use actix_web::{get, HttpRequest, HttpResponse};
-
-#[get("")]
-pub async fn current_user(req: HttpRequest, db: Data>) -> HttpResponse {
- let token = match token_from_headers(req.headers()) {
- Ok(uuid) => uuid,
- _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
- };
- match db
- .send(AuthorizeUser {
- access_token: token,
- })
- .await
- {
- Ok(Ok(user)) => HttpResponse::Ok().json(user),
- _ => crate::errors::ServiceErrors::Unauthorized.into_http_response(),
- }
-}
diff --git a/jirs-server/src/web/mod.rs b/jirs-server/src/web/mod.rs
new file mode 100644
index 00000000..e07a16ef
--- /dev/null
+++ b/jirs-server/src/web/mod.rs
@@ -0,0 +1,79 @@
+use std::fs::*;
+
+use actix::Addr;
+use actix_web::web::Data;
+use actix_web::{HttpRequest, HttpResponse};
+use serde::{Deserialize, Serialize};
+
+use crate::db::authorize_user::AuthorizeUser;
+use crate::db::DbExecutor;
+use crate::errors::ServiceErrors;
+use crate::middleware::authorize::token_from_headers;
+use crate::models::User;
+
+pub async fn user_from_request(
+ req: HttpRequest,
+ db: &Data>,
+) -> Result {
+ let token = match token_from_headers(req.headers()) {
+ Ok(uuid) => uuid,
+ _ => return Err(ServiceErrors::Unauthorized.into_http_response()),
+ };
+ match db
+ .send(AuthorizeUser {
+ access_token: token,
+ })
+ .await
+ {
+ Ok(Ok(user)) => Ok(user),
+ Ok(Err(e)) => Err(e.into_http_response()),
+ _ => Err(ServiceErrors::Unauthorized.into_http_response()),
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Configuration {
+ pub concurrency: usize,
+ pub port: String,
+ pub bind: String,
+ pub ssl: bool,
+}
+
+impl Default for Configuration {
+ fn default() -> Self {
+ Self {
+ concurrency: 2,
+ port: "5000".to_string(),
+ bind: "0.0.0.0".to_string(),
+ ssl: false,
+ }
+ }
+}
+
+impl Configuration {
+ pub fn addr(&self) -> String {
+ format!("{}:{}", self.bind, self.port)
+ }
+
+ pub fn read() -> Self {
+ let contents: String = read_to_string(Self::config_file()).unwrap_or_default();
+ match toml::from_str(contents.as_str()) {
+ Ok(config) => config,
+ _ => {
+ let config = Configuration::default();
+ config.write().unwrap_or_else(|e| panic!(e));
+ config
+ }
+ }
+ }
+
+ pub fn write(&self) -> Result<(), String> {
+ let s = toml::to_string(self).map_err(|e| e.to_string())?;
+ write(Self::config_file(), s.as_str()).map_err(|e| e.to_string())?;
+ Ok(())
+ }
+
+ pub fn config_file() -> &'static str {
+ "web.toml"
+ }
+}
diff --git a/jirs-server/src/ws/auth.rs b/jirs-server/src/ws/auth.rs
index 733dbe9f..1266dcda 100644
--- a/jirs-server/src/ws/auth.rs
+++ b/jirs-server/src/ws/auth.rs
@@ -1,12 +1,21 @@
+use actix::Addr;
+use actix_web::web::Data;
+
+use jirs_data::WsMsg;
+
use crate::db::tokens::CreateBindToken;
use crate::db::users::FindUser;
use crate::db::DbExecutor;
+use crate::mail::welcome::Welcome;
+use crate::mail::MailExecutor;
use crate::ws::WsResult;
-use actix::Addr;
-use actix_web::web::Data;
-use jirs_data::WsMsg;
-pub async fn authenticate(db: &Data>, name: String, email: String) -> WsResult {
+pub async fn authenticate(
+ db: &Data>,
+ mail: &Data>,
+ name: String,
+ email: String,
+) -> WsResult {
// TODO check attempt number, allow only 5 times per day
let user = match db.send(FindUser { name, email }).await {
Ok(Ok(user)) => user,
@@ -19,7 +28,7 @@ pub async fn authenticate(db: &Data>, name: String, email: Stri
return Ok(None);
}
};
- let _token = match db.send(CreateBindToken { user_id: user.id }).await {
+ let token = match db.send(CreateBindToken { user_id: user.id }).await {
Ok(Ok(token)) => token,
Ok(Err(e)) => {
error!("{:?}", e);
@@ -30,6 +39,24 @@ pub async fn authenticate(db: &Data>, name: String, email: Stri
return Ok(None);
}
};
- // TODO send email somehow
+ if let Some(bind_token) = token.bind_token.as_ref().cloned() {
+ match mail
+ .send(Welcome {
+ bind_token,
+ email: user.email.clone(),
+ })
+ .await
+ {
+ Ok(Ok(_)) => (),
+ Ok(Err(e)) => {
+ error!("{}", e);
+ return Ok(None);
+ }
+ Err(e) => {
+ error!("{}", e);
+ return Ok(None);
+ }
+ }
+ }
Ok(Some(WsMsg::AuthenticateSuccess))
}
diff --git a/jirs-server/src/ws/issues.rs b/jirs-server/src/ws/issues.rs
index 5554474e..967664a4 100644
--- a/jirs-server/src/ws/issues.rs
+++ b/jirs-server/src/ws/issues.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
use actix::Addr;
use actix_web::web::Data;
-use jirs_data::WsMsg;
+use jirs_data::{IssueFieldId, PayloadVariant, WsMsg};
use crate::db::issue_assignees::LoadAssignees;
use crate::db::issues::{LoadProjectIssues, UpdateIssue};
@@ -14,28 +14,51 @@ pub async fn update_issue(
db: &Data>,
user: &Option,
issue_id: i32,
- payload: jirs_data::UpdateIssuePayload,
+ issue_field_id: IssueFieldId,
+ payload: PayloadVariant,
) -> WsResult {
current_user(user)?;
- let mut issue: jirs_data::Issue = match db
- .send(UpdateIssue {
- issue_id,
- title: Some(payload.title),
- issue_type: Some(payload.issue_type),
- status: Some(payload.status),
- priority: Some(payload.priority),
- list_position: Some(payload.list_position),
- description: payload.description,
- description_text: payload.description_text,
- estimate: payload.estimate,
- time_spent: payload.time_spent,
- time_remaining: payload.time_remaining,
- project_id: Some(payload.project_id),
- user_ids: Some(payload.user_ids),
- reporter_id: Some(payload.reporter_id),
- })
- .await
- {
+
+ let mut msg = UpdateIssue::default();
+ msg.issue_id = issue_id;
+ match (issue_field_id, payload) {
+ (IssueFieldId::Type, PayloadVariant::IssueType(t)) => {
+ msg.issue_type = Some(t);
+ }
+ (IssueFieldId::Title, PayloadVariant::String(s)) => {
+ msg.title = Some(s);
+ }
+ (IssueFieldId::Description, PayloadVariant::String(s)) => {
+ msg.description = Some(s);
+ }
+ (IssueFieldId::Status, PayloadVariant::IssueStatus(s)) => {
+ msg.status = Some(s);
+ }
+ (IssueFieldId::ListPosition, PayloadVariant::I32(i)) => {
+ msg.list_position = Some(i);
+ }
+ (IssueFieldId::Assignees, PayloadVariant::VecI32(v)) => {
+ msg.user_ids = Some(v);
+ }
+ (IssueFieldId::Reporter, PayloadVariant::I32(i)) => {
+ msg.reporter_id = Some(i);
+ }
+ (IssueFieldId::Priority, PayloadVariant::IssuePriority(p)) => {
+ msg.priority = Some(p);
+ }
+ (IssueFieldId::Estimate, PayloadVariant::OptionI32(o)) => {
+ msg.estimate = o;
+ }
+ (IssueFieldId::TimeSpend, PayloadVariant::OptionI32(o)) => {
+ msg.time_spent = o;
+ }
+ (IssueFieldId::TimeRemaining, PayloadVariant::OptionI32(o)) => {
+ msg.time_remaining = o;
+ }
+ _ => (),
+ };
+
+ let mut issue: jirs_data::Issue = match db.send(msg).await {
Ok(Ok(issue)) => issue.into(),
_ => return Ok(None),
};
diff --git a/jirs-server/src/ws/mod.rs b/jirs-server/src/ws/mod.rs
index 61816edd..f4e70d7f 100644
--- a/jirs-server/src/ws/mod.rs
+++ b/jirs-server/src/ws/mod.rs
@@ -8,6 +8,7 @@ use jirs_data::WsMsg;
use crate::db::authorize_user::AuthorizeUser;
use crate::db::tokens::FindBindToken;
use crate::db::DbExecutor;
+use crate::mail::MailExecutor;
pub mod auth;
pub mod comments;
@@ -30,6 +31,7 @@ trait WsMessageSender {
struct WebSocketActor {
db: Data>,
+ mail: Data>,
current_user: Option,
}
@@ -51,81 +53,79 @@ impl WebSocketActor {
info!("incoming message: {:?}", msg);
}
- let msg = match msg {
- WsMsg::Ping => Some(WsMsg::Pong),
- WsMsg::Pong => Some(WsMsg::Ping),
+ let msg =
+ match msg {
+ WsMsg::Ping => Some(WsMsg::Pong),
+ WsMsg::Pong => Some(WsMsg::Ping),
- // Issues
- WsMsg::IssueUpdateRequest(id, payload) => block_on(issues::update_issue(
- &self.db,
- &self.current_user,
- id,
- payload,
- ))?,
- WsMsg::IssueCreateRequest(payload) => {
- block_on(issues::add_issue(&self.db, &self.current_user, payload))?
- }
- WsMsg::IssueDeleteRequest(id) => {
- block_on(issues::delete_issue(&self.db, &self.current_user, id))?
- }
- WsMsg::ProjectIssuesRequest => {
- block_on(issues::load_issues(&self.db, &self.current_user))?
- }
+ // Issues
+ WsMsg::IssueUpdateRequest(id, field_id, payload) => block_on(
+ issues::update_issue(&self.db, &self.current_user, id, field_id, payload),
+ )?,
+ WsMsg::IssueCreateRequest(payload) => {
+ block_on(issues::add_issue(&self.db, &self.current_user, payload))?
+ }
+ WsMsg::IssueDeleteRequest(id) => {
+ block_on(issues::delete_issue(&self.db, &self.current_user, id))?
+ }
+ WsMsg::ProjectIssuesRequest => {
+ block_on(issues::load_issues(&self.db, &self.current_user))?
+ }
- // projects
- WsMsg::ProjectRequest => {
- block_on(projects::current_project(&self.db, &self.current_user))?
- }
+ // projects
+ WsMsg::ProjectRequest => {
+ block_on(projects::current_project(&self.db, &self.current_user))?
+ }
- WsMsg::ProjectUpdateRequest(payload) => block_on(projects::update_project(
- &self.db,
- &self.current_user,
- payload,
- ))?,
+ WsMsg::ProjectUpdateRequest(payload) => block_on(projects::update_project(
+ &self.db,
+ &self.current_user,
+ payload,
+ ))?,
- // auth
- WsMsg::AuthorizeRequest(uuid) => block_on(self.check_auth_token(uuid))?,
- WsMsg::BindTokenCheck(uuid) => block_on(self.check_bind_token(uuid))?,
- WsMsg::AuthenticateRequest(email, name) => {
- block_on(auth::authenticate(&self.db, name, email))?
- }
+ // auth
+ WsMsg::AuthorizeRequest(uuid) => block_on(self.check_auth_token(uuid))?,
+ WsMsg::BindTokenCheck(uuid) => block_on(self.check_bind_token(uuid))?,
+ WsMsg::AuthenticateRequest(email, name) => {
+ block_on(auth::authenticate(&self.db, &self.mail, name, email))?
+ }
- // users
- WsMsg::ProjectUsersRequest => {
- block_on(users::load_project_users(&self.db, &self.current_user))?
- }
+ // users
+ WsMsg::ProjectUsersRequest => {
+ block_on(users::load_project_users(&self.db, &self.current_user))?
+ }
- // comments
- WsMsg::IssueCommentsRequest(issue_id) => block_on(comments::load_issues(
- &self.db,
- &self.current_user,
- issue_id,
- ))?,
+ // comments
+ WsMsg::IssueCommentsRequest(issue_id) => block_on(comments::load_issues(
+ &self.db,
+ &self.current_user,
+ issue_id,
+ ))?,
- WsMsg::CreateComment(payload) => block_on(comments::create_comment(
- &self.db,
- &self.current_user,
- payload,
- ))?,
+ WsMsg::CreateComment(payload) => block_on(comments::create_comment(
+ &self.db,
+ &self.current_user,
+ payload,
+ ))?,
- WsMsg::UpdateComment(payload) => block_on(comments::update_comment(
- &self.db,
- &self.current_user,
- payload,
- ))?,
+ WsMsg::UpdateComment(payload) => block_on(comments::update_comment(
+ &self.db,
+ &self.current_user,
+ payload,
+ ))?,
- WsMsg::CommentDeleteRequest(comment_id) => block_on(comments::delete_comment(
- &self.db,
- &self.current_user,
- comment_id,
- ))?,
+ WsMsg::CommentDeleteRequest(comment_id) => block_on(comments::delete_comment(
+ &self.db,
+ &self.current_user,
+ comment_id,
+ ))?,
- // else fail
- _ => {
- error!("No handle for {:?} specified", msg);
- None
- }
- };
+ // else fail
+ _ => {
+ error!("No handle for {:?} specified", msg);
+ None
+ }
+ };
if msg.is_some() && msg != Some(WsMsg::Pong) {
info!("sending message {:?}", msg);
}
@@ -193,10 +193,12 @@ pub async fn index(
req: HttpRequest,
stream: web::Payload,
db: Data>,
+ mail: Data>,
) -> Result {
ws::start(
WebSocketActor {
db,
+ mail,
current_user: None,
},
&req,