diff options
author | Tyler Murphy <tylermurphy534@gmail.com> | 2023-01-31 22:21:19 -0500 |
---|---|---|
committer | Tyler Murphy <tylermurphy534@gmail.com> | 2023-01-31 22:21:19 -0500 |
commit | d9125314809e7f03cb155f91d535e94da583a365 (patch) | |
tree | f34bc2e978d5e79f0dc62aa7a5faa8f096b46dc5 | |
parent | fix admin (diff) | |
download | xssbook-d9125314809e7f03cb155f91d535e94da583a365.tar.gz xssbook-d9125314809e7f03cb155f91d535e94da583a365.tar.bz2 xssbook-d9125314809e7f03cb155f91d535e94da583a365.zip |
custosm avatars and banners
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Cargo.lock | 311 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | deployments/docker/Dockerfile | 2 | ||||
-rw-r--r-- | deployments/docker/docker-compose.yml | 1 | ||||
-rw-r--r-- | public/css/main.css | 10 | ||||
-rw-r--r-- | public/css/profile.css | 24 | ||||
-rw-r--r-- | public/image/default/0.png (renamed from public/img/0.png) | bin | 7910 -> 7910 bytes | |||
-rw-r--r-- | public/image/default/1.png (renamed from public/img/1.png) | bin | 5700 -> 5700 bytes | |||
-rw-r--r-- | public/image/default/10.png (renamed from public/img/10.png) | bin | 8045 -> 8045 bytes | |||
-rw-r--r-- | public/image/default/11.png (renamed from public/img/11.png) | bin | 6702 -> 6702 bytes | |||
-rw-r--r-- | public/image/default/12.png (renamed from public/img/12.png) | bin | 6809 -> 6809 bytes | |||
-rw-r--r-- | public/image/default/13.png (renamed from public/img/13.png) | bin | 7080 -> 7080 bytes | |||
-rw-r--r-- | public/image/default/14.png (renamed from public/img/14.png) | bin | 5937 -> 5937 bytes | |||
-rw-r--r-- | public/image/default/15.png (renamed from public/img/15.png) | bin | 6457 -> 6457 bytes | |||
-rw-r--r-- | public/image/default/16.png (renamed from public/img/16.png) | bin | 7261 -> 7261 bytes | |||
-rw-r--r-- | public/image/default/17.png (renamed from public/img/17.png) | bin | 5327 -> 5327 bytes | |||
-rw-r--r-- | public/image/default/18.png (renamed from public/img/18.png) | bin | 7791 -> 7791 bytes | |||
-rw-r--r-- | public/image/default/19.png (renamed from public/img/19.png) | bin | 7928 -> 7928 bytes | |||
-rw-r--r-- | public/image/default/2.png (renamed from public/img/2.png) | bin | 7258 -> 7258 bytes | |||
-rw-r--r-- | public/image/default/20.png (renamed from public/img/20.png) | bin | 8212 -> 8212 bytes | |||
-rw-r--r-- | public/image/default/21.png (renamed from public/img/21.png) | bin | 6249 -> 6249 bytes | |||
-rw-r--r-- | public/image/default/22.png (renamed from public/img/22.png) | bin | 6896 -> 6896 bytes | |||
-rw-r--r-- | public/image/default/23.png (renamed from public/img/23.png) | bin | 7284 -> 7284 bytes | |||
-rw-r--r-- | public/image/default/24.png (renamed from public/img/24.png) | bin | 6256 -> 6256 bytes | |||
-rw-r--r-- | public/image/default/3.png (renamed from public/img/3.png) | bin | 7672 -> 7672 bytes | |||
-rw-r--r-- | public/image/default/4.png (renamed from public/img/4.png) | bin | 7119 -> 7119 bytes | |||
-rw-r--r-- | public/image/default/5.png (renamed from public/img/5.png) | bin | 6799 -> 6799 bytes | |||
-rw-r--r-- | public/image/default/6.png (renamed from public/img/6.png) | bin | 5931 -> 5931 bytes | |||
-rw-r--r-- | public/image/default/7.png (renamed from public/img/7.png) | bin | 6681 -> 6681 bytes | |||
-rw-r--r-- | public/image/default/8.png (renamed from public/img/8.png) | bin | 6546 -> 6546 bytes | |||
-rw-r--r-- | public/image/default/9.png (renamed from public/img/9.png) | bin | 7635 -> 7635 bytes | |||
-rw-r--r-- | public/js/api.js | 29 | ||||
-rw-r--r-- | public/js/main.js | 6 | ||||
-rw-r--r-- | public/js/profile.js | 27 | ||||
-rw-r--r-- | src/api/image.rs | 54 | ||||
-rw-r--r-- | src/api/mod.rs | 1 | ||||
-rw-r--r-- | src/api/users.rs | 28 | ||||
-rw-r--r-- | src/main.rs | 14 | ||||
-rw-r--r-- | src/types/extract.rs | 74 |
41 files changed, 576 insertions, 23 deletions
@@ -1,2 +1,3 @@ /target -xssbook.db
\ No newline at end of file +xssbook.db +/public/avatar/custom/*
\ No newline at end of file @@ -3,6 +3,12 @@ version = 3 [[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -98,6 +104,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -119,6 +131,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] +name = "bytemuck" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] name = "bytes" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -137,6 +161,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] name = "cookie" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -157,6 +187,49 @@ dependencies = [ ] [[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] name = "crossbeam-utils" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -166,6 +239,12 @@ dependencies = [ ] [[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -199,6 +278,27 @@ dependencies = [ ] [[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "exr" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb5f255b5980bb0c8cf676b675d1a99be40f316881444f44e0462eaf5df5ded" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "smallvec", + "threadpool", +] + +[[package]] name = "fallible-iterator" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -211,6 +311,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -347,8 +470,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", ] [[package]] @@ -370,6 +505,15 @@ dependencies = [ ] [[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -485,12 +629,40 @@ dependencies = [ ] [[package]] +name = "image" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + +[[package]] name = "itoa" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + +[[package]] name = "js-sys" version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -506,6 +678,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -563,6 +741,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -579,6 +766,15 @@ dependencies = [ ] [[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] name = "mio" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -591,6 +787,15 @@ dependencies = [ ] [[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] name = "no-std-compat" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -619,6 +824,36 @@ dependencies = [ ] [[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -708,6 +943,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide", +] + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -787,6 +1034,28 @@ dependencies = [ ] [[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -822,6 +1091,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -934,6 +1209,15 @@ dependencies = [ ] [[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] + +[[package]] name = "syn" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -980,6 +1264,26 @@ dependencies = [ ] [[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] name = "time" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1331,6 +1635,12 @@ dependencies = [ ] [[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1416,6 +1726,7 @@ dependencies = [ "axum", "axum-client-ip", "bytes", + "image", "lazy_static", "rand", "rusqlite", @@ -5,7 +5,7 @@ edition = "2021" [dependencies] tokio = { version = "1.23.0", features = ["full"] } -axum = { version = "0.6.4", features = ["headers"] } +axum = { version = "0.6.4", features = ["headers", "query"] } axum-client-ip = "0.3.1" tower-http = { version = "0.3.5", features = ["fs"] } tower_governor = "0.0.4" @@ -19,4 +19,5 @@ serde_json = { version = "1.0", features = ["std"] } rusqlite = { version = "0.28.0", features = ["bundled"] } rand = "0.8.5" time = "0.3.17" -lazy_static = "1.4.0"
\ No newline at end of file +lazy_static = "1.4.0" +image = "0.24.3"
\ No newline at end of file @@ -30,15 +30,17 @@ If you want to run it in a docker container a premade dockerfile is here for you There is also a docker-compose.yml file for your reference in the /deployments/docker folder. -The one thing about the docker container is you have to mount the volume +There are two volumes you have to make for the container. First one for the database otherwise all data will be wiped upon container restart. You only should volume the database file so create the vollume with the directory below. + +`touch [your directory]/xssbook.db` `-v [your directory]/xssbook.db:/data/xssbook.db` -to make the database persistant. Finally, before running the container run +You have to create the database file beforehand because otherwise docker will create a folder there instead, and then the program will crash when it tries to load a folder as a database. -`touch [your directory]/xssbook.db` +Finally, you have to make a volume to store custom user avatars and banners. Without this, this data too will be lost upon contaienr restart. To make the volume simply run this with your container. -since docker will create a folder there otherwise and it won't work. +`-v [another directory]:/data/public/image/custom` **reverse proxy** diff --git a/deployments/docker/Dockerfile b/deployments/docker/Dockerfile index f14b0a6..9764939 100644 --- a/deployments/docker/Dockerfile +++ b/deployments/docker/Dockerfile @@ -13,6 +13,8 @@ COPY --from=builder /usr/local/cargo/bin/xssbook /usr/local/bin/xssbook RUN mkdir /data WORKDIR /data COPY ./public ./public +RUN mkdir ./public/image/custom +VOLUME ./public/image/custom EXPOSE 8080 CMD ["/usr/local/bin/xssbook"]
\ No newline at end of file diff --git a/deployments/docker/docker-compose.yml b/deployments/docker/docker-compose.yml index 09415e4..1b0b269 100644 --- a/deployments/docker/docker-compose.yml +++ b/deployments/docker/docker-compose.yml @@ -10,3 +10,4 @@ services: - 8080:8080 volumes: - ${PWD}/xssbook.db:/data/xssbook.db + - ${PWD}/custom:/data/public/image/custom diff --git a/public/css/main.css b/public/css/main.css index e15ffa6..1926ed9 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -368,4 +368,14 @@ form { input:focus { outline: none; } + + .changeavatar { + filter: invert(100%) !important; + background-color: #bbbbbb !important; + } + + .changebanner { + filter: invert(100%) !important; + background-color: #bbbbbb !important; + } }
\ No newline at end of file diff --git a/public/css/profile.css b/public/css/profile.css index 62ce1d2..1077919 100644 --- a/public/css/profile.css +++ b/public/css/profile.css @@ -24,7 +24,7 @@ body { justify-content: center; } -#banner div, #banner img { +#banner .bg, #banner img { width: 80em; max-width: 100%; height: inherit; @@ -56,6 +56,27 @@ body { border-radius: 7em; } +.changeavatar, .changebanner { + all: unset; + position: absolute; + width: 3em; + height: 3em; + margin-left: -3em; + margin-top: 9em; + border-radius: 3em; + background-color: var(--secondary); + z-index: 10000 !important; + text-align: center; + background-image: url('data:image/png;base64,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/TwzbKRT88RhYqHQxnIbs6KhEk8TRxRVo3x/xmWF8xZntVxlzXvyF4Zy2soy12kNIYFFLEGEABlVlFCGhSjtGikmUnQe9/APOn6RXDK5SmDkWEAFKiTHD/4Hv2dr5qcm3aRQHOh8se2PESC4CzRqtv19bNuNEyDwDFxpLX+lDsx+kl5raZEjoHcbuLhuafIecLkDDDzpkiE5UoCWP58H3s/om7JA3y3QvebOrXmO0wcgTbNK3gAHh8BogbLXPd7d1T63f3ua8/sBAv9y4GhT1LwAAA14aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjNlYWM4M2MyLTUzMjctNDE5ZS1iMjJlLWU2YjE5M2M2ZTc2NiIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0MTNkODIxZC1jMDE1LTQ1ZGQtYTAwNS04MTA2OGY3ZTg1YjEiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4ZGRkOGY2My1iODAwLTQ1MDQtYWE3Yi1iNzAwOTRiOTg5YTgiCiAgIEdJTVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0iMTY3NTIwNTQzNTkyMzY1NCIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjMyIgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjM6MDE6MzFUMTc6NTA6MzUtMDU6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDIzOjAxOjMxVDE3OjUwOjM1LTA1OjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZGJjMjRlYWMtODdmMC00YjJjLWI2MzAtYTBhOTIxY2I1MmJmIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wMS0zMVQxNzo1MDozNS0wNTowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7KJrUFAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH5wEfFjIjpTMVIgAAAlFJREFUaN7t2E2ITXEYx/HPHTNehmlMGSXyMiILJBsbid2QHZZsZj8bL2WtMKLYiJWXkhVJsZLUkJeUl0JCTZJB42UwZphmxsIzdZtmxr3m3OPeOt86nc69z/n/n985z/P8n/MnIyMjIyMjI6OimYcL+IWhEh+fcAwzCnUuV6DdTDzAQjxFV4kf2mLMxV2sR19SA++LJ9WW0tufjHMx554kB76FAdSmGMr1GER7IcZVBQ46JXLjR0JOVmMp6sax6cZPTEtSSJJswzs8x0ccKiJXJ8x99CYwzkr04xvO4GXkQesY9r0xd9kJORCOb4nrxhB2Z6JC0g6tmjh/z3N0IHJmQqQt5GKcT2E/rkchuVRpOSLyoS9vFT8f60ZF5cgws7AWi/5iV7CQ6hK/yfpYD0bShZtJTlSqHJmKI+iIkls2FBNaq/Ak4n8AnWgax34JmsspR6qwN9qKbuzApmhtXmHOKPdswOcQvKschDRFYzmEG1iQ99/2aP4eRc4M0xIiO3Ev7j0bYflfhLREy9GH3WP0Ta3haDum43BcP8b8cP50/HY77+2lIqQRl/McWl7gN82HOF8dpfvdGWH2GqvTELIZ72PStnEWtJGcCBFHMWkMm2Z8QU+MXxIhtTgZznRg3T+U++YC7JbhRcyTuJB+PMtLyroSl/yGKAaJr+zVmI2tsZtSaobLsqSF9GMF3pbjXlUxLcpguYooRsib+G7YmKJvDbG+fE0ytA5GtbmCh1EeS0nOn81AMWeirMG1yJWhFI4eHM/lcjUyMjIyMjIyMiqT3wkE0qfVNfiiAAAAAElFTkSuQmCC'); + cursor: pointer; +} + +.changebanner { + position: relative; + margin-left: -4em; + margin-top: 26em; +} + .infodata { margin-top: 2em; display: flex; @@ -138,5 +159,4 @@ body { .logout { flex: 1; - /* align-self: flex-end !important; */ }
\ No newline at end of file diff --git a/public/img/0.png b/public/image/default/0.png Binary files differindex 19bbb12..19bbb12 100644 --- a/public/img/0.png +++ b/public/image/default/0.png diff --git a/public/img/1.png b/public/image/default/1.png Binary files differindex 0466850..0466850 100644 --- a/public/img/1.png +++ b/public/image/default/1.png diff --git a/public/img/10.png b/public/image/default/10.png Binary files differindex e181798..e181798 100644 --- a/public/img/10.png +++ b/public/image/default/10.png diff --git a/public/img/11.png b/public/image/default/11.png Binary files differindex 10cc420..10cc420 100644 --- a/public/img/11.png +++ b/public/image/default/11.png diff --git a/public/img/12.png b/public/image/default/12.png Binary files differindex f62a977..f62a977 100644 --- a/public/img/12.png +++ b/public/image/default/12.png diff --git a/public/img/13.png b/public/image/default/13.png Binary files differindex c25a5db..c25a5db 100644 --- a/public/img/13.png +++ b/public/image/default/13.png diff --git a/public/img/14.png b/public/image/default/14.png Binary files differindex b4268e0..b4268e0 100644 --- a/public/img/14.png +++ b/public/image/default/14.png diff --git a/public/img/15.png b/public/image/default/15.png Binary files differindex 8641079..8641079 100644 --- a/public/img/15.png +++ b/public/image/default/15.png diff --git a/public/img/16.png b/public/image/default/16.png Binary files differindex 538b8d8..538b8d8 100644 --- a/public/img/16.png +++ b/public/image/default/16.png diff --git a/public/img/17.png b/public/image/default/17.png Binary files differindex 8fbf42a..8fbf42a 100644 --- a/public/img/17.png +++ b/public/image/default/17.png diff --git a/public/img/18.png b/public/image/default/18.png Binary files differindex 60ddadc..60ddadc 100644 --- a/public/img/18.png +++ b/public/image/default/18.png diff --git a/public/img/19.png b/public/image/default/19.png Binary files differindex 87e55ae..87e55ae 100644 --- a/public/img/19.png +++ b/public/image/default/19.png diff --git a/public/img/2.png b/public/image/default/2.png Binary files differindex e3d3fd2..e3d3fd2 100644 --- a/public/img/2.png +++ b/public/image/default/2.png diff --git a/public/img/20.png b/public/image/default/20.png Binary files differindex cad21e0..cad21e0 100644 --- a/public/img/20.png +++ b/public/image/default/20.png diff --git a/public/img/21.png b/public/image/default/21.png Binary files differindex 951295b..951295b 100644 --- a/public/img/21.png +++ b/public/image/default/21.png diff --git a/public/img/22.png b/public/image/default/22.png Binary files differindex 30a7517..30a7517 100644 --- a/public/img/22.png +++ b/public/image/default/22.png diff --git a/public/img/23.png b/public/image/default/23.png Binary files differindex 386d550..386d550 100644 --- a/public/img/23.png +++ b/public/image/default/23.png diff --git a/public/img/24.png b/public/image/default/24.png Binary files differindex fc0ce1a..fc0ce1a 100644 --- a/public/img/24.png +++ b/public/image/default/24.png diff --git a/public/img/3.png b/public/image/default/3.png Binary files differindex aa2b68a..aa2b68a 100644 --- a/public/img/3.png +++ b/public/image/default/3.png diff --git a/public/img/4.png b/public/image/default/4.png Binary files differindex a9ee0d0..a9ee0d0 100644 --- a/public/img/4.png +++ b/public/image/default/4.png diff --git a/public/img/5.png b/public/image/default/5.png Binary files differindex 63e904b..63e904b 100644 --- a/public/img/5.png +++ b/public/image/default/5.png diff --git a/public/img/6.png b/public/image/default/6.png Binary files differindex 7ba485d..7ba485d 100644 --- a/public/img/6.png +++ b/public/image/default/6.png diff --git a/public/img/7.png b/public/image/default/7.png Binary files differindex c861dc6..c861dc6 100644 --- a/public/img/7.png +++ b/public/image/default/7.png diff --git a/public/img/8.png b/public/image/default/8.png Binary files differindex a25833b..a25833b 100644 --- a/public/img/8.png +++ b/public/image/default/8.png diff --git a/public/img/9.png b/public/image/default/9.png Binary files differindex 70b19fa..70b19fa 100644 --- a/public/img/9.png +++ b/public/image/default/9.png diff --git a/public/js/api.js b/public/js/api.js index 9845be5..b2ea597 100644 --- a/public/js/api.js +++ b/public/js/api.js @@ -1,6 +1,27 @@ const endpoint = '/api' +const fileRequest = async (url, file, method) => { + if (method === undefined) method = 'POST' + const response = await fetch(endpoint + url, { + method, + body: file, + headers: {} + }); + if (response.status == 401) { + location.href = 'login' + } + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + const json = await response.json() + return { status: response.status, msg: json.msg, json } + } else { + const msg = await response.text(); + return { status: response.status, msg } + } +} + const request = async (url, body, method) => { + if (method === undefined) method = 'POST' const response = await fetch(endpoint + url, { method, @@ -88,4 +109,12 @@ const adminusers = async () => { const adminsessions = async () => { return await request('/admin/sessions', {}) +} + +const updateavatar = async (file) => { + return await fileRequest('/users/avatar', file, 'PUT') +} + +const updatebanner = async (file) => { + return await fileRequest('/users/banner', file, 'PUT') }
\ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index 87dd8e0..ffbc1f3 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -33,7 +33,11 @@ function remove(id) { } function pfp(id) { - return `<img src="/img/${id % 25}.png">` + return `<img src="/image/avatar?user_id=${id}">` +} + +function banner(id) { + return `<img src="/image/banner?user_id=${id}" onerror="this.remove()" >` } const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', diff --git a/public/js/profile.js b/public/js/profile.js index 10eb873..90787f0 100644 --- a/public/js/profile.js +++ b/public/js/profile.js @@ -16,24 +16,44 @@ function swap(value) { } } +function changeimage(fn) { + + var input = document.createElement('input') + input.type = 'file' + input.accept= 'image/png' + + input.onchange = async (e) => { + var file = e.target.files[0]; + if (file.type !== 'image/png') { + return + } + let response = await fn(file); + alert(response.msg) + } + + input.click(); +} + function render() { const html = ` <div id="top"> <div id="banner"> - <div> - + <div class="bg"> + ${banner(data.user.user_id)} </div> + ${ isself ? `<div class="changebanner" onclick="changeimage(updatebanner)"></div>` : '' } </div> <div id="info"> <div class="face"> ${pfp(data.user.user_id)} + ${ isself ? `<div class="changeavatar" onclick="changeimage(updateavatar)"></div>` : '' } </div> <div class="infodata"> <span class="bold ltext">${data.user.firstname + ' ' + data.user.lastname}</span> <span class="gtext">Joined ${parseDate(new Date(data.user.date))}</span> </div> </div> - <div class="fullline" style="width: 80em; margin-bottom: 0;"></div> + <div class="fullline" style="width: 80em; margin-bottom: 0; z-index: 0;"></div> <div class="profilebuttons"> <button id="profilepostbutton" class="${posts ? 'selected' : ''}" onclick="swap(true)"> Posts @@ -71,6 +91,7 @@ function render() { ` append(about) + } async function logout_button() { diff --git a/src/api/image.rs b/src/api/image.rs new file mode 100644 index 0000000..84eccc7 --- /dev/null +++ b/src/api/image.rs @@ -0,0 +1,54 @@ +use axum::{extract::Query, response::Response, routing::get, Router, http::StatusCode}; +use serde::Deserialize; + +use crate::types::http::ResponseCode; + + + +#[derive(Deserialize)] +struct AvatarRequest { + user_id: u64, +} + +async fn avatar(params: Option<Query<AvatarRequest>>) -> Response { + + let Some(params) = params else { + return ResponseCode::BadRequest.text("Missing query paramaters"); + }; + + let custom = format!("/image/custom/avatar/{}.png", params.user_id); + let default = format!("/image/default/{}.png", params.user_id % 25); + + let file = ResponseCode::Success.file(&custom).await; + if file.status() != StatusCode::OK { + return ResponseCode::Success.file(&default).await + } + file +} + +#[derive(Deserialize)] +struct BannerRequest { + user_id: u64, +} + +async fn banner(params: Option<Query<BannerRequest>>) -> Response { + + let Some(params) = params else { + return ResponseCode::BadRequest.text("Missing query paramaters"); + }; + + let custom = format!("/image/custom/banner/{}.png", params.user_id); + + let file = ResponseCode::Success.file(&custom).await; + if file.status() != StatusCode::OK { + return ResponseCode::NotFound.text("User does not have a custom banner") + } + file +} + + +pub fn router() -> Router { + Router::new() + .route("/avatar", get(avatar)) + .route("/banner", get(banner)) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index d36b127..adc19d7 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -8,6 +8,7 @@ pub mod auth; pub mod pages; pub mod posts; pub mod users; +pub mod image; pub fn router() -> Router { let governor_conf = Box::new( diff --git a/src/api/users.rs b/src/api/users.rs index afcdddd..83a0d4e 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -1,9 +1,9 @@ use crate::types::{ - extract::{AuthorizedUser, Check, CheckResult, Json}, + extract::{AuthorizedUser, Check, CheckResult, Json, Png}, http::ResponseCode, user::User, }; -use axum::{response::Response, routing::post, Router}; +use axum::{response::Response, routing::{post, put}, Router}; use serde::Deserialize; #[derive(Deserialize)] @@ -63,9 +63,33 @@ async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response { ResponseCode::Success.json(&json) } +async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { + + let path = format!("./public/image/custom/avatar/{}.png", user.user_id); + + if img.save(path).is_err() { + return ResponseCode::InternalServerError.text("Failed to update avatar"); + } + + ResponseCode::Success.text("Successfully updated avatar") +} + +async fn banner(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { + + let path = format!("./public/image/custom/banner/{}.png", user.user_id); + + if img.save(path).is_err() { + return ResponseCode::InternalServerError.text("Failed to update banner"); + } + + ResponseCode::Success.text("Successfully updated banner") +} + pub fn router() -> Router { Router::new() .route("/load", post(load_batch)) .route("/self", post(load_self)) .route("/page", post(load_page)) + .route("/avatar", put(avatar)) + .route("/banner", put(banner)) } diff --git a/src/main.rs b/src/main.rs index a72ec5f..74f0a0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,10 @@ use axum::{ http::{Request, StatusCode}, middleware::{self, Next}, response::Response, - RequestExt, Router, + RequestExt, Router, extract::DefaultBodyLimit, }; use axum_client_ip::ClientIp; -use std::{net::SocketAddr, process::exit}; +use std::{net::SocketAddr, process::exit, fs}; use tower_cookies::CookieManagerLayer; use tracing::{error, info, metadata::LevelFilter}; use tracing_subscriber::{ @@ -14,7 +14,7 @@ use tracing_subscriber::{ }; use types::http::ResponseCode; -use crate::api::pages; +use crate::api::{pages, image}; mod admin; mod api; @@ -69,13 +69,19 @@ async fn main() { exit(1) }; + fs::create_dir_all("./public/image/custom").expect("Coudn't make custom data directory"); + fs::create_dir_all("./public/image/custom/avatar").expect("Coudn't make custom avatar directory"); + fs::create_dir_all("./public/image/custom/banner").expect("Coudn't make custom banner directory"); + let app = Router::new() .fallback(not_found) .layer(middleware::from_fn(log)) .layer(middleware::from_fn(serve)) .nest("/", pages::router()) .nest("/api", api::router()) - .layer(CookieManagerLayer::new()); + .nest("/image", image::router()) + .layer(CookieManagerLayer::new()) + .layer(DefaultBodyLimit::max(512_000)); let Ok(addr) = "[::]:8080".parse::<std::net::SocketAddr>() else { error!("Failed to parse port binding"); diff --git a/src/types/extract.rs b/src/types/extract.rs index 50c413b..54f250a 100644 --- a/src/types/extract.rs +++ b/src/types/extract.rs @@ -1,4 +1,4 @@ -use std::io::Read; +use std::io::{Read, Cursor}; use axum::{ async_trait, @@ -10,6 +10,7 @@ use axum::{ }; use axum_client_ip::ClientIp; use bytes::Bytes; +use image::{io::Reader, ImageFormat, DynamicImage}; use serde::de::DeserializeOwned; use tower_cookies::Cookies; @@ -99,6 +100,36 @@ where } } +pub struct Png(pub DynamicImage); + +#[async_trait] +impl<S, B> FromRequest<S, B> for Png +where + B: HttpBody + Sync + Send + 'static, + B::Data: Send, + B::Error: Into<BoxError>, + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request(req: Request<B>, state: &S) -> Result<Self> { + + let bytes = match read_body(req, state).await { + Ok(body) => body, + Err(err) => return Err(err), + }; + + let mut reader = Reader::new(Cursor::new(bytes)); + reader.set_format(ImageFormat::Png); + + let Ok(img) = reader.decode() else { + return Err(ResponseCode::BadRequest.text("Failed to decode png image")) + }; + + Ok(Self(img)) + } +} + pub struct Json<T>(pub T); #[async_trait] @@ -150,7 +181,43 @@ pub trait Check { } } -pub async fn parse_body<S, B>(mut req: Request<B>, state: &S) -> Result<String> +async fn read_body<S, B>(mut req: Request<B>, state: &S) -> Result<Vec<u8>> +where + B: HttpBody + Sync + Send + 'static, + B::Data: Send, + B::Error: Into<BoxError>, + S: Send + Sync, +{ + + let Ok(ClientIp(ip)) = req.extract_parts::<ClientIp>().await else { + tracing::error!("Failed to read client ip"); + return Err(ResponseCode::InternalServerError.text("Failed to read client ip")); + }; + + let method = req.method().clone(); + let uri = req.uri().clone(); + let path = req + .extensions() + .get::<RouterURI>() + .map_or("", |path| path.0); + + let Ok(bytes) = Bytes::from_request(req, state).await else { + return Err(ResponseCode::BadRequest.text("Request can be at most 512kb")); + }; + + console::log( + ip, + method, + uri, + Some(path.to_string()), + None, + ) + .await; + + Ok(bytes.bytes().flatten().collect()) +} + +async fn parse_body<S, B>(mut req: Request<B>, state: &S) -> Result<String> where B: HttpBody + Sync + Send + 'static, B::Data: Send, @@ -170,8 +237,7 @@ where .map_or("", |path| path.0); let Ok(bytes) = Bytes::from_request(req, state).await else { - tracing::error!("Failed to read request body"); - return Err(ResponseCode::InternalServerError.text("Failed to read request body")); + return Err(ResponseCode::BadRequest.text("Request can be at most 512kb")); }; let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else { |