Display listing

This commit is contained in:
Adrian Woźniak 2022-05-09 16:17:27 +02:00
parent 15363fc19d
commit bf6cb6f587
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
33 changed files with 2044 additions and 212 deletions

7
.env
View File

@ -1,8 +1,12 @@
DATABASE_URL=postgres://postgres@localhost/bazzar DATABASE_URL=postgres://postgres@localhost/bazzar
PASS_SALT=18CHwV7eGFAea16z+qMKZg PASS_SALT=18CHwV7eGFAea16z+qMKZg
RUST_LOG=debug RUST_LOG=debug
SESSION_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb" SESSION_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb"
JWT_SECRET="42^iFq&ZnQbUf!hwGWXd&CpyY6QQyJmkPU%esFCvne5&Ejcb3nJ4&GyHZp!MArZLf^9*5c6!!VgM$iZ8T%d#&bWTi&xbZk2S@4RN" JWT_SECRET="42^iFq&ZnQbUf!hwGWXd&CpyY6QQyJmkPU%esFCvne5&Ejcb3nJ4&GyHZp!MArZLf^9*5c6!!VgM$iZ8T%d#&bWTi&xbZk2S@4RN"
SIGNATURE=David
SERVICE_NAME="BaZZaR develop"
PGDATESTYLE= PGDATESTYLE=
SENDGRID_SECRET=SG.CUWRM-eoQfGJNqSU2bbwkg.NW5aBy5vZueCSOwIIyWUBqq5USChGiwAFrWzreBKvOU SENDGRID_SECRET=SG.CUWRM-eoQfGJNqSU2bbwkg.NW5aBy5vZueCSOwIIyWUBqq5USChGiwAFrWzreBKvOU
@ -13,7 +17,8 @@ PAYU_CLIENT_ID="145227"
PAYU_CLIENT_SECRET="12f071174cb7eb79d4aac5bc2f07563f" PAYU_CLIENT_SECRET="12f071174cb7eb79d4aac5bc2f07563f"
PAYU_CLIENT_MERCHANT_ID=300746 PAYU_CLIENT_MERCHANT_ID=300746
WEB_HOST=https://bazzar.ita-prog.pl #WEB_HOST=https://bazzar.ita-prog.pl
WEB_HOST=0.0.0.0
FILES_PUBLIC_PATH=/files FILES_PUBLIC_PATH=/files
FILES_LOCAL_PATH=./tmp FILES_LOCAL_PATH=./tmp

214
Cargo.lock generated
View File

@ -113,7 +113,6 @@ dependencies = [
"mime_guess", "mime_guess",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"tokio-uring 0.2.0",
] ]
[[package]] [[package]]
@ -240,7 +239,6 @@ dependencies = [
"actix-macros", "actix-macros",
"futures-core", "futures-core",
"tokio", "tokio",
"tokio-uring 0.3.0",
] ]
[[package]] [[package]]
@ -258,7 +256,6 @@ dependencies = [
"num_cpus", "num_cpus",
"socket2", "socket2",
"tokio", "tokio",
"tokio-uring 0.3.0",
"tracing", "tracing",
] ]
@ -1259,7 +1256,10 @@ dependencies = [
"model", "model",
"pretty_env_logger", "pretty_env_logger",
"sendgrid", "sendgrid",
"serde",
"serde_json",
"thiserror", "thiserror",
"tinytemplate",
"uuid", "uuid",
] ]
@ -1849,7 +1849,7 @@ dependencies = [
"hyper", "hyper",
"rustls 0.20.4", "rustls 0.20.4",
"tokio", "tokio",
"tokio-rustls 0.23.3", "tokio-rustls 0.23.4",
] ]
[[package]] [[package]]
@ -1920,16 +1920,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "io-uring"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d75829ed9377bab6c90039fe47b9d84caceb4b5063266142e21bcce6550cda8"
dependencies = [
"bitflags",
"libc",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.5.0" version = "2.5.0"
@ -2055,9 +2045,9 @@ dependencies = [
[[package]] [[package]]
name = "local-channel" name = "local-channel"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6246c68cf195087205a0512559c97e15eaf95198bf0e206d662092cdcb03fe9f" checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -2067,9 +2057,9 @@ dependencies = [
[[package]] [[package]]
name = "local-waker" name = "local-waker"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902eb695eb0591864543cbfbf6d742510642a605a61fc5e97fe6ceb5a30ac4fb" checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -2129,9 +2119,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@ -2208,25 +2198,14 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"miow",
"ntapi",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
"winapi", "windows-sys",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
] ]
[[package]] [[package]]
@ -2290,15 +2269,6 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.3.3" version = "0.3.3"
@ -2322,9 +2292,9 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -2341,9 +2311,9 @@ dependencies = [
[[package]] [[package]]
name = "num_threads" name = "num_threads"
version = "0.1.5" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -2370,9 +2340,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.28.3" version = "0.28.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -2397,18 +2367,30 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.38" version = "0.10.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
"once_cell", "once_cell",
"openssl-macros",
"openssl-sys", "openssl-sys",
] ]
[[package]]
name = "openssl-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.5" version = "0.1.5"
@ -2417,9 +2399,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.72" version = "0.9.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cc", "cc",
@ -2501,7 +2483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core 0.9.2", "parking_lot_core 0.9.3",
] ]
[[package]] [[package]]
@ -2520,9 +2502,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.2" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -2754,9 +2736,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@ -2923,7 +2905,7 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-rustls 0.23.3", "tokio-rustls 0.23.4",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -2958,6 +2940,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "rust_decimal_macros"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c70be9367d4bc095d10b48d41b819d09ed4dafc528765a144d32ed1d530654"
dependencies = [
"quote",
"rust_decimal",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.21" version = "0.1.21"
@ -2979,7 +2971,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [ dependencies = [
"semver 1.0.7", "semver 1.0.9",
] ]
[[package]] [[package]]
@ -3016,6 +3008,16 @@ dependencies = [
"base64", "base64",
] ]
[[package]]
name = "rusty-money"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b28f881005eac7ad8d46b6f075da5f322bd7f4f83a38720fc069694ddadd683"
dependencies = [
"rust_decimal",
"rust_decimal_macros",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.9" version = "1.0.9"
@ -3041,12 +3043,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@ -3153,9 +3149,9 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.7" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
[[package]] [[package]]
name = "semver-parser" name = "semver-parser"
@ -3337,12 +3333,12 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.4" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" checksum = "ca642ba17f8b2995138b1d7711829c92e98c0a25ea019de790f4f09279c4e296"
dependencies = [ dependencies = [
"libc", "libc",
"winapi", "windows-sys",
] ]
[[package]] [[package]]
@ -3709,6 +3705,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -3751,9 +3757,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.18.1" version = "1.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc" checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -3803,9 +3809,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.23.3" version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [ dependencies = [
"rustls 0.20.4", "rustls 0.20.4",
"tokio", "tokio",
@ -3823,34 +3829,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-uring"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "062a33613d97344c5054d9635b35beb14492b66d9aa4bdbf21ecde1682d256df"
dependencies = [
"bytes",
"io-uring",
"libc",
"scoped-tls",
"slab",
"tokio",
]
[[package]]
name = "tokio-uring"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3ad494f39874984d990ade7f6319dafbcd3301ff0b1841f8a55a1ebb3e742c8"
dependencies = [
"io-uring",
"libc",
"scoped-tls",
"slab",
"socket2",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.6.9" version = "0.6.9"
@ -4052,9 +4030,9 @@ checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]] [[package]]
name = "unicode_categories" name = "unicode_categories"
@ -4270,6 +4248,8 @@ dependencies = [
"chrono", "chrono",
"gloo-timers", "gloo-timers",
"indexmap", "indexmap",
"model",
"rusty-money",
"seed", "seed",
"serde", "serde",
"serde-wasm-bindgen", "serde-wasm-bindgen",
@ -4378,9 +4358,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [ dependencies = [
"windows_aarch64_msvc", "windows_aarch64_msvc",
"windows_i686_gnu", "windows_i686_gnu",
@ -4391,33 +4371,33 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]] [[package]]
name = "winreg" name = "winreg"

View File

@ -20,7 +20,7 @@ where
{ {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, local_path, file_name SELECT id, local_path, file_name, unique_name
FROM photos FROM photos
"#, "#,
) )
@ -39,6 +39,7 @@ pub struct CreatePhoto {
pub local_path: model::LocalPath, pub local_path: model::LocalPath,
/// Only file name, this part should be also included in `local_path` /// Only file name, this part should be also included in `local_path`
pub file_name: model::FileName, pub file_name: model::FileName,
pub unique_name: model::UniqueName,
} }
crate::db_async_handler!(CreatePhoto, create_photo, model::Photo, inner_create_photo); crate::db_async_handler!(CreatePhoto, create_photo, model::Photo, inner_create_photo);
@ -49,13 +50,14 @@ where
{ {
sqlx::query_as( sqlx::query_as(
r#" r#"
INSERT INTO photos (file_name, local_path) INSERT INTO photos (file_name, local_path, unique_name)
VALUES ($1, $2) VALUES ($1, $2, $3)
RETURNING id, local_path, file_name RETURNING id, local_path, file_name, unique_name
"#, "#,
) )
.bind(msg.file_name) .bind(msg.file_name)
.bind(msg.local_path) .bind(msg.local_path)
.bind(msg.unique_name)
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|e| { .map_err(|e| {
@ -101,7 +103,7 @@ pub(crate) async fn photos_for_products(
) { ) {
log::debug!("scoped product ids {:?}", ids); log::debug!("scoped product ids {:?}", ids);
let query: String = r#" let query: String = r#"
SELECT photos.id, photos.local_path, photos.file_name, product_photos.product_id SELECT photos.id, photos.local_path, photos.file_name, product_photos.product_id, photos.unique_name
FROM photos FROM photos
INNER JOIN product_photos INNER JOIN product_photos
ON photos.id = product_photos.photo_id ON photos.id = product_photos.photo_id

View File

@ -19,3 +19,8 @@ chrono = { version = "0.4", features = ["serde"] }
log = { version = "0.4", features = [] } log = { version = "0.4", features = [] }
pretty_env_logger = { version = "0.4", features = [] } pretty_env_logger = { version = "0.4", features = [] }
tinytemplate = { version = "1.2.1" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = [] }

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Reset password</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
{style}
</style>
</head>
<body>
<section class="debug-screens">
<div class="container p-8 mx-auto mt-12 bg-white">
<div class="rounded-lg">
<h1 class="mb-3 text-xl font-semibold tracking-tight text-sky-600">Trouble signing in?</h1>
<p class="mb-2 leading-normal text-sky-900">Resetting your password is easy.</p>
<p class="mb-2 leading-normal text-sky-900">Just press the button below and follow the instructions. Well have you up and running in no time.</p>
<p>
<a
class="px-4 py-2 inline-block text-white bg-blue-600 border border-transparent rounded-r hover:bg-blue-700"
href="{ url }"
>
Reset Password
</a>
</p>
<p class="mb-2 leading-normal text-sky-900">If you did not make this request then please ignore this email.</p>
</div>
</div>
</section>
</body>
</html>

View File

@ -0,0 +1,9 @@
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}
sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}
input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding-top:.5rem;padding-right:.75rem;padding-bottom:.5rem;padding-left:.75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}
input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;color-adjust:unset;print-color-adjust:unset}
[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}
[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}
*,:before,:after{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246/0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.debug-screens:before{position:fixed;z-index:2147483647;bottom:0;left:0;padding:.3333333em .5em;font-size:12px;line-height:1;font-family:sans-serif;background-color:#000;color:#fff;box-shadow:0 0 0 1px #fff;content:'screen: _'}@media (min-width:640px){.debug-screens:before{content:'screen: sm'}}@media (min-width:768px){.debug-screens:before{content:'screen: md'}
}@media (min-width:1024px){.debug-screens:before{content:'screen: lg'}}@media (min-width:1280px){.debug-screens:before{content:'screen: xl'}}@media (min-width:1536px){.debug-screens:before{content:'screen: 2xl'}}.mx-auto{margin-left:auto;margin-right:auto}.mt-12{margin-top:3rem}.mb-3{margin-bottom:.75rem}.mb-2{margin-bottom:.5rem}.inline-block{display:inline-block}.max-w-sm{max-width:24rem}.rounded-lg{border-radius:.5rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-width:1px}.border-transparent{border-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}.p-8{padding:2rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-semibold{font-weight:600}.leading-normal{line-height:1.5}.tracking-tight{letter-spacing:-.025em}
.text-sky-600{--tw-text-opacity:1;color:rgb(2 132 199/var(--tw-text-opacity))}.text-sky-900{--tw-text-opacity:1;color:rgb(12 74 110/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-md{--tw-shadow:0 4px 6px -1px rgb(0 0 0/0.1),0 2px 4px -2px rgb(0 0 0/0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sky-600{--tw-shadow-color:#0284c7;--tw-shadow:var(--tw-shadow-colored)}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome to { service_name }</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
{style}
</style>
</head>
<body>
<section class="debug-screens">
<div class="container p-8 mx-auto mt-12 bg-white">
<div class="rounded-lg">
<h1 class="mb-3 text-xl font-semibold tracking-tight text-sky-600">Hi { login }</h1>
<p class="mb-2 leading-normal text-sky-900">
Welcome to {service_name} were excited to have you on board and wed love to say thank you on behalf
of our whole company for chosing us.
</p>
<p class="mb-2 leading-normal text-sky-900">Take care,</p>
<p class="mb-2 leading-normal text-sky-900">{signature}</p>
</div>
</div>
</section>
</body>
</html>

View File

@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use config::SharedAppConfig; use config::SharedAppConfig;
use serde::Serialize;
#[macro_export] #[macro_export]
macro_rules! mail_async_handler { macro_rules! mail_async_handler {
@ -17,8 +18,13 @@ macro_rules! mail_async_handler {
}; };
} }
static STYLE: &str = include_str!("../assets/style.css");
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error {} pub enum Error {
#[error("Failed to render reset password template")]
ResetPassTemplate,
}
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -31,6 +37,7 @@ pub struct EmailManager(Arc<Inner>);
pub(crate) struct Inner { pub(crate) struct Inner {
config: SharedAppConfig, config: SharedAppConfig,
send_grid: sendgrid::SGClient, send_grid: sendgrid::SGClient,
template: Arc<tinytemplate::TinyTemplate<'static>>,
} }
impl actix::Actor for EmailManager { impl actix::Actor for EmailManager {
@ -39,36 +46,143 @@ impl actix::Actor for EmailManager {
impl EmailManager { impl EmailManager {
pub fn build(config: SharedAppConfig) -> Result<Self> { pub fn build(config: SharedAppConfig) -> Result<Self> {
let template = {
use tinytemplate::*;
let mut t = TinyTemplate::new();
t.add_template(
"reset-password",
include_str!("../assets/reset-password.html"),
)
.expect("Failed to load e-mail template reset-password");
t.add_template("welcome", include_str!("../assets/welcome.html"))
.expect("Failed to load e-mail template welcome");
t
};
Ok(Self(Arc::new(Inner { Ok(Self(Arc::new(Inner {
config: config.clone(), config: config.clone(),
send_grid: sendgrid::SGClient::new(config.lock().mail().sendgrid_secret()), send_grid: sendgrid::SGClient::new(config.lock().mail().sendgrid_secret()),
template: Arc::new(template),
}))) })))
} }
} }
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<SendState>")] #[rtype(result = "Result<()>")]
pub struct TestMail { pub struct TestMail {
pub receiver: model::Email, pub receiver: model::Email,
} }
mail_async_handler!(TestMail, test_mail, SendState); mail_async_handler!(TestMail, test_mail, ());
pub(crate) async fn test_mail(msg: TestMail, inner: Arc<Inner>) -> Result<()> {
welcome(
Welcome {
login: model::Login::new("Test User"),
email: msg.receiver,
},
inner,
)
.await
}
#[derive(actix::Message, Debug)]
#[rtype(result = "Result<()>")]
pub struct ResetPassword {
pub login: model::Login,
pub email: model::Email,
pub reset_token: model::ResetToken,
}
#[derive(Serialize)]
struct ResetPasswordContext {
url: String,
style: &'static str,
}
mail_async_handler!(ResetPassword, reset_password, ());
pub(crate) async fn reset_password(msg: ResetPassword, inner: Arc<Inner>) -> Result<()> {
let host = { inner.config.lock().web().host() };
let context = ResetPasswordContext {
url: format!(
"{host}/reset-password/{reset_token}",
reset_token = msg.reset_token
),
style: STYLE,
};
let html = inner
.template
.render("reset-password", &context)
.map_err(|e| {
log::error!("{e:?}");
Error::ResetPassTemplate
})?;
pub(crate) async fn test_mail(msg: TestMail, inner: Arc<Inner>) -> Result<SendState> {
let status = inner let status = inner
.send_grid .send_grid
.send( .send(
sendgrid::Mail::new() sendgrid::Mail::new()
.add_to((msg.receiver.as_str(), "User").into()) .add_to((msg.email.as_str(), msg.login.as_str()).into())
.add_from(&inner.config.lock().mail().smtp_from()) .add_from(&inner.config.lock().mail().smtp_from())
.add_subject("Test e-mail") .add_subject("Reset Password")
.add_html("<h1>Test e-mail</h1>") .add_html(html.as_str())
.build(), .build(),
) )
.await; .await;
log::debug!("{:?}", status); log::debug!("{:?}", status);
Ok(SendState {
success: status.is_ok(), Ok(())
}) }
#[derive(actix::Message)]
#[rtype(result = "Result<()>")]
pub struct Welcome {
pub login: model::Login,
pub email: model::Email,
}
#[derive(Serialize)]
struct WelcomeContext {
login: model::Login,
service_name: String,
signature: String,
style: &'static str,
}
mail_async_handler!(Welcome, welcome, ());
pub(crate) async fn welcome(msg: Welcome, inner: Arc<Inner>) -> Result<()> {
let (signature, service_name) = {
let l = inner.config.lock();
let w = l.web();
(w.signature(), w.service_name())
};
let context = WelcomeContext {
login: msg.login.clone(),
service_name,
signature,
style: STYLE,
};
let html = inner.template.render("welcome", &context).map_err(|e| {
log::error!("{e:?}");
Error::ResetPassTemplate
})?;
let status = inner
.send_grid
.send(
sendgrid::Mail::new()
.add_to((msg.email.as_str(), msg.login.as_str()).into())
.add_from(&inner.config.lock().mail().smtp_from())
.add_subject("Welcome")
.add_html(html.as_str())
.build(),
)
.await;
log::debug!("{:?}", status);
Ok(())
} }

View File

@ -4,7 +4,7 @@ use std::ffi::OsStr;
use std::io::Write; use std::io::Write;
use config::SharedAppConfig; use config::SharedAppConfig;
use model::{FileName, LocalPath}; use model::{FileName, LocalPath, UniqueName};
#[macro_export] #[macro_export]
macro_rules! fs_async_handler { macro_rules! fs_async_handler {
@ -147,9 +147,10 @@ pub(crate) async fn remove_file(msg: RemoveFile, config: SharedAppConfig) -> Res
pub struct WriteResult { pub struct WriteResult {
/// Unique file name created with UUID and original file extension /// Unique file name created with UUID and original file extension
pub unique_name: FileName, pub unique_name: UniqueName,
/// Path to file in local storage /// Path to file in local storage
pub local_path: LocalPath, pub local_path: LocalPath,
pub file_name: FileName,
} }
#[derive(actix::Message)] #[derive(actix::Message)]
@ -218,7 +219,8 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
log::debug!("File {:?} successfully written", unique_name); log::debug!("File {:?} successfully written", unique_name);
Ok(WriteResult { Ok(WriteResult {
unique_name: FileName::from(unique_name), file_name: FileName::new(file_name),
unique_name: UniqueName::new(unique_name),
local_path: LocalPath::from(path.to_str().unwrap_or_default().to_string()), local_path: LocalPath::from(path.to_str().unwrap_or_default().to_string()),
}) })
} }

View File

@ -27,7 +27,7 @@ actix-identity = { version = "0.4", features = [] }
actix-web-opentelemetry = { version = "0.12", features = [] } actix-web-opentelemetry = { version = "0.12", features = [] }
actix-session = { version = "0.6", features = ["actix-redis", "redis-actor-session"] } actix-session = { version = "0.6", features = ["actix-redis", "redis-actor-session"] }
actix-redis = { version = "0.11", features = [] } actix-redis = { version = "0.11", features = [] }
actix-files = { version = "0.6", features = ["experimental-io-uring"] } actix-files = { version = "0.6", features = [] }
actix-multipart = { version = "0.4", features = [] } actix-multipart = { version = "0.4", features = [] }
gumdrop = { version = "0.8", features = [] } gumdrop = { version = "0.8", features = [] }

View File

@ -88,6 +88,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
.service({ .service({
let l = app_config.lock(); let l = app_config.lock();
actix_files::Files::new(&l.files().public_path(), l.files().local_path()) actix_files::Files::new(&l.files().public_path(), l.files().local_path())
.use_etag(true)
}) })
.default_service(actix_web::web::to(actix_web::HttpResponse::Ok)) .default_service(actix_web::web::to(actix_web::HttpResponse::Ok))
}) })
@ -181,20 +182,14 @@ async fn test_mailer(opts: TestMailerOpts) -> Result<()> {
let manager = email_manager::EmailManager::build(config) let manager = email_manager::EmailManager::build(config)
.expect("Invalid email manager config") .expect("Invalid email manager config")
.start(); .start();
if manager manager
.send(TestMail { .send(TestMail {
receiver: opts.receiver.expect("e-mail address is required"), receiver: opts.receiver.expect("e-mail address is required"),
}) })
.await .await
.expect("Failed to execute actor") .expect("Failed to execute actor")
.expect("Failed to send email") .expect("Failed to send email");
.success
{
println!("Success!"); println!("Success!");
} else {
eprintln!("Failure!");
std::process::exit(1);
}
Ok(()) Ok(())
} }

View File

@ -2,6 +2,7 @@ use actix::Addr;
use actix_session::Session; use actix_session::Session;
use actix_web::web::{Data, Json, ServiceConfig}; use actix_web::web::{Data, Json, ServiceConfig};
use actix_web::{delete, get, patch, post, HttpResponse}; use actix_web::{delete, get, patch, post, HttpResponse};
use config::SharedAppConfig;
use database_manager::Database; use database_manager::Database;
use model::{ use model::{
api, Days, Price, ProductCategory, ProductId, ProductLongDesc, ProductName, ProductShortDesc, api, Days, Price, ProductCategory, ProductId, ProductLongDesc, ProductName, ProductShortDesc,
@ -18,9 +19,15 @@ use crate::{admin_send_db, routes};
async fn products( async fn products(
session: Session, session: Session,
db: Data<Addr<Database>>, db: Data<Addr<Database>>,
config: Data<SharedAppConfig>,
) -> routes::Result<Json<api::Products>> { ) -> routes::Result<Json<api::Products>> {
session.require_admin()?; session.require_admin()?;
let public_path = {
let l = config.lock();
l.files().public_path()
};
let db = db.into_inner(); let db = db.into_inner();
let products = admin_send_db!(db, database_manager::AllProducts); let products = admin_send_db!(db, database_manager::AllProducts);
@ -30,7 +37,7 @@ async fn products(
product_ids: products.iter().map(|p| p.id).collect() product_ids: products.iter().map(|p| p.id).collect()
} }
); );
Ok(Json((products, photos).into())) Ok(Json((products, photos, public_path).into()))
} }
#[derive(Deserialize)] #[derive(Deserialize)]

View File

@ -64,9 +64,10 @@ async fn upload_product_image(
let write = async { let write = async {
let fs_manager::WriteResult { let fs_manager::WriteResult {
local_path, local_path,
unique_name: file_name, unique_name,
file_name,
} = match fs.send(msg).await { } = match fs.send(msg).await {
Ok(Ok(file_name)) => file_name, Ok(Ok(res)) => res,
Ok(Err(e)) => { Ok(Err(e)) => {
log::error!("{e}"); log::error!("{e}");
return Err(UploadError::FileStreamBroken); return Err(UploadError::FileStreamBroken);
@ -80,7 +81,8 @@ async fn upload_product_image(
db, db,
database_manager::CreatePhoto { database_manager::CreatePhoto {
local_path, local_path,
file_name file_name,
unique_name
}, },
UploadError::DbSave UploadError::DbSave
); );

View File

@ -12,8 +12,15 @@ use crate::routes::{self, Result};
use crate::{public_send_db, Login, Password}; use crate::{public_send_db, Login, Password};
#[get("/products")] #[get("/products")]
async fn products(db: Data<Addr<Database>>) -> Result<Json<api::Products>> { async fn products(
db: Data<Addr<Database>>,
config: Data<SharedAppConfig>,
) -> Result<Json<api::Products>> {
let db = db.into_inner(); let db = db.into_inner();
let public_path = {
let l = config.lock();
l.files().public_path()
};
let products: Vec<model::Product> = public_send_db!(owned, db, database_manager::AllProducts); let products: Vec<model::Product> = public_send_db!(owned, db, database_manager::AllProducts);
let photos: Vec<model::ProductLinkedPhoto> = public_send_db!( let photos: Vec<model::ProductLinkedPhoto> = public_send_db!(
@ -23,7 +30,7 @@ async fn products(db: Data<Addr<Database>>) -> Result<Json<api::Products>> {
product_ids: products.iter().map(|p| p.id).collect() product_ids: products.iter().map(|p| p.id).collect()
} }
); );
Ok(Json((products, photos).into())) Ok(Json((products, photos, public_path).into()))
} }
#[get("/stocks")] #[get("/stocks")]

View File

@ -0,0 +1,2 @@
ALTER TABLE photos
ADD COLUMN unique_name TEXT NOT NULL DEFAULT gen_random_uuid() :: text UNIQUE;

View File

@ -0,0 +1 @@
update photos set unique_name = substring(local_path, 7);

View File

@ -46,7 +46,9 @@ pub struct PaymentConfig {
payu_client_secret: Option<pay_u::ClientSecret>, payu_client_secret: Option<pay_u::ClientSecret>,
/// Create payu account and copy here merchant id /// Create payu account and copy here merchant id
payu_client_merchant_id: Option<pay_u::MerchantPosId>, payu_client_merchant_id: Option<pay_u::MerchantPosId>,
currency: Option<String>,
/// Allow customers to pay on site /// Allow customers to pay on site
#[serde(default)]
optional_payment: bool, optional_payment: bool,
} }
@ -62,6 +64,7 @@ impl Example for PaymentConfig {
/// Create payu account and copy here merchant id /// Create payu account and copy here merchant id
payu_client_merchant_id: Some(pay_u::MerchantPosId::from(0)), payu_client_merchant_id: Some(pay_u::MerchantPosId::from(0)),
/// Allow customers to pay on site /// Allow customers to pay on site
currency: None,
optional_payment: true, optional_payment: true,
} }
} }
@ -104,6 +107,14 @@ impl PaymentConfig {
"payment config payu_client_merchant_id nor PAYU_CLIENT_MERCHANT_ID env was given", "payment config payu_client_merchant_id nor PAYU_CLIENT_MERCHANT_ID env was given",
) )
} }
pub fn currency(&self) -> String {
self.currency
.as_ref()
.cloned()
.or_else(|| std::env::var("CURRENCY").ok())
.unwrap_or_else(|| "PLN".into())
}
} }
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize, Default)]
@ -119,17 +130,23 @@ pub struct WebConfig {
jwt_secret: Option<String>, jwt_secret: Option<String>,
bind: Option<String>, bind: Option<String>,
port: Option<u16>, port: Option<u16>,
/// Name used in email signature
signature: Option<String>,
/// Shop name
service_name: Option<String>,
} }
impl Example for WebConfig { impl Example for WebConfig {
fn example() -> Self { fn example() -> Self {
Self { Self {
host: Some(String::from("https://your.comain.com")), host: Some("https://your.comain.com".into()),
pass_salt: Some(String::from("Generate it with bazzar generate-hash")), pass_salt: Some("Generate it with bazzar generate-hash".into()),
session_secret: Some(String::from("100 characters long random string")), session_secret: Some("100 characters long random string".into()),
jwt_secret: Some(String::from("100 characters long random string")), jwt_secret: Some("100 characters long random string".into()),
bind: Some(String::from("0.0.0.0")), bind: Some("0.0.0.0".into()),
port: Some(8080), port: Some(8080),
signature: Some("John Doe".into()),
service_name: Some("bazzar".into()),
} }
} }
} }
@ -198,6 +215,22 @@ impl WebConfig {
pub fn set_port(&mut self, port: u16) { pub fn set_port(&mut self, port: u16) {
self.port = Some(port); self.port = Some(port);
} }
pub fn signature(&self) -> String {
self.signature
.as_ref()
.cloned()
.or_else(|| std::env::var("SIGNATURE").ok())
.expect("Web config signature nor SIGNATURE env was given")
}
pub fn service_name(&self) -> String {
self.service_name
.as_ref()
.cloned()
.or_else(|| std::env::var("SERVICE_NAME").ok())
.expect("Web config service_name nor SERVICE_NAME env was given")
}
} }
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize, Default)]
@ -210,15 +243,11 @@ pub struct MailConfig {
impl Example for MailConfig { impl Example for MailConfig {
fn example() -> Self { fn example() -> Self {
Self { Self {
sendgrid_secret: Some(String::from( sendgrid_secret: Some("Create sendgrid account and copy credentials here".into()),
"Create sendgrid account and copy credentials here", sendgrid_api_key: Some("Create sendgrid account and copy credentials here".into()),
)), smtp_from: Some(
sendgrid_api_key: Some(String::from( "Valid sendgrid authorized email address. Example: contact@example.com".into(),
"Create sendgrid account and copy credentials here", ),
)),
smtp_from: Some(String::from(
"Valid sendgrid authorized email address. Example: contact@example.com",
)),
} }
} }
} }
@ -257,7 +286,7 @@ pub struct DatabaseConfig {
impl Example for DatabaseConfig { impl Example for DatabaseConfig {
fn example() -> Self { fn example() -> Self {
Self { Self {
url: Some(String::from("postgres://postgres@localhost/bazzar")), url: Some("postgres://postgres@localhost/bazzar".into()),
} }
} }
} }
@ -282,6 +311,7 @@ pub struct SearchConfig {
sonic_search_pass: Option<String>, sonic_search_pass: Option<String>,
sonic_ingest_addr: Option<String>, sonic_ingest_addr: Option<String>,
sonic_ingest_pass: Option<String>, sonic_ingest_pass: Option<String>,
#[serde(default)]
search_active: bool, search_active: bool,
} }
@ -300,10 +330,10 @@ impl Example for SearchConfig {
impl Default for SearchConfig { impl Default for SearchConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
sonic_search_addr: Some(String::from("0.0.0.0:1491")), sonic_search_addr: Some("0.0.0.0:1491".into()),
sonic_search_pass: Some(String::from("SecretPassword")), sonic_search_pass: Some("SecretPassword".into()),
sonic_ingest_addr: Some(String::from("0.0.0.0:1491")), sonic_ingest_addr: Some("0.0.0.0:1491".into()),
sonic_ingest_pass: Some(String::from("SecretPassword")), sonic_ingest_pass: Some("SecretPassword".into()),
search_active: true, search_active: true,
} }
} }
@ -359,8 +389,8 @@ pub struct FilesConfig {
impl Example for FilesConfig { impl Example for FilesConfig {
fn example() -> Self { fn example() -> Self {
Self { Self {
public_path: Some(String::from("/uploads")), public_path: Some("/uploads".into()),
local_path: Some(String::from("/var/local/bazzar")), local_path: Some("/var/local/bazzar".into()),
} }
} }
} }
@ -368,8 +398,8 @@ impl Example for FilesConfig {
impl Default for FilesConfig { impl Default for FilesConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
public_path: Some(String::from("/uploads")), public_path: Some("/uploads".into()),
local_path: Some(String::from("/var/local/bazzar")), local_path: Some("/var/local/bazzar".into()),
} }
} }
} }
@ -380,7 +410,7 @@ impl FilesConfig {
.as_ref() .as_ref()
.cloned() .cloned()
.or_else(|| std::env::var("FILES_PUBLIC_PATH").ok()) .or_else(|| std::env::var("FILES_PUBLIC_PATH").ok())
.unwrap_or_else(|| String::from("/uploads")) .unwrap_or_else(|| "/uploads".into())
} }
pub fn local_path(&self) -> String { pub fn local_path(&self) -> String {
@ -388,17 +418,23 @@ impl FilesConfig {
.as_ref() .as_ref()
.cloned() .cloned()
.or_else(|| std::env::var("FILES_LOCAL_PATH").ok()) .or_else(|| std::env::var("FILES_LOCAL_PATH").ok())
.unwrap_or_else(|| String::from("/var/local/bazzar")) .unwrap_or_else(|| "/var/local/bazzar".into())
} }
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct AppConfig { pub struct AppConfig {
#[serde(default)]
payment: PaymentConfig, payment: PaymentConfig,
#[serde(default)]
web: WebConfig, web: WebConfig,
#[serde(default)]
mail: MailConfig, mail: MailConfig,
#[serde(default)]
database: DatabaseConfig, database: DatabaseConfig,
#[serde(default)]
search: SearchConfig, search: SearchConfig,
#[serde(default)]
files: FilesConfig, files: FilesConfig,
#[serde(skip)] #[serde(skip)]
config_path: String, config_path: String,
@ -514,6 +550,8 @@ SESSION_SECRET - 100 characters admin session encryption
JWT_SECRET - 100 characters user session encryption JWT_SECRET - 100 characters user session encryption
BAZZAR_BIND - address to which server should be bind, typically 0.0.0.0 BAZZAR_BIND - address to which server should be bind, typically 0.0.0.0
BAZZAR_PORT - port which server should use, typically 80 BAZZAR_PORT - port which server should use, typically 80
SIGNATURE - Signature used in e-mails
SERVICE_NAME - Shop name
SENDGRID_SECRET - e-mail sending service secret SENDGRID_SECRET - e-mail sending service secret
SENDGRID_API_KEY - e-mail sending service api key SENDGRID_API_KEY - e-mail sending service api key
@ -536,6 +574,6 @@ FILES_LOCAL_PATH - path where files are saved on server
} }
pub fn save(config_path: &str, config: &mut AppConfig) { pub fn save(config_path: &str, config: &mut AppConfig) {
config.config_path = String::from(config_path); config.config_path = config_path.into();
std::fs::write(config_path, toml::to_string_pretty(&config).unwrap()).unwrap(); std::fs::write(config_path, toml::to_string_pretty(&config).unwrap()).unwrap();
} }

View File

@ -1,8 +1,12 @@
use serde::Serialize; use derive_more::Deref;
#[cfg(feature = "dummy")]
use fake::Fake;
use serde::{Deserialize, Serialize};
use crate::ProductLinkedPhoto; use crate::ProductLinkedPhoto;
#[derive(Serialize, Debug)] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)]
#[serde(transparent)] #[serde(transparent)]
pub struct AccountOrders(pub Vec<AccountOrder>); pub struct AccountOrders(pub Vec<AccountOrder>);
@ -58,7 +62,8 @@ impl From<(crate::AccountOrder, Vec<crate::OrderItem>)> for AccountOrder {
} }
} }
#[derive(Serialize, Debug)] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)]
pub struct AccountOrder { pub struct AccountOrder {
pub id: crate::AccountOrderId, pub id: crate::AccountOrderId,
pub buyer_id: crate::AccountId, pub buyer_id: crate::AccountId,
@ -67,7 +72,8 @@ pub struct AccountOrder {
pub items: Vec<crate::OrderItem>, pub items: Vec<crate::OrderItem>,
} }
#[derive(Serialize, Debug)] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)]
pub struct ShoppingCartItem { pub struct ShoppingCartItem {
pub id: crate::ShoppingCartId, pub id: crate::ShoppingCartId,
pub product_id: crate::ProductId, pub product_id: crate::ProductId,
@ -76,7 +82,8 @@ pub struct ShoppingCartItem {
pub quantity_unit: crate::QuantityUnit, pub quantity_unit: crate::QuantityUnit,
} }
#[derive(Serialize, Debug)] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)]
pub struct ShoppingCart { pub struct ShoppingCart {
pub id: crate::ShoppingCartId, pub id: crate::ShoppingCartId,
pub buyer_id: crate::AccountId, pub buyer_id: crate::AccountId,
@ -124,13 +131,17 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
} }
} }
#[derive(Serialize, Debug)] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)]
pub struct Photo { pub struct Photo {
pub id: crate::PhotoId, pub id: crate::PhotoId,
pub file_name: crate::FileName, pub file_name: crate::FileName,
pub url: String,
pub unique_name: crate::UniqueName,
} }
#[derive(Serialize, Debug)] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug)]
pub struct Product { pub struct Product {
pub id: crate::ProductId, pub id: crate::ProductId,
pub name: crate::ProductName, pub name: crate::ProductName,
@ -142,7 +153,7 @@ pub struct Product {
pub photos: Vec<Photo>, pub photos: Vec<Photo>,
} }
impl From<(crate::Product, &mut Vec<crate::ProductLinkedPhoto>)> for Product { impl<'path> From<(crate::Product, &mut Vec<ProductLinkedPhoto>, &'path str)> for Product {
fn from( fn from(
( (
crate::Product { crate::Product {
@ -155,7 +166,8 @@ impl From<(crate::Product, &mut Vec<crate::ProductLinkedPhoto>)> for Product {
deliver_days_flag, deliver_days_flag,
}, },
photos, photos,
): (crate::Product, &mut Vec<ProductLinkedPhoto>), public_path,
): (crate::Product, &mut Vec<ProductLinkedPhoto>, &'path str),
) -> Self { ) -> Self {
Self { Self {
id, id,
@ -173,23 +185,32 @@ impl From<(crate::Product, &mut Vec<crate::ProductLinkedPhoto>)> for Product {
local_path: _, local_path: _,
file_name, file_name,
product_id: _, product_id: _,
}| Photo { id, file_name }, unique_name,
}| Photo {
id,
url: format!("{}/{}", public_path, unique_name.as_str()),
unique_name,
file_name,
},
) )
.collect(), .collect(),
} }
} }
} }
#[derive(Serialize, Debug)] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug, Deref)]
#[serde(transparent)] #[serde(transparent)]
pub struct Products(Vec<Product>); pub struct Products(pub Vec<Product>);
impl From<(Vec<crate::Product>, Vec<crate::ProductLinkedPhoto>)> for Products { impl From<(Vec<crate::Product>, Vec<ProductLinkedPhoto>, String)> for Products {
fn from((products, mut photos): (Vec<crate::Product>, Vec<crate::ProductLinkedPhoto>)) -> Self { fn from(
(products, mut photos, public_path): (Vec<crate::Product>, Vec<ProductLinkedPhoto>, String),
) -> Self {
Self( Self(
products products
.into_iter() .into_iter()
.map(|p| (p, &mut photos).into()) .map(|p| (p, &mut photos, public_path.as_str()).into())
.collect(), .collect(),
) )
} }

View File

@ -12,6 +12,7 @@ use std::str::FromStr;
use derive_more::{Deref, Display, From}; use derive_more::{Deref, Display, From};
#[cfg(feature = "dummy")] #[cfg(feature = "dummy")]
use fake::Fake; use fake::Fake;
#[cfg(feature = "dummy")]
use rand::Rng; use rand::Rng;
use serde::de::{Error, Visitor}; use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
@ -181,10 +182,16 @@ impl TryFrom<i32> for Quantity {
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Deserialize, Serialize, Debug, Deref, From, Display)] #[derive(Deserialize, Serialize, Debug, Clone, Deref, From, Display)]
#[serde(transparent)] #[serde(transparent)]
pub struct Login(String); pub struct Login(String);
impl Login {
pub fn new<S: Into<String>>(s: S) -> Self {
Self(s.into())
}
}
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Debug, Deref, From, Display)] #[derive(Serialize, Debug, Deref, From, Display)]
@ -422,6 +429,18 @@ where
} }
} }
#[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Deserialize, Debug, Deref, Display)]
#[serde(transparent)]
pub struct ResetToken(String);
impl ResetToken {
pub fn new<S: Into<String>>(s: S) -> Self {
Self(s.into())
}
}
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Deserialize, Debug, Deref, From, Display)] #[derive(Serialize, Deserialize, Debug, Deref, From, Display)]
@ -728,12 +747,36 @@ pub struct Token {
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)] #[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct TokenString(String); pub struct TokenString(String);
impl TokenString {
pub fn new<S: Into<String>>(s: S) -> Self {
Self(s.into())
}
}
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)] #[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct LocalPath(String); pub struct LocalPath(String);
impl LocalPath {
pub fn new<S: Into<String>>(s: S) -> Self {
Self(s.into())
}
}
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct UniqueName(String);
impl UniqueName {
pub fn new<S: Into<String>>(s: S) -> Self {
Self(s.into())
}
}
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))] #[cfg_attr(feature = "db", sqlx(transparent))]
@ -765,6 +808,7 @@ pub struct Photo {
pub id: PhotoId, pub id: PhotoId,
pub local_path: LocalPath, pub local_path: LocalPath,
pub file_name: FileName, pub file_name: FileName,
pub unique_name: UniqueName,
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "dummy", derive(fake::Dummy))]
@ -774,6 +818,7 @@ pub struct ProductLinkedPhoto {
pub id: PhotoId, pub id: PhotoId,
pub local_path: LocalPath, pub local_path: LocalPath,
pub file_name: FileName, pub file_name: FileName,
pub unique_name: UniqueName,
pub product_id: ProductId, pub product_id: ProductId,
} }

View File

@ -7,6 +7,8 @@ edition = "2021"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
model = { path = "../shared/model", features = ["dummy"] }
seed = { version = "0.9.1", features = [] } seed = { version = "0.9.1", features = [] }
chrono = { version = "*" } chrono = { version = "*" }
@ -21,6 +23,8 @@ web-sys = { version = "0.3.57", features = [] }
indexmap = { version = "1", features = ["serde-1"] } indexmap = { version = "1", features = ["serde-1"] }
rusty-money = { version = "0.4.1", features = ["iso"] }
[profile.release] [profile.release]
lto = true lto = true
opt-level = 's' opt-level = 's'

View File

@ -1,2 +1,10 @@
[build] [build]
target = "index.html" target = "index.html"
[[proxy]]
rewrite = "/api/v1/"
backend = "http://localhost:8080/api/v1"
[[proxy]]
rewrite = "/files"
backend = "http://localhost:8080/files"

View File

@ -4,9 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bazzar</title> <title>Bazzar</title>
<!-- <link data-trunk rel="css" href="public/index.css"/>--> <link data-trunk rel="css" href="tmp/tailwind.css"/>
<link data-trunk rel="css" href="assets/css/normalize.css"/>
<link data-trunk rel="css" href="assets/css/skeleton.css"/>
<base data-trunk-public-url/> <base data-trunk-public-url/>
<link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css"> <link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
</head> </head>

12
web/package.json Normal file
View File

@ -0,0 +1,12 @@
{
"dependencies": {
"@tailwindcss/aspect-ratio": "^0.4.0",
"@tailwindcss/forms": "^0.5.1",
"@tailwindcss/line-clamp": "^0.4.0",
"@tailwindcss/typography": "^0.5.2",
"autoprefixer": "^10.4.7",
"postcss": "^8.4.13",
"tailwindcss": "^3.0.24",
"tailwindcss-debug-screens": "^2.2.1"
}
}

1
web/src/api.rs Normal file
View File

@ -0,0 +1 @@
pub mod public;

11
web/src/api/public.rs Normal file
View File

@ -0,0 +1,11 @@
use seed::prelude::*;
pub async fn fetch_products() -> fetch::Result<model::api::Products> {
Request::new("/api/v1/products")
.method(Method::Get)
.fetch()
.await?
.check_status()?
.json()
.await
}

3
web/src/input.css Normal file
View File

@ -0,0 +1,3 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

View File

@ -1,18 +1,32 @@
pub mod api;
mod model; mod model;
mod pages; mod pages;
use model::Model;
use seed::empty; use seed::empty;
use seed::prelude::*; use seed::prelude::*;
use crate::model::Model;
use crate::pages::{Msg, Page, PublicPage}; use crate::pages::{Msg, Page, PublicPage};
macro_rules! fetch_page {
(public $model: expr, $page: ident, $ret: expr) => {{
let p = match &mut $model.page {
crate::pages::Page::Public(p) => p,
_ => return $ret,
};
match p {
crate::pages::PublicPage::$page(p) => p,
_ => return $ret,
}
}};
}
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model { fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
Model { Model {
token: LocalStorage::get("auth-token").ok(), token: LocalStorage::get("auth-token").ok(),
page: Page::Public(PublicPage::Listing(pages::public::listing::init( page: Page::Public(PublicPage::Listing(pages::public::listing::init(
url, url,
&mut orders.proxy(|msg| Msg::Public(pages::public::Msg::Listing(msg))), &mut orders.proxy(proxy_public_listing),
))), ))),
} }
} }
@ -20,7 +34,10 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) { fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
Msg::UrlChanged(subs::UrlChanged(url)) => model.page = Page::init(url, orders), Msg::UrlChanged(subs::UrlChanged(url)) => model.page = Page::init(url, orders),
Msg::Public(_) => {} Msg::Public(pages::public::Msg::Listing(msg)) => {
let page = fetch_page!(public model, Listing, ());
pages::public::listing::update(msg, page, &mut orders.proxy(proxy_public_listing));
}
} }
} }
@ -36,3 +53,7 @@ fn view(model: &Model) -> Node<Msg> {
pub fn start() { pub fn start() {
App::start("main", init, update, view); App::start("main", init, update, view);
} }
fn proxy_public_listing(msg: pages::public::listing::Msg) -> Msg {
Msg::Public(pages::public::Msg::Listing(msg))
}

View File

@ -3,25 +3,93 @@ use seed::prelude::*;
use seed::*; use seed::*;
#[derive(Debug)] #[derive(Debug)]
pub struct Model {} pub struct Model {
pub products: Vec<model::api::Product>,
pub errors: Vec<String>,
}
#[derive(Debug)] #[derive(Debug)]
pub enum Msg {} pub enum Msg {
FetchProducts,
pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model { ProductFetched(fetch::Result<model::api::Products>),
Model {}
} }
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {} pub fn init(_url: Url, orders: &mut impl Orders<Msg>) -> Model {
orders.send_msg(Msg::FetchProducts);
Model {
products: vec![],
errors: vec![],
}
}
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg {
Msg::FetchProducts => {
orders.skip().perform_cmd({
async { Msg::ProductFetched(crate::api::public::fetch_products().await) }
});
}
Msg::ProductFetched(Ok(products)) => {
model.products = products.0;
}
Msg::ProductFetched(Err(_e)) => {
model.errors.push("Failed to load products".into());
}
}
}
pub fn view(model: &Model) -> Node<Msg> { pub fn view(model: &Model) -> Node<Msg> {
let products = model.products.iter().map(product);
div![ div![
C!["container"], C!["grid grid-cols-1 gap-4 lg:grid-cols-6 sm:grid-cols-2"],
products
]
}
fn product(product: &model::api::Product) -> Node<Msg> {
use rusty_money::{iso, Money};
let price = Money::from_minor(**product.price as i64, iso::PLN).to_string();
let _description = product.short_description.as_str();
let name = product.name.as_str();
let img = product
.photos
.first()
.map(|photo| photo.url.as_str())
.unwrap_or_default();
div![ div![
C!["row"], C!["w-full px-4 lg:px-0"],
div![ div![
C!["one-half column"], C!["p-3 bg-white rounded shadow-md"],
p!["This index.html page is a placeholder with the CSS, font and favicon."] div![
div![
C!["relative w-full mb-3 h-62 lg:mb-0"],
img![attrs![
"src" => img,
"alt" => name,
"class" => "object-fill w-full h-full rounded"
]],
],
div![
C!["flex-auto p-2 justify-evenly"],
div![
C!["flex flex-wrap"],
div![
C!["flex items-center justify-between w-full min-w-0"],
h2![
C!["mr-auto text-lg cursor-pointer hover:text-gray-900"],
name
]
]
],
div![
C!["flex items-center justify-between"],
a![C!["px-6 py-2 text-sm text-white bg-indigo-500 rounded-lg outline-none hover:bg-indigo-600 ring-indigo-300"], "Add to cart"],
div![C!["mt-1 text-xl font-semibold"], price],
]
]
] ]
] ]
] ]

822
web/static/output.css Normal file
View File

@ -0,0 +1,822 @@
/*
! tailwindcss v3.0.24 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
*/
html {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
line-height: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input:-ms-input-placeholder, textarea:-ms-input-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/*
Ensure the default browser behavior of the `hidden` attribute.
*/
[hidden] {
display: none;
}
[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
border-radius: 0px;
padding-top: 0.5rem;
padding-right: 0.75rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
font-size: 1rem;
line-height: 1.5rem;
--tw-shadow: 0 0 #0000;
}
[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
border-color: #2563eb;
}
input::-moz-placeholder, textarea::-moz-placeholder {
color: #6b7280;
opacity: 1;
}
input:-ms-input-placeholder, textarea:-ms-input-placeholder {
color: #6b7280;
opacity: 1;
}
input::placeholder,textarea::placeholder {
color: #6b7280;
opacity: 1;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-date-and-time-value {
min-height: 1.5em;
}
::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field {
padding-top: 0;
padding-bottom: 0;
}
select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
-webkit-print-color-adjust: exact;
color-adjust: exact;
print-color-adjust: exact;
}
[multiple] {
background-image: initial;
background-position: initial;
background-repeat: unset;
background-size: initial;
padding-right: 0.75rem;
-webkit-print-color-adjust: unset;
color-adjust: unset;
print-color-adjust: unset;
}
[type='checkbox'],[type='radio'] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding: 0;
-webkit-print-color-adjust: exact;
color-adjust: exact;
print-color-adjust: exact;
display: inline-block;
vertical-align: middle;
background-origin: border-box;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
flex-shrink: 0;
height: 1rem;
width: 1rem;
color: #2563eb;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
--tw-shadow: 0 0 #0000;
}
[type='checkbox'] {
border-radius: 0px;
}
[type='radio'] {
border-radius: 100%;
}
[type='checkbox']:focus,[type='radio']:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
--tw-ring-offset-width: 2px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
[type='checkbox']:checked,[type='radio']:checked {
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
[type='checkbox']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
[type='radio']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
}
[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus {
border-color: transparent;
background-color: currentColor;
}
[type='checkbox']:indeterminate {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus {
border-color: transparent;
background-color: currentColor;
}
[type='file'] {
background: unset;
border-color: inherit;
border-width: 0;
border-radius: 0;
padding: 0;
font-size: unset;
line-height: inherit;
}
[type='file']:focus {
outline: 1px solid ButtonText;
outline: 1px auto -webkit-focus-ring-color;
}
*, ::before, ::after {
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.debug-screens::before {
position: fixed;
z-index: 2147483647;
bottom: 0;
left: 0;
padding: .3333333em .5em;
font-size: 12px;
line-height: 1;
font-family: sans-serif;
background-color: #000;
color: #fff;
box-shadow: 0 0 0 1px #fff;
content: 'screen: _';
}
@media (min-width: 640px) {
.debug-screens::before {
content: 'screen: sm';
}
}
@media (min-width: 768px) {
.debug-screens::before {
content: 'screen: md';
}
}
@media (min-width: 1024px) {
.debug-screens::before {
content: 'screen: lg';
}
}
@media (min-width: 1280px) {
.debug-screens::before {
content: 'screen: xl';
}
}
@media (min-width: 1536px) {
.debug-screens::before {
content: 'screen: 2xl';
}
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.mt-12 {
margin-top: 3rem;
}
.mb-3 {
margin-bottom: 0.75rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.inline-block {
display: inline-block;
}
.rounded-lg {
border-radius: 0.5rem;
}
.rounded-r {
border-top-right-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
}
.border {
border-width: 1px;
}
.border-transparent {
border-color: transparent;
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-blue-600 {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.p-8 {
padding: 2rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.font-semibold {
font-weight: 600;
}
.leading-normal {
line-height: 1.5;
}
.tracking-tight {
letter-spacing: -0.025em;
}
.text-sky-600 {
--tw-text-opacity: 1;
color: rgb(2 132 199 / var(--tw-text-opacity));
}
.text-sky-900 {
--tw-text-opacity: 1;
color: rgb(12 74 110 / var(--tw-text-opacity));
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:bg-blue-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
}

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Reset password</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/output.css" rel="stylesheet">
</head>
<body>
<section class="debug-screens">
<div class="container p-8 mx-auto mt-12 bg-white">
<div class="rounded-lg">
<h1 class="mb-3 text-xl font-semibold tracking-tight text-sky-600">Trouble signing in?</h1>
<p class="mb-2 leading-normal text-sky-900">Resetting your password is easy.</p>
<p class="mb-2 leading-normal text-sky-900">Just press the button below and follow the instructions. Well have you up and running in no time.</p>
<p>
<a
class="px-4 py-2 inline-block text-white bg-blue-600 border border-transparent rounded-r hover:bg-blue-700"
href="{url}"
>
Reset Password
</a>
</p>
<p class="mb-2 leading-normal text-sky-900">If you did not make this request then please ignore this email.</p>
</div>
</div>
</section>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome to {service_name}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/output.css" rel="stylesheet">
</head>
<body>
<section class="debug-screens">
<div class="container p-8 mx-auto mt-12 bg-white">
<div class="rounded-lg">
<h1 class="mb-3 text-xl font-semibold tracking-tight text-sky-600">Hi {login}</h1>
<p class="mb-2 leading-normal text-sky-900">
Welcome to {service_name} were excited to have you on board and wed love to say thank you on behalf
of our whole company for chosing us.
</p>
<p class="mb-2 leading-normal text-sky-900">Take care,</p>
<p class="mb-2 leading-normal text-sky-900">{signature}</p>
</div>
</div>
</section>
</body>
</html>

24
web/tailwind.config.js Normal file
View File

@ -0,0 +1,24 @@
module.exports = {
theme: {
extend: {
spacing: {
'96': '24rem',
},
},
},
content: [
"./static/*.{html,js}",
"./src/**/*.{html,js,rs}"
],
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/line-clamp'),
require('@tailwindcss/aspect-ratio'),
require('tailwindcss-debug-screens'),
],
experimental: {
optimizeUniversalDefaults: true
}
}

507
web/yarn.lock Normal file
View File

@ -0,0 +1,507 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
dependencies:
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@tailwindcss/aspect-ratio@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.0.tgz#c635dd7331cbcc1b111cebdc2647dd3493ebdd3e"
integrity sha512-WJu0I4PpqNPuutpaA9zDUq2JXR+lorZ7PbLcKNLmb6GL9/HLfC7w3CRsMhJF4BbYd/lkY6CfXOvkYpuGnZfkpQ==
"@tailwindcss/forms@^0.5.1":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.1.tgz#7fe86b9b67e6d91cb902e2d3f4ebe561cc057a13"
integrity sha512-QSwsFORnC2BAP0lRzQkz1pw+EzIiiPdk4e27vGQjyXkwJPeC7iLIRVndJzf9CJVbcrrIcirb/TfxF3gRTyFEVA==
dependencies:
mini-svg-data-uri "^1.2.3"
"@tailwindcss/line-clamp@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.0.tgz#03353e31e77636b785f2336e8c978502cec1de81"
integrity sha512-HQZo6gfx1D0+DU3nWlNLD5iA6Ef4JAXh0LeD8lOGrJwEDBwwJNKQza6WoXhhY1uQrxOuU8ROxV7CqiQV4CoiLw==
"@tailwindcss/typography@^0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.2.tgz#24b069dab24d7a2467d01aca0dd432cb4b29f0ee"
integrity sha512-coq8DBABRPFcVhVIk6IbKyyHUt7YTEC/C992tatFB+yEx5WGBQrCgsSFjxHUr8AWXphWckadVJbominEduYBqw==
dependencies:
lodash.castarray "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2"
acorn-node@^1.6.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
dependencies:
acorn "^7.0.0"
acorn-walk "^7.0.0"
xtend "^4.0.2"
acorn-walk@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn@^7.0.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb"
integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==
autoprefixer@^10.4.7:
version "10.4.7"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf"
integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==
dependencies:
browserslist "^4.20.3"
caniuse-lite "^1.0.30001335"
fraction.js "^4.2.0"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
browserslist@^4.20.3:
version "4.20.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf"
integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
dependencies:
caniuse-lite "^1.0.30001332"
electron-to-chromium "^1.4.118"
escalade "^3.1.1"
node-releases "^2.0.3"
picocolors "^1.0.0"
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335:
version "1.0.30001338"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001338.tgz#b5dd7a7941a51a16480bdf6ff82bded1628eec0d"
integrity sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ==
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
color-name@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
detective@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==
dependencies:
acorn-node "^1.6.1"
defined "^1.0.0"
minimist "^1.1.1"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
dlv@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
electron-to-chromium@^1.4.118:
version "1.4.137"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz#186180a45617283f1c012284458510cd99d6787f"
integrity sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
fast-glob@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
dependencies:
reusify "^1.0.4"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
is-glob "^4.0.3"
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.8.1:
version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
lilconfig@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
lodash.castarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
integrity sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
dependencies:
braces "^3.0.2"
picomatch "^2.3.1"
mini-svg-data-uri@^1.2.3:
version "1.4.4"
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939"
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
minimist@^1.1.1:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
nanoid@^3.3.3:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
node-releases@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476"
integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
normalize-range@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
object-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
postcss-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
dependencies:
camelcase-css "^2.0.1"
postcss-load-config@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
dependencies:
lilconfig "^2.0.5"
yaml "^1.10.2"
postcss-nested@5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
dependencies:
postcss-selector-parser "^6.0.6"
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.12, postcss@^8.4.13:
version "8.4.13"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
dependencies:
nanoid "^3.3.3"
picocolors "^1.0.0"
source-map-js "^1.0.2"
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
resolve@^1.22.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
dependencies:
is-core-module "^2.8.1"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
dependencies:
queue-microtask "^1.2.2"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tailwindcss-debug-screens@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tailwindcss-debug-screens/-/tailwindcss-debug-screens-2.2.1.tgz#8dd0854a273daa4f30f4c383370872c6bca337cd"
integrity sha512-EMyA0CYBzqcZJHtVDvBfmYzfx3NxuK4qDyVO5wnzcGOrmJsv25D9xPpWefVTORTvhE6pCh90Z1WYnLUKsg3yMw==
tailwindcss@^3.0.24:
version "3.0.24"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d"
integrity sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig==
dependencies:
arg "^5.0.1"
chokidar "^3.5.3"
color-name "^1.1.4"
detective "^5.2.0"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.2.11"
glob-parent "^6.0.2"
is-glob "^4.0.3"
lilconfig "^2.0.5"
normalize-path "^3.0.0"
object-hash "^3.0.0"
picocolors "^1.0.0"
postcss "^8.4.12"
postcss-js "^4.0.0"
postcss-load-config "^3.1.4"
postcss-nested "5.0.6"
postcss-selector-parser "^6.0.10"
postcss-value-parser "^4.2.0"
quick-lru "^5.1.1"
resolve "^1.22.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==