custosm avatars and banners
3
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
xssbook.db
|
||||
xssbook.db
|
||||
/public/avatar/custom/*
|
311
Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
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"
|
||||
|
@ -97,6 +103,12 @@ version = "0.13.1"
|
|||
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"
|
||||
|
@ -118,6 +130,18 @@ version = "3.12.0"
|
|||
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"
|
||||
|
@ -136,6 +160,12 @@ version = "1.0.0"
|
|||
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"
|
||||
|
@ -156,6 +186,49 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -165,6 +238,12 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -198,6 +277,27 @@ dependencies = [
|
|||
"crypto-common",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -210,6 +310,29 @@ version = "0.1.9"
|
|||
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"
|
||||
|
@ -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]]
|
||||
|
@ -369,6 +504,15 @@ dependencies = [
|
|||
"smallvec",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -484,12 +628,40 @@ dependencies = [
|
|||
"want",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -505,6 +677,12 @@ version = "1.4.0"
|
|||
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"
|
||||
|
@ -562,6 +740,15 @@ version = "2.5.0"
|
|||
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"
|
||||
|
@ -578,6 +765,15 @@ dependencies = [
|
|||
"unicase",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -590,6 +786,15 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -618,6 +823,36 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -707,6 +942,18 @@ version = "0.3.26"
|
|||
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"
|
||||
|
@ -786,6 +1033,28 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -821,6 +1090,12 @@ version = "1.0.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"
|
||||
|
@ -933,6 +1208,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -979,6 +1263,26 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -1330,6 +1634,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -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"
|
||||
lazy_static = "1.4.0"
|
||||
image = "0.24.3"
|
10
README.md
|
@ -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**
|
||||
|
||||
|
|
|
@ -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"]
|
|
@ -10,3 +10,4 @@ services:
|
|||
- 8080:8080
|
||||
volumes:
|
||||
- ${PWD}/xssbook.db:/data/xssbook.db
|
||||
- ${PWD}/custom:/data/public/image/custom
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
@ -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')
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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() {
|
||||
|
|
54
src/api/image.rs
Normal file
|
@ -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))
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
14
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");
|
||||
|
|
|
@ -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 {
|
||||
|
|