add: stripe integration
This commit is contained in:
parent
2fa5a4dc1f
commit
1d120555a0
31 changed files with 1137 additions and 122 deletions
482
Cargo.lock
generated
482
Cargo.lock
generated
|
@ -136,7 +136,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -145,6 +145,17 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
|
@ -153,7 +164,32 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stripe"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecbddf002ad7a13d2041eadf1b234cb3f57653ffdd901a01bc3f1c65aa77440"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hmac",
|
||||
"http-types",
|
||||
"hyper 0.14.32",
|
||||
"hyper-tls 0.5.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_qs 0.10.1",
|
||||
"sha2",
|
||||
"smart-default",
|
||||
"smol_str",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -164,7 +200,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -210,14 +246,14 @@ checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288"
|
|||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
|
@ -248,8 +284,8 @@ checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
|
@ -270,10 +306,10 @@ dependencies = [
|
|||
"axum-core",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"fastrand",
|
||||
"fastrand 2.3.0",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"multer",
|
||||
|
@ -293,7 +329,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -323,6 +359,12 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
|
@ -430,7 +472,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -506,7 +548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b3e7381ca451b439579a09feb1d41d4b07c0e903bf8c74602b9e95219c40e47b"
|
||||
dependencies = [
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"secrecy",
|
||||
|
@ -541,6 +583,7 @@ dependencies = [
|
|||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
@ -599,7 +642,7 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -653,6 +696,15 @@ dependencies = [
|
|||
"xdg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
|
@ -781,7 +833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -805,7 +857,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -816,7 +868,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
|||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -873,7 +925,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -928,6 +980,12 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.73.0"
|
||||
|
@ -971,6 +1029,15 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
|
@ -1074,6 +1141,21 @@ version = "0.3.31"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
|
||||
dependencies = [
|
||||
"fastrand 1.9.0",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
|
@ -1082,7 +1164,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1130,6 +1212,17 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
|
@ -1218,6 +1311,25 @@ dependencies = [
|
|||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.8"
|
||||
|
@ -1229,7 +1341,7 @@ dependencies = [
|
|||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
|
@ -1277,6 +1389,12 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex_fmt"
|
||||
version = "0.3.0"
|
||||
|
@ -1304,6 +1422,17 @@ dependencies = [
|
|||
"match_token",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
|
@ -1315,6 +1444,17 @@ dependencies = [
|
|||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
|
@ -1322,7 +1462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1333,8 +1473,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
|
@ -1344,6 +1484,27 @@ version = "0.4.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||
|
||||
[[package]]
|
||||
name = "http-types"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
"base64 0.13.1",
|
||||
"futures-lite",
|
||||
"http 0.2.12",
|
||||
"infer",
|
||||
"pin-project-lite",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs 0.8.5",
|
||||
"serde_urlencoded",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
|
@ -1365,6 +1526,30 @@ dependencies = [
|
|||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
|
@ -1374,9 +1559,9 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"h2 0.4.8",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
|
@ -1393,8 +1578,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"http 1.3.1",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
|
@ -1404,6 +1589,19 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper 0.14.32",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.6.0"
|
||||
|
@ -1412,7 +1610,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
|
@ -1429,9 +1627,9 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.6.0",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
|
@ -1577,7 +1775,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1672,6 +1870,21 @@ dependencies = [
|
|||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
|
@ -1680,7 +1893,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1853,7 +2066,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1949,7 +2162,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"memchr",
|
||||
"mime",
|
||||
|
@ -2048,7 +2261,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2140,7 +2353,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2167,6 +2380,12 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
|
@ -2251,7 +2470,7 @@ dependencies = [
|
|||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2305,7 +2524,7 @@ dependencies = [
|
|||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2334,7 +2553,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2361,7 +2580,7 @@ version = "1.7.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"indexmap",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
|
@ -2393,7 +2612,7 @@ version = "0.6.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"fallible-iterator 0.2.0",
|
||||
|
@ -2444,7 +2663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2472,7 +2691,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2496,7 +2715,7 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "221b7eace1aef8c95d65dbe09fb7a1a43d006045394a89afba6997721fcb7708"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"image",
|
||||
"qrcodegen",
|
||||
]
|
||||
|
@ -2546,6 +2765,19 @@ version = "5.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
@ -2567,6 +2799,16 @@ dependencies = [
|
|||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
|
@ -2587,6 +2829,15 @@ dependencies = [
|
|||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
|
@ -2605,6 +2856,15 @@ dependencies = [
|
|||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rav1e"
|
||||
version = "0.7.1"
|
||||
|
@ -2766,18 +3026,18 @@ version = "0.12.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"h2 0.4.8",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls",
|
||||
"hyper-tls",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
|
@ -3009,7 +3269,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3034,6 +3294,28 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
|
@ -3150,6 +3432,26 @@ version = "1.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
|
||||
[[package]]
|
||||
name = "smart-default"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.8"
|
||||
|
@ -3229,6 +3531,17 @@ version = "2.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
|
@ -3257,7 +3570,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3329,7 +3642,7 @@ version = "3.19.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"fastrand 2.3.0",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
|
@ -3384,6 +3697,7 @@ name = "tetratto"
|
|||
version = "2.1.0"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"async-stripe",
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"cf-turnstile",
|
||||
|
@ -3414,7 +3728,7 @@ version = "2.1.0"
|
|||
dependencies = [
|
||||
"async-recursion",
|
||||
"base16ct",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bb8-postgres",
|
||||
"bitflags 2.9.0",
|
||||
"md-5",
|
||||
|
@ -3452,7 +3766,7 @@ dependencies = [
|
|||
"rand 0.9.1",
|
||||
"serde",
|
||||
"sha2",
|
||||
"uuid",
|
||||
"uuid 1.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3481,7 +3795,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3492,7 +3806,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3597,7 +3911,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3754,8 +4068,8 @@ dependencies = [
|
|||
"bitflags 2.9.0",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
|
@ -3791,7 +4105,7 @@ dependencies = [
|
|||
"axum",
|
||||
"forwarded-header-value",
|
||||
"governor",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"pin-project",
|
||||
"thiserror 2.0.12",
|
||||
"tower",
|
||||
|
@ -3818,7 +4132,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3875,7 +4189,7 @@ dependencies = [
|
|||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
|
@ -4006,6 +4320,7 @@ dependencies = [
|
|||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4038,6 +4353,15 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.16.0"
|
||||
|
@ -4082,6 +4406,12 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
@ -4101,6 +4431,12 @@ dependencies = [
|
|||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -4144,7 +4480,7 @@ dependencies = [
|
|||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -4179,7 +4515,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -4541,7 +4877,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
|
@ -4562,7 +4898,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4582,7 +4918,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
|
@ -4611,7 +4947,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -40,3 +40,10 @@ redis = { version = "0.30.0", features = [
|
|||
"tokio-comp",
|
||||
], optional = true }
|
||||
tower_governor = "0.7.0"
|
||||
async-stripe = { version = "0.41.0", features = [
|
||||
"events",
|
||||
"checkout",
|
||||
"webhook-events",
|
||||
"billing",
|
||||
"runtime-tokio-hyper",
|
||||
] }
|
||||
|
|
|
@ -8,7 +8,10 @@ use std::{
|
|||
use tera::Context;
|
||||
use tetratto_core::{
|
||||
config::Config,
|
||||
model::auth::{DefaultTimelineChoice, User},
|
||||
model::{
|
||||
auth::{DefaultTimelineChoice, User},
|
||||
permissions::FinePermission,
|
||||
},
|
||||
PUBSUB_ENABLED,
|
||||
};
|
||||
use tetratto_l10n::LangFile;
|
||||
|
@ -36,7 +39,6 @@ pub const ROOT: &str = include_str!("./public/html/root.html");
|
|||
pub const MACROS: &str = include_str!("./public/html/macros.html");
|
||||
pub const COMPONENTS: &str = include_str!("./public/html/components.html");
|
||||
|
||||
pub const MISC_INDEX: &str = include_str!("./public/html/misc/index.html");
|
||||
pub const MISC_ERROR: &str = include_str!("./public/html/misc/error.html");
|
||||
pub const MISC_NOTIFICATIONS: &str = include_str!("./public/html/misc/notifications.html");
|
||||
pub const MISC_MARKDOWN: &str = include_str!("./public/html/misc/markdown.html");
|
||||
|
@ -232,8 +234,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD {
|
|||
write_template!(html_path->"macros.html"(crate::assets::MACROS) --config=config);
|
||||
write_template!(html_path->"components.html"(crate::assets::COMPONENTS) --config=config);
|
||||
|
||||
write_template!(html_path->"misc/index.html"(crate::assets::MISC_INDEX) -d "misc" --config=config);
|
||||
write_template!(html_path->"misc/error.html"(crate::assets::MISC_ERROR) --config=config);
|
||||
write_template!(html_path->"misc/error.html"(crate::assets::MISC_ERROR) -d "misc" --config=config);
|
||||
write_template!(html_path->"misc/notifications.html"(crate::assets::MISC_NOTIFICATIONS) --config=config);
|
||||
write_template!(html_path->"misc/markdown.html"(crate::assets::MISC_MARKDOWN) --config=config);
|
||||
write_template!(html_path->"misc/requests.html"(crate::assets::MISC_REQUESTS) --config=config);
|
||||
|
@ -312,6 +313,9 @@ pub(crate) async fn init_dirs(config: &Config) {
|
|||
create_dir_if_not_exists!(
|
||||
&PathBufD::current().extend(&[config.dirs.media.as_str(), "community_banners"])
|
||||
);
|
||||
create_dir_if_not_exists!(
|
||||
&PathBufD::current().extend(&[config.dirs.media.as_str(), "uploads"])
|
||||
);
|
||||
|
||||
write_if_track!(images_path->"default-avatar.svg"(DEFAULT_AVATAR) --config=config);
|
||||
write_if_track!(images_path->"default-banner.svg"(DEFAULT_BANNER) --config=config);
|
||||
|
@ -346,6 +350,10 @@ pub(crate) async fn initial_context(
|
|||
if let Some(ua) = user {
|
||||
ctx.insert("is_helper", &ua.permissions.check_helper());
|
||||
ctx.insert("is_manager", &ua.permissions.check_manager());
|
||||
ctx.insert(
|
||||
"is_supporter",
|
||||
&ua.permissions.check(FinePermission::SUPPORTER),
|
||||
);
|
||||
ctx.insert("home", ua.settings.default_timeline.relative_url());
|
||||
} else {
|
||||
ctx.insert("is_helper", &false);
|
||||
|
|
|
@ -31,6 +31,7 @@ version = "1.0.0"
|
|||
"general:label.file_report" = "File report"
|
||||
"general:label.account_banned" = "Account banned"
|
||||
"general:label.account_banned_body" = "Your account has been banned for violating our policies."
|
||||
"general:label.better_with_account" = "It's better with an account! Login or sign up to explore more."
|
||||
|
||||
"dialog:action.okay" = "Ok"
|
||||
"dialog:action.continue" = "Continue"
|
||||
|
|
|
@ -559,6 +559,9 @@
|
|||
MANAGE_PINS: 1 << 7,
|
||||
MANAGE_COMMUNITY: 1 << 8,
|
||||
MANAGE_QUESTIONS: 1 << 9,
|
||||
MANAGE_CHANNELS: 1 << 10,
|
||||
MANAGE_MESSAGES: 1 << 11,
|
||||
MANAGE_EMOJIS: 1 << 12,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
|
@ -119,10 +119,17 @@
|
|||
</nav>
|
||||
{%- endmacro %} {% macro timelines_nav(selected="") -%}
|
||||
<div class="pillmenu w-full">
|
||||
{% if user %}
|
||||
<a href="/" class="{% if selected == 'home' %}active{% endif %}">
|
||||
{{ icon "newspaper" }}
|
||||
<span>{{ text "general:link.home" }}</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="/" class="{% if selected == 'all' %}active{% endif %}">
|
||||
{{ icon "earth" }}
|
||||
<span>{{ text "general:link.all" }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="/popular" class="{% if selected == 'popular' %}active{% endif %}">
|
||||
{{ icon "trending-up" }}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>{{ config.name }}</title>
|
||||
{% endblock %} {% block body %}
|
||||
<main class="flex justify-center" style="padding-top: 2rem">
|
||||
<div class="flex flex-col gap-2" style="width: 20rem; max-width: 100%">
|
||||
<h1 class="w-full text-center">{{ config.name }}</h1>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
<a href="/auth/login" class="button w-full">
|
||||
{{ icon "log-in" }}
|
||||
<span>{{ text "auth:action.login" }}</span>
|
||||
</a>
|
||||
|
||||
<a href="/auth/register" class="button w-full secondary">
|
||||
{{ icon "heart" }}
|
||||
<span>{{ text "auth:action.register" }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href="/popular" class="button w-full quaternary">
|
||||
{{ icon "trending-up" }}
|
||||
<span>Explore popular feed</span>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
|
@ -232,6 +232,10 @@
|
|||
SUPPORTER: 1 << 19,
|
||||
MANAGE_REQUESTS: 1 << 20,
|
||||
MANAGE_QUESTIONS: 1 << 21,
|
||||
MANAGE_CHANNELS: 1 << 22,
|
||||
MANAGE_MESSAGES: 1 << 23,
|
||||
MANAGE_UPLOADS: 1 << 24,
|
||||
MANAGE_EMOJIS: 1 << 25,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
|
@ -38,6 +38,34 @@
|
|||
|
||||
<div class="w-full flex flex-col gap-2" data-tab="account">
|
||||
<div class="card tertiary flex flex-col gap-2" id="account_settings">
|
||||
{% if config.stripe %}
|
||||
<div class="card-nest" ui_ident="supporter_card">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "star" }}
|
||||
<b>Supporter status</b>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
{% if is_supporter %}
|
||||
<p>You <b>are</b> a supporter! Thank you for all that you do. You can manage your billing information below. <b>Please use your email address you supplied when paying to login to the billing portal.</b></p>
|
||||
<a href="{{ config.stripe.billing_portal_url }}" class="button quaternary" target="_blank">Manage billing</a>
|
||||
{% else %}
|
||||
<p>You're <b>not</b> currently a supporter! No pressure, but it helps us do some pretty cool things! As a supporter, you'll get:</p>
|
||||
|
||||
<ul style="margin-bottom: 1rem">
|
||||
<li>Vanity badge on profile</li>
|
||||
<li>Ability to upload gif avatars/banners</li>
|
||||
<li>Ability to use community emojis outside of their community <b>(soon)</b></li>
|
||||
<li>Ability to upload and use gif emojis <b>(soon)</b></li>
|
||||
</ul>
|
||||
|
||||
<a href="{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}" class="button" target="_blank">Become a supporter</a>
|
||||
<span class="fade">Please use your <b>real email</b> when completing payment. It is required to manage your billing settings.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-nest" ui_ident="home_timeline">
|
||||
<div class="card small">
|
||||
<b>Home timeline</b>
|
||||
|
@ -894,6 +922,7 @@
|
|||
const theme_settings = document.getElementById("theme_settings");
|
||||
|
||||
ui.refresh_container(account_settings, [
|
||||
"supporter_card",
|
||||
"home_timeline",
|
||||
"notifications",
|
||||
"change_password",
|
||||
|
|
|
@ -4,6 +4,26 @@
|
|||
<main class="flex flex-col gap-2">
|
||||
{{ macros::timelines_nav(selected="all") }} {{
|
||||
macros::timelines_secondary_nav(posts="/all", questions="/all/questions") }}
|
||||
{% if not user %}
|
||||
<div class="card-nest">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "heart" }}
|
||||
<b>{{ text "general:label.better_with_account" }}</b>
|
||||
</div>
|
||||
|
||||
<div class="card flex gap-2">
|
||||
<a href="/auth/login" class="button">
|
||||
{{ icon "log-in" }}
|
||||
<span>{{ text "auth:action.login" }}</span>
|
||||
</a>
|
||||
|
||||
<a href="/auth/register" class="button secondary">
|
||||
{{ icon "user-plus" }}
|
||||
<span>{{ text "auth:action.register" }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<div class="card w-full flex flex-col gap-2">
|
||||
|
|
|
@ -757,7 +757,14 @@ media_theme_pref();
|
|||
// link filter
|
||||
self.define("link_filter", (_) => {
|
||||
for (const anchor of Array.from(document.querySelectorAll("a"))) {
|
||||
if (anchor.href.length === 0) {
|
||||
if (
|
||||
anchor.href.length === 0 ||
|
||||
anchor.href.startsWith("https://github.com") ||
|
||||
anchor.href.startsWith("https://trisua.com") ||
|
||||
anchor.href.startsWith("https://buy.stripe.com") ||
|
||||
anchor.href.startsWith("https://billing.stripe.com") ||
|
||||
anchor.href.startsWith("https://last.fm")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub mod last_fm;
|
||||
pub mod spotify;
|
||||
pub mod stripe;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
|
148
crates/app/src/routes/api/v1/auth/connections/stripe.rs
Normal file
148
crates/app/src/routes/api/v1/auth/connections/stripe.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use axum::{http::HeaderMap, response::IntoResponse, Extension, Json};
|
||||
use tetratto_core::model::{auth::Notification, permissions::FinePermission, ApiReturn, Error};
|
||||
use stripe::{EventObject, EventType};
|
||||
use crate::State;
|
||||
|
||||
pub async fn stripe_webhook(
|
||||
Extension(data): Extension<State>,
|
||||
headers: HeaderMap,
|
||||
body: String,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
|
||||
if data.0.stripe.is_none() {
|
||||
return Json(Error::MiscError("Disabled".to_string()).into());
|
||||
}
|
||||
|
||||
let sig = match headers.get("Stripe-Signature") {
|
||||
Some(s) => s,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
let req = match stripe::Webhook::construct_event(
|
||||
&body,
|
||||
&sig.to_str().unwrap(),
|
||||
&data.0.stripe.as_ref().unwrap().webhook_signing_secret,
|
||||
) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
||||
};
|
||||
|
||||
match req.type_ {
|
||||
EventType::CheckoutSessionCompleted => {
|
||||
// checkout session ended, store user customer id (from stripe) in their user data
|
||||
let session = match req.data.object {
|
||||
EventObject::CheckoutSession(c) => c,
|
||||
_ => unreachable!("cannot be this"),
|
||||
};
|
||||
|
||||
let user_id = session
|
||||
.client_reference_id
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
|
||||
let customer_id = session.customer.unwrap().id();
|
||||
|
||||
let user = match data.get_user_by_id(user_id).await {
|
||||
Ok(ua) => ua,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
tracing::info!("subscribe {} (stripe: {})", user.id, customer_id);
|
||||
if let Err(e) = data
|
||||
.update_user_stripe_id(user.id, customer_id.as_str())
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
}
|
||||
}
|
||||
EventType::InvoicePaymentSucceeded => {
|
||||
// payment finished and subscription created
|
||||
// we're doing this *instead* of CustomerSubscriptionDeleted because
|
||||
// the invoice happens *after* the checkout session ends... which is what we need
|
||||
let invoice = match req.data.object {
|
||||
EventObject::Invoice(c) => c,
|
||||
_ => unreachable!("cannot be this"),
|
||||
};
|
||||
|
||||
let customer_id = invoice.customer.unwrap().id();
|
||||
|
||||
// allow 10s for everything to finalize
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
|
||||
// pull user and update role
|
||||
let user = match data.get_user_by_stripe_id(customer_id.as_str()).await {
|
||||
Ok(ua) => ua,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
tracing::info!("invoice {} (stripe: {})", user.id, customer_id);
|
||||
let new_user_permissions = user.permissions | FinePermission::SUPPORTER;
|
||||
|
||||
if let Err(e) = data
|
||||
.update_user_role(user.id, new_user_permissions, user.clone(), true)
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
}
|
||||
|
||||
if let Err(e) = data
|
||||
.create_notification(Notification::new(
|
||||
"Welcome new supporter!".to_string(),
|
||||
"Thank you for your support! Your account has been updated with your new role."
|
||||
.to_string(),
|
||||
user.id,
|
||||
))
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
}
|
||||
}
|
||||
EventType::CustomerSubscriptionDeleted => {
|
||||
// payment failed and subscription deleted
|
||||
let subscription = match req.data.object {
|
||||
EventObject::Subscription(c) => c,
|
||||
_ => unreachable!("cannot be this"),
|
||||
};
|
||||
|
||||
let customer_id = subscription.customer.id();
|
||||
|
||||
let user = match data.get_user_by_stripe_id(customer_id.as_str()).await {
|
||||
Ok(ua) => ua,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
tracing::info!("unsubscribe {} (stripe: {})", user.id, customer_id);
|
||||
let new_user_permissions = user.permissions - FinePermission::SUPPORTER;
|
||||
|
||||
if let Err(e) = data
|
||||
.update_user_role(user.id, new_user_permissions, user.clone(), true)
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
}
|
||||
|
||||
if let Err(e) = data
|
||||
.create_notification(Notification::new(
|
||||
"Sorry to see you go... :(".to_string(),
|
||||
"Thank you for your past support! Please feel free to leave us feedback on why you decided to cancel."
|
||||
.to_string(),
|
||||
user.id,
|
||||
))
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
}
|
||||
}
|
||||
_ => return Json(Error::Unknown.into()),
|
||||
}
|
||||
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Acceptable".to_string(),
|
||||
payload: (),
|
||||
})
|
||||
}
|
|
@ -247,7 +247,7 @@ pub async fn update_user_role_request(
|
|||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
match data.update_user_role(id, req.role, user).await {
|
||||
match data.update_user_role(id, req.role, user, false).await {
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "User updated".to_string(),
|
||||
|
|
|
@ -281,6 +281,10 @@ pub fn routes() -> Router {
|
|||
"/auth/user/connections/last_fm/api_proxy",
|
||||
post(auth::connections::last_fm::proxy_request),
|
||||
)
|
||||
.route(
|
||||
"/service_hooks/stripe",
|
||||
post(auth::connections::stripe::stripe_webhook),
|
||||
)
|
||||
// channels
|
||||
.route("/channels", post(channels::channels::create_request))
|
||||
.route(
|
||||
|
|
|
@ -35,9 +35,22 @@ pub async fn index_request(
|
|||
Some(ua) => ua,
|
||||
None => {
|
||||
return {
|
||||
// all timeline for unauthenticated users
|
||||
// i'm only changing this for stripe
|
||||
let list = match data.0.get_latest_posts(12, req.page).await {
|
||||
Ok(l) => match data.0.fill_posts_with_community(l, 0, &Vec::new()).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => return Html(render_error(e, &jar, &data, &None).await),
|
||||
},
|
||||
Err(e) => return Html(render_error(e, &jar, &data, &None).await),
|
||||
};
|
||||
|
||||
let lang = get_lang!(jar, data.0);
|
||||
let context = initial_context(&data.0.0, lang, &None).await;
|
||||
Html(data.1.render("misc/index.html", &context).unwrap())
|
||||
let mut context = initial_context(&data.0.0, lang, &None).await;
|
||||
|
||||
context.insert("list", &list);
|
||||
context.insert("page", &req.page);
|
||||
Html(data.1.render("timelines/all.html", &context).unwrap())
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -163,6 +163,39 @@ pub struct ConnectionsConfig {
|
|||
pub last_fm_secret: Option<String>,
|
||||
}
|
||||
|
||||
/// Configuration for Stripe integration.
|
||||
///
|
||||
/// User IDs are sent to Stripe through the payment link.
|
||||
/// <https://docs.stripe.com/payment-links/url-parameters#streamline-reconciliation-with-a-url-parameter>
|
||||
///
|
||||
/// # Testing
|
||||
///
|
||||
/// - Run `stripe login` using the Stripe CLI
|
||||
/// - Run `stripe listen --forward-to localhost:4118/api/v1/service_hooks/stripe`
|
||||
/// - Use testing card numbers: <https://docs.stripe.com/testing?testing-method=card-numbers#visa>
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
pub struct StripeConfig {
|
||||
/// Payment link from the Stripe dashboard.
|
||||
///
|
||||
/// 1. Create a product and set the price for your membership
|
||||
/// 2. Set the product price to a recurring subscription
|
||||
/// 3. Create a payment link for the new product
|
||||
/// 4. The payment link pasted into this config field should NOT include a query string
|
||||
pub payment_link: String,
|
||||
/// To apply benefits to user accounts, you should then go into the Stripe developer
|
||||
/// "workbench" and create a new webhook. The webhook needs the scopes:
|
||||
/// `invoice.payment_succeeded`, `customer.subscription.deleted`, `checkout.session.completed`.
|
||||
///
|
||||
/// The webhook's destination address should be `{your server origin}/api/v1/service_hooks/stripe`.
|
||||
///
|
||||
/// The signing secret can be found on the right after you have created the webhook.
|
||||
pub webhook_signing_secret: String,
|
||||
/// The URL of your customer billing portal.
|
||||
///
|
||||
/// <https://docs.stripe.com/no-code/customer-portal>
|
||||
pub billing_portal_url: String,
|
||||
}
|
||||
|
||||
/// Configuration file
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
|
@ -224,6 +257,8 @@ pub struct Config {
|
|||
/// into every HTML template. They support access to template fields like `{{ user }}`.
|
||||
#[serde(default)]
|
||||
pub html_footer_path: String,
|
||||
#[serde(default)]
|
||||
pub stripe: Option<StripeConfig>,
|
||||
}
|
||||
|
||||
fn default_name() -> String {
|
||||
|
@ -311,6 +346,7 @@ impl Default for Config {
|
|||
town_square: 0,
|
||||
connections: default_connections(),
|
||||
html_footer_path: String::new(),
|
||||
stripe: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ impl DataManager {
|
|||
post_count: get!(x->15(i32)) as usize,
|
||||
request_count: get!(x->16(i32)) as usize,
|
||||
connections: serde_json::from_str(&get!(x->17(String)).to_string()).unwrap(),
|
||||
stripe_id: get!(x->18(String)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +140,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)",
|
||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -159,6 +160,7 @@ impl DataManager {
|
|||
&0_i32,
|
||||
&0_i32,
|
||||
&serde_json::to_string(&data.connections).unwrap(),
|
||||
&""
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -433,24 +435,27 @@ impl DataManager {
|
|||
id: usize,
|
||||
role: FinePermission,
|
||||
user: User,
|
||||
force: bool,
|
||||
) -> Result<()> {
|
||||
// check permission
|
||||
if !user.permissions.check(FinePermission::MANAGE_USERS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
let other_user = self.get_user_by_id(id).await?;
|
||||
|
||||
if other_user.permissions.check_manager() && !user.permissions.check_admin() {
|
||||
return Err(Error::MiscError(
|
||||
"Cannot manage the role of other managers".to_string(),
|
||||
));
|
||||
}
|
||||
if !force {
|
||||
// check permission
|
||||
if !user.permissions.check(FinePermission::MANAGE_USERS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
if other_user.permissions == user.permissions {
|
||||
return Err(Error::MiscError(
|
||||
"Cannot manage users of equal level to you".to_string(),
|
||||
));
|
||||
if other_user.permissions.check_manager() && !user.permissions.check_admin() {
|
||||
return Err(Error::MiscError(
|
||||
"Cannot manage the role of other managers".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if other_user.permissions == user.permissions {
|
||||
return Err(Error::MiscError(
|
||||
"Cannot manage users of equal level to you".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
@ -645,6 +650,9 @@ impl DataManager {
|
|||
auto_method!(update_user_connections(UserConnections)@get_user_by_id -> "UPDATE users SET connections = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||
auto_method!(update_user_subscriptions(HashMap<usize, usize>)@get_user_by_id -> "UPDATE users SET subscriptions = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||
|
||||
auto_method!(get_user_by_stripe_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE stripe_id = $1" --name="user" --returns=User);
|
||||
auto_method!(update_user_stripe_id(&str)@get_user_by_id -> "UPDATE users SET stripe_id = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||
|
||||
auto_method!(incr_user_notifications()@get_user_by_id -> "UPDATE users SET notification_count = notification_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
|
||||
auto_method!(decr_user_notifications()@get_user_by_id -> "UPDATE users SET notification_count = notification_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr);
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ impl DataManager {
|
|||
execute!(&conn, common::CREATE_TABLE_IPBLOCKS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_CHANNELS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_MESSAGES).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_UPLOADS).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_EMOJIS).unwrap();
|
||||
|
||||
self.2
|
||||
.set("atto.active_connections:users".to_string(), "0".to_string())
|
||||
|
|
|
@ -15,3 +15,5 @@ pub const CREATE_TABLE_QUESTIONS: &str = include_str!("./sql/create_questions.sq
|
|||
pub const CREATE_TABLE_IPBLOCKS: &str = include_str!("./sql/create_ipblocks.sql");
|
||||
pub const CREATE_TABLE_CHANNELS: &str = include_str!("./sql/create_channels.sql");
|
||||
pub const CREATE_TABLE_MESSAGES: &str = include_str!("./sql/create_messages.sql");
|
||||
pub const CREATE_TABLE_UPLOADS: &str = include_str!("./sql/create_uploads.sql");
|
||||
pub const CREATE_TABLE_EMOJIS: &str = include_str!("./sql/create_emojis.sql");
|
||||
|
|
8
crates/core/src/database/drivers/sql/create_emojis.sql
Normal file
8
crates/core/src/database/drivers/sql/create_emojis.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE IF NOT EXISTS emojis (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
created BIGINT NOT NULL,
|
||||
owner BIGINT NOT NULL,
|
||||
community BIGINT NOT NULL,
|
||||
upload_id BIGINT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
)
|
6
crates/core/src/database/drivers/sql/create_uploads.sql
Normal file
6
crates/core/src/database/drivers/sql/create_uploads.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS uploads (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
created BIGINT NOT NULL,
|
||||
owner BIGINT NOT NULL,
|
||||
what TEXT NOT NULL
|
||||
)
|
191
crates/core/src/database/emojis.rs
Normal file
191
crates/core/src/database/emojis.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::{
|
||||
Error, Result, auth::User, permissions::FinePermission,
|
||||
communities_permissions::CommunityPermission, uploads::CustomEmoji,
|
||||
};
|
||||
use crate::{auto_method, execute, get, query_row, query_rows, params};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use rusqlite::Row;
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
use tokio_postgres::Row;
|
||||
|
||||
impl DataManager {
|
||||
/// Get a [`CustomEmoji`] from an SQL row.
|
||||
pub(crate) fn get_emoji_from_row(
|
||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||
#[cfg(feature = "postgres")] x: &Row,
|
||||
) -> CustomEmoji {
|
||||
CustomEmoji {
|
||||
id: get!(x->0(i64)) as usize,
|
||||
owner: get!(x->1(i64)) as usize,
|
||||
created: get!(x->2(i64)) as usize,
|
||||
community: get!(x->3(i64)) as usize,
|
||||
upload_id: get!(x->4(i64)) as usize,
|
||||
name: get!(x->5(String)),
|
||||
}
|
||||
}
|
||||
|
||||
auto_method!(get_emoji_by_id(usize as i64)@get_emoji_from_row -> "SELECT * FROM emojis WHERE id = $1" --name="emoji" --returns=CustomEmoji --cache-key-tmpl="atto.emoji:{}");
|
||||
|
||||
/// Get all emojis by community.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `community` - the ID of the community to fetch emojis for
|
||||
pub async fn get_emojis_by_community(&self, community: usize) -> Result<Vec<CustomEmoji>> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM emojis WHERE community = $1 ORDER BY name ASC",
|
||||
&[&(community as i64)],
|
||||
|x| { Self::get_emoji_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("emoji".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get all emojis by user.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `user` - the ID of the user to fetch emojis for
|
||||
pub async fn get_emojis_by_user(&self, user: usize) -> Result<Vec<CustomEmoji>> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM emojis WHERE (owner = $1 OR members LIKE $2) AND community = 0 ORDER BY created DESC",
|
||||
params![&(user as i64), &format!("%{user}%")],
|
||||
|x| { Self::get_emoji_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("emoji".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get a emoji given its `owner` and a member.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `owner` - the ID of the owner
|
||||
/// * `member` - the ID of the member
|
||||
pub async fn get_emoji_by_owner_member(
|
||||
&self,
|
||||
owner: usize,
|
||||
member: usize,
|
||||
) -> Result<CustomEmoji> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_row!(
|
||||
&conn,
|
||||
"SELECT * FROM emojis WHERE owner = $1 AND members = $2 AND community = 0 ORDER BY created DESC",
|
||||
params![&(owner as i64), &format!("[{member}]")],
|
||||
|x| { Ok(Self::get_emoji_from_row(x)) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("emoji".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new emoji in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - a mock [`CustomEmoji`] object to insert
|
||||
pub async fn create_emoji(&self, data: CustomEmoji) -> Result<()> {
|
||||
let user = self.get_user_by_id(data.owner).await?;
|
||||
|
||||
// check user permission in community
|
||||
if data.community != 0 {
|
||||
let membership = self
|
||||
.get_membership_by_owner_community(user.id, data.community)
|
||||
.await?;
|
||||
|
||||
if !membership.role.check(CommunityPermission::MANAGE_EMOJIS)
|
||||
&& !user.permissions.check(FinePermission::MANAGE_EMOJIS)
|
||||
{
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO emojis VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.owner as i64),
|
||||
&(data.created as i64),
|
||||
&(data.community as i64),
|
||||
&(data.upload_id as i64),
|
||||
&data.name
|
||||
]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_emoji(&self, id: usize, user: &User) -> Result<()> {
|
||||
let emoji = self.get_emoji_by_id(id).await?;
|
||||
|
||||
// check user permission in community
|
||||
if user.id != emoji.owner {
|
||||
let membership = self
|
||||
.get_membership_by_owner_community(user.id, emoji.community)
|
||||
.await?;
|
||||
|
||||
if !membership.role.check(CommunityPermission::MANAGE_EMOJIS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(&conn, "DELETE FROM emojis WHERE id = $1", &[&(id as i64)]);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// delete uploads
|
||||
self.delete_upload(emoji.upload_id).await?;
|
||||
|
||||
// ...
|
||||
self.2.remove(format!("atto.emoji:{}", id)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
auto_method!(update_emoji_name(&str)@get_emoji_by_id:MANAGE_EMOJIS -> "UPDATE emojis SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.emoji:{}");
|
||||
}
|
|
@ -4,6 +4,7 @@ mod common;
|
|||
mod communities;
|
||||
pub mod connections;
|
||||
mod drivers;
|
||||
mod emojis;
|
||||
mod ipbans;
|
||||
mod ipblocks;
|
||||
mod memberships;
|
||||
|
@ -13,6 +14,7 @@ mod questions;
|
|||
mod reactions;
|
||||
mod reports;
|
||||
mod requests;
|
||||
mod uploads;
|
||||
mod user_warnings;
|
||||
mod userblocks;
|
||||
mod userfollows;
|
||||
|
|
122
crates/core/src/database/uploads.rs
Normal file
122
crates/core/src/database/uploads.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use std::fs::{exists, remove_file};
|
||||
use super::*;
|
||||
use crate::cache::Cache;
|
||||
use crate::model::{Error, Result, uploads::MediaUpload};
|
||||
use crate::{auto_method, execute, get, query_row, query_rows, params};
|
||||
|
||||
use pathbufd::PathBufD;
|
||||
#[cfg(feature = "sqlite")]
|
||||
use rusqlite::Row;
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
use tokio_postgres::Row;
|
||||
|
||||
impl DataManager {
|
||||
/// Get a [`MediaUpload`] from an SQL row.
|
||||
pub(crate) fn get_upload_from_row(
|
||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||
#[cfg(feature = "postgres")] x: &Row,
|
||||
) -> MediaUpload {
|
||||
MediaUpload {
|
||||
id: get!(x->0(i64)) as usize,
|
||||
created: get!(x->1(i64)) as usize,
|
||||
owner: get!(x->2(i64)) as usize,
|
||||
what: serde_json::from_str(&get!(x->3(String))).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
auto_method!(get_upload_by_id(usize as i64)@get_upload_from_row -> "SELECT * FROM uploads WHERE id = $1" --name="upload" --returns=MediaUpload --cache-key-tmpl="atto.uploads:{}");
|
||||
|
||||
/// Get all uploads (paginated).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `batch` - the limit of items in each page
|
||||
/// * `page` - the page number
|
||||
pub async fn get_uploads(&self, batch: usize, page: usize) -> Result<Vec<MediaUpload>> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM uploads ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
&[&(batch as i64), &((page * batch) as i64)],
|
||||
|x| { Self::get_upload_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("upload".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Create a new upload in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - a mock [`MediaUpload`] object to insert
|
||||
pub async fn create_upload(&self, data: MediaUpload) -> Result<()> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO uploads VALUES ($1, $2, $3, $4)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
&(data.owner as i64),
|
||||
&serde_json::to_string(&data.what).unwrap().as_str(),
|
||||
]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_upload(&self, id: usize) -> Result<()> {
|
||||
// if !user.permissions.check(FinePermission::MANAGE_UPLOADS) {
|
||||
// return Err(Error::NotAllowed);
|
||||
// }
|
||||
|
||||
// delete file
|
||||
// it's most important that the file gets off the file system first, even
|
||||
// if there's an issue in the database
|
||||
//
|
||||
// the actual file takes up much more space than the database entry.
|
||||
let path = PathBufD::current().extend(&[self.0.dirs.media.as_str(), "uploads"]);
|
||||
if let Ok(exists) = exists(&path) {
|
||||
if exists {
|
||||
if let Err(e) = remove_file(&path) {
|
||||
return Err(Error::MiscError(e.to_string()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::GeneralNotFound("file".to_string()));
|
||||
}
|
||||
|
||||
// delete from database
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(&conn, "DELETE FROM uploads WHERE id = $1", &[&(id as i64)]);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
self.2.remove(format!("atto.upload:{}", id)).await;
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -40,6 +40,9 @@ pub struct User {
|
|||
/// External service connection details.
|
||||
#[serde(default)]
|
||||
pub connections: UserConnections,
|
||||
/// The user's Stripe customer ID.
|
||||
#[serde(default)]
|
||||
pub stripe_id: String,
|
||||
}
|
||||
|
||||
pub type UserConnections =
|
||||
|
@ -245,6 +248,7 @@ impl User {
|
|||
post_count: 0,
|
||||
request_count: 0,
|
||||
connections: HashMap::new(),
|
||||
stripe_id: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ bitflags! {
|
|||
const MANAGE_QUESTIONS = 1 << 9;
|
||||
const MANAGE_CHANNELS = 1 << 10;
|
||||
const MANAGE_MESSAGES = 1 << 11;
|
||||
const MANAGE_EMOJIS = 1 << 12;
|
||||
|
||||
const _ = !0;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ pub mod oauth;
|
|||
pub mod permissions;
|
||||
pub mod reactions;
|
||||
pub mod requests;
|
||||
pub mod uploads;
|
||||
|
||||
#[cfg(feature = "redis")]
|
||||
pub mod channels;
|
||||
|
|
|
@ -32,6 +32,8 @@ bitflags! {
|
|||
const MANAGE_QUESTIONS = 1 << 21;
|
||||
const MANAGE_CHANNELS = 1 << 22;
|
||||
const MANAGE_MESSAGES = 1 << 23;
|
||||
const MANAGE_UPLOADS = 1 << 24;
|
||||
const MANAGE_EMOJIS = 1 << 25;
|
||||
|
||||
const _ = !0;
|
||||
}
|
||||
|
|
66
crates/core/src/model/uploads.rs
Normal file
66
crates/core/src/model/uploads.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use tetratto_shared::{snow::AlmostSnowflake, unix_epoch_timestamp};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum MediaType {
|
||||
#[serde(alias = "image/webp")]
|
||||
Webp,
|
||||
#[serde(alias = "image/avif")]
|
||||
Avif,
|
||||
#[serde(alias = "image/png")]
|
||||
Png,
|
||||
#[serde(alias = "image/jpg")]
|
||||
Jpg,
|
||||
#[serde(alias = "image/gif")]
|
||||
Gif,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MediaUpload {
|
||||
pub id: usize,
|
||||
pub created: usize,
|
||||
pub owner: usize,
|
||||
pub what: MediaType,
|
||||
}
|
||||
|
||||
impl MediaUpload {
|
||||
/// Create a new [`MediaUpload`].
|
||||
pub fn new(what: MediaType, owner: usize) -> Self {
|
||||
Self {
|
||||
id: AlmostSnowflake::new(1234567890)
|
||||
.to_string()
|
||||
.parse::<usize>()
|
||||
.unwrap(),
|
||||
created: unix_epoch_timestamp() as usize,
|
||||
owner,
|
||||
what,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CustomEmoji {
|
||||
pub id: usize,
|
||||
pub created: usize,
|
||||
pub owner: usize,
|
||||
pub community: usize,
|
||||
pub upload_id: usize,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl CustomEmoji {
|
||||
/// Create a new [`CustomEmoji`].
|
||||
pub fn new(owner: usize, community: usize, upload_id: usize, name: String) -> Self {
|
||||
Self {
|
||||
id: AlmostSnowflake::new(1234567890)
|
||||
.to_string()
|
||||
.parse::<usize>()
|
||||
.unwrap(),
|
||||
created: unix_epoch_timestamp() as usize,
|
||||
owner,
|
||||
community,
|
||||
upload_id,
|
||||
name,
|
||||
}
|
||||
}
|
||||
}
|
2
sql_changes/users_stripe_id.sql
Normal file
2
sql_changes/users_stripe_id.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE users
|
||||
ADD COLUMN stripe_id TEXT DEFAULT '';
|
Loading…
Add table
Add a link
Reference in a new issue