Reduce update issue payload. Create config files
This commit is contained in:
parent
fbee0e70d6
commit
2f870a5a0a
6
.env
6
.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
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,4 @@
|
||||
/target
|
||||
mail.toml
|
||||
web.toml
|
||||
db.toml
|
||||
|
468
Cargo.lock
generated
468
Cargo.lock
generated
@ -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"
|
||||
|
@ -23,90 +23,85 @@ pub type AvatarFilterActive = bool;
|
||||
pub type AppType = App<Msg, Model, Node<Msg>>;
|
||||
|
||||
#[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();
|
||||
|
@ -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<Msg>) {
|
||||
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<Msg> {
|
||||
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<Msg> {
|
||||
.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<Msg> {
|
||||
|
||||
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<Msg> {
|
||||
.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<Msg> {
|
||||
.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<Msg> {
|
||||
.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.")
|
||||
|
@ -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<Msg>) {
|
||||
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<Msg>) {
|
||||
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<Msg>) {
|
||||
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::<i32>().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::<i32>().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<Msg>) {
|
||||
}
|
||||
}
|
||||
// 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::<i32>() {
|
||||
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::<i32>() {
|
||||
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<Msg>) {
|
||||
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<Msg>) {
|
||||
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<Msg> {
|
||||
.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<Msg> {
|
||||
.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<Msg> {
|
||||
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<Node<Msg>> {
|
||||
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<Node<Msg>> {
|
||||
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<N
|
||||
.add_class("editButton")
|
||||
.on_click(mouse_ev(Ev::Click, move |_| {
|
||||
Msg::ModalChanged(FieldChange::EditComment(
|
||||
FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
|
||||
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
||||
comment_id,
|
||||
))
|
||||
}))
|
||||
@ -486,115 +563,125 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
..
|
||||
} = 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)")
|
||||
|
@ -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<Msg> {
|
||||
let _issue = match find_issue(model, issue_id) {
|
||||
@ -26,26 +26,28 @@ pub fn view(model: &Model, issue_id: IssueId) -> Node<Msg> {
|
||||
|
||||
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
|
||||
|
@ -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,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -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<Msg>) {
|
||||
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
|
||||
|
@ -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<Msg>) {
|
||||
if model.user.is_none() {
|
||||
@ -45,24 +45,24 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
|
||||
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<Msg> {
|
||||
.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<Msg> {
|
||||
.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<Msg> {
|
||||
.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<Msg> {
|
||||
.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)
|
||||
|
@ -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;
|
||||
|
@ -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<ProjectCategory>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum PayloadVariant {
|
||||
OptionI32(Option<i32>),
|
||||
VecI32(Vec<i32>),
|
||||
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),
|
||||
|
@ -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"
|
||||
|
@ -23,7 +23,7 @@ impl Handler<AuthorizeUser> for DbExecutor {
|
||||
use crate::schema::users::dsl::{id, users};
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let token = tokens
|
||||
|
@ -22,7 +22,7 @@ impl Handler<LoadIssueComments> for DbExecutor {
|
||||
use crate::schema::comments::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let rows: Vec<Comment> = comments
|
||||
@ -59,7 +59,7 @@ impl Handler<CreateComment> 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<UpdateComment> 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<DeleteComment> for DbExecutor {
|
||||
use crate::schema::comments::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
diesel::delete(
|
||||
|
@ -22,7 +22,7 @@ impl Handler<LoadAssignees> for DbExecutor {
|
||||
use crate::schema::issue_assignees::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
issue_assignees
|
||||
|
@ -27,7 +27,7 @@ impl Handler<LoadIssue> 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<LoadProjectIssues> 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<LoadProjectIssues> for DbExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct UpdateIssue {
|
||||
pub issue_id: i32,
|
||||
pub title: Option<String>,
|
||||
@ -97,7 +97,7 @@ impl Handler<UpdateIssue> 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<DeleteIssue> for DbExecutor {
|
||||
use crate::schema::issues::dsl::issues;
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
@ -229,7 +229,7 @@ impl Handler<CreateIssue> for DbExecutor {
|
||||
use crate::schema::issues::dsl::{issues, status};
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
|
@ -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<ConnectionManager<dev::VerboseConnection>>;
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
||||
|
||||
pub struct DbExecutor(pub DbPool);
|
||||
pub struct DbExecutor {
|
||||
pub pool: DbPool,
|
||||
pub config: Configuration,
|
||||
}
|
||||
|
||||
impl Actor for DbExecutor {
|
||||
type Context = SyncContext<Self>;
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ impl Handler<LoadCurrentProject> 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<UpdateProject> 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)?;
|
||||
|
||||
|
@ -24,7 +24,7 @@ impl Handler<FindBindToken> 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<CreateBindToken> 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)?;
|
||||
|
||||
|
@ -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<FindUser> 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<LoadProjectUsers> for DbExecutor {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let rows: Vec<User> = users
|
||||
@ -82,7 +83,7 @@ impl Handler<LoadIssueAssignees> for DbExecutor {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.0
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let rows: Vec<(User, IssueAssignee)> = users
|
||||
|
92
jirs-server/src/mail/mod.rs
Normal file
92
jirs-server/src/mail/mod.rs
Normal file
@ -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<Self>;
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
60
jirs-server/src/mail/welcome.rs
Normal file
60
jirs-server/src/mail/welcome.rs
Normal file
@ -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<Welcome> 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#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body>
|
||||
<h1>Welcome in JIRS!</h1>
|
||||
<p>
|
||||
</p>
|
||||
<p>
|
||||
Please copy this code to sign-in single use token field: <pre><code>{bind_token}</code</pre>
|
||||
</p>
|
||||
<p>
|
||||
Notice: This token is single use and will be removed from system once you use it.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
"#,
|
||||
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))
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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<jirs_data::CreateCommentPayload>,
|
||||
db: Data<Addr<DbExecutor>>,
|
||||
) -> 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<i32>,
|
||||
db: Data<Addr<DbExecutor>>,
|
||||
payload: Json<jirs_data::UpdateCommentPayload>,
|
||||
) -> 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<i32>, db: Data<Addr<DbExecutor>>) -> 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("")
|
||||
}
|
@ -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<Addr<DbExecutor>>,
|
||||
) -> Result<User, HttpResponse> {
|
||||
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()),
|
||||
}
|
||||
}
|
@ -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<Addr<DbExecutor>>) -> 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(),
|
||||
}
|
||||
}
|
79
jirs-server/src/web/mod.rs
Normal file
79
jirs-server/src/web/mod.rs
Normal file
@ -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<Addr<DbExecutor>>,
|
||||
) -> Result<User, HttpResponse> {
|
||||
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"
|
||||
}
|
||||
}
|
@ -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<Addr<DbExecutor>>, name: String, email: String) -> WsResult {
|
||||
pub async fn authenticate(
|
||||
db: &Data<Addr<DbExecutor>>,
|
||||
mail: &Data<Addr<MailExecutor>>,
|
||||
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<Addr<DbExecutor>>, 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<Addr<DbExecutor>>, 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))
|
||||
}
|
||||
|
@ -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<Addr<DbExecutor>>,
|
||||
user: &Option<jirs_data::User>,
|
||||
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),
|
||||
};
|
||||
|
@ -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<Addr<DbExecutor>>,
|
||||
mail: Data<Addr<MailExecutor>>,
|
||||
current_user: Option<jirs_data::User>,
|
||||
}
|
||||
|
||||
@ -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<Addr<DbExecutor>>,
|
||||
mail: Data<Addr<MailExecutor>>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
ws::start(
|
||||
WebSocketActor {
|
||||
db,
|
||||
mail,
|
||||
current_user: None,
|
||||
},
|
||||
&req,
|
||||
|
Loading…
Reference in New Issue
Block a user