Compare commits

...

27 Commits

Author SHA1 Message Date
trinity-1686a 15cb3cf156 initial s3 support
probably incomplete
2023-05-11 22:11:19 +02:00
KitaitiMakoto 613ccbcd94 Merge pull request 'Add user search form to admin panel' (#1143) from moderation-improvement into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1143
2023-03-21 10:29:33 +00:00
Kitaiti Makoto b9a09a2511 Follow pagination user list page change 2023-03-21 19:15:22 +09:00
Kitaiti Makoto 213628e400 Don't use LIKE query when username is empty for user search 2023-03-21 19:07:09 +09:00
Kitaiti Makoto d6bb2bfb72 Use unwrap_or_default() instead of unwrap_or("") 2023-03-21 18:49:52 +09:00
Kitaiti Makoto 33bd290679 Use DbConn in model tests 2023-03-20 01:21:39 +09:00
Kitaiti Makoto 85ab5393fd Set style to user search form 2023-03-20 01:03:09 +09:00
Kitaiti Makoto 98c73bb6df Add search form to user list page 2023-03-20 01:00:17 +09:00
Kitaiti Makoto 3e9d9a459f Enable admin_search_user route 2023-03-20 01:00:02 +09:00
Kitaiti Makoto a394c3f210 Define admin_search_user route 2023-03-20 00:59:46 +09:00
Kitaiti Makoto a1a19e091a Define User::search_local_by_name() method 2023-03-20 00:59:16 +09:00
Kitaiti Makoto ec030d500d Exclude instance user when counting local users 2023-03-20 00:19:42 +09:00
Kitaiti Makoto cfa74f84e7 Remove instance users from user list to show 2023-03-06 19:30:18 +09:00
trinity-1686a 97cbe7f446 Merge pull request 'allow timeline manipulation from plm' (#1113) from timeline-cli into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1113
Reviewed-by: KitaitiMakoto <kitaitimakoto@noreply@joinplu.me>
2023-02-26 15:56:16 +00:00
trinity-1686a 7e4d081027 Merge branch 'main' into timeline-cli 2023-02-26 16:48:40 +01:00
KitaitiMakoto 1e5ae92135 Merge pull request 'Update crates' (#1138) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1138
2023-01-10 15:53:07 +00:00
Kitaiti Makoto 036ee6fac4 Merge remote-tracking branch 'origin/main' into update-crates 2023-01-11 00:42:34 +09:00
dependabot[bot] 6028295748 Bump glob from 0.3.0 to 0.3.1
Bumps [glob](https://github.com/rust-lang/glob) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/rust-lang/glob/releases)
- [Commits](https://github.com/rust-lang/glob/compare/0.3.0...0.3.1)

---
updated-dependencies:
- dependency-name: glob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-11 00:41:35 +09:00
dependabot[bot] aa4cfd374d Bump atom_syndication from 0.11.0 to 0.12.0
Bumps [atom_syndication](https://github.com/rust-syndication/atom) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/rust-syndication/atom/releases)
- [Changelog](https://github.com/rust-syndication/atom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-syndication/atom/compare/0.11.0...0.12.0)

---
updated-dependencies:
- dependency-name: atom_syndication
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-11 00:41:35 +09:00
dependabot[bot] 3303a4af84 Bump ructe from 0.14.2 to 0.15.0
Bumps [ructe](https://github.com/kaj/ructe) from 0.14.2 to 0.15.0.
- [Release notes](https://github.com/kaj/ructe/releases)
- [Changelog](https://github.com/kaj/ructe/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/ructe/compare/v0.14.2...v0.15.0)

---
updated-dependencies:
- dependency-name: ructe
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Change blog title specification

Revert "Change blog title specification"

This reverts commit a362b2474fa32b0e937f59acb9edb68d462c0719.
2023-01-11 00:41:22 +09:00
KitaitiMakoto 37a136787b Merge pull request 'Update crates' (#1136) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1136
2023-01-08 20:07:16 +00:00
Kitaiti Makoto 300ff37694 Merge remote-tracking branch 'github/dependabot/cargo/rsass-0.26.0' into update-crates 2023-01-09 04:50:04 +09:00
dependabot[bot] c1d9d39dc1 Bump rsass from 0.25.2 to 0.26.0
Bumps [rsass](https://github.com/kaj/rsass) from 0.25.2 to 0.26.0.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.25.2...v0.26.0)

---
updated-dependencies:
- dependency-name: rsass
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-06 19:06:33 +00:00
dependabot[bot] 93d6ee04d4 Bump ructe from 0.14.2 to 0.15.0
Bumps [ructe](https://github.com/kaj/ructe) from 0.14.2 to 0.15.0.
- [Release notes](https://github.com/kaj/ructe/releases)
- [Changelog](https://github.com/kaj/ructe/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/ructe/compare/v0.14.2...v0.15.0)

---
updated-dependencies:
- dependency-name: ructe
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-06 19:06:09 +00:00
trinity-1686a 35b951967d add a few help messages to the cli 2023-01-01 11:03:50 +01:00
trinity-1686a 771d4325c2 add plm command for list management 2022-12-17 17:51:51 +01:00
trinity-1686a 1536a6d3f3 allow timeline manipulation from plm 2022-12-16 22:51:14 +01:00
25 changed files with 1147 additions and 145 deletions
Generated
+262 -27
View File
@@ -115,6 +115,12 @@ 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"
@@ -166,6 +172,12 @@ 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"
@@ -219,15 +231,15 @@ dependencies = [
[[package]] [[package]]
name = "atom_syndication" name = "atom_syndication"
version = "0.11.0" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21fb6a0b39c6517edafe46f8137e53c51742425a4dae1c73ee12264a37ad7541" checksum = "91a85f2ee28cbd1ecf91288460f6dc74661fd99b4e9a559836a667ccf63aa38c"
dependencies = [ dependencies = [
"chrono", "chrono",
"derive_builder", "derive_builder",
"diligent-date-parser", "diligent-date-parser",
"never", "never",
"quick-xml", "quick-xml 0.27.1",
] ]
[[package]] [[package]]
@@ -241,6 +253,22 @@ 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"
@@ -267,6 +295,31 @@ 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"
@@ -389,6 +442,16 @@ 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"
@@ -578,7 +641,7 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"nom 5.1.2", "nom 5.1.2",
"rust-ini", "rust-ini 0.13.0",
"serde 1.0.152", "serde 1.0.152",
"serde-hjson", "serde-hjson",
"serde_json", "serde_json",
@@ -636,7 +699,7 @@ dependencies = [
"aes-gcm", "aes-gcm",
"base64 0.13.1", "base64 0.13.1",
"hkdf", "hkdf",
"hmac", "hmac 0.10.1",
"percent-encoding 2.2.0", "percent-encoding 2.2.0",
"rand 0.8.5", "rand 0.8.5",
"sha2", "sha2",
@@ -876,6 +939,16 @@ 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"
@@ -947,9 +1020,9 @@ dependencies = [
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.12.4" 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 = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"darling_macro", "darling_macro",
@@ -957,9 +1030,9 @@ dependencies = [
[[package]] [[package]]
name = "darling_core" name = "darling_core"
version = "0.12.4" 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 = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
dependencies = [ dependencies = [
"fnv", "fnv",
"ident_case", "ident_case",
@@ -971,9 +1044,9 @@ dependencies = [
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.12.4" 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 = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote 1.0.23", "quote 1.0.23",
@@ -1024,18 +1097,18 @@ dependencies = [
[[package]] [[package]]
name = "derive_builder" name = "derive_builder"
version = "0.10.2" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30" checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
dependencies = [ dependencies = [
"derive_builder_macro", "derive_builder_macro",
] ]
[[package]] [[package]]
name = "derive_builder_core" name = "derive_builder_core"
version = "0.10.2" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2 1.0.49", "proc-macro2 1.0.49",
@@ -1045,9 +1118,9 @@ dependencies = [
[[package]] [[package]]
name = "derive_builder_macro" name = "derive_builder_macro"
version = "0.10.2" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73" checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
dependencies = [ dependencies = [
"derive_builder_core", "derive_builder_core",
"syn 1.0.107", "syn 1.0.107",
@@ -1150,6 +1223,35 @@ 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"
@@ -1632,9 +1734,9 @@ checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "guid" name = "guid"
@@ -1745,6 +1847,15 @@ 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"
@@ -1794,7 +1905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
dependencies = [ dependencies = [
"digest", "digest",
"hmac", "hmac 0.10.1",
] ]
[[package]] [[package]]
@@ -1803,7 +1914,17 @@ 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", "crypto-mac 0.10.1",
"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",
] ]
@@ -2549,6 +2670,17 @@ 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"
@@ -2641,6 +2773,15 @@ 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"
@@ -3063,6 +3204,16 @@ 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"
@@ -3294,6 +3445,7 @@ 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",
@@ -3409,6 +3561,7 @@ 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",
@@ -3554,9 +3707,18 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.22.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" checksum = "26aab6b48e2590e4a64d1ed808749ba06257882b461d01ca71baeb747074a6dd"
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",
@@ -3836,6 +3998,17 @@ 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"
@@ -3967,6 +4140,7 @@ 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",
@@ -4124,10 +4298,11 @@ dependencies = [
[[package]] [[package]]
name = "rsass" name = "rsass"
version = "0.25.2" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab003a9ff922b4d7b1f5f383bc54645887ad278f534dfc65e071096fcb30023" checksum = "665387d3be91521de331a04cc7118c8c129ffe43c5f4e8a61e78990850046b5a"
dependencies = [ dependencies = [
"arc-swap",
"fastrand", "fastrand",
"lazy_static", "lazy_static",
"nom 7.1.2", "nom 7.1.2",
@@ -4141,9 +4316,9 @@ dependencies = [
[[package]] [[package]]
name = "ructe" name = "ructe"
version = "0.14.2" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef279e568db5dad3a1867112fedc63e2f0567991e82beec01c04ade6bd53c08a" checksum = "85517cd381cf0c34694881d8aaf173107c6af7670e66cec18d7a1a8bfce3b758"
dependencies = [ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"bytecount", "bytecount",
@@ -4158,6 +4333,48 @@ 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"
@@ -4301,6 +4518,18 @@ 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"
@@ -5678,6 +5907,12 @@ 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"
+5 -3
View File
@@ -6,7 +6,7 @@ repository = "https://github.com/Plume-org/Plume"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
atom_syndication = "0.11.0" atom_syndication = "0.12.0"
clap = "2.33" clap = "2.33"
dotenv = "0.15.0" dotenv = "0.15.0"
gettext = "0.4.0" gettext = "0.4.0"
@@ -19,6 +19,7 @@ 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"
@@ -64,8 +65,8 @@ git = "https://git.joinplu.me/plume/rocket_csrf"
rev = "0.1.2" rev = "0.1.2"
[build-dependencies] [build-dependencies]
ructe = "0.14.0" ructe = "0.15.0"
rsass = "0.25" rsass = "0.26"
[features] [features]
default = ["postgres"] default = ["postgres"]
@@ -74,6 +75,7 @@ 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"]
+262
View File
@@ -0,0 +1,262 @@
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();
}
}
}
}
+8
View File
@@ -4,8 +4,10 @@ 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() {
@@ -16,6 +18,8 @@ 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();
@@ -37,6 +41,10 @@ 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."))
} }
+257
View File
@@ -0,0 +1,257 @@
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);
}
+2 -1
View File
@@ -18,6 +18,7 @@ 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"
@@ -27,7 +28,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.0" glob = "0.3.1"
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"
+8 -8
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
db_conn::DbConn, instance::*, medias::Media, posts::Post, safe_string::SafeString, instance::*, medias::Media, posts::Post, safe_string::SafeString, schema::blogs, users::User,
schema::blogs, users::User, Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE, Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
}; };
use activitystreams::{ use activitystreams::{
actor::{ApActor, ApActorExt, AsApActor, Group}, actor::{ApActor, ApActorExt, AsApActor, Group},
@@ -142,10 +142,10 @@ impl Blog {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn find_by_fqn(conn: &DbConn, fqn: &str) -> Result<Blog> { pub fn find_by_fqn(conn: &Connection, 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: &DbConn, acct: &str) -> Result<Blog> { fn fetch_from_webfinger(conn: &Connection, 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()
@@ -372,15 +372,15 @@ impl IntoId for Blog {
} }
} }
impl FromId<DbConn> for Blog { impl FromId<Connection> for Blog {
type Error = Error; type Error = Error;
type Object = CustomGroup; type Object = CustomGroup;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> { fn from_activity(conn: &Connection, 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
+12 -12
View File
@@ -1,6 +1,5 @@
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,
@@ -111,7 +110,7 @@ impl Comment {
.unwrap_or(false) .unwrap_or(false)
} }
pub fn to_activity(&self, conn: &DbConn) -> Result<Note> { pub fn to_activity(&self, conn: &Connection) -> 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(),
@@ -149,7 +148,7 @@ impl Comment {
Ok(note) Ok(note)
} }
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> { pub fn create_activity(&self, conn: &Connection) -> 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)?;
@@ -217,15 +216,15 @@ impl Comment {
} }
} }
impl FromId<DbConn> for Comment { impl FromId<Connection> for Comment {
type Error = Error; type Error = Error;
type Object = Note; type Object = Note;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> { fn from_activity(conn: &Connection, note: Note) -> Result<Self> {
let comm = { let comm = {
let previous_url = note let previous_url = note
.in_reply_to() .in_reply_to()
@@ -354,21 +353,21 @@ impl FromId<DbConn> for Comment {
} }
} }
impl AsObject<User, Create, &DbConn> for Comment { impl AsObject<User, Create, &Connection> for Comment {
type Error = Error; type Error = Error;
type Output = Self; type Output = Self;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self> { fn activity(self, _conn: &Connection, _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, &DbConn> for Comment { impl AsObject<User, Delete, &Connection> for Comment {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, 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);
} }
@@ -387,8 +386,8 @@ impl AsObject<User, Delete, &DbConn> 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(())
} }
} }
@@ -423,6 +422,7 @@ 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};
+103 -5
View File
@@ -6,6 +6,10 @@ 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)]
@@ -27,13 +31,23 @@ 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,
@@ -288,11 +302,7 @@ 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 = match tls.as_ref() { let tls = string_to_bool(&tls, "LDAP_TLS");
"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 {
@@ -349,6 +359,93 @@ 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!(
@@ -380,5 +477,6 @@ 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(),
}; };
} }
+14 -12
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User, ap_url, instance::Instance, notifications::*, schema::follows, users::User, Connection, Error,
Connection, Error, Result, CONFIG, 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, &DbConn> for User { impl AsObject<User, FollowAct, &Connection> for User {
type Error = Error; type Error = Error;
type Output = Follow; type Output = Follow;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Follow> { fn activity(self, conn: &Connection, 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, &DbConn> for User {
} }
} }
impl FromId<DbConn> for Follow { impl FromId<Connection> for Follow {
type Error = Error; type Error = Error;
type Object = FollowAct; type Object = FollowAct;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Follow::find_by_ap_url(conn, id) Follow::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> { fn from_activity(conn: &Connection, follow: FollowAct) -> Result<Self> {
let actor = User::from_id( let actor = User::from_id(
conn, conn,
follow follow
@@ -202,18 +202,18 @@ impl FromId<DbConn> for Follow {
} }
} }
impl AsObject<User, Undo, &DbConn> for Follow { impl AsObject<User, Undo, &Connection> for Follow {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, 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(&notif).execute(&**conn)?; diesel::delete(&notif).execute(conn)?;
} }
Ok(()) Ok(())
@@ -232,7 +232,9 @@ impl IntoId for Follow {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{tests::db, users::tests as user_tests, users::tests::fill_database}; use crate::{
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};
+1 -1
View File
@@ -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())
+7
View File
@@ -69,6 +69,7 @@ pub enum Error {
Webfinger, Webfinger,
Expired, Expired,
UserAlreadyExists, UserAlreadyExists,
Anyhow(anyhow::Error),
} }
impl From<bcrypt::BcryptError> for Error { impl From<bcrypt::BcryptError> for Error {
@@ -170,6 +171,12 @@ impl From<request::Error> for Error {
} }
} }
impl From<anyhow::Error> for Error {
fn from(err: anyhow::Error) -> Error {
Error::Anyhow(err)
}
}
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// Adds a function to a model, that returns the first /// Adds a function to a model, that returns the first
+11 -11
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, users::User,
users::User, Connection, Error, Result, CONFIG, 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, &DbConn> for Post { impl AsObject<User, LikeAct, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = Like; type Output = Like;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Like> { fn activity(self, conn: &Connection, 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, &DbConn> for Post {
} }
} }
impl FromId<DbConn> for Like { impl FromId<Connection> for Like {
type Error = Error; type Error = Error;
type Object = LikeAct; type Object = LikeAct;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Like::find_by_ap_url(conn, id) Like::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, act: LikeAct) -> Result<Self> { fn from_activity(conn: &Connection, act: LikeAct) -> Result<Self> {
let res = Like::insert( let res = Like::insert(
conn, conn,
NewLike { NewLike {
@@ -154,17 +154,17 @@ impl FromId<DbConn> for Like {
} }
} }
impl AsObject<User, Undo, &DbConn> for Like { impl AsObject<User, Undo, &Connection> for Like {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, 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(&notif).execute(&**conn)?; diesel::delete(&notif).execute(conn)?;
} }
Ok(()) Ok(())
} else { } else {
+22
View File
@@ -297,6 +297,28 @@ 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}
+10 -5
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias, ap_url, instance::Instance, safe_string::SafeString, schema::medias, users::User, Connection,
users::User, Connection, Error, Result, CONFIG, 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,7 +170,12 @@ impl Media {
pub fn delete(&self, conn: &Connection) -> Result<()> { pub fn delete(&self, conn: &Connection) -> Result<()> {
if !self.is_remote { if !self.is_remote {
fs::remove_file(self.file_path.as_str())?; if let Some(config) = &CONFIG.s3 {
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)
@@ -206,7 +211,7 @@ impl Media {
} }
// TODO: merge with save_remote? // TODO: merge with save_remote?
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> { pub fn from_activity(conn: &Connection, 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())
@@ -258,7 +263,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)
}) })
+3 -3
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions, comments::Comment, notifications::*, posts::Post, schema::mentions, users::User, Connection,
users::User, Connection, Error, Result, Error, Result,
}; };
use activitystreams::{ use activitystreams::{
base::BaseExt, base::BaseExt,
@@ -60,7 +60,7 @@ impl Mention {
} }
} }
pub fn build_activity(conn: &DbConn, ment: &str) -> Result<link::Mention> { pub fn build_activity(conn: &Connection, 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>()?);
+16 -15
View File
@@ -1,7 +1,7 @@
use crate::{ use crate::{
ap_url, blogs::Blog, db_conn::DbConn, instance::Instance, medias::Media, mentions::Mention, ap_url, blogs::Blog, instance::Instance, medias::Media, mentions::Mention, post_authors::*,
post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, Connection, Error,
Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN, PostEvent::*, Result, CONFIG, POST_CHAN,
}; };
use activitystreams::{ use activitystreams::{
activity::{Create, Delete, Update}, activity::{Create, Delete, Update},
@@ -615,15 +615,15 @@ impl Post {
} }
} }
impl FromId<DbConn> for Post { impl FromId<Connection> for Post {
type Error = Error; type Error = Error;
type Object = LicensedArticle; type Object = LicensedArticle;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> { fn from_activity(conn: &Connection, 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<DbConn> for Post {
} }
} }
impl AsObject<User, Create, &DbConn> for Post { impl AsObject<User, Create, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = Self; type Output = Self;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self::Output> { fn activity(self, _conn: &Connection, _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, &DbConn> for Post { impl AsObject<User, Delete, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<Self::Output> { fn activity(self, conn: &Connection, 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<DbConn> for PostUpdate { impl FromId<Connection> for PostUpdate {
type Error = Error; type Error = Error;
type Object = LicensedArticle; type Object = LicensedArticle;
fn from_db(_: &DbConn, _: &str) -> Result<Self> { fn from_db(_: &Connection, _: &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: &DbConn, updated: Self::Object) -> Result<Self> { fn from_activity(conn: &Connection, 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<DbConn> for PostUpdate {
} }
} }
impl AsObject<User, Update, &DbConn> for PostUpdate { impl AsObject<User, Update, &Connection> for PostUpdate {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, 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,6 +1027,7 @@ 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;
+11 -11
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares, instance::Instance, notifications::*, posts::Post, schema::reshares, timeline::*, users::User,
timeline::*, users::User, Connection, Error, Result, CONFIG, 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, &DbConn> for Post { impl AsObject<User, Announce, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = Reshare; type Output = Reshare;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Reshare> { fn activity(self, conn: &Connection, 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, &DbConn> for Post {
} }
} }
impl FromId<DbConn> for Reshare { impl FromId<Connection> for Reshare {
type Error = Error; type Error = Error;
type Object = Announce; type Object = Announce;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Reshare::find_by_ap_url(conn, id) Reshare::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, act: Announce) -> Result<Self> { fn from_activity(conn: &Connection, act: Announce) -> Result<Self> {
let res = Reshare::insert( let res = Reshare::insert(
conn, conn,
NewReshare { NewReshare {
@@ -183,17 +183,17 @@ impl FromId<DbConn> for Reshare {
} }
} }
impl AsObject<User, Undo, &DbConn> for Reshare { impl AsObject<User, Undo, &Connection> for Reshare {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, 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(&notif).execute(&**conn)?; diesel::delete(&notif).execute(conn)?;
} }
Ok(()) Ok(())
+22 -4
View File
@@ -1,5 +1,4 @@
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},
@@ -12,7 +11,7 @@ use std::ops::Deref;
pub(crate) mod query; pub(crate) mod query;
pub use self::query::Kind; pub use self::query::Kind;
use self::query::{QueryError, TimelineQuery}; pub 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"]
@@ -220,7 +219,7 @@ impl Timeline {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn add_to_all_timelines(conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<()> { pub fn add_to_all_timelines(conn: &Connection, 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)?;
@@ -246,7 +245,26 @@ impl Timeline {
Ok(()) Ok(())
} }
pub fn matches(&self, conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<bool> { pub fn remove_post(&self, conn: &Connection, post: &Post) -> 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)
} }
+6 -7
View File
@@ -1,12 +1,11 @@
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,
Result, Connection, Result,
}; };
use plume_common::activity_pub::inbox::AsActor; use plume_common::activity_pub::inbox::AsActor;
use whatlang::{self, Lang}; use whatlang::{self, Lang};
@@ -155,7 +154,7 @@ enum TQ<'a> {
impl<'a> TQ<'a> { impl<'a> TQ<'a> {
fn matches( fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
@@ -200,7 +199,7 @@ enum Arg<'a> {
impl<'a> Arg<'a> { impl<'a> Arg<'a> {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
@@ -225,7 +224,7 @@ enum WithList {
impl WithList { impl WithList {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
list: &List<'_>, list: &List<'_>,
@@ -361,7 +360,7 @@ enum Bool {
impl Bool { impl Bool {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
@@ -654,7 +653,7 @@ impl<'a> TimelineQuery<'a> {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
+36 -10
View File
@@ -15,7 +15,10 @@ use activitystreams::{
prelude::*, prelude::*,
}; };
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl}; use diesel::{
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,
@@ -186,15 +189,16 @@ 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: &DbConn, fqn: &str) -> Result<User> { pub fn find_by_fqn(conn: &Connection, 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)
@@ -203,6 +207,27 @@ 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
*/ */
@@ -219,7 +244,7 @@ impl User {
.map_err(Error::from) .map_err(Error::from)
} }
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> { fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<User> {
let link = resolve(acct.to_owned(), true)? let link = resolve(acct.to_owned(), true)?
.links .links
.into_iter() .into_iter()
@@ -431,6 +456,7 @@ 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())
@@ -921,15 +947,15 @@ impl IntoId for User {
impl Eq for User {} impl Eq for User {}
impl FromId<DbConn> for User { impl FromId<Connection> for User {
type Error = Error; type Error = Error;
type Object = CustomPerson; type Object = CustomPerson;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> { fn from_activity(conn: &Connection, 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()
@@ -1030,7 +1056,7 @@ impl FromId<DbConn> for User {
} }
} }
impl AsActor<&DbConn> for User { impl AsActor<&Connection> for User {
fn get_inbox_url(&self) -> String { fn get_inbox_url(&self) -> String {
self.inbox_url.clone() self.inbox_url.clone()
} }
@@ -1046,11 +1072,11 @@ impl AsActor<&DbConn> for User {
} }
} }
impl AsObject<User, Delete, &DbConn> for User { impl AsObject<User, Delete, &Connection> for User {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, 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 {
+1
View File
@@ -157,6 +157,7 @@ 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,
+25 -1
View File
@@ -160,7 +160,7 @@ pub fn toggle_block(
)) ))
} }
#[get("/admin/users?<page>")] #[get("/admin/users?<page>", rank = 2)]
pub fn admin_users( pub fn admin_users(
_mod: Moderator, _mod: Moderator,
page: Option<Page>, page: Option<Page>,
@@ -171,6 +171,30 @@ 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)
))) )))
+30 -7
View File
@@ -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},
RawStr, Status, ContentType, 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,10 +204,16 @@ 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: NamedFile, inner: FileKind,
cache_control: CacheControl, cache_control: CacheControl,
} }
@@ -253,19 +259,36 @@ 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> {
NamedFile::open(Path::new(&CONFIG.media_directory).join(file)) if let Some(config) = &CONFIG.s3 {
.ok() let ct = file.extension()
.map(|f| CachedFile { .and_then(|ext| ContentType::from_extension(&ext.to_string_lossy()))
inner: f, .unwrap_or(ContentType::Binary);
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: f, inner: FileKind::Local(f),
cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]), cache_control: CacheControl(vec![CacheDirective::MaxAge(60 * 60 * 24 * 30)]),
}) })
} }
+13 -2
View File
@@ -3,7 +3,7 @@
@use crate::template_utils::*; @use crate::template_utils::*;
@use crate::routes::*; @use crate::routes::*;
@(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32) @(ctx: BaseContext, users: Vec<User>, user: Option<&str>, 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,6 +15,13 @@
(&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">
@@ -46,5 +53,9 @@
} }
</div> </div>
</form> </form>
@paginate(ctx.1, page, n_pages) @if user.is_some() {
@paginate_param(ctx.1, page, n_pages, Some(format!("user={}", encode_query_param(user.unwrap_or_default()))))
} else {
@paginate(ctx.1, page, n_pages)
}
}) })