Compare commits
38 Commits
s3
..
blog-title
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bd8f5272f | |||
| 39cd4f830d | |||
| cd9cb311c7 | |||
| 83ed168f9c | |||
| 83c628d490 | |||
| badff3f3cb | |||
| ba00e36884 | |||
| 5ee84427bf | |||
| f203dddae5 | |||
| ba1eac9482 | |||
| 3dad83b179 | |||
| 4eab51b159 | |||
| abf0b28fd4 | |||
| 115b5b31a4 | |||
| 5a03fd7340 | |||
| e75449410f | |||
| c9bb31b8f5 | |||
| 0c2eaf0f1b | |||
| 71824aa524 | |||
| fc848a8d53 | |||
| 53cdd8198b | |||
| 08f4dac3d3 | |||
| 18a9ed5504 | |||
| 631359c3f7 | |||
| 3111fa0735 | |||
| 890c9a0da4 | |||
| 22b03710be | |||
| e3609f7863 | |||
| 0714d2d010 | |||
| 5bd084eff7 | |||
| f369fa9b25 | |||
| 8afcc1511e | |||
| ce89faef84 | |||
| e18b6e78f2 | |||
| 31e817385d | |||
| af7ed450e2 | |||
| 55a5a64b1a | |||
| 08b7d100fd |
Generated
+35
-262
@@ -115,12 +115,6 @@ dependencies = [
|
|||||||
"const-random",
|
"const-random",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -172,12 +166,6 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.71"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@@ -231,15 +219,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atom_syndication"
|
name = "atom_syndication"
|
||||||
version = "0.12.0"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91a85f2ee28cbd1ecf91288460f6dc74661fd99b4e9a559836a667ccf63aa38c"
|
checksum = "21fb6a0b39c6517edafe46f8137e53c51742425a4dae1c73ee12264a37ad7541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"diligent-date-parser",
|
"diligent-date-parser",
|
||||||
"never",
|
"never",
|
||||||
"quick-xml 0.27.1",
|
"quick-xml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -253,22 +241,6 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "attohttpc"
|
|
||||||
version = "0.18.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e69e13a99a7e6e070bb114f7ff381e58c7ccc188630121fc4c2fe4bcf24cd072"
|
|
||||||
dependencies = [
|
|
||||||
"http 0.2.8",
|
|
||||||
"log 0.4.17",
|
|
||||||
"native-tls",
|
|
||||||
"openssl",
|
|
||||||
"serde 1.0.152",
|
|
||||||
"serde_json",
|
|
||||||
"url 2.3.1",
|
|
||||||
"wildmatch",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -295,31 +267,6 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aws-creds"
|
|
||||||
version = "0.27.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "460a75eac8f3cb7683e0a9a588a83c3ff039331ea7bfbfbfcecf1dacab276e11"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"attohttpc",
|
|
||||||
"dirs",
|
|
||||||
"rust-ini 0.17.0",
|
|
||||||
"serde 1.0.152",
|
|
||||||
"serde-xml-rs",
|
|
||||||
"serde_derive",
|
|
||||||
"url 2.3.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aws-region"
|
|
||||||
version = "0.23.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "10110ddbd800fb47e6bef95e88fc13495795d252f585272a4fa3ac4f5b2e0a4d"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
@@ -442,16 +389,6 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block_on_proc"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b872f3528eeeb4370ee73b51194dc1cd93680c2d0eb6c7a223889038d2c1a167"
|
|
||||||
dependencies = [
|
|
||||||
"quote 1.0.23",
|
|
||||||
"syn 1.0.107",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blowfish"
|
name = "blowfish"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -641,7 +578,7 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"nom 5.1.2",
|
"nom 5.1.2",
|
||||||
"rust-ini 0.13.0",
|
"rust-ini",
|
||||||
"serde 1.0.152",
|
"serde 1.0.152",
|
||||||
"serde-hjson",
|
"serde-hjson",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -699,7 +636,7 @@ dependencies = [
|
|||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"hmac 0.10.1",
|
"hmac",
|
||||||
"percent-encoding 2.2.0",
|
"percent-encoding 2.2.0",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"sha2",
|
"sha2",
|
||||||
@@ -939,16 +876,6 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crypto-mac"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
"subtle",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctr"
|
name = "ctr"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -1020,9 +947,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.14.2"
|
version = "0.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
|
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
@@ -1030,9 +957,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.14.2"
|
version = "0.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
|
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
@@ -1044,9 +971,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.14.2"
|
version = "0.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
|
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote 1.0.23",
|
"quote 1.0.23",
|
||||||
@@ -1097,18 +1024,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder"
|
name = "derive_builder"
|
||||||
version = "0.12.0"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_builder_macro",
|
"derive_builder_macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder_core"
|
name = "derive_builder_core"
|
||||||
version = "0.12.0"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2 1.0.49",
|
"proc-macro2 1.0.49",
|
||||||
@@ -1118,9 +1045,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder_macro"
|
name = "derive_builder_macro"
|
||||||
version = "0.12.0"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_builder_core",
|
"derive_builder_core",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
@@ -1223,35 +1150,6 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs"
|
|
||||||
version = "4.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
|
||||||
dependencies = [
|
|
||||||
"dirs-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs-sys"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"redox_users",
|
|
||||||
"winapi 0.3.9",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dlv-list"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b"
|
|
||||||
dependencies = [
|
|
||||||
"rand 0.8.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenv"
|
name = "dotenv"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@@ -1734,9 +1632,9 @@ checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.1"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "guid"
|
name = "guid"
|
||||||
@@ -1847,15 +1745,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
|
||||||
dependencies = [
|
|
||||||
"ahash 0.4.7",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
@@ -1874,6 +1763,12 @@ dependencies = [
|
|||||||
"ahash 0.7.6",
|
"ahash 0.7.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
@@ -1905,7 +1800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
|
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
"hmac 0.10.1",
|
"hmac",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1914,17 +1809,7 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
|
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-mac 0.10.1",
|
"crypto-mac",
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hmac"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
|
||||||
dependencies = [
|
|
||||||
"crypto-mac 0.11.1",
|
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2670,17 +2555,6 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "maybe-async"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0f1b8c13cb1f814b634a96b2c725449fe7ed464a7b8781de8688be5ffbd3f305"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 1.0.49",
|
|
||||||
"quote 1.0.23",
|
|
||||||
"syn 1.0.107",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "maybe-uninit"
|
name = "maybe-uninit"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -2773,15 +2647,6 @@ dependencies = [
|
|||||||
"unicase 2.6.0",
|
"unicase 2.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minidom"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "332592c2149fc7dd40a64fc9ef6f0d65607284b474cef9817d1fc8c7e7b3608e"
|
|
||||||
dependencies = [
|
|
||||||
"quick-xml 0.20.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -3204,16 +3069,6 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ordered-multimap"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485"
|
|
||||||
dependencies = [
|
|
||||||
"dlv-list",
|
|
||||||
"hashbrown 0.9.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -3445,7 +3300,6 @@ dependencies = [
|
|||||||
"rocket_i18n",
|
"rocket_i18n",
|
||||||
"rsass",
|
"rsass",
|
||||||
"ructe",
|
"ructe",
|
||||||
"rust-s3",
|
|
||||||
"scheduled-thread-pool",
|
"scheduled-thread-pool",
|
||||||
"serde 1.0.152",
|
"serde 1.0.152",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3500,6 +3354,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"shrinkwraprs",
|
"shrinkwraprs",
|
||||||
"syntect",
|
"syntect",
|
||||||
|
"thiserror",
|
||||||
"tokio 1.24.1",
|
"tokio 1.24.1",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url 2.3.1",
|
"url 2.3.1",
|
||||||
@@ -3545,6 +3400,7 @@ dependencies = [
|
|||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"glob",
|
"glob",
|
||||||
"guid-create",
|
"guid-create",
|
||||||
|
"heck",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"ldap3",
|
"ldap3",
|
||||||
@@ -3561,7 +3417,6 @@ dependencies = [
|
|||||||
"riker",
|
"riker",
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_i18n",
|
"rocket_i18n",
|
||||||
"rust-s3",
|
|
||||||
"scheduled-thread-pool",
|
"scheduled-thread-pool",
|
||||||
"serde 1.0.152",
|
"serde 1.0.152",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
@@ -3707,18 +3562,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.20.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26aab6b48e2590e4a64d1ed808749ba06257882b461d01ca71baeb747074a6dd"
|
checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b"
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quick-xml"
|
|
||||||
version = "0.27.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ffc053f057dd768a56f62cd7e434c42c831d296968997e9ac1f76ea7c2d14c41"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -3998,17 +3844,6 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_users"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.2.8",
|
|
||||||
"redox_syscall 0.2.16",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@@ -4140,7 +3975,6 @@ dependencies = [
|
|||||||
"tokio 1.24.1",
|
"tokio 1.24.1",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tokio-socks",
|
"tokio-socks",
|
||||||
"tokio-util 0.7.4",
|
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url 2.3.1",
|
"url 2.3.1",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -4298,11 +4132,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsass"
|
name = "rsass"
|
||||||
version = "0.26.0"
|
version = "0.25.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "665387d3be91521de331a04cc7118c8c129ffe43c5f4e8a61e78990850046b5a"
|
checksum = "0ab003a9ff922b4d7b1f5f383bc54645887ad278f534dfc65e071096fcb30023"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"nom 7.1.2",
|
"nom 7.1.2",
|
||||||
@@ -4316,9 +4149,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ructe"
|
name = "ructe"
|
||||||
version = "0.15.0"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85517cd381cf0c34694881d8aaf173107c6af7670e66cec18d7a1a8bfce3b758"
|
checksum = "ef279e568db5dad3a1867112fedc63e2f0567991e82beec01c04ade6bd53c08a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
@@ -4333,48 +4166,6 @@ version = "0.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
|
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rust-ini"
|
|
||||||
version = "0.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 1.0.0",
|
|
||||||
"ordered-multimap",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rust-s3"
|
|
||||||
version = "0.29.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a4e82923ed07143871571852a390742200607e5058ce633afec89752f9c3f82"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"aws-creds",
|
|
||||||
"aws-region",
|
|
||||||
"base64 0.13.1",
|
|
||||||
"block_on_proc",
|
|
||||||
"cfg-if 1.0.0",
|
|
||||||
"hex",
|
|
||||||
"hmac 0.11.0",
|
|
||||||
"http 0.2.8",
|
|
||||||
"log 0.4.17",
|
|
||||||
"maybe-async",
|
|
||||||
"md5",
|
|
||||||
"minidom",
|
|
||||||
"percent-encoding 2.2.0",
|
|
||||||
"reqwest 0.11.13",
|
|
||||||
"serde 1.0.152",
|
|
||||||
"serde-xml-rs",
|
|
||||||
"serde_derive",
|
|
||||||
"sha2",
|
|
||||||
"time 0.3.17",
|
|
||||||
"tokio 1.24.1",
|
|
||||||
"tokio-stream",
|
|
||||||
"url 2.3.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-stemmers"
|
name = "rust-stemmers"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -4518,18 +4309,6 @@ dependencies = [
|
|||||||
"serde 0.8.23",
|
"serde 0.8.23",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde-xml-rs"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa"
|
|
||||||
dependencies = [
|
|
||||||
"log 0.4.17",
|
|
||||||
"serde 1.0.152",
|
|
||||||
"thiserror",
|
|
||||||
"xml-rs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.152"
|
version = "1.0.152"
|
||||||
@@ -5907,12 +5686,6 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wildmatch"
|
|
||||||
version = "2.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ee583bdc5ff1cf9db20e9db5bb3ff4c3089a8f6b8b31aff265c9aba85812db86"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
|||||||
+3
-5
@@ -6,7 +6,7 @@ repository = "https://github.com/Plume-org/Plume"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atom_syndication = "0.12.0"
|
atom_syndication = "0.11.0"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
gettext = "0.4.0"
|
gettext = "0.4.0"
|
||||||
@@ -19,7 +19,6 @@ rocket = "0.4.11"
|
|||||||
rocket_contrib = { version = "0.4.11", features = ["json"] }
|
rocket_contrib = { version = "0.4.11", features = ["json"] }
|
||||||
rocket_i18n = "0.4.1"
|
rocket_i18n = "0.4.1"
|
||||||
scheduled-thread-pool = "0.2.6"
|
scheduled-thread-pool = "0.2.6"
|
||||||
rust-s3 = { version = "0.29.0", no-default-features = true, features = ["blocking"], optional = true}
|
|
||||||
serde = "1.0.137"
|
serde = "1.0.137"
|
||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
@@ -65,8 +64,8 @@ git = "https://git.joinplu.me/plume/rocket_csrf"
|
|||||||
rev = "0.1.2"
|
rev = "0.1.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ructe = "0.15.0"
|
ructe = "0.14.0"
|
||||||
rsass = "0.26"
|
rsass = "0.25"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["postgres"]
|
default = ["postgres"]
|
||||||
@@ -75,7 +74,6 @@ sqlite = ["plume-models/sqlite", "diesel/sqlite"]
|
|||||||
debug-mailer = []
|
debug-mailer = []
|
||||||
test = []
|
test = []
|
||||||
search-lindera = ["plume-models/search-lindera"]
|
search-lindera = ["plume-models/search-lindera"]
|
||||||
s3 = ["rust-s3"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"]
|
members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"]
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE users DROP CONSTRAINT users_fqn;
|
||||||
|
ALTER TABLE blogs DROP CONSTRAINT blogs_actor_id;
|
||||||
|
ALTER TABLE blogs DROP CONSTRAINT blogs_fqn;
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE users ADD CONSTRAINT users_fqn UNIQUE (fqn);
|
||||||
|
ALTER TABLE blogs ADD CONSTRAINT blogs_actor_id UNIQUE (actor_id);
|
||||||
|
ALTER TABLE blogs ADD CONSTRAINT blogs_fqn UNIQUE (fqn);
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
DROP INDEX users_fqn;
|
||||||
|
DROP INDEX blogs_actor_id;
|
||||||
|
DROP INDEX blogs_fqn;
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
CREATE UNIQUE INDEX users_fqn ON users (fqn);
|
||||||
|
CREATE UNIQUE INDEX blogs_actor_id ON blogs (actor_id);
|
||||||
|
CREATE UNIQUE INDEX blogs_fqn ON blogs (fqn);
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
|
|
||||||
use plume_models::{blogs::Blog, instance::Instance, lists::*, users::User, Connection};
|
|
||||||
|
|
||||||
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
|
||||||
SubCommand::with_name("lists")
|
|
||||||
.about("Manage lists")
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("new")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of this list"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("type")
|
|
||||||
.short("t")
|
|
||||||
.long("type")
|
|
||||||
.takes_value(true)
|
|
||||||
.help(
|
|
||||||
r#"The type of this list (one of "user", "blog", "word" or "prefix")"#,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Username of whom this list is for. Empty for an instance list"),
|
|
||||||
)
|
|
||||||
.about("Create a new list"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("delete")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of the list to delete"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Username of whom this list was for. Empty for instance list"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("yes")
|
|
||||||
.short("y")
|
|
||||||
.long("yes")
|
|
||||||
.help("Confirm the deletion"),
|
|
||||||
)
|
|
||||||
.about("Delete a list"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("add")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of the list to add an element to"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Username of whom this list is for. Empty for instance list"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("value")
|
|
||||||
.short("v")
|
|
||||||
.long("value")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The value to add"),
|
|
||||||
)
|
|
||||||
.about("Add element to a list"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("rm")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of the list to remove an element from"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Username of whom this list is for. Empty for instance list"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("value")
|
|
||||||
.short("v")
|
|
||||||
.long("value")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The value to remove"),
|
|
||||||
)
|
|
||||||
.about("Remove element from list"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
|
||||||
let conn = conn;
|
|
||||||
match args.subcommand() {
|
|
||||||
("new", Some(x)) => new(x, conn),
|
|
||||||
("delete", Some(x)) => delete(x, conn),
|
|
||||||
("add", Some(x)) => add(x, conn),
|
|
||||||
("rm", Some(x)) => rm(x, conn),
|
|
||||||
("", None) => command().print_help().unwrap(),
|
|
||||||
_ => println!("Unknown subcommand"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_list_identifier(args: &ArgMatches<'_>) -> (String, Option<String>) {
|
|
||||||
let name = args
|
|
||||||
.value_of("name")
|
|
||||||
.map(String::from)
|
|
||||||
.expect("No name provided for the list");
|
|
||||||
let user = args.value_of("user").map(String::from);
|
|
||||||
(name, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_list_type(args: &ArgMatches<'_>) -> ListType {
|
|
||||||
let typ = args
|
|
||||||
.value_of("type")
|
|
||||||
.map(String::from)
|
|
||||||
.expect("No name type for the list");
|
|
||||||
match typ.as_str() {
|
|
||||||
"user" => ListType::User,
|
|
||||||
"blog" => ListType::Blog,
|
|
||||||
"word" => ListType::Word,
|
|
||||||
"prefix" => ListType::Prefix,
|
|
||||||
_ => panic!("Invalid list type: {}", typ),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_value(args: &ArgMatches<'_>) -> String {
|
|
||||||
args.value_of("value")
|
|
||||||
.map(String::from)
|
|
||||||
.expect("No query provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_user(username: &str, conn: &Connection) -> User {
|
|
||||||
let instance = Instance::get_local_uncached(conn).expect("Failed to load local instance");
|
|
||||||
|
|
||||||
User::find_by_name(conn, username, instance.id).expect("User not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_list_identifier(args);
|
|
||||||
let typ = get_list_type(args);
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
List::new(conn, &name, user.as_ref(), typ).expect("failed to create list");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_list_identifier(args);
|
|
||||||
|
|
||||||
if !args.is_present("yes") {
|
|
||||||
panic!("Warning, this operation is destructive. Add --yes to confirm you want to do it.")
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
let list =
|
|
||||||
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
|
|
||||||
|
|
||||||
list.delete(conn).expect("Failed to update list");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_list_identifier(args);
|
|
||||||
let value = get_value(args);
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
let list =
|
|
||||||
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
|
|
||||||
|
|
||||||
match list.kind() {
|
|
||||||
ListType::Blog => {
|
|
||||||
let blog_id = Blog::find_by_fqn(conn, &value).expect("unknown blog").id;
|
|
||||||
if !list.contains_blog(conn, blog_id).unwrap() {
|
|
||||||
list.add_blogs(conn, &[blog_id]).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListType::User => {
|
|
||||||
let user_id = User::find_by_fqn(conn, &value).expect("unknown user").id;
|
|
||||||
if !list.contains_user(conn, user_id).unwrap() {
|
|
||||||
list.add_users(conn, &[user_id]).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListType::Word => {
|
|
||||||
if !list.contains_word(conn, &value).unwrap() {
|
|
||||||
list.add_words(conn, &[&value]).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListType::Prefix => {
|
|
||||||
if !list.contains_prefix(conn, &value).unwrap() {
|
|
||||||
list.add_prefixes(conn, &[&value]).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rm(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_list_identifier(args);
|
|
||||||
let value = get_value(args);
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
let list =
|
|
||||||
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
|
|
||||||
|
|
||||||
match list.kind() {
|
|
||||||
ListType::Blog => {
|
|
||||||
let blog_id = Blog::find_by_fqn(conn, &value).expect("unknown blog").id;
|
|
||||||
let mut blogs = list.list_blogs(conn).unwrap();
|
|
||||||
if let Some(index) = blogs.iter().position(|b| b.id == blog_id) {
|
|
||||||
blogs.swap_remove(index);
|
|
||||||
let blogs = blogs.iter().map(|b| b.id).collect::<Vec<_>>();
|
|
||||||
list.set_blogs(conn, &blogs).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListType::User => {
|
|
||||||
let user_id = User::find_by_fqn(conn, &value).expect("unknown user").id;
|
|
||||||
let mut users = list.list_users(conn).unwrap();
|
|
||||||
if let Some(index) = users.iter().position(|u| u.id == user_id) {
|
|
||||||
users.swap_remove(index);
|
|
||||||
let users = users.iter().map(|u| u.id).collect::<Vec<_>>();
|
|
||||||
list.set_users(conn, &users).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListType::Word => {
|
|
||||||
let mut words = list.list_words(conn).unwrap();
|
|
||||||
if let Some(index) = words.iter().position(|w| *w == value) {
|
|
||||||
words.swap_remove(index);
|
|
||||||
let words = words.iter().map(String::as_str).collect::<Vec<_>>();
|
|
||||||
list.set_words(conn, &words).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListType::Prefix => {
|
|
||||||
let mut prefixes = list.list_prefixes(conn).unwrap();
|
|
||||||
if let Some(index) = prefixes.iter().position(|p| *p == value) {
|
|
||||||
prefixes.swap_remove(index);
|
|
||||||
let prefixes = prefixes.iter().map(String::as_str).collect::<Vec<_>>();
|
|
||||||
list.set_prefixes(conn, &prefixes).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,10 +4,8 @@ use plume_models::{instance::Instance, Connection as Conn, CONFIG};
|
|||||||
use std::io::{self, prelude::*};
|
use std::io::{self, prelude::*};
|
||||||
|
|
||||||
mod instance;
|
mod instance;
|
||||||
mod list;
|
|
||||||
mod migration;
|
mod migration;
|
||||||
mod search;
|
mod search;
|
||||||
mod timeline;
|
|
||||||
mod users;
|
mod users;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -18,8 +16,6 @@ fn main() {
|
|||||||
.subcommand(instance::command())
|
.subcommand(instance::command())
|
||||||
.subcommand(migration::command())
|
.subcommand(migration::command())
|
||||||
.subcommand(search::command())
|
.subcommand(search::command())
|
||||||
.subcommand(timeline::command())
|
|
||||||
.subcommand(list::command())
|
|
||||||
.subcommand(users::command());
|
.subcommand(users::command());
|
||||||
let matches = app.clone().get_matches();
|
let matches = app.clone().get_matches();
|
||||||
|
|
||||||
@@ -41,10 +37,6 @@ fn main() {
|
|||||||
("search", Some(args)) => {
|
("search", Some(args)) => {
|
||||||
search::run(args, &conn.expect("Couldn't connect to the database."))
|
search::run(args, &conn.expect("Couldn't connect to the database."))
|
||||||
}
|
}
|
||||||
("timeline", Some(args)) => {
|
|
||||||
timeline::run(args, &conn.expect("Couldn't connect to the database."))
|
|
||||||
}
|
|
||||||
("lists", Some(args)) => list::run(args, &conn.expect("Couldn't connect to the database.")),
|
|
||||||
("users", Some(args)) => {
|
("users", Some(args)) => {
|
||||||
users::run(args, &conn.expect("Couldn't connect to the database."))
|
users::run(args, &conn.expect("Couldn't connect to the database."))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
|
|
||||||
use plume_models::{instance::Instance, posts::Post, timeline::*, users::*, Connection};
|
|
||||||
|
|
||||||
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
|
||||||
SubCommand::with_name("timeline")
|
|
||||||
.about("Manage public timeline")
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("new")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of this timeline"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("query")
|
|
||||||
.short("q")
|
|
||||||
.long("query")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The query posts in this timelines have to match"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help(
|
|
||||||
"Username of whom this timeline is for. Empty for an instance timeline",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("preload-count")
|
|
||||||
.short("p")
|
|
||||||
.long("preload-count")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Number of posts to try to preload in this timeline at its creation"),
|
|
||||||
)
|
|
||||||
.about("Create a new timeline"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("delete")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of the timeline to delete"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help(
|
|
||||||
"Username of whom this timeline was for. Empty for instance timeline",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("yes")
|
|
||||||
.short("y")
|
|
||||||
.long("yes")
|
|
||||||
.help("Confirm the deletion"),
|
|
||||||
)
|
|
||||||
.about("Delete a timeline"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("edit")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of the timeline to edit"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Username of whom this timeline is for. Empty for instance timeline"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("query")
|
|
||||||
.short("q")
|
|
||||||
.long("query")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The query posts in this timelines have to match"),
|
|
||||||
)
|
|
||||||
.about("Edit the query of a timeline"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("repopulate")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("The name of the timeline to repopulate"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("user")
|
|
||||||
.short("u")
|
|
||||||
.long("user")
|
|
||||||
.takes_value(true)
|
|
||||||
.help(
|
|
||||||
"Username of whom this timeline was for. Empty for instance timeline",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("preload-count")
|
|
||||||
.short("p")
|
|
||||||
.long("preload-count")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Number of posts to try to preload in this timeline at its creation"),
|
|
||||||
)
|
|
||||||
.about("Repopulate a timeline. Run this after modifying a list the timeline depends on."),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
|
||||||
let conn = conn;
|
|
||||||
match args.subcommand() {
|
|
||||||
("new", Some(x)) => new(x, conn),
|
|
||||||
("edit", Some(x)) => edit(x, conn),
|
|
||||||
("delete", Some(x)) => delete(x, conn),
|
|
||||||
("repopulate", Some(x)) => repopulate(x, conn),
|
|
||||||
("", None) => command().print_help().unwrap(),
|
|
||||||
_ => println!("Unknown subcommand"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_timeline_identifier(args: &ArgMatches<'_>) -> (String, Option<String>) {
|
|
||||||
let name = args
|
|
||||||
.value_of("name")
|
|
||||||
.map(String::from)
|
|
||||||
.expect("No name provided for the timeline");
|
|
||||||
let user = args.value_of("user").map(String::from);
|
|
||||||
(name, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_query(args: &ArgMatches<'_>) -> String {
|
|
||||||
let query = args
|
|
||||||
.value_of("query")
|
|
||||||
.map(String::from)
|
|
||||||
.expect("No query provided");
|
|
||||||
|
|
||||||
match TimelineQuery::parse(&query) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(QueryError::SyntaxError(start, end, message)) => panic!(
|
|
||||||
"Query parsing error between {} and {}: {}",
|
|
||||||
start, end, message
|
|
||||||
),
|
|
||||||
Err(QueryError::UnexpectedEndOfQuery) => {
|
|
||||||
panic!("Query parsing error: unexpected end of query")
|
|
||||||
}
|
|
||||||
Err(QueryError::RuntimeError(message)) => panic!("Query parsing error: {}", message),
|
|
||||||
}
|
|
||||||
|
|
||||||
query
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_preload_count(args: &ArgMatches<'_>) -> usize {
|
|
||||||
args.value_of("preload-count")
|
|
||||||
.map(|arg| arg.parse().expect("invalid preload-count"))
|
|
||||||
.unwrap_or(plume_models::ITEMS_PER_PAGE as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_user(username: &str, conn: &Connection) -> User {
|
|
||||||
let instance = Instance::get_local_uncached(conn).expect("Failed to load local instance");
|
|
||||||
|
|
||||||
User::find_by_name(conn, username, instance.id).expect("User not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn preload(timeline: Timeline, count: usize, conn: &Connection) {
|
|
||||||
timeline.remove_all_posts(conn).unwrap();
|
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut posts = Vec::with_capacity(count as usize);
|
|
||||||
for post in Post::list_filtered(conn, None, None, None)
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
{
|
|
||||||
if timeline.matches(conn, &post, Kind::Original).unwrap() {
|
|
||||||
posts.push(post);
|
|
||||||
if posts.len() >= count {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for post in posts.iter().rev() {
|
|
||||||
timeline.add_post(conn, post).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_timeline_identifier(args);
|
|
||||||
let query = get_query(args);
|
|
||||||
let preload_count = get_preload_count(args);
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
let timeline = if let Some(user) = user {
|
|
||||||
Timeline::new_for_user(conn, user.id, name, query)
|
|
||||||
} else {
|
|
||||||
Timeline::new_for_instance(conn, name, query)
|
|
||||||
}
|
|
||||||
.expect("Failed to create new timeline");
|
|
||||||
|
|
||||||
preload(timeline, preload_count, conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn edit(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_timeline_identifier(args);
|
|
||||||
let query = get_query(args);
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
let mut timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
|
|
||||||
.expect("timeline not found");
|
|
||||||
|
|
||||||
timeline.query = query;
|
|
||||||
|
|
||||||
timeline.update(conn).expect("Failed to update timeline");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_timeline_identifier(args);
|
|
||||||
|
|
||||||
if !args.is_present("yes") {
|
|
||||||
panic!("Warning, this operation is destructive. Add --yes to confirm you want to do it.")
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
let timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
|
|
||||||
.expect("timeline not found");
|
|
||||||
|
|
||||||
timeline.delete(conn).expect("Failed to update timeline");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn repopulate(args: &ArgMatches<'_>, conn: &Connection) {
|
|
||||||
let (name, user) = get_timeline_identifier(args);
|
|
||||||
let preload_count = get_preload_count(args);
|
|
||||||
|
|
||||||
let user = user.map(|user| resolve_user(&user, conn));
|
|
||||||
|
|
||||||
let timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
|
|
||||||
.expect("timeline not found");
|
|
||||||
preload(timeline, preload_count, conn);
|
|
||||||
}
|
|
||||||
@@ -25,6 +25,7 @@ url = "2.2.2"
|
|||||||
flume = "0.10.13"
|
flume = "0.10.13"
|
||||||
tokio = { version = "1.19.2", features = ["full"] }
|
tokio = { version = "1.19.2", features = ["full"] }
|
||||||
futures = "0.3.25"
|
futures = "0.3.25"
|
||||||
|
thiserror = "1.0.38"
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ use rocket::{
|
|||||||
response::{Responder, Response},
|
response::{Responder, Response},
|
||||||
Outcome,
|
Outcome,
|
||||||
};
|
};
|
||||||
|
use std::{
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
|
fmt,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
runtime,
|
runtime,
|
||||||
time::{sleep, Duration},
|
time::{sleep, Duration},
|
||||||
@@ -241,6 +246,97 @@ pub trait IntoId {
|
|||||||
fn into_id(self) -> Id;
|
fn into_id(self) -> Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum PreferredUsernameError {
|
||||||
|
#[error("preferredUsername must be longer than 2 characters")]
|
||||||
|
TooShort,
|
||||||
|
#[error("Invaliad character at {character:?}: {position:?}")]
|
||||||
|
InvalidCharacter { character: char, position: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Shrinkwrap, PartialEq, Eq, Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct PreferredUsername(String);
|
||||||
|
|
||||||
|
// Mastodon allows only /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i for `preferredUsername`
|
||||||
|
impl PreferredUsername {
|
||||||
|
fn validate(name: &str) -> std::result::Result<(), PreferredUsernameError> {
|
||||||
|
let len = name.len();
|
||||||
|
if len < 3 {
|
||||||
|
return Err(PreferredUsernameError::TooShort);
|
||||||
|
}
|
||||||
|
match name.chars().enumerate().find(|(pos, c)| {
|
||||||
|
if pos == &0 || pos == &(len - 1) {
|
||||||
|
c != &'_' && !c.is_ascii_alphanumeric()
|
||||||
|
} else {
|
||||||
|
match c {
|
||||||
|
'_' | '\\' | '.' | '-' => false,
|
||||||
|
_ => !c.is_ascii_alphanumeric(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Some((pos, c)) => Err(PreferredUsernameError::InvalidCharacter {
|
||||||
|
character: c,
|
||||||
|
position: pos,
|
||||||
|
}),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The given string must be match against /\A[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?\z/i in Ruby's RegExp which is required by Mastodon.
|
||||||
|
pub unsafe fn new_unchecked(name: String) -> Self {
|
||||||
|
Self(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(name: String) -> std::result::Result<Self, PreferredUsernameError> {
|
||||||
|
Self::validate(&name).map(|_| unsafe { Self::new_unchecked(name) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PreferredUsername {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for PreferredUsername {
|
||||||
|
type Error = PreferredUsernameError;
|
||||||
|
|
||||||
|
fn try_from(name: String) -> std::result::Result<Self, Self::Error> {
|
||||||
|
Self::new(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for PreferredUsername {
|
||||||
|
type Error = PreferredUsernameError;
|
||||||
|
|
||||||
|
fn try_from(name: &str) -> std::result::Result<Self, Self::Error> {
|
||||||
|
Self::new(name.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PreferredUsername> for String {
|
||||||
|
fn from(preferred_username: PreferredUsername) -> Self {
|
||||||
|
preferred_username.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for PreferredUsername {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PreferredUsername {
|
||||||
|
type Err = PreferredUsernameError;
|
||||||
|
|
||||||
|
fn from_str(name: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
name.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ApSignature {
|
pub struct ApSignature {
|
||||||
@@ -524,6 +620,35 @@ mod tests {
|
|||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use serde_json::{from_str, json, to_value};
|
use serde_json::{from_str, json, to_value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preferred_username() {
|
||||||
|
assert!(PreferredUsername::new("".into()).is_err());
|
||||||
|
assert!(PreferredUsername::new("a".into()).is_err());
|
||||||
|
assert!(PreferredUsername::new("ab".into()).is_err());
|
||||||
|
assert_eq!(
|
||||||
|
"abc",
|
||||||
|
PreferredUsername::new("abc".into()).unwrap().as_str()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"abcd",
|
||||||
|
PreferredUsername::new("abcd".into()).unwrap().as_str()
|
||||||
|
);
|
||||||
|
assert!(PreferredUsername::new("abc-".into()).is_err());
|
||||||
|
assert!(PreferredUsername::new("日本語".into()).is_err());
|
||||||
|
assert_eq!("abc", "abc".parse::<PreferredUsername>().unwrap().as_str());
|
||||||
|
assert!("abc-".parse::<PreferredUsername>().is_err());
|
||||||
|
assert_eq!(
|
||||||
|
PreferredUsername::new("admin".into()).unwrap(),
|
||||||
|
PreferredUsername("admin".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefferred_username_to_string() {
|
||||||
|
let pu = PreferredUsername::new("admin".into()).unwrap();
|
||||||
|
assert_eq!("admin".to_string(), pu.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn se_ap_signature() {
|
fn se_ap_signature() {
|
||||||
let ap_signature = ApSignature {
|
let ap_signature = ApSignature {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use activitystreams::iri_string::percent_encode::PercentEncodedForIri;
|
||||||
use openssl::rand::rand_bytes;
|
use openssl::rand::rand_bytes;
|
||||||
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag};
|
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag};
|
||||||
use regex_syntax::is_word_character;
|
use regex_syntax::is_word_character;
|
||||||
use rocket::http::uri::Uri;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use syntect::html::{ClassStyle, ClassedHTMLGenerator};
|
use syntect::html::{ClassStyle, ClassedHTMLGenerator};
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
@@ -21,51 +21,7 @@ pub fn random_hex() -> String {
|
|||||||
* Intended to be used for generating Post ap_url.
|
* Intended to be used for generating Post ap_url.
|
||||||
*/
|
*/
|
||||||
pub fn iri_percent_encode_seg(segment: &str) -> String {
|
pub fn iri_percent_encode_seg(segment: &str) -> String {
|
||||||
segment.chars().map(iri_percent_encode_seg_char).collect()
|
PercentEncodedForIri::from_path_segment(segment).to_string()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iri_percent_encode_seg_char(c: char) -> String {
|
|
||||||
if c.is_alphanumeric() {
|
|
||||||
c.to_string()
|
|
||||||
} else {
|
|
||||||
match c {
|
|
||||||
'-'
|
|
||||||
| '.'
|
|
||||||
| '_'
|
|
||||||
| '~'
|
|
||||||
| '\u{A0}'..='\u{D7FF}'
|
|
||||||
| '\u{20000}'..='\u{2FFFD}'
|
|
||||||
| '\u{30000}'..='\u{3FFFD}'
|
|
||||||
| '\u{40000}'..='\u{4FFFD}'
|
|
||||||
| '\u{50000}'..='\u{5FFFD}'
|
|
||||||
| '\u{60000}'..='\u{6FFFD}'
|
|
||||||
| '\u{70000}'..='\u{7FFFD}'
|
|
||||||
| '\u{80000}'..='\u{8FFFD}'
|
|
||||||
| '\u{90000}'..='\u{9FFFD}'
|
|
||||||
| '\u{A0000}'..='\u{AFFFD}'
|
|
||||||
| '\u{B0000}'..='\u{BFFFD}'
|
|
||||||
| '\u{C0000}'..='\u{CFFFD}'
|
|
||||||
| '\u{D0000}'..='\u{DFFFD}'
|
|
||||||
| '\u{E0000}'..='\u{EFFFD}'
|
|
||||||
| '!'
|
|
||||||
| '$'
|
|
||||||
| '&'
|
|
||||||
| '\''
|
|
||||||
| '('
|
|
||||||
| ')'
|
|
||||||
| '*'
|
|
||||||
| '+'
|
|
||||||
| ','
|
|
||||||
| ';'
|
|
||||||
| '='
|
|
||||||
| ':'
|
|
||||||
| '@' => c.to_string(),
|
|
||||||
_ => {
|
|
||||||
let s = c.to_string();
|
|
||||||
Uri::percent_encode(&s).to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ rocket_i18n = "0.4.1"
|
|||||||
reqwest = "0.11.11"
|
reqwest = "0.11.11"
|
||||||
scheduled-thread-pool = "0.2.6"
|
scheduled-thread-pool = "0.2.6"
|
||||||
serde = "1.0.137"
|
serde = "1.0.137"
|
||||||
rust-s3 = { version = "0.29.0", no-default-features = true, features = ["blocking"] }
|
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
tantivy = "0.13.3"
|
tantivy = "0.13.3"
|
||||||
@@ -28,7 +27,7 @@ webfinger = "0.4.1"
|
|||||||
whatlang = "0.16.2"
|
whatlang = "0.16.2"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
diesel-derive-newtype = "1.0.0"
|
diesel-derive-newtype = "1.0.0"
|
||||||
glob = "0.3.1"
|
glob = "0.3.0"
|
||||||
lindera-tantivy = { version = "0.7.1", optional = true }
|
lindera-tantivy = { version = "0.7.1", optional = true }
|
||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
riker = "0.4.2"
|
riker = "0.4.2"
|
||||||
@@ -36,6 +35,7 @@ once_cell = "1.12.0"
|
|||||||
lettre = "0.9.6"
|
lettre = "0.9.6"
|
||||||
native-tls = "0.2.10"
|
native-tls = "0.2.10"
|
||||||
activitystreams = "=0.7.0-alpha.20"
|
activitystreams = "=0.7.0-alpha.20"
|
||||||
|
heck = "0.4.0"
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|||||||
+83
-60
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
instance::*, medias::Media, posts::Post, safe_string::SafeString, schema::blogs, users::User,
|
db_conn::DbConn, instance::*, medias::Media, posts::Post, safe_string::SafeString,
|
||||||
Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
|
schema::blogs, users::User, Connection, Error, Fqn, PlumeRocket, Result, CONFIG,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
actor::{ApActor, ApActorExt, AsApActor, Group},
|
actor::{ApActor, ApActorExt, AsApActor, Group},
|
||||||
@@ -42,14 +43,14 @@ pub struct Blog {
|
|||||||
pub ap_url: String,
|
pub ap_url: String,
|
||||||
pub private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
pub fqn: String,
|
pub fqn: Fqn,
|
||||||
pub summary_html: SafeString,
|
pub summary_html: SafeString,
|
||||||
pub icon_id: Option<i32>,
|
pub icon_id: Option<i32>,
|
||||||
pub banner_id: Option<i32>,
|
pub banner_id: Option<i32>,
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Insertable)]
|
#[derive(Insertable)]
|
||||||
#[table_name = "blogs"]
|
#[table_name = "blogs"]
|
||||||
pub struct NewBlog {
|
pub struct NewBlog {
|
||||||
pub actor_id: String,
|
pub actor_id: String,
|
||||||
@@ -61,6 +62,7 @@ pub struct NewBlog {
|
|||||||
pub ap_url: String,
|
pub ap_url: String,
|
||||||
pub private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
|
pub fqn: Fqn,
|
||||||
pub summary_html: SafeString,
|
pub summary_html: SafeString,
|
||||||
pub icon_id: Option<i32>,
|
pub icon_id: Option<i32>,
|
||||||
pub banner_id: Option<i32>,
|
pub banner_id: Option<i32>,
|
||||||
@@ -84,15 +86,13 @@ impl Blog {
|
|||||||
inserted.ap_url = instance.compute_box(BLOG_PREFIX, &inserted.actor_id, "");
|
inserted.ap_url = instance.compute_box(BLOG_PREFIX, &inserted.actor_id, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if inserted.fqn.is_empty() {
|
if inserted.fqn.to_string().is_empty() {
|
||||||
|
// This might not enough for some titles such as all-Japanese title,
|
||||||
|
// but better than doing nothing.
|
||||||
if instance.local {
|
if instance.local {
|
||||||
inserted.fqn = iri_percent_encode_seg(&inserted.actor_id);
|
inserted.fqn = Fqn::make_local(&inserted.title)?;
|
||||||
} else {
|
} else {
|
||||||
inserted.fqn = format!(
|
inserted.fqn = Fqn::make_remote(&inserted.title, instance.public_domain)?;
|
||||||
"{}@{}",
|
|
||||||
iri_percent_encode_seg(&inserted.actor_id),
|
|
||||||
instance.public_domain
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +102,8 @@ impl Blog {
|
|||||||
find_by!(blogs, find_by_ap_url, ap_url as &str);
|
find_by!(blogs, find_by_ap_url, ap_url as &str);
|
||||||
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
|
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
|
||||||
|
|
||||||
pub fn slug(title: &str) -> &str {
|
pub fn slug(title: &str) -> String {
|
||||||
title
|
iri_percent_encode_seg(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
|
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
|
||||||
@@ -142,10 +142,10 @@ impl Blog {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<Blog> {
|
pub fn find_by_fqn(conn: &DbConn, fqn: &str) -> Result<Blog> {
|
||||||
let from_db = blogs::table
|
let from_db = blogs::table
|
||||||
.filter(blogs::fqn.eq(fqn))
|
.filter(blogs::fqn.eq(fqn))
|
||||||
.first(conn)
|
.first(&**conn)
|
||||||
.optional()?;
|
.optional()?;
|
||||||
if let Some(from_db) = from_db {
|
if let Some(from_db) = from_db {
|
||||||
Ok(from_db)
|
Ok(from_db)
|
||||||
@@ -154,7 +154,7 @@ impl Blog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<Blog> {
|
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<Blog> {
|
||||||
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
|
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
|
||||||
.links
|
.links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -173,7 +173,7 @@ impl Blog {
|
|||||||
|
|
||||||
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
||||||
let mut blog = ApActor::new(self.inbox_url.parse()?, Group::new());
|
let mut blog = ApActor::new(self.inbox_url.parse()?, Group::new());
|
||||||
blog.set_preferred_username(iri_percent_encode_seg(&self.actor_id));
|
blog.set_preferred_username(self.fqn.to_string());
|
||||||
blog.set_name(self.title.clone());
|
blog.set_name(self.title.clone());
|
||||||
blog.set_outbox(self.outbox_url.parse()?);
|
blog.set_outbox(self.outbox_url.parse()?);
|
||||||
blog.set_summary(self.summary_html.to_string());
|
blog.set_summary(self.summary_html.to_string());
|
||||||
@@ -372,15 +372,15 @@ impl IntoId for Blog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<Connection> for Blog {
|
impl FromId<DbConn> for Blog {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = CustomGroup;
|
type Object = CustomGroup;
|
||||||
|
|
||||||
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, acct: CustomGroup) -> Result<Self> {
|
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> {
|
||||||
let (name, outbox_url, inbox_url) = {
|
let (name, outbox_url, inbox_url) = {
|
||||||
let actor = acct.ap_actor_ref();
|
let actor = acct.ap_actor_ref();
|
||||||
let name = actor
|
let name = actor
|
||||||
@@ -398,22 +398,18 @@ impl FromId<Connection> for Blog {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_blog = NewBlog {
|
let actor_id = iri_percent_encode_seg(
|
||||||
actor_id: name.to_string(),
|
&acct
|
||||||
outbox_url,
|
.name()
|
||||||
inbox_url,
|
.and_then(|name| name.to_as_string())
|
||||||
public_key: acct.ext_one.public_key.public_key_pem.to_string(),
|
.ok_or(Error::MissingApProperty)?,
|
||||||
private_key: None,
|
);
|
||||||
theme: None,
|
|
||||||
..NewBlog::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let object = ApObject::new(acct.inner);
|
let object = ApObject::new(acct.inner);
|
||||||
new_blog.title = object
|
let title = object
|
||||||
.name()
|
.name()
|
||||||
.and_then(|name| name.to_as_string())
|
.and_then(|name| name.to_as_string())
|
||||||
.unwrap_or(name);
|
.unwrap_or(name.clone());
|
||||||
new_blog.summary_html = SafeString::new(
|
let summary_html = SafeString::new(
|
||||||
&object
|
&object
|
||||||
.summary()
|
.summary()
|
||||||
.and_then(|summary| summary.to_as_string())
|
.and_then(|summary| summary.to_as_string())
|
||||||
@@ -435,7 +431,6 @@ impl FromId<Connection> for Blog {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|m| m.id);
|
.map(|m| m.id);
|
||||||
new_blog.icon_id = icon_id;
|
|
||||||
|
|
||||||
let banner_id = object
|
let banner_id = object
|
||||||
.image()
|
.image()
|
||||||
@@ -452,13 +447,12 @@ impl FromId<Connection> for Blog {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|m| m.id);
|
.map(|m| m.id);
|
||||||
new_blog.banner_id = banner_id;
|
|
||||||
|
|
||||||
new_blog.summary = acct.ext_two.source.content;
|
let summary = acct.ext_two.source.content;
|
||||||
|
|
||||||
let any_base = AnyBase::from_extended(object)?;
|
let any_base = AnyBase::from_extended(object)?;
|
||||||
let id = any_base.id().ok_or(Error::MissingApProperty)?;
|
let id = any_base.id().ok_or(Error::MissingApProperty)?;
|
||||||
new_blog.ap_url = id.to_string();
|
let ap_url = id.to_string();
|
||||||
|
|
||||||
let inst = id
|
let inst = id
|
||||||
.authority_components()
|
.authority_components()
|
||||||
@@ -482,7 +476,29 @@ impl FromId<Connection> for Blog {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
new_blog.instance_id = instance.id;
|
let instance_id = instance.id;
|
||||||
|
let fqn = if instance.local {
|
||||||
|
Fqn::new_local(name)?
|
||||||
|
} else {
|
||||||
|
Fqn::new_remote(name, instance.public_domain)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_blog = NewBlog {
|
||||||
|
actor_id,
|
||||||
|
outbox_url,
|
||||||
|
inbox_url,
|
||||||
|
fqn,
|
||||||
|
public_key: acct.ext_one.public_key.public_key_pem.to_string(),
|
||||||
|
private_key: None,
|
||||||
|
theme: None,
|
||||||
|
title,
|
||||||
|
summary,
|
||||||
|
ap_url,
|
||||||
|
summary_html,
|
||||||
|
icon_id,
|
||||||
|
banner_id,
|
||||||
|
instance_id,
|
||||||
|
};
|
||||||
|
|
||||||
Blog::insert(conn, new_blog)
|
Blog::insert(conn, new_blog)
|
||||||
}
|
}
|
||||||
@@ -538,12 +554,19 @@ impl NewBlog {
|
|||||||
let (pub_key, priv_key) = sign::gen_keypair();
|
let (pub_key, priv_key) = sign::gen_keypair();
|
||||||
Ok(NewBlog {
|
Ok(NewBlog {
|
||||||
actor_id,
|
actor_id,
|
||||||
|
fqn: Fqn::make_local(&title)?,
|
||||||
title,
|
title,
|
||||||
summary,
|
summary,
|
||||||
instance_id,
|
instance_id,
|
||||||
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
|
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
|
||||||
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
|
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
|
||||||
..NewBlog::default()
|
outbox_url: Default::default(),
|
||||||
|
inbox_url: Default::default(),
|
||||||
|
ap_url: Default::default(),
|
||||||
|
summary_html: Default::default(),
|
||||||
|
icon_id: Default::default(),
|
||||||
|
banner_id: Default::default(),
|
||||||
|
theme: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,8 +588,8 @@ pub(crate) mod tests {
|
|||||||
let mut blog1 = Blog::insert(
|
let mut blog1 = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"BlogName".to_owned(),
|
"Blog%20Name".to_owned(),
|
||||||
"Blog name".to_owned(),
|
"Blog Name".to_owned(),
|
||||||
"This is a small blog".to_owned(),
|
"This is a small blog".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
)
|
)
|
||||||
@@ -576,7 +599,7 @@ pub(crate) mod tests {
|
|||||||
let blog2 = Blog::insert(
|
let blog2 = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"MyBlog".to_owned(),
|
"My%20Blog".to_owned(),
|
||||||
"My blog".to_owned(),
|
"My blog".to_owned(),
|
||||||
"Welcome to my blog".to_owned(),
|
"Welcome to my blog".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
@@ -587,7 +610,7 @@ pub(crate) mod tests {
|
|||||||
let blog3 = Blog::insert(
|
let blog3 = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"WhyILikePlume".to_owned(),
|
"Why%20I%20Like%20Plume".to_owned(),
|
||||||
"Why I like Plume".to_owned(),
|
"Why I like Plume".to_owned(),
|
||||||
"In this blog I will explay you why I like Plume so much".to_owned(),
|
"In this blog I will explay you why I like Plume so much".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
@@ -682,7 +705,7 @@ pub(crate) mod tests {
|
|||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"Some%20Name".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
"This is some blog".to_owned(),
|
"This is some blog".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
@@ -709,7 +732,7 @@ pub(crate) mod tests {
|
|||||||
let b1 = Blog::insert(
|
let b1 = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"Some%20Name".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
"This is some blog".to_owned(),
|
"This is some blog".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
@@ -810,7 +833,7 @@ pub(crate) mod tests {
|
|||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"Some%20Name".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
"This is some blog".to_owned(),
|
"This is some blog".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
@@ -830,10 +853,10 @@ pub(crate) mod tests {
|
|||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
fill_database(conn);
|
fill_database(conn);
|
||||||
|
|
||||||
let blog = Blog::insert(
|
let _ = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"Some%20Name".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
"This is some blog".to_owned(),
|
"This is some blog".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
@@ -842,7 +865,6 @@ pub(crate) mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(blog.fqn, "SomeName");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -868,7 +890,7 @@ pub(crate) mod tests {
|
|||||||
let b1 = Blog::insert(
|
let b1 = Blog::insert(
|
||||||
conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"Some%20Name".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
"This is some blog".to_owned(),
|
"This is some blog".to_owned(),
|
||||||
Instance::get_local().unwrap().id,
|
Instance::get_local().unwrap().id,
|
||||||
@@ -968,6 +990,7 @@ pub(crate) mod tests {
|
|||||||
let _: Blog = blogs[0].save_changes(&**conn).unwrap();
|
let _: Blog = blogs[0].save_changes(&**conn).unwrap();
|
||||||
let ap_repr = blogs[0].to_activity(conn).unwrap();
|
let ap_repr = blogs[0].to_activity(conn).unwrap();
|
||||||
blogs[0].delete(conn).unwrap();
|
blogs[0].delete(conn).unwrap();
|
||||||
|
eprintln!("{:#?}", &ap_repr);
|
||||||
let blog = Blog::from_activity(conn, ap_repr).unwrap();
|
let blog = Blog::from_activity(conn, ap_repr).unwrap();
|
||||||
|
|
||||||
assert_eq!(blog.actor_id, blogs[0].actor_id);
|
assert_eq!(blog.actor_id, blogs[0].actor_id);
|
||||||
@@ -1001,19 +1024,19 @@ pub(crate) mod tests {
|
|||||||
"type": "Image",
|
"type": "Image",
|
||||||
"url": "https://plu.me/aaa.png"
|
"url": "https://plu.me/aaa.png"
|
||||||
},
|
},
|
||||||
"id": "https://plu.me/~/BlogName/",
|
"id": "https://plu.me/~/Blog%20Name/",
|
||||||
"image": {
|
"image": {
|
||||||
"attributedTo": "https://plu.me/@/admin/",
|
"attributedTo": "https://plu.me/@/admin/",
|
||||||
"type": "Image",
|
"type": "Image",
|
||||||
"url": "https://plu.me/bbb.png"
|
"url": "https://plu.me/bbb.png"
|
||||||
},
|
},
|
||||||
"inbox": "https://plu.me/~/BlogName/inbox",
|
"inbox": "https://plu.me/~/Blog%20Name/inbox",
|
||||||
"name": "Blog name",
|
"name": "Blog Name",
|
||||||
"outbox": "https://plu.me/~/BlogName/outbox",
|
"outbox": "https://plu.me/~/Blog%20Name/outbox",
|
||||||
"preferredUsername": "BlogName",
|
"preferredUsername": "BlogName",
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"id": "https://plu.me/~/BlogName/#main-key",
|
"id": "https://plu.me/~/Blog%20Name/#main-key",
|
||||||
"owner": "https://plu.me/~/BlogName/",
|
"owner": "https://plu.me/~/Blog%20Name/",
|
||||||
"publicKeyPem": blog.public_key
|
"publicKeyPem": blog.public_key
|
||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
@@ -1041,8 +1064,8 @@ pub(crate) mod tests {
|
|||||||
let expected = json!({
|
let expected = json!({
|
||||||
"items": [],
|
"items": [],
|
||||||
"totalItems": 0,
|
"totalItems": 0,
|
||||||
"first": "https://plu.me/~/BlogName/outbox?page=1",
|
"first": "https://plu.me/~/Blog%20Name/outbox?page=1",
|
||||||
"last": "https://plu.me/~/BlogName/outbox?page=0",
|
"last": "https://plu.me/~/Blog%20Name/outbox?page=0",
|
||||||
"type": "OrderedCollection"
|
"type": "OrderedCollection"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1061,8 +1084,8 @@ pub(crate) mod tests {
|
|||||||
let act = blog.outbox_collection_page(conn, (33, 36))?;
|
let act = blog.outbox_collection_page(conn, (33, 36))?;
|
||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
"next": "https://plu.me/~/BlogName/outbox?page=3",
|
"next": "https://plu.me/~/Blog%20Name/outbox?page=3",
|
||||||
"prev": "https://plu.me/~/BlogName/outbox?page=1",
|
"prev": "https://plu.me/~/Blog%20Name/outbox?page=1",
|
||||||
"items": [],
|
"items": [],
|
||||||
"type": "OrderedCollectionPage"
|
"type": "OrderedCollectionPage"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comment_seers::{CommentSeers, NewCommentSeers},
|
comment_seers::{CommentSeers, NewCommentSeers},
|
||||||
|
db_conn::DbConn,
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
medias::Media,
|
medias::Media,
|
||||||
mentions::Mention,
|
mentions::Mention,
|
||||||
@@ -110,7 +111,7 @@ impl Comment {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_activity(&self, conn: &Connection) -> Result<Note> {
|
pub fn to_activity(&self, conn: &DbConn) -> Result<Note> {
|
||||||
let author = User::get(conn, self.author_id)?;
|
let author = User::get(conn, self.author_id)?;
|
||||||
let (html, mentions, _hashtags) = utils::md_to_html(
|
let (html, mentions, _hashtags) = utils::md_to_html(
|
||||||
self.content.get().as_ref(),
|
self.content.get().as_ref(),
|
||||||
@@ -148,7 +149,7 @@ impl Comment {
|
|||||||
Ok(note)
|
Ok(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
|
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> {
|
||||||
let author = User::get(conn, self.author_id)?;
|
let author = User::get(conn, self.author_id)?;
|
||||||
|
|
||||||
let note = self.to_activity(conn)?;
|
let note = self.to_activity(conn)?;
|
||||||
@@ -216,15 +217,15 @@ impl Comment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<Connection> for Comment {
|
impl FromId<DbConn> for Comment {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = Note;
|
type Object = Note;
|
||||||
|
|
||||||
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, note: Note) -> Result<Self> {
|
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
|
||||||
let comm = {
|
let comm = {
|
||||||
let previous_url = note
|
let previous_url = note
|
||||||
.in_reply_to()
|
.in_reply_to()
|
||||||
@@ -353,21 +354,21 @@ impl FromId<Connection> for Comment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &Connection> for Comment {
|
impl AsObject<User, Create, &DbConn> for Comment {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn activity(self, _conn: &Connection, _actor: User, _id: &str) -> Result<Self> {
|
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self> {
|
||||||
// The actual creation takes place in the FromId impl
|
// The actual creation takes place in the FromId impl
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Delete, &Connection> for Comment {
|
impl AsObject<User, Delete, &DbConn> for Comment {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
if self.author_id != actor.id {
|
if self.author_id != actor.id {
|
||||||
return Err(Error::Unauthorized);
|
return Err(Error::Unauthorized);
|
||||||
}
|
}
|
||||||
@@ -386,8 +387,8 @@ impl AsObject<User, Delete, &Connection> for Comment {
|
|||||||
diesel::update(comments::table)
|
diesel::update(comments::table)
|
||||||
.filter(comments::in_response_to_id.eq(self.id))
|
.filter(comments::in_response_to_id.eq(self.id))
|
||||||
.set(comments::in_response_to_id.eq(self.in_response_to_id))
|
.set(comments::in_response_to_id.eq(self.in_response_to_id))
|
||||||
.execute(conn)?;
|
.execute(&**conn)?;
|
||||||
diesel::delete(&self).execute(conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,7 +423,6 @@ impl CommentTree {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::blogs::Blog;
|
use crate::blogs::Blog;
|
||||||
use crate::db_conn::DbConn;
|
|
||||||
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
||||||
use crate::safe_string::SafeString;
|
use crate::safe_string::SafeString;
|
||||||
use crate::tests::{db, format_datetime};
|
use crate::tests::{db, format_datetime};
|
||||||
@@ -463,13 +463,13 @@ mod tests {
|
|||||||
assert_json_eq!(to_value(&act).unwrap(), json!({
|
assert_json_eq!(to_value(&act).unwrap(), json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": ["https://plu.me/@/admin/followers"],
|
"cc": ["https://plu.me/@/admin/followers"],
|
||||||
"id": format!("https://plu.me/~/BlogName/testing/comment/{}/activity", original_comm.id),
|
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}/activity", original_comm.id),
|
||||||
"object": {
|
"object": {
|
||||||
"attributedTo": "https://plu.me/@/admin/",
|
"attributedTo": "https://plu.me/@/admin/",
|
||||||
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
|
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
|
||||||
"###,
|
"###,
|
||||||
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", original_comm.id),
|
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", original_comm.id),
|
||||||
"inReplyTo": "https://plu.me/~/BlogName/testing",
|
"inReplyTo": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"published": format_datetime(&original_comm.creation_date),
|
"published": format_datetime(&original_comm.creation_date),
|
||||||
"summary": "My CW",
|
"summary": "My CW",
|
||||||
"tag": [
|
"tag": [
|
||||||
@@ -505,12 +505,12 @@ mod tests {
|
|||||||
assert_json_eq!(to_value(&reply_act).unwrap(), json!({
|
assert_json_eq!(to_value(&reply_act).unwrap(), json!({
|
||||||
"actor": "https://plu.me/@/user/",
|
"actor": "https://plu.me/@/user/",
|
||||||
"cc": ["https://plu.me/@/user/followers"],
|
"cc": ["https://plu.me/@/user/followers"],
|
||||||
"id": format!("https://plu.me/~/BlogName/testing/comment/{}/activity", reply.id),
|
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}/activity", reply.id),
|
||||||
"object": {
|
"object": {
|
||||||
"attributedTo": "https://plu.me/@/user/",
|
"attributedTo": "https://plu.me/@/user/",
|
||||||
"content": "",
|
"content": "",
|
||||||
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", reply.id),
|
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", reply.id),
|
||||||
"inReplyTo": format!("https://plu.me/~/BlogName/testing/comment/{}", original_comm.id),
|
"inReplyTo": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", original_comm.id),
|
||||||
"published": format_datetime(&reply.creation_date),
|
"published": format_datetime(&reply.creation_date),
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"tag": [],
|
"tag": [],
|
||||||
@@ -554,8 +554,8 @@ mod tests {
|
|||||||
"attributedTo": "https://plu.me/@/admin/",
|
"attributedTo": "https://plu.me/@/admin/",
|
||||||
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
|
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
|
||||||
"###,
|
"###,
|
||||||
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", comment.id),
|
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", comment.id),
|
||||||
"inReplyTo": "https://plu.me/~/BlogName/testing",
|
"inReplyTo": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"published": format_datetime(&comment.creation_date),
|
"published": format_datetime(&comment.creation_date),
|
||||||
"summary": "My CW",
|
"summary": "My CW",
|
||||||
"tag": [
|
"tag": [
|
||||||
@@ -584,9 +584,9 @@ mod tests {
|
|||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"id": format!("https://plu.me/~/BlogName/testing/comment/{}#delete", comment.id),
|
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}#delete", comment.id),
|
||||||
"object": {
|
"object": {
|
||||||
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", comment.id),
|
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", comment.id),
|
||||||
"type": "Tombstone"
|
"type": "Tombstone"
|
||||||
},
|
},
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
|||||||
+6
-104
@@ -6,10 +6,6 @@ use rocket::Config as RocketConfig;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env::{self, var};
|
use std::env::{self, var};
|
||||||
|
|
||||||
use s3::{Bucket, Region};
|
|
||||||
use s3::creds::Credentials;
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
const DB_NAME: &str = "plume";
|
const DB_NAME: &str = "plume";
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -31,23 +27,13 @@ pub struct Config {
|
|||||||
pub mail: Option<MailConfig>,
|
pub mail: Option<MailConfig>,
|
||||||
pub ldap: Option<LdapConfig>,
|
pub ldap: Option<LdapConfig>,
|
||||||
pub proxy: Option<ProxyConfig>,
|
pub proxy: Option<ProxyConfig>,
|
||||||
pub s3: Option<S3Config>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn proxy(&self) -> Option<&reqwest::Proxy> {
|
pub fn proxy(&self) -> Option<&reqwest::Proxy> {
|
||||||
self.proxy.as_ref().map(|p| &p.proxy)
|
self.proxy.as_ref().map(|p| &p.proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_bool(val: &str, name: &str) -> bool {
|
|
||||||
match val {
|
|
||||||
"1" | "true" | "TRUE" => true,
|
|
||||||
"0" | "false" | "FALSE" => false,
|
|
||||||
_ => panic!("Invalid configuration: {} is not boolean", name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum InvalidRocketConfig {
|
pub enum InvalidRocketConfig {
|
||||||
Env,
|
Env,
|
||||||
@@ -55,7 +41,7 @@ pub enum InvalidRocketConfig {
|
|||||||
SecretKey,
|
SecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rocket_config() -> Result<RocketConfig, InvalidRocketConfig> {
|
pub fn get_rocket_config() -> Result<RocketConfig, InvalidRocketConfig> {
|
||||||
let mut c = RocketConfig::active().map_err(|_| InvalidRocketConfig::Env)?;
|
let mut c = RocketConfig::active().map_err(|_| InvalidRocketConfig::Env)?;
|
||||||
|
|
||||||
let address = var("ROCKET_ADDRESS").unwrap_or_else(|_| "localhost".to_owned());
|
let address = var("ROCKET_ADDRESS").unwrap_or_else(|_| "localhost".to_owned());
|
||||||
@@ -302,7 +288,11 @@ fn get_ldap_config() -> Option<LdapConfig> {
|
|||||||
match (addr, base_dn) {
|
match (addr, base_dn) {
|
||||||
(Some(addr), Some(base_dn)) => {
|
(Some(addr), Some(base_dn)) => {
|
||||||
let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned());
|
let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned());
|
||||||
let tls = string_to_bool(&tls, "LDAP_TLS");
|
let tls = match tls.as_ref() {
|
||||||
|
"1" | "true" | "TRUE" => true,
|
||||||
|
"0" | "false" | "FALSE" => false,
|
||||||
|
_ => panic!("Invalid LDAP configuration : tls"),
|
||||||
|
};
|
||||||
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
|
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
|
||||||
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
|
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
|
||||||
Some(LdapConfig {
|
Some(LdapConfig {
|
||||||
@@ -359,93 +349,6 @@ fn get_proxy_config() -> Option<ProxyConfig> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct S3Config {
|
|
||||||
pub bucket: String,
|
|
||||||
pub access_key_id: String,
|
|
||||||
pub access_key_secret: String,
|
|
||||||
|
|
||||||
// region? If not set, default to us-east-1
|
|
||||||
pub region: String,
|
|
||||||
// hostname for s3. If not set, default to $region.amazonaws.com
|
|
||||||
pub hostname: String,
|
|
||||||
// may be useful when using self hosted s3. Won't work with recent AWS buckets
|
|
||||||
pub path_style: bool,
|
|
||||||
pub protocol: String,
|
|
||||||
|
|
||||||
// options below this comment are not used yet
|
|
||||||
// upload directly from user to S3, without going through Plume. Uses PostObject endpoint
|
|
||||||
pub direct_upload: bool,
|
|
||||||
// download directly from s3 to user, wihout going through Plume. Require public read on bucket
|
|
||||||
pub direct_download: bool,
|
|
||||||
// use this hostname for downloads, can be used with caching proxy in front of s3
|
|
||||||
pub alias: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl S3Config {
|
|
||||||
pub fn get_bucket(&self) -> Bucket {
|
|
||||||
let region = Region::Custom {
|
|
||||||
region: self.region.clone(),
|
|
||||||
endpoint: format!("{}://{}", self.protocol, self.hostname),
|
|
||||||
};
|
|
||||||
let credentials = Credentials {
|
|
||||||
access_key: Some(self.access_key_id.clone()),
|
|
||||||
secret_key: Some(self.access_key_secret.clone()),
|
|
||||||
security_token: None,
|
|
||||||
session_token: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.path_style {
|
|
||||||
Bucket::new_with_path_style(&self.bucket, region, credentials)
|
|
||||||
} else {
|
|
||||||
Bucket::new(&self.bucket, region, credentials)
|
|
||||||
}.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_s3_config() -> Option<S3Config> {
|
|
||||||
let bucket = var("S3_BUCKET").ok();
|
|
||||||
let access_key_id = var("AWS_ACCESS_KEY_ID").ok();
|
|
||||||
let access_key_secret = var("AWS_SECRET_ACCESS_KEY").ok();
|
|
||||||
if bucket.is_none() && access_key_id.is_none() && access_key_secret.is_none() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if bucket.is_none() || access_key_id.is_none() || access_key_secret.is_none() {
|
|
||||||
panic!("Invalid S3 configuration: some required values are set, but not others");
|
|
||||||
}
|
|
||||||
let bucket = bucket.unwrap();
|
|
||||||
let access_key_id = access_key_id.unwrap();
|
|
||||||
let access_key_secret = access_key_secret.unwrap();
|
|
||||||
|
|
||||||
let region = var("S3_REGION").unwrap_or_else(|_| "us-east-1".to_owned());
|
|
||||||
let hostname = var("S3_HOSTNAME").unwrap_or_else(|_| format!("{}.amazonaws.com", region));
|
|
||||||
|
|
||||||
let protocol = var("S3_PROTOCOL").unwrap_or_else(|_| "https".to_owned());
|
|
||||||
if protocol != "http" && protocol != "https" {
|
|
||||||
panic!("Invalid S3 configuration: invalid protocol {}", protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
let path_style = var("S3_PATH_STYLE").unwrap_or_else(|_| "false".to_owned());
|
|
||||||
let path_style = string_to_bool(&path_style, "S3_PATH_STYLE");
|
|
||||||
let direct_upload = var("S3_DIRECT_UPLOAD").unwrap_or_else(|_| "false".to_owned());
|
|
||||||
let direct_upload = string_to_bool(&direct_upload, "S3_DIRECT_UPLOAD");
|
|
||||||
let direct_download = var("S3_DIRECT_DOWNLOAD").unwrap_or_else(|_| "false".to_owned());
|
|
||||||
let direct_download = string_to_bool(&direct_download, "S3_DIRECT_DOWNLOAD");
|
|
||||||
|
|
||||||
let alias = var("S3_ALIAS_HOST").ok();
|
|
||||||
Some(S3Config {
|
|
||||||
bucket,
|
|
||||||
access_key_id,
|
|
||||||
access_key_secret,
|
|
||||||
region,
|
|
||||||
hostname,
|
|
||||||
protocol,
|
|
||||||
path_style,
|
|
||||||
direct_upload,
|
|
||||||
direct_download,
|
|
||||||
alias,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CONFIG: Config = Config {
|
pub static ref CONFIG: Config = Config {
|
||||||
base_url: var("BASE_URL").unwrap_or_else(|_| format!(
|
base_url: var("BASE_URL").unwrap_or_else(|_| format!(
|
||||||
@@ -477,6 +380,5 @@ lazy_static! {
|
|||||||
mail: get_mail_config(),
|
mail: get_mail_config(),
|
||||||
ldap: get_ldap_config(),
|
ldap: get_ldap_config(),
|
||||||
proxy: get_proxy_config(),
|
proxy: get_proxy_config(),
|
||||||
s3: get_s3_config(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-14
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, instance::Instance, notifications::*, schema::follows, users::User, Connection, Error,
|
ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User,
|
||||||
Result, CONFIG,
|
Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo},
|
activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo},
|
||||||
@@ -150,11 +150,11 @@ impl Follow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, FollowAct, &Connection> for User {
|
impl AsObject<User, FollowAct, &DbConn> for User {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Follow;
|
type Output = Follow;
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Follow> {
|
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Follow> {
|
||||||
// Mastodon (at least) requires the full Follow object when accepting it,
|
// Mastodon (at least) requires the full Follow object when accepting it,
|
||||||
// so we rebuilt it here
|
// so we rebuilt it here
|
||||||
let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?);
|
let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?);
|
||||||
@@ -162,15 +162,15 @@ impl AsObject<User, FollowAct, &Connection> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<Connection> for Follow {
|
impl FromId<DbConn> for Follow {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = FollowAct;
|
type Object = FollowAct;
|
||||||
|
|
||||||
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Follow::find_by_ap_url(conn, id)
|
Follow::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, follow: FollowAct) -> Result<Self> {
|
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
|
||||||
let actor = User::from_id(
|
let actor = User::from_id(
|
||||||
conn,
|
conn,
|
||||||
follow
|
follow
|
||||||
@@ -202,18 +202,18 @@ impl FromId<Connection> for Follow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &Connection> for Follow {
|
impl AsObject<User, Undo, &DbConn> for Follow {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
let conn = conn;
|
let conn = conn;
|
||||||
if self.follower_id == actor.id {
|
if self.follower_id == actor.id {
|
||||||
diesel::delete(&self).execute(conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
||||||
diesel::delete(¬if).execute(conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -232,9 +232,7 @@ impl IntoId for Follow {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{tests::db, users::tests as user_tests, users::tests::fill_database};
|
||||||
db_conn::DbConn, tests::db, users::tests as user_tests, users::tests::fill_database,
|
|
||||||
};
|
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use diesel::Connection;
|
use diesel::Connection;
|
||||||
use serde_json::{json, to_value};
|
use serde_json::{json, to_value};
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ impl_into_inbox_result! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
||||||
Inbox::handle(&**conn, act)
|
Inbox::handle(conn, act)
|
||||||
.with::<User, Announce, Post>(CONFIG.proxy())
|
.with::<User, Announce, Post>(CONFIG.proxy())
|
||||||
.with::<User, Create, Comment>(CONFIG.proxy())
|
.with::<User, Create, Comment>(CONFIG.proxy())
|
||||||
.with::<User, Create, Post>(CONFIG.proxy())
|
.with::<User, Create, Post>(CONFIG.proxy())
|
||||||
@@ -268,7 +268,7 @@ pub(crate) mod tests {
|
|||||||
"actor": users[0].ap_url,
|
"actor": users[0].ap_url,
|
||||||
"object": {
|
"object": {
|
||||||
"type": "Article",
|
"type": "Article",
|
||||||
"id": "https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"attributedTo": [users[0].ap_url, blogs[0].ap_url],
|
"attributedTo": [users[0].ap_url, blogs[0].ap_url],
|
||||||
"content": "Hello.",
|
"content": "Hello.",
|
||||||
"name": "My Article",
|
"name": "My Article",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use plume_common::utils::{iri_percent_encode_seg, md_to_html};
|
use plume_common::utils::md_to_html;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
#[derive(Clone, Identifiable, Queryable)]
|
#[derive(Clone, Identifiable, Queryable)]
|
||||||
@@ -173,8 +173,8 @@ impl Instance {
|
|||||||
"{instance}/{prefix}/{name}/{box_name}",
|
"{instance}/{prefix}/{name}/{box_name}",
|
||||||
instance = self.public_domain,
|
instance = self.public_domain,
|
||||||
prefix = prefix,
|
prefix = prefix,
|
||||||
name = iri_percent_encode_seg(name),
|
name = name,
|
||||||
box_name = iri_percent_encode_seg(box_name)
|
box_name = box_name
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+114
-7
@@ -17,12 +17,18 @@ extern crate serde_json;
|
|||||||
extern crate tantivy;
|
extern crate tantivy;
|
||||||
|
|
||||||
use activitystreams::iri_string;
|
use activitystreams::iri_string;
|
||||||
|
use diesel::backend::Backend;
|
||||||
|
use diesel::sql_types::Text;
|
||||||
|
use diesel::types::{FromSql, ToSql};
|
||||||
|
use heck::ToUpperCamelCase;
|
||||||
pub use lettre;
|
pub use lettre;
|
||||||
pub use lettre::smtp;
|
pub use lettre::smtp;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use plume_common::activity_pub::{inbox::InboxError, request, sign};
|
use plume_common::activity_pub::PreferredUsernameError;
|
||||||
|
use plume_common::activity_pub::{inbox::InboxError, request, sign, PreferredUsername};
|
||||||
use posts::PostEvent;
|
use posts::PostEvent;
|
||||||
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
||||||
|
use std::{fmt, io::Write, string::ToString};
|
||||||
use users::UserEvent;
|
use users::UserEvent;
|
||||||
|
|
||||||
#[cfg(not(any(feature = "sqlite", feature = "postgres")))]
|
#[cfg(not(any(feature = "sqlite", feature = "postgres")))]
|
||||||
@@ -69,7 +75,6 @@ pub enum Error {
|
|||||||
Webfinger,
|
Webfinger,
|
||||||
Expired,
|
Expired,
|
||||||
UserAlreadyExists,
|
UserAlreadyExists,
|
||||||
Anyhow(anyhow::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<bcrypt::BcryptError> for Error {
|
impl From<bcrypt::BcryptError> for Error {
|
||||||
@@ -171,9 +176,10 @@ impl From<request::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<anyhow::Error> for Error {
|
impl From<PreferredUsernameError> for Error {
|
||||||
fn from(err: anyhow::Error) -> Error {
|
fn from(err: PreferredUsernameError) -> Error {
|
||||||
Error::Anyhow(err)
|
tracing::trace!("{:?}", err);
|
||||||
|
Error::InvalidValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +314,7 @@ macro_rules! last {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::CONFIG;
|
pub use config::{get_rocket_config, Config, SearchTokenizerConfig, CONFIG};
|
||||||
|
|
||||||
pub fn ap_url(url: &str) -> String {
|
pub fn ap_url(url: &str) -> String {
|
||||||
format!("https://{}", url)
|
format!("https://{}", url)
|
||||||
@@ -341,10 +347,97 @@ impl SmtpNewWithAddr for smtp::SmtpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(AsExpression, PartialEq, Eq, Clone, FromSqlRow, Debug)]
|
||||||
|
#[sql_type = "Text"]
|
||||||
|
pub enum Fqn {
|
||||||
|
Local(PreferredUsername),
|
||||||
|
Remote(PreferredUsername, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fqn {
|
||||||
|
pub fn new_local(username: String) -> Result<Self> {
|
||||||
|
Ok(Self::Local(
|
||||||
|
PreferredUsername::new(username)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_remote(username: String, domain: String) -> Result<Self> {
|
||||||
|
Ok(Self::Remote(
|
||||||
|
PreferredUsername::new(username)?,
|
||||||
|
domain,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_local_string(base: &str) -> String {
|
||||||
|
base.to_upper_camel_case()
|
||||||
|
.chars()
|
||||||
|
.filter(|c| c.is_ascii_alphanumeric())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_local(base: &str) -> Result<Self> {
|
||||||
|
Self::new_local(Self::make_local_string(base))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_remote(base: &str, domain: String) -> Result<Self> {
|
||||||
|
Self::new_remote(Self::make_local_string(base), domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Fqn> for String {
|
||||||
|
fn from(fqn: &Fqn) -> Self {
|
||||||
|
match fqn {
|
||||||
|
Fqn::Local(username) => username.to_string(),
|
||||||
|
Fqn::Remote(username, domain) => format!("{}@{}", username, domain),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Fqn {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
String::from(self).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB> ToSql<Text, DB> for Fqn
|
||||||
|
where
|
||||||
|
DB: diesel::backend::Backend,
|
||||||
|
{
|
||||||
|
fn to_sql<W: Write>(
|
||||||
|
&self,
|
||||||
|
out: &mut diesel::serialize::Output<W, DB>,
|
||||||
|
) -> diesel::serialize::Result {
|
||||||
|
let fqn = match self {
|
||||||
|
Self::Local(username) => username.to_string(),
|
||||||
|
Self::Remote(username, domain) => format!("{}@{}", username, domain),
|
||||||
|
};
|
||||||
|
ToSql::<Text, DB>::to_sql::<W>(&fqn, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB> FromSql<Text, DB> for Fqn
|
||||||
|
where
|
||||||
|
DB: diesel::backend::Backend,
|
||||||
|
String: FromSql<Text, DB>,
|
||||||
|
{
|
||||||
|
/// We use PreferredUsername::new_unchecked() because, even if bytes is invalid as `preferredUsername`,
|
||||||
|
/// we need return some value.
|
||||||
|
fn from_sql(bytes: Option<&<DB as Backend>::RawValue>) -> diesel::deserialize::Result<Self> {
|
||||||
|
let value = <String as FromSql<Text, DB>>::from_sql(bytes)?;
|
||||||
|
Ok(match value.rsplit_once('@') {
|
||||||
|
None => Self::Local(unsafe { PreferredUsername::new_unchecked(value) }),
|
||||||
|
Some((username, domain)) => Self::Remote(
|
||||||
|
unsafe { PreferredUsername::new_unchecked(username.into()) },
|
||||||
|
domain.into(),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{db_conn, migrations::IMPORTED_MIGRATIONS, Connection as Conn, CONFIG};
|
use crate::{db_conn, migrations::IMPORTED_MIGRATIONS, Connection as Conn, CONFIG, Fqn};
|
||||||
use chrono::{naive::NaiveDateTime, Datelike, Timelike};
|
use chrono::{naive::NaiveDateTime, Datelike, Timelike};
|
||||||
use diesel::r2d2::ConnectionManager;
|
use diesel::r2d2::ConnectionManager;
|
||||||
use plume_common::utils::random_hex;
|
use plume_common::utils::random_hex;
|
||||||
@@ -405,6 +498,20 @@ mod tests {
|
|||||||
dt.second()
|
dt.second()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fqn() {
|
||||||
|
assert_eq!(
|
||||||
|
Fqn::new_local("admin".to_string()).unwrap(),
|
||||||
|
Fqn::new_local("admin".to_string()).unwrap()
|
||||||
|
);
|
||||||
|
assert!(Fqn::new_local("admin".to_string()).is_ok());
|
||||||
|
let fqn = Fqn::new_local("admin".to_string()).unwrap();
|
||||||
|
assert_eq!("admin".to_string(), String::from(&fqn));
|
||||||
|
let fqn = Fqn::new_local("admin".to_string()).unwrap();
|
||||||
|
assert_eq!("admin".to_string(), ToString::to_string(&fqn));
|
||||||
|
assert_eq!("admin".to_string(), fqn.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod admin;
|
pub mod admin;
|
||||||
|
|||||||
+16
-16
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, users::User,
|
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*,
|
||||||
Connection, Error, Result, CONFIG,
|
users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{ActorAndObjectRef, Like as LikeAct, Undo},
|
activity::{ActorAndObjectRef, Like as LikeAct, Undo},
|
||||||
@@ -85,11 +85,11 @@ impl Like {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, LikeAct, &Connection> for Post {
|
impl AsObject<User, LikeAct, &DbConn> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Like;
|
type Output = Like;
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Like> {
|
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Like> {
|
||||||
let res = Like::insert(
|
let res = Like::insert(
|
||||||
conn,
|
conn,
|
||||||
NewLike {
|
NewLike {
|
||||||
@@ -105,15 +105,15 @@ impl AsObject<User, LikeAct, &Connection> for Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<Connection> for Like {
|
impl FromId<DbConn> for Like {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = LikeAct;
|
type Object = LikeAct;
|
||||||
|
|
||||||
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Like::find_by_ap_url(conn, id)
|
Like::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, act: LikeAct) -> Result<Self> {
|
fn from_activity(conn: &DbConn, act: LikeAct) -> Result<Self> {
|
||||||
let res = Like::insert(
|
let res = Like::insert(
|
||||||
conn,
|
conn,
|
||||||
NewLike {
|
NewLike {
|
||||||
@@ -154,17 +154,17 @@ impl FromId<Connection> for Like {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &Connection> for Like {
|
impl AsObject<User, Undo, &DbConn> for Like {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
if actor.id == self.user_id {
|
if actor.id == self.user_id {
|
||||||
diesel::delete(&self).execute(conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
||||||
diesel::delete(¬if).execute(conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -205,8 +205,8 @@ mod tests {
|
|||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": ["https://plu.me/@/admin/followers"],
|
"cc": ["https://plu.me/@/admin/followers"],
|
||||||
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/@/admin/like/https://plu.me/~/Blog%20Name/testing",
|
||||||
"object": "https://plu.me/~/BlogName/testing",
|
"object": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Like",
|
"type": "Like",
|
||||||
});
|
});
|
||||||
@@ -229,12 +229,12 @@ mod tests {
|
|||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": ["https://plu.me/@/admin/followers"],
|
"cc": ["https://plu.me/@/admin/followers"],
|
||||||
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing#delete",
|
"id": "https://plu.me/@/admin/like/https://plu.me/~/Blog%20Name/testing#delete",
|
||||||
"object": {
|
"object": {
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": ["https://plu.me/@/admin/followers"],
|
"cc": ["https://plu.me/@/admin/followers"],
|
||||||
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/@/admin/like/https://plu.me/~/Blog%20Name/testing",
|
||||||
"object": "https://plu.me/~/BlogName/testing",
|
"object": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Like",
|
"type": "Like",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -297,28 +297,6 @@ impl List {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
|
||||||
if let Some(user_id) = self.user_id {
|
|
||||||
diesel::delete(
|
|
||||||
lists::table
|
|
||||||
.filter(lists::user_id.eq(user_id))
|
|
||||||
.filter(lists::name.eq(&self.name)),
|
|
||||||
)
|
|
||||||
.execute(conn)
|
|
||||||
.map(|_| ())
|
|
||||||
.map_err(Error::from)
|
|
||||||
} else {
|
|
||||||
diesel::delete(
|
|
||||||
lists::table
|
|
||||||
.filter(lists::user_id.is_null())
|
|
||||||
.filter(lists::name.eq(&self.name)),
|
|
||||||
)
|
|
||||||
.execute(conn)
|
|
||||||
.map(|_| ())
|
|
||||||
.map_err(Error::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func! {set: set_users, User, add_users}
|
func! {set: set_users, User, add_users}
|
||||||
func! {set: set_blogs, Blog, add_blogs}
|
func! {set: set_blogs, Blog, add_blogs}
|
||||||
func! {set: set_words, Word, add_words}
|
func! {set: set_words, Word, add_words}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, instance::Instance, safe_string::SafeString, schema::medias, users::User, Connection,
|
ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias,
|
||||||
Error, Result, CONFIG,
|
users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{object::Image, prelude::*};
|
use activitystreams::{object::Image, prelude::*};
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
@@ -170,12 +170,7 @@ impl Media {
|
|||||||
|
|
||||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||||
if !self.is_remote {
|
if !self.is_remote {
|
||||||
if let Some(config) = &CONFIG.s3 {
|
fs::remove_file(self.file_path.as_str())?;
|
||||||
config.get_bucket()
|
|
||||||
.delete_object_blocking(&self.file_path)?;
|
|
||||||
} else {
|
|
||||||
fs::remove_file(self.file_path.as_str())?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
diesel::delete(self)
|
diesel::delete(self)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
@@ -211,7 +206,7 @@ impl Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge with save_remote?
|
// TODO: merge with save_remote?
|
||||||
pub fn from_activity(conn: &Connection, image: &Image) -> Result<Media> {
|
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> {
|
||||||
let remote_url = image
|
let remote_url = image
|
||||||
.url()
|
.url()
|
||||||
.and_then(|url| url.to_as_uri())
|
.and_then(|url| url.to_as_uri())
|
||||||
@@ -263,7 +258,7 @@ impl Media {
|
|||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
if updated {
|
if updated {
|
||||||
diesel::update(&media).set(&media).execute(conn)?;
|
diesel::update(&media).set(&media).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
Ok(media)
|
Ok(media)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comments::Comment, notifications::*, posts::Post, schema::mentions, users::User, Connection,
|
comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions,
|
||||||
Error, Result,
|
users::User, Connection, Error, Result,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
base::BaseExt,
|
base::BaseExt,
|
||||||
@@ -60,7 +60,7 @@ impl Mention {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_activity(conn: &Connection, ment: &str) -> Result<link::Mention> {
|
pub fn build_activity(conn: &DbConn, ment: &str) -> Result<link::Mention> {
|
||||||
let user = User::find_by_fqn(conn, ment)?;
|
let user = User::find_by_fqn(conn, ment)?;
|
||||||
let mut mention = link::Mention::new();
|
let mut mention = link::Mention::new();
|
||||||
mention.set_href(user.ap_url.parse::<IriString>()?);
|
mention.set_href(user.ap_url.parse::<IriString>()?);
|
||||||
|
|||||||
+34
-35
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, blogs::Blog, instance::Instance, medias::Media, mentions::Mention, post_authors::*,
|
ap_url, blogs::Blog, db_conn::DbConn, instance::Instance, medias::Media, mentions::Mention,
|
||||||
safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, Connection, Error,
|
post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User,
|
||||||
PostEvent::*, Result, CONFIG, POST_CHAN,
|
Connection, Error, Fqn, PostEvent::*, Result, CONFIG, POST_CHAN,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Create, Delete, Update},
|
activity::{Create, Delete, Update},
|
||||||
@@ -28,7 +28,7 @@ use riker::actors::{Publish, Tell};
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, Fqn>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, Clone, AsChangeset, Debug)]
|
#[derive(Queryable, Identifiable, Clone, AsChangeset, Debug)]
|
||||||
#[changeset_options(treat_none_as_null = "true")]
|
#[changeset_options(treat_none_as_null = "true")]
|
||||||
@@ -255,7 +255,7 @@ impl Post {
|
|||||||
ap_url(&format!(
|
ap_url(&format!(
|
||||||
"{}/~/{}/{}/",
|
"{}/~/{}/{}/",
|
||||||
CONFIG.base_url,
|
CONFIG.base_url,
|
||||||
iri_percent_encode_seg(&blog.fqn),
|
&blog.fqn,
|
||||||
iri_percent_encode_seg(slug)
|
iri_percent_encode_seg(slug)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -298,9 +298,9 @@ impl Post {
|
|||||||
/// This caches query result. The best way to cache query result is holding it in `Post`s field
|
/// This caches query result. The best way to cache query result is holding it in `Post`s field
|
||||||
/// but Diesel doesn't allow it currently.
|
/// but Diesel doesn't allow it currently.
|
||||||
/// If sometime Diesel allow it, this method should be removed.
|
/// If sometime Diesel allow it, this method should be removed.
|
||||||
pub fn get_blog_fqn(&self, conn: &Connection) -> String {
|
pub fn get_blog_fqn(&self, conn: &Connection) -> Fqn {
|
||||||
if let Some(blog_fqn) = BLOG_FQN_CACHE.lock().unwrap().get(&self.blog_id) {
|
if let Some(blog_fqn) = BLOG_FQN_CACHE.lock().unwrap().get(&self.blog_id) {
|
||||||
return blog_fqn.to_string();
|
return blog_fqn.to_owned();
|
||||||
}
|
}
|
||||||
let blog_fqn = self.get_blog(conn).unwrap().fqn;
|
let blog_fqn = self.get_blog(conn).unwrap().fqn;
|
||||||
BLOG_FQN_CACHE
|
BLOG_FQN_CACHE
|
||||||
@@ -615,15 +615,15 @@ impl Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<Connection> for Post {
|
impl FromId<DbConn> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = LicensedArticle;
|
type Object = LicensedArticle;
|
||||||
|
|
||||||
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, article: LicensedArticle) -> Result<Self> {
|
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> {
|
||||||
let license = article.ext_one.license.unwrap_or_default();
|
let license = article.ext_one.license.unwrap_or_default();
|
||||||
let article = article.inner;
|
let article = article.inner;
|
||||||
|
|
||||||
@@ -821,21 +821,21 @@ impl FromId<Connection> for Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &Connection> for Post {
|
impl AsObject<User, Create, &DbConn> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn activity(self, _conn: &Connection, _actor: User, _id: &str) -> Result<Self::Output> {
|
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self::Output> {
|
||||||
// TODO: check that _actor is actually one of the author?
|
// TODO: check that _actor is actually one of the author?
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Delete, &Connection> for Post {
|
impl AsObject<User, Delete, &DbConn> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<Self::Output> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<Self::Output> {
|
||||||
let can_delete = self
|
let can_delete = self
|
||||||
.get_authors(conn)?
|
.get_authors(conn)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -859,16 +859,16 @@ pub struct PostUpdate {
|
|||||||
pub tags: Option<serde_json::Value>,
|
pub tags: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<Connection> for PostUpdate {
|
impl FromId<DbConn> for PostUpdate {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = LicensedArticle;
|
type Object = LicensedArticle;
|
||||||
|
|
||||||
fn from_db(_: &Connection, _: &str) -> Result<Self> {
|
fn from_db(_: &DbConn, _: &str) -> Result<Self> {
|
||||||
// Always fail because we always want to deserialize the AP object
|
// Always fail because we always want to deserialize the AP object
|
||||||
Err(Error::NotFound)
|
Err(Error::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, updated: Self::Object) -> Result<Self> {
|
fn from_activity(conn: &DbConn, updated: Self::Object) -> Result<Self> {
|
||||||
let mut post_update = PostUpdate {
|
let mut post_update = PostUpdate {
|
||||||
ap_url: updated
|
ap_url: updated
|
||||||
.ap_object_ref()
|
.ap_object_ref()
|
||||||
@@ -923,11 +923,11 @@ impl FromId<Connection> for PostUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Update, &Connection> for PostUpdate {
|
impl AsObject<User, Update, &DbConn> for PostUpdate {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
let mut post =
|
let mut post =
|
||||||
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
|
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
|
||||||
|
|
||||||
@@ -1027,7 +1027,6 @@ impl From<PostEvent> for Arc<Post> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db_conn::DbConn;
|
|
||||||
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
||||||
use crate::mentions::{Mention, NewMention};
|
use crate::mentions::{Mention, NewMention};
|
||||||
use crate::safe_string::SafeString;
|
use crate::safe_string::SafeString;
|
||||||
@@ -1110,10 +1109,10 @@ mod tests {
|
|||||||
let act = post.to_activity(&conn)?;
|
let act = post.to_activity(&conn)?;
|
||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
|
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/Blog%20Name/"],
|
||||||
"cc": [],
|
"cc": [],
|
||||||
"content": "Hello",
|
"content": "Hello",
|
||||||
"id": "https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"name": "Testing",
|
"name": "Testing",
|
||||||
"published": format_datetime(&post.creation_date),
|
"published": format_datetime(&post.creation_date),
|
||||||
@@ -1131,7 +1130,7 @@ mod tests {
|
|||||||
],
|
],
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Article",
|
"type": "Article",
|
||||||
"url": "https://plu.me/~/BlogName/testing"
|
"url": "https://plu.me/~/Blog%20Name/testing"
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_json_eq!(to_value(act)?, expected);
|
assert_json_eq!(to_value(act)?, expected);
|
||||||
@@ -1150,12 +1149,12 @@ mod tests {
|
|||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": [],
|
"cc": [],
|
||||||
"id": "https://plu.me/~/BlogName/testing/activity",
|
"id": "https://plu.me/~/Blog%20Name/testing/activity",
|
||||||
"object": {
|
"object": {
|
||||||
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
|
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/Blog%20Name/"],
|
||||||
"cc": [],
|
"cc": [],
|
||||||
"content": "Hello",
|
"content": "Hello",
|
||||||
"id": "https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"name": "Testing",
|
"name": "Testing",
|
||||||
"published": format_datetime(&post.creation_date),
|
"published": format_datetime(&post.creation_date),
|
||||||
@@ -1173,7 +1172,7 @@ mod tests {
|
|||||||
],
|
],
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Article",
|
"type": "Article",
|
||||||
"url": "https://plu.me/~/BlogName/testing"
|
"url": "https://plu.me/~/Blog%20Name/testing"
|
||||||
},
|
},
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Create"
|
"type": "Create"
|
||||||
@@ -1195,12 +1194,12 @@ mod tests {
|
|||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": [],
|
"cc": [],
|
||||||
"id": "https://plu.me/~/BlogName/testing/update-",
|
"id": "https://plu.me/~/Blog%20Name/testing/update-",
|
||||||
"object": {
|
"object": {
|
||||||
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
|
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/Blog%20Name/"],
|
||||||
"cc": [],
|
"cc": [],
|
||||||
"content": "Hello",
|
"content": "Hello",
|
||||||
"id": "https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"name": "Testing",
|
"name": "Testing",
|
||||||
"published": format_datetime(&post.creation_date),
|
"published": format_datetime(&post.creation_date),
|
||||||
@@ -1218,7 +1217,7 @@ mod tests {
|
|||||||
],
|
],
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Article",
|
"type": "Article",
|
||||||
"url": "https://plu.me/~/BlogName/testing"
|
"url": "https://plu.me/~/Blog%20Name/testing"
|
||||||
},
|
},
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Update"
|
"type": "Update"
|
||||||
@@ -1227,10 +1226,10 @@ mod tests {
|
|||||||
|
|
||||||
let id = actual["id"].to_string();
|
let id = actual["id"].to_string();
|
||||||
let (id_pre, id_post) = id.rsplit_once('-').unwrap();
|
let (id_pre, id_post) = id.rsplit_once('-').unwrap();
|
||||||
assert_eq!(post.ap_url, "https://plu.me/~/BlogName/testing");
|
assert_eq!(post.ap_url, "https://plu.me/~/Blog%20Name/testing");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
id_pre,
|
id_pre,
|
||||||
to_value("\"https://plu.me/~/BlogName/testing/update")
|
to_value("\"https://plu.me/~/Blog%20Name/testing/update")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1260,9 +1259,9 @@ mod tests {
|
|||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"id": "https://plu.me/~/BlogName/testing#delete",
|
"id": "https://plu.me/~/Blog%20Name/testing#delete",
|
||||||
"object": {
|
"object": {
|
||||||
"id": "https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"type": "Tombstone"
|
"type": "Tombstone"
|
||||||
},
|
},
|
||||||
"to": [
|
"to": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
instance::Instance, notifications::*, posts::Post, schema::reshares, timeline::*, users::User,
|
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares,
|
||||||
Connection, Error, Result, CONFIG,
|
timeline::*, users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{ActorAndObjectRef, Announce, Undo},
|
activity::{ActorAndObjectRef, Announce, Undo},
|
||||||
@@ -113,11 +113,11 @@ impl Reshare {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Announce, &Connection> for Post {
|
impl AsObject<User, Announce, &DbConn> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Reshare;
|
type Output = Reshare;
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Reshare> {
|
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Reshare> {
|
||||||
let conn = conn;
|
let conn = conn;
|
||||||
let reshare = Reshare::insert(
|
let reshare = Reshare::insert(
|
||||||
conn,
|
conn,
|
||||||
@@ -134,15 +134,15 @@ impl AsObject<User, Announce, &Connection> for Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<Connection> for Reshare {
|
impl FromId<DbConn> for Reshare {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = Announce;
|
type Object = Announce;
|
||||||
|
|
||||||
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Reshare::find_by_ap_url(conn, id)
|
Reshare::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, act: Announce) -> Result<Self> {
|
fn from_activity(conn: &DbConn, act: Announce) -> Result<Self> {
|
||||||
let res = Reshare::insert(
|
let res = Reshare::insert(
|
||||||
conn,
|
conn,
|
||||||
NewReshare {
|
NewReshare {
|
||||||
@@ -183,17 +183,17 @@ impl FromId<Connection> for Reshare {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &Connection> for Reshare {
|
impl AsObject<User, Undo, &DbConn> for Reshare {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
if actor.id == self.user_id {
|
if actor.id == self.user_id {
|
||||||
diesel::delete(&self).execute(conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
||||||
diesel::delete(¬if).execute(conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -235,8 +235,8 @@ mod test {
|
|||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": ["https://plu.me/@/admin/followers"],
|
"cc": ["https://plu.me/@/admin/followers"],
|
||||||
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/Blog%20Name/testing",
|
||||||
"object": "https://plu.me/~/BlogName/testing",
|
"object": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Announce",
|
"type": "Announce",
|
||||||
});
|
});
|
||||||
@@ -259,12 +259,12 @@ mod test {
|
|||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": ["https://plu.me/@/admin/followers"],
|
"cc": ["https://plu.me/@/admin/followers"],
|
||||||
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing#delete",
|
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/Blog%20Name/testing#delete",
|
||||||
"object": {
|
"object": {
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
"cc": ["https://plu.me/@/admin/followers"],
|
"cc": ["https://plu.me/@/admin/followers"],
|
||||||
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing",
|
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/Blog%20Name/testing",
|
||||||
"object": "https://plu.me/~/BlogName/testing",
|
"object": "https://plu.me/~/Blog%20Name/testing",
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"type": "Announce"
|
"type": "Announce"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
comment_seers (id) {
|
||||||
|
id -> Int4,
|
||||||
|
comment_id -> Int4,
|
||||||
|
user_id -> Int4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
comments (id) {
|
comments (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
@@ -66,14 +74,6 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
|
||||||
comment_seers (id) {
|
|
||||||
id -> Int4,
|
|
||||||
comment_id -> Int4,
|
|
||||||
user_id -> Int4,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
email_blocklist (id) {
|
email_blocklist (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
@@ -314,8 +314,8 @@ allow_tables_to_appear_in_same_query!(
|
|||||||
apps,
|
apps,
|
||||||
blog_authors,
|
blog_authors,
|
||||||
blogs,
|
blogs,
|
||||||
comments,
|
|
||||||
comment_seers,
|
comment_seers,
|
||||||
|
comments,
|
||||||
email_blocklist,
|
email_blocklist,
|
||||||
email_signups,
|
email_signups,
|
||||||
follows,
|
follows,
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ impl ActorFactoryArgs<(Arc<Searcher>, DbPool)> for SearchActor {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::diesel::Connection;
|
use crate::diesel::Connection;
|
||||||
|
use crate::Fqn;
|
||||||
use crate::{
|
use crate::{
|
||||||
blog_authors::{BlogAuthor, NewBlogAuthor},
|
blog_authors::{BlogAuthor, NewBlogAuthor},
|
||||||
blogs::{Blog, NewBlog},
|
blogs::{Blog, NewBlog},
|
||||||
@@ -190,13 +191,22 @@ mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let title = random_hex();
|
||||||
let blog = NewBlog {
|
let blog = NewBlog {
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
actor_id: random_hex(),
|
actor_id: random_hex(),
|
||||||
ap_url: random_hex(),
|
ap_url: random_hex(),
|
||||||
inbox_url: random_hex(),
|
inbox_url: random_hex(),
|
||||||
outbox_url: random_hex(),
|
outbox_url: random_hex(),
|
||||||
..Default::default()
|
fqn: Fqn::make_local(&title).unwrap(),
|
||||||
|
title,
|
||||||
|
summary: Default::default(),
|
||||||
|
summary_html: Default::default(),
|
||||||
|
private_key: Default::default(),
|
||||||
|
public_key: Default::default(),
|
||||||
|
icon_id: Default::default(),
|
||||||
|
banner_id: Default::default(),
|
||||||
|
theme: Default::default(),
|
||||||
};
|
};
|
||||||
let blog = Blog::insert(conn, blog).unwrap();
|
let blog = Blog::insert(conn, blog).unwrap();
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
db_conn::DbConn,
|
||||||
lists::List,
|
lists::List,
|
||||||
posts::Post,
|
posts::Post,
|
||||||
schema::{posts, timeline, timeline_definition},
|
schema::{posts, timeline, timeline_definition},
|
||||||
@@ -11,7 +12,7 @@ use std::ops::Deref;
|
|||||||
pub(crate) mod query;
|
pub(crate) mod query;
|
||||||
|
|
||||||
pub use self::query::Kind;
|
pub use self::query::Kind;
|
||||||
pub use self::query::{QueryError, TimelineQuery};
|
use self::query::{QueryError, TimelineQuery};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset)]
|
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset)]
|
||||||
#[table_name = "timeline_definition"]
|
#[table_name = "timeline_definition"]
|
||||||
@@ -219,7 +220,7 @@ impl Timeline {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_to_all_timelines(conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<()> {
|
pub fn add_to_all_timelines(conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<()> {
|
||||||
let timelines = timeline_definition::table
|
let timelines = timeline_definition::table
|
||||||
.load::<Self>(conn.deref())
|
.load::<Self>(conn.deref())
|
||||||
.map_err(Error::from)?;
|
.map_err(Error::from)?;
|
||||||
@@ -245,26 +246,7 @@ impl Timeline {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_post(&self, conn: &Connection, post: &Post) -> Result<bool> {
|
pub fn matches(&self, conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<bool> {
|
||||||
if self.includes_post(conn, post)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
diesel::delete(
|
|
||||||
timeline::table
|
|
||||||
.filter(timeline::timeline_id.eq(self.id))
|
|
||||||
.filter(timeline::post_id.eq(post.id)),
|
|
||||||
)
|
|
||||||
.execute(conn)?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_all_posts(&self, conn: &Connection) -> Result<u64> {
|
|
||||||
let count = diesel::delete(timeline::table.filter(timeline::timeline_id.eq(self.id)))
|
|
||||||
.execute(conn)?;
|
|
||||||
Ok(count as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches(&self, conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<bool> {
|
|
||||||
let query = TimelineQuery::parse(&self.query)?;
|
let query = TimelineQuery::parse(&self.query)?;
|
||||||
query.matches(conn, self, post, kind)
|
query.matches(conn, self, post, kind)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
blogs::Blog,
|
blogs::Blog,
|
||||||
|
db_conn::DbConn,
|
||||||
lists::{self, ListType},
|
lists::{self, ListType},
|
||||||
posts::Post,
|
posts::Post,
|
||||||
tags::Tag,
|
tags::Tag,
|
||||||
timeline::Timeline,
|
timeline::Timeline,
|
||||||
users::User,
|
users::User,
|
||||||
Connection, Result,
|
Result,
|
||||||
};
|
};
|
||||||
use plume_common::activity_pub::inbox::AsActor;
|
use plume_common::activity_pub::inbox::AsActor;
|
||||||
use whatlang::{self, Lang};
|
use whatlang::{self, Lang};
|
||||||
@@ -154,7 +155,7 @@ enum TQ<'a> {
|
|||||||
impl<'a> TQ<'a> {
|
impl<'a> TQ<'a> {
|
||||||
fn matches(
|
fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &Connection,
|
conn: &DbConn,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
@@ -199,7 +200,7 @@ enum Arg<'a> {
|
|||||||
impl<'a> Arg<'a> {
|
impl<'a> Arg<'a> {
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &Connection,
|
conn: &DbConn,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
@@ -224,7 +225,7 @@ enum WithList {
|
|||||||
impl WithList {
|
impl WithList {
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &Connection,
|
conn: &DbConn,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
list: &List<'_>,
|
list: &List<'_>,
|
||||||
@@ -360,7 +361,7 @@ enum Bool {
|
|||||||
impl Bool {
|
impl Bool {
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &Connection,
|
conn: &DbConn,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
@@ -653,7 +654,7 @@ impl<'a> TimelineQuery<'a> {
|
|||||||
|
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &Connection,
|
conn: &DbConn,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
|
|||||||
+11
-37
@@ -15,10 +15,7 @@ use activitystreams::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use diesel::{
|
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
||||||
self, BelongingToDsl, BoolExpressionMethods, ExpressionMethods, OptionalExtension, QueryDsl,
|
|
||||||
RunQueryDsl, TextExpressionMethods,
|
|
||||||
};
|
|
||||||
use ldap3::{LdapConn, Scope, SearchEntry};
|
use ldap3::{LdapConn, Scope, SearchEntry};
|
||||||
use openssl::{
|
use openssl::{
|
||||||
hash::MessageDigest,
|
hash::MessageDigest,
|
||||||
@@ -189,16 +186,15 @@ impl User {
|
|||||||
pub fn count_local(conn: &Connection) -> Result<i64> {
|
pub fn count_local(conn: &Connection) -> Result<i64> {
|
||||||
users::table
|
users::table
|
||||||
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
||||||
.filter(users::role.ne(Role::Instance as i32))
|
|
||||||
.count()
|
.count()
|
||||||
.get_result(conn)
|
.get_result(conn)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<User> {
|
pub fn find_by_fqn(conn: &DbConn, fqn: &str) -> Result<User> {
|
||||||
let from_db = users::table
|
let from_db = users::table
|
||||||
.filter(users::fqn.eq(fqn))
|
.filter(users::fqn.eq(fqn))
|
||||||
.first(conn)
|
.first(&**conn)
|
||||||
.optional()?;
|
.optional()?;
|
||||||
if let Some(from_db) = from_db {
|
if let Some(from_db) = from_db {
|
||||||
Ok(from_db)
|
Ok(from_db)
|
||||||
@@ -207,27 +203,6 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_local_by_name(
|
|
||||||
conn: &Connection,
|
|
||||||
name: &str,
|
|
||||||
(min, max): (i32, i32),
|
|
||||||
) -> Result<Vec<User>> {
|
|
||||||
users::table
|
|
||||||
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
|
||||||
.filter(users::role.ne(Role::Instance as i32))
|
|
||||||
// TODO: use `ilike` instead of `like` for PostgreSQL
|
|
||||||
.filter(
|
|
||||||
users::username
|
|
||||||
.like(format!("%{}%", name))
|
|
||||||
.or(users::display_name.like(format!("%{}%", name))),
|
|
||||||
)
|
|
||||||
.order(users::username.asc())
|
|
||||||
.offset(min.into())
|
|
||||||
.limit((max - min).into())
|
|
||||||
.load::<User>(conn)
|
|
||||||
.map_err(Error::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Should create user record with normalized(lowercased) email
|
* TODO: Should create user record with normalized(lowercased) email
|
||||||
*/
|
*/
|
||||||
@@ -244,7 +219,7 @@ impl User {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<User> {
|
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> {
|
||||||
let link = resolve(acct.to_owned(), true)?
|
let link = resolve(acct.to_owned(), true)?
|
||||||
.links
|
.links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -456,7 +431,6 @@ impl User {
|
|||||||
pub fn get_local_page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<User>> {
|
pub fn get_local_page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<User>> {
|
||||||
users::table
|
users::table
|
||||||
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
||||||
.filter(users::role.ne(Role::Instance as i32))
|
|
||||||
.order(users::username.asc())
|
.order(users::username.asc())
|
||||||
.offset(min.into())
|
.offset(min.into())
|
||||||
.limit((max - min).into())
|
.limit((max - min).into())
|
||||||
@@ -787,7 +761,7 @@ impl User {
|
|||||||
actor.set_url(ap_url.clone());
|
actor.set_url(ap_url.clone());
|
||||||
actor.set_inbox(self.inbox_url.parse()?);
|
actor.set_inbox(self.inbox_url.parse()?);
|
||||||
actor.set_outbox(self.outbox_url.parse()?);
|
actor.set_outbox(self.outbox_url.parse()?);
|
||||||
actor.set_preferred_username(self.username.clone());
|
actor.set_preferred_username(&self.fqn);
|
||||||
actor.set_followers(self.followers_endpoint.parse()?);
|
actor.set_followers(self.followers_endpoint.parse()?);
|
||||||
|
|
||||||
if let Some(shared_inbox_url) = self.shared_inbox_url.clone() {
|
if let Some(shared_inbox_url) = self.shared_inbox_url.clone() {
|
||||||
@@ -947,15 +921,15 @@ impl IntoId for User {
|
|||||||
|
|
||||||
impl Eq for User {}
|
impl Eq for User {}
|
||||||
|
|
||||||
impl FromId<Connection> for User {
|
impl FromId<DbConn> for User {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = CustomPerson;
|
type Object = CustomPerson;
|
||||||
|
|
||||||
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &Connection, acct: CustomPerson) -> Result<Self> {
|
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> {
|
||||||
let actor = acct.ap_actor_ref();
|
let actor = acct.ap_actor_ref();
|
||||||
let username = actor
|
let username = actor
|
||||||
.preferred_username()
|
.preferred_username()
|
||||||
@@ -1056,7 +1030,7 @@ impl FromId<Connection> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&Connection> for User {
|
impl AsActor<&DbConn> for User {
|
||||||
fn get_inbox_url(&self) -> String {
|
fn get_inbox_url(&self) -> String {
|
||||||
self.inbox_url.clone()
|
self.inbox_url.clone()
|
||||||
}
|
}
|
||||||
@@ -1072,11 +1046,11 @@ impl AsActor<&Connection> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Delete, &Connection> for User {
|
impl AsObject<User, Delete, &DbConn> for User {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
if self.id == actor.id {
|
if self.id == actor.id {
|
||||||
self.delete(conn).map(|_| ())
|
self.delete(conn).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+32
-32
@@ -16,7 +16,7 @@ use plume_models::{
|
|||||||
migrations::IMPORTED_MIGRATIONS,
|
migrations::IMPORTED_MIGRATIONS,
|
||||||
remote_fetch_actor::RemoteFetchActor,
|
remote_fetch_actor::RemoteFetchActor,
|
||||||
search::{actor::SearchActor, Searcher as UnmanagedSearcher},
|
search::{actor::SearchActor, Searcher as UnmanagedSearcher},
|
||||||
Connection, CONFIG,
|
Config, Connection, CONFIG,
|
||||||
};
|
};
|
||||||
use rocket_csrf::CsrfFairingBuilder;
|
use rocket_csrf::CsrfFairingBuilder;
|
||||||
use scheduled_thread_pool::ScheduledThreadPool;
|
use scheduled_thread_pool::ScheduledThreadPool;
|
||||||
@@ -47,12 +47,12 @@ include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
|||||||
compile_i18n!();
|
compile_i18n!();
|
||||||
|
|
||||||
/// Initializes a database pool.
|
/// Initializes a database pool.
|
||||||
fn init_pool() -> Option<DbPool> {
|
fn init_pool(config: &Config) -> Option<DbPool> {
|
||||||
let manager = ConnectionManager::<Connection>::new(CONFIG.database_url.as_str());
|
let manager = ConnectionManager::<Connection>::new(CONFIG.database_url.as_str());
|
||||||
let mut builder = DbPool::builder()
|
let mut builder = DbPool::builder()
|
||||||
.connection_customizer(Box::new(PragmaForeignKey))
|
.connection_customizer(Box::new(PragmaForeignKey))
|
||||||
.min_idle(CONFIG.db_min_idle);
|
.min_idle(config.db_min_idle);
|
||||||
if let Some(max_size) = CONFIG.db_max_size {
|
if let Some(max_size) = config.db_max_size {
|
||||||
builder = builder.max_size(max_size);
|
builder = builder.max_size(max_size);
|
||||||
};
|
};
|
||||||
let pool = builder.build(manager).ok()?;
|
let pool = builder.build(manager).ok()?;
|
||||||
@@ -63,28 +63,8 @@ fn init_pool() -> Option<DbPool> {
|
|||||||
Some(pool)
|
Some(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn init_rocket() -> rocket::Rocket {
|
pub(crate) fn init_rocket(config: &Config) -> rocket::Rocket {
|
||||||
match dotenv::dotenv() {
|
let dbpool = init_pool(config).expect("main: database pool initialization error");
|
||||||
Ok(path) => eprintln!("Configuration read from {}", path.display()),
|
|
||||||
Err(ref e) if e.not_found() => eprintln!("no .env was found"),
|
|
||||||
e => e.map(|_| ()).unwrap(),
|
|
||||||
}
|
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
|
|
||||||
App::new("Plume")
|
|
||||||
.bin_name("plume")
|
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
|
||||||
.about("Plume backend server")
|
|
||||||
.after_help(
|
|
||||||
r#"
|
|
||||||
The plume command should be run inside the directory
|
|
||||||
containing the `.env` configuration file and `static` directory.
|
|
||||||
See https://docs.joinplu.me/installation/config
|
|
||||||
and https://docs.joinplu.me/installation/init for more info.
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
let dbpool = init_pool().expect("main: database pool initialization error");
|
|
||||||
if IMPORTED_MIGRATIONS
|
if IMPORTED_MIGRATIONS
|
||||||
.is_pending(&dbpool.get().unwrap())
|
.is_pending(&dbpool.get().unwrap())
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
@@ -104,8 +84,8 @@ Then try to restart Plume.
|
|||||||
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
||||||
// we want a fast exit here, so
|
// we want a fast exit here, so
|
||||||
let searcher = Arc::new(UnmanagedSearcher::open_or_recreate(
|
let searcher = Arc::new(UnmanagedSearcher::open_or_recreate(
|
||||||
&CONFIG.search_index,
|
&config.search_index,
|
||||||
&CONFIG.search_tokenizers,
|
&config.search_tokenizers,
|
||||||
));
|
));
|
||||||
RemoteFetchActor::init(dbpool.clone());
|
RemoteFetchActor::init(dbpool.clone());
|
||||||
SearchActor::init(searcher.clone(), dbpool.clone());
|
SearchActor::init(searcher.clone(), dbpool.clone());
|
||||||
@@ -125,12 +105,12 @@ Then try to restart Plume.
|
|||||||
.expect("Error setting Ctrl-c handler");
|
.expect("Error setting Ctrl-c handler");
|
||||||
|
|
||||||
let mail = mail::init();
|
let mail = mail::init();
|
||||||
if mail.is_none() && CONFIG.rocket.as_ref().unwrap().environment.is_prod() {
|
if mail.is_none() && config.rocket.as_ref().unwrap().environment.is_prod() {
|
||||||
warn!("Warning: the email server is not configured (or not completely).");
|
warn!("Warning: the email server is not configured (or not completely).");
|
||||||
warn!("Please refer to the documentation to see how to configure it.");
|
warn!("Please refer to the documentation to see how to configure it.");
|
||||||
}
|
}
|
||||||
|
|
||||||
rocket::custom(CONFIG.rocket.clone().unwrap())
|
rocket::custom(config.rocket.clone().unwrap())
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![
|
routes![
|
||||||
@@ -157,7 +137,6 @@ Then try to restart Plume.
|
|||||||
routes::instance::admin_mod,
|
routes::instance::admin_mod,
|
||||||
routes::instance::admin_instances,
|
routes::instance::admin_instances,
|
||||||
routes::instance::admin_users,
|
routes::instance::admin_users,
|
||||||
routes::instance::admin_search_users,
|
|
||||||
routes::instance::admin_email_blocklist,
|
routes::instance::admin_email_blocklist,
|
||||||
routes::instance::add_email_blocklist,
|
routes::instance::add_email_blocklist,
|
||||||
routes::instance::delete_email_blocklist,
|
routes::instance::delete_email_blocklist,
|
||||||
@@ -281,7 +260,28 @@ Then try to restart Plume.
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let rocket = init_rocket();
|
match dotenv::dotenv() {
|
||||||
|
Ok(path) => eprintln!("Configuration read from {}", path.display()),
|
||||||
|
Err(ref e) if e.not_found() => eprintln!("no .env was found"),
|
||||||
|
e => e.map(|_| ()).unwrap(),
|
||||||
|
}
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
App::new("Plume")
|
||||||
|
.bin_name("plume")
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.about("Plume backend server")
|
||||||
|
.after_help(
|
||||||
|
r#"
|
||||||
|
The plume command should be run inside the directory
|
||||||
|
containing the `.env` configuration file and `static` directory.
|
||||||
|
See https://docs.joinplu.me/installation/config
|
||||||
|
and https://docs.joinplu.me/installation/init for more info.
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let rocket = init_rocket(&CONFIG);
|
||||||
|
|
||||||
#[cfg(feature = "test")]
|
#[cfg(feature = "test")]
|
||||||
let rocket = rocket.mount("/test", routes![test_routes::health,]);
|
let rocket = rocket.mount("/test", routes![test_routes::health,]);
|
||||||
|
|||||||
+127
-33
@@ -1,4 +1,7 @@
|
|||||||
use activitystreams::collection::{OrderedCollection, OrderedCollectionPage};
|
use activitystreams::{
|
||||||
|
collection::{OrderedCollection, OrderedCollectionPage},
|
||||||
|
iri_string::{spec::IriSpec, validate::iri_reference},
|
||||||
|
};
|
||||||
use diesel::SaveChangesDsl;
|
use diesel::SaveChangesDsl;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::ContentType,
|
http::ContentType,
|
||||||
@@ -80,7 +83,7 @@ pub struct NewBlogForm {
|
|||||||
|
|
||||||
fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
||||||
let slug = Blog::slug(title);
|
let slug = Blog::slug(title);
|
||||||
if slug.is_empty() {
|
if slug.is_empty() || iri_reference::<IriSpec>(&slug).is_err() {
|
||||||
Err(ValidationError::new("empty_slug"))
|
Err(ValidationError::new("empty_slug"))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -101,7 +104,7 @@ pub fn create(
|
|||||||
Ok(_) => ValidationErrors::new(),
|
Ok(_) => ValidationErrors::new(),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
if Blog::find_by_fqn(&conn, slug).is_ok() {
|
if Blog::find_by_fqn(&conn, &slug).is_ok() {
|
||||||
errors.add(
|
errors.add(
|
||||||
"title",
|
"title",
|
||||||
ValidationError {
|
ValidationError {
|
||||||
@@ -122,7 +125,7 @@ pub fn create(
|
|||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
&conn,
|
&conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
slug.into(),
|
slug.clone(),
|
||||||
form.title.to_string(),
|
form.title.to_string(),
|
||||||
String::from(""),
|
String::from(""),
|
||||||
Instance::get_local()
|
Instance::get_local()
|
||||||
@@ -144,7 +147,7 @@ pub fn create(
|
|||||||
.expect("blog::create: author error");
|
.expect("blog::create: author error");
|
||||||
|
|
||||||
Flash::success(
|
Flash::success(
|
||||||
Redirect::to(uri!(details: name = slug, page = _)),
|
Redirect::to(uri!(details: name = &slug, page = _)),
|
||||||
&i18n!(intl, "Your blog was successfully created!"),
|
&i18n!(intl, "Your blog was successfully created!"),
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
@@ -379,6 +382,8 @@ pub fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::env::var;
|
||||||
|
|
||||||
use super::valid_slug;
|
use super::valid_slug;
|
||||||
use crate::init_rocket;
|
use crate::init_rocket;
|
||||||
use diesel::Connection;
|
use diesel::Connection;
|
||||||
@@ -387,62 +392,107 @@ mod tests {
|
|||||||
blog_authors::{BlogAuthor, NewBlogAuthor},
|
blog_authors::{BlogAuthor, NewBlogAuthor},
|
||||||
blogs::{Blog, NewBlog},
|
blogs::{Blog, NewBlog},
|
||||||
db_conn::{DbConn, DbPool},
|
db_conn::{DbConn, DbPool},
|
||||||
|
get_rocket_config,
|
||||||
instance::{Instance, NewInstance},
|
instance::{Instance, NewInstance},
|
||||||
post_authors::{NewPostAuthor, PostAuthor},
|
post_authors::{NewPostAuthor, PostAuthor},
|
||||||
posts::{NewPost, Post},
|
posts::{NewPost, Post},
|
||||||
safe_string::SafeString,
|
safe_string::SafeString,
|
||||||
|
search::Searcher,
|
||||||
users::{NewUser, User, AUTH_COOKIE},
|
users::{NewUser, User, AUTH_COOKIE},
|
||||||
Connection as Conn, CONFIG,
|
Config, Fqn, SearchTokenizerConfig,
|
||||||
};
|
};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::{Cookie, Cookies, SameSite},
|
http::{ContentType, Cookie, Cookies, SameSite, Status},
|
||||||
local::{Client, LocalRequest},
|
local::{Client, LocalRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
type Models = (Instance, User, Blog, Post);
|
||||||
fn edit_link_within_post_card() {
|
|
||||||
let conn = Conn::establish(CONFIG.database_url.as_str()).unwrap();
|
fn setup() -> (Client, Models) {
|
||||||
Instance::insert(
|
dotenv::from_path(".env.test").unwrap();
|
||||||
&conn,
|
let config = Config {
|
||||||
NewInstance {
|
base_url: var("BASE_URL").unwrap(),
|
||||||
public_domain: "example.org".to_string(),
|
db_name: "plume",
|
||||||
name: "Plume".to_string(),
|
db_max_size: None,
|
||||||
local: true,
|
db_min_idle: None,
|
||||||
long_description: SafeString::new(""),
|
signup: Default::default(),
|
||||||
short_description: SafeString::new(""),
|
database_url: var("DATABASE_URL").unwrap(),
|
||||||
default_license: "CC-BY-SA".to_string(),
|
search_index: format!("/tmp/plume_test-{}", random_hex()),
|
||||||
open_registrations: true,
|
search_tokenizers: SearchTokenizerConfig::init(),
|
||||||
short_description_html: String::new(),
|
rocket: get_rocket_config(),
|
||||||
long_description_html: String::new(),
|
logo: Default::default(),
|
||||||
},
|
default_theme: Default::default(),
|
||||||
)
|
media_directory: format!("/tmp/plume_test-{}", random_hex()),
|
||||||
.unwrap();
|
mail: None,
|
||||||
let rocket = init_rocket();
|
ldap: None,
|
||||||
|
proxy: None,
|
||||||
|
};
|
||||||
|
let _ = Searcher::create(&config.search_index, &config.search_tokenizers).unwrap();
|
||||||
|
let rocket = init_rocket(&config);
|
||||||
let client = Client::new(rocket).expect("valid rocket instance");
|
let client = Client::new(rocket).expect("valid rocket instance");
|
||||||
let dbpool = client.rocket().state::<DbPool>().unwrap();
|
let dbpool = client.rocket().state::<DbPool>().unwrap();
|
||||||
let conn = &DbConn(dbpool.get().unwrap());
|
let conn = &DbConn(dbpool.get().unwrap());
|
||||||
|
|
||||||
let (_instance, user, blog, post) = create_models(conn);
|
(client, create_models(conn))
|
||||||
|
}
|
||||||
|
|
||||||
let blog_path = uri!(super::activity_details: name = &blog.fqn).to_string();
|
fn teardown((client, (instance, user, _blog, _post)): (&Client, Models)) {
|
||||||
|
let rocket = client.rocket();
|
||||||
|
|
||||||
|
let dbpool = rocket.state::<DbPool>().unwrap();
|
||||||
|
let conn = &DbConn(dbpool.get().unwrap());
|
||||||
|
|
||||||
|
user.delete(conn).unwrap();
|
||||||
|
let _ = diesel::delete(&instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edit_link_within_post_card() {
|
||||||
|
let (client, (instance, user, blog, post)) = setup();
|
||||||
|
|
||||||
|
let blog_path = uri!(super::activity_details: name = &blog.fqn.to_string()).to_string();
|
||||||
let edit_link = uri!(
|
let edit_link = uri!(
|
||||||
super::super::posts::edit: blog = &blog.fqn,
|
super::super::posts::edit: blog = &blog.fqn.to_string(),
|
||||||
slug = &post.slug
|
slug = &post.slug
|
||||||
)
|
)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut response = client.get(&blog_path).dispatch();
|
let mut response = client.get(&blog_path).dispatch();
|
||||||
let body = response.body_string().unwrap();
|
let body = response.body_string().unwrap();
|
||||||
assert!(!body.contains(&edit_link));
|
let body_not_contain_edit_link = !body.contains(&edit_link);
|
||||||
|
|
||||||
let request = client.get(&blog_path);
|
let request = client.get(&blog_path);
|
||||||
login(&request, &user);
|
login(&request, &user);
|
||||||
let mut response = request.dispatch();
|
let mut response = request.dispatch();
|
||||||
let body = response.body_string().unwrap();
|
let body = response.body_string().unwrap();
|
||||||
assert!(body.contains(&edit_link));
|
eprintln!("{:?}", &blog.fqn);
|
||||||
|
let body_contains_edit_lnk = body.contains(&edit_link);
|
||||||
|
|
||||||
|
teardown((&client, (instance, user, blog, post)));
|
||||||
|
|
||||||
|
assert!(body_not_contain_edit_link);
|
||||||
|
assert!(body_contains_edit_lnk);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_models(conn: &DbConn) -> (Instance, User, Blog, Post) {
|
fn create_models(conn: &DbConn) -> Models {
|
||||||
|
Instance::find_by_domain(conn, "example.org").unwrap_or_else(|_| {
|
||||||
|
Instance::insert(
|
||||||
|
conn,
|
||||||
|
NewInstance {
|
||||||
|
public_domain: "example.org".to_string(),
|
||||||
|
name: "Plume".to_string(),
|
||||||
|
local: true,
|
||||||
|
long_description: SafeString::new(""),
|
||||||
|
short_description: SafeString::new(""),
|
||||||
|
default_license: "CC-BY-SA".to_string(),
|
||||||
|
open_registrations: true,
|
||||||
|
short_description_html: String::new(),
|
||||||
|
long_description_html: String::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
conn.transaction::<(Instance, User, Blog, Post), diesel::result::Error, _>(|| {
|
conn.transaction::<(Instance, User, Blog, Post), diesel::result::Error, _>(|| {
|
||||||
let instance = Instance::get_local().unwrap_or_else(|_| {
|
let instance = Instance::get_local().unwrap_or_else(|_| {
|
||||||
let instance = Instance::insert(
|
let instance = Instance::insert(
|
||||||
@@ -470,16 +520,26 @@ mod tests {
|
|||||||
inbox_url: random_hex(),
|
inbox_url: random_hex(),
|
||||||
outbox_url: random_hex(),
|
outbox_url: random_hex(),
|
||||||
followers_endpoint: random_hex(),
|
followers_endpoint: random_hex(),
|
||||||
|
fqn: random_hex(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = User::insert(conn, user).unwrap();
|
let user = User::insert(conn, user).unwrap();
|
||||||
|
let title = random_hex();
|
||||||
let blog = NewBlog {
|
let blog = NewBlog {
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
|
fqn: Fqn::make_local(&title).unwrap(),
|
||||||
|
title,
|
||||||
actor_id: random_hex(),
|
actor_id: random_hex(),
|
||||||
ap_url: random_hex(),
|
ap_url: random_hex(),
|
||||||
inbox_url: random_hex(),
|
inbox_url: random_hex(),
|
||||||
outbox_url: random_hex(),
|
outbox_url: random_hex(),
|
||||||
..Default::default()
|
summary: Default::default(),
|
||||||
|
summary_html: Default::default(),
|
||||||
|
public_key: Default::default(),
|
||||||
|
private_key: Default::default(),
|
||||||
|
icon_id: Default::default(),
|
||||||
|
banner_id: Default::default(),
|
||||||
|
theme: Default::default(),
|
||||||
};
|
};
|
||||||
let blog = Blog::insert(conn, blog).unwrap();
|
let blog = Blog::insert(conn, blog).unwrap();
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
@@ -535,4 +595,38 @@ mod tests {
|
|||||||
assert!(valid_slug("Blog Title").is_ok());
|
assert!(valid_slug("Blog Title").is_ok());
|
||||||
assert!(valid_slug("ブログ タイトル").is_ok());
|
assert!(valid_slug("ブログ タイトル").is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_blog_with_same_title_twice() {
|
||||||
|
let (client, (instance, user, blog, post)) = setup();
|
||||||
|
|
||||||
|
let new_path = uri!(super::new).to_string();
|
||||||
|
let request = client.get(new_path);
|
||||||
|
login(&request, &user);
|
||||||
|
let mut response = request.dispatch();
|
||||||
|
let body = response.body_string().unwrap();
|
||||||
|
let prefix = r#"<input type="hidden" name="csrf-token" value=""#;
|
||||||
|
let pos = body.find(prefix).unwrap();
|
||||||
|
let token = body[pos + prefix.len()..pos + prefix.len() + 123].to_string();
|
||||||
|
|
||||||
|
let create_path = uri!(super::create).to_string();
|
||||||
|
let response = client
|
||||||
|
.post(&create_path)
|
||||||
|
.body(format!("title=My%20Blog&csrf-token={}", &token))
|
||||||
|
.header(ContentType::Form)
|
||||||
|
.dispatch();
|
||||||
|
let first_attempt = response;
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(&create_path)
|
||||||
|
.body(format!("title=My%20Blog&csrf-token={}", &token))
|
||||||
|
.header(ContentType::Form)
|
||||||
|
.dispatch();
|
||||||
|
let second_attempt = response;
|
||||||
|
|
||||||
|
teardown((&client, (instance, user, blog, post)));
|
||||||
|
|
||||||
|
assert_eq!(first_attempt.status(), Status::SeeOther);
|
||||||
|
assert_eq!(second_attempt.status(), Status::SeeOther);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-27
@@ -160,7 +160,7 @@ pub fn toggle_block(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/admin/users?<page>", rank = 2)]
|
#[get("/admin/users?<page>")]
|
||||||
pub fn admin_users(
|
pub fn admin_users(
|
||||||
_mod: Moderator,
|
_mod: Moderator,
|
||||||
page: Option<Page>,
|
page: Option<Page>,
|
||||||
@@ -171,30 +171,6 @@ pub fn admin_users(
|
|||||||
Ok(render!(instance::users(
|
Ok(render!(instance::users(
|
||||||
&(&conn, &rockets).to_context(),
|
&(&conn, &rockets).to_context(),
|
||||||
User::get_local_page(&conn, page.limits())?,
|
User::get_local_page(&conn, page.limits())?,
|
||||||
None,
|
|
||||||
page.0,
|
|
||||||
Page::total(User::count_local(&conn)? as i32)
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
#[get("/admin/users?<user>&<page>", rank = 1)]
|
|
||||||
pub fn admin_search_users(
|
|
||||||
_mod: Moderator,
|
|
||||||
user: String,
|
|
||||||
page: Option<Page>,
|
|
||||||
conn: DbConn,
|
|
||||||
rockets: PlumeRocket,
|
|
||||||
) -> Result<Ructe, ErrorPage> {
|
|
||||||
let page = page.unwrap_or_default();
|
|
||||||
let users = if user.is_empty() {
|
|
||||||
User::get_local_page(&conn, page.limits())?
|
|
||||||
} else {
|
|
||||||
User::search_local_by_name(&conn, &user, page.limits())?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(render!(instance::users(
|
|
||||||
&(&conn, &rockets).to_context(),
|
|
||||||
users,
|
|
||||||
Some(user.as_str()),
|
|
||||||
page.0,
|
page.0,
|
||||||
Page::total(User::count_local(&conn)? as i32)
|
Page::total(User::count_local(&conn)? as i32)
|
||||||
)))
|
)))
|
||||||
@@ -431,7 +407,7 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
|
|||||||
|
|
||||||
if let Ok(post) = Post::from_id(&conn, &target, None, CONFIG.proxy()) {
|
if let Ok(post) = Post::from_id(&conn, &target, None, CONFIG.proxy()) {
|
||||||
return Some(Redirect::to(uri!(
|
return Some(Redirect::to(uri!(
|
||||||
super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn,
|
super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn.to_string(),
|
||||||
slug = &post.slug,
|
slug = &post.slug,
|
||||||
responding_to = _
|
responding_to = _
|
||||||
)));
|
)));
|
||||||
@@ -442,7 +418,7 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
|
|||||||
let post = comment.get_post(&conn).expect("Can't retrieve post");
|
let post = comment.get_post(&conn).expect("Can't retrieve post");
|
||||||
return Some(Redirect::to(uri!(
|
return Some(Redirect::to(uri!(
|
||||||
super::posts::details: blog =
|
super::posts::details: blog =
|
||||||
post.get_blog(&conn).expect("Can't retrieve blog").fqn,
|
post.get_blog(&conn).expect("Can't retrieve blog").fqn.to_string(),
|
||||||
slug = &post.slug,
|
slug = &post.slug,
|
||||||
responding_to = comment.id
|
responding_to = comment.id
|
||||||
)));
|
)));
|
||||||
|
|||||||
+7
-30
@@ -9,7 +9,7 @@ use rocket::{
|
|||||||
http::{
|
http::{
|
||||||
hyper::header::{CacheControl, CacheDirective, ETag, EntityTag},
|
hyper::header::{CacheControl, CacheDirective, ETag, EntityTag},
|
||||||
uri::{FromUriParam, Query},
|
uri::{FromUriParam, Query},
|
||||||
ContentType, RawStr, Status,
|
RawStr, Status,
|
||||||
},
|
},
|
||||||
request::{self, FromFormValue, FromRequest, Request},
|
request::{self, FromFormValue, FromRequest, Request},
|
||||||
response::{self, Flash, NamedFile, Redirect, Responder, Response},
|
response::{self, Flash, NamedFile, Redirect, Responder, Response},
|
||||||
@@ -204,16 +204,10 @@ pub mod timelines;
|
|||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod well_known;
|
pub mod well_known;
|
||||||
|
|
||||||
#[derive(Responder)]
|
|
||||||
enum FileKind {
|
|
||||||
Local(NamedFile),
|
|
||||||
S3(Vec<u8>, ContentType),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Responder)]
|
#[derive(Responder)]
|
||||||
#[response()]
|
#[response()]
|
||||||
pub struct CachedFile {
|
pub struct CachedFile {
|
||||||
inner: FileKind,
|
inner: NamedFile,
|
||||||
cache_control: CacheControl,
|
cache_control: CacheControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,36 +253,19 @@ pub fn plume_static_files(file: PathBuf, build_id: &RawStr) -> Option<CachedFile
|
|||||||
}
|
}
|
||||||
#[get("/static/media/<file..>")]
|
#[get("/static/media/<file..>")]
|
||||||
pub fn plume_media_files(file: PathBuf) -> Option<CachedFile> {
|
pub fn plume_media_files(file: PathBuf) -> Option<CachedFile> {
|
||||||
if let Some(config) = &CONFIG.s3 {
|
NamedFile::open(Path::new(&CONFIG.media_directory).join(file))
|
||||||
let ct = file.extension()
|
.ok()
|
||||||
.and_then(|ext| ContentType::from_extension(&ext.to_string_lossy()))
|
.map(|f| CachedFile {
|
||||||
.unwrap_or(ContentType::Binary);
|
inner: f,
|
||||||
|
|
||||||
let (data, code) = config.get_bucket()
|
|
||||||
.get_object_blocking(format!("plume-media/{}", file.to_string_lossy())).ok()?;
|
|
||||||
if code != 200 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(CachedFile {
|
|
||||||
inner: FileKind::S3 ( data, ct),
|
|
||||||
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
NamedFile::open(Path::new(&CONFIG.media_directory).join(file))
|
|
||||||
.ok()
|
|
||||||
.map(|f| CachedFile {
|
|
||||||
inner: FileKind::Local(f),
|
|
||||||
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[get("/static/<file..>", rank = 3)]
|
#[get("/static/<file..>", rank = 3)]
|
||||||
pub fn static_files(file: PathBuf) -> Option<CachedFile> {
|
pub fn static_files(file: PathBuf) -> Option<CachedFile> {
|
||||||
NamedFile::open(Path::new("static/").join(file))
|
NamedFile::open(Path::new("static/").join(file))
|
||||||
.ok()
|
.ok()
|
||||||
.map(|f| CachedFile {
|
.map(|f| CachedFile {
|
||||||
inner: FileKind::Local(f),
|
inner: f,
|
||||||
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<meta content="@blog.summary_html" property="og:description" />
|
<meta content="@blog.summary_html" property="og:description" />
|
||||||
<meta content="@blog.icon_url(ctx.0)" property="og:image" />
|
<meta content="@blog.icon_url(ctx.0)" property="og:image" />
|
||||||
|
|
||||||
<link href='@Instance::get_local().unwrap().compute_box("~", &blog.fqn, "atom.xml")' rel='alternate' type='application/atom+xml'>
|
<link href='@Instance::get_local().unwrap().compute_box("~", &blog.fqn.to_string(), "atom.xml")' rel='alternate' type='application/atom+xml'>
|
||||||
<link href='@blog.ap_url' rel='alternate' type='application/activity+json'>
|
<link href='@blog.ap_url' rel='alternate' type='application/activity+json'>
|
||||||
<link href='@blog.ap_url' rel='canonical'>
|
<link href='@blog.ap_url' rel='canonical'>
|
||||||
@if !ctx.2.clone().map(|u| u.hide_custom_css).unwrap_or(false) {
|
@if !ctx.2.clone().map(|u| u.hide_custom_css).unwrap_or(false) {
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
<a href="@uri!(blogs::details: name = &blog.fqn, page = _)" dir="auto">@blog.title</a>
|
<a href="@uri!(blogs::details: name = &blog.fqn.to_string(), page = _)" dir="auto">@blog.title</a>
|
||||||
}, {
|
}, {
|
||||||
<div class="hidden">
|
<div class="hidden">
|
||||||
@for author in authors {
|
@for author in authors {
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@if ctx.2.clone().and_then(|u| u.is_author_in(ctx.0, &blog).ok()).unwrap_or(false) {
|
@if ctx.2.clone().and_then(|u| u.is_author_in(ctx.0, &blog).ok()).unwrap_or(false) {
|
||||||
<a href="@uri!(posts::new: blog = &blog.fqn)" class="button" dir="auto">@i18n!(ctx.1, "New article")</a>
|
<a href="@uri!(posts::new: blog = &blog.fqn.to_string())" class="button" dir="auto">@i18n!(ctx.1, "New article")</a>
|
||||||
<a href="@uri!(blogs::edit: name = &blog.fqn)" class="button" dir="auto">@i18n!(ctx.1, "Edit")</a>
|
<a href="@uri!(blogs::edit: name = &blog.fqn.to_string())" class="button" dir="auto">@i18n!(ctx.1, "Edit")</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<h2 dir="auto">
|
<h2 dir="auto">
|
||||||
@i18n!(ctx.1, "Latest articles")
|
@i18n!(ctx.1, "Latest articles")
|
||||||
<small><a href="@uri!(blogs::atom_feed: name = &blog.fqn)" title="Atom feed">@icon!("rss")</a></small>
|
<small><a href="@uri!(blogs::atom_feed: name = &blog.fqn.to_string())" title="Atom feed">@icon!("rss")</a></small>
|
||||||
</h2>
|
</h2>
|
||||||
@if posts.is_empty() {
|
@if posts.is_empty() {
|
||||||
<p dir="auto">@i18n!(ctx.1, "No posts to see here yet.")</p>
|
<p dir="auto">@i18n!(ctx.1, "No posts to see here yet.")</p>
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
@(ctx: BaseContext, blog: &Blog, medias: Vec<Media>, form: &EditForm, errors: ValidationErrors)
|
@(ctx: BaseContext, blog: &Blog, medias: Vec<Media>, form: &EditForm, errors: ValidationErrors)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Edit \"{}\""; &blog.title), {}, {
|
@:base(ctx, i18n!(ctx.1, "Edit \"{}\""; &blog.title), {}, {
|
||||||
<a href="@uri!(blogs::details: name = &blog.fqn, page = _)">@blog.title</a>
|
<a href="@uri!(blogs::details: name = &blog.fqn.to_string(), page = _)">@blog.title</a>
|
||||||
}, {
|
}, {
|
||||||
<h1>@i18n!(ctx.1, "Edit \"{}\""; &blog.title)</h1>
|
<h1>@i18n!(ctx.1, "Edit \"{}\""; &blog.title)</h1>
|
||||||
<form method="post" action="@uri!(blogs::update: name = &blog.fqn)">
|
<form method="post" action="@uri!(blogs::update: name = &blog.fqn.to_string())">
|
||||||
<!-- Rocket hack to use various HTTP methods -->
|
<!-- Rocket hack to use various HTTP methods -->
|
||||||
<input type=hidden name="_method" value="put">
|
<input type=hidden name="_method" value="put">
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
<h2>@i18n!(ctx.1, "Danger zone")</h2>
|
<h2>@i18n!(ctx.1, "Danger zone")</h2>
|
||||||
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be reversed.")</p>
|
<p>@i18n!(ctx.1, "Be very careful, any action taken here can't be reversed.")</p>
|
||||||
<form method="post" action="@uri!(blogs::delete: name = &blog.fqn)" onsubmit="return confirm('@i18n!(ctx.1, "Are you sure that you want to permanently delete this blog?")')">
|
<form method="post" action="@uri!(blogs::delete: name = &blog.fqn.to_string())" onsubmit="return confirm('@i18n!(ctx.1, "Are you sure that you want to permanently delete this blog?")')">
|
||||||
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Permanently delete this blog")">
|
<input type="submit" class="inline-block button destructive" value="@i18n!(ctx.1, "Permanently delete this blog")">
|
||||||
</form>
|
</form>
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
|
|
||||||
@(ctx: BaseContext, users: Vec<User>, user: Option<&str>, page: i32, n_pages: i32)
|
@(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Users"), {}, {}, {
|
@:base(ctx, i18n!(ctx.1, "Users"), {}, {}, {
|
||||||
<h1>@i18n!(ctx.1, "Users")</h1>
|
<h1>@i18n!(ctx.1, "Users")</h1>
|
||||||
@@ -15,13 +15,6 @@
|
|||||||
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), false)
|
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), false)
|
||||||
])
|
])
|
||||||
|
|
||||||
<form method="get" action="@uri!(instance::admin_search_users: page = _, user = user.unwrap_or_default())">
|
|
||||||
<header>
|
|
||||||
<input type="search" name="user" value="@user.unwrap_or_default()">
|
|
||||||
<input type="submit" value="@i18n!(ctx.1, "Search users")">
|
|
||||||
</header>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form method="post" action="@uri!(instance::edit_users)">
|
<form method="post" action="@uri!(instance::edit_users)">
|
||||||
<header>
|
<header>
|
||||||
<select name="action">
|
<select name="action">
|
||||||
@@ -53,9 +46,5 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@if user.is_some() {
|
@paginate(ctx.1, page, n_pages)
|
||||||
@paginate_param(ctx.1, page, n_pages, Some(format!("user={}", encode_query_param(user.unwrap_or_default()))))
|
|
||||||
} else {
|
|
||||||
@paginate(ctx.1, page, n_pages)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,19 +6,19 @@
|
|||||||
|
|
||||||
<div class="card h-entry">
|
<div class="card h-entry">
|
||||||
@if article.cover_id.is_some() {
|
@if article.cover_id.is_some() {
|
||||||
<a class="cover-link" href="@uri!(posts::details: blog = article.get_blog_fqn(ctx.0), slug = &article.slug, responding_to = _)">
|
<a class="cover-link" href="@uri!(posts::details: blog = article.get_blog_fqn(ctx.0).to_string(), slug = &article.slug, responding_to = _)">
|
||||||
<div class="cover" style="background-image: url('@Html(article.cover_url(ctx.0).unwrap_or_default())')"></div>
|
<div class="cover" style="background-image: url('@Html(article.cover_url(ctx.0).unwrap_or_default())')"></div>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
<header dir="auto">
|
<header dir="auto">
|
||||||
<h3 class="p-name">
|
<h3 class="p-name">
|
||||||
<a class="u-url" href="@uri!(posts::details: blog = article.get_blog_fqn(ctx.0), slug = &article.slug, responding_to = _)">
|
<a class="u-url" href="@uri!(posts::details: blog = article.get_blog_fqn(ctx.0).to_string(), slug = &article.slug, responding_to = _)">
|
||||||
@article.title
|
@article.title
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
@if ctx.2.clone().and_then(|u| article.is_author(ctx.0, u.id).ok()).unwrap_or(false) {
|
@if ctx.2.clone().and_then(|u| article.is_author(ctx.0, u.id).ok()).unwrap_or(false) {
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<a class="button" href="@uri!(posts::edit: blog = &article.get_blog_fqn(ctx.0), slug = &article.slug)">@i18n!(ctx.1, "Edit")</a>
|
<a class="button" href="@uri!(posts::edit: blog = &article.get_blog_fqn(ctx.0).to_string(), slug = &article.slug)">@i18n!(ctx.1, "Edit")</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</header>
|
</header>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
@if article.published {
|
@if article.published {
|
||||||
⋅ <span class="dt-published" datetime="@article.creation_date.format("%F %T")">@article.creation_date.format("%B %e, %Y")</span>
|
⋅ <span class="dt-published" datetime="@article.creation_date.format("%F %T")">@article.creation_date.format("%B %e, %Y")</span>
|
||||||
}
|
}
|
||||||
⋅ <a href="@uri!(blogs::details: name = &article.get_blog_fqn(ctx.0), page = _)">@article.get_blog(ctx.0).unwrap().title</a>
|
⋅ <a href="@uri!(blogs::details: name = &article.get_blog_fqn(ctx.0).to_string(), page = _)">@article.get_blog(ctx.0).unwrap().title</a>
|
||||||
⋅
|
⋅
|
||||||
</div>
|
</div>
|
||||||
@if !article.published {
|
@if !article.published {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
@if article.cover_id.is_some() {
|
@if article.cover_id.is_some() {
|
||||||
<meta property="og:image" content="@Html(article.cover_url(ctx.0).unwrap_or_default())"/>
|
<meta property="og:image" content="@Html(article.cover_url(ctx.0).unwrap_or_default())"/>
|
||||||
}
|
}
|
||||||
<meta property="og:url" content="@uri!(posts::details: blog = &blog.fqn, slug = &article.slug, responding_to = _)"/>
|
<meta property="og:url" content="@uri!(posts::details: blog = &blog.fqn.to_string(), slug = &article.slug, responding_to = _)"/>
|
||||||
<meta property="og:description" content="@article.subtitle"/>
|
<meta property="og:description" content="@article.subtitle"/>
|
||||||
<link rel="canonical" href="@article.ap_url"/>
|
<link rel="canonical" href="@article.ap_url"/>
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
<a href="@uri!(blogs::details: name = &blog.fqn, page = _)">@blog.title</a>
|
<a href="@uri!(blogs::details: name = &blog.fqn.to_string(), page = _)">@blog.title</a>
|
||||||
}, {
|
}, {
|
||||||
<div class="h-entry">
|
<div class="h-entry">
|
||||||
<header
|
<header
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</section>
|
</section>
|
||||||
@if ctx.2.is_some() {
|
@if ctx.2.is_some() {
|
||||||
<section class="actions">
|
<section class="actions">
|
||||||
<form id="likes" class="likes" action="@uri!(likes::create: blog = &blog.fqn, slug = &article.slug)#likes" method="POST">
|
<form id="likes" class="likes" action="@uri!(likes::create: blog = &blog.fqn.to_string(), slug = &article.slug)#likes" method="POST">
|
||||||
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)">
|
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)">
|
||||||
@n_likes
|
@n_likes
|
||||||
</p>
|
</p>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<button type="submit" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</button>
|
<button type="submit" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</button>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
<form id="reshares" class="reshares" action="@uri!(reshares::create: blog = &blog.fqn, slug = &article.slug)#reshares" method="POST">
|
<form id="reshares" class="reshares" action="@uri!(reshares::create: blog = &blog.fqn.to_string(), slug = &article.slug)#reshares" method="POST">
|
||||||
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)">
|
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)">
|
||||||
@n_reshares
|
@n_reshares
|
||||||
</p>
|
</p>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
} else {
|
} else {
|
||||||
<p class="center">@Html(i18n!(ctx.1, "{0}Log in{1}, or {2}use your Fediverse account{3} to interact with this article";
|
<p class="center">@Html(i18n!(ctx.1, "{0}Log in{1}, or {2}use your Fediverse account{3} to interact with this article";
|
||||||
format!("<a href='{}'>", escape(&uri!(session::new: m = _).to_string())), "</a>",
|
format!("<a href='{}'>", escape(&uri!(session::new: m = _).to_string())), "</a>",
|
||||||
format!("<a href='{}'>", escape(&uri!(posts::remote_interact: blog_name = &blog.fqn, slug = &article.slug).to_string())), "</a>"
|
format!("<a href='{}'>", escape(&uri!(posts::remote_interact: blog_name = &blog.fqn.to_string(), slug = &article.slug).to_string())), "</a>"
|
||||||
))
|
))
|
||||||
</p>
|
</p>
|
||||||
<section class="actions">
|
<section class="actions">
|
||||||
@@ -112,14 +112,14 @@
|
|||||||
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)">
|
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)">
|
||||||
@n_likes
|
@n_likes
|
||||||
</p>
|
</p>
|
||||||
<a href="@uri!(posts::remote_interact: blog_name = &blog.fqn, slug = &article.slug)" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</a>
|
<a href="@uri!(posts::remote_interact: blog_name = &blog.fqn.to_string(), slug = &article.slug)" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="reshares" class="reshares">
|
<div id="reshares" class="reshares">
|
||||||
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)">
|
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)">
|
||||||
@n_reshares
|
@n_reshares
|
||||||
</p>
|
</p>
|
||||||
<a href="@uri!(posts::remote_interact: blog_name = &blog.fqn, slug = &article.slug)" class="action">@icon!("repeat") @i18n!(ctx.1, "Boost")</a>
|
<a href="@uri!(posts::remote_interact: blog_name = &blog.fqn.to_string(), slug = &article.slug)" class="action">@icon!("repeat") @i18n!(ctx.1, "Boost")</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
<h2>@i18n!(ctx.1, "Comments")</h2>
|
<h2>@i18n!(ctx.1, "Comments")</h2>
|
||||||
|
|
||||||
@if ctx.2.is_some() {
|
@if ctx.2.is_some() {
|
||||||
<form method="post" action="@uri!(comments::create: blog_name = &blog.fqn, slug = &article.slug)#comments">
|
<form method="post" action="@uri!(comments::create: blog_name = &blog.fqn.to_string(), slug = &article.slug)#comments">
|
||||||
@(Input::new("warning", i18n!(ctx.1, "Content warning"))
|
@(Input::new("warning", i18n!(ctx.1, "Content warning"))
|
||||||
.default(&comment_form.warning)
|
.default(&comment_form.warning)
|
||||||
.error(&comment_errors)
|
.error(&comment_errors)
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
|
|
||||||
@if !comments.is_empty() {
|
@if !comments.is_empty() {
|
||||||
@for comm in comments {
|
@for comm in comments {
|
||||||
@:comment(ctx, &comm, Some(&article.ap_url), &blog.fqn, &article.slug)
|
@:comment(ctx, &comm, Some(&article.ap_url), &blog.fqn.to_string(), &article.slug)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
<p class="center" dir="auto">@i18n!(ctx.1, "No comments yet. Be the first to react!")</p>
|
<p class="center" dir="auto">@i18n!(ctx.1, "No comments yet. Be the first to react!")</p>
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
@if ctx.2.clone().and_then(|u| article.is_author(ctx.0, u.id).ok()).unwrap_or(false) {
|
@if ctx.2.clone().and_then(|u| article.is_author(ctx.0, u.id).ok()).unwrap_or(false) {
|
||||||
<aside class="bottom-bar">
|
<aside class="bottom-bar">
|
||||||
<div>
|
<div>
|
||||||
<form class="inline" method="post" action="@uri!(posts::delete: blog_name = &blog.fqn, slug = &article.slug)">
|
<form class="inline" method="post" action="@uri!(posts::delete: blog_name = &blog.fqn.to_string(), slug = &article.slug)">
|
||||||
<input class="button destructive" onclick="return confirm('@i18n!(ctx.1, "Are you sure?")')" type="submit" value="@i18n!(ctx.1, "Delete")">
|
<input class="button destructive" onclick="return confirm('@i18n!(ctx.1, "Are you sure?")')" type="submit" value="@i18n!(ctx.1, "Delete")">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,9 +186,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@if !article.published {
|
@if !article.published {
|
||||||
<a class="button secondary" href="@uri!(posts::edit: blog = &blog.fqn, slug = &article.slug)">@i18n!(ctx.1, "Publish")</a>
|
<a class="button secondary" href="@uri!(posts::edit: blog = &blog.fqn.to_string(), slug = &article.slug)">@i18n!(ctx.1, "Publish")</a>
|
||||||
}
|
}
|
||||||
<a class="button" href="@uri!(posts::edit: blog = &blog.fqn, slug = &article.slug)">@i18n!(ctx.1, "Edit")</a>
|
<a class="button" href="@uri!(posts::edit: blog = &blog.fqn.to_string(), slug = &article.slug)">@i18n!(ctx.1, "Edit")</a>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user