Compare commits

...

879 Commits

Author SHA1 Message Date
Kitaiti Makoto 9bd8f5272f Define From<PreferredUsernameError> for Error 2023-01-15 08:57:08 +09:00
Kitaiti Makoto 39cd4f830d Remove anyhow from plume-common dependencies 2023-01-15 08:52:18 +09:00
Kitaiti Makoto cd9cb311c7 Define PreferredUsernameError and use it 2023-01-15 08:51:38 +09:00
Kitaiti Makoto 83ed168f9c Add thiserror to plume-common dependencies 2023-01-15 08:38:55 +09:00
Kitaiti Makoto 83c628d490 Remove heck from plume-common dependencies 2023-01-15 08:14:58 +09:00
Kitaiti Makoto badff3f3cb Remove unused make_fqn() 2023-01-15 08:14:58 +09:00
Kitaiti Makoto ba00e36884 use Fqn::make_local() instead of make_fqn() 2023-01-15 08:14:58 +09:00
Kitaiti Makoto 5ee84427bf Add functions to make FQN to Fqn 2023-01-15 08:14:58 +09:00
Kitaiti Makoto f203dddae5 Add heck to plume-models dependencies 2023-01-15 08:14:58 +09:00
Kitaiti Makoto ba1eac9482 Add test for blog title 2023-01-15 08:14:56 +09:00
Kitaiti Makoto 3dad83b179 Set up Rocket for testing environment 2023-01-15 06:58:01 +09:00
Kitaiti Makoto 4eab51b159 Make Config a argument for init_rocket() 2023-01-15 06:20:54 +09:00
Kitaiti Makoto abf0b28fd4 Move set up from init_rocket() to main() 2023-01-15 06:07:24 +09:00
Kitaiti Makoto 115b5b31a4 Follow Blog's API change 2023-01-14 03:40:25 +09:00
Kitaiti Makoto 5a03fd7340 Make Blog::fqn Fqn 2023-01-14 03:23:45 +09:00
Kitaiti Makoto e75449410f Add test for Fqn 2023-01-14 03:22:42 +09:00
Kitaiti Makoto c9bb31b8f5 Format 2023-01-14 03:22:21 +09:00
Kitaiti Makoto 0c2eaf0f1b Define Fqn struct 2023-01-14 03:22:18 +09:00
Kitaiti Makoto 71824aa524 Define PreferredUsername struct 2023-01-13 20:19:36 +09:00
Kitaiti Makoto fc848a8d53 Allow ASCII and numeric only for fqn 2023-01-10 23:49:37 +09:00
Kitaiti Makoto 53cdd8198b Add anyhow to dependencies 2023-01-09 19:42:54 +09:00
Kitaiti Makoto 08f4dac3d3 Set blog title in test fixture 2023-01-09 19:14:20 +09:00
Kitaiti Makoto 18a9ed5504 Extract setup() and teardown() from blogs test 2023-01-09 17:46:54 +09:00
Kitaiti Makoto 631359c3f7 Create local instance in create_models() function 2023-01-09 17:05:55 +09:00
Kitaiti Makoto 3111fa0735 Run migration 2023-01-09 16:19:30 +09:00
Kitaiti Makoto 890c9a0da4 Add migration for SQLite 2023-01-09 16:19:05 +09:00
Kitaiti Makoto 22b03710be Implement migration for PostgreSQL 2023-01-09 16:13:12 +09:00
Kitaiti Makoto e3609f7863 Generate migration to add unique constraint to ActivityPub related fields
% diesel migration generate add_unique_constraint_to_activity_pub_related_fields
2023-01-09 16:10:23 +09:00
Kitaiti Makoto 0714d2d010 Use User.fqn for user activity for consistency with blog 2023-01-09 15:39:58 +09:00
Kitaiti Makoto 5bd084eff7 Make test data follow blog test data change 2023-01-09 07:29:01 +09:00
Kitaiti Makoto f369fa9b25 Fix blog title conversion for ActivityPub 2023-01-09 07:22:17 +09:00
Kitaiti Makoto 8afcc1511e Define make_fqn() 2023-01-09 06:55:49 +09:00
Kitaiti Makoto ce89faef84 Install heck 2023-01-09 06:54:38 +09:00
Kitaiti Makoto e18b6e78f2 Add heck to plume-common's dependencies 2023-01-09 06:54:30 +09:00
Kitaiti Makoto 31e817385d Percent-encode slug in Blog::slug() 2023-01-09 06:18:58 +09:00
Kitaiti Makoto af7ed450e2 Fix iri_percent_encode_seg() to encode some missing characters 2023-01-09 06:15:29 +09:00
Kitaiti Makoto 55a5a64b1a Fix valid slug spec 2023-01-09 06:15:29 +09:00
Kitaiti Makoto 08b7d100fd Change blog title specification 2023-01-09 06:15:23 +09:00
KitaitiMakoto ae7bf2e132 Merge pull request 'Update crates' (#1134) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1134
2023-01-06 14:57:17 +00:00
Kitaiti Makoto 0020242571 Format 2023-01-06 23:40:05 +09:00
Kitaiti Makoto 4f796e788c Clippy 2023-01-06 23:38:38 +09:00
Kitaiti Makoto 3d192c1179 Merge remote-tracking branches 'github/dependabot/cargo/whatlang-0.16.2', 'github/dependabot/cargo/ldap3-0.11.1' and 'github/dependabot/cargo/futures-0.3.25' into update-crates 2023-01-06 23:12:47 +09:00
dependabot[bot] 2f8d188d59 Bump futures from 0.3.21 to 0.3.25
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.21 to 0.3.25.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.21...0.3.25)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 19:07:12 +00:00
dependabot[bot] 19766662f1 Bump ldap3 from 0.10.6 to 0.11.1
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.10.6 to 0.11.1.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.10.6...v0.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 19:06:52 +00:00
dependabot[bot] 301aad3f73 Bump whatlang from 0.16.0 to 0.16.2
Bumps [whatlang](https://github.com/greyblake/whatlang-rs) from 0.16.0 to 0.16.2.
- [Release notes](https://github.com/greyblake/whatlang-rs/releases)
- [Changelog](https://github.com/greyblake/whatlang-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/greyblake/whatlang-rs/compare/v0.16.0...v0.16.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 19:06:39 +00:00
KitaitiMakoto 92a8f8aa4c Merge pull request 'Update crates' (#1131) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1131
2023-01-05 16:35:11 +00:00
Kitaiti Makoto 0c856a5252 Update Ructe 2023-01-06 01:11:46 +09:00
Kitaiti Makoto 2df6138ff1 Fix caddy run option 2023-01-06 00:59:00 +09:00
Kitaiti Makoto b2942f3f47 Update crates 2023-01-06 00:42:29 +09:00
Kitaiti Makoto 94f20c8fc2 Update rustfmt and clippy on Ci 2023-01-06 00:42:20 +09:00
Kitaiti Makoto 5d48b93c8b Update Docker image for CI 2023-01-06 00:27:57 +09:00
Kitaiti Makoto bbf2e00920 Install updated crates 2023-01-06 00:04:53 +09:00
Kitaiti Makoto c97361f5f4 Update crates 2023-01-06 00:04:45 +09:00
KitaitiMakoto 7c799e8abf Merge pull request 'Add changelogs' (#1130) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1130
2023-01-05 14:47:52 +00:00
Kitaiti Makoto d196e1dbd0 Add changelogs [skip ci] 2023-01-05 23:46:50 +09:00
Kitaiti Makoto 1679315322 Clippy 2023-01-05 05:08:53 +09:00
KitaitiMakoto dd3a5f4a5b Merge pull request 'Fixes #1128 Some ActivityPub related fixes' (#1129) from ap-fixes into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1129
2023-01-04 19:27:50 +00:00
Kitaiti Makoto 3580fb04fa Format 2023-01-05 04:14:25 +09:00
Kitaiti Makoto 699fdc30d9 Make preferredUsername of blogs valid 2023-01-05 04:13:52 +09:00
Kitaiti Makoto 704e9aa47f Make blogs.fqn valid 2023-01-05 04:13:40 +09:00
Kitaiti Makoto d741238ccb Revert "Rejectd illegal characters from blog name"
This reverts commit 9776374d17.
2023-01-05 03:11:20 +09:00
Kitaiti Makoto 9776374d17 Rejectd illegal characters from blog name 2023-01-05 02:49:48 +09:00
Kitaiti Makoto 2d10ddb9fa Clippy 2023-01-05 02:33:29 +09:00
Kitaiti Makoto e746a0b03f Add error log for invalid preferredUsername 2023-01-05 02:25:58 +09:00
Kitaiti Makoto 85cacf4239 Format 2023-01-05 02:23:50 +09:00
Kitaiti Makoto f138ae6ed9 Allow empty avatar for remote users 2023-01-05 02:20:57 +09:00
Kitaiti Makoto 399af4004a Build CustomPerson from source string at once 2023-01-05 02:20:25 +09:00
Kitaiti Makoto d36f13e984 Add test for deserializing CustomGroup 2023-01-05 02:19:10 +09:00
Kitaiti Makoto 9a3699160d Fix test name 2023-01-05 01:16:13 +09:00
KitaitiMakoto 4103e7513d Merge pull request 'Fix #966: Don't retrieve user info from blocked instances' (#1120) from block-instance-user into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1120
2023-01-04 16:11:34 +00:00
KitaitiMakoto ed9970b102 Merge pull request 'Percent encode to create ActivityPub URI' (#1127) from blog-slug into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1127
2023-01-04 09:55:34 +00:00
Kitaiti Makoto afa875366e Percent encode to create ActivityPub URI 2023-01-04 18:54:38 +09:00
KitaitiMakoto 9696f04c64 Merge pull request 'Fix #1125 Fix a bug about blog title and AP URL' (#1126) from blog-slug into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1126
2023-01-04 09:02:06 +00:00
Kitaiti Makoto 40e1a1fc2c Don't encode whole AP ID 2023-01-04 17:49:16 +09:00
Kitaiti Makoto ee1e553460 Fix a but about blog title and AP URL 2023-01-03 22:09:43 +09:00
KitaitiMakoto d20ce6dd0b Merge pull request 'Downgrade Docker image to run on CI' (#1124) from downgrade-ci-docker into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1124
2023-01-03 10:16:01 +00:00
Kitaiti Makoto 72f7909a42 Downgrade Docker image to run on CI 2023-01-03 19:15:32 +09:00
KitaitiMakoto 4e1fb64868 Merge pull request 'Update CI environment' (#1123) from clippy-on-ci into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1123
2023-01-03 09:30:54 +00:00
KitaitiMakoto 85c1bfa300 Merge pull request 'Fix #1121: Check email block list when email sign-up' (#1122) from block-on-email-signup into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1122
2023-01-03 09:11:20 +00:00
Kitaiti Makoto 172c78c41d Update Dockerfile for CI 2023-01-03 18:10:18 +09:00
Kitaiti Makoto 3b08d5b485 Update .dockerignore 2023-01-03 18:05:25 +09:00
Kitaiti Makoto 832479a706 Extract EmailSingup::ensure_email_not_blocked() 2023-01-03 17:49:15 +09:00
Kitaiti Makoto 3b3148fa6b Clippy 2023-01-03 17:42:26 +09:00
Kitaiti Makoto b38d55f486 Check email block list when email sign-up 2023-01-03 17:41:25 +09:00
Kitaiti Makoto 2804a490ed Don't retrieve user info from blocked instances 2023-01-03 03:23:50 +09:00
KitaitiMakoto 8c098def61 Merge pull request 'Update Rust' (#1119) from update-rust into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1119
2023-01-02 18:22:29 +00:00
Kitaiti Makoto e10ddb50c0 Ignore sum doc tests 2023-01-03 03:22:05 +09:00
Kitaiti Makoto 4df2c3e6f6 Clippy 2023-01-03 02:53:12 +09:00
Kitaiti Makoto 2f53fc78b6 Remove unnecessary trick 2023-01-03 02:41:51 +09:00
Kitaiti Makoto fded87654d Update Rust 2023-01-03 02:34:28 +09:00
Kitaiti Makoto 08cd777f81 Update pear_codegen 2023-01-03 02:34:22 +09:00
KitaitiMakoto 96b88353c5 Merge pull request 'docker-management' (#1118) from docker-management into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1118
2023-01-02 17:31:36 +00:00
Kitaiti Makoto 302026feb9 Update Rust on building Docker image [skip ci] 2023-01-03 02:30:43 +09:00
Kitaiti Makoto ba6d322da7 Set wasm-opt = false 2023-01-03 02:29:48 +09:00
KitaitiMakoto 488563e9c1 Merge pull request 'Update GitHub actions' (#1117) from docker-management into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1117
2023-01-02 16:13:28 +00:00
Kitaiti Makoto 130bb4c102 Update GitHub actions 2023-01-03 01:10:11 +09:00
KitaitiMakoto 9368aebe70 Merge pull request 'Fix #924 Update rocket_csrf' (#1116) from update-rocket_csrf into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1116
2023-01-02 15:29:34 +00:00
Kitaiti Makoto ca2843822e Install rocket_csrf 0.1.2 2023-01-03 00:27:05 +09:00
Kitaiti Makoto bd91b4a346 Update rocket_csrf to 0.1.2 2023-01-03 00:26:31 +09:00
KitaitiMakoto 63d2cf91e9 Merge pull request 'Update crates' (#1115) from update-rocket_csrf into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1115
2023-01-01 00:10:29 +00:00
Kitaiti Makoto 263cf9e04f Stick activitystreams for all packages 2023-01-01 08:52:08 +09:00
Kitaiti Makoto 22ebecba67 Stick serde for all packages 2023-01-01 08:11:14 +09:00
Kitaiti Makoto 903b48ed12 Stick activitystreams version 2023-01-01 07:54:08 +09:00
Kitaiti Makoto a550291c85 Stick activitystreams version 2023-01-01 07:41:54 +09:00
Kitaiti Makoto 47394fc620 Stick serde 2023-01-01 07:24:32 +09:00
Kitaiti Makoto b180089b1b Update ldap3 2023-01-01 07:10:31 +09:00
Kitaiti Makoto a275aa5965 Stick serde version 2023-01-01 07:10:09 +09:00
Kitaiti Makoto 87edb2486c Update openssl 2023-01-01 06:36:30 +09:00
Kitaiti Makoto 10617f3144 Install rocket_csrf 2023-01-01 06:23:25 +09:00
Kitaiti Makoto 6654ad28b7 Update rocket_csrf to 0.1.1 2023-01-01 06:23:18 +09:00
KitaitiMakoto 620726cc25 Merge pull request 'Update crates' (#1107) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1107
2022-06-16 09:12:07 +00:00
Kitaiti Makoto 0eef7c0b89 Merge remote-tracking branch 'github/dependabot/cargo/web-sys-0.3.58' into update-crates 2022-06-16 15:58:57 +09:00
dependabot[bot] 321e40ea3f Bump web-sys from 0.3.57 to 0.3.58
Bumps [web-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.57 to 0.3.58.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-15 19:27:28 +00:00
dependabot[bot] a218b4ea4f Bump reqwest from 0.11.10 to 0.11.11
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.10 to 0.11.11.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.10...v0.11.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-15 19:26:39 +00:00
KitaitiMakoto 9613ccd0c3 Merge pull request 'Fix Cargo.toml' (#1106) from fix-cargo into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1106
2022-06-15 04:52:21 +00:00
Kitaiti Makoto 9493c1ad06 Fix Cargo.toml 2022-06-15 13:51:30 +09:00
KitaitiMakoto e92ac1a13f Merge pull request 'Update crates' (#1105) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1105
2022-06-15 04:47:53 +00:00
Kitaiti Makoto 1517b4d91e Merge remote-tracking branch 'github/dependabot/cargo/js-sys-0.3.58' into update-crates 2022-06-15 13:46:55 +09:00
dependabot[bot] 38cc4c043d Bump js-sys from 0.3.57 to 0.3.58
Bumps [js-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.57 to 0.3.58.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-14 19:21:44 +00:00
dependabot[bot] 05c1d727dc Bump wasm-bindgen from 0.2.80 to 0.2.81
Bumps [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) from 0.2.80 to 0.2.81.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-14 19:21:16 +00:00
KitaitiMakoto 84645c7ed9 Merge pull request 'Update crates' (#1103) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1103
2022-06-12 11:54:55 +00:00
KitaitiMakoto 7c505bde7f Merge pull request 'Blog's header buttons margin fix in RTL' (#1093) from mskf1383/Plume:main into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1093
2022-06-12 11:30:08 +00:00
Kitaiti Makoto 9f543f1b6b Merge remote-tracking branch 'github/dependabot/cargo/flume-0.10.13' into update-crates 2022-06-12 20:26:19 +09:00
Kitaiti Makoto 0f7b882749 Merge remote-tracking branch 'github/dependabot/cargo/tracing-0.1.35' into update-crates 2022-06-12 20:25:10 +09:00
Kitaiti Makoto f9f4375a40 Merge remote-tracking branch 'github/dependabot/cargo/tokio-1.19.2' into update-crates 2022-06-12 20:24:58 +09:00
dependabot[bot] 12c2848cc7 Bump flume from 0.10.12 to 0.10.13
Bumps [flume](https://github.com/zesterer/flume) from 0.10.12 to 0.10.13.
- [Release notes](https://github.com/zesterer/flume/releases)
- [Changelog](https://github.com/zesterer/flume/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zesterer/flume/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-10 19:31:34 +00:00
dependabot[bot] 4cfb3e2494 Bump tracing from 0.1.34 to 0.1.35
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.34 to 0.1.35.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.34...tracing-0.1.35)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-08 19:30:58 +00:00
dependabot[bot] 090b0a6f0d Bump tokio from 1.18.2 to 1.19.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.18.2 to 1.19.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-06 19:47:34 +00:00
MohammadSaleh Kamyab 4502b77094 Failsafe 2022-06-06 05:39:21 +00:00
MohammadSaleh Kamyab 8f5a86206a Merge branch 'main' into main 2022-06-01 09:26:34 +00:00
dependabot[bot] 340157f80d Bump scheduled-thread-pool from 0.2.5 to 0.2.6
Bumps [scheduled-thread-pool](https://github.com/sfackler/scheduled-thread-pool) from 0.2.5 to 0.2.6.
- [Release notes](https://github.com/sfackler/scheduled-thread-pool/releases)
- [Commits](https://github.com/sfackler/scheduled-thread-pool/compare/v0.2.5...v0.2.6)

---
updated-dependencies:
- dependency-name: scheduled-thread-pool
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-30 19:35:18 +00:00
KitaitiMakoto 16b10695df Merge pull request 'Bump rocket_contrib from 0.4.10 to 0.4.11' (#1101) from rocket_contrib-0.4.11 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1101
2022-05-27 02:08:27 +00:00
Kitaiti Makoto b8eb631aa3 Merge remote-tracking branch 'origin/main' into rocket_contrib-0.4.11 2022-05-27 11:07:59 +09:00
KitaitiMakoto 5c9094fede Merge pull request 'Bump rocket from 0.4.10 to 0.4.11' (#1100) from rocket-0.4.11 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1100
2022-05-27 02:03:58 +00:00
dependabot[bot] 4e2ca515ce Bump rocket_contrib from 0.4.10 to 0.4.11
Bumps [rocket_contrib](https://github.com/SergioBenitez/Rocket) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/SergioBenitez/Rocket/releases)
- [Changelog](https://github.com/SergioBenitez/Rocket/blob/master/CHANGELOG.md)
- [Commits](https://github.com/SergioBenitez/Rocket/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-26 19:33:42 +00:00
dependabot[bot] eccfbd3fbc Bump rocket from 0.4.10 to 0.4.11
Bumps [rocket](https://github.com/SergioBenitez/Rocket) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/SergioBenitez/Rocket/releases)
- [Changelog](https://github.com/SergioBenitez/Rocket/blob/master/CHANGELOG.md)
- [Commits](https://github.com/SergioBenitez/Rocket/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-26 19:33:24 +00:00
KitaitiMakoto 8408342b5d Merge pull request 'Bump once_cell from 1.11.0 to 1.12.0' (#1099) from once_cell-1.12.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1099
2022-05-24 05:48:38 +00:00
dependabot[bot] c47921bb25 Bump once_cell from 1.11.0 to 1.12.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.11.0...v1.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 19:24:07 +00:00
KitaitiMakoto 03f470f04c Merge pull request 'Bump regex-syntax from 0.6.25 to 0.6.26' (#1096) from regex-syntax-0.6.26 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1096
2022-05-21 06:35:34 +00:00
KitaitiMakoto 5770c3b85b Merge branch 'main' into regex-syntax-0.6.26 2022-05-21 06:35:19 +00:00
KitaitiMakoto c92f46b2c9 Merge pull request 'Bump once_cell from 1.10.0 to 1.11.0' (#1095) from once_cell-1.11.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1095
2022-05-21 06:34:41 +00:00
dependabot[bot] 69eba69528 Bump regex-syntax from 0.6.25 to 0.6.26
Bumps [regex-syntax](https://github.com/rust-lang/regex) from 0.6.25 to 0.6.26.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-20 19:26:36 +00:00
dependabot[bot] c302d842e0 Bump once_cell from 1.10.0 to 1.11.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.10.0...v1.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-19 19:36:52 +00:00
KitaitiMakoto f660220495 Merge pull request 'Fix blog slug' (#1094) from fix-blog-slug into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1094
2022-05-19 17:29:34 +00:00
KitaitiMakoto 155df7bdf0 Merge branch 'main' into fix-blog-slug 2022-05-19 17:28:56 +00:00
Kitaiti Makoto 29055d1957 Follow clippy 2022-05-20 02:13:23 +09:00
Kitaiti Makoto d6ee49b880 Update Cargo.lock 2022-05-20 01:55:12 +09:00
Kitaiti Makoto 248ed265c4 Remove unsed heck from dependencies 2022-05-20 01:53:16 +09:00
Kitaiti Makoto a1f958ee7a Remove unused import 2022-05-20 01:52:26 +09:00
Kitaiti Makoto abf352b957 Remove unused function 2022-05-20 01:51:53 +09:00
Kitaiti Makoto 393f8e5e0c Use Blog::slug() to determine blog's slug 2022-05-20 01:51:16 +09:00
Kitaiti Makoto 4dfe300ee3 Define Blog::slug() 2022-05-20 01:50:51 +09:00
Kitaiti Makoto 65829094c9 Add test for blog slug validation 2022-05-20 01:40:51 +09:00
MohammadSaleh Kamyab d5c3e6d6f0 Blog's header buttons margin fix in RTL 2022-05-19 15:47:56 +00:00
KitaitiMakoto d702dd2fae Merge pull request 'Bidirectional support for user page header' (#1092) from mskf1383/Plume:main into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1092
2022-05-19 15:38:36 +00:00
MohammadSaleh Kamyab 0d855823c9 Bidirectional support for user page header 2022-05-19 15:26:44 +00:00
KitaitiMakoto 2dd33769d4 Merge pull request 'Bump rsass from 0.24.0 to 0.25.0' (#1091) from rsass-0.25.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1091
2022-05-19 01:29:12 +00:00
dependabot[bot] 4ea9f6ecf1 Bump rsass from 0.24.0 to 0.25.0
Bumps [rsass](https://github.com/kaj/rsass) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.24.0...v0.25.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-18 19:31:26 +00:00
KitaitiMakoto 3b0b6c4b0b Merge pull request 'Fix .venv path in buildenv' (#1090) from venv-path into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1090
2022-05-17 17:39:10 +00:00
Kitaiti Makoto 4328fad5a3 Don't load venv 2022-05-18 02:15:15 +09:00
Kitaiti Makoto b26822c045 Update buildenv image 2022-05-16 12:59:05 +09:00
Kitaiti Makoto f372282b04 Use apt package for setuptools instead of pyenv 2022-05-16 12:20:06 +09:00
Kitaiti Makoto 145253ccbf Fix .venv path in buildenv 2022-05-15 11:41:50 +09:00
KitaitiMakoto 485223a3dd Merge pull request 'Add fmt and clippy on CI' (#1089) from add-toolchain into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1089
2022-05-15 02:22:29 +00:00
Kitaiti Makoto 7f75fa74e7 Add fmt and clippy on CI 2022-05-15 11:21:07 +09:00
KitaitiMakoto 821fce1903 Merge pull request 'Use rust-toolchain in buildenv' (#1088) from buildenv-rust-toolchain into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1088
2022-05-15 02:16:39 +00:00
Kitaiti Makoto ce484de61e Bump buildenv image 2022-05-15 11:15:05 +09:00
Kitaiti Makoto 35fb57718d Add rust-toolchain into buildenv 2022-05-15 06:46:18 +09:00
KitaitiMakoto 35d12d7cae Merge pull request 'Activate venv on integration test' (#1087) from fix-test-env into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1087
2022-05-14 21:34:37 +00:00
Kitaiti Makoto 846154efe1 Activate venv on integration test 2022-05-15 06:34:09 +09:00
KitaitiMakoto 1ec7acbdfe Merge pull request 'Update Crowdin enviroment' (#1086) from update-crowdin into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1086
2022-05-14 21:14:15 +00:00
Kitaiti Makoto e384fdfcff Update buildenv image to v0.5.0 2022-05-15 05:56:31 +09:00
Kitaiti Makoto ed58e44d2e Use Python 3 to install Selenium 2022-05-15 05:45:25 +09:00
Kitaiti Makoto f151dee339 Don't strip in buildenv 2022-05-15 05:13:09 +09:00
Kitaiti Makoto 61f25941e8 Install crowdin CLI using apt in buildenv 2022-05-15 05:13:09 +09:00
Kitaiti Makoto 0628a14be6 Use Rust image for buildenv 2022-05-15 05:13:03 +09:00
KitaitiMakoto b46ae83377 Merge pull request 'Change default branch to main' (#1085) from default-branch into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1085
2022-05-14 18:57:21 +00:00
Kitaiti Makoto 70bc7f8edf Change default branch to main 2022-05-15 03:55:33 +09:00
KitaitiMakoto aff481b947 Merge pull request 'Add 'My feed' to i18n timeline name' (#1084) from myfeed-translation into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1084
2022-05-14 18:35:35 +00:00
Kitaiti Makoto 29ef73d307 Add 'My feed' to i18n timeline name 2022-05-15 03:32:45 +09:00
KitaitiMakoto db205d0d9d Merge pull request 'Bump ldap3 from 0.10.4 to 0.10.5' (#1083) from ldap3-0.10.5 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1083
2022-05-12 23:19:47 +00:00
dependabot[bot] 57ab7edf23 Bump ldap3 from 0.10.4 to 0.10.5
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.10.4 to 0.10.5.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.10.4...v0.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-12 19:26:54 +00:00
KitaitiMakoto e4bd9d65cf Merge pull request 'Bump diesel-derive-newtype from 0.1.2 to 1.0.0' (#1081) from diesel-derive-newtype-1.0.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1081
2022-05-11 21:56:27 +00:00
KitaitiMakoto 17e4ddb32d Merge branch 'main' into diesel-derive-newtype-1.0.0 2022-05-11 21:56:09 +00:00
KitaitiMakoto 4fd85b30f1 Merge pull request '(cargo-release) version v0.7.3-dev' (#1080) from v0.7.3-dev into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1080
2022-05-11 21:33:43 +00:00
Kitaiti Makoto 6148f29c66 (cargo-release) version {{version}} 2022-05-12 06:30:25 +09:00
KitaitiMakoto 1a3fad2d6a Merge pull request 'Release v0.7.2' (#1079) from v0.7.2 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1079
2022-05-11 19:50:40 +00:00
Kitaiti Makoto 0945d3bc53 Set release to false for sub crates [skip ci] 2022-05-12 04:35:05 +09:00
Kitaiti Makoto 9a824f06c3 (cargo-release) version {{version}} 2022-05-12 04:29:46 +09:00
Kitaiti Makoto eec09d79fe Fix release.toml 2022-05-12 04:27:48 +09:00
dependabot[bot] 7f63d2a129 Bump diesel-derive-newtype from 0.1.2 to 1.0.0
Bumps [diesel-derive-newtype](https://github.com/quodlibetor/diesel-derive-newtype) from 0.1.2 to 1.0.0.
- [Release notes](https://github.com/quodlibetor/diesel-derive-newtype/releases)
- [Changelog](https://github.com/quodlibetor/diesel-derive-newtype/blob/main/CHANGELOG.md)
- [Commits](https://github.com/quodlibetor/diesel-derive-newtype/commits)

---
updated-dependencies:
- dependency-name: diesel-derive-newtype
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-11 19:24:14 +00:00
Kitaiti Makoto 79b639c3e6 Update PO files 2022-05-11 03:29:33 +09:00
Kitaiti Makoto efef208f53 Update translations 2022-05-11 03:26:01 +09:00
KitaitiMakoto 27e0f755f6 Merge pull request 'Bump whatlang from 0.15.0 to 0.16.0' (#1078) from whatlang-0.16.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1078
2022-05-10 17:53:35 +00:00
KitaitiMakoto a9d7aae5d6 Merge branch 'main' into whatlang-0.16.0 2022-05-10 17:52:46 +00:00
KitaitiMakoto 42e584a363 Merge pull request 'Add blank line' (#1077) from tiny-change into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1077
2022-05-10 17:50:07 +00:00
Kitaiti Makoto 8c37ea3ec3 Add blank line 2022-05-11 02:35:30 +09:00
dependabot[bot] 3c14fa0058 Bump whatlang from 0.15.0 to 0.16.0
Bumps [whatlang](https://github.com/greyblake/whatlang-rs) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/greyblake/whatlang-rs/releases)
- [Changelog](https://github.com/greyblake/whatlang-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/greyblake/whatlang-rs/compare/v0.15.0...v0.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-11 02:33:23 +09:00
dependabot[bot] ab94cca210 Bump tokio from 1.18.1 to 1.18.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.18.1 to 1.18.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.18.1...tokio-1.18.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 19:31:15 +00:00
Kitaiti Makoto ea62388985 Update PO files 2022-05-09 23:34:32 +09:00
KitaitiMakoto a9219efee4 Merge pull request 'Move to action area after liking/boosting/commenting' (#1074) from action-id into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1074
2022-05-08 10:24:15 +00:00
Kitaiti Makoto 776ed058c7 [skip ci]Add changelog 2022-05-08 18:53:57 +09:00
Kitaiti Makoto aa3e196b8f Make comment content required 2022-05-08 18:51:09 +09:00
Kitaiti Makoto 52cb7270a9 Set id attributes to action forms in post details page 2022-05-08 18:44:42 +09:00
KitaitiMakoto 66376afb36 Merge pull request 'Upgrade activitystreams to 0.7, again' (#1022) from ap07 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1022
2022-05-08 09:14:53 +00:00
Kitaiti Makoto 96860be1be Fix Follow::accept_follow() 2022-05-08 18:00:42 +09:00
KitaitiMakoto 3bf61efc34 Merge pull request '[skip ci]Update changelog' (#1073) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1073
2022-05-07 16:45:23 +00:00
KitaitiMakoto d95549f58b Merge pull request 'Move local feed before federated feed for non-logged-in users' (#1072) from timeline-order into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1072
2022-05-07 16:35:28 +00:00
Kitaiti Makoto bf24e4878a Merge remote-tracking branch 'origin/main' into ap07 2022-05-07 13:04:47 +09:00
Kitaiti Makoto 9ae231fcef [skip ci]Update changelog 2022-05-07 12:58:47 +09:00
KitaitiMakoto c32acb2fcf Merge pull request 'Sleep between broadcasting' (#1071) from sleep-broadcasting into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1071
2022-05-07 03:50:42 +00:00
Kitaiti Makoto 770c77ee81 Move local feed before federated feed for non-logged-in users 2022-05-07 12:48:34 +09:00
KitaitiMakoto aa3e4d7cf8 Merge branch 'main' into sleep-broadcasting 2022-05-07 03:27:53 +00:00
KitaitiMakoto 156a875f02 Merge pull request 'Move local timeline before federated timeline' (#1070) from timeline-order into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1070
2022-05-07 03:25:01 +00:00
Kitaiti Makoto cfed02bbcf Merge remote-tracking branch 'origin/main' into ap07 2022-05-07 11:15:04 +09:00
Kitaiti Makoto 4d3db9af73 Sleep between broadcasting 2022-05-07 04:50:58 +09:00
Kitaiti Makoto c1c606bc86 [skip ci]Add changelog about timeline order change 2022-05-07 04:46:28 +09:00
Kitaiti Makoto f401949037 Move local timeline before federated timeline 2022-05-07 04:43:00 +09:00
Kitaiti Makoto e8dc0942e5 Merge remote-tracking branch 'origin/main' into ap07 2022-05-06 12:38:47 +09:00
Kitaiti Makoto 9fbafd8e79 Fix Follow object in accepting follow 2022-05-06 12:06:37 +09:00
KitaitiMakoto b9ea83a602 Merge pull request 'More personal timelines' (#1069) from timeline-order into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1069
2022-05-05 19:11:47 +00:00
KitaitiMakoto 2ada5a83af Merge branch 'main' into timeline-order 2022-05-05 19:11:30 +00:00
KitaitiMakoto ec25599d1f Merge pull request 'Fixes #949 Fix time out error on broadcasting' (#1068) from fix-timeout into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1068
2022-05-05 17:15:28 +00:00
KitaitiMakoto 57551610e2 Merge pull request 'Update CircleCI image' (#1066) from update-circleci-image into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1066
2022-05-05 11:29:37 +00:00
Kitaiti Makoto 8948b7acc1 Center timeline tabs 2022-05-05 19:11:23 +09:00
Kitaiti Makoto ccf7ff2bc9 Remove Latest articles from timeline tabs 2022-05-05 19:05:54 +09:00
Kitaiti Makoto 39de967141 Show first timeline at home 2022-05-05 19:03:33 +09:00
Kitaiti Makoto 118cfd7166 Replace hard tabs with soft tabs 2022-05-05 18:14:55 +09:00
Kitaiti Makoto c2fd4ab3a5 Merge remote-tracking branch 'origin/main' into fix-timeout 2022-05-05 17:14:52 +09:00
Kitaiti Makoto 70b5bee00f Move My feed first in timelines 2022-05-05 16:48:51 +09:00
Kitaiti Makoto de605deb1e Don't unwrap 2022-05-05 16:35:03 +09:00
Kitaiti Makoto 116974f811 Add comment about broadcast capacity 2022-05-05 16:29:52 +09:00
Kitaiti Makoto c57f36ccca Merge branch 'fix-timeout' into ap07 2022-05-05 15:50:51 +09:00
Kitaiti Makoto 9def0355aa Reduce broadcast request connections 2022-05-05 15:50:44 +09:00
Kitaiti Makoto 4e833c2061 Follow clippy 2022-05-05 13:14:11 +09:00
Kitaiti Makoto 5871ed7301 Merge branch 'fix-timeout' into ap07 2022-05-05 13:12:04 +09:00
Kitaiti Makoto 97632fdbfe Broadcast asynchronously 2022-05-05 13:03:41 +09:00
Kitaiti Makoto 1f8da7e63d Install futures 2022-05-05 12:41:16 +09:00
Kitaiti Makoto 76ca7c1462 Add futures to plume-common's dependencies 2022-05-05 12:40:37 +09:00
Kitaiti Makoto f06f444a13 Update CircleCI image
See https://discuss.circleci.com/t/legacy-convenience-image-deprecation/41034
2022-05-05 11:32:26 +09:00
Kitaiti Makoto 10dfecf45c Merge remote-tracking branch 'origin/fix-timeout' into ap07 2022-05-05 10:42:03 +09:00
Kitaiti Makoto a7b899817a Run HTTP request in broadcast() on tokio runtime 2022-05-05 09:04:54 +09:00
Kitaiti Makoto e0258003b9 Install tokio and flume 2022-05-05 09:04:34 +09:00
Kitaiti Makoto 2326eb77cd Add tokio to plume-common's dependencies 2022-05-05 08:42:26 +09:00
Kitaiti Makoto 504d41d887 Add flume to plume-common's dependencies 2022-05-05 08:38:57 +09:00
KitaitiMakoto 5a7d5e8099 Merge pull request 'Bump serde_json from 1.0.80 to 1.0.81' (#1064) from serde_json-1.0.81 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1064
2022-05-04 19:56:28 +00:00
KitaitiMakoto 74a1daac8c Merge branch 'main' into serde_json-1.0.81 2022-05-04 19:56:18 +00:00
KitaitiMakoto 1f855601ea Merge pull request 'Bump openssl from 0.10.38 to 0.10.40' (#1063) from openssl-0.10.40 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1063
2022-05-04 19:55:39 +00:00
Kitaiti Makoto f22c4d5c78 Await in consumer 2022-05-05 04:51:42 +09:00
Kitaiti Makoto ce4b216722 Broadcast asynchronously 2022-05-05 04:34:04 +09:00
Kitaiti Makoto 9016995d92 Install tokio 2022-05-05 04:33:52 +09:00
dependabot[bot] 853a1db028 Bump serde_json from 1.0.80 to 1.0.81
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.80 to 1.0.81.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.80...v1.0.81)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-04 19:25:50 +00:00
dependabot[bot] 712ee30a1f Bump openssl from 0.10.38 to 0.10.40
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.38 to 0.10.40.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.38...openssl-v0.10.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-04 19:25:29 +00:00
Kitaiti Makoto 9e5f9255d1 Add tokio to plume-common's dependencies 2022-05-05 03:34:11 +09:00
Kitaiti Makoto 2e35441483 Follow reqwest change 2022-05-05 01:21:25 +09:00
Kitaiti Makoto 5c74f598d8 Update Cargo.lock 2022-05-05 01:21:12 +09:00
Kitaiti Makoto 5d711dc47c Upgrade reqwest to 0.11 2022-05-05 01:20:18 +09:00
KitaitiMakoto 9ae3057106 Merge pull request 'Fixes #1061 Render 404 when page not found' (#1062) from render-404 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1062
2022-05-04 12:23:04 +00:00
Kitaiti Makoto b7ea154e51 Render 404 when page not found 2022-05-04 21:21:58 +09:00
Kitaiti Makoto 692e6b1c82 Uninstall tokio 2022-05-04 21:13:30 +09:00
Kitaiti Makoto 528f1bac48 Remove tokio from dependencies 2022-05-04 21:12:52 +09:00
Kitaiti Makoto 35aa2374c4 Execute broadcast synchronously 2022-05-04 21:12:35 +09:00
Kitaiti Makoto 3eb7662aef Log inbox URI when broadcast() failed 2022-05-04 21:04:49 +09:00
Kitaiti Makoto de4fcaee93 Merge remote-tracking branch 'origin/main' into ap07 2022-05-04 05:04:57 +09:00
KitaitiMakoto 812fd3d956 Merge pull request 'Reuse reqwest client on broadcasting' (#1059) from fix-timeout into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1059
2022-05-03 19:59:05 +00:00
KitaitiMakoto 5d3b480790 Merge branch 'main' into fix-timeout 2022-05-03 19:58:38 +00:00
KitaitiMakoto 2f1801acae Merge pull request 'Bump validator from 0.14.0 to 0.15.0' (#1060) from validator-0.15.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1060
2022-05-03 19:57:56 +00:00
Kitaiti Makoto 0404528908 Remove unnecessary clone of config 2022-05-04 04:40:16 +09:00
Kitaiti Makoto 4529b929d8 [skip ci]Add changelog 2022-05-04 04:26:47 +09:00
dependabot[bot] 889decc720 Bump validator from 0.14.0 to 0.15.0
Bumps [validator](https://github.com/Keats/validator) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/Keats/validator/releases)
- [Changelog](https://github.com/Keats/validator/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Keats/validator/compare/v0.14.0...v0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-03 19:24:10 +00:00
Kitaiti Makoto db0f1a3c46 Reuse reqwest client on broadcasting
See https://users.rust-lang.org/t/reqwest-http-client-fails-when-too-much-concurrency/55644/2
2022-05-04 04:23:10 +09:00
KitaitiMakoto ef57ef91f0 Merge pull request 'Fixes #1051 Fix accept header' (#1058) from activitystreams-content-type into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1058
2022-05-03 17:21:25 +00:00
Kitaiti Makoto 073b72c9ed Add more fixes 2022-05-04 02:01:58 +09:00
Kitaiti Makoto 45a6744d4d [skip ci]Add changelog 2022-05-04 01:58:44 +09:00
Kitaiti Makoto 4d19861a25 Fix accept header 2022-05-04 01:56:49 +09:00
Kitaiti Makoto f5906cacf3 Restore missing logic for Media 2022-05-03 22:25:43 +09:00
Kitaiti Makoto 03ba77a577 Restore filter 2022-05-03 12:36:31 +09:00
Kitaiti Makoto 0fc7372781 Restore order of decl of boundary of broadcast() 2022-05-03 12:34:56 +09:00
Kitaiti Makoto 6c2846980a Merge remote-tracking branch 'origin/main' into ap07 2022-05-03 12:24:04 +09:00
Kitaiti Makoto 0685c59bf3 Add changelog 2022-05-03 12:18:52 +09:00
KitaitiMakoto 5f629195f8 Merge pull request 'Bump whatlang from 0.13.0 to 0.15.0' (#1057) from whatlang-0.15.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1057
2022-05-03 03:08:09 +00:00
KitaitiMakoto 13eeedb620 Merge branch 'main' into whatlang-0.15.0 2022-05-03 02:49:51 +00:00
KitaitiMakoto a076c132ca Merge pull request 'Bump serde from 1.0.136 to 1.0.137' (#1056) from serde-1.0.137 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1056
2022-05-03 02:49:09 +00:00
KitaitiMakoto a76e0dfe5b Merge branch 'main' into serde-1.0.137 2022-05-03 02:27:46 +00:00
KitaitiMakoto 8faac20977 Merge pull request 'Bump serde_json from 1.0.79 to 1.0.80' (#1055) from serde_json-1.0.80 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1055
2022-05-03 02:22:54 +00:00
Kitaiti Makoto b04edfa05e Follow clippy 2022-05-03 10:53:16 +09:00
Kitaiti Makoto 1f62bf27f8 Fix nest of source property for Post 2022-05-03 10:47:21 +09:00
Kitaiti Makoto 1e0d1fb97a Add test for CustomGroup 2022-05-03 10:16:57 +09:00
Kitaiti Makoto 19d30c12d1 Parse source property properly 2022-05-03 10:04:22 +09:00
Kitaiti Makoto 384474930c Add test for Create activity with licensed article 2022-05-03 08:24:22 +09:00
dependabot[bot] 4cb64e0a8c Bump whatlang from 0.13.0 to 0.15.0
Bumps [whatlang](https://github.com/greyblake/whatlang-rs) from 0.13.0 to 0.15.0.
- [Release notes](https://github.com/greyblake/whatlang-rs/releases)
- [Changelog](https://github.com/greyblake/whatlang-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/greyblake/whatlang-rs/compare/v0.13.0...v0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:35:35 +00:00
dependabot[bot] cd2a2df48d Bump serde from 1.0.136 to 1.0.137
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.136 to 1.0.137.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.136...v1.0.137)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:35:19 +00:00
dependabot[bot] cbf960500b Bump serde_json from 1.0.79 to 1.0.80
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.79 to 1.0.80.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.79...v1.0.80)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:35:06 +00:00
Kitaiti Makoto 3d434f1923 Remove old activitypub related crates 2022-05-03 03:05:49 +09:00
Kitaiti Makoto 52022fb597 Remove activitypub crate from plume-models 2022-05-03 02:54:08 +09:00
Kitaiti Makoto d96940c848 Don't implement activitypub::Link for Id 2022-05-03 02:51:36 +09:00
Kitaiti Makoto b17884681d Implement ap_followers using activitystreams 2022-05-03 02:50:14 +09:00
Kitaiti Makoto 78a001ac89 Remove trailing 07 in routes/posts.rs 2022-05-03 02:14:23 +09:00
Kitaiti Makoto 9b04fb96e6 Remote trailing 07 in inbox.rs 2022-05-03 02:12:59 +09:00
Kitaiti Makoto d23002b817 Remove trailing 07 from method name 2022-05-03 02:11:46 +09:00
Kitaiti Makoto 6282b98b03 Fix doc test 2022-05-03 02:05:46 +09:00
Kitaiti Makoto d75600ba14 Remove trailing 07 in activity_pub/inbox.rs 2022-05-03 01:32:47 +09:00
Kitaiti Makoto e6ea302319 Remove activitypub crate from Inbox test 2022-05-03 01:31:39 +09:00
Kitaiti Makoto df005a28f8 Rename: activity07() -> activity() 2022-05-03 01:26:15 +09:00
Kitaiti Makoto 15134eed60 Rename: get_sender07() -> get_sender() 2022-05-03 01:24:22 +09:00
Kitaiti Makoto 7dd56a71e3 Rename: from_db07() -> from_db() 2022-05-03 01:23:37 +09:00
Kitaiti Makoto 06d2f68ecd Rename: from_activity07() -> from_activity() 2022-05-03 01:22:55 +09:00
Kitaiti Makoto 9a640b3438 Rename: deref07() -> deref() 2022-05-03 01:22:02 +09:00
Kitaiti Makoto 9791607793 Rename: with07() -> with() 2022-05-03 01:20:40 +09:00
Kitaiti Makoto ccd3c8a3f2 Don't implement activitypub's Object for Source 2022-05-03 01:17:27 +09:00
Kitaiti Makoto 6bbadc78b0 Rename: Licensed07 -> Licensed 2022-05-03 01:15:13 +09:00
Kitaiti Makoto e1673787b4 Remove unused Licensed struct 2022-05-03 01:14:29 +09:00
Kitaiti Makoto 1dd176dd80 Rename: broadcast07() -> broadcast() 2022-05-03 01:12:39 +09:00
Kitaiti Makoto 5c59687cb8 Remove unused broadcast() 2022-05-03 01:12:02 +09:00
Kitaiti Makoto a24e3c46e6 Remove trailing 07 in posts.rs 2022-05-03 01:07:52 +09:00
Kitaiti Makoto 267fecba66 Remove unsed posts::LicensedArticle 2022-05-03 01:03:54 +09:00
Kitaiti Makoto fc99d2b7a0 Remove trailing 07 in remote_fetch_actor.rs 2022-05-03 00:57:19 +09:00
Kitaiti Makoto 1b32fa1e34 Remove unused Media::from_activity() 2022-05-03 00:54:37 +09:00
Kitaiti Makoto ce42524273 Remote trailng 07 from Note in comments.rs 2022-05-03 00:50:43 +09:00
Kitaiti Makoto 595fa05660 Use Follow::to_activity07() instead of to_activity() 2022-05-03 00:47:46 +09:00
Kitaiti Makoto f8a0dff526 Remove unused Follow::build_accept() 2022-05-03 00:45:22 +09:00
Kitaiti Makoto 06d216c7ed Remove unused Follow::accept_follow() 2022-05-03 00:43:47 +09:00
Kitaiti Makoto f44bca30f4 Use Follow::build_undo07() instead of build_undo() 2022-05-03 00:41:05 +09:00
Kitaiti Makoto e4180b3b38 Rename: ApSignature07 -> ApSignature 2022-05-03 00:29:44 +09:00
Kitaiti Makoto 992a482b96 Remove unused ApSignature type 2022-05-03 00:26:37 +09:00
Kitaiti Makoto 0ad845e0f7 Remove unused User::fetch_outbox() 2022-05-03 00:22:22 +09:00
Kitaiti Makoto ee97213c90 Remove trailing 07 from import of OrderedCollectionPage 2022-05-03 00:18:42 +09:00
Kitaiti Makoto 6d919da049 Remove duplicate import 2022-05-03 00:17:19 +09:00
Kitaiti Makoto 6c615d01ad Remove users::CustomPerson 2022-05-03 00:15:46 +09:00
Kitaiti Makoto f8870af9fe Remove trailing 07 from Hashtag 2022-05-03 00:10:51 +09:00
Kitaiti Makoto 2fe2505a01 Remove unused Hashtag 2022-05-03 00:09:15 +09:00
Kitaiti Makoto e41fa353e4 Use User::to_activity07() instead of to_activity() 2022-05-03 00:06:18 +09:00
Kitaiti Makoto effdc44943 Use User::delete_activity07() instead of delete_activity() 2022-05-03 00:03:11 +09:00
Kitaiti Makoto fd341bdb22 Use User::outbox07() instead of outbox() 2022-05-02 23:59:38 +09:00
Kitaiti Makoto 68c794c54b Use User::outbox_page07() instead of outbox_page() 2022-05-02 23:55:18 +09:00
Kitaiti Makoto 7b3b00be23 Remove unused Tag::from_activity() and to_activity() 2022-05-02 23:51:32 +09:00
Kitaiti Makoto 41ccacc5d3 Remove unused Mention::from_activity() 2022-05-02 23:47:36 +09:00
Kitaiti Makoto 4ef9350ce7 Remove unused Mention::to_activity() 2022-05-02 23:45:06 +09:00
Kitaiti Makoto 5d08ff6c3b Use Mention::build_activity07() instead of build_activity() 2022-05-02 23:43:24 +09:00
Kitaiti Makoto 01dca62ce5 Rename: Like07 -> LikeAct 2022-05-02 23:38:32 +09:00
Kitaiti Makoto 6ab1ecd57b Use Like::to_activity07() instead of to_activity() 2022-05-02 23:37:58 +09:00
Kitaiti Makoto b13444895f Use Like::build_undo07() instead of build_undo() 2022-05-02 23:34:19 +09:00
Kitaiti Makoto 771c157fe5 Use Comment::to_activity07() instead of to_activity() 2022-05-02 23:30:44 +09:00
Kitaiti Makoto b5e1076b0e Use Comment::build_delete07() instead of build_delete() 2022-05-02 23:26:25 +09:00
Kitaiti Makoto 6cc43c2420 Use Comment::create_activity07() instead of create_activity() 2022-05-02 23:23:28 +09:00
Kitaiti Makoto f365041a45 Use Reshare::to_activity07() instead of to_activity() 2022-05-02 23:19:29 +09:00
Kitaiti Makoto ae9c9262f7 Use Reshare::build_undo07() instead of build_undo() 2022-05-02 23:16:55 +09:00
Kitaiti Makoto 40ce515e6c Don't rename activitystreams' tokens to 07 2022-05-02 23:11:48 +09:00
Kitaiti Makoto bc96af7f5f Remove unused blogs::CustomGroup 2022-05-02 23:08:41 +09:00
Kitaiti Makoto 811c20c8fb Use Blog::to_activity07() instead of to_activity() 2022-05-02 23:07:05 +09:00
Kitaiti Makoto 4b4c22cf8a Use Blog::outbox_page07() instead of outbox_page() 2022-05-02 23:01:26 +09:00
Kitaiti Makoto 0524b0b153 Add Blog::outbox_page07() 2022-05-02 22:56:54 +09:00
Kitaiti Makoto cd6c57b9c5 Use Blog::outbox07() instead of outbox() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto f608f7a4d6 Install activitystreams 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 803680186b Add Blog::outbox07() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 68a01d5f9b Add activitystreams to Plume's dependencies 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 5b3a472b66 Use Post::to_activity07() instead of to_activity() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 2a85f775e9 Use Post::build_delete07() instead of build_delete() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto a958300a58 Use Post::update_hashtags07() instead of update_hashtags() 2022-05-02 22:55:03 +09:00
Kitaiti Makoto a8be31b177 Use Post::update_tags07() instead of update_tags() 2022-05-02 22:26:59 +09:00
Kitaiti Makoto 6cd68ab8b0 Use Post::update_activity07() instead of update_activity() 2022-05-02 22:23:12 +09:00
Kitaiti Makoto a589435f4f Use Post::create_activity07() instead of create_activity() 2022-05-02 22:19:00 +09:00
Kitaiti Makoto 39b49c707e Use Post::update_mentions07() instead of update_mentions() 2022-05-02 21:49:00 +09:00
Kitaiti Makoto c4bb1f771b Add test for Tag::from_activity07() 2022-05-02 21:00:44 +09:00
Kitaiti Makoto 28440271bb Rename: FromId::from_id07 -> from_id 2022-05-02 19:24:36 +09:00
Kitaiti Makoto 0ab7774e29 Rename: AsObject07 -> AsObject 2022-05-02 17:43:03 +09:00
Kitaiti Makoto 33afe9111e Remove AsObject 2022-05-02 17:38:08 +09:00
Kitaiti Makoto d8a2e1925f Rename FromId07 -> FromId 2022-05-02 16:07:08 +09:00
Kitaiti Makoto 2804f44a06 Rmove FromId 2022-05-02 12:58:01 +09:00
Kitaiti Makoto 2165c286ae Remove with() 2022-05-01 19:49:12 +09:00
Kitaiti Makoto 8cbf410faf Remove execute permission from plume-common/src/lib.rs 2022-05-01 19:15:16 +09:00
Kitaiti Makoto c521a81373 Make test follow LicensedArticle change 2022-05-01 19:13:38 +09:00
Kitaiti Makoto 7ade0550c9 Remove unused import 2022-05-01 18:54:11 +09:00
Kitaiti Makoto 41bc2d6949 Make LicensedArticle's license fieald optional 2022-05-01 18:53:51 +09:00
Kitaiti Makoto de6e9c0e2e Fix Post::from_activity07() 2022-05-01 13:00:04 +09:00
Kitaiti Makoto 38ebc9ea41 Modify test data for Post 2022-05-01 12:59:48 +09:00
Kitaiti Makoto 8f976be998 Implement AsObject07 for PostUpdate 2022-05-01 09:56:16 +09:00
Kitaiti Makoto e5a2850105 Implement FromId07 for PostUpdate 2022-05-01 09:56:03 +09:00
KitaitiMakoto 85727c6d4c Merge pull request 'Bump ldap3 from 0.10.3 to 0.10.4' (#1054) from ldap3-0.10.4 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1054
2022-04-30 23:35:00 +00:00
KitaitiMakoto 87247a23b3 Merge branch 'main' into ldap3-0.10.4 2022-04-30 23:08:53 +00:00
KitaitiMakoto 61785364e3 Merge pull request 'Bump ctrlc from 3.2.1 to 3.2.2' (#1053) from ctrlc-3.2.2 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1053
2022-04-30 23:08:30 +00:00
Kitaiti Makoto 76f688c967 Replace some Inbox::with with with07 2022-05-01 07:45:42 +09:00
Kitaiti Makoto 05df3b89a1 Fix Follow::activity07() 2022-05-01 07:45:05 +09:00
Kitaiti Makoto 4e42a34337 Replace some with() with with07() 2022-05-01 06:05:20 +09:00
Kitaiti Makoto 62372201e0 Fix inbox::tests::create_post() 2022-05-01 06:04:58 +09:00
Kitaiti Makoto 036913a828 Use id() for reply_tos 2022-05-01 04:45:56 +09:00
dependabot[bot] b2a889b9e4 Bump ldap3 from 0.10.3 to 0.10.4
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.10.3 to 0.10.4.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.10.3...v0.10.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-26 19:28:07 +00:00
dependabot[bot] fae8338772 Bump ctrlc from 3.2.1 to 3.2.2
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.2.1...3.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 19:25:52 +00:00
Kitaiti Makoto 79b5d9a690 Replace Inbox::with() with with07() 2022-04-24 07:25:31 +09:00
Kitaiti Makoto 3e54d10981 Implement AsObject07<User, Undo07, &DbConn> for Like 2022-04-24 07:11:46 +09:00
Kitaiti Makoto a1c3bfb646 Implement FromId07 for Like 2022-04-24 07:10:56 +09:00
Kitaiti Makoto b2528c21ff Implement AsObject07<User, Like07, &DbConn> for Post 2022-04-24 07:04:30 +09:00
Kitaiti Makoto fcc9e1d81b Implement Like::build_undo07() 2022-04-24 07:03:13 +09:00
Kitaiti Makoto 3093f713ef Add test for Like::build_undo07() 2022-04-24 07:03:06 +09:00
Kitaiti Makoto 4ea29d29a0 Implement Like::to_activity07() 2022-04-24 06:58:33 +09:00
Kitaiti Makoto 6b8d90d8b6 Add test for Like::to_activity07() 2022-04-24 06:58:17 +09:00
Kitaiti Makoto bd3e6a5a91 Replace some Inbox::with with with07 2022-04-24 06:54:07 +09:00
Kitaiti Makoto 46f4676efb Implement Reshare::build_undo07() 2022-04-24 06:48:31 +09:00
Kitaiti Makoto c814ac5681 Add test for Reshare::build_undo07() 2022-04-24 06:48:16 +09:00
Kitaiti Makoto 0887399048 Implement AsObject07<User, Undo07, &DbConn> for Reshare 2022-04-24 06:42:48 +09:00
Kitaiti Makoto f2a2bf2b23 Implement FromId07 for Reshare 2022-04-24 06:41:21 +09:00
Kitaiti Makoto e2702a187b Implement AsObject07<User, Announce07, &DbConn> for Post 2022-04-24 06:35:50 +09:00
Kitaiti Makoto d78a57ce47 Implement Reshare::to_ativity07() 2022-04-24 06:34:00 +09:00
Kitaiti Makoto 10acbdd41f Add test for Reshare::to_activity07() 2022-04-24 06:33:39 +09:00
Kitaiti Makoto 73009818f2 Implement AsObject07<User, Undo07, &DbConn> for Follow 2022-04-24 06:11:54 +09:00
Kitaiti Makoto fb5027becd Implement FromId07 for Follow 2022-04-24 06:09:00 +09:00
Kitaiti Makoto 86609b51fa Implement AsObject07<User, FollowAct07, &DbConn> for User 2022-04-24 05:51:24 +09:00
Kitaiti Makoto 44799e94fd Implement Follow::accept_follow07() 2022-04-24 05:50:45 +09:00
Kitaiti Makoto f14c307786 Remove unused type parameter from broadcast07() 2022-04-24 05:47:11 +09:00
Kitaiti Makoto 174624f5c1 Implement Follow::build_undo07() 2022-04-24 03:45:22 +09:00
Kitaiti Makoto 5f91345d69 Add test for Follow::build_undo07() 2022-04-24 03:45:11 +09:00
Kitaiti Makoto 9ca975113c Implement Follow::build_accept07() 2022-04-24 03:39:08 +09:00
Kitaiti Makoto 38a55857c6 Add test for Follow::build_accept07() 2022-04-24 03:38:54 +09:00
Kitaiti Makoto 9343d3a120 Implement Follow::to_activity07() 2022-04-24 03:38:36 +09:00
Kitaiti Makoto c5656971c9 Add test for Follow::to_activity07() 2022-04-24 03:38:24 +09:00
Kitaiti Makoto ed55b66253 Implement AsObject07 for Comment 2022-04-24 01:49:27 +09:00
Kitaiti Makoto 713ffb9506 Fix Comment::create_activity07() 2022-04-24 01:37:30 +09:00
Kitaiti Makoto 9969e844ca Add test for Comment self federation 2022-04-24 01:37:03 +09:00
Kitaiti Makoto 0c61dca9ca Follow clippy 2022-04-23 22:49:13 +09:00
Kitaiti Makoto 957725fbf8 impl FromId07<DbConn> for Comment 2022-04-23 22:46:49 +09:00
Kitaiti Makoto 1f6361a9a2 Fix Cargo.toml 2022-04-18 00:21:17 +09:00
Kitaiti Makoto cf870971d1 Add test for Comment::build_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 08ac7227b5 Implement Comment::builde_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 88eb61c320 Implement Comment::create_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 1c1dbd481a Add test for Comment::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 86b4f622ea Implement Comment::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto f854bc5838 Add test for LicensedArticle deserialization 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 489156f4a3 Add test for Post's self federation 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 01e8b0bce8 Implement AsObject for Post 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 9183d04e66 Fix Post::from_activity07() for borrow checker 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 5e463e2cc9 Implement FromId07 for Post 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 6e2bff10f7 Add test for Post::build_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 3e9d9a81b7 Implement Mention::build_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 98e0754976 Add test for Post::build_delete() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto da7870eeba Implement Post::update_hashtags07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto c1562f3868 Implement Post::update_tags07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto e0390cb105 Implement Post::update_mentions07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 32cd91cfb9 Implement Mention::from_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 991dfccf3b Add test for Post::update_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 16e012ba00 Implement Post::update_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 871618f45d Add test for Post::create_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 680d321a2e Implement Post::create_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto c37ff54857 Fix Post::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto d4018d61d4 Add test for Post::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 21a0059755 Follow clippy 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 05f4c186f4 Fix test for LicensedArticle serialization 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 53512a6167 Fix SourceProperty property 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 7cf7700ef7 Implement Post::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 216855d3a7 Add SourceProperty to LicensedArticle 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 23f273e5e8 Readd assert-json-diff 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 70949fad02 Rename: ActorSource -> SourceProperty 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 1f5ce8e504 Add test for Mention::build_activity07() and to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 2316d36e03 Add test for Tag::from_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 2b1ddc71ac Implement Tag::to_activity07() and Tag::build_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto b9dac1a21a Define Hashtag07 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 95fb5a3c71 Implement Media::from_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 75b43a738f Follow clippy 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 5bd467c4c1 Remove unnecessary records 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 74d6dc5089 Implement Blog::to_activity07(), outbox_collection07() and outbox_collection_page07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 994a4dbb2d Add source property to CustomGroup 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 67996cc938 Add test for Blog::outbox_collection_page() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto f5e776c4d7 Fix first and last link in Blog::outbox_collection() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto e27fc47287 Extract Blog::outbox_collection_page() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 0ed91b89ff Add test for Blog::outbox_collection() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 00862790a1 Extract Blog::outbox_collection() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto ab6f39c192 Add test for Blog::to_activity() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 4edc201c14 Implement FromId07 for Blog 2022-04-18 00:19:33 +09:00
Kitaiti Makoto a1a7acfe94 Use new activitystreams APIs 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 6b5a1d2130 Use Base::retract() instead of into_any_base() on creating activity 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 85e35fdb5d Update activitystreams 2022-04-18 00:19:33 +09:00
Kitaiti Makoto da9e13622c Use Inbox::with07() for User, Delete, User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 8f4dd8a57b Implement User::fetch_outbox07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 78b0535063 Implement User::fetch_outbox_page07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 6323c7aef8 Add test for User::outbox_page_collection07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 7f0ad56d07 Implement User::outbox_collection_page07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e7eea3901f Implement User::outbox_page07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 0979471e54 Add test for User::delete_activity07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 8d69051a61 Implement User::delete_activity07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 55ca1345e1 Add test self_federation07() for User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto ad951ca842 Add test for User::to_activity07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto cb8e2e9294 Implement User::to_activity() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 038d65acaa Implement User::outbox07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e392a89526 Add test for User::outbox_collection07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto d62f51665b Implement User::outbox_collection07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e42aa6fe8e Implement From<iri_string::validate::Error> for Error 2022-04-18 00:11:12 +09:00
Kitaiti Makoto ab126563f3 Implement AsObject07 for User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto c1b9ebdae6 [REFACTORING]Reduce duplicated closure 2022-04-18 00:11:12 +09:00
Kitaiti Makoto d3e11c78d7 [REFACTORING]Use method chain instead of if clauses 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 4ccfec8019 Use OneOrMany<AnyBase>::to_as_uri() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto bb5157637d Implement OneOrMany<AnyBase>::to_as_uri() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 456df3e535 Use OneOrMany<&AnyString>::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto f0112850fa Implement OneOrMany<&AnyString>::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a6a21d5dfa Rewrite to_as_string() using method chain instead of if expressions 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 249fbbe891 Remove unused import 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e925865767 Use &AnyString::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 28643fc2c2 Implement &AnyString::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 3db10a09bb Use OneOrMany<&AnyString>::to_as_string() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a80a95d471 Implement OneOrMany<&AnyString>::to_as_string() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e407d58ee9 Implement FromId07 for User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a6d839a766 Make fields of ApSignature07 and PublicKey07 public 2022-04-18 00:11:12 +09:00
Kitaiti Makoto f3b67ab6c9 WIP 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 66f5628a27 Add suffix 07 to activitystreams 0.7 related methods 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 4b3b5c1f40 Implement From<activitystreams::checked::CheckError> for Error 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 3e687f3af0 Reduce type parameter from broadcast07 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 119d3e4f6a [plume-common]Add tests for new ActivityPub functions 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a21d66178e [plume-common]Implement ActivityPub related function using activitystreams 0.7 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 52967f3e47 [plume-common]Implement ActivityPub-related code using activitystreams 0.7 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 29439f9d02 Add tests for newly added ActivityPub-related structs 2022-04-18 00:11:12 +09:00
Kitaiti Makoto bc72a4c2d1 Install assert-json-diff 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 3ded0e2166 Add assert-json-diff to plume-common's dependencies 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 27c10e5e5c Install activitystreams-ext 2022-04-18 00:10:41 +09:00
Kitaiti Makoto 816aefe72a Add ActivityStreams Ext to plume-common dependencies 2022-04-18 00:10:41 +09:00
Kitaiti Makoto bff50f8e4c Add activitystreams 0.7 to plume-models dependencies 2022-04-18 00:10:41 +09:00
Kitaiti Makoto be1c22815b Install activitystreams 0.7 2022-04-18 00:10:19 +09:00
Kitaiti Makoto 08ab7ffd08 Add activitystreams 0.7 to plume-common dependencies 2022-04-17 23:47:13 +09:00
KitaitiMakoto 8709f6cf9f Merge pull request 'Bump tracing from 0.1.32 to 0.1.34' (#1050) from tracing-0.1.34 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1050
2022-04-17 14:44:46 +00:00
KitaitiMakoto 04cae95635 Merge branch 'main' into tracing-0.1.34 2022-04-17 14:44:27 +00:00
KitaitiMakoto 5d37b2534a Merge pull request 'Bump wasm-bindgen from 0.2.78 to 0.2.80' (#1049) from wasm-bindgen-0.2.80 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1049
2022-04-17 14:44:00 +00:00
dependabot[bot] eafafdaadf Bump wasm-bindgen from 0.2.78 to 0.2.80
Bumps [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) from 0.2.78 to 0.2.80.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.78...0.2.80)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-17 23:43:25 +09:00
KitaitiMakoto 6897b8fa58 Merge pull request 'Bump js-sys from 0.3.55 to 0.3.57' (#1048) from js-sys-0.3.57 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1048
2022-04-17 14:40:47 +00:00
dependabot[bot] 16d3279d72 Bump js-sys from 0.3.55 to 0.3.57
Bumps [js-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.55 to 0.3.57.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-17 23:40:01 +09:00
KitaitiMakoto 36c76c534d Merge pull request 'Bump web-sys from 0.3.55 to 0.3.57' (#1047) from web-sys-0.3.57 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1047
2022-04-17 14:39:18 +00:00
KitaitiMakoto 26f460be89 Merge branch 'main' into web-sys-0.3.57 2022-04-17 14:39:05 +00:00
KitaitiMakoto b9fb13104a Merge pull request 'Bump ammonia from 3.1.4 to 3.2.0' (#1046) from ammonia-3.2.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1046
2022-04-17 14:38:08 +00:00
dependabot[bot] 5cc411158f Bump tracing from 0.1.32 to 0.1.34
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.32 to 0.1.34.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.32...tracing-0.1.34)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-15 19:29:35 +00:00
dependabot[bot] ca69c93531 Bump web-sys from 0.3.55 to 0.3.57
Bumps [web-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.55 to 0.3.57.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 19:48:13 +00:00
dependabot[bot] 95cb7cc904 Bump ammonia from 3.1.4 to 3.2.0
Bumps [ammonia](https://github.com/rust-ammonia/ammonia) from 3.1.4 to 3.2.0.
- [Release notes](https://github.com/rust-ammonia/ammonia/releases)
- [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-ammonia/ammonia/compare/v3.1.4...v3.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 19:48:04 +00:00
KitaitiMakoto 0a62fa46aa Merge pull request 'Bump tracing-subscriber from 0.3.9 to 0.3.10' (#1045) from tracing-subscriber-0.3.10 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1045
2022-04-03 13:28:51 +00:00
KitaitiMakoto 2e60410969 Merge branch 'main' into tracing-subscriber-0.3.10 2022-04-03 13:28:09 +00:00
KitaitiMakoto a12d3a591b Merge pull request 'Bump ldap3 from 0.10.2 to 0.10.3' (#1044) from ldap3-0.10.3 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1044
2022-04-03 13:27:42 +00:00
dependabot[bot] 3cf7c67b6d Bump tracing-subscriber from 0.3.9 to 0.3.10
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.9 to 0.3.10.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.9...tracing-subscriber-0.3.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 19:26:25 +00:00
dependabot[bot] 2cf79f31b7 Bump ldap3 from 0.10.2 to 0.10.3
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.10.2 to 0.10.3.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.10.2...v0.10.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-30 19:30:55 +00:00
KitaitiMakoto 24cf941303 Merge pull request 'Bump native-tls from 0.2.8 to 0.2.10' (#1043) from native-tls-0.2.10 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1043
2022-03-30 19:04:42 +00:00
dependabot[bot] 38cf9b5496 Bump native-tls from 0.2.8 to 0.2.10
Bumps [native-tls](https://github.com/sfackler/rust-native-tls) from 0.2.8 to 0.2.10.
- [Release notes](https://github.com/sfackler/rust-native-tls/releases)
- [Changelog](https://github.com/sfackler/rust-native-tls/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sfackler/rust-native-tls/compare/v0.2.8...v0.2.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-29 19:29:35 +00:00
KitaitiMakoto b9607b32ac Merge pull request 'Bump rsass from 0.23.4 to 0.24.0' (#1042) from rsass-0.24.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1042
2022-03-26 15:10:16 +00:00
KitaitiMakoto 3393da2560 Merge pull request 'Bump bcrypt from 0.12.0 to 0.12.1' (#1041) from bcrypt-0.12.1 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1041
2022-03-26 15:09:54 +00:00
dependabot[bot] 7566f94690 Bump rsass from 0.23.4 to 0.24.0
Bumps [rsass](https://github.com/kaj/rsass) from 0.23.4 to 0.24.0.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.23.4...v0.24.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-24 19:29:35 +00:00
dependabot[bot] ee8312fb57 Bump bcrypt from 0.12.0 to 0.12.1
Bumps [bcrypt](https://github.com/Keats/rust-bcrypt) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/Keats/rust-bcrypt/releases)
- [Commits](https://github.com/Keats/rust-bcrypt/compare/v0.12.0...v0.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-21 19:32:31 +00:00
KitaitiMakoto 76219704f3 Merge pull request 'Bump rpassword from 5.0.1 to 6.0.1' (#1040) from rpassword-6.0.1 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1040
2022-03-20 20:29:35 +00:00
dependabot[bot] 2a43d4e88a Bump rpassword from 5.0.1 to 6.0.1
Bumps [rpassword](https://github.com/conradkleinespel/rpassword) from 5.0.1 to 6.0.1.
- [Release notes](https://github.com/conradkleinespel/rpassword/releases)
- [Commits](https://github.com/conradkleinespel/rpassword/compare/v5.0.1...v6.0.1)

---
updated-dependencies:
- dependency-name: rpassword
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-14 19:29:46 +00:00
KitaitiMakoto f0ce073a37 Merge pull request 'Bump tracing from 0.1.31 to 0.1.32' (#1039) from tracing-0.1.32 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1039
2022-03-12 13:46:19 +00:00
dependabot[bot] 8047196394 Bump tracing from 0.1.31 to 0.1.32
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.31 to 0.1.32.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.31...tracing-0.1.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-09 19:30:07 +00:00
KitaitiMakoto 3cf52b3985 Merge pull request 'Add test for Tag::from_activity()' (#1037) from tag-test into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1037
2022-03-06 11:44:02 +00:00
KitaitiMakoto ac378e448b Merge pull request 'Bump once_cell from 1.9.0 to 1.10.0' (#1038) from once_cell-1.10.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1038
2022-03-06 11:43:40 +00:00
Kitaiti Makoto daae2038f8 Add test for Tag::from_activity() 2022-03-06 20:22:36 +09:00
dependabot[bot] a2356c6e59 Bump once_cell from 1.9.0 to 1.10.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.9.0...v1.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-03 19:31:09 +00:00
KitaitiMakoto 44b91c6f07 Merge pull request 'Update crates' (#1036) from ldap3-0.10.2 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1036
2022-03-01 13:30:25 +00:00
Kitaiti Makoto 144565d13e Merge remote-tracking branch 'github/dependabot/cargo/bcrypt-0.12.0' into ldap3-0.10.2 2022-03-01 22:06:38 +09:00
dependabot[bot] 07fd66863d Bump bcrypt from 0.11.0 to 0.12.0
Bumps [bcrypt](https://github.com/Keats/rust-bcrypt) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/Keats/rust-bcrypt/releases)
- [Commits](https://github.com/Keats/rust-bcrypt/compare/v0.11.0...v0.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 19:46:44 +00:00
dependabot[bot] 385a5f7c33 Bump ldap3 from 0.9.3 to 0.10.2
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.9.3 to 0.10.2.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.9.3...v0.10.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 19:46:38 +00:00
KitaitiMakoto 8c8c2edc66 Merge pull request 'Add tests for Tag' (#1035) from ap-tests into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1035
2022-02-27 12:50:36 +00:00
Kitaiti Makoto f7e393bded Add tests for Tag 2022-02-27 21:47:40 +09:00
KitaitiMakoto 1df25e34b0 Merge pull request 'Update gettext-macros to 0.6.1' (#1033) from update-gettext-macros into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1033
2022-02-26 15:47:44 +00:00
Kitaiti Makoto ed491bad21 Update gettext-macros to 0.6.1 2022-02-26 11:20:45 +09:00
KitaitiMakoto 306f2d5738 Merge pull request 'Bump bcrypt from 0.10.1 to 0.11.0' (#1032) from bcrypt-0.11.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1032
2022-02-23 20:43:41 +00:00
dependabot[bot] 2196cb95c0 Bump bcrypt from 0.10.1 to 0.11.0
Bumps [bcrypt](https://github.com/Keats/rust-bcrypt) from 0.10.1 to 0.11.0.
- [Release notes](https://github.com/Keats/rust-bcrypt/releases)
- [Commits](https://github.com/Keats/rust-bcrypt/compare/v0.10.1...v0.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-23 19:27:58 +00:00
KitaitiMakoto b4d494a5c7 Merge pull request 'Bump tracing from 0.1.30 to 0.1.31' (#1030) from tracing-0.1.31 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1030
2022-02-19 18:17:15 +00:00
Kitaiti Makoto e8432f575e Merge remote-tracking branch 'origin/main' into tracing-0.1.31 2022-02-20 02:55:50 +09:00
KitaitiMakoto eb48723c08 Merge pull request 'Bump tracing-subscriber from 0.3.8 to 0.3.9' (#1028) from tracing-subscriber-0.3.9 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1028
2022-02-19 17:28:02 +00:00
dependabot[bot] 65168202b4 Bump tracing from 0.1.30 to 0.1.31
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.30 to 0.1.31.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.30...tracing-0.1.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-18 19:38:05 +00:00
dependabot[bot] dced3cf881 Bump tracing-subscriber from 0.3.8 to 0.3.9
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.8 to 0.3.9.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.8...tracing-subscriber-0.3.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-18 19:37:58 +00:00
KitaitiMakoto 170fd6026c Merge pull request 'Bump ammonia from 3.1.3 to 3.1.4' (#1027) from update-crate into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1027
2022-02-18 10:49:25 +00:00
dependabot[bot] 1ccaa817b3 Bump ammonia from 3.1.3 to 3.1.4
Bumps [ammonia](https://github.com/rust-ammonia/ammonia) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/rust-ammonia/ammonia/releases)
- [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-ammonia/ammonia/compare/v3.1.3...v3.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-17 19:29:15 +00:00
KitaitiMakoto 65ba083720 Merge pull request 'Switch gettext crate from GitHub to crates.io' (#1018) from gettext-cratesio into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1018
2022-02-17 14:32:47 +00:00
Kitaiti Makoto dba902d262 Merge remote-tracking branch 'origin/main' into gettext-cratesio 2022-02-17 23:09:50 +09:00
Kitaiti Makoto d52c7a3afa Update gettext-macros and gettext-utils 2022-02-17 23:08:16 +09:00
KitaitiMakoto c63f88fb7f Merge pull request 'Update crates' (#1026) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1026
2022-02-17 13:58:30 +00:00
Kitaiti Makoto 4412e0598f Follow API change of rocket_i18n 2022-02-17 22:36:13 +09:00
Kitaiti Makoto eb22c1168e Install rocket_i18n from crates.io 2022-02-17 22:36:13 +09:00
Kitaiti Makoto 917eda356d Use rocket_i18n on crates.io 2022-02-17 22:36:13 +09:00
Kitaiti Makoto bc6580bbdc Switch gettext crate from GitHub to crates.io 2022-02-17 22:36:13 +09:00
Kitaiti Makoto 920cf622c5 Merge remote-tracking branch 'github/dependabot/cargo/askama_escape-0.10.3' into update-crates 2022-02-17 22:34:41 +09:00
dependabot[bot] 13dcb193dc Bump askama_escape from 0.10.2 to 0.10.3
Bumps [askama_escape](https://github.com/djc/askama) from 0.10.2 to 0.10.3.
- [Release notes](https://github.com/djc/askama/releases)
- [Commits](https://github.com/djc/askama/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-16 19:34:15 +00:00
dependabot[bot] 3afb724fed Bump serde_json from 1.0.78 to 1.0.79
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.78 to 1.0.79.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.78...v1.0.79)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 19:32:32 +00:00
KitaitiMakoto 9662936b44 Merge pull request 'Update crates' (#1023) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1023
2022-02-08 08:13:37 +00:00
Kitaiti Makoto 4780472d48 Make Circle CI follow ructe change 2022-02-08 16:54:03 +09:00
Kitaiti Makoto 6f68c4504b Update Cargo.lock 2022-02-08 16:53:35 +09:00
Kitaiti Makoto 28e0cdfe63 Remove activitystreams from dependencies 2022-02-08 16:50:00 +09:00
Kitaiti Makoto a5003526c8 Follow clippy 2022-02-08 16:47:48 +09:00
Kitaiti Makoto ec3d78b509 Merge remote-tracking branches 'github/dependabot/cargo/ructe-0.14.0', 'github/dependabot/cargo/rsass-0.23.4' and 'github/dependabot/cargo/tracing-subscriber-0.3.8' into update-crates 2022-02-08 16:37:12 +09:00
dependabot[bot] 4205e38605 Bump rsass from 0.23.2 to 0.23.4
Bumps [rsass](https://github.com/kaj/rsass) from 0.23.2 to 0.23.4.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.23.2...v0.23.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 19:38:02 +00:00
dependabot[bot] 8438d48c71 Bump ructe from 0.13.4 to 0.14.0
Bumps [ructe](https://github.com/kaj/ructe) from 0.13.4 to 0.14.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.13.4...v0.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 19:37:50 +00:00
dependabot[bot] 52faf5996b Bump tracing-subscriber from 0.3.7 to 0.3.8
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.7...tracing-subscriber-0.3.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 19:37:34 +00:00
KitaitiMakoto 69eccc50a3 Merge pull request 'Add ActivityPub tests and a little fixes' (#1021) from ap-tests into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1021
2022-02-05 09:17:27 +00:00
Kitaiti Makoto 54cbdb236f Add tests for Mention activity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 34c374de1a Attach icon field to User activity only whene it has avatar 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 113722e4ba Add more ActivityPub tests for User 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 3b429909f1 Extract User::outbox_collection_page() from outbox_collection() for testablity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto f1cdf4552f Extract User::outbox_collection() from outbox() for testablity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 7d320e57da Don't make medias::tests::clean() panic when file not found 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e1a598a459 Attach avater to sample user 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 6107842303 Add tests for Comment::to_activity() and build_delete() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 65372d2018 Extract comments::tests::prepare_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 4842385ca6 Add test about reply 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 05f55fc1ca Add https scheme to mention URI in contents 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e8153d4b42 Fix Comment::to_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 2087a659f9 Add test to validate comment json 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 1770336c11 Make format_datetime() crate public 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 9c177f6286 Change format_datetime implementation according to feature 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 93a2c6d99f Add tests for Follow::build_accept() and build_undo() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 5ef76873b7 Fix tests 2022-02-05 17:58:00 +09:00
Kitaiti Makoto b97c3fdb87 Extract Follow::build_accept 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 64838ad864 Add test for Follow::to_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 4df2ce5744 Add mention to test suite for Post activities 2022-02-05 17:58:00 +09:00
Kitaiti Makoto c1f42836d9 Fix variable names 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 0cbc9438d4 Complete a slash to Post Create activity's ID 2022-02-05 17:58:00 +09:00
Kitaiti Makoto ca6cd534d8 Add tests for Post::to_activity(), create_activity() and update_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto f529e803ef Fix ap_url of Reshare 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e5bc84badf Add tests for Reshare::to_activity and build_undo 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e2077bed59 Add test for Like::build_undo 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 9ab9d29efb Remove double slashes 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 5373a674e1 Add test for Like::to_activity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto bfaa2fafaf Install assert-json-diff 2022-02-05 17:58:00 +09:00
Kitaiti Makoto d4a13a13d4 Add assert-json-diff to dev dependencies of plume-common
% cargo add assert-json-diff -p plume-models --dev
2022-02-05 17:58:00 +09:00
Kitaiti Makoto 92c0368dd8 Install activitystreams 0.7.0 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 80c0426768 Add activitystreams 0.7.0 to plume-models dependencies 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 71b21289ab Install activitystreams 0.7.0 2022-02-05 17:58:00 +09:00
Kitaiti Makoto fa861ff314 Add activitystreams 0.7.0 to plume-common dependencies 2022-02-05 17:58:00 +09:00
KitaitiMakoto 400d2dee32 Merge pull request 'Update crates' (#1020) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1020
2022-02-05 05:48:47 +00:00
Kitaiti Makoto 3993dda17d Merge remote-tracking branch 'github/dependabot/cargo/tracing-0.1.30' into update-crates 2022-02-05 14:31:20 +09:00
dependabot[bot] b1255efdcd Bump tracing from 0.1.29 to 0.1.30
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.29 to 0.1.30.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.29...tracing-0.1.30)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-04 19:30:06 +00:00
dependabot[bot] 22036c6a94 Bump rsass from 0.23.0 to 0.23.2
Bumps [rsass](https://github.com/kaj/rsass) from 0.23.0 to 0.23.2.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.23.0...v0.23.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-04 19:29:57 +00:00
KitaitiMakoto f2df4b7d7d Merge pull request 'Don't fill empty content when switching rich editor' (#1017) from content-placeholder into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1017
2022-01-29 17:29:41 +00:00
Kitaiti Makoto 7c57bf78a1 [skip ci]Add changelog 2022-01-30 02:29:16 +09:00
Kitaiti Makoto 8d898ff477 Don't fill empty content when switching rich editor 2022-01-30 02:26:23 +09:00
KitaitiMakoto a1045dbce9 Merge pull request 'Fixes #988 Fix email_blocklist schema' (#1016) from block_list-schema into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1016
2022-01-29 16:38:33 +00:00
Kitaiti Makoto 23a07f3f7b [skip ci]Add changelog 2022-01-30 01:17:33 +09:00
Kitaiti Makoto 458d87fef1 Run migration 2022-01-30 01:16:51 +09:00
Kitaiti Makoto 82df86d09e Set null to email_blocklist table fields for SQLite 2022-01-30 01:16:03 +09:00
Kitaiti Makoto 858cad2995 Set null to email_blocklist table fields 2022-01-30 00:54:10 +09:00
Kitaiti Makoto c0483cf12e Generate migration files for adding NOT NULL constraints to email_blocklist table fields
% diesel migration generate add_not_null_constraint_to_email_blocklist
2022-01-30 00:45:18 +09:00
KitaitiMakoto 57a54cf016 Merge pull request 'Update Rust' (#1015) from bump-rust into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1015
2022-01-27 03:14:47 +00:00
Kitaiti Makoto 325d8cde08 [skip ci]Add changelog about Rust bump 2022-01-27 12:01:54 +09:00
Kitaiti Makoto 9e2c76c3bc Satisfy clippy 2022-01-27 11:54:41 +09:00
Kitaiti Makoto 996b161c1e Satisfy clippy 2022-01-27 11:45:35 +09:00
Kitaiti Makoto 831ef88431 Update Rust 2022-01-27 11:45:21 +09:00
KitaitiMakoto 89517e5988 Merge pull request 'Update crates' (#1014) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1014
2022-01-27 01:22:16 +00:00
Kitaiti Makoto 48dbcf75a9 Update crates 2022-01-27 10:02:47 +09:00
Kitaiti Makoto a56a9bc9c5 Add changelogs 2022-01-27 10:02:30 +09:00
KitaitiMakoto 918103fa29 Merge pull request 'Fix #1011 Add Basque' (#1013) from langs into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1013
2022-01-26 13:37:53 +00:00
KitaitiMakoto c9b8f5a739 Merge pull request 'Fix #1009 Email Sign-up Explanation' (#1012) from email-signup-explanation into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1012
2022-01-26 13:37:38 +00:00
Kitaiti Makoto d58ff36d80 Update po files 2022-01-26 22:17:11 +09:00
Kitaiti Makoto 00d647c0ad Add Basque po files 2022-01-26 22:16:55 +09:00
Kitaiti Makoto a27f196578 Add Basque 2022-01-26 22:15:35 +09:00
Kitaiti Makoto abe82b79ce Update pot file 2022-01-26 22:10:47 +09:00
Kitaiti Makoto 95230c3a23 Add explanation for email signup 2022-01-26 22:10:14 +09:00
Kitaiti Makoto eade69a12c Fix indentation 2022-01-26 22:09:45 +09:00
KitaitiMakoto 4f89e214ef Merge pull request 'Update crates' (#1010) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1010
2022-01-25 00:46:41 +00:00
Kitaiti Makoto 2936679326 Update crates 2022-01-25 09:26:23 +09:00
KitaitiMakoto 18a67fe1b5 Merge pull request 'Update crates' (#1008) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1008
2022-01-16 03:23:13 +00:00
Kitaiti Makoto ba29c8ef6f Follow atom_syndication API change 2022-01-16 12:02:12 +09:00
Kitaiti Makoto d253f1a020 Upgrade atom_syndication 2022-01-16 12:02:03 +09:00
Kitaiti Makoto 03060d6ee2 Update crates 2022-01-16 11:31:18 +09:00
KitaitiMakoto ac8ad3aae2 Merge pull request 'Add dependabot.yml' (#1007) from dependabot into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1007
2022-01-15 02:01:40 +00:00
Kitaiti Makoto 14e294efed Add dependabot.yml 2022-01-15 06:02:16 +09:00
KitaitiMakoto ec3205b372 Merge pull request 'v0.7.1' (#1006) from v0.7.1 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1006
2022-01-12 02:10:48 +00:00
Kitaiti Makoto 45119d9a8c (cargo-release) version {{version}} 2022-01-12 10:42:45 +09:00
Kitaiti Makoto 1065078f75 Update translation files 2022-01-12 10:29:50 +09:00
Kitaiti Makoto 0ce904a985 Update translation files 2022-01-12 10:24:14 +09:00
Kitaiti Makoto 254eec8e6a Follow cargo-release update 2022-01-12 10:19:58 +09:00
KitaitiMakoto 0e4cb4f6e1 Merge pull request '[skip ci]Fix # of pull reuqest in changelog' (#1004) from fix-changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1004
2022-01-12 00:37:19 +00:00
Kitaiti Makoto 9b05ac90df [skip ci]Fix # of pull reuqest in changelog 2022-01-12 09:36:32 +09:00
KitaitiMakoto f28a7fa508 Merge pull request 'Add changelogs' (#1003) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1003
2022-01-12 00:35:26 +00:00
Kitaiti Makoto 65e95d8998 Add changelogs 2022-01-12 09:34:39 +09:00
KitaitiMakoto 808b8f8e98 Merge pull request 'Fix #1001 Deny access to disabled sign-up strategy' (#1002) from restrict-signup into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1002
2022-01-12 00:33:04 +00:00
Kitaiti Makoto 43b46a8be4 Make email_signups::create return ErrorPage on error 2022-01-12 09:20:20 +09:00
Kitaiti Makoto 9bbfc71fc8 Fix registration openess condition mistake 2022-01-12 09:19:48 +09:00
Kitaiti Makoto 5d58b31f1c Remove unreachable code 2022-01-12 09:00:04 +09:00
Kitaiti Makoto e31a2238fb Respond with error status code when error 2022-01-12 08:58:42 +09:00
Kitaiti Makoto 7de37bc9b7 Hide password sign-up routings when it's disabled 2022-01-12 08:46:11 +09:00
Kitaiti Makoto 13f7734751 Hide email sign-up routings when it's disabled 2022-01-12 08:40:20 +09:00
Kitaiti Makoto b4395bce99 Implement request guard to detect enabled sign-up strategy 2022-01-12 08:39:33 +09:00
Kitaiti Makoto 7c82b08615 Use into() instead of explicitly wrapping return values 2022-01-12 04:36:01 +09:00
Kitaiti Makoto 6498dbfbb7 Reuse form values 2022-01-12 04:28:02 +09:00
Kitaiti Makoto 74254aed4a Move require_logins from plume-common to plume 2022-01-12 04:18:13 +09:00
KitaitiMakoto 8c48abf48e Merge pull request 'Update crates' (#997) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/997
2022-01-07 18:58:18 +00:00
Kitaiti Makoto 8958226604 Upgrade shrinkwraprs 2022-01-08 03:54:38 +09:00
Kitaiti Makoto 005a6db230 Update crates 2022-01-08 03:20:22 +09:00
KitaitiMakoto 4397abd8ab Merge pull request 'Add plume-front.pot' (#994) from front-po into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/994
2022-01-07 15:40:31 +00:00
Kitaiti Makoto e53882f555 Add plume-front.pot 2022-01-08 00:39:17 +09:00
KitaitiMakoto 5d5e61dfa1 Merge pull request 'Update crates' (#993) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/993
2022-01-06 21:45:53 +00:00
Kitaiti Makoto c5c6b70a89 Upgrade ldap3 2022-01-07 06:17:58 +09:00
Kitaiti Makoto 6778a0e943 Remove hyper from plume-common 2022-01-07 06:16:00 +09:00
Kitaiti Makoto 677e238c6d Follow API change of heck 2022-01-07 06:12:15 +09:00
Kitaiti Makoto b0bc2372fa Upgrade heck 2022-01-07 06:12:03 +09:00
Kitaiti Makoto 6a808c7cc5 Upgrade hex 2022-01-07 06:07:07 +09:00
Kitaiti Makoto d53543ccb1 Upgrade base64 2022-01-07 06:06:49 +09:00
Kitaiti Makoto 88d7d54601 Upgrade whatlang 2022-01-07 06:05:20 +09:00
Kitaiti Makoto 0f0c896887 Upgrade itertools 2022-01-07 06:05:05 +09:00
Kitaiti Makoto 65233c0a9a Upgrade ammonia 2022-01-07 05:53:09 +09:00
Kitaiti Makoto 32e1e4788f Upgrade shrinkwraprs 2022-01-07 05:49:06 +09:00
Kitaiti Makoto 181a78876b Remove askama_escape from dependencies of plume-models 2022-01-07 05:47:46 +09:00
Kitaiti Makoto 61d5446113 Use plume_common::escape() instead of askama_escape::escape() directly 2022-01-07 05:47:08 +09:00
Kitaiti Makoto c786569171 Define plume_common::escape() 2022-01-07 05:36:39 +09:00
Kitaiti Makoto d83a75e3f4 Add askama_escape to plume-common 2022-01-07 05:26:32 +09:00
Kitaiti Makoto a6f06559ea Remove rspassword from dependencies 2022-01-07 05:15:07 +09:00
Kitaiti Makoto 2084145dd3 Upgrade multipart 2022-01-07 05:12:29 +09:00
Kitaiti Makoto dd54058516 Upgrade guid-create 2022-01-07 05:09:08 +09:00
Kitaiti Makoto 4056a54d44 Upgrade tracing-subscriber 2022-01-07 05:03:07 +09:00
Kitaiti Makoto 191cd11741 Upgrade dotenv 2022-01-07 04:59:03 +09:00
Kitaiti Makoto 800e74da67 Follow API change of validator 2022-01-07 04:55:49 +09:00
Kitaiti Makoto 237da47950 Upgrade validator 2022-01-07 04:55:41 +09:00
Kitaiti Makoto ec12539fd0 Follow rsass API change 2022-01-07 04:41:20 +09:00
Kitaiti Makoto a537db559b Upgrade rsass 2022-01-07 04:41:11 +09:00
Kitaiti Makoto 2ba158df67 Update crates 2022-01-07 01:41:22 +09:00
KitaitiMakoto c0c066547f Merge pull request 'Update po files' (#991) from po into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/991
2022-01-06 13:24:09 +00:00
Kitaiti Makoto c3f59b14b9 Update po files 2022-01-06 22:14:24 +09:00
KitaitiMakoto 1d06a8f1ad Merge pull request 'Fixes #636 Email sign up feature' (#990) from mail-confirmation into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/990
2022-01-06 12:04:24 +00:00
Kitaiti Makoto efaf1295e9 Suppress clippy 2022-01-06 20:43:40 +09:00
Kitaiti Makoto 4bc9cf3ad1 [skip ci]Complete changelogs 2022-01-06 20:35:34 +09:00
Kitaiti Makoto 1e3851ea69 Execute SQLs for email_signups in transaction 2022-01-06 20:27:55 +09:00
Kitaiti Makoto b6d38536e3 Add email signup feature 2022-01-06 20:18:20 +09:00
Kitaiti Makoto 9b4c678aa9 Make signup token transparent 2022-01-06 18:40:24 +09:00
Kitaiti Makoto a65775d85b Implement EmailSignup 2022-01-05 03:17:58 +09:00
Kitaiti Makoto 192c7677c3 Run migration
% diesel migration run
2022-01-05 00:01:36 +09:00
Kitaiti Makoto 2a31a7b601 Define email_singups table 2022-01-05 00:01:36 +09:00
Kitaiti Makoto 355fd7cb1d Generate create_email_signups_table migration
% diesel migration generate create_email_signups_table
2022-01-05 00:01:36 +09:00
Kitaiti Makoto 40efd73dfc Add config for sign up strategy 2022-01-05 00:01:32 +09:00
KitaitiMakoto 31b144c76d Merge pull request 'Remove unnecessary prefix' (#986) from fix-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/986
2022-01-04 10:17:53 +00:00
Kitaiti Makoto 31a46514cb Remove unnecessary prefix 2022-01-04 19:17:04 +09:00
KitaitiMakoto c8d906eb99 Merge pull request 'Quote version tag' (#985) from fix-action into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/985
2022-01-04 10:11:19 +00:00
Kitaiti Makoto 2895a1c819 Quote version tag 2022-01-04 19:10:37 +09:00
KitaitiMakoto a4a5d08662 Merge pull request 'deploy-tags' (#984) from deploy-tags into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/984
2022-01-04 10:08:31 +00:00
Kitaiti Makoto b97c9d2165 Deploy Docker images with tags 2022-01-04 19:07:47 +09:00
Kitaiti Makoto 5b7e8a69a5 Revert "Deploy tags"
This reverts commit d9a59f1b07.
2022-01-04 19:04:58 +09:00
KitaitiMakoto 9601e99e33 Merge pull request 'Deploy tags' (#983) from deploy-tags into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/983
2022-01-04 09:42:19 +00:00
Kitaiti Makoto d9a59f1b07 Deploy tags 2022-01-04 18:41:06 +09:00
KitaitiMakoto b6a6af906a Merge pull request '[skip ci]Add changelog abourt MAIL_PORT' (#982) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/982
2022-01-03 16:07:13 +00:00
Kitaiti Makoto 2c4799ce27 [skip ci]Add changelog abourt MAIL_PORT 2022-01-04 01:06:12 +09:00
KitaitiMakoto b33b19849c Merge pull request 'Fix notification page error' (#981) from fix-notification-page into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/981
2022-01-03 14:29:43 +00:00
Kitaiti Makoto e398f36c57 Add changelog 2022-01-03 23:28:56 +09:00
Kitaiti Makoto ee6064eee8 Don't unwrap() 2022-01-03 23:28:05 +09:00
KitaitiMakoto 9d012c8f3c Merge pull request 'Closes #944 Mail server port configuration' (#980) from mail-server-port into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/980
2022-01-03 10:37:53 +00:00
Kitaiti Makoto 8888dbba0a Initialize SMTP client with port number 2022-01-03 18:09:26 +09:00
Kitaiti Makoto 6f8d5c1eb4 Add SmtpClient::new_with_addr() method 2022-01-03 18:09:13 +09:00
Kitaiti Makoto 62da4a3d5c Add native-tls to plume-models' dependencies 2022-01-03 18:04:49 +09:00
Kitaiti Makoto 5cfc8e71a5 Remove Lettre from plume module dependencies 2022-01-03 17:36:37 +09:00
Kitaiti Makoto a599760891 Use smtp module from plume_models instead of lettre directly 2022-01-03 17:36:11 +09:00
Kitaiti Makoto 00324f668f Add port field to MailConfig 2022-01-03 17:25:46 +09:00
Kitaiti Makoto d4549704b9 Install Lettre 2022-01-03 17:11:35 +09:00
Kitaiti Makoto 0836e3d693 Add Lettre to plume-models' dependencies 2022-01-03 17:09:47 +09:00
Kitaiti Makoto 0058c3053d Move mail config from plume::mail::mailer to plume_models::CONFIG 2022-01-03 15:50:04 +09:00
KitaitiMakoto 2a1a0a23a5 Merge pull request 'Move bottombar styles to _article.scss' (#978) from bottombar-styling into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/978
2022-01-02 18:03:46 +00:00
Kitaiti Makoto 5614e3bd59 Move bottombar styles to _article.scss 2022-01-03 03:01:59 +09:00
KitaitiMakoto acbda3cde1 Merge pull request 'Make bottom bar smaller in narrow window' (#977) from layout-post-control into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/977
2022-01-02 17:50:20 +00:00
Kitaiti Makoto 0755436458 Make bottom bar smaller in narrow window 2022-01-03 02:49:45 +09:00
KitaitiMakoto 3daf405ae2 Merge pull request 'Make blog cover clickable' (#976) from clickable-blog-image into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/976
2022-01-02 17:40:30 +00:00
Kitaiti Makoto 53dc3b0c03 Fix cover size of posts 2022-01-03 02:39:17 +09:00
Kitaiti Makoto 371dcc5091 Address blog title positoin in dashboard 2022-01-03 02:37:45 +09:00
Kitaiti Makoto 905fe54fa3 Make blog cover a link 2022-01-03 02:35:07 +09:00
Kitaiti Makoto 62c0827ff5 Remove needless whitespaces 2022-01-03 02:32:17 +09:00
KitaitiMakoto d6c65ce81a Merge pull request 'Fix #927 Ensure Post ap_url' (#975) from ensure-ap-url into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/975
2022-01-02 17:24:04 +00:00
Kitaiti Makoto 5532b4a4d7 Ensure Post ap_url 2022-01-03 02:21:34 +09:00
KitaitiMakoto 637bd3347b Merge pull request 'Fix #967 Fix comment link' (#974) from fix-comment-link into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/974
2022-01-02 17:08:10 +00:00
Kitaiti Makoto bac373a818 Fix comment link 2022-01-03 02:06:59 +09:00
Kitaiti Makoto f0e7ea5640 (cargo-release) version {{version}} 2022-01-03 01:17:07 +09:00
Kitaiti Makoto 4b981e0fad (cargo-release) version {{version}} 2022-01-03 00:56:29 +09:00
KitaitiMakoto 8fb9d861de Merge pull request '[skip ci]Give up to deploy tags' (#973) from giveup-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/973
2022-01-02 15:43:05 +00:00
Kitaiti Makoto 199269ba3c [skip ci]Give up to deploy tags 2022-01-03 00:42:36 +09:00
KitaitiMakoto 0b9ec4c52c Merge pull request 'Use Git tag for Docker image tag' (#972) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/972
2022-01-02 15:14:38 +00:00
Kitaiti Makoto 0e51565cc8 Use Git tag for Docker image tag 2022-01-03 00:14:00 +09:00
KitaitiMakoto 0418d35b67 Merge pull request 'Fix a typo' (#971) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/971
2022-01-02 15:06:26 +00:00
Kitaiti Makoto bf9d25363b Fix a typo 2022-01-03 00:04:57 +09:00
KitaitiMakoto 84f00c57d1 Merge pull request 'Fix tag calculation' (#970) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/970
2022-01-02 15:02:15 +00:00
Kitaiti Makoto 7c1a5421fa Fix tag calculation 2022-01-03 00:01:18 +09:00
KitaitiMakoto 3815bfe980 Merge pull request '[skip ci]Deploy tags to Docker Hub' (#969) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/969
2022-01-02 14:51:03 +00:00
Kitaiti Makoto de448c3192 [skip ci]Deploy tags to Docker Hub 2022-01-02 23:49:42 +09:00
KitaitiMakoto 967e2dfde6 Merge pull request 'Add GitHub Action to deploy Docker image' (#968) from gh-action into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/968
2022-01-02 14:28:27 +00:00
Kitaiti Makoto dd3c1eac5f Add GitHub Action to deploy Docker image 2022-01-02 23:26:39 +09:00
KitaitiMakoto abc0a794c1 Merge pull request 'v0.7.0' (#923) from v0.7.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/923
2022-01-02 11:48:46 +00:00
Kitaiti Makoto ef628aa498 (cargo-release) version {{version}} 2022-01-02 20:41:28 +09:00
Kitaiti Makoto 011bd9602d Update plume-buildenv Docker image to v0.4.0 2021-12-12 00:54:26 +09:00
Kitaiti Makoto b0745cfd82 Fix search string in release.toml 2021-12-12 00:38:22 +09:00
Kitaiti Makoto d1b1d9f507 Update translations 2021-12-12 00:01:11 +09:00
Kitaiti Makoto 742d545f1c Update po files 2021-12-11 23:57:05 +09:00
Kitaiti Makoto 67dec4df22 Merge remote-tracking branch 'origin/main' into v0.7.0 2021-12-09 14:35:10 +09:00
KitaitiMakoto 33f3bbb774 Merge pull request 'Proper scaling of the default image, when a user doesn't have custom avatar' (#965) from mareklach/Plume:proper_scaling_for_blank_user_avatar into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/965
2021-12-09 05:33:52 +00:00
mareklach 7c1df80695 Update 'assets/themes/default/_global.scss' 2021-12-06 15:15:28 +00:00
mareklach 2adbb6f74c Merge branch 'main' into proper_scaling_for_blank_user_avatar 2021-12-06 14:52:28 +00:00
mareklach 79c05e8381 Proper scaling of the default image, when a user doesn't have custom avatar 2021-12-06 14:48:05 +00:00
Kitaiti Makoto 31a1591043 Merge remote-tracking branch 'origin/main' into v0.7.0 2021-12-06 07:32:05 +09:00
KitaitiMakoto 0ede2ab3ab Merge pull request 'Fixes #936: Sign GET request' (#957) from sign-get into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/957
2021-12-05 22:13:24 +00:00
Kitaiti Makoto 43656d8e46 Create instance for test 2021-12-06 00:33:48 +09:00
Kitaiti Makoto b7cc2369a7 Allow clippy::needless_borrow on CI 2021-12-06 00:33:20 +09:00
Kitaiti Makoto 05a98175bf Assign unsed return value to underscore variable 2021-12-05 21:56:00 +09:00
Kitaiti Makoto 346a67fe1c Create local instance user on start 2021-12-05 21:34:36 +09:00
Kitaiti Makoto a22f5f2336 Add more changelogs 2021-12-05 21:20:37 +09:00
Kitaiti Makoto 55439990c6 Complete changelog 2021-12-05 21:12:50 +09:00
Kitaiti Makoto 3b0f28d061 Update po files 2021-12-05 21:12:14 +09:00
Kitaiti Makoto ac3acfb4ac Merge latest translations 2021-12-05 21:12:14 +09:00
Kitaiti Makoto d5256e9ffc Merge translations from Crowdin 2021-12-05 21:12:14 +09:00
Kitaiti Makoto 13be46445c Add changelog about signing GET requests 2021-12-05 21:10:22 +09:00
Kitaiti Makoto c67b702425 Cache LOCAL_INSTANCE_USER once 2021-12-05 20:57:10 +09:00
Kitaiti Makoto e01539ef16 Follow Clippy 2021-12-05 20:24:11 +09:00
Kitaiti Makoto de4380fd34 Cache local when creating local instance 2021-12-05 20:22:32 +09:00
Kitaiti Makoto 5651e11ba1 Add FIXME comment 2021-12-05 20:22:10 +09:00
KitaitiMakoto 5815602309 Merge pull request 'Don't shrink edit link' (#964) from edit-link-no-shrink into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/964
2021-12-05 10:38:49 +00:00
Kitaiti Makoto a0a69dfb22 Don't shrink edit link 2021-12-05 19:37:38 +09:00
Kitaiti Makoto d98132db80 Don't log unnecessary error 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 25fe2ad802 Use request::get() instead of ClientBuilder 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 48fab8ad2c Impl From for request::Error 2021-12-05 19:32:27 +09:00
Kitaiti Makoto a7d8d49faf Define request::get() function 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 388acd6738 Remove needless reference sign 2021-12-05 19:32:27 +09:00
Kitaiti Makoto d3c035aa39 Remove needless code 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 12a8d00f8e Cache local instance user for test 2021-12-05 19:32:27 +09:00
Kitaiti Makoto b9ea06a01a Don't stop even when caching local instance user 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 1e67b3c13c Create local instance user on caching if it doesn't exist 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 5a5c8bdac8 Make inbox test follow API change 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 44f9d36df1 Make request test follow API change 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 7d349c2de6 Install once_cell 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 79715ec7c7 Add once_cell to dev-dependencies for plume-common 2021-12-05 19:32:27 +09:00
Kitaiti Makoto f4d7dfb261 Sign GET request to other instances 2021-12-05 19:32:27 +09:00
Kitaiti Makoto c525410062 Create local instance user on creating instance 2021-12-05 19:32:27 +09:00
Kitaiti Makoto af5b0b961b Extract Instance::create_local_instance_user() from get_local_instance_user_uncached() 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 2f7a5cbf56 Cache local instance user on start 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 1506802c20 Implement LOCAL_INSTANCE_USER and related methods 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 897ea8e11e Change const name: LOCAL_INSTANCE_USER -> LOCAL_INSTANCE_USERNAME 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 858806149a Use concrete Error for Signer 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 0da9572627 Implement User::get_local_instance_user() 2021-12-05 19:32:27 +09:00
Kitaiti Makoto 94cc260803 Add Instance to users::Role 2021-12-05 19:32:27 +09:00
KitaitiMakoto 7198b06a33 Merge pull request 'Fix problem of PL #956' (#963) from mskf1383/Plume:main into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/963
Reviewed-by: KitaitiMakoto <kitaitimakoto@noreply@joinplu.me>
2021-12-05 10:12:48 +00:00
MohammadSaleh Kamyab 81006e1db8 Update 'assets/themes/default/_global.scss' 2021-12-02 13:16:14 +00:00
KitaitiMakoto 0f7094a70e Merge pull request 'Update crates' (#961) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/961
2021-11-28 00:54:35 +00:00
Kitaiti Makoto a0b661fffe Make template follow change of Rust 2021-11-28 08:48:59 +09:00
Kitaiti Makoto 4bef91f08b Follow clippy warnings 2021-11-28 08:36:27 +09:00
Kitaiti Makoto 20c17be124 Update Rust version 2021-11-28 08:13:16 +09:00
Kitaiti Makoto ae3344f318 Follow Rust and crates update 2021-11-28 08:06:29 +09:00
Kitaiti Makoto 9187e4dde9 Update crates 2021-11-28 08:06:29 +09:00
Kitaiti Makoto 89c185f819 Specify Rocket and serde_json version loosely 2021-11-28 08:06:29 +09:00
Kitaiti Makoto 09b9a37720 Update Rust version 2021-11-28 08:06:24 +09:00
KitaitiMakoto 12a8bfcf2d Merge pull request 'Fix article title overflowing on small screens' (#960) from mareklach/Plume:fix-article-title-overflow-spill into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/960
2021-11-26 19:53:40 +00:00
mareklach b2be00b125 Fix article title overflowing on small screens
Improves CSS scaling of the article title for mobile-sized screens, to prevent it spilling over.
2021-11-26 16:50:08 +00:00
KitaitiMakoto 914d394bd0 Merge pull request 'Stick serde_json versoin for plume-models' (#959) from stick-serde_json into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/959
2021-11-25 23:55:37 +00:00
Kitaiti Makoto 811db1be0e Stick serde_json versoin for plume-models 2021-11-26 08:53:55 +09:00
KitaitiMakoto cd81f042b9 Merge pull request 'Stick serde_json to < 1.0.70' (#958) from stick-serde_json into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/958
2021-11-25 23:35:31 +00:00
Kitaiti Makoto c6111fcd28 Stick serde_json to < 1.0.70 2021-11-26 08:34:38 +09:00
KitaitiMakoto 82ebdc023c Merge pull request 'Fix RTL problem in post card' (#956) from mskf1383/Plume:main into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/956
Reviewed-by: KitaitiMakoto <kitaitimakoto@noreply@joinplu.me>
2021-11-21 14:32:14 +00:00
MohammadSaleh Kamyab 7b110179a9 Update 'assets/themes/default/_global.scss' 2021-11-21 12:21:47 +00:00
MohammadSaleh Kamyab afd66ce7cb Update 'assets/themes/default/_global.scss' 2021-11-21 05:46:48 +00:00
MohammadSaleh Kamyab 9dac97045b Update 'templates/partials/post_card.rs.html' 2021-11-21 05:37:57 +00:00
KitaitiMakoto 188d4ac063 Merge pull request 'Post ap_url as valid IRI' (#947) from post-slug into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/947
2021-09-07 05:36:00 +00:00
Kitaiti Makoto 0fbefe2cdc Percent-encode Post slug on sending Activity to other instances 2021-09-07 13:31:06 +09:00
Kitaiti Makoto eedd5fe4e9 Define utility function to percent-encode for IRI 2021-09-06 17:49:10 +09:00
KitaitiMakoto 27a1a56223 Merge pull request 'Make Feature list more clear' (#946) from github-856 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/946
2021-09-05 12:50:49 +00:00
Kitaiti Makoto 8e3322776a Merge remote-tracking branch 'origin/main' into github-856 2021-09-05 21:49:50 +09:00
KitaitiMakoto 3d4336c548 Merge pull request 'update some dependancies' (#945) from update-deps into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/945
Reviewed-by: trinity-1686a <trinity-1686a@noreply@joinplu.me>
2021-09-05 12:48:07 +00:00
Trinity Pointard 0c8c607aa4 update some dependancies 2021-09-05 14:41:36 +02:00
D5k H3h 60d6734fe2 Make Feature list more clear
Say it, when something is currently missing.
Otherwise, people will get confused.
2021-09-05 11:58:02 +02:00
KitaitiMakoto 8c372aa6fc Merge pull request 'Fixes #929 Don't stip shipped binaries' (#942) from dont-strip into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/942
2021-08-15 15:46:02 +00:00
Kitaiti Makoto d1a74ca8e6 Don't stip shipped binaries 2021-08-16 00:44:15 +09:00
KitaitiMakoto c374e0af4c Merge pull request 'Upgrade bcrypt create' (#941) from update-blowfish into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/941
2021-08-14 10:23:41 +00:00
Kitaiti Makoto 58e8569048 Install bcrypt 2021-08-14 19:20:30 +09:00
Kitaiti Makoto 83dbf2a945 Upgrade bcrypt 2021-08-14 19:20:21 +09:00
trinity-1686a d7d6d5f644 Merge pull request 'Draw side line for blockquote on start' (#933) from ahangarha/Plume:ahangarha-patch-1-blockquote-line into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/933
Reviewed-by: trinity-1686a <trinity-1686a@noreply@joinplu.me>
2021-07-13 10:16:26 +00:00
trinity-1686a 9aa9885a89 Merge pull request 'Specify rocket version' (#937) from zynnnn/Plume:fix-rocket-version into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/937
Reviewed-by: trinity-1686a <trinity-1686a@noreply@joinplu.me>
2021-07-13 10:15:53 +00:00
Trinity Pointard 7fe1e083e0 use newer pulldown-cmark 2021-07-10 13:34:13 +02:00
Tdxdxoz a946823554 Specify rocket version 2021-07-06 22:36:59 +08:00
ahangarha 3a1872c03e Draw side line for blockquote on start
By this change, the line beside blockquote would appear at start. It means if the text is in LTR language, it appears on left and if it is in RTL, the line appears on right.
2021-06-11 11:53:28 +00:00
KitaitiMakoto 5424f9110f Merge pull request 'Add changelog about change of slug' (#921) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/921
2021-04-11 02:45:56 +00:00
Kitaiti Makoto a597816617 Add changelog about change of slug 2021-04-11 11:44:52 +09:00
KitaitiMakoto fa7a44f6bb Merge pull request 'Fix #326 and #721: Keep title in URI' (#920) from keep-title-in-uri into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/920
2021-04-10 08:27:31 +00:00
Kitaiti Makoto dfcdcc1833 Remove heck from plume and plume-models 2021-04-10 16:39:23 +09:00
Kitaiti Makoto 6345a57498 Don't modify article title for slug 2021-04-10 16:37:26 +09:00
Kitaiti Makoto 87457c0ed1 Define Post::slug() 2021-04-10 16:30:50 +09:00
KitaitiMakoto 859a1fd528 Merge pull request 'Set default RUST_LOG to info' (#919) from default-log-level into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/919
2021-04-10 07:04:53 +00:00
Kitaiti Makoto 5069aab584 Set default RUST_LOG to info 2021-04-10 15:59:15 +09:00
KitaitiMakoto 033fde38fe Merge pull request 'Extract Post::ap_url()' (#918) from ap_url into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/918
2021-04-09 08:41:04 +00:00
Kitaiti Makoto 7b2bab0f9d Extract Post::ap_url() 2021-04-09 10:55:09 +09:00
KitaitiMakoto a419ef5319 Merge pull request 'Prevent duplicated posts in 'all' timeline' (#917) from dup-timeline into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/917
2021-03-29 15:31:52 +00:00
Kitaiti Makoto f77dce9f12 Add changelog about fix to duplicated posts in all timeline 2021-03-29 23:35:31 +09:00
Kitaiti Makoto 4c2cd92f0d Prevent duplicated posts in 'all' timeline 2021-03-29 23:33:57 +09:00
KitaitiMakoto dd932e1f15 Merge pull request 'Calculate media URI properly even when MEDIA_UPLOAD_DIRECTORY configured' (#916) from media-directory into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/916
2021-03-28 18:18:14 +00:00
Kitaiti Makoto 4376810d96 Add changelog about fix of MEDIA_UPLOAD_DIRECTORY handling 2021-03-29 02:32:02 +09:00
Kitaiti Makoto c961f4751b Complete pull request number to changelog 2021-03-29 02:29:50 +09:00
Kitaiti Makoto 9e5ec0c9df Calculate media URI properly even when MEDIA_UPLOAD_DIRECTORY configured 2021-03-29 02:29:15 +09:00
KitaitiMakoto 112c034e27 Merge pull request 'Update Post.ap_url when published' (#915) from update-ap-url into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/915
2021-03-28 17:27:45 +00:00
Kitaiti Makoto 664a3ddeea Add changelog about update of ap_url 2021-03-29 02:26:30 +09:00
Kitaiti Makoto 2ffd357d95 Update Post.ap_url when published 2021-03-29 01:38:28 +09:00
KitaitiMakoto 2bc4a13964 Merge pull request 'Add migration to add index to medias.file_path for SQLite3' (#914) from upsert-sqlite into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/914
2021-03-28 14:22:10 +00:00
210 changed files with 16643 additions and 11758 deletions
+6 -4
View File
@@ -10,8 +10,8 @@ executors:
type: boolean type: boolean
default: false default: false
docker: docker:
- image: plumeorg/plume-buildenv:v0.4.0 - image: plumeorg/plume-buildenv:v0.8.0
- image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>> - image: <<#parameters.postgres>>cimg/postgres:14.2<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_DB: plume POSTGRES_DB: plume
@@ -38,7 +38,7 @@ commands:
- restore_cache: - restore_cache:
keys: keys:
- v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-{{ .Branch }} - v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-{{ .Branch }}
- v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-master - v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-main
cache: cache:
description: push cache description: push cache
@@ -63,6 +63,7 @@ commands:
type: boolean type: boolean
default: false default: false
steps: steps:
- run: rustup component add clippy --toolchain nightly-2022-07-19-x86_64-unknown-linux-gnu
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings - run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings
run_with_coverage: run_with_coverage:
@@ -111,6 +112,7 @@ jobs:
name: default name: default
steps: steps:
- restore_env - restore_env
- run: rustup component add rustfmt --toolchain nightly-2022-07-19-x86_64-unknown-linux-gnu
- run: cargo fmt --all -- --check - run: cargo fmt --all -- --check
clippy: clippy:
@@ -258,4 +260,4 @@ workflows:
filters: filters:
branches: branches:
only: only:
- /^master/ - /^main/
+7 -15
View File
@@ -1,4 +1,4 @@
FROM debian:buster-20210208 FROM rust:1
ENV PATH="/root/.cargo/bin:${PATH}" ENV PATH="/root/.cargo/bin:${PATH}"
#install native/circleci/build dependancies #install native/circleci/build dependancies
@@ -6,19 +6,19 @@ RUN apt update &&\
apt install -y --no-install-recommends git ssh tar gzip ca-certificates default-jre&&\ apt install -y --no-install-recommends git ssh tar gzip ca-certificates default-jre&&\
echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \ echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \
| tee -a /etc/apt/sources.list.d/caddy-fury.list &&\ | tee -a /etc/apt/sources.list.d/caddy-fury.list &&\
wget -qO - https://artifacts.crowdin.com/repo/GPG-KEY-crowdin | apt-key add - &&\
echo "deb https://artifacts.crowdin.com/repo/deb/ /" > /etc/apt/sources.list.d/crowdin.list &&\
apt update &&\ apt update &&\
apt install -y --no-install-recommends binutils-dev build-essential cmake curl gcc gettext git libcurl4-openssl-dev libdw-dev libelf-dev libiberty-dev libpq-dev libsqlite3-dev libssl-dev make openssl pkg-config postgresql postgresql-contrib python zlib1g-dev python3-pip zip unzip libclang-dev clang caddy&&\ apt install -y --no-install-recommends binutils-dev build-essential cmake curl gcc gettext git libcurl4-openssl-dev libdw-dev libelf-dev libiberty-dev libpq-dev libsqlite3-dev libssl-dev make openssl pkg-config postgresql postgresql-contrib python zlib1g-dev python3-dev python3-pip python3-setuptools zip unzip libclang-dev clang caddy crowdin3 &&\
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
#install and configure rust #stick rust environment
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-01-15 -y &&\ COPY rust-toolchain ./
rustup component add rustfmt clippy &&\ RUN rustup component add rustfmt clippy
rustup component add rust-std --target wasm32-unknown-unknown
#compile some deps #compile some deps
RUN cargo install wasm-pack &&\ RUN cargo install wasm-pack &&\
cargo install grcov &&\ cargo install grcov &&\
strip /root/.cargo/bin/* &&\
rm -fr ~/.cargo/registry rm -fr ~/.cargo/registry
#set some compilation parametters #set some compilation parametters
@@ -29,11 +29,3 @@ RUN pip3 install selenium
#configure caddy #configure caddy
COPY Caddyfile /Caddyfile COPY Caddyfile /Caddyfile
#install crowdin
RUN mkdir /crowdin && cd /crowdin &&\
curl -O https://downloads.crowdin.com/cli/v2/crowdin-cli.zip &&\
unzip crowdin-cli.zip && rm crowdin-cli.zip &&\
cd * && mv crowdin-cli.jar /usr/local/bin && cd && rm -rf /crowdin &&\
/bin/echo -e '#!/bin/sh\njava -jar /usr/local/bin/crowdin-cli.jar $@' > /usr/local/bin/crowdin &&\
chmod +x /usr/local/bin/crowdin
@@ -0,0 +1 @@
nightly-2022-07-19
+2
View File
@@ -3,3 +3,5 @@ data
Dockerfile Dockerfile
docker-compose.yml docker-compose.yml
.env .env
target
data
+1 -1
View File
@@ -16,7 +16,7 @@ DATABASE_URL=postgres://plume:plume@localhost/plume
BASE_URL=plu.me BASE_URL=plu.me
# Log level for each crate # Log level for each crate
RUST_LOG=warn,html5ever=warn,hyper=warn,tantivy=warn RUST_LOG=info
# The secret key for private cookies and CSRF protection # The secret key for private cookies and CSRF protection
# You can generate one with `openssl rand -base64 32` # You can generate one with `openssl rand -base64 32`
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: daily
@@ -0,0 +1,30 @@
name: cd
on:
push:
branches:
- 'main'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v3
with:
push: true
tags: plumeorg/plume:latest
+36
View File
@@ -0,0 +1,36 @@
name: cd
on:
push:
tags:
- '*.*.*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: plumeorg/plume
-
name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v3
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
+80 -2
View File
@@ -6,19 +6,88 @@
### Added ### Added
- Add 'My feed' to i18n timeline name (#1084)
- Bidirectional support for user page header (#1092)
### Changed
- Use blog title as slug (#1094, #1126, #1127)
- Bump Rust to nightly 2022-07-19 (#1119)
### Fixed
- Malfunction while creating a blog post in Persian (#1116)
- Email block list is ignored when email sign-up (#1122)
- Bug that some Activity Sytreams properties are not parsed properly (#1129)
- Allow empty avatar for remote users (#1129)
- Percent encode blog FQN for federation interoperability (#1129)
- The same to `preferredUsername` (#1129)
## [[0.7.2]] - 2022-05-11
### Added
- Basque language (#1013)
- Unit tests for ActivityPub (#1021)
- Move to action area after liking/boosting/commenting (#1074)
### Changed
- Bump Rust to nightly 2022-01-26 (#1015)
- Remove "Latest articles" timeline (#1069)
- Change order of timeline tabs (#1069, #1070, #1072)
- Migrate ActivityPub-related crates from activitypub 0.1 to activitystreams 0.7 (#1022)
### Fixed
- Add explanation of sign-up step at sign-up page when email sign-up mode (#1012)
- Add NOT NULL constraint to email_blocklist table fields (#1016)
- Don't fill empty content when switching rich editor (#1017)
- Fix accept header (#1058)
- Render 404 page instead of 500 when data is not found (#1062)
- Reuse reqwest client on broadcasting (#1059)
- Reduce broadcasting HTTP request at once to prevent them being timed out (#1068, #1071)
- Some ActivityPub data (#1021)
## [[0.7.1]] - 2022-01-12
### Added
- Introduce environment variable `MAIL_PORT` (#980)
- Introduce email sign-up feature (#636, #1002)
### Changed
- Some styling improvements (#976, #977, #978)
- Respond with error status code when error (#1002)
### Fiexed
- Fix comment link (#974)
- Fix a bug that prevents posting articles (#975)
- Fix a bug that notification page doesn't show (#981)
## [[0.7.0]] - 2022-01-02
### Added
- Allow `dir` attributes for LtoR text in RtoL document (#860) - Allow `dir` attributes for LtoR text in RtoL document (#860)
- More translation languages (#862) - More translation languages (#862)
- Proxy support (#829) - Proxy support (#829)
- Riker a actor system library (#870) - Riker a actor system library (#870)
- (request-target) and Host header in HTTP Signature (#872) - (request-target) and Host header in HTTP Signature (#872)
- Default log levels for RUST_LOG (#885, #886, #919)
### Changed ### Changed
- Upgrade some dependent crates (#858) - Upgrade some dependent crates (#858)
- Use tracing crate (#868) - Use tracing crate (#868)
- Update Rust version to nightly-2021-01-15 (#878) - Update Rust version to nightly-2021-11-27 (#961)
- Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878) - Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878)
- Run searcher on actor system (#870) - Run searcher on actor system (#870)
- Extract a function to calculate posts' ap_url and share it with some places (#918)
- Use article title as its slug instead of capitalizing and inserting hyphens (#920)
- Sign GET requests to other instances (#957)
### Fixed ### Fixed
@@ -26,6 +95,12 @@
- Menu animation not opening on iOS (#876, #897) - Menu animation not opening on iOS (#876, #897)
- Make actors subscribe to channel once (#913) - Make actors subscribe to channel once (#913)
- Upsert posts and media instead of trying to insert and fail (#912) - Upsert posts and media instead of trying to insert and fail (#912)
- Update post's ActivityPub id when published by update (#915)
- Calculate media URI properly even when MEDIA_UPLOAD_DIRECTORY configured (#916)
- Prevent duplicated posts in 'all' timeline (#917)
- Draw side line for blockquote on start (#933)
- Fix URIs of posts on Mastodon (#947)
- Place edit link proper position (#956, #963, #964)
## [[0.6.0]] - 2020-12-29 ## [[0.6.0]] - 2020-12-29
@@ -206,7 +281,10 @@
- Ability to create multiple blogs - Ability to create multiple blogs
<!-- next-url --> <!-- next-url -->
[Unreleased]: https://github.com/Plume-org/Plume/compare/0.6.0...HEAD [Unreleased]: https://github.com/Plume-org/Plume/compare/0.7.2...HEAD
[[0.7.2]]: https://github.com/Plume-org/Plume/compare/0.7.1...0.7.2
[[0.7.1]]: https://github.com/Plume-org/Plume/compare/0.7.0...0.7.1
[[0.7.0]]: https://github.com/Plume-org/Plume/compare/0.6.0...0.7.0
[[0.6.0]]: https://github.com/Plume-org/Plume/compare/0.5.0...0.6.0 [[0.6.0]]: https://github.com/Plume-org/Plume/compare/0.5.0...0.6.0
[0.5.0]: https://github.com/Plume-org/Plume/compare/0.4.0-alpha-4...0.5.0 [0.5.0]: https://github.com/Plume-org/Plume/compare/0.4.0-alpha-4...0.5.0
[0.4.0]: https://github.com/Plume-org/Plume/compare/0.3.0-alpha-2...0.4.0-alpha-4 [0.4.0]: https://github.com/Plume-org/Plume/compare/0.3.0-alpha-2...0.4.0-alpha-4
Generated
+1926 -1354
View File
File diff suppressed because it is too large Load Diff
+23 -28
View File
@@ -1,38 +1,33 @@
[package] [package]
authors = ["Plume contributors"] authors = ["Plume contributors"]
name = "plume" name = "plume"
version = "0.6.1-dev" version = "0.7.3-dev"
repository = "https://github.com/Plume-org/Plume" repository = "https://github.com/Plume-org/Plume"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.3" atom_syndication = "0.11.0"
askama_escape = "0.1"
atom_syndication = "0.6"
clap = "2.33" clap = "2.33"
dotenv = "0.15.0" dotenv = "0.15.0"
gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" } gettext = "0.4.0"
gettext-macros = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-macros = "0.6.1"
gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-utils = "0.1.0"
guid-create = "0.1" guid-create = "0.2"
heck = "0.3.0"
lettre = "0.9.2"
lettre_email = "0.9.2" lettre_email = "0.9.2"
num_cpus = "1.10" num_cpus = "1.10"
rocket = "0.4.6" rocket = "0.4.11"
rocket_contrib = { version = "0.4.5", features = ["json"] } rocket_contrib = { version = "0.4.11", features = ["json"] }
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" } rocket_i18n = "0.4.1"
rpassword = "4.0" scheduled-thread-pool = "0.2.6"
scheduled-thread-pool = "0.2.2" serde = "1.0.137"
serde = "1.0" serde_json = "1.0.81"
serde_json = "1.0" shrinkwraprs = "0.3.0"
shrinkwraprs = "0.2.1" validator = { version = "0.15", features = ["derive"] }
validator = "0.8"
validator_derive = "0.8"
webfinger = "0.4.1" webfinger = "0.4.1"
tracing = "0.1.22" tracing = "0.1.35"
tracing-subscriber = "0.2.15" tracing-subscriber = "0.3.10"
riker = "0.4.2" riker = "0.4.2"
activitystreams = "=0.7.0-alpha.20"
[[bin]] [[bin]]
name = "plume" name = "plume"
@@ -44,7 +39,7 @@ version = "0.4"
[dependencies.ctrlc] [dependencies.ctrlc]
features = ["termination"] features = ["termination"]
version = "3.1.2" version = "3.2.2"
[dependencies.diesel] [dependencies.diesel]
features = ["r2d2", "chrono"] features = ["r2d2", "chrono"]
@@ -53,7 +48,7 @@ version = "1.4.5"
[dependencies.multipart] [dependencies.multipart]
default-features = false default-features = false
features = ["server"] features = ["server"]
version = "0.16" version = "0.18"
[dependencies.plume-api] [dependencies.plume-api]
path = "plume-api" path = "plume-api"
@@ -65,12 +60,12 @@ path = "plume-common"
path = "plume-models" path = "plume-models"
[dependencies.rocket_csrf] [dependencies.rocket_csrf]
git = "https://github.com/fdb-hiroshima/rocket_csrf" git = "https://git.joinplu.me/plume/rocket_csrf"
rev = "29910f2829e7e590a540da3804336577b48c7b31" rev = "0.1.2"
[build-dependencies] [build-dependencies]
ructe = "0.13.0" ructe = "0.14.0"
rsass = "0.9" rsass = "0.25"
[features] [features]
default = ["postgres"] default = ["postgres"]
+3 -5
View File
@@ -1,4 +1,4 @@
FROM rust:1-buster as builder FROM rust:1 as builder
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
@@ -18,17 +18,15 @@ COPY script/wasm-deps.sh .
RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh
WORKDIR /app WORKDIR /app
COPY Cargo.toml Cargo.lock rust-toolchain ./
RUN cargo install wasm-pack
COPY . . COPY . .
RUN cargo install wasm-pack
RUN chmod a+x ./script/plume-front.sh && sleep 1 && ./script/plume-front.sh RUN chmod a+x ./script/plume-front.sh && sleep 1 && ./script/plume-front.sh
RUN cargo install --path ./ --force --no-default-features --features postgres RUN cargo install --path ./ --force --no-default-features --features postgres
RUN cargo install --path plume-cli --force --no-default-features --features postgres RUN cargo install --path plume-cli --force --no-default-features --features postgres
RUN cargo clean RUN cargo clean
FROM debian:buster-slim FROM debian:stable-slim
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
+4 -3
View File
@@ -1,10 +1,10 @@
<h1 align="center"> <h1 align="center">
<img src="https://raw.githubusercontent.com/Plume-org/Plume/master/assets/icons/trwnh/feather/plumeFeather64.png" alt="Plume's logo"> <img src="https://raw.githubusercontent.com/Plume-org/Plume/main/assets/icons/trwnh/feather/plumeFeather64.png" alt="Plume's logo">
Plume Plume
</h1> </h1>
<p align="center"> <p align="center">
<a href="https://github.com/Plume-org/Plume/"><img alt="CircleCI" src="https://img.shields.io/circleci/build/gh/Plume-org/Plume.svg"></a> <a href="https://github.com/Plume-org/Plume/"><img alt="CircleCI" src="https://img.shields.io/circleci/build/gh/Plume-org/Plume.svg"></a>
<a href="https://codecov.io/gh/Plume-org/Plume"><img src="https://codecov.io/gh/Plume-org/Plume/branch/master/graph/badge.svg" alt="Code coverage"></a> <a href="https://codecov.io/gh/Plume-org/Plume"><img src="https://codecov.io/gh/Plume-org/Plume/branch/main/graph/badge.svg" alt="Code coverage"></a>
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/plume"><img src="https://d322cqt584bo4o.cloudfront.net/plume/localized.svg"></a> <a title="Crowdin" target="_blank" href="https://crowdin.com/project/plume"><img src="https://d322cqt584bo4o.cloudfront.net/plume/localized.svg"></a>
<a href="https://hub.docker.com/r/plumeorg/plume"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/plumeorg/plume.svg"></a> <a href="https://hub.docker.com/r/plumeorg/plume"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/plumeorg/plume.svg"></a>
<a href="https://liberapay.com/Plume"><img alt="Liberapay patrons" src="https://img.shields.io/liberapay/patrons/Plume.svg"></a> <a href="https://liberapay.com/Plume"><img alt="Liberapay patrons" src="https://img.shields.io/liberapay/patrons/Plume.svg"></a>
@@ -30,7 +30,7 @@ A lot of features are still missing, but what is already here should be quite st
- **Media management**: you can upload pictures to illustrate your articles, but also audio files if you host a podcast, and manage them all from Plume. - **Media management**: you can upload pictures to illustrate your articles, but also audio files if you host a podcast, and manage them all from Plume.
- **Federation**: Plume is part of a network of interconnected websites called the Fediverse. Each of these websites (often called *instances*) have their own - **Federation**: Plume is part of a network of interconnected websites called the Fediverse. Each of these websites (often called *instances*) have their own
rules and thematics, but they can all communicate with each other. rules and thematics, but they can all communicate with each other.
- **Collaborative writing**: invite other people to your blogs, and write articles together. - **Collaborative writing**: invite other people to your blogs, and write articles together. (Not implemented yet, but will be in 1.0)
## Get involved ## Get involved
@@ -53,3 +53,4 @@ As we want the various spaces related to the project (GitHub, Matrix, Loomio, et
We provide various way to install Plume: from source, with pre-built binaries, with Docker or with YunoHost. We provide various way to install Plume: from source, with pre-built binaries, with Docker or with YunoHost.
For detailed explanations, please refer to [the documentation](https://docs.joinplu.me/installation/). For detailed explanations, please refer to [the documentation](https://docs.joinplu.me/installation/).
+8 -1
View File
@@ -98,7 +98,7 @@ main article {
} }
blockquote { blockquote {
border-left: 5px solid $gray; border-inline-start: 5px solid $gray;
margin: 1em auto; margin: 1em auto;
padding: 0em 2em; padding: 0em 2em;
} }
@@ -516,4 +516,11 @@ input:checked ~ .cw-container > .cw-text {
main .article-meta > *, main .article-meta .comments, main .article-meta > .banner > * { main .article-meta > *, main .article-meta .comments, main .article-meta > .banner > * {
margin: 0 5%; margin: 0 5%;
} }
.bottom-bar {
align-items: center;
& > div:nth-child(2) {
margin: 0;
}
}
} }
+1
View File
@@ -135,6 +135,7 @@ form.new-post {
.button + .button { .button + .button {
margin-left: 1em; margin-left: 1em;
margin-inline-start: 1em;
} }
.split { .split {
+23 -15
View File
@@ -219,15 +219,23 @@ p.error {
margin: 20px; margin: 20px;
} }
.cover-link {
margin: 0;
&:hover {
opacity: 0.9;
}
}
.cover { .cover {
min-height: 10em; min-height: 10em;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
margin: 0px; margin: 0px;
}
&:hover { header {
opacity: 0.9; display: flex;
}
} }
h3 { h3 {
@@ -236,9 +244,14 @@ p.error {
font-family: $playfair; font-family: $playfair;
font-size: 1.75em; font-size: 1.75em;
font-weight: normal; font-weight: normal;
line-height: 1.75; line-height: 1.10;
display: inline-block;
position: relative;
a { a {
display: block; display: block;
width: 100%;
height: 100%;
padding-block-start: 0.5em;
transition: color 0.1s ease-in; transition: color 0.1s ease-in;
color: $text-color; color: $text-color;
@@ -247,7 +260,8 @@ p.error {
} }
.controls { .controls {
float: right; flex-shrink: 0;
text-align: end;
.button { .button {
margin-top: 0; margin-top: 0;
@@ -261,7 +275,7 @@ p.error {
font-family: $lora; font-family: $lora;
font-size: 1em; font-size: 1em;
line-height: 1.25; line-height: 1.25;
text-align: left; text-align: initial;
overflow: hidden; overflow: hidden;
} }
} }
@@ -465,9 +479,10 @@ figure {
/// Avatars /// Avatars
.avatar { .avatar {
background-position: center; background-position: center !important;
background-size: cover; background-size: cover;
border-radius: 100%; border-radius: 100%;
flex-shrink: 0;
&.small { &.small {
width: 50px; width: 50px;
@@ -492,6 +507,7 @@ figure {
margin: auto $horizontal-margin 2em; margin: auto $horizontal-margin 2em;
overflow: auto; overflow: auto;
display: flex; display: flex;
justify-content: center;
a { a {
display: inline-block; display: inline-block;
@@ -561,14 +577,6 @@ figure {
} }
} }
.bottom-bar {
flex-direction: column;
align-items: center;
& > div {
margin: 0;
}
}
main .article-meta .comments .comment { main .article-meta .comments .comment {
header { header {
flex-direction: column; flex-direction: column;
+13 -7
View File
@@ -41,9 +41,9 @@ fn main() {
.expect("compile templates"); .expect("compile templates");
compile_themes().expect("Theme compilation error"); compile_themes().expect("Theme compilation error");
recursive_copy(&Path::new("assets").join("icons"), &Path::new("static")) recursive_copy(&Path::new("assets").join("icons"), Path::new("static"))
.expect("Couldn't copy icons"); .expect("Couldn't copy icons");
recursive_copy(&Path::new("assets").join("images"), &Path::new("static")) recursive_copy(&Path::new("assets").join("images"), Path::new("static"))
.expect("Couldn't copy images"); .expect("Couldn't copy images");
create_dir_all(&Path::new("static").join("media")).expect("Couldn't init media directory"); create_dir_all(&Path::new("static").join("media")).expect("Couldn't init media directory");
@@ -97,12 +97,12 @@ fn compile_theme(path: &Path, out_dir: &Path) -> std::io::Result<()> {
.components() .components()
.skip_while(|c| *c != Component::Normal(OsStr::new("themes"))) .skip_while(|c| *c != Component::Normal(OsStr::new("themes")))
.skip(1) .skip(1)
.filter_map(|c| { .map(|c| {
c.as_os_str() c.as_os_str()
.to_str() .to_str()
.unwrap_or_default() .unwrap_or_default()
.splitn(2, '.') .split_once('.')
.next() .map_or(c.as_os_str().to_str().unwrap_or_default(), |x| x.0)
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("-"); .join("-");
@@ -120,8 +120,14 @@ fn compile_theme(path: &Path, out_dir: &Path) -> std::io::Result<()> {
// compile the .scss/.sass file // compile the .scss/.sass file
let mut out = File::create(out.join("theme.css"))?; let mut out = File::create(out.join("theme.css"))?;
out.write_all( out.write_all(
&rsass::compile_scss_file(path, rsass::OutputStyle::Compressed) &rsass::compile_scss_path(
.expect("SCSS compilation error"), path,
rsass::output::Format {
style: rsass::output::Style::Compressed,
..rsass::output::Format::default()
},
)
.expect("SCSS compilation error"),
)?; )?;
Ok(()) Ok(())
@@ -0,0 +1 @@
DROP TABLE email_signups;
@@ -0,0 +1,9 @@
CREATE TABLE email_signups (
id SERIAL PRIMARY KEY,
email VARCHAR NOT NULL,
token VARCHAR NOT NULL,
expiration_date TIMESTAMP NOT NULL
);
CREATE INDEX email_signups_token ON email_signups (token);
CREATE UNIQUE INDEX email_signups_token_requests_email ON email_signups (email);
@@ -0,0 +1,4 @@
ALTER TABLE email_blocklist ALTER COLUMN notification_text DROP NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN notify_user DROP NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN note DROP NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN email_address DROP NOT NULL;
@@ -0,0 +1,4 @@
ALTER TABLE email_blocklist ALTER COLUMN email_address SET NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN note SET NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN notify_user SET NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN notification_text SET NOT NULL;
@@ -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;
@@ -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);
@@ -0,0 +1 @@
DROP TABLE email_signups;
@@ -0,0 +1,9 @@
CREATE TABLE email_signups (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
email VARCHAR NOT NULL,
token VARCHAR NOT NULL,
expiration_date TIMESTAMP NOT NULL
);
CREATE INDEX email_signups_token ON email_signups (token);
CREATE UNIQUE INDEX email_signups_token_requests_email ON email_signups (email);
@@ -0,0 +1,9 @@
CREATE TABLE email_blocklist2(id INTEGER PRIMARY KEY,
email_address TEXT UNIQUE,
note TEXT,
notify_user BOOLEAN DEFAULT FALSE,
notification_text TEXT);
INSERT INTO email_blocklist2 SELECT * FROM email_blocklist;
DROP TABLE email_blocklist;
ALTER TABLE email_blocklist2 RENAME TO email_blocklist;
@@ -0,0 +1,9 @@
CREATE TABLE email_blocklist2(id INTEGER PRIMARY KEY,
email_address TEXT UNIQUE NOT NULL,
note TEXT NOT NULL,
notify_user BOOLEAN DEFAULT FALSE NOT NULL,
notification_text TEXT NOT NULL);
INSERT INTO email_blocklist2 SELECT * FROM email_blocklist;
DROP TABLE email_blocklist;
ALTER TABLE email_blocklist2 RENAME TO email_blocklist;
@@ -0,0 +1,3 @@
DROP INDEX users_fqn;
DROP INDEX blogs_actor_id;
DROP INDEX blogs_fqn;
@@ -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);
+2 -2
View File
@@ -1,9 +1,9 @@
[package] [package]
name = "plume-api" name = "plume-api"
version = "0.6.1-dev" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
serde = "1.0" serde = "1.0.137"
serde_derive = "1.0" serde_derive = "1.0"
+1
View File
@@ -1,2 +1,3 @@
pre-release-hook = ["cargo", "fmt"] pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = [] pre-release-replacements = []
release = false
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-cli" name = "plume-cli"
version = "0.6.1-dev" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -10,8 +10,8 @@ path = "src/main.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
dotenv = "0.14" dotenv = "0.15"
rpassword = "5.0.0" rpassword = "6.0.1"
[dependencies.diesel] [dependencies.diesel]
features = ["r2d2", "chrono"] features = ["r2d2", "chrono"]
+1
View File
@@ -1,2 +1,3 @@
pre-release-hook = ["cargo", "fmt"] pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = [] pre-release-replacements = []
release = false
+2
View File
@@ -68,4 +68,6 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
}, },
) )
.expect("Couldn't save instance"); .expect("Couldn't save instance");
Instance::cache_local(conn);
Instance::create_local_instance_user(conn).expect("Couldn't save local instance user");
} }
+1 -1
View File
@@ -25,7 +25,7 @@ fn main() {
e => e.map(|_| ()).unwrap(), e => e.map(|_| ()).unwrap(),
} }
let conn = Conn::establish(CONFIG.database_url.as_str()); let conn = Conn::establish(CONFIG.database_url.as_str());
let _ = conn.as_ref().map(|conn| Instance::cache_local(conn)); let _ = conn.as_ref().map(Instance::cache_local);
match matches.subcommand() { match matches.subcommand() {
("instance", Some(args)) => { ("instance", Some(args)) => {
+24 -16
View File
@@ -1,29 +1,31 @@
[package] [package]
name = "plume-common" name = "plume-common"
version = "0.6.1-dev" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.1"
activitystreams-derive = "0.1.1"
activitystreams-traits = "0.1.0"
array_tool = "1.0" array_tool = "1.0"
base64 = "0.10" base64 = "0.13"
heck = "0.3.0" hex = "0.4"
hex = "0.3" openssl = "0.10.40"
hyper = "0.12.33" rocket = "0.4.11"
openssl = "0.10.22" reqwest = { version = "0.11.11", features = ["blocking", "json", "socks"] }
rocket = "0.4.6" serde = "1.0.137"
reqwest = { version = "0.9", features = ["socks"] }
serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0.81"
shrinkwraprs = "0.3.0" shrinkwraprs = "0.3.0"
syntect = "4.5.0" syntect = "4.5.0"
tokio = "0.1.22" regex-syntax = { version = "0.6.26", default-features = false, features = ["unicode-perl"] }
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] } tracing = "0.1.35"
tracing = "0.1.22" askama_escape = "0.10.3"
activitystreams = "=0.7.0-alpha.20"
activitystreams-ext = "0.1.0-alpha.2"
url = "2.2.2"
flume = "0.10.13"
tokio = { version = "1.19.2", features = ["full"] }
futures = "0.3.25"
thiserror = "1.0.38"
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
@@ -33,3 +35,9 @@ version = "0.4"
default-features = false default-features = false
git = "https://git.joinplu.me/Plume/pulldown-cmark" git = "https://git.joinplu.me/Plume/pulldown-cmark"
branch = "bidi-plume" branch = "bidi-plume"
[dev-dependencies]
assert-json-diff = "2.0.1"
once_cell = "1.12.0"
[features]
+1
View File
@@ -1,2 +1,3 @@
pre-release-hook = ["cargo", "fmt"] pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = [] pre-release-replacements = []
release = false
+231 -85
View File
@@ -1,6 +1,8 @@
use reqwest::header::{HeaderValue, ACCEPT}; use reqwest;
use std::fmt::Debug; use std::fmt::Debug;
use super::{request, sign::Signer};
/// Represents an ActivityPub inbox. /// Represents an ActivityPub inbox.
/// ///
/// It routes an incoming Activity through the registered handlers. /// It routes an incoming Activity through the registered handlers.
@@ -8,9 +10,51 @@ use std::fmt::Debug;
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # extern crate activitypub; /// # use activitystreams::{prelude::*, base::Base, actor::Person, activity::{Announce, Create}, object::Note, iri_string::types::IriString};
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note}; /// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
/// # use once_cell::sync::Lazy;
/// # use plume_common::activity_pub::inbox::*; /// # use plume_common::activity_pub::inbox::*;
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
/// #
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
/// #
/// # struct MySigner {
/// # public_key: String,
/// # private_key: String,
/// # }
/// #
/// # impl MySigner {
/// # fn new() -> Self {
/// # let (pub_key, priv_key) = gen_keypair();
/// # Self {
/// # public_key: String::from_utf8(pub_key).unwrap(),
/// # private_key: String::from_utf8(priv_key).unwrap(),
/// # }
/// # }
/// # }
/// #
/// # impl Signer for MySigner {
/// # fn get_key_id(&self) -> String {
/// # "mysigner".into()
/// # }
/// #
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
/// # .unwrap();
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
/// # signer.update(to_sign.as_bytes()).unwrap();
/// # signer.sign_to_vec().map_err(|_| SignError())
/// # }
/// #
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
/// # .unwrap();
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
/// # verifier.update(data.as_bytes()).unwrap();
/// # verifier.verify(&signature).map_err(|_| SignError())
/// # }
/// # }
/// #
/// # struct User; /// # struct User;
/// # impl FromId<()> for User { /// # impl FromId<()> for User {
/// # type Error = (); /// # type Error = ();
@@ -23,6 +67,10 @@ use std::fmt::Debug;
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> { /// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
/// # Ok(User) /// # Ok(User)
/// # } /// # }
/// #
/// # fn get_sender() -> &'static dyn Signer {
/// # &*MY_SIGNER
/// # }
/// # } /// # }
/// # impl AsActor<&()> for User { /// # impl AsActor<&()> for User {
/// # fn get_inbox_url(&self) -> String { /// # fn get_inbox_url(&self) -> String {
@@ -42,6 +90,10 @@ use std::fmt::Debug;
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> { /// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
/// # Ok(Message) /// # Ok(Message)
/// # } /// # }
/// #
/// # fn get_sender() -> &'static dyn Signer {
/// # &*MY_SIGNER
/// # }
/// # } /// # }
/// # impl AsObject<User, Create, &()> for Message { /// # impl AsObject<User, Create, &()> for Message {
/// # type Error = (); /// # type Error = ();
@@ -60,12 +112,13 @@ use std::fmt::Debug;
/// # } /// # }
/// # } /// # }
/// # /// #
/// # let mut act = Create::default(); /// # let mut person = Person::new();
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap(); /// # person.set_id("https://test.ap/actor".parse::<IriString>().unwrap());
/// # let mut person = Person::default(); /// # let mut act = Create::new(
/// # person.object_props.set_id_string(String::from("https://test.ap/actor")).unwrap(); /// # Base::retract(person).unwrap().into_generic().unwrap(),
/// # act.create_props.set_actor_object(person).unwrap(); /// # Base::retract(Note::new()).unwrap().into_generic().unwrap()
/// # act.create_props.set_object_object(Note::default()).unwrap(); /// # );
/// # act.set_id("https://test.ap/activity".parse::<IriString>().unwrap());
/// # let activity_json = serde_json::to_value(act).unwrap(); /// # let activity_json = serde_json::to_value(act).unwrap();
/// # /// #
/// # let conn = (); /// # let conn = ();
@@ -144,29 +197,29 @@ where
} }
/// Registers an handler on this Inbox. /// Registers an handler on this Inbox.
pub fn with<A, V, M>(self, proxy: Option<&reqwest::Proxy>) -> Inbox<'a, C, E, R> pub fn with<A, V, M>(self, proxy: Option<&reqwest::Proxy>) -> Self
where where
A: AsActor<&'a C> + FromId<C, Error = E>, A: AsActor<&'a C> + FromId<C, Error = E>,
V: activitypub::Activity, V: activitystreams::markers::Activity + serde::de::DeserializeOwned,
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>, M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
M::Output: Into<R>, M::Output: Into<R>,
{ {
if let Inbox::NotHandled(ctx, mut act, e) = self { if let Self::NotHandled(ctx, mut act, e) = self {
if serde_json::from_value::<V>(act.clone()).is_ok() { if serde_json::from_value::<V>(act.clone()).is_ok() {
let act_clone = act.clone(); let act_clone = act.clone();
let act_id = match act_clone["id"].as_str() { let act_id = match act_clone["id"].as_str() {
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidID), None => return Self::NotHandled(ctx, act, InboxError::InvalidID),
}; };
// Get the actor ID // Get the actor ID
let actor_id = match get_id(act["actor"].clone()) { let actor_id = match get_id(act["actor"].clone()) {
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)), None => return Self::NotHandled(ctx, act, InboxError::InvalidActor(None)),
}; };
if Self::is_spoofed_activity(&actor_id, &act) { if Self::is_spoofed_activity(&actor_id, &act) {
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None)); return Self::NotHandled(ctx, act, InboxError::InvalidObject(None));
} }
// Transform this actor to a model (see FromId for details about the from_id function) // Transform this actor to a model (see FromId for details about the from_id function)
@@ -182,14 +235,14 @@ where
if let Some(json) = json { if let Some(json) = json {
act["actor"] = json; act["actor"] = json;
} }
return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(Some(e))); return Self::NotHandled(ctx, act, InboxError::InvalidActor(Some(e)));
} }
}; };
// Same logic for "object" // Same logic for "object"
let obj_id = match get_id(act["object"].clone()) { let obj_id = match get_id(act["object"].clone()) {
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None)), None => return Self::NotHandled(ctx, act, InboxError::InvalidObject(None)),
}; };
let obj = match M::from_id( let obj = match M::from_id(
ctx, ctx,
@@ -202,19 +255,19 @@ where
if let Some(json) = json { if let Some(json) = json {
act["object"] = json; act["object"] = json;
} }
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(Some(e))); return Self::NotHandled(ctx, act, InboxError::InvalidObject(Some(e)));
} }
}; };
// Handle the activity // Handle the activity
match obj.activity(ctx, actor, &act_id) { match obj.activity(ctx, actor, act_id) {
Ok(res) => Inbox::Handled(res.into()), Ok(res) => Self::Handled(res.into()),
Err(e) => Inbox::Failed(e), Err(e) => Self::Failed(e),
} }
} else { } else {
// If the Activity type is not matching the expected one for // If the Activity type is not matching the expected one for
// this handler, try with the next one. // this handler, try with the next one.
Inbox::NotHandled(ctx, act, e) Self::NotHandled(ctx, act, e)
} }
} else { } else {
self self
@@ -280,7 +333,7 @@ pub trait FromId<C>: Sized {
type Error: From<InboxError<Self::Error>> + Debug; type Error: From<InboxError<Self::Error>> + Debug;
/// The ActivityPub object type representing Self /// The ActivityPub object type representing Self
type Object: activitypub::Object; type Object: activitystreams::markers::Object + serde::de::DeserializeOwned;
/// Tries to get an instance of `Self` from an ActivityPub ID. /// Tries to get an instance of `Self` from an ActivityPub ID.
/// ///
@@ -311,35 +364,16 @@ pub trait FromId<C>: Sized {
id: &str, id: &str,
proxy: Option<reqwest::Proxy>, proxy: Option<reqwest::Proxy>,
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> { ) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
if let Some(proxy) = proxy { request::get(id, Self::get_sender(), proxy)
reqwest::ClientBuilder::new().proxy(proxy) .map_err(|_| (None, InboxError::DerefError))
} else { .and_then(|r| {
reqwest::ClientBuilder::new() let json: serde_json::Value = r
} .json()
.connect_timeout(Some(std::time::Duration::from_secs(5))) .map_err(|_| (None, InboxError::InvalidObject(None)))?;
.build() serde_json::from_value(json.clone())
.map_err(|_| (None, InboxError::DerefError.into()))? .map_err(|_| (Some(json), InboxError::InvalidObject(None)))
.get(id) })
.header( .map_err(|(json, e)| (json, e.into()))
ACCEPT,
HeaderValue::from_str(
&super::ap_accept_header()
.into_iter()
.collect::<Vec<_>>()
.join(", "),
)
.map_err(|_| (None, InboxError::DerefError.into()))?,
)
.send()
.map_err(|_| (None, InboxError::DerefError))
.and_then(|mut r| {
let json: serde_json::Value = r
.json()
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
serde_json::from_value(json.clone())
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
})
.map_err(|(json, e)| (json, e.into()))
} }
/// Builds a `Self` from its ActivityPub representation /// Builds a `Self` from its ActivityPub representation
@@ -347,6 +381,8 @@ pub trait FromId<C>: Sized {
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database) /// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>; fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
fn get_sender() -> &'static dyn Signer;
} }
/// Should be implemented by anything representing an ActivityPub actor. /// Should be implemented by anything representing an ActivityPub actor.
@@ -382,9 +418,51 @@ pub trait AsActor<C> {
/// representing the Note by a Message type, without any specific context. /// representing the Note by a Message type, without any specific context.
/// ///
/// ```rust /// ```rust
/// # extern crate activitypub; /// # use activitystreams::{prelude::*, activity::Create, actor::Person, object::Note};
/// # use activitypub::{activity::Create, actor::Person, object::Note};
/// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId}; /// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId};
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
/// # use once_cell::sync::Lazy;
/// #
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
/// #
/// # struct MySigner {
/// # public_key: String,
/// # private_key: String,
/// # }
/// #
/// # impl MySigner {
/// # fn new() -> Self {
/// # let (pub_key, priv_key) = gen_keypair();
/// # Self {
/// # public_key: String::from_utf8(pub_key).unwrap(),
/// # private_key: String::from_utf8(priv_key).unwrap(),
/// # }
/// # }
/// # }
/// #
/// # impl Signer for MySigner {
/// # fn get_key_id(&self) -> String {
/// # "mysigner".into()
/// # }
/// #
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
/// # .unwrap();
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
/// # signer.update(to_sign.as_bytes()).unwrap();
/// # signer.sign_to_vec().map_err(|_| SignError())
/// # }
/// #
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
/// # .unwrap();
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
/// # verifier.update(data.as_bytes()).unwrap();
/// # verifier.verify(&signature).map_err(|_| SignError())
/// # }
/// # }
/// #
/// # struct Account; /// # struct Account;
/// # impl FromId<()> for Account { /// # impl FromId<()> for Account {
/// # type Error = (); /// # type Error = ();
@@ -397,6 +475,10 @@ pub trait AsActor<C> {
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> { /// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
/// # Ok(Account) /// # Ok(Account)
/// # } /// # }
/// #
/// # fn get_sender() -> &'static dyn Signer {
/// # &*MY_SIGNER
/// # }
/// # } /// # }
/// # impl AsActor<()> for Account { /// # impl AsActor<()> for Account {
/// # fn get_inbox_url(&self) -> String { /// # fn get_inbox_url(&self) -> String {
@@ -418,7 +500,14 @@ pub trait AsActor<C> {
/// } /// }
/// ///
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> { /// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? }) /// Ok(Message {
/// text: obj.content()
/// .and_then(|content| content.to_owned().single_xsd_string()).ok_or(())?
/// })
/// }
///
/// fn get_sender() -> &'static dyn Signer {
/// &*MY_SIGNER
/// } /// }
/// } /// }
/// ///
@@ -434,7 +523,7 @@ pub trait AsActor<C> {
/// ``` /// ```
pub trait AsObject<A, V, C> pub trait AsObject<A, V, C>
where where
V: activitypub::Activity, V: activitystreams::markers::Activity,
{ {
/// What kind of error is returned when something fails /// What kind of error is returned when something fails
type Error; type Error;
@@ -459,7 +548,57 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use activitypub::{activity::*, actor::Person, object::Note}; use crate::activity_pub::sign::{
gen_keypair, Error as SignError, Result as SignResult, Signer,
};
use activitystreams::{
activity::{Announce, Create, Delete, Like},
actor::Person,
base::Base,
object::Note,
prelude::*,
};
use once_cell::sync::Lazy;
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
static MY_SIGNER: Lazy<MySigner> = Lazy::new(MySigner::new);
struct MySigner {
public_key: String,
private_key: String,
}
impl MySigner {
fn new() -> Self {
let (pub_key, priv_key) = gen_keypair();
Self {
public_key: String::from_utf8(pub_key).unwrap(),
private_key: String::from_utf8(priv_key).unwrap(),
}
}
}
impl Signer for MySigner {
fn get_key_id(&self) -> String {
"mysigner".into()
}
fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
.unwrap();
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
signer.update(to_sign.as_bytes()).unwrap();
signer.sign_to_vec().map_err(|_| SignError())
}
fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
.unwrap();
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
verifier.update(data.as_bytes()).unwrap();
verifier.verify(signature).map_err(|_| SignError())
}
}
struct MyActor; struct MyActor;
impl FromId<()> for MyActor { impl FromId<()> for MyActor {
@@ -467,11 +606,15 @@ mod tests {
type Object = Person; type Object = Person;
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> { fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
Ok(MyActor) Ok(Self)
} }
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> { fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
Ok(MyActor) Ok(Self)
}
fn get_sender() -> &'static dyn Signer {
&*MY_SIGNER
} }
} }
@@ -491,11 +634,15 @@ mod tests {
type Object = Note; type Object = Note;
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> { fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
Ok(MyObject) Ok(Self)
} }
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> { fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
Ok(MyObject) Ok(Self)
}
fn get_sender() -> &'static dyn Signer {
&*MY_SIGNER
} }
} }
impl AsObject<MyActor, Create, &()> for MyObject { impl AsObject<MyActor, Create, &()> for MyObject {
@@ -539,21 +686,15 @@ mod tests {
} }
fn build_create() -> Create { fn build_create() -> Create {
let mut act = Create::default(); let mut person = Person::new();
act.object_props person.set_id("https://test.ap/actor".parse().unwrap());
.set_id_string(String::from("https://test.ap/activity")) let mut note = Note::new();
.unwrap(); note.set_id("https://test.ap/note".parse().unwrap());
let mut person = Person::default(); let mut act = Create::new(
person Base::retract(person).unwrap().into_generic().unwrap(),
.object_props Base::retract(note).unwrap().into_generic().unwrap(),
.set_id_string(String::from("https://test.ap/actor")) );
.unwrap(); act.set_id("https://test.ap/activity".parse().unwrap());
act.create_props.set_actor_object(person).unwrap();
let mut note = Note::default();
note.object_props
.set_id_string(String::from("https://test.ap/note"))
.unwrap();
act.create_props.set_object_object(note).unwrap();
act act
} }
@@ -590,6 +731,16 @@ mod tests {
} }
struct FailingActor; struct FailingActor;
impl AsActor<&()> for FailingActor {
fn get_inbox_url(&self) -> String {
String::from("https://test.ap/failing-actor/inbox")
}
fn is_local(&self) -> bool {
false
}
}
impl FromId<()> for FailingActor { impl FromId<()> for FailingActor {
type Error = (); type Error = ();
type Object = Person; type Object = Person;
@@ -598,17 +749,12 @@ mod tests {
Err(()) Err(())
} }
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> { fn from_activity(_: &(), _obj: Self::Object) -> Result<Self, Self::Error> {
Err(()) Err(())
} }
}
impl AsActor<&()> for FailingActor {
fn get_inbox_url(&self) -> String {
String::from("https://test.ap/failing-actor/inbox")
}
fn is_local(&self) -> bool { fn get_sender() -> &'static dyn Signer {
false &*MY_SIGNER
} }
} }
@@ -636,7 +782,7 @@ mod tests {
.done(); .done();
assert!(res.is_err()); assert!(res.is_err());
let res: Result<(), ()> = Inbox::handle(&(), act.clone()) let res: Result<(), ()> = Inbox::handle(&(), act)
.with::<FailingActor, Create, MyObject>(None) .with::<FailingActor, Create, MyObject>(None)
.with::<MyActor, Create, MyObject>(None) .with::<MyActor, Create, MyObject>(None)
.done(); .done();
+753 -91
View File
@@ -1,13 +1,32 @@
use activitypub::{Activity, Link, Object}; use activitystreams::{
actor::{ApActor, Group, Person},
base::{AnyBase, Base, Extends},
iri_string::types::IriString,
kind,
markers::{self, Activity},
object::{ApObject, Article, Object},
primitives::{AnyString, OneOrMany},
unparsed::UnparsedMutExt,
};
use activitystreams_ext::{Ext1, Ext2, UnparsedExtension};
use array_tool::vec::Uniq; use array_tool::vec::Uniq;
use reqwest::{header::HeaderValue, r#async::ClientBuilder, Url}; use futures::future::join_all;
use reqwest::{header::HeaderValue, ClientBuilder, RequestBuilder, Url};
use rocket::{ use rocket::{
http::Status, http::Status,
request::{FromRequest, Request}, request::{FromRequest, Request},
response::{Responder, Response}, response::{Responder, Response},
Outcome, Outcome,
}; };
use tokio::prelude::*; use std::{
convert::{TryFrom, TryInto},
fmt,
str::FromStr,
};
use tokio::{
runtime,
time::{sleep, Duration},
};
use tracing::{debug, warn}; use tracing::{debug, warn};
use self::sign::Signable; use self::sign::Signable;
@@ -24,8 +43,8 @@ pub const AP_CONTENT_TYPE: &str =
pub fn ap_accept_header() -> Vec<&'static str> { pub fn ap_accept_header() -> Vec<&'static str> {
vec![ vec![
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\"", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"application/ld+json;profile=\"https://w3.org/ns/activitystreams\"", "application/ld+json;profile=\"https://www.w3.org/ns/activitystreams\"",
"application/activity+json", "application/activity+json",
"application/ld+json", "application/ld+json",
] ]
@@ -63,7 +82,7 @@ impl<T> ActivityStream<T> {
} }
} }
impl<'r, O: Object> Responder<'r> for ActivityStream<O> { impl<'r, O: serde::Serialize> Responder<'r> for ActivityStream<O> {
fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> {
let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?; let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?;
json["@context"] = context(); json["@context"] = context();
@@ -87,14 +106,16 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
.map(|header| { .map(|header| {
header header
.split(',') .split(',')
.map(|ct| match ct.trim() { .map(|ct| {
match ct.trim() {
// bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise // bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\"" "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
| "application/ld+json;profile=\"https://w3.org/ns/activitystreams\"" | "application/ld+json;profile=\"https://www.w3.org/ns/activitystreams\""
| "application/activity+json" | "application/activity+json"
| "application/ld+json" => Outcome::Success(ApRequest), | "application/ld+json" => Outcome::Success(ApRequest),
"text/html" => Outcome::Forward(true), "text/html" => Outcome::Forward(true),
_ => Outcome::Forward(false), _ => Outcome::Forward(false),
}
}) })
.fold(Outcome::Forward(false), |out, ct| { .fold(Outcome::Forward(false), |out, ct| {
if out.clone().forwarded().unwrap_or_else(|| out.is_success()) { if out.clone().forwarded().unwrap_or_else(|| out.is_success()) {
@@ -108,10 +129,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
.unwrap_or(Outcome::Forward(())) .unwrap_or(Outcome::Forward(()))
} }
} }
pub fn broadcast<S, A, T, C>(sender: &S, act: A, to: Vec<T>, proxy: Option<reqwest::Proxy>) pub fn broadcast<S, A, T, C>(sender: &S, act: A, to: Vec<T>, proxy: Option<reqwest::Proxy>)
where where
S: sign::Signer, S: sign::Signer,
A: Activity, A: Activity + serde::Serialize,
T: inbox::AsActor<C>, T: inbox::AsActor<C>,
{ {
let boxes = to let boxes = to
@@ -130,59 +152,79 @@ where
.sign(sender) .sign(sender)
.expect("activity_pub::broadcast: signature error"); .expect("activity_pub::broadcast: signature error");
let mut rt = tokio::runtime::current_thread::Runtime::new() let client = if let Some(proxy) = proxy {
ClientBuilder::new().proxy(proxy)
} else {
ClientBuilder::new()
}
.connect_timeout(std::time::Duration::from_secs(5))
.build()
.expect("Can't build client");
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Error while initializing tokio runtime for federation"); .expect("Error while initializing tokio runtime for federation");
for inbox in boxes { rt.block_on(async {
let body = signed.to_string(); // TODO: should be determined dependent on database connections because
let mut headers = request::headers(); // after broadcasting, target instance sends request to this instance,
let url = Url::parse(&inbox); // and Plume accesses database at that time.
if url.is_err() { let capacity = 6;
warn!("Inbox is invalid URL: {:?}", &inbox); let (tx, rx) = flume::bounded::<RequestBuilder>(capacity);
continue; let mut handles = Vec::with_capacity(capacity);
for _ in 0..capacity {
let rx = rx.clone();
let handle = rt.spawn(async move {
while let Ok(request_builder) = rx.recv_async().await {
// After broadcasting, target instance sends request to this instance.
// Sleep here in order to reduce requests at once
sleep(Duration::from_millis(500)).await;
let _ = request_builder
.send()
.await
.map(move |r| {
if r.status().is_success() {
debug!("Successfully sent activity to inbox ({})", &r.url());
} else {
warn!("Error while sending to inbox ({:?})", &r)
}
debug!("Response: \"{:?}\"\n", r);
})
.map_err(|e| warn!("Error while sending to inbox ({:?})", e));
}
});
handles.push(handle);
} }
let url = url.unwrap(); for inbox in boxes {
if !url.has_host() { let body = signed.to_string();
warn!("Inbox doesn't have host: {:?}", &inbox); let mut headers = request::headers();
continue; let url = Url::parse(&inbox);
}; if url.is_err() {
let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable")); warn!("Inbox is invalid URL: {:?}", &inbox);
if host_header_value.is_err() { continue;
warn!("Header value is invalid: {:?}", url.host_str());
continue;
}
headers.insert("Host", host_header_value.unwrap());
headers.insert("Digest", request::Digest::digest(&body));
rt.spawn(
if let Some(proxy) = proxy.clone() {
ClientBuilder::new().proxy(proxy)
} else {
ClientBuilder::new()
} }
.connect_timeout(std::time::Duration::from_secs(5)) let url = url.unwrap();
.build() if !url.has_host() {
.expect("Can't build client") warn!("Inbox doesn't have host: {:?}", &inbox);
.post(&inbox) continue;
.headers(headers.clone()) };
.header( let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"));
if host_header_value.is_err() {
warn!("Header value is invalid: {:?}", url.host_str());
continue;
}
headers.insert("Host", host_header_value.unwrap());
headers.insert("Digest", request::Digest::digest(&body));
headers.insert(
"Signature", "Signature",
request::signature(sender, &headers, ("post", url.path(), url.query())) request::signature(sender, &headers, ("post", url.path(), url.query()))
.expect("activity_pub::broadcast: request signature error"), .expect("activity_pub::broadcast: request signature error"),
) );
.body(body) let request_builder = client.post(&inbox).headers(headers.clone()).body(body);
.send() let _ = tx.send_async(request_builder).await;
.and_then(move |r| { }
if r.status().is_success() { drop(tx);
debug!("Successfully sent activity to inbox ({})", &inbox); join_all(handles).await;
} else { });
warn!("Error while sending to inbox ({:?})", &r)
}
r.into_body().concat2()
})
.map(move |response| debug!("Response: \"{:?}\"\n", response))
.map_err(|e| warn!("Error while sending to inbox ({:?})", e)),
);
}
rt.run().unwrap();
} }
#[derive(Shrinkwrap, Clone, Serialize, Deserialize)] #[derive(Shrinkwrap, Clone, Serialize, Deserialize)]
@@ -204,46 +246,284 @@ pub trait IntoId {
fn into_id(self) -> Id; fn into_id(self) -> Id;
} }
impl Link for 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 },
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] #[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)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ApSignature { pub struct ApSignature {
#[activitystreams(concrete(PublicKey), functional)] pub public_key: PublicKey,
pub public_key: Option<serde_json::Value>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PublicKey { pub struct PublicKey {
#[activitystreams(concrete(String), functional)] pub id: IriString,
pub id: Option<serde_json::Value>, pub owner: IriString,
pub public_key_pem: String,
#[activitystreams(concrete(String), functional)]
pub owner: Option<serde_json::Value>,
#[activitystreams(concrete(String), functional)]
pub public_key_pem: Option<serde_json::Value>,
} }
#[derive(Clone, Debug, Default, UnitString)] impl<U> UnparsedExtension<U> for ApSignature
#[activitystreams(Hashtag)] where
pub struct HashtagType; U: UnparsedMutExt,
{
type Error = serde_json::Error;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(ApSignature {
public_key: unparsed_mut.remove("publicKey")?,
})
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("publicKey", self.public_key)?;
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Hashtag { pub struct SourceProperty {
#[serde(rename = "type")] pub source: Source,
kind: HashtagType,
#[activitystreams(concrete(String), functional)]
pub href: Option<serde_json::Value>,
#[activitystreams(concrete(String), functional)]
pub name: Option<serde_json::Value>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)] impl<U> UnparsedExtension<U> for SourceProperty
where
U: UnparsedMutExt,
{
type Error = serde_json::Error;
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(SourceProperty {
source: unparsed_mut.remove("source")?,
})
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("source", self.source)?;
Ok(())
}
}
pub type CustomPerson = Ext1<ApActor<Person>, ApSignature>;
pub type CustomGroup = Ext2<ApActor<Group>, ApSignature, SourceProperty>;
kind!(HashtagType, Hashtag);
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct Hashtag {
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<IriString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<AnyString>,
#[serde(flatten)]
inner: Object<HashtagType>,
}
impl Hashtag {
pub fn new() -> Self {
Self {
href: None,
name: None,
inner: Object::new(),
}
}
pub fn extending(mut inner: Object<HashtagType>) -> Result<Self, serde_json::Error> {
let href = inner.remove("href")?;
let name = inner.remove("name")?;
Ok(Self { href, name, inner })
}
pub fn retracting(self) -> Result<Object<HashtagType>, serde_json::Error> {
let Self {
href,
name,
mut inner,
} = self;
inner.insert("href", href)?;
inner.insert("name", name)?;
Ok(inner)
}
}
pub trait AsHashtag: markers::Object {
fn hashtag_ref(&self) -> &Hashtag;
fn hashtag_mut(&mut self) -> &mut Hashtag;
}
pub trait HashtagExt: AsHashtag {
fn href(&self) -> Option<&IriString> {
self.hashtag_ref().href.as_ref()
}
fn set_href<T>(&mut self, href: T) -> &mut Self
where
T: Into<IriString>,
{
self.hashtag_mut().href = Some(href.into());
self
}
fn take_href(&mut self) -> Option<IriString> {
self.hashtag_mut().href.take()
}
fn delete_href(&mut self) -> &mut Self {
self.hashtag_mut().href = None;
self
}
fn name(&self) -> Option<&AnyString> {
self.hashtag_ref().name.as_ref()
}
fn set_name<T>(&mut self, name: T) -> &mut Self
where
T: Into<AnyString>,
{
self.hashtag_mut().name = Some(name.into());
self
}
fn take_name(&mut self) -> Option<AnyString> {
self.hashtag_mut().name.take()
}
fn delete_name(&mut self) -> &mut Self {
self.hashtag_mut().name = None;
self
}
}
impl Default for Hashtag {
fn default() -> Self {
Self::new()
}
}
impl AsHashtag for Hashtag {
fn hashtag_ref(&self) -> &Self {
self
}
fn hashtag_mut(&mut self) -> &mut Self {
self
}
}
impl Extends<HashtagType> for Hashtag {
type Error = serde_json::Error;
fn extends(base: Base<HashtagType>) -> Result<Self, Self::Error> {
let inner = Object::extends(base)?;
Self::extending(inner)
}
fn retracts(self) -> Result<Base<HashtagType>, Self::Error> {
let inner = self.retracting()?;
inner.retracts()
}
}
impl markers::Base for Hashtag {}
impl markers::Object for Hashtag {}
impl<T> HashtagExt for T where T: AsHashtag {}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Source { pub struct Source {
pub media_type: String, pub media_type: String,
@@ -251,13 +531,395 @@ pub struct Source {
pub content: String, pub content: String,
} }
impl Object for Source {} impl<U> UnparsedExtension<U> for Source
where
U: UnparsedMutExt,
{
type Error = serde_json::Error;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
#[serde(rename_all = "camelCase")] Ok(Source {
pub struct Licensed { content: unparsed_mut.remove("content")?,
#[activitystreams(concrete(String), functional)] media_type: unparsed_mut.remove("mediaType")?,
pub license: Option<serde_json::Value>, })
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("content", self.content)?;
unparsed_mut.insert("mediaType", self.media_type)?;
Ok(())
}
} }
impl Object for Licensed {} #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Licensed {
pub license: Option<String>,
}
impl<U> UnparsedExtension<U> for Licensed
where
U: UnparsedMutExt,
{
type Error = serde_json::Error;
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(Licensed {
license: unparsed_mut.remove("license")?,
})
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("license", self.license)?;
Ok(())
}
}
pub type LicensedArticle = Ext1<ApObject<Article>, Licensed>;
pub trait ToAsString {
fn to_as_string(&self) -> Option<String>;
}
impl ToAsString for OneOrMany<&AnyString> {
fn to_as_string(&self) -> Option<String> {
self.as_as_str().map(|s| s.to_string())
}
}
trait AsAsStr {
fn as_as_str(&self) -> Option<&str>;
}
impl AsAsStr for OneOrMany<&AnyString> {
fn as_as_str(&self) -> Option<&str> {
self.iter().next().map(|prop| prop.as_str())
}
}
pub trait ToAsUri {
fn to_as_uri(&self) -> Option<String>;
}
impl ToAsUri for OneOrMany<AnyBase> {
fn to_as_uri(&self) -> Option<String> {
self.iter()
.next()
.and_then(|prop| prop.as_xsd_any_uri().map(|uri| uri.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use activitystreams::{
activity::{ActorAndObjectRef, Create},
object::{kind::ArticleType, Image},
prelude::{ApActorExt, BaseExt, ExtendsExt, ObjectExt},
};
use assert_json_diff::assert_json_eq;
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]
fn se_ap_signature() {
let ap_signature = ApSignature {
public_key: PublicKey {
id: "https://example.com/pubkey".parse().unwrap(),
owner: "https://example.com/owner".parse().unwrap(),
public_key_pem: "pubKeyPem".into(),
},
};
let expected = json!({
"publicKey": {
"id": "https://example.com/pubkey",
"owner": "https://example.com/owner",
"publicKeyPem": "pubKeyPem"
}
});
assert_json_eq!(to_value(ap_signature).unwrap(), expected);
}
#[test]
fn de_ap_signature() {
let value: ApSignature = from_str(
r#"
{
"publicKey": {
"id": "https://example.com/",
"owner": "https://example.com/",
"publicKeyPem": ""
}
}
"#,
)
.unwrap();
let expected = ApSignature {
public_key: PublicKey {
id: "https://example.com/".parse().unwrap(),
owner: "https://example.com/".parse().unwrap(),
public_key_pem: "".into(),
},
};
assert_eq!(value, expected);
}
#[test]
fn se_custom_person() {
let actor = ApActor::new("https://example.com/inbox".parse().unwrap(), Person::new());
let person = CustomPerson::new(
actor,
ApSignature {
public_key: PublicKey {
id: "https://example.com/pubkey".parse().unwrap(),
owner: "https://example.com/owner".parse().unwrap(),
public_key_pem: "pubKeyPem".into(),
},
},
);
let expected = json!({
"inbox": "https://example.com/inbox",
"type": "Person",
"publicKey": {
"id": "https://example.com/pubkey",
"owner": "https://example.com/owner",
"publicKeyPem": "pubKeyPem"
}
});
assert_eq!(to_value(person).unwrap(), expected);
}
#[test]
fn se_custom_group() {
let group = CustomGroup::new(
ApActor::new("https://example.com/inbox".parse().unwrap(), Group::new()),
ApSignature {
public_key: PublicKey {
id: "https://example.com/pubkey".parse().unwrap(),
owner: "https://example.com/owner".parse().unwrap(),
public_key_pem: "pubKeyPem".into(),
},
},
SourceProperty {
source: Source {
content: String::from("This is a *custom* group."),
media_type: String::from("text/markdown"),
},
},
);
let expected = json!({
"inbox": "https://example.com/inbox",
"type": "Group",
"publicKey": {
"id": "https://example.com/pubkey",
"owner": "https://example.com/owner",
"publicKeyPem": "pubKeyPem"
},
"source": {
"content": "This is a *custom* group.",
"mediaType": "text/markdown"
}
});
assert_eq!(to_value(group).unwrap(), expected);
}
#[test]
fn de_custom_group() {
let value: CustomGroup = from_str(
r#"
{
"icon": {
"type": "Image"
},
"id": "https://plume01.localhost/~/Plume01%20Blog%202/",
"image": {
"type": "Image"
},
"inbox": "https://plume01.localhost/~/Plume01%20Blog%202/inbox",
"name": "Plume01 Blog 2",
"outbox": "https://plume01.localhost/~/Plume01%20Blog%202/outbox",
"preferredUsername": "Plume01 Blog 2",
"publicKey": {
"id": "https://plume01.localhost/~/Plume01%20Blog%202/#main-key",
"owner": "https://plume01.localhost/~/Plume01%20Blog%202/",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPGtKkl/iMsNAyeVaJGz\noEz5PoNkjRnKK7G97MFvb4zw9zs5SpzWW7b/pKHa4dODcGDJXmkCJ1H5JWyguzN8\n2GNoFjtEOJHxEGwBHSYDsTmhuLNB0DKxMU2iu55g8iIiXhZiIW1FBNGs/Geaymvr\nh/TEtzdReN8wzloRR55kOVcU49xBkqx8cfDSk/lrrDLlpveHdqgaFnIvuw2vycK0\nxFzS3xlEUpzJk9kHxoR1uEAfZ+gCv26Sgo/HqOAhqSD5IU3QZC3kdkr/hwVqtr8U\nXGkGG6Mo1rgzhkYiCFkWrV2WoKkcEHD4nEzbgoZZ5MyuSoloxnyF3NiScqmqW+Yx\nkQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"source": {
"content": "",
"mediaType": "text/markdown"
},
"summary": "",
"type": "Group"
}
"#
).unwrap();
let mut expected = CustomGroup::new(
ApActor::new("https://plume01.localhost/~/Plume01%20Blog%202/inbox".parse().unwrap(), Group::new()),
ApSignature {
public_key: PublicKey {
id: "https://plume01.localhost/~/Plume01%20Blog%202/#main-key".parse().unwrap(),
owner: "https://plume01.localhost/~/Plume01%20Blog%202/".parse().unwrap(),
public_key_pem: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPGtKkl/iMsNAyeVaJGz\noEz5PoNkjRnKK7G97MFvb4zw9zs5SpzWW7b/pKHa4dODcGDJXmkCJ1H5JWyguzN8\n2GNoFjtEOJHxEGwBHSYDsTmhuLNB0DKxMU2iu55g8iIiXhZiIW1FBNGs/Geaymvr\nh/TEtzdReN8wzloRR55kOVcU49xBkqx8cfDSk/lrrDLlpveHdqgaFnIvuw2vycK0\nxFzS3xlEUpzJk9kHxoR1uEAfZ+gCv26Sgo/HqOAhqSD5IU3QZC3kdkr/hwVqtr8U\nXGkGG6Mo1rgzhkYiCFkWrV2WoKkcEHD4nEzbgoZZ5MyuSoloxnyF3NiScqmqW+Yx\nkQIDAQAB\n-----END PUBLIC KEY-----\n".into(),
}
},
SourceProperty {
source: Source {
content: String::from(""),
media_type: String::from("text/markdown")
}
}
);
expected.set_icon(Image::new().into_any_base().unwrap());
expected.set_id(
"https://plume01.localhost/~/Plume01%20Blog%202/"
.parse()
.unwrap(),
);
expected.set_image(Image::new().into_any_base().unwrap());
expected.set_name("Plume01 Blog 2");
expected.set_outbox(
"https://plume01.localhost/~/Plume01%20Blog%202/outbox"
.parse()
.unwrap(),
);
expected.set_preferred_username("Plume01 Blog 2");
expected.set_summary("");
assert_json_eq!(value, expected);
}
#[test]
fn se_licensed_article() {
let object = ApObject::new(Article::new());
let licensed_article = LicensedArticle::new(
object,
Licensed {
license: Some("CC-0".into()),
},
);
let expected = json!({
"type": "Article",
"license": "CC-0",
});
assert_json_eq!(to_value(licensed_article).unwrap(), expected);
}
#[test]
fn de_licensed_article() {
let value: LicensedArticle = from_str(
r#"
{
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
}
"#,
)
.unwrap();
let expected = json!({
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
});
assert_eq!(to_value(value).unwrap(), expected);
}
#[test]
fn de_create_with_licensed_article() {
let create: Create = from_str(
r#"
{
"id": "https://plu.me/~/Blog/my-article",
"type": "Create",
"actor": "https://plu.me/@/Admin",
"to": "https://www.w3.org/ns/activitystreams#Public",
"object": {
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
}
}
"#,
)
.unwrap();
let base = create.object_field_ref().as_single_base().unwrap();
let any_base = AnyBase::from_base(base.clone());
let value = any_base.extend::<LicensedArticle, ArticleType>().unwrap();
let expected = json!({
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
});
assert_eq!(to_value(value).unwrap(), expected);
}
}
+58 -11
View File
@@ -1,6 +1,12 @@
use chrono::{offset::Utc, DateTime}; use chrono::{offset::Utc, DateTime};
use openssl::hash::{Hasher, MessageDigest}; use openssl::hash::{Hasher, MessageDigest};
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT}; use reqwest::{
blocking::{ClientBuilder, Response},
header::{
HeaderMap, HeaderValue, InvalidHeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT,
},
Proxy, Url,
};
use std::ops::Deref; use std::ops::Deref;
use std::time::SystemTime; use std::time::SystemTime;
use tracing::warn; use tracing::warn;
@@ -13,6 +19,24 @@ const PLUME_USER_AGENT: &str = concat!("Plume/", env!("CARGO_PKG_VERSION"));
#[derive(Debug)] #[derive(Debug)]
pub struct Error(); pub struct Error();
impl From<url::ParseError> for Error {
fn from(_err: url::ParseError) -> Self {
Error()
}
}
impl From<InvalidHeaderValue> for Error {
fn from(_err: InvalidHeaderValue) -> Self {
Error()
}
}
impl From<reqwest::Error> for Error {
fn from(_err: reqwest::Error) -> Self {
Error()
}
}
pub struct Digest(String); pub struct Digest(String);
impl Digest { impl Digest {
@@ -118,8 +142,8 @@ type Path<'a> = &'a str;
type Query<'a> = &'a str; type Query<'a> = &'a str;
type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>); type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>);
pub fn signature<S: Signer>( pub fn signature(
signer: &S, signer: &dyn Signer,
headers: &HeaderMap, headers: &HeaderMap,
request_target: RequestTarget, request_target: RequestTarget,
) -> Result<HeaderValue, Error> { ) -> Result<HeaderValue, Error> {
@@ -164,10 +188,35 @@ pub fn signature<S: Signer>(
)).map_err(|_| Error()) )).map_err(|_| Error())
} }
pub fn get(url_str: &str, sender: &dyn Signer, proxy: Option<Proxy>) -> Result<Response, Error> {
let mut headers = headers();
let url = Url::parse(url_str)?;
if !url.has_host() {
return Err(Error());
}
let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"))?;
headers.insert(HOST, host_header_value);
if let Some(proxy) = proxy {
ClientBuilder::new().proxy(proxy)
} else {
ClientBuilder::new()
}
.connect_timeout(Some(std::time::Duration::from_secs(5)))
.build()?
.get(url_str)
.headers(headers.clone())
.header(
"Signature",
signature(sender, &headers, ("get", url.path(), url.query()))?,
)
.send()
.map_err(|_| Error())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{signature, Error}; use super::signature;
use crate::activity_pub::sign::{gen_keypair, Signer}; use crate::activity_pub::sign::{gen_keypair, Error, Result, Signer};
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
@@ -187,13 +236,11 @@ mod tests {
} }
impl Signer for MySigner { impl Signer for MySigner {
type Error = Error;
fn get_key_id(&self) -> String { fn get_key_id(&self) -> String {
"mysigner".into() "mysigner".into()
} }
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error> { fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap()) let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
.unwrap(); .unwrap();
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap(); let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
@@ -201,12 +248,12 @@ mod tests {
signer.sign_to_vec().map_err(|_| Error()) signer.sign_to_vec().map_err(|_| Error())
} }
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error> { fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap()) let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
.unwrap(); .unwrap();
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap(); let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
verifier.update(data.as_bytes()).unwrap(); verifier.update(data.as_bytes()).unwrap();
verifier.verify(&signature).map_err(|_| Error()) verifier.verify(signature).map_err(|_| Error())
} }
} }
@@ -215,7 +262,7 @@ mod tests {
let signer = MySigner::new(); let signer = MySigner::new();
let headers = HeaderMap::new(); let headers = HeaderMap::new();
let result = signature(&signer, &headers, ("post", "/inbox", None)).unwrap(); let result = signature(&signer, &headers, ("post", "/inbox", None)).unwrap();
let fields: Vec<&str> = result.to_str().unwrap().split(",").collect(); let fields: Vec<&str> = result.to_str().unwrap().split(',').collect();
assert_eq!(r#"headers="(request-target)""#, fields[2]); assert_eq!(r#"headers="(request-target)""#, fields[2]);
let sign = &fields[3][11..(fields[3].len() - 1)]; let sign = &fields[3][11..(fields[3].len() - 1)];
assert!(signer.verify("post /inbox", sign.as_bytes()).is_ok()); assert!(signer.verify("post /inbox", sign.as_bytes()).is_ok());
+13 -8
View File
@@ -19,20 +19,25 @@ pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
#[derive(Debug)] #[derive(Debug)]
pub struct Error(); pub struct Error();
pub type Result<T> = std::result::Result<T, Error>;
impl From<openssl::error::ErrorStack> for Error {
fn from(_: openssl::error::ErrorStack) -> Self {
Self()
}
}
pub trait Signer { pub trait Signer {
type Error;
fn get_key_id(&self) -> String; fn get_key_id(&self) -> String;
/// Sign some data with the signer keypair /// Sign some data with the signer keypair
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error>; fn sign(&self, to_sign: &str) -> Result<Vec<u8>>;
/// Verify if the signature is valid /// Verify if the signature is valid
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error>; fn verify(&self, data: &str, signature: &[u8]) -> Result<bool>;
} }
pub trait Signable { pub trait Signable {
fn sign<T>(&mut self, creator: &T) -> Result<&mut Self, Error> fn sign<T>(&mut self, creator: &T) -> Result<&mut Self>
where where
T: Signer; T: Signer;
fn verify<T>(self, creator: &T) -> bool fn verify<T>(self, creator: &T) -> bool
@@ -46,7 +51,7 @@ pub trait Signable {
} }
impl Signable for serde_json::Value { impl Signable for serde_json::Value {
fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value, Error> { fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value> {
let creation_date = Utc::now().to_rfc3339(); let creation_date = Utc::now().to_rfc3339();
let mut options = json!({ let mut options = json!({
"type": "RsaSignature2017", "type": "RsaSignature2017",
@@ -114,7 +119,7 @@ impl Signable for serde_json::Value {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SignatureValidity { pub enum SignatureValidity {
Invalid, Invalid,
ValidNoDigest, ValidNoDigest,
@@ -182,7 +187,7 @@ pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>(
} }
let digest = all_headers.get_one("digest").unwrap_or(""); let digest = all_headers.get_one("digest").unwrap_or("");
let digest = request::Digest::from_header(digest); let digest = request::Digest::from_header(digest);
if !digest.map(|d| d.verify_header(&data)).unwrap_or(false) { if !digest.map(|d| d.verify_header(data)).unwrap_or(false) {
// signature was valid, but body content does not match its digest // signature was valid, but body content does not match its digest
return SignatureValidity::Invalid; return SignatureValidity::Invalid;
} }
Executable → Regular
-2
View File
@@ -1,7 +1,5 @@
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#[macro_use]
extern crate activitystreams_derive;
#[macro_use] #[macro_use]
extern crate shrinkwraprs; extern crate shrinkwraprs;
#[macro_use] #[macro_use]
+31 -30
View File
@@ -1,11 +1,7 @@
use heck::CamelCase; 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,
response::{Flash, Redirect},
};
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;
@@ -19,25 +15,13 @@ pub fn random_hex() -> String {
.fold(String::new(), |res, byte| format!("{}{:x}", res, byte)) .fold(String::new(), |res, byte| format!("{}{:x}", res, byte))
} }
/// Remove non alphanumeric characters and CamelCase a string
pub fn make_actor_id(name: &str) -> String {
name.to_camel_case()
.chars()
.filter(|c| c.is_alphanumeric())
.collect()
}
/** /**
* Redirects to the login page with a given message. * Percent-encode characters which are not allowed in IRI path segments.
* *
* Note that the message should be translated before passed to this function. * Intended to be used for generating Post ap_url.
*/ */
pub fn requires_login<T: Into<Uri<'static>>>(message: &str, url: T) -> Flash<Redirect> { pub fn iri_percent_encode_seg(segment: &str) -> String {
Flash::new( PercentEncodedForIri::from_path_segment(segment).to_string()
Redirect::to(format!("/login?m={}", Uri::percent_encode(message))),
"callback",
url.into().to_string(),
)
} }
#[derive(Debug)] #[derive(Debug)]
@@ -88,13 +72,13 @@ fn highlight_code<'a>(
unreachable!(); unreachable!();
}; };
let syntax_set = SyntaxSet::load_defaults_newlines(); let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set.find_syntax_by_token(&lang).unwrap_or_else(|| { let syntax = syntax_set.find_syntax_by_token(lang).unwrap_or_else(|| {
syntax_set syntax_set
.find_syntax_by_name(&lang) .find_syntax_by_name(lang)
.unwrap_or_else(|| syntax_set.find_syntax_plain_text()) .unwrap_or_else(|| syntax_set.find_syntax_plain_text())
}); });
let mut html = ClassedHTMLGenerator::new_with_class_style( let mut html = ClassedHTMLGenerator::new_with_class_style(
&syntax, syntax,
&syntax_set, &syntax_set,
ClassStyle::Spaced, ClassStyle::Spaced,
); );
@@ -234,7 +218,7 @@ pub fn md_to_html<'a>(
media_processor: Option<MediaProcessor<'a>>, media_processor: Option<MediaProcessor<'a>>,
) -> (String, HashSet<String>, HashSet<String>) { ) -> (String, HashSet<String>, HashSet<String>) {
let base_url = if let Some(base_url) = base_url { let base_url = if let Some(base_url) = base_url {
format!("//{}/", base_url) format!("https://{}/", base_url)
} else { } else {
"/".to_owned() "/".to_owned()
}; };
@@ -281,16 +265,15 @@ pub fn md_to_html<'a>(
text_acc.push(c) text_acc.push(c)
} }
let mention = text_acc; let mention = text_acc;
let short_mention = mention.splitn(1, '@').next().unwrap_or("");
let link = Tag::Link( let link = Tag::Link(
LinkType::Inline, LinkType::Inline,
format!("{}@/{}/", base_url, &mention).into(), format!("{}@/{}/", base_url, &mention).into(),
short_mention.to_owned().into(), mention.clone().into(),
); );
mentions.push(mention.clone()); mentions.push(mention.clone());
events.push(Event::Start(link.clone())); events.push(Event::Start(link.clone()));
events.push(Event::Text(format!("@{}", &short_mention).into())); events.push(Event::Text(format!("@{}", &mention).into()));
events.push(Event::End(link)); events.push(Event::End(link));
( (
@@ -414,6 +397,10 @@ pub fn md_to_html<'a>(
(buf, mentions.collect(), hashtags.collect()) (buf, mentions.collect(), hashtags.collect())
} }
pub fn escape(string: &str) -> askama_escape::Escaped<askama_escape::Html> {
askama_escape::escape(string, askama_escape::Html)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -476,6 +463,20 @@ mod tests {
} }
} }
#[test]
fn test_iri_percent_encode_seg() {
assert_eq!(
&iri_percent_encode_seg("including whitespace"),
"including%20whitespace"
);
assert_eq!(&iri_percent_encode_seg("%20"), "%2520");
assert_eq!(&iri_percent_encode_seg("é"), "é");
assert_eq!(
&iri_percent_encode_seg("空白入り 日本語"),
"空白入り%20日本語"
);
}
#[test] #[test]
fn test_inline() { fn test_inline() {
assert_eq!( assert_eq!(
+11 -8
View File
@@ -1,26 +1,29 @@
[package] [package]
name = "plume-front" name = "plume-front"
version = "0.6.1-dev" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" } gettext = "0.4.0"
gettext-macros = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-macros = "0.6.1"
gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-utils = "0.1.0"
lazy_static = "1.3" lazy_static = "1.3"
serde = "1.0" serde = "1.0.137"
serde_json = "1.0" serde_json = "1.0"
wasm-bindgen = "0.2.70" wasm-bindgen = "0.2.81"
js-sys = "0.3.47" js-sys = "0.3.58"
serde_derive = "1.0.123" serde_derive = "1.0.123"
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3.47" version = "0.3.58"
features = [ features = [
'console', 'console',
'ClipboardEvent', 'ClipboardEvent',
+1
View File
@@ -1,2 +1,3 @@
pre-release-hook = ["cargo", "fmt"] pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = [] pre-release-replacements = []
release = false
+23 -15
View File
@@ -54,11 +54,6 @@ pub enum EditorError {
DOMError, DOMError,
} }
impl From<std::option::NoneError> for EditorError {
fn from(_: std::option::NoneError) -> Self {
EditorError::NoneError
}
}
const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000; const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct AutosaveInformation { struct AutosaveInformation {
@@ -198,7 +193,7 @@ fn clear_autosave() {
.unwrap() .unwrap()
.remove_item(&get_autosave_id()) .remove_item(&get_autosave_id())
.unwrap(); .unwrap();
console::log_1(&&format!("Saved to {}", &get_autosave_id()).into()); console::log_1(&format!("Saved to {}", &get_autosave_id()).into());
} }
type TimeoutHandle = i32; type TimeoutHandle = i32;
lazy_static! { lazy_static! {
@@ -366,7 +361,9 @@ fn init_editor() -> Result<(), EditorError> {
return Ok(()); return Ok(());
} }
let old_ed = old_ed.unwrap(); let old_ed = old_ed.unwrap();
let old_title = document().get_element_by_id("plume-editor-title")?; let old_title = document()
.get_element_by_id("plume-editor-title")
.ok_or(EditorError::NoneError)?;
old_ed old_ed
.dyn_ref::<HtmlElement>() .dyn_ref::<HtmlElement>()
.unwrap() .unwrap()
@@ -400,7 +397,9 @@ fn init_editor() -> Result<(), EditorError> {
content_val.clone(), content_val.clone(),
false, false,
)?; )?;
content.set_inner_html(&content_val); if !content_val.is_empty() {
content.set_inner_html(&content_val);
}
// character counter // character counter
let character_counter = Closure::wrap(Box::new(mv!(content => move |_| { let character_counter = Closure::wrap(Box::new(mv!(content => move |_| {
@@ -434,7 +433,8 @@ fn init_editor() -> Result<(), EditorError> {
bg.class_list().add_1("show").unwrap(); bg.class_list().add_1("show").unwrap();
})) as Box<dyn FnMut(MouseEvent)>); })) as Box<dyn FnMut(MouseEvent)>);
document() document()
.get_element_by_id("publish")? .get_element_by_id("publish")
.ok_or(EditorError::NoneError)?
.add_event_listener_with_callback("click", show_popup.as_ref().unchecked_ref()) .add_event_listener_with_callback("click", show_popup.as_ref().unchecked_ref())
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
show_popup.forget(); show_popup.forget();
@@ -528,8 +528,14 @@ fn init_popup(
cover_label cover_label
.set_attribute("for", "cover") .set_attribute("for", "cover")
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
let cover = document.get_element_by_id("cover")?; let cover = document
cover.parent_element()?.remove_child(&cover).ok(); .get_element_by_id("cover")
.ok_or(EditorError::NoneError)?;
cover
.parent_element()
.ok_or(EditorError::NoneError)?
.remove_child(&cover)
.ok();
popup popup
.append_child(&cover_label) .append_child(&cover_label)
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
@@ -554,7 +560,7 @@ fn init_popup(
draft.set_checked(draft_checkbox.checked()); draft.set_checked(draft_checkbox.checked());
draft_label draft_label
.append_child(&draft) .append_child(draft)
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
draft_label draft_label
.append_child(&document.create_text_node(&i18n!(CATALOG, "This is a draft"))) .append_child(&document.create_text_node(&i18n!(CATALOG, "This is a draft")))
@@ -620,11 +626,12 @@ fn init_popup(
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
callback.forget(); callback.forget();
popup popup
.append_child(&button) .append_child(button)
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
document document
.body()? .body()
.ok_or(EditorError::NoneError)?
.append_child(&popup) .append_child(&popup)
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
Ok(popup) Ok(popup)
@@ -641,7 +648,8 @@ fn init_popup_bg() -> Result<Element, EditorError> {
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
document() document()
.body()? .body()
.ok_or(EditorError::NoneError)?
.append_child(&bg) .append_child(&bg)
.map_err(|_| EditorError::DOMError)?; .map_err(|_| EditorError::DOMError)?;
let callback = Closure::wrap(Box::new(|_| close_popup()) as Box<dyn FnMut(MouseEvent)>); let callback = Closure::wrap(Box::new(|_| close_popup()) as Box<dyn FnMut(MouseEvent)>);
+4 -3
View File
@@ -1,5 +1,5 @@
#![recursion_limit = "128"] #![recursion_limit = "128"]
#![feature(decl_macro, proc_macro_hygiene, try_trait)] #![feature(decl_macro, proc_macro_hygiene)]
#[macro_use] #[macro_use]
extern crate gettext_macros; extern crate gettext_macros;
@@ -23,6 +23,7 @@ init_i18n!(
en, en,
eo, eo,
es, es,
eu,
fa, fa,
fi, fi,
fr, fr,
@@ -61,7 +62,7 @@ lazy_static! {
static ref CATALOG: gettext::Catalog = { static ref CATALOG: gettext::Catalog = {
let catalogs = include_i18n!(); let catalogs = include_i18n!();
let lang = window().unwrap().navigator().language().unwrap(); let lang = window().unwrap().navigator().language().unwrap();
let lang = lang.splitn(2, '-').next().unwrap_or("en"); let lang = lang.split_once('-').map_or("en", |x| x.0);
let english_position = catalogs let english_position = catalogs
.iter() .iter()
@@ -85,7 +86,7 @@ pub fn main() -> Result<(), JsValue> {
menu(); menu();
search(); search();
editor::init() editor::init()
.map_err(|e| console::error_1(&&format!("Editor error: {:?}", e).into())) .map_err(|e| console::error_1(&format!("Editor error: {:?}", e).into()))
.ok(); .ok();
Ok(()) Ok(())
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-macro" name = "plume-macro"
version = "0.6.1-dev" version = "0.7.2"
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"] authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
edition = "2018" edition = "2018"
description = "Plume procedural macros" description = "Plume procedural macros"
+1
View File
@@ -1,2 +1,3 @@
pre-release-hook = ["cargo", "fmt"] pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = [] pre-release-replacements = []
release = false
+2 -2
View File
@@ -58,7 +58,7 @@ pub fn import_migrations(input: TokenStream) -> TokenStream {
(name, up_sql, down_sql) (name, up_sql, down_sql)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let migrations_name = migrations.iter().map(|m| &m.0).collect::<Vec<_>>(); let migrations_name = migrations.iter().map(|m| &m.0);
let migrations_up = migrations let migrations_up = migrations
.iter() .iter()
.map(|m| m.1.as_str()) .map(|m| m.1.as_str())
@@ -103,7 +103,7 @@ fn file_to_migration(file: &str) -> TokenStream2 {
acc.push('\n'); acc.push('\n');
} }
} else if let Some(acc_str) = line.strip_prefix("--#!") { } else if let Some(acc_str) = line.strip_prefix("--#!") {
acc.push_str(&acc_str); acc.push_str(acc_str);
acc.push('\n'); acc.push('\n');
} else if line.starts_with("--") { } else if line.starts_with("--") {
continue; continue;
+23 -21
View File
@@ -1,40 +1,41 @@
[package] [package]
name = "plume-models" name = "plume-models"
version = "0.6.1-dev" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.1" ammonia = "3.2.0"
ammonia = "2.1.1" bcrypt = "0.12.1"
askama_escape = "0.1" guid-create = "0.2"
bcrypt = "0.5" itertools = "0.10.3"
guid-create = "0.1"
heck = "0.3.0"
itertools = "0.8.0"
lazy_static = "1.0" lazy_static = "1.0"
ldap3 = "0.7.1" ldap3 = "0.11.1"
migrations_internals= "1.4.0" migrations_internals= "1.4.0"
openssl = "0.10.22" openssl = "0.10.40"
rocket = "0.4.6" rocket = "0.4.11"
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" } rocket_i18n = "0.4.1"
reqwest = "0.9" reqwest = "0.11.11"
scheduled-thread-pool = "0.2.2" scheduled-thread-pool = "0.2.6"
serde = "1.0" serde = "1.0.137"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0.81"
tantivy = "0.13.3" tantivy = "0.13.3"
url = "2.1" url = "2.1"
walkdir = "2.2" walkdir = "2.2"
webfinger = "0.4.1" webfinger = "0.4.1"
whatlang = "0.11.1" whatlang = "0.16.2"
shrinkwraprs = "0.2.1" shrinkwraprs = "0.3.0"
diesel-derive-newtype = "0.1.2" diesel-derive-newtype = "1.0.0"
glob = "0.3.0" glob = "0.3.0"
lindera-tantivy = { version = "0.7.1", optional = true } lindera-tantivy = { version = "0.7.1", optional = true }
tracing = "0.1.22" tracing = "0.1.35"
riker = "0.4.2" riker = "0.4.2"
once_cell = "1.5.2" once_cell = "1.12.0"
lettre = "0.9.6"
native-tls = "0.2.10"
activitystreams = "=0.7.0-alpha.20"
heck = "0.4.0"
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
@@ -54,6 +55,7 @@ path = "../plume-common"
path = "../plume-macro" path = "../plume-macro"
[dev-dependencies] [dev-dependencies]
assert-json-diff = "2.0.1"
diesel_migrations = "1.3.0" diesel_migrations = "1.3.0"
[features] [features]
+1
View File
@@ -1,2 +1,3 @@
pre-release-hook = ["cargo", "fmt"] pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = [] pre-release-replacements = []
release = false
+13 -9
View File
@@ -86,20 +86,24 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
} }
let mut parsed_header = headers[0].split(' '); let mut parsed_header = headers[0].split(' ');
let auth_type = parsed_header.next().map_or_else( let auth_type = parsed_header
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)), .next()
Outcome::Success, .map_or_else::<rocket::Outcome<&str, _, ()>, _, _>(
)?; || Outcome::Failure((Status::BadRequest, TokenError::NoType)),
let val = parsed_header.next().map_or_else( Outcome::Success,
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)), )?;
Outcome::Success, let val = parsed_header
)?; .next()
.map_or_else::<rocket::Outcome<&str, _, ()>, _, _>(
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)),
Outcome::Success,
)?;
if auth_type == "Bearer" { if auth_type == "Bearer" {
let conn = request let conn = request
.guard::<DbConn>() .guard::<DbConn>()
.map_failure(|_| (Status::InternalServerError, TokenError::DbError))?; .map_failure(|_| (Status::InternalServerError, TokenError::DbError))?;
if let Ok(token) = ApiToken::find_by_value(&*conn, val) { if let Ok(token) = ApiToken::find_by_value(&conn, val) {
return Outcome::Success(token); return Outcome::Success(token);
} }
} }
+4 -7
View File
@@ -28,7 +28,7 @@ impl BlocklistedEmail {
pub fn delete_entries(conn: &Connection, ids: Vec<i32>) -> Result<bool> { pub fn delete_entries(conn: &Connection, ids: Vec<i32>) -> Result<bool> {
use diesel::delete; use diesel::delete;
for i in ids { for i in ids {
let be: BlocklistedEmail = BlocklistedEmail::find_by_id(&conn, i)?; let be: BlocklistedEmail = BlocklistedEmail::find_by_id(conn, i)?;
delete(&be).execute(conn)?; delete(&be).execute(conn)?;
} }
Ok(true) Ok(true)
@@ -126,12 +126,9 @@ pub(crate) mod tests {
.id, .id,
various[1].id various[1].id
); );
assert_eq!( assert!(BlocklistedEmail::matches_blocklist(&conn, no_match)
BlocklistedEmail::matches_blocklist(&conn, no_match) .unwrap()
.unwrap() .is_none());
.is_none(),
true
);
Ok(()) Ok(())
}); });
} }
+437 -236
View File
File diff suppressed because it is too large Load Diff
+279 -118
View File
@@ -11,17 +11,23 @@ use crate::{
users::User, users::User,
Connection, Error, Result, CONFIG, Connection, Error, Result, CONFIG,
}; };
use activitypub::{ use activitystreams::{
activity::{Create, Delete}, activity::{Create, Delete},
link, base::{AnyBase, Base},
iri_string::types::IriString,
link::{self, kind::MentionType},
object::{Note, Tombstone}, object::{Note, Tombstone},
prelude::*,
primitives::OneOrMany,
time::OffsetDateTime,
}; };
use chrono::{self, NaiveDateTime}; use chrono::{self, NaiveDateTime};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
Id, IntoId, PUBLIC_VISIBILITY, sign::Signer,
IntoId, ToAsString, ToAsUri, PUBLIC_VISIBILITY,
}, },
utils, utils,
}; };
@@ -58,7 +64,7 @@ impl Comment {
insert!(comments, NewComment, |inserted, conn| { insert!(comments, NewComment, |inserted, conn| {
if inserted.ap_url.is_none() { if inserted.ap_url.is_none() {
inserted.ap_url = Some(format!( inserted.ap_url = Some(format!(
"{}comment/{}", "{}/comment/{}",
inserted.get_post(conn)?.ap_url, inserted.get_post(conn)?.ap_url,
inserted.id inserted.id
)); ));
@@ -114,45 +120,59 @@ impl Comment {
Some(Media::get_media_processor(conn, vec![&author])), Some(Media::get_media_processor(conn, vec![&author])),
); );
let mut note = Note::default(); let mut note = Note::new();
let to = vec![Id::new(PUBLIC_VISIBILITY.to_string())]; let to = vec![PUBLIC_VISIBILITY.parse::<IriString>()?];
note.object_props note.set_id(
.set_id_string(self.ap_url.clone().unwrap_or_default())?; self.ap_url
note.object_props .clone()
.set_summary_string(self.spoiler_text.clone())?; .unwrap_or_default()
note.object_props.set_content_string(html)?; .parse::<IriString>()?,
note.object_props );
.set_in_reply_to_link(Id::new(self.in_response_to_id.map_or_else( note.set_summary(self.spoiler_text.clone());
|| Ok(Post::get(conn, self.post_id)?.ap_url), note.set_content(html);
|id| Ok(Comment::get(conn, id)?.ap_url.unwrap_or_default()) as Result<String>, note.set_in_reply_to(self.in_response_to_id.map_or_else(
)?))?; || Post::get(conn, self.post_id).map(|post| post.ap_url),
note.object_props |id| Comment::get(conn, id).map(|comment| comment.ap_url.unwrap_or_default()),
.set_published_string(chrono::Utc::now().to_rfc3339())?; )?);
note.object_props.set_attributed_to_link(author.into_id())?; note.set_published(
note.object_props.set_to_link_vec(to)?; OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into())
note.object_props.set_tag_link_vec( .expect("OffsetDateTime"),
mentions );
.into_iter() note.set_attributed_to(author.into_id().parse::<IriString>()?);
.filter_map(|m| Mention::build_activity(conn, &m).ok()) note.set_many_tos(to);
.collect::<Vec<link::Mention>>(), note.set_many_tags(mentions.into_iter().filter_map(|m| {
)?; Mention::build_activity(conn, &m)
.map(|mention| mention.into_any_base().expect("Can convert"))
.ok()
}));
Ok(note) Ok(note)
} }
pub fn create_activity(&self, conn: &DbConn) -> 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)?;
let mut act = Create::default(); let note_clone = note.clone();
act.create_props.set_actor_link(author.into_id())?;
act.create_props.set_object_object(note.clone())?; let mut act = Create::new(
act.object_props author.into_id().parse::<IriString>()?,
.set_id_string(format!("{}/activity", self.ap_url.clone()?,))?; Base::retract(note)?.into_generic()?,
act.object_props );
.set_to_link_vec(note.object_props.to_link_vec::<Id>()?)?; act.set_id(
act.object_props format!(
.set_cc_link_vec(vec![Id::new(self.get_author(&conn)?.followers_endpoint)])?; "{}/activity",
self.ap_url.clone().ok_or(Error::MissingApProperty)?,
)
.parse::<IriString>()?,
);
act.set_many_tos(
note_clone
.to()
.iter()
.flat_map(|tos| tos.iter().map(|to| to.to_owned())),
);
act.set_many_ccs(vec![self.get_author(conn)?.followers_endpoint]);
Ok(act) Ok(act)
} }
@@ -177,18 +197,21 @@ impl Comment {
} }
pub fn build_delete(&self, conn: &Connection) -> Result<Delete> { pub fn build_delete(&self, conn: &Connection) -> Result<Delete> {
let mut act = Delete::default(); let mut tombstone = Tombstone::new();
act.delete_props tombstone.set_id(
.set_actor_link(self.get_author(conn)?.into_id())?; self.ap_url
.as_ref()
.ok_or(Error::MissingApProperty)?
.parse::<IriString>()?,
);
let mut tombstone = Tombstone::default(); let mut act = Delete::new(
tombstone.object_props.set_id_string(self.ap_url.clone()?)?; self.get_author(conn)?.into_id().parse::<IriString>()?,
act.delete_props.set_object_object(tombstone)?; Base::retract(tombstone)?.into_generic()?,
);
act.object_props act.set_id(format!("{}#delete", self.ap_url.clone().unwrap()).parse::<IriString>()?);
.set_id_string(format!("{}#delete", self.ap_url.clone().unwrap()))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY)])?;
Ok(act) Ok(act)
} }
@@ -204,103 +227,110 @@ impl FromId<DbConn> for Comment {
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> { fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
let comm = { let comm = {
let previous_url = note.object_props.in_reply_to.as_ref()?.as_str()?; let previous_url = note
let previous_comment = Comment::find_by_ap_url(conn, previous_url); .in_reply_to()
.ok_or(Error::MissingApProperty)?
.iter()
.next()
.ok_or(Error::MissingApProperty)?
.id()
.ok_or(Error::MissingApProperty)?;
let previous_comment = Comment::find_by_ap_url(conn, previous_url.as_str());
let is_public = |v: &Option<serde_json::Value>| match v let is_public = |v: &Option<&OneOrMany<AnyBase>>| match v {
.as_ref() Some(one_or_many) => one_or_many.iter().any(|any_base| {
.unwrap_or(&serde_json::Value::Null) let id = any_base.id();
{ id.is_some() && id.unwrap() == PUBLIC_VISIBILITY
serde_json::Value::Array(v) => v }),
.iter() None => false,
.filter_map(serde_json::Value::as_str)
.any(|s| s == PUBLIC_VISIBILITY),
serde_json::Value::String(s) => s == PUBLIC_VISIBILITY,
_ => false,
}; };
let public_visibility = is_public(&note.object_props.to) let public_visibility = is_public(&note.to())
|| is_public(&note.object_props.bto) || is_public(&note.bto())
|| is_public(&note.object_props.cc) || is_public(&note.cc())
|| is_public(&note.object_props.bcc); || is_public(&note.bcc());
let summary = note.summary().and_then(|summary| summary.to_as_string());
let sensitive = summary.is_some();
let comm = Comment::insert( let comm = Comment::insert(
conn, conn,
NewComment { NewComment {
content: SafeString::new(&note.object_props.content_string()?), content: SafeString::new(
spoiler_text: note.object_props.summary_string().unwrap_or_default(), &note
ap_url: note.object_props.id_string().ok(), .content()
.ok_or(Error::MissingApProperty)?
.to_as_string()
.ok_or(Error::InvalidValue)?,
),
spoiler_text: summary.unwrap_or_default(),
ap_url: Some(
note.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_string(),
),
in_response_to_id: previous_comment.iter().map(|c| c.id).next(), in_response_to_id: previous_comment.iter().map(|c| c.id).next(),
post_id: previous_comment.map(|c| c.post_id).or_else(|_| { post_id: previous_comment.map(|c| c.post_id).or_else(|_| {
Ok(Post::find_by_ap_url(conn, previous_url)?.id) as Result<i32> Ok(Post::find_by_ap_url(conn, previous_url.as_str())?.id) as Result<i32>
})?, })?,
author_id: User::from_id( author_id: User::from_id(
conn, conn,
&note.object_props.attributed_to_link::<Id>()?, &note
.attributed_to()
.ok_or(Error::MissingApProperty)?
.to_as_uri()
.ok_or(Error::MissingApProperty)?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
.map_err(|(_, e)| e)? .map_err(|(_, e)| e)?
.id, .id,
sensitive: note.object_props.summary_string().is_ok(), sensitive,
public_visibility, public_visibility,
}, },
)?; )?;
// save mentions // save mentions
if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() { if let Some(tags) = note.tag() {
for tag in tags { let author_url = &Post::get(conn, comm.post_id)?.get_authors(conn)?[0].ap_url;
serde_json::from_value::<link::Mention>(tag) for tag in tags.iter() {
.map_err(Error::from) let m = tag.clone().extend::<link::Mention, MentionType>()?; // FIXME: Don't clone
.and_then(|m| { if m.is_none() {
let author = &Post::get(conn, comm.post_id)?.get_authors(conn)?[0]; continue;
let not_author = m.link_props.href_string()? != author.ap_url.clone(); }
Mention::from_activity(conn, &m, comm.id, false, not_author) let m = m.unwrap();
}) let not_author = m.href().ok_or(Error::MissingApProperty)? != author_url;
.ok(); let _ = Mention::from_activity(conn, &m, comm.id, false, not_author);
} }
} }
comm comm
}; };
if !comm.public_visibility { if !comm.public_visibility {
let receivers_ap_url = |v: Option<serde_json::Value>| { let mut receiver_ids = HashSet::new();
let filter = |e: serde_json::Value| { let mut receivers_id = |v: Option<&'_ OneOrMany<AnyBase>>| {
if let serde_json::Value::String(s) = e { if let Some(one_or_many) = v {
Some(s) for any_base in one_or_many.iter() {
} else { if let Some(id) = any_base.id() {
None receiver_ids.insert(id.to_string());
}
} }
};
match v.unwrap_or(serde_json::Value::Null) {
serde_json::Value::Array(v) => v,
v => vec![v],
} }
.into_iter()
.filter_map(filter)
}; };
let mut note = note; receivers_id(note.to());
receivers_id(note.cc());
receivers_id(note.bto());
receivers_id(note.bcc());
let to = receivers_ap_url(note.object_props.to.take()); let receivers_ap_url = receiver_ids
let cc = receivers_ap_url(note.object_props.cc.take());
let bto = receivers_ap_url(note.object_props.bto.take());
let bcc = receivers_ap_url(note.object_props.bcc.take());
let receivers_ap_url = to
.chain(cc)
.chain(bto)
.chain(bcc)
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
.into_iter() .into_iter()
.map(|v| { .flat_map(|v| {
if let Ok(user) = User::from_id(conn, &v, None, CONFIG.proxy()) { if let Ok(user) = User::from_id(conn, v.as_ref(), None, CONFIG.proxy()) {
vec![user] vec![user]
} else { } else {
vec![] // TODO try to fetch collection vec![] // TODO try to fetch collection
} }
}) })
.flatten()
.filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false)) .filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false))
.collect::<HashSet<User>>(); //remove duplicates (prevent db error) .collect::<HashSet<User>>(); //remove duplicates (prevent db error)
@@ -318,6 +348,10 @@ impl FromId<DbConn> for Comment {
comm.notify(conn)?; comm.notify(conn)?;
Ok(comm) Ok(comm)
} }
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
} }
impl AsObject<User, Create, &DbConn> for Comment { impl AsObject<User, Create, &DbConn> for Comment {
@@ -346,8 +380,8 @@ impl AsObject<User, Delete, &DbConn> for Comment {
m.delete(conn)?; m.delete(conn)?;
} }
for n in Notification::find_for_comment(&conn, &self)? { for n in Notification::find_for_comment(conn, &self)? {
n.delete(&**conn)?; n.delete(conn)?;
} }
diesel::update(comments::table) diesel::update(comments::table)
@@ -388,10 +422,34 @@ impl CommentTree {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::blogs::Blog;
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; use crate::tests::{db, format_datetime};
use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Comment, Vec<Post>, Vec<User>, Vec<Blog>) {
let (posts, users, blogs) = fill_database(conn);
let comment = Comment::insert(
conn,
NewComment {
content: SafeString::new("My comment, mentioning to @user"),
in_response_to_id: None,
post_id: posts[0].id,
author_id: users[0].id,
ap_url: None,
sensitive: true,
spoiler_text: "My CW".into(),
public_visibility: true,
},
)
.unwrap();
(comment, posts, users, blogs)
}
// creates a post, get it's Create activity, delete the post, // creates a post, get it's Create activity, delete the post,
// "send" the Create to the inbox, and check it works // "send" the Create to the inbox, and check it works
@@ -399,30 +457,77 @@ mod tests {
fn self_federation() { fn self_federation() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (posts, users, _) = fill_database(&conn); let (original_comm, posts, users, _blogs) = prepare_activity(conn);
let act = original_comm.create_activity(conn).unwrap();
let original_comm = Comment::insert( assert_json_eq!(to_value(&act).unwrap(), json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}/activity", original_comm.id),
"object": {
"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>
"###,
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", original_comm.id),
"inReplyTo": "https://plu.me/~/Blog%20Name/testing",
"published": format_datetime(&original_comm.creation_date),
"summary": "My CW",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create",
}));
let reply = Comment::insert(
conn, conn,
NewComment { NewComment {
content: SafeString::new("My comment"), content: SafeString::new(""),
in_response_to_id: None, in_response_to_id: Some(original_comm.id),
post_id: posts[0].id, post_id: posts[0].id,
author_id: users[0].id, author_id: users[1].id,
ap_url: None, ap_url: None,
sensitive: true, sensitive: false,
spoiler_text: "My CW".into(), spoiler_text: "".into(),
public_visibility: true, public_visibility: true,
}, },
) )
.unwrap(); .unwrap();
let act = original_comm.create_activity(&conn).unwrap(); let reply_act = reply.create_activity(conn).unwrap();
assert_json_eq!(to_value(&reply_act).unwrap(), json!({
"actor": "https://plu.me/@/user/",
"cc": ["https://plu.me/@/user/followers"],
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}/activity", reply.id),
"object": {
"attributedTo": "https://plu.me/@/user/",
"content": "",
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", reply.id),
"inReplyTo": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", original_comm.id),
"published": format_datetime(&reply.creation_date),
"summary": "",
"tag": [],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
}));
inbox( inbox(
&conn, conn,
serde_json::to_value(original_comm.build_delete(&conn).unwrap()).unwrap(), serde_json::to_value(original_comm.build_delete(conn).unwrap()).unwrap(),
) )
.unwrap(); .unwrap();
match inbox(&conn, serde_json::to_value(act).unwrap()).unwrap() { match inbox(conn, to_value(act).unwrap()).unwrap() {
InboxResult::Commented(c) => { InboxResult::Commented(c) => {
// TODO: one is HTML, the other markdown: assert_eq!(c.content, original_comm.content); // TODO: one is HTML, the other markdown: assert_eq!(c.content, original_comm.content);
assert_eq!(c.in_response_to_id, original_comm.in_response_to_id); assert_eq!(c.in_response_to_id, original_comm.in_response_to_id);
@@ -437,4 +542,60 @@ mod tests {
Ok(()) Ok(())
}) })
} }
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (comment, _posts, _users, _blogs) = prepare_activity(&conn);
let act = comment.to_activity(&conn)?;
let expected = json!({
"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>
"###,
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", comment.id),
"inReplyTo": "https://plu.me/~/Blog%20Name/testing",
"published": format_datetime(&comment.creation_date),
"summary": "My CW",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_delete() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (comment, _posts, _users, _blogs) = prepare_activity(&conn);
let act = comment.build_delete(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}#delete", comment.id),
"object": {
"id": format!("https://plu.me/~/Blog%20Name/testing/comment/{}", comment.id),
"type": "Tombstone"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Delete"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
} }
+42 -11
View File
@@ -1,4 +1,6 @@
use crate::search::TokenizerKind as SearchTokenizer; use crate::search::TokenizerKind as SearchTokenizer;
use crate::signups::Strategy as SignupStrategy;
use crate::smtp::{SMTP_PORT, SUBMISSIONS_PORT, SUBMISSION_PORT};
use rocket::config::Limits; use rocket::config::Limits;
use rocket::Config as RocketConfig; use rocket::Config as RocketConfig;
use std::collections::HashSet; use std::collections::HashSet;
@@ -15,12 +17,14 @@ pub struct Config {
pub db_name: &'static str, pub db_name: &'static str,
pub db_max_size: Option<u32>, pub db_max_size: Option<u32>,
pub db_min_idle: Option<u32>, pub db_min_idle: Option<u32>,
pub signup: SignupStrategy,
pub search_index: String, pub search_index: String,
pub search_tokenizers: SearchTokenizerConfig, pub search_tokenizers: SearchTokenizerConfig,
pub rocket: Result<RocketConfig, RocketError>, pub rocket: Result<RocketConfig, InvalidRocketConfig>,
pub logo: LogoConfig, pub logo: LogoConfig,
pub default_theme: String, pub default_theme: String,
pub media_directory: String, pub media_directory: String,
pub mail: Option<MailConfig>,
pub ldap: Option<LdapConfig>, pub ldap: Option<LdapConfig>,
pub proxy: Option<ProxyConfig>, pub proxy: Option<ProxyConfig>,
} }
@@ -31,21 +35,21 @@ impl Config {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum RocketError { pub enum InvalidRocketConfig {
InvalidEnv, Env,
InvalidAddress, Address,
InvalidSecretKey, SecretKey,
} }
fn get_rocket_config() -> Result<RocketConfig, RocketError> { pub fn get_rocket_config() -> Result<RocketConfig, InvalidRocketConfig> {
let mut c = RocketConfig::active().map_err(|_| RocketError::InvalidEnv)?; 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());
let port = var("ROCKET_PORT") let port = var("ROCKET_PORT")
.ok() .ok()
.map(|s| s.parse::<u16>().unwrap()) .map(|s| s.parse::<u16>().unwrap())
.unwrap_or(7878); .unwrap_or(7878);
let secret_key = var("ROCKET_SECRET_KEY").map_err(|_| RocketError::InvalidSecretKey)?; let secret_key = var("ROCKET_SECRET_KEY").map_err(|_| InvalidRocketConfig::SecretKey)?;
let form_size = var("FORM_SIZE") let form_size = var("FORM_SIZE")
.unwrap_or_else(|_| "128".to_owned()) .unwrap_or_else(|_| "128".to_owned())
.parse::<u64>() .parse::<u64>()
@@ -56,10 +60,10 @@ fn get_rocket_config() -> Result<RocketConfig, RocketError> {
.unwrap(); .unwrap();
c.set_address(address) c.set_address(address)
.map_err(|_| RocketError::InvalidAddress)?; .map_err(|_| InvalidRocketConfig::Address)?;
c.set_port(port); c.set_port(port);
c.set_secret_key(secret_key) c.set_secret_key(secret_key)
.map_err(|_| RocketError::InvalidSecretKey)?; .map_err(|_| InvalidRocketConfig::SecretKey)?;
c.set_limits( c.set_limits(
Limits::new() Limits::new()
@@ -155,7 +159,7 @@ impl Default for LogoConfig {
.ok() .ok()
.or_else(|| custom_main.clone()); .or_else(|| custom_main.clone());
let other = if let Some(main) = custom_main.clone() { let other = if let Some(main) = custom_main.clone() {
let ext = |path: &str| match path.rsplitn(2, '.').next() { let ext = |path: &str| match path.rsplit_once('.').map(|x| x.1) {
Some("png") => Some("image/png".to_owned()), Some("png") => Some("image/png".to_owned()),
Some("jpg") | Some("jpeg") => Some("image/jpeg".to_owned()), Some("jpg") | Some("jpeg") => Some("image/jpeg".to_owned()),
Some("svg") => Some("image/svg+xml".to_owned()), Some("svg") => Some("image/svg+xml".to_owned()),
@@ -245,6 +249,31 @@ impl SearchTokenizerConfig {
} }
} }
pub struct MailConfig {
pub server: String,
pub port: u16,
pub helo_name: String,
pub username: String,
pub password: String,
}
fn get_mail_config() -> Option<MailConfig> {
Some(MailConfig {
server: env::var("MAIL_SERVER").ok()?,
port: env::var("MAIL_PORT").map_or(SUBMISSIONS_PORT, |port| match port.as_str() {
"smtp" => SMTP_PORT,
"submissions" => SUBMISSIONS_PORT,
"submission" => SUBMISSION_PORT,
number => number
.parse()
.expect(r#"MAIL_PORT must be "smtp", "submissions", "submission" or an integer."#),
}),
helo_name: env::var("MAIL_HELO_NAME").unwrap_or_else(|_| "localhost".to_owned()),
username: env::var("MAIL_USER").ok()?,
password: env::var("MAIL_PASSWORD").ok()?,
})
}
pub struct LdapConfig { pub struct LdapConfig {
pub addr: String, pub addr: String,
pub base_dn: String, pub base_dn: String,
@@ -335,6 +364,7 @@ lazy_static! {
s.parse::<u32>() s.parse::<u32>()
.expect("Couldn't parse DB_MIN_IDLE into u32") .expect("Couldn't parse DB_MIN_IDLE into u32")
)), )),
signup: var("SIGNUP").map_or(SignupStrategy::default(), |s| s.parse().unwrap()),
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
database_url: var("DATABASE_URL") database_url: var("DATABASE_URL")
.unwrap_or_else(|_| format!("postgres://plume:plume@localhost/{}", DB_NAME)), .unwrap_or_else(|_| format!("postgres://plume:plume@localhost/{}", DB_NAME)),
@@ -347,6 +377,7 @@ lazy_static! {
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()), default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
media_directory: var("MEDIA_UPLOAD_DIRECTORY") media_directory: var("MEDIA_UPLOAD_DIRECTORY")
.unwrap_or_else(|_| "static/media".to_owned()), .unwrap_or_else(|_| "static/media".to_owned()),
mail: get_mail_config(),
ldap: get_ldap_config(), ldap: get_ldap_config(),
proxy: get_proxy_config(), proxy: get_proxy_config(),
}; };
+2 -1
View File
@@ -69,7 +69,8 @@ pub(crate) mod tests {
impl CustomizeConnection<Connection, ConnError> for TestConnectionCustomizer { impl CustomizeConnection<Connection, ConnError> for TestConnectionCustomizer {
fn on_acquire(&self, conn: &mut Connection) -> Result<(), ConnError> { fn on_acquire(&self, conn: &mut Connection) -> Result<(), ConnError> {
PragmaForeignKey.on_acquire(conn)?; PragmaForeignKey.on_acquire(conn)?;
Ok(conn.begin_test_transaction().unwrap()) conn.begin_test_transaction().unwrap();
Ok(())
} }
} }
} }
+158
View File
@@ -0,0 +1,158 @@
use crate::{
blocklisted_emails::BlocklistedEmail,
db_conn::DbConn,
schema::email_signups,
users::{NewUser, Role, User},
Error, Result,
};
use chrono::{offset::Utc, Duration, NaiveDateTime};
use diesel::{
Connection as _, ExpressionMethods, Identifiable, Insertable, QueryDsl, Queryable, RunQueryDsl,
};
use plume_common::utils::random_hex;
use std::ops::Deref;
const TOKEN_VALIDITY_HOURS: i64 = 2;
#[repr(transparent)]
pub struct Token(String);
impl From<String> for Token {
fn from(string: String) -> Self {
Token(string)
}
}
impl From<Token> for String {
fn from(token: Token) -> Self {
token.0
}
}
impl Deref for Token {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Token {
fn generate() -> Self {
Self(random_hex())
}
}
#[derive(Identifiable, Queryable)]
pub struct EmailSignup {
pub id: i32,
pub email: String,
pub token: String,
pub expiration_date: NaiveDateTime,
}
#[derive(Insertable)]
#[table_name = "email_signups"]
pub struct NewEmailSignup<'a> {
pub email: &'a str,
pub token: &'a str,
pub expiration_date: NaiveDateTime,
}
impl EmailSignup {
pub fn start(conn: &DbConn, email: &str) -> Result<Token> {
Self::ensure_email_not_blocked(conn, email)?;
conn.transaction(|| {
Self::ensure_user_not_exist_by_email(conn, email)?;
let _rows = Self::delete_existings_by_email(conn, email)?;
let token = Token::generate();
let expiration_date = Utc::now()
.naive_utc()
.checked_add_signed(Duration::hours(TOKEN_VALIDITY_HOURS))
.expect("could not calculate expiration date");
let new_signup = NewEmailSignup {
email,
token: &token,
expiration_date,
};
let _rows = diesel::insert_into(email_signups::table)
.values(new_signup)
.execute(&**conn)?;
Ok(token)
})
}
pub fn find_by_token(conn: &DbConn, token: Token) -> Result<Self> {
let signup = email_signups::table
.filter(email_signups::token.eq(token.as_str()))
.first::<Self>(&**conn)
.map_err(Error::from)?;
Ok(signup)
}
pub fn confirm(&self, conn: &DbConn) -> Result<()> {
Self::ensure_email_not_blocked(conn, &self.email)?;
conn.transaction(|| {
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
if self.expired() {
Self::delete_existings_by_email(conn, &self.email)?;
return Err(Error::Expired);
}
Ok(())
})
}
pub fn complete(&self, conn: &DbConn, username: String, password: String) -> Result<User> {
Self::ensure_email_not_blocked(conn, &self.email)?;
conn.transaction(|| {
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
let user = NewUser::new_local(
conn,
username,
"".to_string(),
Role::Normal,
"",
self.email.clone(),
Some(User::hash_pass(&password)?),
)?;
self.delete(conn)?;
Ok(user)
})
}
fn delete(&self, conn: &DbConn) -> Result<()> {
let _rows = diesel::delete(self).execute(&**conn).map_err(Error::from)?;
Ok(())
}
fn ensure_email_not_blocked(conn: &DbConn, email: &str) -> Result<()> {
if let Some(x) = BlocklistedEmail::matches_blocklist(conn, email)? {
Err(Error::Blocklisted(x.notify_user, x.notification_text))
} else {
Ok(())
}
}
fn ensure_user_not_exist_by_email(conn: &DbConn, email: &str) -> Result<()> {
if User::email_used(conn, email)? {
let _rows = Self::delete_existings_by_email(conn, email)?;
return Err(Error::UserAlreadyExists);
}
Ok(())
}
fn delete_existings_by_email(conn: &DbConn, email: &str) -> Result<usize> {
let existing_signups = email_signups::table.filter(email_signups::email.eq(email));
diesel::delete(existing_signups)
.execute(&**conn)
.map_err(Error::from)
}
fn expired(&self) -> bool {
self.expiration_date < Utc::now().naive_utc()
}
}
+166 -55
View File
@@ -1,8 +1,13 @@
use crate::{ use crate::{
ap_url, db_conn::DbConn, 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::{
activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo},
base::AnyBase,
iri_string::types::IriString,
prelude::*,
}; };
use activitypub::activity::{Accept, Follow as FollowAct, Undo};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
broadcast, broadcast,
@@ -53,15 +58,13 @@ impl Follow {
pub fn to_activity(&self, conn: &Connection) -> Result<FollowAct> { pub fn to_activity(&self, conn: &Connection) -> Result<FollowAct> {
let user = User::get(conn, self.follower_id)?; let user = User::get(conn, self.follower_id)?;
let target = User::get(conn, self.following_id)?; let target = User::get(conn, self.following_id)?;
let target_id = target.ap_url.parse::<IriString>()?;
let mut act = FollowAct::new(user.ap_url.parse::<IriString>()?, target_id.clone());
act.set_id(self.ap_url.parse::<IriString>()?);
act.set_many_tos(vec![target_id]);
act.set_many_ccs(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
let mut act = FollowAct::default();
act.follow_props.set_actor_link::<Id>(user.into_id())?;
act.follow_props
.set_object_link::<Id>(target.clone().into_id())?;
act.object_props.set_id_string(self.ap_url.clone())?;
act.object_props.set_to_link_vec(vec![target.into_id()])?;
act.object_props
.set_cc_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?;
Ok(act) Ok(act)
} }
@@ -94,49 +97,55 @@ impl Follow {
NewFollow { NewFollow {
follower_id: from_id, follower_id: from_id,
following_id: target_id, following_id: target_id,
ap_url: follow.object_props.id_string()?, ap_url: follow
.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.to_string(),
}, },
)?; )?;
res.notify(conn)?; res.notify(conn)?;
let mut accept = Accept::default(); let accept = res.build_accept(from, target, follow)?;
let accept_id = ap_url(&format!( broadcast(target, accept, vec![from.clone()], CONFIG.proxy().cloned());
"{}/follow/{}/accept",
CONFIG.base_url.as_str(),
&res.id
));
accept.object_props.set_id_string(accept_id)?;
accept
.object_props
.set_to_link_vec(vec![from.clone().into_id()])?;
accept
.object_props
.set_cc_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?;
accept
.accept_props
.set_actor_link::<Id>(target.clone().into_id())?;
accept.accept_props.set_object_object(follow)?;
broadcast(
&*target,
accept,
vec![from.clone()],
CONFIG.proxy().cloned(),
);
Ok(res) Ok(res)
} }
pub fn build_accept<A: Signer + IntoId + Clone, B: Clone + AsActor<T> + IntoId, T>(
&self,
from: &B,
target: &A,
follow: FollowAct,
) -> Result<Accept> {
let mut accept = Accept::new(
target.clone().into_id().parse::<IriString>()?,
AnyBase::from_extended(follow)?,
);
let accept_id = ap_url(&format!(
"{}/follows/{}/accept",
CONFIG.base_url.as_str(),
self.id
));
accept.set_id(accept_id.parse::<IriString>()?);
accept.set_many_tos(vec![from.clone().into_id().parse::<IriString>()?]);
accept.set_many_ccs(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
Ok(accept)
}
pub fn build_undo(&self, conn: &Connection) -> Result<Undo> { pub fn build_undo(&self, conn: &Connection) -> Result<Undo> {
let mut undo = Undo::default(); let mut undo = Undo::new(
undo.undo_props User::get(conn, self.follower_id)?
.set_actor_link(User::get(conn, self.follower_id)?.into_id())?; .ap_url
undo.object_props .parse::<IriString>()?,
.set_id_string(format!("{}/undo", self.ap_url))?; self.ap_url.parse::<IriString>()?,
undo.undo_props );
.set_object_link::<Id>(self.clone().into_id())?; undo.set_id(format!("{}/undo", self.ap_url).parse::<IriString>()?);
undo.object_props undo.set_many_tos(vec![User::get(conn, self.following_id)?
.set_to_link_vec(vec![User::get(conn, self.following_id)?.into_id()])?; .ap_url
undo.object_props .parse::<IriString>()?]);
.set_cc_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; undo.set_many_ccs(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
Ok(undo) Ok(undo)
} }
} }
@@ -148,11 +157,7 @@ impl AsObject<User, FollowAct, &DbConn> for User {
fn activity(self, conn: &DbConn, 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 mut follow = FollowAct::default(); let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?);
follow.object_props.set_id_string(id.to_string())?;
follow
.follow_props
.set_actor_link::<Id>(actor.clone().into_id())?;
Follow::accept_follow(conn, &actor, &self, follow, actor.id, self.id) Follow::accept_follow(conn, &actor, &self, follow, actor.id, self.id)
} }
} }
@@ -168,7 +173,11 @@ impl FromId<DbConn> for Follow {
fn from_activity(conn: &DbConn, 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_props.actor_link::<Id>()?, follow
.actor_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -176,13 +185,21 @@ impl FromId<DbConn> for Follow {
let target = User::from_id( let target = User::from_id(
conn, conn,
&follow.follow_props.object_link::<Id>()?, follow
.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
.map_err(|(_, e)| e)?; .map_err(|(_, e)| e)?;
Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id) Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id)
} }
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
} }
impl AsObject<User, Undo, &DbConn> for Follow { impl AsObject<User, Undo, &DbConn> for Follow {
@@ -195,7 +212,7 @@ impl AsObject<User, Undo, &DbConn> for Follow {
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)?;
} }
@@ -215,8 +232,29 @@ 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}; use crate::{tests::db, users::tests as user_tests, users::tests::fill_database};
use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Follow, User, User, Vec<User>) {
let users = fill_database(conn);
let following = &users[1];
let follower = &users[2];
let mut follow = Follow::insert(
conn,
NewFollow {
follower_id: follower.id,
following_id: following.id,
ap_url: "".into(),
},
)
.unwrap();
// following.ap_url = format!("https://plu.me/follows/{}", follow.id);
follow.ap_url = format!("https://plu.me/follows/{}", follow.id);
(follow, following.to_owned(), follower.to_owned(), users)
}
#[test] #[test]
fn test_id() { fn test_id() {
@@ -251,4 +289,77 @@ mod tests {
Ok(()) Ok(())
}) })
} }
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, _following, _follower, _users) = prepare_activity(&conn);
let act = follow.to_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}", follow.id),
"object": "https://plu.me/@/user/",
"to": ["https://plu.me/@/user/"],
"type": "Follow"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_accept() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, following, follower, _users) = prepare_activity(&conn);
let act = follow.build_accept(&follower, &following, follow.to_activity(&conn)?)?;
let expected = json!({
"actor": "https://plu.me/@/user/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://127.0.0.1:7878/follows/{}/accept", follow.id),
"object": {
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}", follow.id),
"object": "https://plu.me/@/user/",
"to": ["https://plu.me/@/user/"],
"type": "Follow"
},
"to": ["https://plu.me/@/other/"],
"type": "Accept"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, _following, _follower, _users) = prepare_activity(&conn);
let act = follow.build_undo(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}/undo", follow.id),
"object": format!("https://plu.me/follows/{}", follow.id),
"to": ["https://plu.me/@/user/"],
"type": "Undo"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
} }
+17 -17
View File
@@ -1,4 +1,4 @@
use activitypub::activity::*; use activitystreams::activity::{Announce, Create, Delete, Follow, Like, Undo, Update};
use crate::{ use crate::{
comments::Comment, comments::Comment,
@@ -82,9 +82,9 @@ pub(crate) mod tests {
use crate::post_authors::*; use crate::post_authors::*;
use crate::posts::*; use crate::posts::*;
let (users, blogs) = blog_fill_db(&conn); let (users, blogs) = blog_fill_db(conn);
let post = Post::insert( let post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "testing".to_owned(), slug: "testing".to_owned(),
@@ -94,15 +94,15 @@ pub(crate) mod tests {
license: "WTFPL".to_owned(), license: "WTFPL".to_owned(),
creation_date: None, creation_date: None,
ap_url: format!("https://plu.me/~/{}/testing", blogs[0].actor_id), ap_url: format!("https://plu.me/~/{}/testing", blogs[0].actor_id),
subtitle: String::new(), subtitle: "Bye".to_string(),
source: String::new(), source: "Hello".to_string(),
cover_id: None, cover_id: None,
}, },
) )
.unwrap(); .unwrap();
PostAuthor::insert( PostAuthor::insert(
&conn, conn,
NewPostAuthor { NewPostAuthor {
post_id: post.id, post_id: post.id,
author_id: users[0].id, author_id: users[0].id,
@@ -190,7 +190,7 @@ pub(crate) mod tests {
}); });
assert!(matches!( assert!(matches!(
super::inbox(&conn, act.clone()), super::inbox(&conn, act),
Err(super::Error::Inbox( Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_), box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
)) ))
@@ -221,7 +221,7 @@ pub(crate) mod tests {
}); });
assert!(matches!( assert!(matches!(
super::inbox(&conn, act.clone()), super::inbox(&conn, act),
Err(super::Error::Inbox( Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_), box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
)) ))
@@ -249,7 +249,7 @@ pub(crate) mod tests {
}); });
assert!(matches!( assert!(matches!(
super::inbox(&conn, act.clone()), super::inbox(&conn, act),
Err(super::Error::Inbox( Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_), box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
)) ))
@@ -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/~/Blog/my-article", "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",
@@ -286,11 +286,11 @@ pub(crate) mod tests {
match super::inbox(&conn, act).unwrap() { match super::inbox(&conn, act).unwrap() {
super::InboxResult::Post(p) => { super::InboxResult::Post(p) => {
assert!(p.is_author(&conn, users[0].id).unwrap()); assert!(p.is_author(&conn, users[0].id).unwrap());
assert_eq!(p.source, "Hello.".to_owned()); assert_eq!(p.source, "Hello".to_owned());
assert_eq!(p.blog_id, blogs[0].id); assert_eq!(p.blog_id, blogs[0].id);
assert_eq!(p.content, SafeString::new("Hello.")); assert_eq!(p.content, SafeString::new("Hello"));
assert_eq!(p.subtitle, "Bye.".to_owned()); assert_eq!(p.subtitle, "Bye".to_owned());
assert_eq!(p.title, "My Article".to_owned()); assert_eq!(p.title, "Testing".to_owned());
} }
_ => panic!("Unexpected result"), _ => panic!("Unexpected result"),
}; };
@@ -324,7 +324,7 @@ pub(crate) mod tests {
}); });
assert!(matches!( assert!(matches!(
super::inbox(&conn, act.clone()), super::inbox(&conn, act),
Err(super::Error::Inbox( Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_), box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
)) ))
@@ -362,7 +362,7 @@ pub(crate) mod tests {
}); });
assert!(matches!( assert!(matches!(
super::inbox(&conn, act.clone()), super::inbox(&conn, act),
Err(super::Error::Inbox( Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_), box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
)) ))
@@ -397,7 +397,7 @@ pub(crate) mod tests {
}); });
assert!(matches!( assert!(matches!(
super::inbox(&conn, act.clone()), super::inbox(&conn, act),
Err(super::Error::Inbox( Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_), box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
)) ))
+44 -3
View File
@@ -3,11 +3,12 @@ use crate::{
medias::Media, medias::Media,
safe_string::SafeString, safe_string::SafeString,
schema::{instances, users}, schema::{instances, users},
users::{Role, User}, users::{NewUser, Role, User},
Connection, Error, Result, Connection, Error, Result,
}; };
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
use once_cell::sync::OnceCell;
use plume_common::utils::md_to_html; use plume_common::utils::md_to_html;
use std::sync::RwLock; use std::sync::RwLock;
@@ -45,6 +46,9 @@ lazy_static! {
static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None); static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None);
} }
const LOCAL_INSTANCE_USERNAME: &str = "__instance__";
static LOCAL_INSTANCE_USER: OnceCell<User> = OnceCell::new();
impl Instance { impl Instance {
pub fn set_local(self) { pub fn set_local(self) {
LOCAL_INSTANCE.write().unwrap().replace(self); LOCAL_INSTANCE.write().unwrap().replace(self);
@@ -76,6 +80,42 @@ impl Instance {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn create_local_instance_user(conn: &Connection) -> Result<User> {
let instance = Instance::get_local()?;
let email = format!("{}@{}", LOCAL_INSTANCE_USERNAME, &instance.public_domain);
NewUser::new_local(
conn,
LOCAL_INSTANCE_USERNAME.into(),
instance.public_domain,
Role::Instance,
"Local instance",
email,
None,
)
}
pub fn get_local_instance_user() -> Option<&'static User> {
LOCAL_INSTANCE_USER.get()
}
pub fn get_local_instance_user_uncached(conn: &Connection) -> Result<User> {
users::table
.filter(users::role.eq(3))
.first(conn)
.or_else(|err| match err {
NotFound => Self::create_local_instance_user(conn),
_ => Err(Error::Db(err)),
})
}
pub fn cache_local_instance_user(conn: &Connection) {
let _ = LOCAL_INSTANCE_USER.get_or_init(|| {
Self::get_local_instance_user_uncached(conn)
.or_else(|_| Self::create_local_instance_user(conn))
.expect("Failed to cache local instance user")
});
}
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> { pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> {
instances::table instances::table
.order(instances::public_domain.asc()) .order(instances::public_domain.asc())
@@ -304,6 +344,7 @@ pub(crate) mod tests {
}) })
.collect(); .collect();
Instance::cache_local(conn); Instance::cache_local(conn);
Instance::cache_local_instance_user(conn);
res res
} }
@@ -482,7 +523,7 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
let inst = Instance::get(conn, inst.id).unwrap(); let inst = Instance::get(conn, inst.id).unwrap();
assert_eq!(inst.name, "NewName".to_owned()); assert_eq!(inst.name, "NewName".to_owned());
assert_eq!(inst.open_registrations, false); assert!(!inst.open_registrations);
assert_eq!( assert_eq!(
inst.long_description.get(), inst.long_description.get(),
"[long_description](/with_link)" "[long_description](/with_link)"
Executable → Regular
+204 -21
View File
@@ -1,4 +1,3 @@
#![feature(try_trait)]
#![feature(never_type)] #![feature(never_type)]
#![feature(proc_macro_hygiene)] #![feature(proc_macro_hygiene)]
#![feature(box_patterns)] #![feature(box_patterns)]
@@ -17,10 +16,19 @@ extern crate serde_json;
#[macro_use] #[macro_use]
extern crate tantivy; extern crate tantivy;
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::smtp;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plume_common::activity_pub::inbox::InboxError; 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")))]
@@ -66,6 +74,7 @@ pub enum Error {
Url, Url,
Webfinger, Webfinger,
Expired, Expired,
UserAlreadyExists,
} }
impl From<bcrypt::BcryptError> for Error { impl From<bcrypt::BcryptError> for Error {
@@ -80,20 +89,26 @@ impl From<openssl::error::ErrorStack> for Error {
} }
} }
impl From<sign::Error> for Error {
fn from(_: sign::Error) -> Self {
Error::Signature
}
}
impl From<diesel::result::Error> for Error { impl From<diesel::result::Error> for Error {
fn from(err: diesel::result::Error) -> Self { fn from(err: diesel::result::Error) -> Self {
Error::Db(err) Error::Db(err)
} }
} }
impl From<std::option::NoneError> for Error { impl From<url::ParseError> for Error {
fn from(_: std::option::NoneError) -> Self { fn from(_: url::ParseError) -> Self {
Error::NotFound Error::Url
} }
} }
impl From<url::ParseError> for Error { impl From<iri_string::validate::Error> for Error {
fn from(_: url::ParseError) -> Self { fn from(_: iri_string::validate::Error) -> Self {
Error::Url Error::Url
} }
} }
@@ -116,12 +131,9 @@ impl From<reqwest::header::InvalidHeaderValue> for Error {
} }
} }
impl From<activitypub::Error> for Error { impl From<activitystreams::checked::CheckError> for Error {
fn from(err: activitypub::Error) -> Self { fn from(_: activitystreams::checked::CheckError) -> Error {
match err { Error::MissingApProperty
activitypub::Error::NotFound => Error::MissingApProperty,
_ => Error::SerDe,
}
} }
} }
@@ -158,6 +170,19 @@ impl From<InboxError<Error>> for Error {
} }
} }
impl From<request::Error> for Error {
fn from(_err: request::Error) -> Error {
Error::Request
}
}
impl From<PreferredUsernameError> for Error {
fn from(err: PreferredUsernameError) -> Error {
tracing::trace!("{:?}", err);
Error::InvalidValue
}
}
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
@@ -165,7 +190,7 @@ pub type Result<T> = std::result::Result<T, Error>;
/// ///
/// Usage: /// Usage:
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// find_by!(model_table, name_of_the_function, field1 as String, field2 as i32); /// find_by!(model_table, name_of_the_function, field1 as String, field2 as i32);
/// } /// }
@@ -189,7 +214,7 @@ macro_rules! find_by {
/// ///
/// Usage: /// Usage:
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// list_by!(model_table, name_of_the_function, field1 as String); /// list_by!(model_table, name_of_the_function, field1 as String);
/// } /// }
@@ -213,7 +238,7 @@ macro_rules! list_by {
/// ///
/// # Usage /// # Usage
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// get!(model_table); /// get!(model_table);
/// } /// }
@@ -236,7 +261,7 @@ macro_rules! get {
/// ///
/// # Usage /// # Usage
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// insert!(model_table, NewModelType); /// insert!(model_table, NewModelType);
/// } /// }
@@ -268,7 +293,7 @@ macro_rules! insert {
/// ///
/// # Usage /// # Usage
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// last!(model_table); /// last!(model_table);
/// } /// }
@@ -289,16 +314,131 @@ 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)
} }
pub trait SmtpNewWithAddr {
fn new_with_addr(
addr: (&str, u16),
) -> std::result::Result<smtp::SmtpClient, smtp::error::Error>;
}
impl SmtpNewWithAddr for smtp::SmtpClient {
// Stolen from lettre::smtp::SmtpClient::new_simple()
fn new_with_addr(addr: (&str, u16)) -> std::result::Result<Self, smtp::error::Error> {
use native_tls::TlsConnector;
use smtp::{
client::net::{ClientTlsParameters, DEFAULT_TLS_PROTOCOLS},
ClientSecurity, SmtpClient,
};
let (domain, port) = addr;
let mut tls_builder = TlsConnector::builder();
tls_builder.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]));
let tls_parameters =
ClientTlsParameters::new(domain.to_string(), tls_builder.build().unwrap());
SmtpClient::new((domain, port), ClientSecurity::Wrapper(tls_parameters))
}
}
#[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 diesel::r2d2::ConnectionManager; use diesel::r2d2::ConnectionManager;
use plume_common::utils::random_hex; use plume_common::utils::random_hex;
use std::env::temp_dir; use std::env::temp_dir;
@@ -314,7 +454,7 @@ mod tests {
}; };
} }
pub fn db<'a>() -> db_conn::DbConn { pub fn db() -> db_conn::DbConn {
db_conn::DbConn((*DB_POOL).get().unwrap()) db_conn::DbConn((*DB_POOL).get().unwrap())
} }
@@ -331,6 +471,47 @@ mod tests {
pool pool
}; };
} }
#[cfg(feature = "postgres")]
pub(crate) fn format_datetime(dt: &NaiveDateTime) -> String {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.timestamp_subsec_micros()
)
}
#[cfg(feature = "sqlite")]
pub(crate) fn format_datetime(dt: &NaiveDateTime) -> String {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
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;
@@ -342,6 +523,7 @@ pub mod blogs;
pub mod comment_seers; pub mod comment_seers;
pub mod comments; pub mod comments;
pub mod db_conn; pub mod db_conn;
pub mod email_signups;
pub mod follows; pub mod follows;
pub mod headers; pub mod headers;
pub mod inbox; pub mod inbox;
@@ -362,6 +544,7 @@ pub mod safe_string;
#[allow(unused_imports)] #[allow(unused_imports)]
pub mod schema; pub mod schema;
pub mod search; pub mod search;
pub mod signups;
pub mod tags; pub mod tags;
pub mod timeline; pub mod timeline;
pub mod users; pub mod users;
+116 -38
View File
@@ -1,13 +1,19 @@
use crate::{ use crate::{
db_conn::DbConn, 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::{
activity::{ActorAndObjectRef, Like as LikeAct, Undo},
base::AnyBase,
iri_string::types::IriString,
prelude::*,
}; };
use activitypub::activity;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
Id, IntoId, PUBLIC_VISIBILITY, sign::Signer,
PUBLIC_VISIBILITY,
}; };
#[derive(Clone, Queryable, Identifiable)] #[derive(Clone, Queryable, Identifiable)]
@@ -33,18 +39,16 @@ impl Like {
find_by!(likes, find_by_ap_url, ap_url as &str); find_by!(likes, find_by_ap_url, ap_url as &str);
find_by!(likes, find_by_user_on_post, user_id as i32, post_id as i32); find_by!(likes, find_by_user_on_post, user_id as i32, post_id as i32);
pub fn to_activity(&self, conn: &Connection) -> Result<activity::Like> { pub fn to_activity(&self, conn: &Connection) -> Result<LikeAct> {
let mut act = activity::Like::default(); let mut act = LikeAct::new(
act.like_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; Post::get(conn, self.post_id)?.ap_url.parse::<IriString>()?,
act.like_props );
.set_object_link(Post::get(conn, self.post_id)?.into_id())?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![User::get(conn, self.user_id)?
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .followers_endpoint
act.object_props.set_cc_link_vec(vec![Id::new( .parse::<IriString>()?]);
User::get(conn, self.user_id)?.followers_endpoint, act.set_id(self.ap_url.parse::<IriString>()?);
)])?;
act.object_props.set_id_string(self.ap_url.clone())?;
Ok(act) Ok(act)
} }
@@ -66,24 +70,22 @@ impl Like {
Ok(()) Ok(())
} }
pub fn build_undo(&self, conn: &Connection) -> Result<activity::Undo> { pub fn build_undo(&self, conn: &Connection) -> Result<Undo> {
let mut act = activity::Undo::default(); let mut act = Undo::new(
act.undo_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; AnyBase::from_extended(self.to_activity(conn)?)?,
act.undo_props.set_object_object(self.to_activity(conn)?)?; );
act.object_props act.set_id(format!("{}#delete", self.ap_url).parse::<IriString>()?);
.set_id_string(format!("{}#delete", self.ap_url))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![User::get(conn, self.user_id)?
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .followers_endpoint
act.object_props.set_cc_link_vec(vec![Id::new( .parse::<IriString>()?]);
User::get(conn, self.user_id)?.followers_endpoint,
)])?;
Ok(act) Ok(act)
} }
} }
impl AsObject<User, activity::Like, &DbConn> for Post { impl AsObject<User, LikeAct, &DbConn> for Post {
type Error = Error; type Error = Error;
type Output = Like; type Output = Like;
@@ -105,19 +107,22 @@ impl AsObject<User, activity::Like, &DbConn> for Post {
impl FromId<DbConn> for Like { impl FromId<DbConn> for Like {
type Error = Error; type Error = Error;
type Object = activity::Like; type Object = LikeAct;
fn from_db(conn: &DbConn, 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: &DbConn, act: activity::Like) -> Result<Self> { fn from_activity(conn: &DbConn, act: LikeAct) -> Result<Self> {
let res = Like::insert( let res = Like::insert(
conn, conn,
NewLike { NewLike {
post_id: Post::from_id( post_id: Post::from_id(
conn, conn,
&act.like_props.object_link::<Id>()?, act.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -125,21 +130,31 @@ impl FromId<DbConn> for Like {
.id, .id,
user_id: User::from_id( user_id: User::from_id(
conn, conn,
&act.like_props.actor_link::<Id>()?, act.actor_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
.map_err(|(_, e)| e)? .map_err(|(_, e)| e)?
.id, .id,
ap_url: act.object_props.id_string()?, ap_url: act
.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_string(),
}, },
)?; )?;
res.notify(conn)?; res.notify(conn)?;
Ok(res) Ok(res)
} }
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
} }
impl AsObject<User, activity::Undo, &DbConn> for Like { impl AsObject<User, Undo, &DbConn> for Like {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
@@ -148,7 +163,7 @@ impl AsObject<User, activity::Undo, &DbConn> for Like {
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(())
@@ -160,8 +175,7 @@ impl AsObject<User, activity::Undo, &DbConn> for Like {
impl NewLike { impl NewLike {
pub fn new(p: &Post, u: &User) -> Self { pub fn new(p: &Post, u: &User) -> Self {
// TODO: this URL is not valid let ap_url = format!("{}like/{}", u.ap_url, p.ap_url);
let ap_url = format!("{}/like/{}", u.ap_url, p.ap_url);
NewLike { NewLike {
post_id: p.id, post_id: p.id,
user_id: u.id, user_id: u.id,
@@ -169,3 +183,67 @@ impl NewLike {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::diesel::Connection;
use crate::{inbox::tests::fill_database, tests::db};
use assert_json_diff::assert_json_eq;
use serde_json::{json, to_value};
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&conn, NewLike::new(post, user))?;
let act = like.to_activity(&conn).unwrap();
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/Blog%20Name/testing",
"object": "https://plu.me/~/Blog%20Name/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Like",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&conn, NewLike::new(post, user))?;
let act = like.build_undo(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/Blog%20Name/testing#delete",
"object": {
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/Blog%20Name/testing",
"object": "https://plu.me/~/Blog%20Name/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Like",
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Undo",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}
+2 -1
View File
@@ -143,6 +143,7 @@ macro_rules! func {
} }
} }
#[allow(dead_code)]
#[derive(Clone, Queryable, Identifiable)] #[derive(Clone, Queryable, Identifiable)]
struct ListElem { struct ListElem {
pub id: i32, pub id: i32,
@@ -412,7 +413,7 @@ mod tests {
&List::find_for_user_by_name(conn, l1.user_id, &l1.name).unwrap(), &List::find_for_user_by_name(conn, l1.user_id, &l1.name).unwrap(),
); );
l_eq( l_eq(
&&l1u, &l1u,
&List::find_for_user_by_name(conn, l1u.user_id, &l1u.name).unwrap(), &List::find_for_user_by_name(conn, l1u.user_id, &l1u.name).unwrap(),
); );
Ok(()) Ok(())
+55 -40
View File
@@ -2,13 +2,12 @@ use crate::{
ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias, ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias,
users::User, Connection, Error, Result, CONFIG, users::User, Connection, Error, Result, CONFIG,
}; };
use activitypub::object::Image; use activitystreams::{object::Image, prelude::*};
use askama_escape::escape;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use guid_create::GUID; use guid_create::GUID;
use plume_common::{ use plume_common::{
activity_pub::{inbox::FromId, Id}, activity_pub::{inbox::FromId, request, ToAsString, ToAsUri},
utils::MediaProcessor, utils::{escape, MediaProcessor},
}; };
use std::{ use std::{
fs::{self, DirBuilder}, fs::{self, DirBuilder},
@@ -43,7 +42,7 @@ pub struct NewMedia {
pub owner_id: i32, pub owner_id: i32,
} }
#[derive(PartialEq)] #[derive(PartialEq, Eq)]
pub enum MediaCategory { pub enum MediaCategory {
Image, Image,
Audio, Audio,
@@ -104,8 +103,8 @@ impl Media {
pub fn category(&self) -> MediaCategory { pub fn category(&self) -> MediaCategory {
match &*self match &*self
.file_path .file_path
.rsplitn(2, '.') .rsplit_once('.')
.next() .map(|x| x.1)
.expect("Media::category: extension error") .expect("Media::category: extension error")
.to_lowercase() .to_lowercase()
{ {
@@ -156,7 +155,11 @@ impl Media {
if self.is_remote { if self.is_remote {
Ok(self.remote_url.clone().unwrap_or_default()) Ok(self.remote_url.clone().unwrap_or_default())
} else { } else {
let file_path = self.file_path.replace(path::MAIN_SEPARATOR, "/"); let file_path = self.file_path.replace(path::MAIN_SEPARATOR, "/").replacen(
&CONFIG.media_directory,
"static/media",
1,
); // "static/media" from plume::routs::plume_media_files()
Ok(ap_url(&format!( Ok(ap_url(&format!(
"{}/{}", "{}/{}",
Instance::get_local()?.public_domain, Instance::get_local()?.public_domain,
@@ -204,33 +207,36 @@ 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: &DbConn, image: &Image) -> Result<Media> {
let remote_url = image.object_props.url_string().ok()?; let remote_url = image
.url()
.and_then(|url| url.to_as_uri())
.ok_or(Error::MissingApProperty)?;
let path = determine_mirror_file_path(&remote_url); let path = determine_mirror_file_path(&remote_url);
let parent = path.parent()?; let parent = path.parent().ok_or(Error::InvalidValue)?;
if !parent.is_dir() { if !parent.is_dir() {
DirBuilder::new().recursive(true).create(parent)?; DirBuilder::new().recursive(true).create(parent)?;
} }
let mut dest = fs::File::create(path.clone()).ok()?; let mut dest = fs::File::create(path.clone())?;
// TODO: conditional GET // TODO: conditional GET
if let Some(proxy) = CONFIG.proxy() { request::get(
reqwest::ClientBuilder::new().proxy(proxy.clone()).build()? remote_url.as_str(),
} else { User::get_sender(),
reqwest::Client::new() CONFIG.proxy().cloned(),
} )?
.get(remote_url.as_str()) .copy_to(&mut dest)?;
.send()
.ok()?
.copy_to(&mut dest)
.ok()?;
Media::find_by_file_path(conn, &path.to_str()?) Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?)
.and_then(|mut media| { .and_then(|mut media| {
let mut updated = false; let mut updated = false;
let alt_text = image.object_props.content_string().ok()?; let alt_text = image
let sensitive = image.object_props.summary_string().is_ok(); .content()
let content_warning = image.object_props.summary_string().ok(); .and_then(|content| content.to_as_string())
.ok_or(Error::NotFound)?;
let summary = image.summary().and_then(|summary| summary.to_as_string());
let sensitive = summary.is_some();
let content_warning = summary;
if media.alt_text != alt_text { if media.alt_text != alt_text {
media.alt_text = alt_text; media.alt_text = alt_text;
updated = true; updated = true;
@@ -257,24 +263,25 @@ impl Media {
Ok(media) Ok(media)
}) })
.or_else(|_| { .or_else(|_| {
let summary = image.summary().and_then(|summary| summary.to_as_string());
Media::insert( Media::insert(
conn, conn,
NewMedia { NewMedia {
file_path: path.to_str()?.to_string(), file_path: path.to_str().ok_or(Error::InvalidValue)?.to_string(),
alt_text: image.object_props.content_string().ok()?, alt_text: image
.content()
.and_then(|content| content.to_as_string())
.ok_or(Error::NotFound)?,
is_remote: false, is_remote: false,
remote_url: None, remote_url: None,
sensitive: image.object_props.summary_string().is_ok(), sensitive: summary.is_some(),
content_warning: image.object_props.summary_string().ok(), content_warning: summary,
owner_id: User::from_id( owner_id: User::from_id(
conn, conn,
image &image
.object_props .attributed_to()
.attributed_to_link_vec::<Id>() .and_then(|attributed_to| attributed_to.to_as_uri())
.ok()? .ok_or(Error::MissingApProperty)?,
.into_iter()
.next()?
.as_ref(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -321,7 +328,7 @@ fn determine_mirror_file_path(url: &str) -> PathBuf {
.next() .next()
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.unwrap_or_else(|| String::from("png")); .unwrap_or_else(|| String::from("png"));
file_path.push(format!("{}.{}", GUID::rand().to_string(), ext)); file_path.push(format!("{}.{}", GUID::rand(), ext));
}); });
file_path file_path
} }
@@ -336,7 +343,7 @@ pub(crate) mod tests {
use std::path::Path; use std::path::Path;
pub(crate) fn fill_database(conn: &Conn) -> (Vec<User>, Vec<Media>) { pub(crate) fn fill_database(conn: &Conn) -> (Vec<User>, Vec<Media>) {
let mut wd = current_dir().unwrap().to_path_buf(); let mut wd = current_dir().unwrap();
while wd.pop() { while wd.pop() {
if wd.join(".git").exists() { if wd.join(".git").exists() {
set_current_dir(wd).unwrap(); set_current_dir(wd).unwrap();
@@ -391,7 +398,15 @@ pub(crate) mod tests {
pub(crate) fn clean(conn: &Conn) { pub(crate) fn clean(conn: &Conn) {
//used to remove files generated by tests //used to remove files generated by tests
for media in Media::list_all_medias(conn).unwrap() { for media in Media::list_all_medias(conn).unwrap() {
media.delete(conn).unwrap(); if let Some(err) = media.delete(conn).err() {
match &err {
Error::Io(e) => match e.kind() {
std::io::ErrorKind::NotFound => (),
_ => panic!("{:?}", err),
},
_ => panic!("{:?}", err),
}
}
} }
} }
@@ -441,7 +456,7 @@ pub(crate) mod tests {
let media = Media::insert( let media = Media::insert(
conn, conn,
NewMedia { NewMedia {
file_path: path.clone(), file_path: path,
alt_text: "alt message".to_owned(), alt_text: "alt message".to_owned(),
is_remote: false, is_remote: false,
remote_url: None, remote_url: None,
+77 -12
View File
@@ -2,7 +2,11 @@ use crate::{
comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions, comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions,
users::User, Connection, Error, Result, users::User, Connection, Error, Result,
}; };
use activitypub::link; use activitystreams::{
base::BaseExt,
iri_string::types::IriString,
link::{self, LinkExt},
};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::inbox::AsActor; use plume_common::activity_pub::inbox::AsActor;
@@ -47,26 +51,28 @@ impl Mention {
pub fn get_user(&self, conn: &Connection) -> Result<User> { pub fn get_user(&self, conn: &Connection) -> Result<User> {
match self.get_post(conn) { match self.get_post(conn) {
Ok(p) => Ok(p.get_authors(conn)?.into_iter().next()?), Ok(p) => Ok(p
.get_authors(conn)?
.into_iter()
.next()
.ok_or(Error::NotFound)?),
Err(_) => self.get_comment(conn).and_then(|c| c.get_author(conn)), Err(_) => self.get_comment(conn).and_then(|c| c.get_author(conn)),
} }
} }
pub fn build_activity(conn: &DbConn, 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::default(); let mut mention = link::Mention::new();
mention.link_props.set_href_string(user.ap_url)?; mention.set_href(user.ap_url.parse::<IriString>()?);
mention.link_props.set_name_string(format!("@{}", ment))?; mention.set_name(format!("@{}", ment));
Ok(mention) Ok(mention)
} }
pub fn to_activity(&self, conn: &Connection) -> Result<link::Mention> { pub fn to_activity(&self, conn: &Connection) -> Result<link::Mention> {
let user = self.get_mentioned(conn)?; let user = self.get_mentioned(conn)?;
let mut mention = link::Mention::default(); let mut mention = link::Mention::new();
mention.link_props.set_href_string(user.ap_url.clone())?; mention.set_href(user.ap_url.parse::<IriString>()?);
mention mention.set_name(format!("@{}", user.fqn));
.link_props
.set_name_string(format!("@{}", user.fqn))?;
Ok(mention) Ok(mention)
} }
@@ -77,8 +83,8 @@ impl Mention {
in_post: bool, in_post: bool,
notify: bool, notify: bool,
) -> Result<Self> { ) -> Result<Self> {
let ap_url = ment.link_props.href_string().ok()?; let ap_url = ment.href().ok_or(Error::NotFound)?.as_str();
let mentioned = User::find_by_ap_url(conn, &ap_url)?; let mentioned = User::find_by_ap_url(conn, ap_url)?;
if in_post { if in_post {
Post::get(conn, inside).and_then(|post| { Post::get(conn, inside).and_then(|post| {
@@ -141,3 +147,62 @@ impl Mention {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{inbox::tests::fill_database, tests::db, Error};
use assert_json_diff::assert_json_eq;
use diesel::Connection;
use serde_json::{json, to_value};
#[test]
fn build_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (_posts, users, _blogs) = fill_database(&conn);
let user = &users[0];
let name = &user.username;
let act = Mention::build_activity(&conn, name)?;
let expected = json!({
"href": "https://plu.me/@/admin/",
"name": "@admin",
"type": "Mention",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &users[0];
let mention = Mention::insert(
&conn,
NewMention {
mentioned_id: user.id,
post_id: Some(post.id),
comment_id: None,
},
)?;
let act = mention.to_activity(&conn)?;
let expected = json!({
"href": "https://plu.me/@/admin/",
"name": "@admin",
"type": "Mention",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}
+2 -1
View File
@@ -105,7 +105,8 @@ impl ImportedMigrations {
pub fn rerun_last_migration(&self, conn: &Connection, path: &Path) -> Result<()> { pub fn rerun_last_migration(&self, conn: &Connection, path: &Path) -> Result<()> {
let latest_migration = conn.latest_run_migration_version()?; let latest_migration = conn.latest_run_migration_version()?;
let id = latest_migration let id = latest_migration
.and_then(|m| self.0.binary_search_by_key(&m.as_str(), |m| m.name).ok())?; .and_then(|m| self.0.binary_search_by_key(&m.as_str(), |m| m.name).ok())
.ok_or(Error::NotFound)?;
let migration = &self.0[id]; let migration = &self.0[id];
conn.transaction(|| { conn.transaction(|| {
migration.revert(conn, path)?; migration.revert(conn, path)?;
+6 -6
View File
@@ -61,7 +61,7 @@ impl PasswordResetRequest {
} }
pub fn find_and_delete_by_token(conn: &Connection, token: &str) -> Result<Self> { pub fn find_and_delete_by_token(conn: &Connection, token: &str) -> Result<Self> {
let request = Self::find_by_token(&conn, &token)?; let request = Self::find_by_token(conn, token)?;
let filter = let filter =
password_reset_requests::table.filter(password_reset_requests::id.eq(request.id)); password_reset_requests::table.filter(password_reset_requests::id.eq(request.id));
@@ -89,7 +89,7 @@ mod tests {
let request = PasswordResetRequest::find_by_token(&conn, &token) let request = PasswordResetRequest::find_by_token(&conn, &token)
.expect("couldn't retrieve request"); .expect("couldn't retrieve request");
assert!(&token.len() > &32); assert!(token.len() > 32);
assert_eq!(&request.email, &admin_email); assert_eq!(&request.email, &admin_email);
Ok(()) Ok(())
@@ -103,8 +103,8 @@ mod tests {
user_tests::fill_database(&conn); user_tests::fill_database(&conn);
let admin_email = "admin@example.com"; let admin_email = "admin@example.com";
PasswordResetRequest::insert(&conn, &admin_email).expect("couldn't insert new request"); PasswordResetRequest::insert(&conn, admin_email).expect("couldn't insert new request");
PasswordResetRequest::insert(&conn, &admin_email) PasswordResetRequest::insert(&conn, admin_email)
.expect("couldn't insert second request"); .expect("couldn't insert second request");
let count = password_reset_requests::table.count().get_result(&*conn); let count = password_reset_requests::table.count().get_result(&*conn);
@@ -132,7 +132,7 @@ mod tests {
.execute(&*conn) .execute(&*conn)
.expect("could not insert request"); .expect("could not insert request");
match PasswordResetRequest::find_by_token(&conn, &token) { match PasswordResetRequest::find_by_token(&conn, token) {
Err(Error::Expired) => (), Err(Error::Expired) => (),
_ => panic!("Received unexpected result finding expired token"), _ => panic!("Received unexpected result finding expired token"),
} }
@@ -148,7 +148,7 @@ mod tests {
user_tests::fill_database(&conn); user_tests::fill_database(&conn);
let admin_email = "admin@example.com"; let admin_email = "admin@example.com";
let token = PasswordResetRequest::insert(&conn, &admin_email) let token = PasswordResetRequest::insert(&conn, admin_email)
.expect("couldn't insert new request"); .expect("couldn't insert new request");
PasswordResetRequest::find_and_delete_by_token(&conn, &token) PasswordResetRequest::find_and_delete_by_token(&conn, &token)
.expect("couldn't find and delete request"); .expect("couldn't find and delete request");
+493 -239
View File
@@ -1,32 +1,34 @@
use crate::{ use crate::{
ap_url, blogs::Blog, db_conn::DbConn, instance::Instance, medias::Media, mentions::Mention, ap_url, blogs::Blog, db_conn::DbConn, instance::Instance, medias::Media, mentions::Mention,
post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User,
Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN, Connection, Error, Fqn, PostEvent::*, Result, CONFIG, POST_CHAN,
}; };
use activitypub::{ use activitystreams::{
activity::{Create, Delete, Update}, activity::{Create, Delete, Update},
link, base::{AnyBase, Base},
object::{Article, Image, Tombstone}, iri_string::types::IriString,
CustomObject, link::{self, kind::MentionType},
object::{kind::ImageType, ApObject, Article, AsApObject, Image, ObjectExt, Tombstone},
prelude::*,
time::OffsetDateTime,
}; };
use chrono::{NaiveDateTime, TimeZone, Utc}; use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
use heck::KebabCase;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY, sign::Signer,
Hashtag, HashtagType, Id, IntoId, Licensed, LicensedArticle, ToAsString, ToAsUri,
PUBLIC_VISIBILITY,
}, },
utils::md_to_html, utils::{iri_percent_encode_seg, md_to_html},
}; };
use riker::actors::{Publish, Tell}; 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};
pub type LicensedArticle = CustomObject<Licensed, Article>; static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, Fqn>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, String>>> = 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")]
@@ -67,20 +69,15 @@ impl Post {
find_by!(posts, find_by_ap_url, ap_url as &str); find_by!(posts, find_by_ap_url, ap_url as &str);
last!(posts); last!(posts);
pub fn insert(conn: &Connection, new: NewPost) -> Result<Self> { pub fn insert(conn: &Connection, mut new: NewPost) -> Result<Self> {
if new.ap_url.is_empty() {
let blog = Blog::get(conn, new.blog_id)?;
new.ap_url = Self::ap_url(blog, &new.slug);
}
diesel::insert_into(posts::table) diesel::insert_into(posts::table)
.values(new) .values(new)
.execute(conn)?; .execute(conn)?;
let mut post = Self::last(conn)?; let post = Self::last(conn)?;
if post.ap_url.is_empty() {
post.ap_url = ap_url(&format!(
"{}/~/{}/{}/",
CONFIG.base_url,
post.get_blog(conn)?.fqn,
post.slug
));
let _: Post = post.save_changes(conn)?;
}
if post.published { if post.published {
post.publish_published(); post.publish_published();
@@ -103,7 +100,7 @@ impl Post {
} }
pub fn delete(&self, conn: &Connection) -> Result<()> { pub fn delete(&self, conn: &Connection) -> Result<()> {
for m in Mention::list_for_post(&conn, self.id)? { for m in Mention::list_for_post(conn, self.id)? {
m.delete(conn)?; m.delete(conn)?;
} }
diesel::delete(self).execute(conn)?; diesel::delete(self).execute(conn)?;
@@ -137,7 +134,7 @@ impl Post {
.filter(posts::published.eq(true)) .filter(posts::published.eq(true))
.count() .count()
.load(conn)? .load(conn)?
.get(0) .first()
.cloned() .cloned()
.ok_or(Error::NotFound) .ok_or(Error::NotFound)
} }
@@ -254,6 +251,20 @@ impl Post {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn ap_url(blog: Blog, slug: &str) -> String {
ap_url(&format!(
"{}/~/{}/{}/",
CONFIG.base_url,
&blog.fqn,
iri_percent_encode_seg(slug)
))
}
// It's better to calc slug in insert and update
pub fn slug(title: &str) -> &str {
title
}
pub fn get_authors(&self, conn: &Connection) -> Result<Vec<User>> { pub fn get_authors(&self, conn: &Connection) -> Result<Vec<User>> {
use crate::schema::post_authors; use crate::schema::post_authors;
use crate::schema::users; use crate::schema::users;
@@ -287,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
@@ -344,92 +355,92 @@ impl Post {
.collect::<Vec<serde_json::Value>>(); .collect::<Vec<serde_json::Value>>();
mentions_json.append(&mut tags_json); mentions_json.append(&mut tags_json);
let mut article = Article::default(); let mut article = ApObject::new(Article::new());
article.object_props.set_name_string(self.title.clone())?; article.set_name(self.title.clone());
article.object_props.set_id_string(self.ap_url.clone())?; article.set_id(self.ap_url.parse::<IriString>()?);
let mut authors = self let mut authors = self
.get_authors(conn)? .get_authors(conn)?
.into_iter() .into_iter()
.map(|x| Id::new(x.ap_url)) .filter_map(|x| x.ap_url.parse::<IriString>().ok())
.collect::<Vec<Id>>(); .collect::<Vec<IriString>>();
authors.push(self.get_blog(conn)?.into_id()); // add the blog URL here too authors.push(self.get_blog(conn)?.ap_url.parse::<IriString>()?); // add the blog URL here too
article article.set_many_attributed_tos(authors);
.object_props article.set_content(self.content.get().clone());
.set_attributed_to_link_vec::<Id>(authors)?; let source = AnyBase::from_arbitrary_json(serde_json::json!({
article "content": self.source,
.object_props "mediaType": "text/markdown",
.set_content_string(self.content.get().clone())?; }))?;
article.ap_object_props.set_source_object(Source { article.set_source(source);
content: self.source.clone(), article.set_published(
media_type: String::from("text/markdown"), OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into())
})?; .expect("OffsetDateTime"),
article );
.object_props article.set_summary(&*self.subtitle);
.set_published_utctime(Utc.from_utc_datetime(&self.creation_date))?; article.set_many_tags(
article mentions_json
.object_props .iter()
.set_summary_string(self.subtitle.clone())?; .filter_map(|mention_json| AnyBase::from_arbitrary_json(mention_json).ok()),
article.object_props.tag = Some(json!(mentions_json)); );
if let Some(media_id) = self.cover_id { if let Some(media_id) = self.cover_id {
let media = Media::get(conn, media_id)?; let media = Media::get(conn, media_id)?;
let mut cover = Image::default(); let mut cover = Image::new();
cover.object_props.set_url_string(media.url()?)?; cover.set_url(media.url()?);
if media.sensitive { if media.sensitive {
cover cover.set_summary(media.content_warning.unwrap_or_default());
.object_props
.set_summary_string(media.content_warning.unwrap_or_default())?;
} }
cover.object_props.set_content_string(media.alt_text)?; cover.set_content(media.alt_text);
cover cover.set_many_attributed_tos(vec![User::get(conn, media.owner_id)?
.object_props .ap_url
.set_attributed_to_link_vec(vec![User::get(conn, media.owner_id)?.into_id()])?; .parse::<IriString>()?]);
article.object_props.set_icon_object(cover)?; article.set_icon(cover.into_any_base()?);
} }
article.object_props.set_url_string(self.ap_url.clone())?; article.set_url(self.ap_url.parse::<IriString>()?);
article article.set_many_tos(
.object_props to.into_iter()
.set_to_link_vec::<Id>(to.into_iter().map(Id::new).collect())?; .filter_map(|to| to.parse::<IriString>().ok())
article .collect::<Vec<IriString>>(),
.object_props );
.set_cc_link_vec::<Id>(cc.into_iter().map(Id::new).collect())?; article.set_many_ccs(
let mut license = Licensed::default(); cc.into_iter()
license.set_license_string(self.license.clone())?; .filter_map(|cc| cc.parse::<IriString>().ok())
.collect::<Vec<IriString>>(),
);
let license = Licensed {
license: Some(self.license.clone()),
};
Ok(LicensedArticle::new(article, license)) Ok(LicensedArticle::new(article, license))
} }
pub fn create_activity(&self, conn: &Connection) -> Result<Create> { pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
let article = self.to_activity(conn)?; let article = self.to_activity(conn)?;
let mut act = Create::default(); let to = article.to().ok_or(Error::MissingApProperty)?.clone();
act.object_props let cc = article.cc().ok_or(Error::MissingApProperty)?.clone();
.set_id_string(format!("{}activity", self.ap_url))?; let mut act = Create::new(
act.object_props self.get_authors(conn)?[0].ap_url.parse::<IriString>()?,
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?; Base::retract(article)?.into_generic()?,
act.object_props );
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?; act.set_id(format!("{}/activity", self.ap_url).parse::<IriString>()?);
act.create_props act.set_many_tos(to);
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?; act.set_many_ccs(cc);
act.create_props.set_object_object(article)?;
Ok(act) Ok(act)
} }
pub fn update_activity(&self, conn: &Connection) -> Result<Update> { pub fn update_activity(&self, conn: &Connection) -> Result<Update> {
let article = self.to_activity(conn)?; let article = self.to_activity(conn)?;
let mut act = Update::default(); let to = article.to().ok_or(Error::MissingApProperty)?.clone();
act.object_props.set_id_string(format!( let cc = article.cc().ok_or(Error::MissingApProperty)?.clone();
"{}/update-{}", let mut act = Update::new(
self.ap_url, self.get_authors(conn)?[0].ap_url.parse::<IriString>()?,
Utc::now().timestamp() Base::retract(article)?.into_generic()?,
))?; );
act.object_props act.set_id(
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?; format!("{}/update-{}", self.ap_url, Utc::now().timestamp()).parse::<IriString>()?,
act.object_props );
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?; act.set_many_tos(to);
act.update_props act.set_many_ccs(cc);
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?;
act.update_props.set_object_object(article)?;
Ok(act) Ok(act)
} }
@@ -438,10 +449,8 @@ impl Post {
.into_iter() .into_iter()
.map(|m| { .map(|m| {
( (
m.link_props m.href()
.href_string() .and_then(|ap_url| User::find_by_ap_url(conn, ap_url.as_ref()).ok())
.ok()
.and_then(|ap_url| User::find_by_ap_url(conn, &ap_url).ok())
.map(|u| u.id), .map(|u| u.id),
m, m,
) )
@@ -449,14 +458,14 @@ impl Post {
.filter_map(|(id, m)| id.map(|id| (m, id))) .filter_map(|(id, m)| id.map(|id| (m, id)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let old_mentions = Mention::list_for_post(&conn, self.id)?; let old_mentions = Mention::list_for_post(conn, self.id)?;
let old_user_mentioned = old_mentions let old_user_mentioned = old_mentions
.iter() .iter()
.map(|m| m.mentioned_id) .map(|m| m.mentioned_id)
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
for (m, id) in &mentions { for (m, id) in &mentions {
if !old_user_mentioned.contains(&id) { if !old_user_mentioned.contains(id) {
Mention::from_activity(&*conn, &m, self.id, true, true)?; Mention::from_activity(conn, m, self.id, true, true)?;
} }
} }
@@ -468,7 +477,7 @@ impl Post {
.iter() .iter()
.filter(|m| !new_mentions.contains(&m.mentioned_id)) .filter(|m| !new_mentions.contains(&m.mentioned_id))
{ {
m.delete(&conn)?; m.delete(conn)?;
} }
Ok(()) Ok(())
} }
@@ -476,10 +485,10 @@ impl Post {
pub fn update_tags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> { pub fn update_tags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> {
let tags_name = tags let tags_name = tags
.iter() .iter()
.filter_map(|t| t.name_string().ok()) .filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let old_tags = Tag::for_post(&*conn, self.id)?; let old_tags = Tag::for_post(conn, self.id)?;
let old_tags_name = old_tags let old_tags_name = old_tags
.iter() .iter()
.filter_map(|tag| { .filter_map(|tag| {
@@ -493,8 +502,9 @@ impl Post {
for t in tags { for t in tags {
if !t if !t
.name_string() .name
.map(|n| old_tags_name.contains(&n)) .as_ref()
.map(|n| old_tags_name.contains(n.as_str()))
.unwrap_or(true) .unwrap_or(true)
{ {
Tag::from_activity(conn, &t, self.id, false)?; Tag::from_activity(conn, &t, self.id, false)?;
@@ -512,10 +522,10 @@ impl Post {
pub fn update_hashtags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> { pub fn update_hashtags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> {
let tags_name = tags let tags_name = tags
.iter() .iter()
.filter_map(|t| t.name_string().ok()) .filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let old_tags = Tag::for_post(&*conn, self.id)?; let old_tags = Tag::for_post(conn, self.id)?;
let old_tags_name = old_tags let old_tags_name = old_tags
.iter() .iter()
.filter_map(|tag| { .filter_map(|tag| {
@@ -529,8 +539,9 @@ impl Post {
for t in tags { for t in tags {
if !t if !t
.name_string() .name
.map(|n| old_tags_name.contains(&n)) .as_ref()
.map(|n| old_tags_name.contains(n.as_str()))
.unwrap_or(true) .unwrap_or(true)
{ {
Tag::from_activity(conn, &t, self.id, true)?; Tag::from_activity(conn, &t, self.id, true)?;
@@ -557,18 +568,19 @@ impl Post {
} }
pub fn build_delete(&self, conn: &Connection) -> Result<Delete> { pub fn build_delete(&self, conn: &Connection) -> Result<Delete> {
let mut act = Delete::default(); let mut tombstone = Tombstone::new();
act.delete_props tombstone.set_id(self.ap_url.parse()?);
.set_actor_link(self.get_authors(conn)?[0].clone().into_id())?;
let mut tombstone = Tombstone::default(); let mut act = Delete::new(
tombstone.object_props.set_id_string(self.ap_url.clone())?; self.get_authors(conn)?[0]
act.delete_props.set_object_object(tombstone)?; .clone()
.into_id()
.parse::<IriString>()?,
Base::retract(tombstone)?.into_generic()?,
);
act.object_props act.set_id(format!("{}#delete", self.ap_url).parse()?);
.set_id_string(format!("{}#delete", self.ap_url))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY)])?;
Ok(act) Ok(act)
} }
@@ -612,49 +624,84 @@ impl FromId<DbConn> for Post {
} }
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> { fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> {
let conn = conn; let license = article.ext_one.license.unwrap_or_default();
let license = article.custom_props.license_string().unwrap_or_default(); let article = article.inner;
let article = article.object;
let (blog, authors) = article let (blog, authors) = article
.object_props .ap_object_ref()
.attributed_to_link_vec::<Id>()? .attributed_to()
.into_iter() .ok_or(Error::MissingApProperty)?
.iter()
.fold((None, vec![]), |(blog, mut authors), link| { .fold((None, vec![]), |(blog, mut authors), link| {
let url = link; if let Some(url) = link.id() {
match User::from_id(conn, &url, None, CONFIG.proxy()) { match User::from_id(conn, url.as_str(), None, CONFIG.proxy()) {
Ok(u) => { Ok(u) => {
authors.push(u); authors.push(u);
(blog, authors) (blog, authors)
}
Err(_) => (
blog.or_else(|| {
Blog::from_id(conn, url.as_str(), None, CONFIG.proxy()).ok()
}),
authors,
),
} }
Err(_) => ( } else {
blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()), // logically, url possible to be an object without id proprty like {"type":"Person", "name":"Sally"} but we ignore the case
authors, (blog, authors)
),
} }
}); });
let cover = article let cover = article.icon().and_then(|icon| {
.object_props icon.iter().next().and_then(|img| {
.icon_object::<Image>() let image = img.to_owned().extend::<Image, ImageType>().ok()??;
.ok() Media::from_activity(conn, &image).ok().map(|m| m.id)
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)); })
});
let title = article.object_props.name_string()?; let title = article
.name()
.and_then(|name| name.to_as_string())
.ok_or(Error::MissingApProperty)?;
let id = AnyBase::from_extended(article.clone()) // FIXME: Don't clone
.ok()
.ok_or(Error::MissingApProperty)?
.id()
.map(|id| id.to_string());
let ap_url = article let ap_url = article
.object_props .url()
.url_string() .and_then(|url| url.to_as_uri().or(id))
.or_else(|_| article.object_props.id_string())?; .ok_or(Error::MissingApProperty)?;
let source = article
.source()
.and_then(|s| {
serde_json::to_value(s).ok().and_then(|obj| {
if !obj.is_object() {
return None;
}
obj.get("content")
.and_then(|content| content.as_str().map(|c| c.to_string()))
})
})
.unwrap_or_default();
let post = Post::from_db(conn, &ap_url) let post = Post::from_db(conn, &ap_url)
.and_then(|mut post| { .and_then(|mut post| {
let mut updated = false; let mut updated = false;
let slug = title.to_kebab_case(); let slug = Self::slug(&title);
let content = SafeString::new(&article.object_props.content_string()?); let content = SafeString::new(
let subtitle = article.object_props.summary_string()?; &article
let source = article.ap_object_props.source_object::<Source>()?.content; .content()
.and_then(|content| content.to_as_string())
.ok_or(Error::MissingApProperty)?,
);
let subtitle = article
.summary()
.and_then(|summary| summary.to_as_string())
.ok_or(Error::MissingApProperty)?;
if post.slug != slug { if post.slug != slug {
post.slug = slug; post.slug = slug.to_string();
updated = true; updated = true;
} }
if post.title != title { if post.title != title {
@@ -674,7 +721,7 @@ impl FromId<DbConn> for Post {
updated = true; updated = true;
} }
if post.source != source { if post.source != source {
post.source = source; post.source = source.clone();
updated = true; updated = true;
} }
if post.cover_id != cover { if post.cover_id != cover {
@@ -692,17 +739,34 @@ impl FromId<DbConn> for Post {
Post::insert( Post::insert(
conn, conn,
NewPost { NewPost {
blog_id: blog?.id, blog_id: blog.ok_or(Error::NotFound)?.id,
slug: title.to_kebab_case(), slug: Self::slug(&title).to_string(),
title, title,
content: SafeString::new(&article.object_props.content_string()?), content: SafeString::new(
&article
.content()
.and_then(|content| content.to_as_string())
.ok_or(Error::MissingApProperty)?,
),
published: true, published: true,
license, license,
// FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields // FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields
ap_url, ap_url,
creation_date: Some(article.object_props.published_utctime()?.naive_utc()), creation_date: article.published().map(|published| {
subtitle: article.object_props.summary_string()?, let timestamp_secs = published.unix_timestamp();
source: article.ap_object_props.source_object::<Source>()?.content, let timestamp_nanos = published.unix_timestamp_nanos()
- (timestamp_secs as i128) * 1000i128 * 1000i128 * 1000i128;
NaiveDateTime::from_timestamp_opt(
timestamp_secs,
timestamp_nanos as u32,
)
.unwrap()
}),
subtitle: article
.summary()
.and_then(|summary| summary.to_as_string())
.ok_or(Error::MissingApProperty)?,
source,
cover_id: cover, cover_id: cover,
}, },
) )
@@ -726,22 +790,22 @@ impl FromId<DbConn> for Post {
.2 .2
.into_iter() .into_iter()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag { if let Some(tags) = article.tag() {
for tag in tags { for tag in tags.iter() {
serde_json::from_value::<link::Mention>(tag.clone()) tag.clone()
.map(|m| Mention::from_activity(conn, &m, post.id, true, true)) .extend::<link::Mention, MentionType>() // FIXME: Don't clone
.map(|mention| {
mention.map(|m| Mention::from_activity(conn, &m, post.id, true, true))
})
.ok(); .ok();
serde_json::from_value::<Hashtag>(tag.clone()) tag.clone()
.map_err(Error::from) .extend::<Hashtag, HashtagType>() // FIXME: Don't clone
.and_then(|t| { .map(|hashtag| {
let tag_name = t.name_string()?; hashtag.and_then(|t| {
Ok(Tag::from_activity( let tag_name = t.name.clone()?.as_str().to_string();
conn, Tag::from_activity(conn, &t, post.id, hashtags.remove(&tag_name)).ok()
&t, })
post.id,
hashtags.remove(&tag_name),
))
}) })
.ok(); .ok();
} }
@@ -751,13 +815,17 @@ impl FromId<DbConn> for Post {
Ok(post) Ok(post)
} }
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to get local instance user")
}
} }
impl AsObject<User, Create, &DbConn> for Post { impl AsObject<User, Create, &DbConn> for Post {
type Error = Error; type Error = Error;
type Output = Post; type Output = Self;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Post> { 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)
} }
@@ -767,7 +835,7 @@ impl AsObject<User, Delete, &DbConn> for Post {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { 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()
@@ -800,27 +868,58 @@ impl FromId<DbConn> for PostUpdate {
Err(Error::NotFound) Err(Error::NotFound)
} }
fn from_activity(conn: &DbConn, updated: LicensedArticle) -> Result<Self> { fn from_activity(conn: &DbConn, updated: Self::Object) -> Result<Self> {
Ok(PostUpdate { let mut post_update = PostUpdate {
ap_url: updated.object.object_props.id_string()?, ap_url: updated
title: updated.object.object_props.name_string().ok(), .ap_object_ref()
subtitle: updated.object.object_props.summary_string().ok(), .id_unchecked()
content: updated.object.object_props.content_string().ok(), .ok_or(Error::MissingApProperty)?
cover: updated .to_string(),
.object title: updated
.object_props .ap_object_ref()
.icon_object::<Image>() .name()
.ok() .and_then(|name| name.to_as_string()),
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)), subtitle: updated
source: updated .ap_object_ref()
.object .summary()
.ap_object_props .and_then(|summary| summary.to_as_string()),
.source_object::<Source>() content: updated
.ok() .ap_object_ref()
.map(|x| x.content), .content()
license: updated.custom_props.license_string().ok(), .and_then(|content| content.to_as_string()),
tags: updated.object.object_props.tag, cover: None,
}) source: updated.source().and_then(|s| {
serde_json::to_value(s).ok().and_then(|obj| {
if !obj.is_object() {
return None;
}
obj.get("content")
.and_then(|content| content.as_str().map(|c| c.to_string()))
})
}),
license: None,
tags: updated
.tag()
.and_then(|tags| serde_json::to_value(tags).ok()),
};
post_update.cover = updated.ap_object_ref().icon().and_then(|img| {
img.iter()
.next()
.and_then(|img| {
img.clone()
.extend::<Image, ImageType>()
.map(|img| img.and_then(|img| Media::from_activity(conn, &img).ok()))
.ok()
})
.and_then(|m| m.map(|m| m.id))
});
post_update.license = updated.ext_one.license;
Ok(post_update)
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
} }
} }
@@ -838,7 +937,7 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
} }
if let Some(title) = self.title { if let Some(title) = self.title {
post.slug = title.to_kebab_case(); post.slug = Post::slug(&title).to_string();
post.title = title; post.title = title;
} }
@@ -876,8 +975,12 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
serde_json::from_value::<Hashtag>(tag.clone()) serde_json::from_value::<Hashtag>(tag.clone())
.map_err(Error::from) .map_err(Error::from)
.and_then(|t| { .and_then(|t| {
let tag_name = t.name_string()?; let tag_name = t.name.as_ref().ok_or(Error::MissingApProperty)?;
if txt_hashtags.remove(&tag_name) { let tag_name_str = tag_name
.as_xsd_string()
.or_else(|| tag_name.as_rdf_lang_string().map(|rls| &*rls.value))
.ok_or(Error::MissingApProperty)?;
if txt_hashtags.remove(tag_name_str) {
hashtags.push(t); hashtags.push(t);
} else { } else {
tags.push(t); tags.push(t);
@@ -925,9 +1028,28 @@ impl From<PostEvent> for Arc<Post> {
mod tests { mod tests {
use super::*; use super::*;
use crate::inbox::{inbox, tests::fill_database, InboxResult}; use crate::inbox::{inbox, tests::fill_database, InboxResult};
use crate::mentions::{Mention, NewMention};
use crate::safe_string::SafeString; use crate::safe_string::SafeString;
use crate::tests::db; use crate::tests::{db, format_datetime};
use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Post, Mention, Vec<Post>, Vec<User>, Vec<Blog>) {
let (posts, users, blogs) = fill_database(conn);
let post = &posts[0];
let mentioned = &users[1];
let mention = Mention::insert(
conn,
NewMention {
mentioned_id: mentioned.id,
post_id: Some(post.id),
comment_id: None,
},
)
.unwrap();
(post.to_owned(), mention, posts, users, blogs)
}
// creates a post, get it's Create activity, delete the post, // creates a post, get it's Create activity, delete the post,
// "send" the Create to the inbox, and check it works // "send" the Create to the inbox, and check it works
@@ -935,9 +1057,9 @@ mod tests {
fn self_federation() { fn self_federation() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (_, users, blogs) = fill_database(&conn); let (_, users, blogs) = fill_database(conn);
let post = Post::insert( let post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "yo".into(), slug: "yo".into(),
@@ -954,19 +1076,19 @@ mod tests {
) )
.unwrap(); .unwrap();
PostAuthor::insert( PostAuthor::insert(
&conn, conn,
NewPostAuthor { NewPostAuthor {
post_id: post.id, post_id: post.id,
author_id: users[0].id, author_id: users[0].id,
}, },
) )
.unwrap(); .unwrap();
let create = post.create_activity(&conn).unwrap(); let create = post.create_activity(conn).unwrap();
post.delete(&conn).unwrap(); post.delete(conn).unwrap();
match inbox(&conn, serde_json::to_value(create).unwrap()).unwrap() { match inbox(conn, serde_json::to_value(create).unwrap()).unwrap() {
InboxResult::Post(p) => { InboxResult::Post(p) => {
assert!(p.is_author(&conn, users[0].id).unwrap()); assert!(p.is_author(conn, users[0].id).unwrap());
assert_eq!(p.source, "Hello".to_owned()); assert_eq!(p.source, "Hello".to_owned());
assert_eq!(p.blog_id, blogs[0].id); assert_eq!(p.blog_id, blogs[0].id);
assert_eq!(p.content, SafeString::new("Hello")); assert_eq!(p.content, SafeString::new("Hello"));
@@ -980,45 +1102,177 @@ mod tests {
} }
#[test] #[test]
fn licensed_article_serde() { fn to_activity() {
let mut article = Article::default(); let conn = db();
article.object_props.set_id_string("Yo".into()).unwrap(); conn.test_transaction::<_, Error, _>(|| {
let mut license = Licensed::default(); let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
license.set_license_string("WTFPL".into()).unwrap(); let act = post.to_activity(&conn)?;
let full_article = LicensedArticle::new(article, license);
let json = serde_json::to_value(full_article).unwrap(); let expected = json!({
let article_from_json: LicensedArticle = serde_json::from_value(json).unwrap(); "attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/Blog%20Name/"],
assert_eq!( "cc": [],
"Yo", "content": "Hello",
&article_from_json.object.object_props.id_string().unwrap() "id": "https://plu.me/~/Blog%20Name/testing",
); "license": "WTFPL",
assert_eq!( "name": "Testing",
"WTFPL", "published": format_datetime(&post.creation_date),
&article_from_json.custom_props.license_string().unwrap() "source": {
); "content": "Hello",
"mediaType": "text/markdown"
},
"summary": "Bye",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/Blog%20Name/testing"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
} }
#[test] #[test]
fn licensed_article_deserialization() { fn create_activity() {
let json = json!({ let conn = db();
"type": "Article", conn.test_transaction::<_, Error, _>(|| {
"id": "https://plu.me/~/Blog/my-article", let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"], let act = post.create_activity(&conn)?;
"content": "Hello.",
"name": "My Article", let expected = json!({
"summary": "Bye.", "actor": "https://plu.me/@/admin/",
"source": { "cc": [],
"content": "Hello.", "id": "https://plu.me/~/Blog%20Name/testing/activity",
"mediaType": "text/markdown" "object": {
}, "attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/Blog%20Name/"],
"published": "2014-12-12T12:12:12Z", "cc": [],
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY] "content": "Hello",
"id": "https://plu.me/~/Blog%20Name/testing",
"license": "WTFPL",
"name": "Testing",
"published": format_datetime(&post.creation_date),
"source": {
"content": "Hello",
"mediaType": "text/markdown"
},
"summary": "Bye",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/Blog%20Name/testing"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn update_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
let act = post.update_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": [],
"id": "https://plu.me/~/Blog%20Name/testing/update-",
"object": {
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/Blog%20Name/"],
"cc": [],
"content": "Hello",
"id": "https://plu.me/~/Blog%20Name/testing",
"license": "WTFPL",
"name": "Testing",
"published": format_datetime(&post.creation_date),
"source": {
"content": "Hello",
"mediaType": "text/markdown"
},
"summary": "Bye",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/Blog%20Name/testing"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Update"
});
let actual = to_value(act)?;
let id = actual["id"].to_string();
let (id_pre, id_post) = id.rsplit_once('-').unwrap();
assert_eq!(post.ap_url, "https://plu.me/~/Blog%20Name/testing");
assert_eq!(
id_pre,
to_value("\"https://plu.me/~/Blog%20Name/testing/update")
.unwrap()
.as_str()
.unwrap()
);
assert_eq!(id_post.len(), 11);
assert_eq!(
id_post.matches(char::is_numeric).collect::<String>().len(),
10
);
for (key, value) in actual.as_object().unwrap().into_iter() {
if key == "id" {
continue;
}
assert_json_eq!(value, expected.get(key).unwrap());
}
Ok(())
});
}
#[test]
fn build_delete() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
let act = post.build_delete(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"id": "https://plu.me/~/Blog%20Name/testing#delete",
"object": {
"id": "https://plu.me/~/Blog%20Name/testing",
"type": "Tombstone"
},
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Delete"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
}); });
let article: LicensedArticle = serde_json::from_value(json).unwrap();
assert_eq!(
"https://plu.me/~/Blog/my-article",
&article.object.object_props.id_string().unwrap()
);
} }
} }
+20 -6
View File
@@ -1,12 +1,16 @@
use crate::{ use crate::{
db_conn::{DbConn, DbPool}, db_conn::{DbConn, DbPool},
follows, follows,
posts::{LicensedArticle, Post}, posts::Post,
users::{User, UserEvent}, users::{User, UserEvent},
ACTOR_SYS, CONFIG, USER_CHAN, ACTOR_SYS, CONFIG, USER_CHAN,
}; };
use activitypub::activity::Create; use activitystreams::{
use plume_common::activity_pub::inbox::FromId; activity::{ActorAndObjectRef, Create},
base::AnyBase,
object::kind::ArticleType,
};
use plume_common::activity_pub::{inbox::FromId, LicensedArticle};
use riker::actors::{Actor, ActorFactoryArgs, ActorRefFactory, Context, Sender, Subscribe, Tell}; use riker::actors::{Actor, ActorFactoryArgs, ActorRefFactory, Context, Sender, Subscribe, Tell};
use std::sync::Arc; use std::sync::Arc;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
@@ -41,6 +45,12 @@ impl Actor for RemoteFetchActor {
RemoteUserFound(user) => match self.conn.get() { RemoteUserFound(user) => match self.conn.get() {
Ok(conn) => { Ok(conn) => {
let conn = DbConn(conn); let conn = DbConn(conn);
if user
.get_instance(&conn)
.map_or(false, |instance| instance.blocked)
{
return;
}
// Don't call these functions in parallel // Don't call these functions in parallel
// for the case database connections limit is too small // for the case database connections limit is too small
fetch_and_cache_articles(&user, &conn); fetch_and_cache_articles(&user, &conn);
@@ -68,13 +78,17 @@ fn fetch_and_cache_articles(user: &Arc<User>, conn: &DbConn) {
match create_acts { match create_acts {
Ok(create_acts) => { Ok(create_acts) => {
for create_act in create_acts { for create_act in create_acts {
match create_act.create_props.object_object::<LicensedArticle>() { match create_act.object_field_ref().as_single_base().map(|base| {
Ok(article) => { let any_base = AnyBase::from_base(base.clone()); // FIXME: Don't clone()
any_base.extend::<LicensedArticle, ArticleType>()
}) {
Some(Ok(Some(article))) => {
Post::from_activity(conn, article) Post::from_activity(conn, article)
.expect("Article from remote user couldn't be saved"); .expect("Article from remote user couldn't be saved");
info!("Fetched article from remote user"); info!("Fetched article from remote user");
} }
Err(e) => warn!("Error while fetching articles in background: {:?}", e), Some(Err(e)) => warn!("Error while fetching articles in background: {:?}", e),
_ => warn!("Error while fetching articles in background"),
} }
} }
} }
+112 -29
View File
@@ -1,13 +1,19 @@
use crate::{ use crate::{
db_conn::DbConn, 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::{
activity::{ActorAndObjectRef, Announce, Undo},
base::AnyBase,
iri_string::types::IriString,
prelude::*,
}; };
use activitypub::activity::{Announce, Undo};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
Id, IntoId, PUBLIC_VISIBILITY, sign::Signer,
PUBLIC_VISIBILITY,
}; };
#[derive(Clone, Queryable, Identifiable)] #[derive(Clone, Queryable, Identifiable)]
@@ -60,16 +66,16 @@ impl Reshare {
} }
pub fn to_activity(&self, conn: &Connection) -> Result<Announce> { pub fn to_activity(&self, conn: &Connection) -> Result<Announce> {
let mut act = Announce::default(); let mut act = Announce::new(
act.announce_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; Post::get(conn, self.post_id)?.ap_url.parse::<IriString>()?,
act.announce_props );
.set_object_link(Post::get(conn, self.post_id)?.into_id())?; act.set_id(self.ap_url.parse::<IriString>()?);
act.object_props.set_id_string(self.ap_url.clone())?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![self
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .get_user(conn)?
act.object_props .followers_endpoint
.set_cc_link_vec(vec![Id::new(self.get_user(conn)?.followers_endpoint)])?; .parse::<IriString>()?]);
Ok(act) Ok(act)
} }
@@ -92,16 +98,16 @@ impl Reshare {
} }
pub fn build_undo(&self, conn: &Connection) -> Result<Undo> { pub fn build_undo(&self, conn: &Connection) -> Result<Undo> {
let mut act = Undo::default(); let mut act = Undo::new(
act.undo_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; AnyBase::from_extended(self.to_activity(conn)?)?,
act.undo_props.set_object_object(self.to_activity(conn)?)?; );
act.object_props act.set_id(format!("{}#delete", self.ap_url).parse::<IriString>()?);
.set_id_string(format!("{}#delete", self.ap_url))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![self
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .get_user(conn)?
act.object_props .followers_endpoint
.set_cc_link_vec(vec![Id::new(self.get_user(conn)?.followers_endpoint)])?; .parse::<IriString>()?]);
Ok(act) Ok(act)
} }
@@ -142,7 +148,10 @@ impl FromId<DbConn> for Reshare {
NewReshare { NewReshare {
post_id: Post::from_id( post_id: Post::from_id(
conn, conn,
&act.announce_props.object_link::<Id>()?, act.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -150,18 +159,28 @@ impl FromId<DbConn> for Reshare {
.id, .id,
user_id: User::from_id( user_id: User::from_id(
conn, conn,
&act.announce_props.actor_link::<Id>()?, act.actor_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
.map_err(|(_, e)| e)? .map_err(|(_, e)| e)?
.id, .id,
ap_url: act.object_props.id_string()?, ap_url: act
.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_string(),
}, },
)?; )?;
res.notify(conn)?; res.notify(conn)?;
Ok(res) Ok(res)
} }
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
} }
impl AsObject<User, Undo, &DbConn> for Reshare { impl AsObject<User, Undo, &DbConn> for Reshare {
@@ -173,7 +192,7 @@ impl AsObject<User, Undo, &DbConn> for Reshare {
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)?;
} }
@@ -186,7 +205,7 @@ impl AsObject<User, Undo, &DbConn> for Reshare {
impl NewReshare { impl NewReshare {
pub fn new(p: &Post, u: &User) -> Self { pub fn new(p: &Post, u: &User) -> Self {
let ap_url = format!("{}/reshare/{}", u.ap_url, p.ap_url); let ap_url = format!("{}reshare/{}", u.ap_url, p.ap_url);
NewReshare { NewReshare {
post_id: p.id, post_id: p.id,
user_id: u.id, user_id: u.id,
@@ -194,3 +213,67 @@ impl NewReshare {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
use crate::diesel::Connection;
use crate::{inbox::tests::fill_database, tests::db};
use assert_json_diff::assert_json_eq;
use serde_json::{json, to_value};
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&conn, NewReshare::new(post, user))?;
let act = reshare.to_activity(&conn).unwrap();
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/Blog%20Name/testing",
"object": "https://plu.me/~/Blog%20Name/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Announce",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&conn, NewReshare::new(post, user))?;
let act = reshare.build_undo(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/Blog%20Name/testing#delete",
"object": {
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/Blog%20Name/testing",
"object": "https://plu.me/~/Blog%20Name/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Announce"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Undo",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}
+2 -2
View File
@@ -93,7 +93,7 @@ fn url_add_prefix(url: &str) -> Option<Cow<'_, str>> {
} }
} }
#[derive(Debug, Clone, PartialEq, AsExpression, FromSqlRow, Default)] #[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow, Default)]
#[sql_type = "Text"] #[sql_type = "Text"]
pub struct SafeString { pub struct SafeString {
value: String, value: String,
@@ -102,7 +102,7 @@ pub struct SafeString {
impl SafeString { impl SafeString {
pub fn new(value: &str) -> Self { pub fn new(value: &str) -> Self {
SafeString { SafeString {
value: CLEAN.clean(&value).to_string(), value: CLEAN.clean(value).to_string(),
} }
} }
+22 -10
View File
@@ -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,
@@ -67,22 +75,24 @@ table! {
} }
table! { table! {
comment_seers (id) { email_blocklist (id) {
id -> Int4, id -> Int4,
comment_id -> Int4, email_address -> Text,
user_id -> Int4,
}
}
table! {
email_blocklist(id){
id -> Int4,
email_address -> VarChar,
note -> Text, note -> Text,
notify_user -> Bool, notify_user -> Bool,
notification_text -> Text, notification_text -> Text,
} }
} }
table! {
email_signups (id) {
id -> Int4,
email -> Varchar,
token -> Varchar,
expiration_date -> Timestamp,
}
}
table! { table! {
follows (id) { follows (id) {
id -> Int4, id -> Int4,
@@ -304,8 +314,10 @@ allow_tables_to_appear_in_same_query!(
apps, apps,
blog_authors, blog_authors,
blogs, blogs,
comments,
comment_seers, comment_seers,
comments,
email_blocklist,
email_signups,
follows, follows,
instances, instances,
likes, likes,
+28 -16
View File
@@ -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},
@@ -108,7 +109,7 @@ mod tests {
let searcher = Arc::new(get_searcher(&CONFIG.search_tokenizers)); let searcher = Arc::new(get_searcher(&CONFIG.search_tokenizers));
SearchActor::init(searcher.clone(), db_pool.clone()); SearchActor::init(searcher.clone(), db_pool.clone());
let conn = db_pool.clone().get().unwrap(); let conn = db_pool.get().unwrap();
let title = random_hex()[..8].to_owned(); let title = random_hex()[..8].to_owned();
let (_instance, _user, blog) = fill_database(&conn); let (_instance, _user, blog) = fill_database(&conn);
@@ -161,41 +162,52 @@ mod tests {
long_description_html: "<p>Good morning</p>".to_string(), long_description_html: "<p>Good morning</p>".to_string(),
short_description: SafeString::new("Hello"), short_description: SafeString::new("Hello"),
short_description_html: "<p>Hello</p>".to_string(), short_description_html: "<p>Hello</p>".to_string(),
name: random_hex().to_string(), name: random_hex(),
open_registrations: true, open_registrations: true,
public_domain: random_hex().to_string(), public_domain: random_hex(),
}, },
) )
.unwrap(); .unwrap();
let user = User::insert( let user = User::insert(
conn, conn,
NewUser { NewUser {
username: random_hex().to_string(), username: random_hex(),
display_name: random_hex().to_string(), display_name: random_hex(),
outbox_url: random_hex().to_string(), outbox_url: random_hex(),
inbox_url: random_hex().to_string(), inbox_url: random_hex(),
summary: "".to_string(), summary: "".to_string(),
email: None, email: None,
hashed_password: None, hashed_password: None,
instance_id: instance.id, instance_id: instance.id,
ap_url: random_hex().to_string(), ap_url: random_hex(),
private_key: None, private_key: None,
public_key: "".to_string(), public_key: "".to_string(),
shared_inbox_url: None, shared_inbox_url: None,
followers_endpoint: random_hex().to_string(), followers_endpoint: random_hex(),
avatar_id: None, avatar_id: None,
summary_html: SafeString::new(""), summary_html: SafeString::new(""),
role: 0, role: 0,
fqn: random_hex().to_string(), fqn: random_hex(),
}, },
) )
.unwrap(); .unwrap();
let mut blog = NewBlog::default(); let title = random_hex();
blog.instance_id = instance.id; let blog = NewBlog {
blog.actor_id = random_hex().to_string(); instance_id: instance.id,
blog.ap_url = random_hex().to_string(); actor_id: random_hex(),
blog.inbox_url = random_hex().to_string(); ap_url: random_hex(),
blog.outbox_url = random_hex().to_string(); inbox_url: random_hex(),
outbox_url: random_hex(),
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(
conn, conn,
+1 -1
View File
@@ -154,7 +154,7 @@ pub(crate) mod tests {
}, },
) )
.unwrap(); .unwrap();
searcher.add_document(&conn, &post).unwrap(); searcher.add_document(conn, &post).unwrap();
searcher.commit(); searcher.commit();
assert_eq!( assert_eq!(
searcher.search_document(conn, Query::from_str(&title).unwrap(), (0, 1))[0].id, searcher.search_document(conn, Query::from_str(&title).unwrap(), (0, 1))[0].id,
+19 -11
View File
@@ -94,7 +94,7 @@ macro_rules! gen_to_string {
)* )*
$( $(
for val in &$self.$date { for val in &$self.$date {
$result.push_str(&format!("{}:{} ", stringify!($date), NaiveDate::from_num_days_from_ce(*val as i32).format("%Y-%m-%d"))); $result.push_str(&format!("{}:{} ", stringify!($date), NaiveDate::from_num_days_from_ce_opt(*val as i32).unwrap().format("%Y-%m-%d")));
} }
)* )*
} }
@@ -148,7 +148,7 @@ impl PlumeQuery {
/// Parse a query string into this Query /// Parse a query string into this Query
pub fn parse_query(&mut self, query: &str) -> &mut Self { pub fn parse_query(&mut self, query: &str) -> &mut Self {
self.from_str_req(&query.trim()) self.from_str_req(query.trim())
} }
/// Convert this Query to a Tantivy Query /// Convert this Query to a Tantivy Query
@@ -180,12 +180,16 @@ impl PlumeQuery {
if self.before.is_some() || self.after.is_some() { if self.before.is_some() || self.after.is_some() {
// if at least one range bound is provided // if at least one range bound is provided
let after = self let after = self.after.unwrap_or_else(|| {
.after i64::from(
.unwrap_or_else(|| i64::from(NaiveDate::from_ymd(2000, 1, 1).num_days_from_ce())); NaiveDate::from_ymd_opt(2000, 1, 1)
.unwrap()
.num_days_from_ce(),
)
});
let before = self let before = self
.before .before
.unwrap_or_else(|| i64::from(Utc::today().num_days_from_ce())); .unwrap_or_else(|| i64::from(Utc::now().date_naive().num_days_from_ce()));
let field = Searcher::schema().get_field("creation_date").unwrap(); let field = Searcher::schema().get_field("creation_date").unwrap();
let range = let range =
RangeQuery::new_i64_bounds(field, Bound::Included(after), Bound::Included(before)); RangeQuery::new_i64_bounds(field, Bound::Included(after), Bound::Included(before));
@@ -202,16 +206,20 @@ impl PlumeQuery {
pub fn before<D: Datelike>(&mut self, date: &D) -> &mut Self { pub fn before<D: Datelike>(&mut self, date: &D) -> &mut Self {
let before = self let before = self
.before .before
.unwrap_or_else(|| i64::from(Utc::today().num_days_from_ce())); .unwrap_or_else(|| i64::from(Utc::now().date_naive().num_days_from_ce()));
self.before = Some(cmp::min(before, i64::from(date.num_days_from_ce()))); self.before = Some(cmp::min(before, i64::from(date.num_days_from_ce())));
self self
} }
// documents older than the provided date will be ignored // documents older than the provided date will be ignored
pub fn after<D: Datelike>(&mut self, date: &D) -> &mut Self { pub fn after<D: Datelike>(&mut self, date: &D) -> &mut Self {
let after = self let after = self.after.unwrap_or_else(|| {
.after i64::from(
.unwrap_or_else(|| i64::from(NaiveDate::from_ymd(2000, 1, 1).num_days_from_ce())); NaiveDate::from_ymd_opt(2000, 1, 1)
.unwrap()
.num_days_from_ce(),
)
});
self.after = Some(cmp::max(after, i64::from(date.num_days_from_ce()))); self.after = Some(cmp::max(after, i64::from(date.num_days_from_ce())));
self self
} }
@@ -360,7 +368,7 @@ impl std::str::FromStr for PlumeQuery {
fn from_str(query: &str) -> Result<PlumeQuery, !> { fn from_str(query: &str) -> Result<PlumeQuery, !> {
let mut res: PlumeQuery = Default::default(); let mut res: PlumeQuery = Default::default();
res.from_str_req(&query.trim()); res.from_str_req(query.trim());
Ok(res) Ok(res)
} }
} }
+1 -1
View File
@@ -57,7 +57,7 @@ impl<'a> WhitespaceTokenStream<'a> {
.filter(|&(_, ref c)| c.is_whitespace()) .filter(|&(_, ref c)| c.is_whitespace())
.map(|(offset, _)| offset) .map(|(offset, _)| offset)
.next() .next()
.unwrap_or_else(|| self.text.len()) .unwrap_or(self.text.len())
} }
} }
+72
View File
@@ -0,0 +1,72 @@
use crate::CONFIG;
use rocket::request::{FromRequest, Outcome, Request};
use std::fmt;
use std::str::FromStr;
pub enum Strategy {
Password,
Email,
}
impl Default for Strategy {
fn default() -> Self {
Self::Password
}
}
impl FromStr for Strategy {
type Err = StrategyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::Strategy::*;
match s {
"password" => Ok(Password),
"email" => Ok(Email),
s => Err(StrategyError::Unsupported(s.to_string())),
}
}
}
#[derive(Debug)]
pub enum StrategyError {
Unsupported(String),
}
impl fmt::Display for StrategyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::StrategyError::*;
match self {
// FIXME: Calc option strings from enum
Unsupported(s) => write!(f, "Unsupported strategy: {}. Choose password or email", s),
}
}
}
impl std::error::Error for StrategyError {}
pub struct Password();
pub struct Email();
impl<'a, 'r> FromRequest<'a, 'r> for Password {
type Error = ();
fn from_request(_request: &'a Request<'r>) -> Outcome<Self, ()> {
match matches!(CONFIG.signup, Strategy::Password) {
true => Outcome::Success(Self()),
false => Outcome::Forward(()),
}
}
}
impl<'a, 'r> FromRequest<'a, 'r> for Email {
type Error = ();
fn from_request(_request: &'a Request<'r>) -> Outcome<Self, ()> {
match matches!(CONFIG.signup, Strategy::Email) {
true => Outcome::Success(Self()),
false => Outcome::Forward(()),
}
}
}
+92 -16
View File
@@ -1,6 +1,7 @@
use crate::{ap_url, instance::Instance, schema::tags, Connection, Error, Result}; use crate::{ap_url, instance::Instance, schema::tags, Connection, Error, Result};
use activitystreams::iri_string::types::IriString;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::Hashtag; use plume_common::activity_pub::{Hashtag, HashtagExt};
#[derive(Clone, Identifiable, Queryable)] #[derive(Clone, Identifiable, Queryable)]
pub struct Tag { pub struct Tag {
@@ -25,13 +26,16 @@ impl Tag {
list_by!(tags, for_post, post_id as i32); list_by!(tags, for_post, post_id as i32);
pub fn to_activity(&self) -> Result<Hashtag> { pub fn to_activity(&self) -> Result<Hashtag> {
let mut ht = Hashtag::default(); let mut ht = Hashtag::new();
ht.set_href_string(ap_url(&format!( ht.set_href(
"{}/tag/{}", ap_url(&format!(
Instance::get_local()?.public_domain, "{}/tag/{}",
self.tag Instance::get_local()?.public_domain,
)))?; self.tag
ht.set_name_string(self.tag.clone())?; ))
.parse::<IriString>()?,
);
ht.set_name(self.tag.clone());
Ok(ht) Ok(ht)
} }
@@ -44,7 +48,7 @@ impl Tag {
Tag::insert( Tag::insert(
conn, conn,
NewTag { NewTag {
tag: tag.name_string()?, tag: tag.name().ok_or(Error::MissingApProperty)?.as_str().into(),
is_hashtag, is_hashtag,
post_id: post, post_id: post,
}, },
@@ -52,13 +56,16 @@ impl Tag {
} }
pub fn build_activity(tag: String) -> Result<Hashtag> { pub fn build_activity(tag: String) -> Result<Hashtag> {
let mut ht = Hashtag::default(); let mut ht = Hashtag::new();
ht.set_href_string(ap_url(&format!( ht.set_href(
"{}/tag/{}", ap_url(&format!(
Instance::get_local()?.public_domain, "{}/tag/{}",
tag Instance::get_local()?.public_domain,
)))?; tag
ht.set_name_string(tag)?; ))
.parse::<IriString>()?,
);
ht.set_name(tag);
Ok(ht) Ok(ht)
} }
@@ -69,3 +76,72 @@ impl Tag {
.map_err(Error::from) .map_err(Error::from)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::db;
use crate::{diesel::Connection, inbox::tests::fill_database};
use assert_json_diff::assert_json_eq;
use serde_json::to_value;
#[test]
fn from_activity() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(conn);
let post_id = posts[0].id;
let mut ht = Hashtag::new();
ht.set_href(ap_url("https://plu.me/tag/a_tag").parse::<IriString>()?);
ht.set_name("a_tag".to_string());
let tag = Tag::from_activity(conn, &ht, post_id, true)?;
assert_eq!(&tag.tag, "a_tag");
assert!(tag.is_hashtag);
Ok(())
});
}
#[test]
fn to_activity() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
fill_database(conn);
let tag = Tag {
id: 0,
tag: "a_tag".into(),
is_hashtag: false,
post_id: 0,
};
let act = tag.to_activity()?;
let expected = json!({
"href": "https://plu.me/tag/a_tag",
"name": "a_tag",
"type": "Hashtag"
});
assert_json_eq!(to_value(&act)?, expected);
Ok(())
})
}
#[test]
fn build_activity() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
fill_database(conn);
let act = Tag::build_activity("a_tag".into())?;
let expected = json!({
"href": "https://plu.me/tag/a_tag",
"name": "a_tag",
"type": "Hashtag"
});
assert_json_eq!(to_value(&act)?, expected);
Ok(())
});
}
}
+132 -118
View File
@@ -6,6 +6,7 @@ use crate::{
Connection, Error, Result, Connection, Error, Result,
}; };
use diesel::{self, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl};
use std::cmp::Ordering;
use std::ops::Deref; use std::ops::Deref;
pub(crate) mod query; pub(crate) mod query;
@@ -13,7 +14,7 @@ pub(crate) mod query;
pub use self::query::Kind; pub use self::query::Kind;
use self::query::{QueryError, TimelineQuery}; use self::query::{QueryError, TimelineQuery};
#[derive(Clone, Debug, PartialEq, Queryable, Identifiable, AsChangeset)] #[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset)]
#[table_name = "timeline_definition"] #[table_name = "timeline_definition"]
pub struct Timeline { pub struct Timeline {
pub id: i32, pub id: i32,
@@ -92,6 +93,16 @@ impl Timeline {
.load::<Self>(conn) .load::<Self>(conn)
.map_err(Error::from) .map_err(Error::from)
} }
.map(|mut timelines| {
timelines.sort_by(|t1, t2| {
if t1.user_id.is_some() && t2.user_id.is_none() {
Ordering::Less
} else {
t1.id.cmp(&t2.id)
}
});
timelines
})
} }
pub fn new_for_user( pub fn new_for_user(
@@ -223,6 +234,9 @@ impl Timeline {
} }
pub fn add_post(&self, conn: &Connection, post: &Post) -> Result<()> { pub fn add_post(&self, conn: &Connection, post: &Post) -> Result<()> {
if self.includes_post(conn, post)? {
return Ok(());
}
diesel::insert_into(timeline::table) diesel::insert_into(timeline::table)
.values(TimelineEntry { .values(TimelineEntry {
post_id: post.id, post_id: post.id,
@@ -236,6 +250,16 @@ impl Timeline {
let query = TimelineQuery::parse(&self.query)?; let query = TimelineQuery::parse(&self.query)?;
query.matches(conn, self, post, kind) query.matches(conn, self, post, kind)
} }
fn includes_post(&self, conn: &Connection, post: &Post) -> Result<bool> {
diesel::dsl::select(diesel::dsl::exists(
timeline::table
.filter(timeline::timeline_id.eq(self.id))
.filter(timeline::post_id.eq(post.id)),
))
.get_result(conn)
.map_err(Error::from)
}
} }
#[cfg(test)] #[cfg(test)]
@@ -258,73 +282,63 @@ mod tests {
fn test_timeline() { fn test_timeline() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let users = userTests::fill_database(&conn); let users = userTests::fill_database(conn);
let mut tl1_u1 = Timeline::new_for_user( let mut tl1_u1 = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"my timeline".to_owned(), "my timeline".to_owned(),
"all".to_owned(), "all".to_owned(),
) )
.unwrap(); .unwrap();
List::new( List::new(conn, "languages I speak", Some(&users[1]), ListType::Prefix).unwrap();
&conn,
"languages I speak",
Some(&users[1]),
ListType::Prefix,
)
.unwrap();
let tl2_u1 = Timeline::new_for_user( let tl2_u1 = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"another timeline".to_owned(), "another timeline".to_owned(),
"followed".to_owned(), "followed".to_owned(),
) )
.unwrap(); .unwrap();
let tl1_u2 = Timeline::new_for_user( let tl1_u2 = Timeline::new_for_user(
&conn, conn,
users[1].id, users[1].id,
"english posts".to_owned(), "english posts".to_owned(),
"lang in \"languages I speak\"".to_owned(), "lang in \"languages I speak\"".to_owned(),
) )
.unwrap(); .unwrap();
let tl1_instance = Timeline::new_for_instance( let tl1_instance = Timeline::new_for_instance(
&conn, conn,
"english posts".to_owned(), "english posts".to_owned(),
"license in [cc]".to_owned(), "license in [cc]".to_owned(),
) )
.unwrap(); .unwrap();
assert_eq!(tl1_u1, Timeline::get(&conn, tl1_u1.id).unwrap()); assert_eq!(tl1_u1, Timeline::get(conn, tl1_u1.id).unwrap());
assert_eq!( assert_eq!(
tl2_u1, tl2_u1,
Timeline::find_for_user_by_name(&conn, Some(users[0].id), "another timeline") Timeline::find_for_user_by_name(conn, Some(users[0].id), "another timeline")
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
tl1_instance, tl1_instance,
Timeline::find_for_user_by_name(&conn, None, "english posts").unwrap() Timeline::find_for_user_by_name(conn, None, "english posts").unwrap()
); );
let tl_u1 = Timeline::list_for_user(&conn, Some(users[0].id)).unwrap(); let tl_u1 = Timeline::list_for_user(conn, Some(users[0].id)).unwrap();
assert_eq!(3, tl_u1.len()); // it is not 2 because there is a "Your feed" tl created for each user automatically assert_eq!(3, tl_u1.len()); // it is not 2 because there is a "Your feed" tl created for each user automatically
assert!(tl_u1.iter().fold(false, |res, tl| { res || *tl == tl1_u1 })); assert!(tl_u1.iter().any(|tl| *tl == tl1_u1));
assert!(tl_u1.iter().fold(false, |res, tl| { res || *tl == tl2_u1 })); assert!(tl_u1.iter().any(|tl| *tl == tl2_u1));
let tl_instance = Timeline::list_for_user(&conn, None).unwrap(); let tl_instance = Timeline::list_for_user(conn, None).unwrap();
assert_eq!(3, tl_instance.len()); // there are also the local and federated feed by default assert_eq!(3, tl_instance.len()); // there are also the local and federated feed by default
assert!(tl_instance assert!(tl_instance.iter().any(|tl| *tl == tl1_instance));
.iter()
.fold(false, |res, tl| { res || *tl == tl1_instance }));
tl1_u1.name = "My Super TL".to_owned(); tl1_u1.name = "My Super TL".to_owned();
let new_tl1_u2 = tl1_u2.update(&conn).unwrap(); let new_tl1_u2 = tl1_u2.update(conn).unwrap();
let tl_u2 = Timeline::list_for_user(&conn, Some(users[1].id)).unwrap(); let tl_u2 = Timeline::list_for_user(conn, Some(users[1].id)).unwrap();
assert_eq!(2, tl_u2.len()); // same here assert_eq!(2, tl_u2.len()); // same here
assert!(tl_u2 assert!(tl_u2.iter().any(|tl| *tl == new_tl1_u2));
.iter()
.fold(false, |res, tl| { res || *tl == new_tl1_u2 }));
Ok(()) Ok(())
}); });
@@ -334,48 +348,48 @@ mod tests {
fn test_timeline_creation_error() { fn test_timeline_creation_error() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let users = userTests::fill_database(&conn); let users = userTests::fill_database(conn);
assert!(Timeline::new_for_user( assert!(Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"my timeline".to_owned(), "my timeline".to_owned(),
"invalid keyword".to_owned(), "invalid keyword".to_owned(),
) )
.is_err()); .is_err());
assert!(Timeline::new_for_instance( assert!(Timeline::new_for_instance(
&conn, conn,
"my timeline".to_owned(), "my timeline".to_owned(),
"invalid keyword".to_owned(), "invalid keyword".to_owned(),
) )
.is_err()); .is_err());
assert!(Timeline::new_for_user( assert!(Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"my timeline".to_owned(), "my timeline".to_owned(),
"author in non_existant_list".to_owned(), "author in non_existant_list".to_owned(),
) )
.is_err()); .is_err());
assert!(Timeline::new_for_instance( assert!(Timeline::new_for_instance(
&conn, conn,
"my timeline".to_owned(), "my timeline".to_owned(),
"lang in dont-exist".to_owned(), "lang in dont-exist".to_owned(),
) )
.is_err()); .is_err());
List::new(&conn, "friends", Some(&users[0]), ListType::User).unwrap(); List::new(conn, "friends", Some(&users[0]), ListType::User).unwrap();
List::new(&conn, "idk", None, ListType::Blog).unwrap(); List::new(conn, "idk", None, ListType::Blog).unwrap();
assert!(Timeline::new_for_user( assert!(Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"my timeline".to_owned(), "my timeline".to_owned(),
"blog in friends".to_owned(), "blog in friends".to_owned(),
) )
.is_err()); .is_err());
assert!(Timeline::new_for_instance( assert!(Timeline::new_for_instance(
&conn, conn,
"my timeline".to_owned(), "my timeline".to_owned(),
"not author in idk".to_owned(), "not author in idk".to_owned(),
) )
@@ -389,10 +403,10 @@ mod tests {
fn test_simple_match() { fn test_simple_match() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (users, blogs) = blogTests::fill_database(&conn); let (users, blogs) = blogTests::fill_database(conn);
let gnu_tl = Timeline::new_for_user( let gnu_tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"GNU timeline".to_owned(), "GNU timeline".to_owned(),
"license in [AGPL, LGPL, GPL]".to_owned(), "license in [AGPL, LGPL, GPL]".to_owned(),
@@ -400,7 +414,7 @@ mod tests {
.unwrap(); .unwrap();
let gnu_post = Post::insert( let gnu_post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "slug".to_string(), slug: "slug".to_string(),
@@ -416,10 +430,10 @@ mod tests {
}, },
) )
.unwrap(); .unwrap();
assert!(gnu_tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(gnu_tl.matches(conn, &gnu_post, Kind::Original).unwrap());
let non_free_post = Post::insert( let non_free_post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "slug2".to_string(), slug: "slug2".to_string(),
@@ -436,7 +450,7 @@ mod tests {
) )
.unwrap(); .unwrap();
assert!(!gnu_tl assert!(!gnu_tl
.matches(&conn, &non_free_post, Kind::Original) .matches(conn, &non_free_post, Kind::Original)
.unwrap()); .unwrap());
Ok(()) Ok(())
@@ -447,9 +461,9 @@ mod tests {
fn test_complex_match() { fn test_complex_match() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (users, blogs) = blogTests::fill_database(&conn); let (users, blogs) = blogTests::fill_database(conn);
Follow::insert( Follow::insert(
&conn, conn,
NewFollow { NewFollow {
follower_id: users[0].id, follower_id: users[0].id,
following_id: users[1].id, following_id: users[1].id,
@@ -459,11 +473,11 @@ mod tests {
.unwrap(); .unwrap();
let fav_blogs_list = let fav_blogs_list =
List::new(&conn, "fav_blogs", Some(&users[0]), ListType::Blog).unwrap(); List::new(conn, "fav_blogs", Some(&users[0]), ListType::Blog).unwrap();
fav_blogs_list.add_blogs(&conn, &[blogs[0].id]).unwrap(); fav_blogs_list.add_blogs(conn, &[blogs[0].id]).unwrap();
let my_tl = Timeline::new_for_user( let my_tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"My timeline".to_owned(), "My timeline".to_owned(),
"blog in fav_blogs and not has_cover or local and followed exclude likes" "blog in fav_blogs and not has_cover or local and followed exclude likes"
@@ -472,7 +486,7 @@ mod tests {
.unwrap(); .unwrap();
let post = Post::insert( let post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "about-linux".to_string(), slug: "about-linux".to_string(),
@@ -488,10 +502,10 @@ mod tests {
}, },
) )
.unwrap(); .unwrap();
assert!(my_tl.matches(&conn, &post, Kind::Original).unwrap()); // matches because of "blog in fav_blogs" (and there is no cover) assert!(my_tl.matches(conn, &post, Kind::Original).unwrap()); // matches because of "blog in fav_blogs" (and there is no cover)
let post = Post::insert( let post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[1].id, blog_id: blogs[1].id,
slug: "about-linux-2".to_string(), slug: "about-linux-2".to_string(),
@@ -509,7 +523,7 @@ mod tests {
}, },
) )
.unwrap(); .unwrap();
assert!(!my_tl.matches(&conn, &post, Kind::Like(&users[1])).unwrap()); assert!(!my_tl.matches(conn, &post, Kind::Like(&users[1])).unwrap());
Ok(()) Ok(())
}); });
@@ -519,17 +533,17 @@ mod tests {
fn test_add_to_all_timelines() { fn test_add_to_all_timelines() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (users, blogs) = blogTests::fill_database(&conn); let (users, blogs) = blogTests::fill_database(conn);
let gnu_tl = Timeline::new_for_user( let gnu_tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"GNU timeline".to_owned(), "GNU timeline".to_owned(),
"license in [AGPL, LGPL, GPL]".to_owned(), "license in [AGPL, LGPL, GPL]".to_owned(),
) )
.unwrap(); .unwrap();
let non_gnu_tl = Timeline::new_for_user( let non_gnu_tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"Stallman disapproved timeline".to_owned(), "Stallman disapproved timeline".to_owned(),
"not license in [AGPL, LGPL, GPL]".to_owned(), "not license in [AGPL, LGPL, GPL]".to_owned(),
@@ -537,7 +551,7 @@ mod tests {
.unwrap(); .unwrap();
let gnu_post = Post::insert( let gnu_post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "slug".to_string(), slug: "slug".to_string(),
@@ -555,7 +569,7 @@ mod tests {
.unwrap(); .unwrap();
let non_free_post = Post::insert( let non_free_post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "slug2".to_string(), slug: "slug2".to_string(),
@@ -572,13 +586,13 @@ mod tests {
) )
.unwrap(); .unwrap();
Timeline::add_to_all_timelines(&conn, &gnu_post, Kind::Original).unwrap(); Timeline::add_to_all_timelines(conn, &gnu_post, Kind::Original).unwrap();
Timeline::add_to_all_timelines(&conn, &non_free_post, Kind::Original).unwrap(); Timeline::add_to_all_timelines(conn, &non_free_post, Kind::Original).unwrap();
let res = gnu_tl.get_latest(&conn, 2).unwrap(); let res = gnu_tl.get_latest(conn, 2).unwrap();
assert_eq!(res.len(), 1); assert_eq!(res.len(), 1);
assert_eq!(res[0].id, gnu_post.id); assert_eq!(res[0].id, gnu_post.id);
let res = non_gnu_tl.get_latest(&conn, 2).unwrap(); let res = non_gnu_tl.get_latest(conn, 2).unwrap();
assert_eq!(res.len(), 1); assert_eq!(res.len(), 1);
assert_eq!(res[0].id, non_free_post.id); assert_eq!(res[0].id, non_free_post.id);
@@ -590,10 +604,10 @@ mod tests {
fn test_matches_lists_direct() { fn test_matches_lists_direct() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (users, blogs) = blogTests::fill_database(&conn); let (users, blogs) = blogTests::fill_database(conn);
let gnu_post = Post::insert( let gnu_post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "slug".to_string(), slug: "slug".to_string(),
@@ -610,63 +624,63 @@ mod tests {
) )
.unwrap(); .unwrap();
gnu_post gnu_post
.update_tags(&conn, vec![Tag::build_activity("free".to_owned()).unwrap()]) .update_tags(conn, vec![Tag::build_activity("free".to_owned()).unwrap()])
.unwrap(); .unwrap();
PostAuthor::insert( PostAuthor::insert(
&conn, conn,
NewPostAuthor { NewPostAuthor {
post_id: gnu_post.id, post_id: gnu_post.id,
author_id: blogs[0].list_authors(&conn).unwrap()[0].id, author_id: blogs[0].list_authors(conn).unwrap()[0].id,
}, },
) )
.unwrap(); .unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"blog timeline".to_owned(), "blog timeline".to_owned(),
format!("blog in [{}]", blogs[0].fqn), format!("blog in [{}]", blogs[0].fqn),
) )
.unwrap(); .unwrap();
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"blog timeline".to_owned(), "blog timeline".to_owned(),
"blog in [no_one@nowhere]".to_owned(), "blog in [no_one@nowhere]".to_owned(),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"author timeline".to_owned(), "author timeline".to_owned(),
format!( format!(
"author in [{}]", "author in [{}]",
blogs[0].list_authors(&conn).unwrap()[0].fqn blogs[0].list_authors(conn).unwrap()[0].fqn
), ),
) )
.unwrap(); .unwrap();
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"author timeline".to_owned(), "author timeline".to_owned(),
format!("author in [{}]", users[2].fqn), format!("author in [{}]", users[2].fqn),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
assert!(tl assert!(tl
.matches(&conn, &gnu_post, Kind::Reshare(&users[2])) .matches(conn, &gnu_post, Kind::Reshare(&users[2]))
.unwrap()); .unwrap());
assert!(!tl.matches(&conn, &gnu_post, Kind::Like(&users[2])).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Like(&users[2])).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"author timeline".to_owned(), "author timeline".to_owned(),
format!( format!(
@@ -675,50 +689,50 @@ mod tests {
), ),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
assert!(!tl assert!(!tl
.matches(&conn, &gnu_post, Kind::Reshare(&users[2])) .matches(conn, &gnu_post, Kind::Reshare(&users[2]))
.unwrap()); .unwrap());
assert!(tl.matches(&conn, &gnu_post, Kind::Like(&users[2])).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Like(&users[2])).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"tag timeline".to_owned(), "tag timeline".to_owned(),
"tags in [free]".to_owned(), "tags in [free]".to_owned(),
) )
.unwrap(); .unwrap();
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"tag timeline".to_owned(), "tag timeline".to_owned(),
"tags in [private]".to_owned(), "tags in [private]".to_owned(),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"english timeline".to_owned(), "english timeline".to_owned(),
"lang in [en]".to_owned(), "lang in [en]".to_owned(),
) )
.unwrap(); .unwrap();
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"franco-italian timeline".to_owned(), "franco-italian timeline".to_owned(),
"lang in [fr, it]".to_owned(), "lang in [fr, it]".to_owned(),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
Ok(()) Ok(())
}); });
@@ -762,10 +776,10 @@ mod tests {
fn test_matches_keyword() { fn test_matches_keyword() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (users, blogs) = blogTests::fill_database(&conn); let (users, blogs) = blogTests::fill_database(conn);
let gnu_post = Post::insert( let gnu_post = Post::insert(
&conn, conn,
NewPost { NewPost {
blog_id: blogs[0].id, blog_id: blogs[0].id,
slug: "slug".to_string(), slug: "slug".to_string(),
@@ -783,61 +797,61 @@ mod tests {
.unwrap(); .unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"Linux title".to_owned(), "Linux title".to_owned(),
"title contains Linux".to_owned(), "title contains Linux".to_owned(),
) )
.unwrap(); .unwrap();
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"Microsoft title".to_owned(), "Microsoft title".to_owned(),
"title contains Microsoft".to_owned(), "title contains Microsoft".to_owned(),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"Linux subtitle".to_owned(), "Linux subtitle".to_owned(),
"subtitle contains Stallman".to_owned(), "subtitle contains Stallman".to_owned(),
) )
.unwrap(); .unwrap();
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"Microsoft subtitle".to_owned(), "Microsoft subtitle".to_owned(),
"subtitle contains Nadella".to_owned(), "subtitle contains Nadella".to_owned(),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"Linux content".to_owned(), "Linux content".to_owned(),
"content contains Linux".to_owned(), "content contains Linux".to_owned(),
) )
.unwrap(); .unwrap();
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
let tl = Timeline::new_for_user( let tl = Timeline::new_for_user(
&conn, conn,
users[0].id, users[0].id,
"Microsoft content".to_owned(), "Microsoft content".to_owned(),
"subtitle contains Windows".to_owned(), "subtitle contains Windows".to_owned(),
) )
.unwrap(); .unwrap();
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap()); assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
tl.delete(&conn).unwrap(); tl.delete(conn).unwrap();
Ok(()) Ok(())
}); });
+52 -33
View File
@@ -11,22 +11,16 @@ use crate::{
use plume_common::activity_pub::inbox::AsActor; use plume_common::activity_pub::inbox::AsActor;
use whatlang::{self, Lang}; use whatlang::{self, Lang};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryError { pub enum QueryError {
SyntaxError(usize, usize, String), SyntaxError(usize, usize, String),
UnexpectedEndOfQuery, UnexpectedEndOfQuery,
RuntimeError(String), RuntimeError(String),
} }
impl From<std::option::NoneError> for QueryError {
fn from(_: std::option::NoneError) -> Self {
QueryError::UnexpectedEndOfQuery
}
}
pub type QueryResult<T> = std::result::Result<T, QueryError>; pub type QueryResult<T> = std::result::Result<T, QueryError>;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind<'a> { pub enum Kind<'a> {
Original, Original,
Reshare(&'a User), Reshare(&'a User),
@@ -239,7 +233,7 @@ impl WithList {
) -> Result<bool> { ) -> Result<bool> {
match list { match list {
List::List(name) => { List::List(name) => {
let list = lists::List::find_for_user_by_name(conn, timeline.user_id, &name)?; let list = lists::List::find_for_user_by_name(conn, timeline.user_id, name)?;
match (self, list.kind()) { match (self, list.kind()) {
(WithList::Blog, ListType::Blog) => list.contains_blog(conn, post.blog_id), (WithList::Blog, ListType::Blog) => list.contains_blog(conn, post.blog_id),
(WithList::Author { boosts, likes }, ListType::User) => match kind { (WithList::Author { boosts, likes }, ListType::User) => match kind {
@@ -298,7 +292,7 @@ impl WithList {
WithList::Author { boosts, likes } => match kind { WithList::Author { boosts, likes } => match kind {
Kind::Original => Ok(list Kind::Original => Ok(list
.iter() .iter()
.filter_map(|a| User::find_by_fqn(&*conn, a).ok()) .filter_map(|a| User::find_by_fqn(conn, a).ok())
.any(|a| post.is_author(conn, a.id).unwrap_or(false))), .any(|a| post.is_author(conn, a.id).unwrap_or(false))),
Kind::Reshare(u) => { Kind::Reshare(u) => {
if *boosts { if *boosts {
@@ -414,7 +408,7 @@ enum List<'a> {
fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> { fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
let mut res = Vec::new(); let mut res = Vec::new();
let (left, token) = parse_a(&stream)?; let (left, token) = parse_a(stream)?;
res.push(token); res.push(token);
stream = left; stream = left;
while !stream.is_empty() { while !stream.is_empty() {
@@ -436,7 +430,7 @@ fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
fn parse_a<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> { fn parse_a<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
let mut res = Vec::new(); let mut res = Vec::new();
let (left, token) = parse_b(&stream)?; let (left, token) = parse_b(stream)?;
res.push(token); res.push(token);
stream = left; stream = left;
while !stream.is_empty() { while !stream.is_empty() {
@@ -463,7 +457,7 @@ fn parse_b<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<
match left.get(0) { match left.get(0) {
Some(Token::RParent(_)) => Ok((&left[1..], token)), Some(Token::RParent(_)) => Ok((&left[1..], token)),
Some(t) => t.get_error(Token::RParent(0)), Some(t) => t.get_error(Token::RParent(0)),
None => None?, None => Err(QueryError::UnexpectedEndOfQuery),
} }
} }
_ => parse_c(stream), _ => parse_c(stream),
@@ -484,9 +478,13 @@ fn parse_c<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<
} }
fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Arg<'a>)> { fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Arg<'a>)> {
match stream.get(0).map(Token::get_text)? { match stream
.get(0)
.map(Token::get_text)
.ok_or(QueryError::UnexpectedEndOfQuery)?
{
s @ "blog" | s @ "author" | s @ "license" | s @ "tags" | s @ "lang" => { s @ "blog" | s @ "author" | s @ "license" | s @ "tags" | s @ "lang" => {
match stream.get(1)? { match stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)? {
Token::Word(_, _, r#in) if r#in == &"in" => { Token::Word(_, _, r#in) if r#in == &"in" => {
let (mut left, list) = parse_l(&stream[2..])?; let (mut left, list) = parse_l(&stream[2..])?;
let kind = match s { let kind = match s {
@@ -498,7 +496,12 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
if *clude != "include" && *clude != "exclude" { if *clude != "include" && *clude != "exclude" {
break; break;
} }
match (*clude, left.get(1).map(Token::get_text)?) { match (
*clude,
left.get(1)
.map(Token::get_text)
.ok_or(QueryError::UnexpectedEndOfQuery)?,
) {
("include", "reshares") | ("include", "reshare") => { ("include", "reshares") | ("include", "reshare") => {
boosts = true boosts = true
} }
@@ -529,7 +532,10 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
t => t.get_error(Token::Word(0, 0, "'in'")), t => t.get_error(Token::Word(0, 0, "'in'")),
} }
} }
s @ "title" | s @ "subtitle" | s @ "content" => match (stream.get(1)?, stream.get(2)?) { s @ "title" | s @ "subtitle" | s @ "content" => match (
stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)?,
stream.get(2).ok_or(QueryError::UnexpectedEndOfQuery)?,
) {
(Token::Word(_, _, contains), Token::Word(_, _, w)) if contains == &"contains" => Ok(( (Token::Word(_, _, contains), Token::Word(_, _, w)) if contains == &"contains" => Ok((
&stream[3..], &stream[3..],
Arg::Contains( Arg::Contains(
@@ -555,7 +561,13 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
if *clude != "include" && *clude != "exclude" { if *clude != "include" && *clude != "exclude" {
break; break;
} }
match (*clude, stream.get(2).map(Token::get_text)?) { match (
*clude,
stream
.get(2)
.map(Token::get_text)
.ok_or(QueryError::UnexpectedEndOfQuery)?,
) {
("include", "reshares") | ("include", "reshare") => boosts = true, ("include", "reshares") | ("include", "reshare") => boosts = true,
("exclude", "reshares") | ("exclude", "reshare") => boosts = false, ("exclude", "reshares") | ("exclude", "reshare") => boosts = false,
("include", "likes") | ("include", "like") => likes = true, ("include", "likes") | ("include", "like") => likes = true,
@@ -577,20 +589,23 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
"all" => Ok((&stream[1..], Arg::Boolean(Bool::All))), "all" => Ok((&stream[1..], Arg::Boolean(Bool::All))),
_ => unreachable!(), _ => unreachable!(),
}, },
_ => stream.get(0)?.get_error(Token::Word( _ => stream
0, .get(0)
0, .ok_or(QueryError::UnexpectedEndOfQuery)?
"one of 'blog', 'author', 'license', 'tags', 'lang', \ .get_error(Token::Word(
0,
0,
"one of 'blog', 'author', 'license', 'tags', 'lang', \
'title', 'subtitle', 'content', 'followed', 'has_cover', 'local' or 'all'", 'title', 'subtitle', 'content', 'followed', 'has_cover', 'local' or 'all'",
)), )),
} }
} }
fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], List<'a>)> { fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], List<'a>)> {
match stream.get(0)? { match stream.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
Token::LBracket(_) => { Token::LBracket(_) => {
let (left, list) = parse_m(&stream[1..])?; let (left, list) = parse_m(&stream[1..])?;
match left.get(0)? { match left.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
Token::RBracket(_) => Ok((&left[1..], List::Array(list))), Token::RBracket(_) => Ok((&left[1..], List::Array(list))),
t => t.get_error(Token::Word(0, 0, "one of ']' or ','")), t => t.get_error(Token::Word(0, 0, "one of ']' or ','")),
} }
@@ -601,16 +616,20 @@ fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Lis
} }
fn parse_m<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Vec<&'a str>)> { fn parse_m<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Vec<&'a str>)> {
let mut res: Vec<&str> = vec![match stream.get(0)? { let mut res: Vec<&str> = vec![
Token::Word(_, _, w) => w, match stream.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
t => return t.get_error(Token::Word(0, 0, "any word")),
}];
stream = &stream[1..];
while let Token::Comma(_) = stream[0] {
res.push(match stream.get(1)? {
Token::Word(_, _, w) => w, Token::Word(_, _, w) => w,
t => return t.get_error(Token::Word(0, 0, "any word")), t => return t.get_error(Token::Word(0, 0, "any word")),
}); },
];
stream = &stream[1..];
while let Token::Comma(_) = stream[0] {
res.push(
match stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)? {
Token::Word(_, _, w) => w,
t => return t.get_error(Token::Word(0, 0, "any word")),
},
);
stream = &stream[2..]; stream = &stream[2..];
} }
+465 -273
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Afrikaans\n" "Language-Team: Afrikaans\n"
"Language: af_ZA\n" "Language: af_ZA\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Arabic\n" "Language-Team: Arabic\n"
"Language: ar_SA\n" "Language: ar_SA\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "فتح محرر النصوص الغني" msgstr "فتح محرر النصوص الغني"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "العنوان" msgstr "العنوان"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "العنوان الثانوي أو الملخص" msgstr "العنوان الثانوي أو الملخص"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "اكتب مقالك هنا. ماركداون مُدَعَّم." msgstr "اكتب مقالك هنا. ماركداون مُدَعَّم."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "يتبقا {} حرفا تقريبا" msgstr "يتبقا {} حرفا تقريبا"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "الوسوم" msgstr "الوسوم"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "الرخصة" msgstr "الرخصة"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "الغلاف" msgstr "الغلاف"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "هذه مسودة" msgstr "هذه مسودة"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "نشر كتابا" msgstr "نشر كتابا"
+13 -13
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Bulgarian\n" "Language-Team: Bulgarian\n"
"Language: bg_BG\n" "Language: bg_BG\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr "Искате ли да активирате локално автоматично запаметяване, последно редактирано в {}?"
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Отворете редактора с богат текст" msgstr "Отворете редактора с богат текст"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Заглавие" msgstr "Заглавие"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Подзаглавие или резюме" msgstr "Подзаглавие или резюме"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Напишете статията си тук. Поддържа се Markdown." msgstr "Напишете статията си тук. Поддържа се Markdown."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Остават {} знака вляво" msgstr "Остават {} знака вляво"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Етикети" msgstr "Етикети"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Лиценз" msgstr "Лиценз"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Основно изображение" msgstr "Основно изображение"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Това е проект" msgstr "Това е проект"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Публикувай" msgstr "Публикувай"
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Catalan\n" "Language-Team: Catalan\n"
"Language: ca_ES\n" "Language: ca_ES\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Obre leditor de text enriquit" msgstr "Obre leditor de text enriquit"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Títol" msgstr "Títol"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Subtítol o resum" msgstr "Subtítol o resum"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Escriviu el vostre article ací. Podeu fer servir el Markdown." msgstr "Escriviu el vostre article ací. Podeu fer servir el Markdown."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Queden uns {} caràcters" msgstr "Queden uns {} caràcters"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etiquetes" msgstr "Etiquetes"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Llicència" msgstr "Llicència"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Coberta" msgstr "Coberta"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Açò és un esborrany" msgstr "Açò és un esborrany"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publica" msgstr "Publica"
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-05-09 09:58\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Czech\n" "Language-Team: Czech\n"
"Language: cs_CZ\n" "Language: cs_CZ\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Otevřít editor formátovaného textu" msgstr "Otevřít editor formátovaného textu"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Nadpis" msgstr "Nadpis"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Podnadpis, nebo shrnutí" msgstr "Podnadpis, nebo shrnutí"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Sem napište svůj článek. Markdown je podporován." msgstr "Sem napište svůj článek. Markdown je podporován."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Zbývá kolem {} znaků" msgstr "Zbývá kolem {} znaků"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tagy" msgstr "Tagy"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licence" msgstr "Licence"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Titulka" msgstr "Titulka"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Tohle je koncept" msgstr "Tohle je koncept"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Zveřejnit" msgstr "Zveřejnit"
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Danish\n" "Language-Team: Danish\n"
"Language: da_DK\n" "Language: da_DK\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+13 -13
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-26 13:16\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Language: de_DE\n" "Language: de_DE\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr "Möchten Sie die lokale automatische Speicherung laden, die zuletzt um {} bearbeitet wurde?"
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr " Rich Text Editor (RTE) öffnen" msgstr " Rich Text Editor (RTE) öffnen"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Untertitel oder Zusammenfassung" msgstr "Untertitel oder Zusammenfassung"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Schreiben deinen Artikel hier. Markdown wird unterstützt." msgstr "Schreiben deinen Artikel hier. Markdown wird unterstützt."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Ungefähr {} Zeichen übrig" msgstr "Ungefähr {} Zeichen übrig"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Schlagwörter" msgstr "Schlagwörter"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Lizenz" msgstr "Lizenz"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Einband" msgstr "Einband"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Dies ist ein Entwurf" msgstr "Dies ist ein Entwurf"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Veröffentlichen" msgstr "Veröffentlichen"
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Greek\n" "Language-Team: Greek\n"
"Language: el_GR\n" "Language: el_GR\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: English\n" "Language-Team: English\n"
"Language: en_US\n" "Language: en_US\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Esperanto\n" "Language-Team: Esperanto\n"
"Language: eo_UY\n" "Language: eo_UY\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Malfermi la riĉan redaktilon" msgstr "Malfermi la riĉan redaktilon"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titolo" msgstr "Titolo"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Verku vian artikolon ĉi tie. Markdown estas subtenita." msgstr "Verku vian artikolon ĉi tie. Markdown estas subtenita."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Proksimume {} signoj restantaj" msgstr "Proksimume {} signoj restantaj"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etikedoj" msgstr "Etikedoj"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Permesilo" msgstr "Permesilo"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Kovro" msgstr "Kovro"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Malfinias" msgstr "Malfinias"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Eldoni" msgstr "Eldoni"
+13 -13
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:56\n" "PO-Revision-Date: 2022-01-26 13:16\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Language: es_ES\n" "Language: es_ES\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr "¿Quieres cargar el guardado automático local editado por última vez en {}?"
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Abrir el editor de texto enriquecido" msgstr "Abrir el editor de texto enriquecido"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Título" msgstr "Título"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Subtítulo, o resumen" msgstr "Subtítulo, o resumen"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Escriba su artículo aquí. Puede utilizar Markdown." msgstr "Escriba su artículo aquí. Puede utilizar Markdown."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Quedan unos {} caracteres" msgstr "Quedan unos {} caracteres"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etiquetas" msgstr "Etiquetas"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licencia" msgstr "Licencia"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Cubierta" msgstr "Cubierta"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Esto es un borrador" msgstr "Esto es un borrador"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
+63
View File
@@ -0,0 +1,63 @@
msgid ""
msgstr ""
"Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2022-05-09 09:58\n"
"Last-Translator: \n"
"Language-Team: Basque\n"
"Language: eu_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: plume\n"
"X-Crowdin-Project-ID: 352097\n"
"X-Crowdin-Language: eu\n"
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?"
msgstr "{}(t)an automatikoki gordetako azken kopia lokala kargatu nahi al duzu?"
# plume-front/src/editor.rs:326
msgid "Open the rich text editor"
msgstr "Ireki testu-formatutzaile aberatsa"
# plume-front/src/editor.rs:385
msgid "Title"
msgstr "Izenburua"
# plume-front/src/editor.rs:389
msgid "Subtitle, or summary"
msgstr "Azpititulua edo laburpena"
# plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported."
msgstr "Idatzi hemen testua. Markdown erabil dezakezu."
# plume-front/src/editor.rs:407
msgid "Around {} characters left"
msgstr "%{count} karaktere geratzen dira"
# plume-front/src/editor.rs:517
msgid "Tags"
msgstr "Etiketak"
# plume-front/src/editor.rs:518
msgid "License"
msgstr "Lizentzia"
# plume-front/src/editor.rs:524
msgid "Cover"
msgstr "Azala"
# plume-front/src/editor.rs:564
msgid "This is a draft"
msgstr "Zirriborro bat da"
# plume-front/src/editor.rs:575
msgid "Publish"
msgstr "Argitaratu"
+16 -16
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:56\n" "PO-Revision-Date: 2022-05-10 17:54\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Persian\n" "Language-Team: Persian\n"
"Language: fa_IR\n" "Language: fa_IR\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr "آیا می‌خواهید نسخهٔ ذخیره شدهٔ خودکار محلّی از آخرین ویرایش در {} را بار کنید؟"
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "باز کردن ویرایشگر غنی" msgstr "گشودن ویرایشگر غنی"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "عنوان" msgstr "عنوان"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "زیرعنوان، یا چکیده" msgstr "زیرعنوان، یا چکیده"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "مقاله را اینجا بنویسید. از مارک‌داون پشتیبانی می‌شود." msgstr "مقاله‌تان را اینجا بنویسید. از مارک‌داون پشتیبانی می‌شود."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "نزدیک به {} حرف باقی مانده است" msgstr "نزدیک به {} حرف باقی مانده است"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "برچسب‌ها" msgstr "برچسب‌ها"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "پروانه" msgstr "پروانه"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "تصویر شاخص" msgstr "جلد"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "این، یک پیش‌نویس است" msgstr "این، یک پیش‌نویس است"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "انتشار" msgstr "انتشار"
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Finnish\n" "Language-Team: Finnish\n"
"Language: fi_FI\n" "Language: fi_FI\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Avaa edistynyt tekstieditori" msgstr "Avaa edistynyt tekstieditori"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Otsikko" msgstr "Otsikko"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Alaotsikko tai tiivistelmä" msgstr "Alaotsikko tai tiivistelmä"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Kirjoita artikkelisi tähän. Markdown -kuvauskieli on tuettu." msgstr "Kirjoita artikkelisi tähän. Markdown -kuvauskieli on tuettu."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "%{count} merkkiä jäljellä" msgstr "%{count} merkkiä jäljellä"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tagit" msgstr "Tagit"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Lisenssi" msgstr "Lisenssi"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Kansi" msgstr "Kansi"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Tämä on luonnos" msgstr "Tämä on luonnos"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Julkaise" msgstr "Julkaise"
+13 -13
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-05-09 09:59\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: French\n" "Language-Team: French\n"
"Language: fr_FR\n" "Language: fr_FR\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr "Voulez vous charger la sauvegarde automatique locale, éditée la dernière fois à {}?"
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Ouvrir l'éditeur de texte avancé" msgstr "Ouvrir l'éditeur de texte avancé"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Sous-titre ou résumé" msgstr "Sous-titre ou résumé"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Écrivez votre article ici. Vous pouvez utiliser du Markdown." msgstr "Écrivez votre article ici. Vous pouvez utiliser du Markdown."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Environ {} caractères restant" msgstr "Environ {} caractères restant"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Étiquettes" msgstr "Étiquettes"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licence" msgstr "Licence"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Illustration" msgstr "Illustration"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Ceci est un brouillon" msgstr "Ceci est un brouillon"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publier" msgstr "Publier"
+13 -13
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-26 13:16\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Galician\n" "Language-Team: Galician\n"
"Language: gl_ES\n" "Language: gl_ES\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr "Queres cargar a última copia gardada editada o {}?"
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Abre o editor de texto enriquecido" msgstr "Abre o editor de texto enriquecido"
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Título" msgstr "Título"
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Subtítulo, ou resumo" msgstr "Subtítulo, ou resumo"
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "Escribe aquí o teu artigo: podes utilizar Markdown." msgstr "Escribe aquí o teu artigo: podes utilizar Markdown."
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Dispós de {} caracteres" msgstr "Dispós de {} caracteres"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etiquetas" msgstr "Etiquetas"
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licenza" msgstr "Licenza"
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Portada" msgstr "Portada"
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Este é un borrador" msgstr "Este é un borrador"
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
+12 -12
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: plume\n" "Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n" "POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2020-12-19 09:55\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Hebrew\n" "Language-Team: Hebrew\n"
"Language: he_IL\n" "Language: he_IL\n"
@@ -17,47 +17,47 @@ msgstr ""
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n" "X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:189 # plume-front/src/editor.rs:172
msgid "Do you want to load the local autosave last edited at {}?" msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
# plume-front/src/editor.rs:282 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:315 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:319 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:326 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:337 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:415 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:418 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:438 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:445 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""

Some files were not shown because too many files have changed in this diff Show More