Compare commits

...
219 Commits
Author SHA1 Message Date
aitzol 939c8a75d2 entrypoint 2026-09-30 04:48:19 +01:00
aitzol a3fcfffef2 entrypoint 2026-09-30 04:42:07 +01:00
aitzol 3aee14852a rust bertsio berritzea 2026-09-26 16:05:44 +01:00
aitzol 2fb52dc594 lowercase usernames 2025-06-23 11:14:03 +02:00
aitzol e2c9e8000a docker build fixes 2025-06-23 11:07:54 +02:00
aitzol 475271ca56 Merge pull request 'tables style' (#7) from tables-style into main
Reviewed-on: #7
2024-12-23 23:18:52 +01:00
aitzol bf1c593cd1 tables style 2024-12-23 22:41:52 +01:00
aitzol bf11857d3e Merge pull request 'tables-style' (#6) from tables-style into main
Reviewed-on: #6
2024-12-22 11:13:06 +01:00
aitzol 09d1a414aa cleanup 2024-12-22 10:35:58 +01:00
aitzol 1a0d30762d cleanup 2024-12-22 10:33:58 +01:00
aitzol f568fd35b7 dependencies update 2024-12-21 23:11:13 +01:00
aitzol 477d5c54d9 Merge pull request 'tables-style' (#5) from tables-style into main
Reviewed-on: #5
2024-12-19 22:53:59 +01:00
aitzol 5e320d2782 tables style 2024-12-19 22:35:37 +01:00
aitzol d06b08786c tables style 2024-12-19 20:23:43 +01:00
aitzol 6940574a3e blockquote style 2024-12-19 16:21:52 +01:00
aitzol d3e09ce8ac blockquote style 2024-12-19 16:18:45 +01:00
aitzol e4e996f3a7 Fix horizontal scroll 2024-12-18 15:59:13 +01:00
aitzol f5b1b6f1c7 Merge pull request 'Fix moderation issues #1156(#1157)' (#1) from fix-moderation-issues into main
Reviewed-on: #1
2024-08-16 09:58:04 +02:00
aitzol 5d2e653a3b Fix moderation issues 2024-08-16 08:02:28 +02:00
aitzol ebc9fbc88a changelog 2024-01-11 22:09:25 +01:00
aitzol db8cc6e7e8 css aldaketak pantaila txikirako 2024-01-11 22:01:44 +01:00
aitzol 2d888d6aa5 changelog 2024-01-06 17:57:57 +01:00
aitzol 78b290a0dc garbiketa 2024-01-06 17:50:15 +01:00
aitzol 59e1123d35 eguneraketa 2024-01-06 17:48:05 +01:00
aitzol 56f6e46c71 lehen commita 2024-01-05 18:50:06 +01:00
trinity-1686a 304fb740d8 Merge pull request 'Support for storing media on S3' (#1149) from lx/Plume:s3 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1149
Reviewed-by: trinity-1686a <trinity-1686a@noreply@joinplu.me>
2023-06-21 18:18:37 +00:00
Alex Auvolat 61e65a55ad improve formatting 2023-05-15 12:35:39 +02:00
Alex Auvolat 3f93212424 fix plume-cli 2023-05-12 18:25:19 +02:00
Alex Auvolat 20d77c22df try (and fail) to build with Nix 2023-05-12 17:20:45 +02:00
Alex Auvolat 24d3b289da Properly handle Content-Type 2023-05-12 16:11:29 +02:00
Alex Auvolat 20fa2cacf4 Store replicated remote media on S3 if available 2023-05-12 15:54:44 +02:00
Alex Auvolat 4e67eb8317 Uniformize media path/URL handling and implement direct download from S3 backend 2023-05-12 15:40:36 +02:00
Alex Auvolat 24c008b0de Add support for uploading media files to S3 2023-05-12 13:24:36 +02:00
Alex Auvolat 1cb9459a23 Update S3 features and make S3 support optional 2023-05-12 12:35:11 +02:00
Alex Auvolat 10e06737cf Update rust-s3 dependency and move Cargo.toml dependencies 2023-05-12 12:12:32 +02:00
Alex Auvolat 30a3cec87e Add Nix development shell 2023-05-12 12:12:09 +02:00
trinity-1686a 54af93d8ff initial s3 support
probably incomplete
2023-05-12 11:30:31 +02:00
KitaitiMakoto 9425b44d08 Merge pull request 'FIX: #1145 Fix SCSS errors' (#1146) from scss-errors into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1146
2023-04-16 07:13:55 +00:00
Kitaiti Makoto 487f296db5 Fix Clippy warnings 2023-04-16 16:12:09 +09:00
Kitaiti Makoto 8bdd481e0d Fix SCSS errors 2023-04-16 16:10:54 +09:00
KitaitiMakoto 19f18421bc Merge pull request 'delete comments properly when deleting users' (#1144) from fix-delete-user into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1144
Reviewed-by: KitaitiMakoto <kitaitimakoto@noreply@joinplu.me>
2023-04-16 06:59:53 +00:00
trinity-1686a e1777e9071 delete comments properly when deleting users 2023-04-09 12:54:29 +02:00
KitaitiMakoto 613ccbcd94 Merge pull request 'Add user search form to admin panel' (#1143) from moderation-improvement into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1143
2023-03-21 10:29:33 +00:00
Kitaiti Makoto b9a09a2511 Follow pagination user list page change 2023-03-21 19:15:22 +09:00
Kitaiti Makoto 213628e400 Don't use LIKE query when username is empty for user search 2023-03-21 19:07:09 +09:00
Kitaiti Makoto d6bb2bfb72 Use unwrap_or_default() instead of unwrap_or("") 2023-03-21 18:49:52 +09:00
Kitaiti Makoto 33bd290679 Use DbConn in model tests 2023-03-20 01:21:39 +09:00
Kitaiti Makoto 85ab5393fd Set style to user search form 2023-03-20 01:03:09 +09:00
Kitaiti Makoto 98c73bb6df Add search form to user list page 2023-03-20 01:00:17 +09:00
Kitaiti Makoto 3e9d9a459f Enable admin_search_user route 2023-03-20 01:00:02 +09:00
Kitaiti Makoto a394c3f210 Define admin_search_user route 2023-03-20 00:59:46 +09:00
Kitaiti Makoto a1a19e091a Define User::search_local_by_name() method 2023-03-20 00:59:16 +09:00
Kitaiti Makoto ec030d500d Exclude instance user when counting local users 2023-03-20 00:19:42 +09:00
Kitaiti Makoto cfa74f84e7 Remove instance users from user list to show 2023-03-06 19:30:18 +09:00
trinity-1686a 97cbe7f446 Merge pull request 'allow timeline manipulation from plm' (#1113) from timeline-cli into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1113
Reviewed-by: KitaitiMakoto <kitaitimakoto@noreply@joinplu.me>
2023-02-26 15:56:16 +00:00
trinity-1686a 7e4d081027 Merge branch 'main' into timeline-cli 2023-02-26 16:48:40 +01:00
KitaitiMakoto 1e5ae92135 Merge pull request 'Update crates' (#1138) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1138
2023-01-10 15:53:07 +00:00
Kitaiti Makoto 036ee6fac4 Merge remote-tracking branch 'origin/main' into update-crates 2023-01-11 00:42:34 +09:00
dependabot[bot] 6028295748 Bump glob from 0.3.0 to 0.3.1
Bumps [glob](https://github.com/rust-lang/glob) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/rust-lang/glob/releases)
- [Commits](https://github.com/rust-lang/glob/compare/0.3.0...0.3.1)

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

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

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

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

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

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

Change blog title specification

Revert "Change blog title specification"

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-06 19:06:09 +00:00
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
trinity-1686a 35b951967d add a few help messages to the cli 2023-01-01 11:03:50 +01: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
trinity-1686a 771d4325c2 add plm command for list management 2022-12-17 17:51:51 +01:00
trinity-1686a 1536a6d3f3 allow timeline manipulation from plm 2022-12-16 22:51:14 +01:00
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
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
142 changed files with 19908 additions and 36476 deletions
+5 -3
View File
@@ -10,7 +10,7 @@ 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>>cimg/postgres:14.2<</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
@@ -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-11-27 -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
View File
@@ -0,0 +1 @@
use flake
+4 -4
View File
@@ -11,20 +11,20 @@ jobs:
steps: steps:
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v2
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v2
- -
name: Login to DockerHub name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- -
name: Build and push name: Build and push
id: docker_build id: docker_build
uses: docker/build-push-action@v2 uses: docker/build-push-action@v3
with: with:
push: true push: true
tags: plumeorg/plume:latest tags: plumeorg/plume:latest
+4 -4
View File
@@ -11,10 +11,10 @@ jobs:
steps: steps:
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v2
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v2
- -
name: Docker meta name: Docker meta
id: meta id: meta
@@ -23,14 +23,14 @@ jobs:
images: plumeorg/plume images: plumeorg/plume
- -
name: Login to DockerHub name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- -
name: Build and push name: Build and push
id: docker_build id: docker_build
uses: docker/build-push-action@v2 uses: docker/build-push-action@v3
with: with:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
+2
View File
@@ -20,3 +20,5 @@ search_index
__pycache__ __pycache__
.vscode/ .vscode/
*-journal *-journal
.direnv/
build.log*
+33
View File
@@ -4,6 +4,39 @@
## [Unreleased] - ReleaseDate ## [Unreleased] - ReleaseDate
### Added
- Add 'My feed' to i18n timeline name (#1084)
- Bidirectional support for user page header (#1092)
- Add non anonymous bind to LDAP server, taken from https://git.joinplu.me/Plume/Plume/src/branch/ldap-non-anon PR
### Changed
- Use blog title as slug (#1094, #1126, #1127)
- Bump Rust to nightly 2022-07-19 (#1119)
- Force LDAP simple bind with *cn* rdn instead of *uid*
- Update rust-toolchain to nightly-2023-04-14
- Update chrono from 0.4.0 to 0.4.31
- Update scheduled-thread-pool from 0.2.6 to 0.2.7
- Change blockquote color to `lightpurple`
- Some colour has been added to the tables to make them more visible
### 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)
- Deprecation warnings during build process(see rust crate updates)
- Server error 500 creating new blog with white spaces inside title. Bug reported on https://git.joinplu.me/Plume/Plume/issues/1152
- Show _Subscribe_ button in column format instead of row format in screen smaller than 600px. https://git.lainoa.eus/aitzol/Plume/commit/db8cc6e7e8351a5d74f7ce0399126e13493c62d9
### To do
- Choose rdn via environment variables for LDAP simple bind
## [[0.7.2]] - 2022-05-11 ## [[0.7.2]] - 2022-05-11
### Added ### Added
Generated
+1862 -1147
View File
File diff suppressed because it is too large Load Diff
+21 -18
View File
@@ -1,33 +1,34 @@
[package] [package]
authors = ["Plume contributors"] authors = ["Plume contributors"]
name = "plume" name = "plume"
version = "0.7.2" version = "0.7.3-dev-fork"
repository = "https://github.com/Plume-org/Plume" repository = "https://git.lainoa.eus/aitzol/Plume"
edition = "2018" edition = "2021"
[dependencies] [dependencies]
atom_syndication = "0.11.0" atom_syndication = "0.12.0"
clap = "2.33" clap = "2.33"
dotenv = "0.15.0" dotenv = "0.15.0"
gettext = "0.4.0" gettext = "0.4.0"
gettext-macros = "0.6.1" gettext-macros = "0.6.1"
gettext-utils = "0.1.0" gettext-utils = "0.1.0"
guid-create = "0.2" guid-create = "0.4.1"
conv = "0.3.3"
lettre_email = "0.9.2" lettre_email = "0.9.2"
num_cpus = "1.10" num_cpus = "1.16.0"
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 = "0.4.1" rocket_i18n = "0.4.1"
scheduled-thread-pool = "0.2.2" scheduled-thread-pool = "0.2.7"
serde = "1.0" serde = "1.0.137"
serde_json = "1.0.81" serde_json = "1.0.81"
shrinkwraprs = "0.3.0" shrinkwraprs = "0.3.0"
validator = { version = "0.15", features = ["derive"] } validator = { version = "0.15", features = ["derive"] }
webfinger = "0.4.1" webfinger = "0.4.1"
tracing = "0.1.34" tracing = "0.1.35"
tracing-subscriber = "0.3.10" tracing-subscriber = "0.3.10"
riker = "0.4.2" riker = "0.4.2"
activitystreams = "0.7.0-alpha.18" activitystreams = "=0.7.0-alpha.20"
[[bin]] [[bin]]
name = "plume" name = "plume"
@@ -35,7 +36,7 @@ path = "src/main.rs"
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
version = "0.4" version = "0.4.31"
[dependencies.ctrlc] [dependencies.ctrlc]
features = ["termination"] features = ["termination"]
@@ -60,20 +61,22 @@ 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.14.0" ructe = "0.15.0"
rsass = "0.24" rsass = "0.26"
[features] [features]
default = ["postgres"] default = ["postgres", "s3"]
postgres = ["plume-models/postgres", "diesel/postgres"] postgres = ["plume-models/postgres", "diesel/postgres"]
sqlite = ["plume-models/sqlite", "diesel/sqlite"] sqlite = ["plume-models/sqlite", "diesel/sqlite"]
debug-mailer = [] debug-mailer = []
test = [] test = []
search-lindera = ["plume-models/search-lindera"] search-lindera = ["plume-models/search-lindera"]
s3 = ["plume-models/s3"]
cargo-clippy = []
[workspace] [workspace]
members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"] members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"]
+62
View File
@@ -0,0 +1,62 @@
FROM rust:1.82 AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
gettext \
postgresql-client \
libpq-dev \
git \
curl \
gcc \
make \
openssl \
libssl-dev \
clang
RUN ln -s /usr/bin/python3 /usr/bin/python & \
ln -s /usr/bin/pip3 /usr/bin/pip
WORKDIR /scratch
COPY script/wasm-deps.sh .
RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh
WORKDIR /app
COPY . .
RUN echo "=== TOOLCHAIN ===" && \
rustc --version && \
cargo --version && \
rustup show && \
echo "=== TIME-CORE IN LOCK ===" && \
grep -A5 -B2 'name = "time-core"' Cargo.lock
#RUN cargo install wasm-pack
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
RUN chmod a+x ./script/plume-front.sh && sleep 1 && ./script/plume-front.sh
RUN cargo install --locked --path ./ --force --no-default-features --features postgres
RUN cargo install --locked --path plume-cli --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 clean
FROM debian:stable-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
libpq5
WORKDIR /app
COPY --from=builder /app /app
COPY --from=builder /usr/local/cargo/bin/plm /bin/
COPY --from=builder /usr/local/cargo/bin/plume /bin/
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 755 /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["plume"]
EXPOSE 7878
+7 -7
View File
@@ -1,4 +1,4 @@
FROM rust:1-buster as builder FROM rust:latest 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 \
@@ -13,27 +13,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libssl-dev \ libssl-dev \
clang clang
RUN ln -s /usr/bin/python3 /usr/bin/python & \
ln -s /usr/bin/pip3 /usr/bin/pip
WORKDIR /scratch WORKDIR /scratch
COPY script/wasm-deps.sh . 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 \
libpq5 \ libpq5
libssl1.1
WORKDIR /app WORKDIR /app
+2 -2
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>
+3 -3
View File
@@ -98,7 +98,7 @@ main article {
} }
blockquote { blockquote {
border-inline-start: 5px solid $gray; border-inline-start: 5px solid $lightpurple;
margin: 1em auto; margin: 1em auto;
padding: 0em 2em; padding: 0em 2em;
} }
@@ -228,7 +228,7 @@ main .article-meta {
fill: currentColor; fill: currentColor;
} }
.action.liked:hover svg.feather { .action.liked:hover svg.feather {
background: transparentize($red, 0.75) background: transparentize($red, 0.75);
color: $red; color: $red;
} }
} }
@@ -252,7 +252,7 @@ main .article-meta {
background: $primary; background: $primary;
} }
.action.reshared:hover svg.feather { .action.reshared:hover svg.feather {
background: transparentize($primary, 0.75) background: transparentize($primary, 0.75);
color: $primary; color: $primary;
} }
} }
+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 {
+4
View File
@@ -37,6 +37,10 @@ body > header {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
&.right-nav {
overflow-x: hidden;
}
hr { hr {
height: 100%; height: 100%;
width: 0.2em; width: 0.2em;
+10
View File
@@ -0,0 +1,10 @@
/* tables */
table, td, th, tr {
border: 1px dotted $lightpurple;
}
table {
border-collapse: collapse;
}
tr:nth-child(even){background-color: $gray;}
+1
View File
@@ -12,3 +12,4 @@
@import "header"; @import "header";
@import "article"; @import "article";
@import "forms"; @import "forms";
@import "tables";
+1
View File
@@ -12,3 +12,4 @@
@import "header"; @import "header";
@import "article"; @import "article";
@import "forms"; @import "forms";
@import "tables";
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
set -eu
ENV_FILE=/app/.env
cat > "$ENV_FILE" <<EOF
BASE_URL=blog.lainoa.eus
ROCKET_SECRET_KEY=${ROCKET_SECRET_KEY}
MAIL_SERVER=smtp.lainoa.eus
MAIL_USER=aitzol
MAIL_PASSWORD=${EMAIL_PASSWORD}
POSTGRES_USER=plume
POSTGRES_DB=plume
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
DATABASE_URL=postgres://plume:${POSTGRES_PASSWORD}@postgres:5432/plume
MIGRATION_DIRECTORY=migrations/postgres
USE_HTTPS=1
ROCKET_ADDRESS=0.0.0.0
ROCKET_PORT=7878
LDAP_ADDR=ldaps://ldap.lainoa.eus:636
LDAP_BASE_DN=cn=users,ou=groups,dc=ldap,dc=lainoa,dc=eus
LDAP_USER_NAME_ATTR=uid
LDAP_USER_MAIL_ATTR=mail
LDAP_TLS=true
LDAP_USER=cn=readonly,dc=ldap,dc=lainoa,dc=eus
LDAP_PASSWORD=${LDAP_PASSWORD}
RUST_BACTRACE=1
EOF
chmod 600 "$ENV_FILE"
exec "$@"
Generated
+116
View File
@@ -0,0 +1,116 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1683408522,
"narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1683857898,
"narHash": "sha256-pyVY4UxM6zUX97g6bk6UyCbZGCWZb2Zykrne8YxacRA=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "4e7fba3f37f5e184ada0ef3cf1e4d8ef450f240b",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+60
View File
@@ -0,0 +1,60 @@
{
description = "Developpment shell for Plume including nightly Rust compiler";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs { inherit system overlays; };
inputs = with pkgs; [
(rust-bin.nightly.latest.default.override {
targets = [ "wasm32-unknown-unknown" ];
})
wasm-pack
openssl
pkg-config
gettext
postgresql
sqlite
];
in {
packages.default = pkgs.rustPlatform.buildRustPackage {
pname = "plume";
version = "0.7.3-dev";
src = ./.;
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"pulldown-cmark-0.8.0" = "sha256-lpfoRDuY3zJ3QmUqJ5k9OL0MEdGDpwmpJ+u5BCj2kIA=";
"rocket_csrf-0.1.2" = "sha256-WywZfMiwZqTPfSDcAE7ivTSYSaFX+N9fjnRsLSLb9wE=";
};
};
buildNoDefaultFeatures = true;
buildFeatures = ["postgresql" "s3"];
nativeBuildInputs = inputs;
buildPhase = ''
wasm-pack build --target web --release plume-front
cargo build --no-default-features --features postgresql,s3 --path .
cargo build --no-default-features --features postgresql,s3 --path plume-cli
'';
installPhase = ''
cargo install --no-default-features --features postgresql,s3 --path . --target-dir $out
cargo install --no-default-features --features postgresql,s3 --path plume-cli --target-dir $out
'';
};
devShells.default = pkgs.mkShell {
packages = inputs;
};
});
}
+1 -1
View File
@@ -5,5 +5,5 @@ 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
+1
View File
@@ -24,3 +24,4 @@ path = "../plume-models"
postgres = ["plume-models/postgres", "diesel/postgres"] postgres = ["plume-models/postgres", "diesel/postgres"]
sqlite = ["plume-models/sqlite", "diesel/sqlite"] sqlite = ["plume-models/sqlite", "diesel/sqlite"]
search-lindera = ["plume-models/search-lindera"] search-lindera = ["plume-models/search-lindera"]
s3 = ["plume-models/s3"]
+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
+262
View File
@@ -0,0 +1,262 @@
use clap::{App, Arg, ArgMatches, SubCommand};
use plume_models::{blogs::Blog, instance::Instance, lists::*, users::User, Connection};
pub fn command<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("lists")
.about("Manage lists")
.subcommand(
SubCommand::with_name("new")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of this list"),
)
.arg(
Arg::with_name("type")
.short("t")
.long("type")
.takes_value(true)
.help(
r#"The type of this list (one of "user", "blog", "word" or "prefix")"#,
),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help("Username of whom this list is for. Empty for an instance list"),
)
.about("Create a new list"),
)
.subcommand(
SubCommand::with_name("delete")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of the list to delete"),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help("Username of whom this list was for. Empty for instance list"),
)
.arg(
Arg::with_name("yes")
.short("y")
.long("yes")
.help("Confirm the deletion"),
)
.about("Delete a list"),
)
.subcommand(
SubCommand::with_name("add")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of the list to add an element to"),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help("Username of whom this list is for. Empty for instance list"),
)
.arg(
Arg::with_name("value")
.short("v")
.long("value")
.takes_value(true)
.help("The value to add"),
)
.about("Add element to a list"),
)
.subcommand(
SubCommand::with_name("rm")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of the list to remove an element from"),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help("Username of whom this list is for. Empty for instance list"),
)
.arg(
Arg::with_name("value")
.short("v")
.long("value")
.takes_value(true)
.help("The value to remove"),
)
.about("Remove element from list"),
)
}
pub fn run<'a>(args: &ArgMatches<'a>, conn: &Connection) {
let conn = conn;
match args.subcommand() {
("new", Some(x)) => new(x, conn),
("delete", Some(x)) => delete(x, conn),
("add", Some(x)) => add(x, conn),
("rm", Some(x)) => rm(x, conn),
("", None) => command().print_help().unwrap(),
_ => println!("Unknown subcommand"),
}
}
fn get_list_identifier(args: &ArgMatches<'_>) -> (String, Option<String>) {
let name = args
.value_of("name")
.map(String::from)
.expect("No name provided for the list");
let user = args.value_of("user").map(String::from);
(name, user)
}
fn get_list_type(args: &ArgMatches<'_>) -> ListType {
let typ = args
.value_of("type")
.map(String::from)
.expect("No name type for the list");
match typ.as_str() {
"user" => ListType::User,
"blog" => ListType::Blog,
"word" => ListType::Word,
"prefix" => ListType::Prefix,
_ => panic!("Invalid list type: {}", typ),
}
}
fn get_value(args: &ArgMatches<'_>) -> String {
args.value_of("value")
.map(String::from)
.expect("No query provided")
}
fn resolve_user(username: &str, conn: &Connection) -> User {
let instance = Instance::get_local_uncached(conn).expect("Failed to load local instance");
User::find_by_name(conn, username, instance.id).expect("User not found")
}
fn new(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_list_identifier(args);
let typ = get_list_type(args);
let user = user.map(|user| resolve_user(&user, conn));
List::new(conn, &name, user.as_ref(), typ).expect("failed to create list");
}
fn delete(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_list_identifier(args);
if !args.is_present("yes") {
panic!("Warning, this operation is destructive. Add --yes to confirm you want to do it.")
}
let user = user.map(|user| resolve_user(&user, conn));
let list =
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
list.delete(conn).expect("Failed to update list");
}
fn add(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_list_identifier(args);
let value = get_value(args);
let user = user.map(|user| resolve_user(&user, conn));
let list =
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
match list.kind() {
ListType::Blog => {
let blog_id = Blog::find_by_fqn(conn, &value).expect("unknown blog").id;
if !list.contains_blog(conn, blog_id).unwrap() {
list.add_blogs(conn, &[blog_id]).unwrap();
}
}
ListType::User => {
let user_id = User::find_by_fqn(conn, &value).expect("unknown user").id;
if !list.contains_user(conn, user_id).unwrap() {
list.add_users(conn, &[user_id]).unwrap();
}
}
ListType::Word => {
if !list.contains_word(conn, &value).unwrap() {
list.add_words(conn, &[&value]).unwrap();
}
}
ListType::Prefix => {
if !list.contains_prefix(conn, &value).unwrap() {
list.add_prefixes(conn, &[&value]).unwrap();
}
}
}
}
fn rm(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_list_identifier(args);
let value = get_value(args);
let user = user.map(|user| resolve_user(&user, conn));
let list =
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
match list.kind() {
ListType::Blog => {
let blog_id = Blog::find_by_fqn(conn, &value).expect("unknown blog").id;
let mut blogs = list.list_blogs(conn).unwrap();
if let Some(index) = blogs.iter().position(|b| b.id == blog_id) {
blogs.swap_remove(index);
let blogs = blogs.iter().map(|b| b.id).collect::<Vec<_>>();
list.set_blogs(conn, &blogs).unwrap();
}
}
ListType::User => {
let user_id = User::find_by_fqn(conn, &value).expect("unknown user").id;
let mut users = list.list_users(conn).unwrap();
if let Some(index) = users.iter().position(|u| u.id == user_id) {
users.swap_remove(index);
let users = users.iter().map(|u| u.id).collect::<Vec<_>>();
list.set_users(conn, &users).unwrap();
}
}
ListType::Word => {
let mut words = list.list_words(conn).unwrap();
if let Some(index) = words.iter().position(|w| *w == value) {
words.swap_remove(index);
let words = words.iter().map(String::as_str).collect::<Vec<_>>();
list.set_words(conn, &words).unwrap();
}
}
ListType::Prefix => {
let mut prefixes = list.list_prefixes(conn).unwrap();
if let Some(index) = prefixes.iter().position(|p| *p == value) {
prefixes.swap_remove(index);
let prefixes = prefixes.iter().map(String::as_str).collect::<Vec<_>>();
list.set_prefixes(conn, &prefixes).unwrap();
}
}
}
}
+8
View File
@@ -4,8 +4,10 @@ use plume_models::{instance::Instance, Connection as Conn, CONFIG};
use std::io::{self, prelude::*}; use std::io::{self, prelude::*};
mod instance; mod instance;
mod list;
mod migration; mod migration;
mod search; mod search;
mod timeline;
mod users; mod users;
fn main() { fn main() {
@@ -16,6 +18,8 @@ fn main() {
.subcommand(instance::command()) .subcommand(instance::command())
.subcommand(migration::command()) .subcommand(migration::command())
.subcommand(search::command()) .subcommand(search::command())
.subcommand(timeline::command())
.subcommand(list::command())
.subcommand(users::command()); .subcommand(users::command());
let matches = app.clone().get_matches(); let matches = app.clone().get_matches();
@@ -37,6 +41,10 @@ fn main() {
("search", Some(args)) => { ("search", Some(args)) => {
search::run(args, &conn.expect("Couldn't connect to the database.")) search::run(args, &conn.expect("Couldn't connect to the database."))
} }
("timeline", Some(args)) => {
timeline::run(args, &conn.expect("Couldn't connect to the database."))
}
("lists", Some(args)) => list::run(args, &conn.expect("Couldn't connect to the database.")),
("users", Some(args)) => { ("users", Some(args)) => {
users::run(args, &conn.expect("Couldn't connect to the database.")) users::run(args, &conn.expect("Couldn't connect to the database."))
} }
+257
View File
@@ -0,0 +1,257 @@
use clap::{App, Arg, ArgMatches, SubCommand};
use plume_models::{instance::Instance, posts::Post, timeline::*, users::*, Connection};
pub fn command<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("timeline")
.about("Manage public timeline")
.subcommand(
SubCommand::with_name("new")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of this timeline"),
)
.arg(
Arg::with_name("query")
.short("q")
.long("query")
.takes_value(true)
.help("The query posts in this timelines have to match"),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help(
"Username of whom this timeline is for. Empty for an instance timeline",
),
)
.arg(
Arg::with_name("preload-count")
.short("p")
.long("preload-count")
.takes_value(true)
.help("Number of posts to try to preload in this timeline at its creation"),
)
.about("Create a new timeline"),
)
.subcommand(
SubCommand::with_name("delete")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of the timeline to delete"),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help(
"Username of whom this timeline was for. Empty for instance timeline",
),
)
.arg(
Arg::with_name("yes")
.short("y")
.long("yes")
.help("Confirm the deletion"),
)
.about("Delete a timeline"),
)
.subcommand(
SubCommand::with_name("edit")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of the timeline to edit"),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help("Username of whom this timeline is for. Empty for instance timeline"),
)
.arg(
Arg::with_name("query")
.short("q")
.long("query")
.takes_value(true)
.help("The query posts in this timelines have to match"),
)
.about("Edit the query of a timeline"),
)
.subcommand(
SubCommand::with_name("repopulate")
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.takes_value(true)
.help("The name of the timeline to repopulate"),
)
.arg(
Arg::with_name("user")
.short("u")
.long("user")
.takes_value(true)
.help(
"Username of whom this timeline was for. Empty for instance timeline",
),
)
.arg(
Arg::with_name("preload-count")
.short("p")
.long("preload-count")
.takes_value(true)
.help("Number of posts to try to preload in this timeline at its creation"),
)
.about("Repopulate a timeline. Run this after modifying a list the timeline depends on."),
)
}
pub fn run<'a>(args: &ArgMatches<'a>, conn: &Connection) {
let conn = conn;
match args.subcommand() {
("new", Some(x)) => new(x, conn),
("edit", Some(x)) => edit(x, conn),
("delete", Some(x)) => delete(x, conn),
("repopulate", Some(x)) => repopulate(x, conn),
("", None) => command().print_help().unwrap(),
_ => println!("Unknown subcommand"),
}
}
fn get_timeline_identifier(args: &ArgMatches<'_>) -> (String, Option<String>) {
let name = args
.value_of("name")
.map(String::from)
.expect("No name provided for the timeline");
let user = args.value_of("user").map(String::from);
(name, user)
}
fn get_query(args: &ArgMatches<'_>) -> String {
let query = args
.value_of("query")
.map(String::from)
.expect("No query provided");
match TimelineQuery::parse(&query) {
Ok(_) => (),
Err(QueryError::SyntaxError(start, end, message)) => panic!(
"Query parsing error between {} and {}: {}",
start, end, message
),
Err(QueryError::UnexpectedEndOfQuery) => {
panic!("Query parsing error: unexpected end of query")
}
Err(QueryError::RuntimeError(message)) => panic!("Query parsing error: {}", message),
}
query
}
fn get_preload_count(args: &ArgMatches<'_>) -> usize {
args.value_of("preload-count")
.map(|arg| arg.parse().expect("invalid preload-count"))
.unwrap_or(plume_models::ITEMS_PER_PAGE as usize)
}
fn resolve_user(username: &str, conn: &Connection) -> User {
let instance = Instance::get_local_uncached(conn).expect("Failed to load local instance");
User::find_by_name(conn, username, instance.id).expect("User not found")
}
fn preload(timeline: Timeline, count: usize, conn: &Connection) {
timeline.remove_all_posts(conn).unwrap();
if count == 0 {
return;
}
let mut posts = Vec::with_capacity(count as usize);
for post in Post::list_filtered(conn, None, None, None)
.unwrap()
.into_iter()
.rev()
{
if timeline.matches(conn, &post, Kind::Original).unwrap() {
posts.push(post);
if posts.len() >= count {
break;
}
}
}
for post in posts.iter().rev() {
timeline.add_post(conn, post).unwrap();
}
}
fn new(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_timeline_identifier(args);
let query = get_query(args);
let preload_count = get_preload_count(args);
let user = user.map(|user| resolve_user(&user, conn));
let timeline = if let Some(user) = user {
Timeline::new_for_user(conn, user.id, name, query)
} else {
Timeline::new_for_instance(conn, name, query)
}
.expect("Failed to create new timeline");
preload(timeline, preload_count, conn);
}
fn edit(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_timeline_identifier(args);
let query = get_query(args);
let user = user.map(|user| resolve_user(&user, conn));
let mut timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
.expect("timeline not found");
timeline.query = query;
timeline.update(conn).expect("Failed to update timeline");
}
fn delete(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_timeline_identifier(args);
if !args.is_present("yes") {
panic!("Warning, this operation is destructive. Add --yes to confirm you want to do it.")
}
let user = user.map(|user| resolve_user(&user, conn));
let timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
.expect("timeline not found");
timeline.delete(conn).expect("Failed to update timeline");
}
fn repopulate(args: &ArgMatches<'_>, conn: &Connection) {
let (name, user) = get_timeline_identifier(args);
let preload_count = get_preload_count(args);
let user = user.map(|user| resolve_user(&user, conn));
let timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
.expect("timeline not found");
preload(timeline, preload_count, conn);
}
+11 -12
View File
@@ -7,29 +7,28 @@ edition = "2018"
[dependencies] [dependencies]
array_tool = "1.0" array_tool = "1.0"
base64 = "0.13" base64 = "0.13"
heck = "0.4.0"
hex = "0.4" hex = "0.4"
openssl = "0.10.40" openssl = "0.10.40"
rocket = "0.4.6" rocket = "0.4.11"
reqwest = { version = "0.11.10", features = ["blocking", "json", "socks"] } reqwest = { version = "0.11.11", features = ["blocking", "json", "socks"] }
serde = "1.0" serde = "1.0.137"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0.81" serde_json = "1.0.81"
shrinkwraprs = "0.3.0" shrinkwraprs = "0.3.0"
syntect = "4.5.0" syntect = "4.5.0"
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] } regex-syntax = { version = "0.6.26", default-features = false, features = ["unicode-perl"] }
tracing = "0.1.34" tracing = "0.1.35"
askama_escape = "0.10.3" askama_escape = "0.10.3"
activitystreams = "0.7.0-alpha.18" activitystreams = "=0.7.0-alpha.20"
activitystreams-ext = "0.1.0-alpha.2" activitystreams-ext = "0.1.0-alpha.2"
url = "2.2.2" url = "2.2.2"
flume = "0.10.12" flume = "0.10.13"
tokio = { version = "1.18.2", features = ["full"] } tokio = { version = "1.19.2", features = ["full"] }
futures = "0.3.21" futures = "0.3.25"
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
version = "0.4" version = "0.4.31"
[dependencies.pulldown-cmark] [dependencies.pulldown-cmark]
default-features = false default-features = false
@@ -38,6 +37,6 @@ branch = "bidi-plume"
[dev-dependencies] [dev-dependencies]
assert-json-diff = "2.0.1" assert-json-diff = "2.0.1"
once_cell = "1.10.0" once_cell = "1.12.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
+3 -3
View File
@@ -561,7 +561,7 @@ mod tests {
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new()); static MY_SIGNER: Lazy<MySigner> = Lazy::new(MySigner::new);
struct MySigner { struct MySigner {
public_key: String, public_key: String,
@@ -596,7 +596,7 @@ mod tests {
.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(|_| SignError()) verifier.verify(signature).map_err(|_| SignError())
} }
} }
@@ -782,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();
+68 -2
View File
@@ -518,7 +518,8 @@ mod tests {
use super::*; use super::*;
use activitystreams::{ use activitystreams::{
activity::{ActorAndObjectRef, Create}, activity::{ActorAndObjectRef, Create},
object::kind::ArticleType, object::{kind::ArticleType, Image},
prelude::{ApActorExt, BaseExt, ExtendsExt, ObjectExt},
}; };
use assert_json_diff::assert_json_eq; use assert_json_diff::assert_json_eq;
use serde_json::{from_str, json, to_value}; use serde_json::{from_str, json, to_value};
@@ -592,7 +593,7 @@ mod tests {
} }
#[test] #[test]
fn de_custom_group() { fn se_custom_group() {
let group = CustomGroup::new( let group = CustomGroup::new(
ApActor::new("https://example.com/inbox".parse().unwrap(), Group::new()), ApActor::new("https://example.com/inbox".parse().unwrap(), Group::new()),
ApSignature { ApSignature {
@@ -625,6 +626,71 @@ mod tests {
assert_eq!(to_value(group).unwrap(), expected); 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] #[test]
fn se_licensed_article() { fn se_licensed_article() {
let object = ApObject::new(Article::new()); let object = ApObject::new(Article::new());
+2 -2
View File
@@ -253,7 +253,7 @@ mod tests {
.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())
} }
} }
@@ -262,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());
+1 -1
View File
@@ -119,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,
-9
View File
@@ -1,4 +1,3 @@
use heck::ToUpperCamelCase;
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;
@@ -16,14 +15,6 @@ 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_upper_camel_case()
.chars()
.filter(|c| c.is_alphanumeric())
.collect()
}
/** /**
* Percent-encode characters which are not allowed in IRI path segments. * Percent-encode characters which are not allowed in IRI path segments.
* *
+8 -5
View File
@@ -2,7 +2,10 @@
name = "plume-front" name = "plume-front"
version = "0.7.2" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2021"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
@@ -12,15 +15,15 @@ gettext = "0.4.0"
gettext-macros = "0.6.1" gettext-macros = "0.6.1"
gettext-utils = "0.1.0" 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.80" wasm-bindgen = "0.2.99"
js-sys = "0.3.57" js-sys = "0.3.76"
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.57" version = "0.3.76"
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
+5 -1
View File
@@ -168,10 +168,14 @@ fn load_autosave() {
.get(&get_autosave_id()) .get(&get_autosave_id())
{ {
let autosave_info: AutosaveInformation = serde_json::from_str(&autosave_str).ok().unwrap(); let autosave_info: AutosaveInformation = serde_json::from_str(&autosave_str).ok().unwrap();
let d = &JsValue::from_f64(autosave_info.last_saved);
let message = i18n!( let message = i18n!(
CATALOG, CATALOG,
"Do you want to load the local autosave last edited at {}?"; "Do you want to load the local autosave last edited at {}?";
Date::new(&JsValue::from_f64(autosave_info.last_saved)).to_date_string().as_string().unwrap() // next line shows 'unexpected token' error on docker image building
//Date::new(&JsValue::from_f64(autosave_info.last_saved)).to_date_string().as_string().unwrap()
Date::new(d).to_date_string().as_string().unwrap()
); );
if let Ok(true) = window().unwrap().confirm_with_message(&message) { if let Ok(true) = window().unwrap().confirm_with_message(&message) {
set_value("editor-content", &autosave_info.contents); set_value("editor-content", &autosave_info.contents);
+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
+17 -13
View File
@@ -7,38 +7,41 @@ edition = "2018"
[dependencies] [dependencies]
ammonia = "3.2.0" ammonia = "3.2.0"
bcrypt = "0.12.1" bcrypt = "0.12.1"
guid-create = "0.2" guid-create = "0.3.0"
itertools = "0.10.3" itertools = "0.10.3"
lazy_static = "1.0" lazy_static = "1.0"
ldap3 = "0.10.4" ldap3 = "0.11.1"
migrations_internals= "1.4.0" migrations_internals= "1.4.0"
openssl = "0.10.40" openssl = "0.10.40"
rocket = "0.4.6" rocket = "0.4.11"
rocket_i18n = "0.4.1" rocket_i18n = "0.4.1"
reqwest = "0.11.10" reqwest = "0.11.11"
scheduled-thread-pool = "0.2.2" scheduled-thread-pool = "0.2.7"
serde = "1.0" serde = "1.0.137"
rust-s3 = { version = "0.33.0", optional = true, features = ["blocking"] }
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0.81" serde_json = "1.0.81"
tantivy = "0.13.3" tantivy = "0.13.3"
url = "2.1" url = "2.1"
walkdir = "2.2" walkdir = "2.2"
webfinger = "0.4.1" webfinger = "0.4.1"
whatlang = "0.16.0" whatlang = "0.16.2"
shrinkwraprs = "0.3.0" shrinkwraprs = "0.3.0"
diesel-derive-newtype = "0.1.2" diesel-derive-newtype = "1.0.0"
glob = "0.3.0" glob = "0.3.1"
lindera-tantivy = { version = "0.7.1", optional = true } lindera-tantivy = { version = "0.7.1", optional = true }
tracing = "0.1.34" tracing = "0.1.35"
riker = "0.4.2" riker = "0.4.2"
once_cell = "1.10.0" once_cell = "1.12.0"
lettre = "0.9.6" lettre = "0.9.6"
native-tls = "0.2.10" native-tls = "0.2.10"
activitystreams = "0.7.0-alpha.18" activitystreams = "=0.7.0-alpha.20"
ahash = "=0.8.11"
heck = "0.4.1"
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
version = "0.4" version = "0.4.31"
[dependencies.diesel] [dependencies.diesel]
features = ["r2d2", "chrono"] features = ["r2d2", "chrono"]
@@ -61,3 +64,4 @@ diesel_migrations = "1.3.0"
postgres = ["diesel/postgres", "plume-macro/postgres" ] postgres = ["diesel/postgres", "plume-macro/postgres" ]
sqlite = ["diesel/sqlite", "plume-macro/sqlite" ] sqlite = ["diesel/sqlite", "plume-macro/sqlite" ]
search-lindera = ["lindera-tantivy"] search-lindera = ["lindera-tantivy"]
s3 = ["rust-s3"]
+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
+18 -1
View File
@@ -5,7 +5,7 @@ use rocket::{
Outcome, Outcome,
}; };
/// Wrapper around User to use as a request guard on pages reserved to admins. /// Wrapper around User to use as a request guard on pages exclusively reserved to admins.
pub struct Admin(pub User); pub struct Admin(pub User);
impl<'a, 'r> FromRequest<'a, 'r> for Admin { impl<'a, 'r> FromRequest<'a, 'r> for Admin {
@@ -21,6 +21,23 @@ impl<'a, 'r> FromRequest<'a, 'r> for Admin {
} }
} }
/// Same as `Admin` but it forwards to next guard if the user is not an admin.
/// It's useful when there are multiple implementations of routes for admin and moderator.
pub struct InclusiveAdmin(pub User);
impl<'a, 'r> FromRequest<'a, 'r> for InclusiveAdmin {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<InclusiveAdmin, ()> {
let user = request.guard::<User>()?;
if user.is_admin() {
Outcome::Success(InclusiveAdmin(user))
} else {
Outcome::Forward(())
}
}
}
/// Same as `Admin` but for moderators. /// Same as `Admin` but for moderators.
pub struct Moderator(pub User); pub struct Moderator(pub User);
+1 -1
View File
@@ -103,7 +103,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
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);
} }
} }
+1
View File
@@ -1,6 +1,7 @@
use crate::{schema::apps, Error, Result}; use crate::{schema::apps, Error, Result};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use serde_derive::Serialize;
#[derive(Clone, Queryable, Serialize)] #[derive(Clone, Queryable, Serialize)]
pub struct App { pub struct App {
+3 -6
View File
@@ -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(())
}); });
} }
+83 -61
View File
@@ -1,6 +1,7 @@
use heck::ToUpperCamelCase;
use crate::{ use crate::{
db_conn::DbConn, instance::*, medias::Media, posts::Post, safe_string::SafeString, instance::*, medias::Media, posts::Post, safe_string::SafeString, schema::blogs, users::User,
schema::blogs, users::User, Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE, Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
}; };
use activitystreams::{ use activitystreams::{
actor::{ApActor, ApActorExt, AsApActor, Group}, actor::{ApActor, ApActorExt, AsApActor, Group},
@@ -18,10 +19,13 @@ use openssl::{
rsa::Rsa, rsa::Rsa,
sign::{Signer, Verifier}, sign::{Signer, Verifier},
}; };
use plume_common::activity_pub::{ use plume_common::{
inbox::{AsActor, FromId}, activity_pub::{
sign, ActivityStream, ApSignature, CustomGroup, Id, IntoId, PublicKey, Source, SourceProperty, inbox::{AsActor, FromId},
ToAsString, ToAsUri, sign, ActivityStream, ApSignature, CustomGroup, Id, IntoId, PublicKey, Source,
SourceProperty, ToAsString, ToAsUri,
},
utils::iri_percent_encode_seg,
}; };
use webfinger::*; use webfinger::*;
@@ -83,9 +87,13 @@ impl Blog {
if inserted.fqn.is_empty() { if inserted.fqn.is_empty() {
if instance.local { if instance.local {
inserted.fqn = inserted.actor_id.clone(); inserted.fqn = iri_percent_encode_seg(&inserted.actor_id);
} else { } else {
inserted.fqn = format!("{}@{}", inserted.actor_id, instance.public_domain); inserted.fqn = format!(
"{}@{}",
iri_percent_encode_seg(&inserted.actor_id),
instance.public_domain
);
} }
} }
@@ -95,6 +103,19 @@ impl Blog {
find_by!(blogs, find_by_ap_url, ap_url as &str); find_by!(blogs, find_by_ap_url, ap_url as &str);
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32); find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
/// Remove non alphanumeric characters and CamelCase a string
pub fn slug(title: &str) -> String {
title.to_upper_camel_case()
.chars()
.filter(|c| c.is_alphanumeric())
.collect()
}
/*
pub fn slug(title: &str) -> &str {
title
}
*/
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> { pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
Instance::get(conn, self.instance_id) Instance::get(conn, self.instance_id)
} }
@@ -131,10 +152,10 @@ impl Blog {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn find_by_fqn(conn: &DbConn, fqn: &str) -> Result<Blog> { pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<Blog> {
let from_db = blogs::table let from_db = blogs::table
.filter(blogs::fqn.eq(fqn)) .filter(blogs::fqn.eq(fqn))
.first(&**conn) .first(conn)
.optional()?; .optional()?;
if let Some(from_db) = from_db { if let Some(from_db) = from_db {
Ok(from_db) Ok(from_db)
@@ -143,7 +164,7 @@ impl Blog {
} }
} }
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<Blog> { fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<Blog> {
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)? resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
.links .links
.into_iter() .into_iter()
@@ -162,7 +183,7 @@ impl Blog {
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> { pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
let mut blog = ApActor::new(self.inbox_url.parse()?, Group::new()); let mut blog = ApActor::new(self.inbox_url.parse()?, Group::new());
blog.set_preferred_username(self.actor_id.clone()); blog.set_preferred_username(iri_percent_encode_seg(&self.actor_id));
blog.set_name(self.title.clone()); blog.set_name(self.title.clone());
blog.set_outbox(self.outbox_url.parse()?); blog.set_outbox(self.outbox_url.parse()?);
blog.set_summary(self.summary_html.to_string()); blog.set_summary(self.summary_html.to_string());
@@ -361,15 +382,15 @@ impl IntoId for Blog {
} }
} }
impl FromId<DbConn> for Blog { impl FromId<Connection> for Blog {
type Error = Error; type Error = Error;
type Object = CustomGroup; type Object = CustomGroup;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> { fn from_activity(conn: &Connection, acct: CustomGroup) -> Result<Self> {
let (name, outbox_url, inbox_url) = { let (name, outbox_url, inbox_url) = {
let actor = acct.ap_actor_ref(); let actor = acct.ap_actor_ref();
let name = actor let name = actor
@@ -377,6 +398,7 @@ impl FromId<DbConn> for Blog {
.ok_or(Error::MissingApProperty)? .ok_or(Error::MissingApProperty)?
.to_string(); .to_string();
if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) { if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
tracing::error!("preferredUsername includes invalid character(s): {}", &name);
return Err(Error::InvalidValue); return Err(Error::InvalidValue);
} }
( (
@@ -656,7 +678,7 @@ pub(crate) mod tests {
.unwrap() .unwrap()
.id, .id,
); );
let _: Blog = blog1.save_changes(&*conn).unwrap(); let _: Blog = blog1.save_changes(conn).unwrap();
(users, vec![blog1, blog2, blog3]) (users, vec![blog1, blog2, blog3])
} }
@@ -665,10 +687,10 @@ pub(crate) mod tests {
fn get_instance() { fn get_instance() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
fill_database(&conn); fill_database(conn);
let blog = Blog::insert( let blog = Blog::insert(
&conn, conn,
NewBlog::new_local( NewBlog::new_local(
"SomeName".to_owned(), "SomeName".to_owned(),
"Some name".to_owned(), "Some name".to_owned(),
@@ -680,7 +702,7 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
blog.get_instance(&conn).unwrap().id, blog.get_instance(conn).unwrap().id,
Instance::get_local().unwrap().id Instance::get_local().unwrap().id
); );
// TODO add tests for remote instance // TODO add tests for remote instance
@@ -692,10 +714,10 @@ pub(crate) mod tests {
fn authors() { fn authors() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (user, _) = fill_database(&conn); let (user, _) = fill_database(conn);
let b1 = Blog::insert( let b1 = Blog::insert(
&conn, conn,
NewBlog::new_local( NewBlog::new_local(
"SomeName".to_owned(), "SomeName".to_owned(),
"Some name".to_owned(), "Some name".to_owned(),
@@ -706,7 +728,7 @@ pub(crate) mod tests {
) )
.unwrap(); .unwrap();
let b2 = Blog::insert( let b2 = Blog::insert(
&conn, conn,
NewBlog::new_local( NewBlog::new_local(
"Blog".to_owned(), "Blog".to_owned(),
"Blog".to_owned(), "Blog".to_owned(),
@@ -719,7 +741,7 @@ pub(crate) mod tests {
let blog = vec![b1, b2]; let blog = vec![b1, b2];
BlogAuthor::insert( BlogAuthor::insert(
&conn, conn,
NewBlogAuthor { NewBlogAuthor {
blog_id: blog[0].id, blog_id: blog[0].id,
author_id: user[0].id, author_id: user[0].id,
@@ -729,7 +751,7 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
BlogAuthor::insert( BlogAuthor::insert(
&conn, conn,
NewBlogAuthor { NewBlogAuthor {
blog_id: blog[0].id, blog_id: blog[0].id,
author_id: user[1].id, author_id: user[1].id,
@@ -739,7 +761,7 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
BlogAuthor::insert( BlogAuthor::insert(
&conn, conn,
NewBlogAuthor { NewBlogAuthor {
blog_id: blog[1].id, blog_id: blog[1].id,
author_id: user[0].id, author_id: user[0].id,
@@ -749,39 +771,39 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
assert!(blog[0] assert!(blog[0]
.list_authors(&conn) .list_authors(conn)
.unwrap() .unwrap()
.iter() .iter()
.any(|a| a.id == user[0].id)); .any(|a| a.id == user[0].id));
assert!(blog[0] assert!(blog[0]
.list_authors(&conn) .list_authors(conn)
.unwrap() .unwrap()
.iter() .iter()
.any(|a| a.id == user[1].id)); .any(|a| a.id == user[1].id));
assert!(blog[1] assert!(blog[1]
.list_authors(&conn) .list_authors(conn)
.unwrap() .unwrap()
.iter() .iter()
.any(|a| a.id == user[0].id)); .any(|a| a.id == user[0].id));
assert!(!blog[1] assert!(!blog[1]
.list_authors(&conn) .list_authors(conn)
.unwrap() .unwrap()
.iter() .iter()
.any(|a| a.id == user[1].id)); .any(|a| a.id == user[1].id));
assert!(Blog::find_for_author(&conn, &user[0]) assert!(Blog::find_for_author(conn, &user[0])
.unwrap() .unwrap()
.iter() .iter()
.any(|b| b.id == blog[0].id)); .any(|b| b.id == blog[0].id));
assert!(Blog::find_for_author(&conn, &user[1]) assert!(Blog::find_for_author(conn, &user[1])
.unwrap() .unwrap()
.iter() .iter()
.any(|b| b.id == blog[0].id)); .any(|b| b.id == blog[0].id));
assert!(Blog::find_for_author(&conn, &user[0]) assert!(Blog::find_for_author(conn, &user[0])
.unwrap() .unwrap()
.iter() .iter()
.any(|b| b.id == blog[1].id)); .any(|b| b.id == blog[1].id));
assert!(!Blog::find_for_author(&conn, &user[1]) assert!(!Blog::find_for_author(conn, &user[1])
.unwrap() .unwrap()
.iter() .iter()
.any(|b| b.id == blog[1].id)); .any(|b| b.id == blog[1].id));
@@ -793,10 +815,10 @@ pub(crate) mod tests {
fn find_local() { fn find_local() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
fill_database(&conn); fill_database(conn);
let blog = Blog::insert( let blog = Blog::insert(
&conn, conn,
NewBlog::new_local( NewBlog::new_local(
"SomeName".to_owned(), "SomeName".to_owned(),
"Some name".to_owned(), "Some name".to_owned(),
@@ -807,7 +829,7 @@ pub(crate) mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(Blog::find_by_fqn(&conn, "SomeName").unwrap().id, blog.id); assert_eq!(Blog::find_by_fqn(conn, "SomeName").unwrap().id, blog.id);
Ok(()) Ok(())
}) })
} }
@@ -816,10 +838,10 @@ pub(crate) mod tests {
fn get_fqn() { fn get_fqn() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
fill_database(&conn); fill_database(conn);
let blog = Blog::insert( let blog = Blog::insert(
&conn, conn,
NewBlog::new_local( NewBlog::new_local(
"SomeName".to_owned(), "SomeName".to_owned(),
"Some name".to_owned(), "Some name".to_owned(),
@@ -839,10 +861,10 @@ pub(crate) mod tests {
fn delete() { fn delete() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (_, blogs) = fill_database(&conn); let (_, blogs) = fill_database(conn);
blogs[0].delete(&conn).unwrap(); blogs[0].delete(conn).unwrap();
assert!(Blog::get(&conn, blogs[0].id).is_err()); assert!(Blog::get(conn, blogs[0].id).is_err());
Ok(()) Ok(())
}) })
} }
@@ -851,10 +873,10 @@ pub(crate) mod tests {
fn delete_via_user() { fn delete_via_user() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (user, _) = fill_database(&conn); let (user, _) = fill_database(conn);
let b1 = Blog::insert( let b1 = Blog::insert(
&conn, conn,
NewBlog::new_local( NewBlog::new_local(
"SomeName".to_owned(), "SomeName".to_owned(),
"Some name".to_owned(), "Some name".to_owned(),
@@ -865,7 +887,7 @@ pub(crate) mod tests {
) )
.unwrap(); .unwrap();
let b2 = Blog::insert( let b2 = Blog::insert(
&conn, conn,
NewBlog::new_local( NewBlog::new_local(
"Blog".to_owned(), "Blog".to_owned(),
"Blog".to_owned(), "Blog".to_owned(),
@@ -878,7 +900,7 @@ pub(crate) mod tests {
let blog = vec![b1, b2]; let blog = vec![b1, b2];
BlogAuthor::insert( BlogAuthor::insert(
&conn, conn,
NewBlogAuthor { NewBlogAuthor {
blog_id: blog[0].id, blog_id: blog[0].id,
author_id: user[0].id, author_id: user[0].id,
@@ -888,7 +910,7 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
BlogAuthor::insert( BlogAuthor::insert(
&conn, conn,
NewBlogAuthor { NewBlogAuthor {
blog_id: blog[0].id, blog_id: blog[0].id,
author_id: user[1].id, author_id: user[1].id,
@@ -898,7 +920,7 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
BlogAuthor::insert( BlogAuthor::insert(
&conn, conn,
NewBlogAuthor { NewBlogAuthor {
blog_id: blog[1].id, blog_id: blog[1].id,
author_id: user[0].id, author_id: user[0].id,
@@ -907,11 +929,11 @@ pub(crate) mod tests {
) )
.unwrap(); .unwrap();
user[0].delete(&conn).unwrap(); user[0].delete(conn).unwrap();
assert!(Blog::get(&conn, blog[0].id).is_ok()); assert!(Blog::get(conn, blog[0].id).is_ok());
assert!(Blog::get(&conn, blog[1].id).is_err()); assert!(Blog::get(conn, blog[1].id).is_err());
user[1].delete(&conn).unwrap(); user[1].delete(conn).unwrap();
assert!(Blog::get(&conn, blog[0].id).is_err()); assert!(Blog::get(conn, blog[0].id).is_err());
Ok(()) Ok(())
}) })
} }
@@ -920,10 +942,10 @@ pub(crate) mod tests {
fn self_federation() { fn self_federation() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (users, mut blogs) = fill_database(&conn); let (users, mut blogs) = fill_database(conn);
blogs[0].icon_id = Some( blogs[0].icon_id = Some(
Media::insert( Media::insert(
&conn, conn,
NewMedia { NewMedia {
file_path: "aaa.png".into(), file_path: "aaa.png".into(),
alt_text: String::new(), alt_text: String::new(),
@@ -939,7 +961,7 @@ pub(crate) mod tests {
); );
blogs[0].banner_id = Some( blogs[0].banner_id = Some(
Media::insert( Media::insert(
&conn, conn,
NewMedia { NewMedia {
file_path: "bbb.png".into(), file_path: "bbb.png".into(),
alt_text: String::new(), alt_text: String::new(),
@@ -954,9 +976,9 @@ pub(crate) mod tests {
.id, .id,
); );
let _: Blog = blogs[0].save_changes(&**conn).unwrap(); let _: Blog = blogs[0].save_changes(&**conn).unwrap();
let ap_repr = blogs[0].to_activity(&conn).unwrap(); let ap_repr = blogs[0].to_activity(conn).unwrap();
blogs[0].delete(&conn).unwrap(); blogs[0].delete(conn).unwrap();
let blog = Blog::from_activity(&conn, ap_repr).unwrap(); let blog = Blog::from_activity(conn, ap_repr).unwrap();
assert_eq!(blog.actor_id, blogs[0].actor_id); assert_eq!(blog.actor_id, blogs[0].actor_id);
assert_eq!(blog.title, blogs[0].title); assert_eq!(blog.title, blogs[0].title);
@@ -968,8 +990,8 @@ pub(crate) mod tests {
assert_eq!(blog.public_key, blogs[0].public_key); assert_eq!(blog.public_key, blogs[0].public_key);
assert_eq!(blog.fqn, blogs[0].fqn); assert_eq!(blog.fqn, blogs[0].fqn);
assert_eq!(blog.summary_html, blogs[0].summary_html); assert_eq!(blog.summary_html, blogs[0].summary_html);
assert_eq!(blog.icon_url(&conn), blogs[0].icon_url(&conn)); assert_eq!(blog.icon_url(conn), blogs[0].icon_url(conn));
assert_eq!(blog.banner_url(&conn), blogs[0].banner_url(&conn)); assert_eq!(blog.banner_url(conn), blogs[0].banner_url(conn));
Ok(()) Ok(())
}) })
@@ -979,7 +1001,7 @@ pub(crate) mod tests {
fn to_activity() { fn to_activity() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, Error, _>(|| { conn.test_transaction::<_, Error, _>(|| {
let (_users, blogs) = fill_database(&conn); let (_users, blogs) = fill_database(conn);
let blog = &blogs[0]; let blog = &blogs[0];
let act = blog.to_activity(conn)?; let act = blog.to_activity(conn)?;
+23 -21
View File
@@ -1,6 +1,5 @@
use crate::{ use crate::{
comment_seers::{CommentSeers, NewCommentSeers}, comment_seers::{CommentSeers, NewCommentSeers},
db_conn::DbConn,
instance::Instance, instance::Instance,
medias::Media, medias::Media,
mentions::Mention, mentions::Mention,
@@ -74,6 +73,7 @@ impl Comment {
}); });
get!(comments); get!(comments);
list_by!(comments, list_by_post, post_id as i32); list_by!(comments, list_by_post, post_id as i32);
list_by!(comments, list_by_author, author_id as i32);
find_by!(comments, find_by_ap_url, ap_url as &str); find_by!(comments, find_by_ap_url, ap_url as &str);
pub fn get_author(&self, conn: &Connection) -> Result<User> { pub fn get_author(&self, conn: &Connection) -> Result<User> {
@@ -111,7 +111,7 @@ impl Comment {
.unwrap_or(false) .unwrap_or(false)
} }
pub fn to_activity(&self, conn: &DbConn) -> Result<Note> { pub fn to_activity(&self, conn: &Connection) -> Result<Note> {
let author = User::get(conn, self.author_id)?; let author = User::get(conn, self.author_id)?;
let (html, mentions, _hashtags) = utils::md_to_html( let (html, mentions, _hashtags) = utils::md_to_html(
self.content.get().as_ref(), self.content.get().as_ref(),
@@ -136,7 +136,8 @@ impl Comment {
|id| Comment::get(conn, id).map(|comment| comment.ap_url.unwrap_or_default()), |id| Comment::get(conn, id).map(|comment| comment.ap_url.unwrap_or_default()),
)?); )?);
note.set_published( note.set_published(
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into()) //EZABATU OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos_opt().unwrap().into())
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.and_utc().timestamp_nanos_opt().unwrap().into())
.expect("OffsetDateTime"), .expect("OffsetDateTime"),
); );
note.set_attributed_to(author.into_id().parse::<IriString>()?); note.set_attributed_to(author.into_id().parse::<IriString>()?);
@@ -149,7 +150,7 @@ impl Comment {
Ok(note) Ok(note)
} }
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> { pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
let author = User::get(conn, self.author_id)?; let author = User::get(conn, self.author_id)?;
let note = self.to_activity(conn)?; let note = self.to_activity(conn)?;
@@ -217,15 +218,15 @@ impl Comment {
} }
} }
impl FromId<DbConn> for Comment { impl FromId<Connection> for Comment {
type Error = Error; type Error = Error;
type Object = Note; type Object = Note;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> { fn from_activity(conn: &Connection, note: Note) -> Result<Self> {
let comm = { let comm = {
let previous_url = note let previous_url = note
.in_reply_to() .in_reply_to()
@@ -354,21 +355,21 @@ impl FromId<DbConn> for Comment {
} }
} }
impl AsObject<User, Create, &DbConn> for Comment { impl AsObject<User, Create, &Connection> for Comment {
type Error = Error; type Error = Error;
type Output = Self; type Output = Self;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self> { fn activity(self, _conn: &Connection, _actor: User, _id: &str) -> Result<Self> {
// The actual creation takes place in the FromId impl // The actual creation takes place in the FromId impl
Ok(self) Ok(self)
} }
} }
impl AsObject<User, Delete, &DbConn> for Comment { impl AsObject<User, Delete, &Connection> for Comment {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
if self.author_id != actor.id { if self.author_id != actor.id {
return Err(Error::Unauthorized); return Err(Error::Unauthorized);
} }
@@ -381,14 +382,14 @@ impl AsObject<User, Delete, &DbConn> for Comment {
} }
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)
.filter(comments::in_response_to_id.eq(self.id)) .filter(comments::in_response_to_id.eq(self.id))
.set(comments::in_response_to_id.eq(self.in_response_to_id)) .set(comments::in_response_to_id.eq(self.in_response_to_id))
.execute(&**conn)?; .execute(conn)?;
diesel::delete(&self).execute(&**conn)?; diesel::delete(&self).execute(conn)?;
Ok(()) Ok(())
} }
} }
@@ -423,6 +424,7 @@ impl CommentTree {
mod tests { mod tests {
use super::*; use super::*;
use crate::blogs::Blog; use crate::blogs::Blog;
use crate::db_conn::DbConn;
use crate::inbox::{inbox, tests::fill_database, InboxResult}; use crate::inbox::{inbox, tests::fill_database, InboxResult};
use crate::safe_string::SafeString; use crate::safe_string::SafeString;
use crate::tests::{db, format_datetime}; use crate::tests::{db, format_datetime};
@@ -431,7 +433,7 @@ mod tests {
use serde_json::{json, to_value}; use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Comment, Vec<Post>, Vec<User>, Vec<Blog>) { fn prepare_activity(conn: &DbConn) -> (Comment, Vec<Post>, Vec<User>, Vec<Blog>) {
let (posts, users, blogs) = fill_database(&conn); let (posts, users, blogs) = fill_database(conn);
let comment = Comment::insert( let comment = Comment::insert(
conn, conn,
@@ -457,8 +459,8 @@ mod tests {
fn self_federation() { fn self_federation() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (original_comm, posts, users, _blogs) = prepare_activity(&conn); let (original_comm, posts, users, _blogs) = prepare_activity(conn);
let act = original_comm.create_activity(&conn).unwrap(); let act = original_comm.create_activity(conn).unwrap();
assert_json_eq!(to_value(&act).unwrap(), json!({ assert_json_eq!(to_value(&act).unwrap(), json!({
"actor": "https://plu.me/@/admin/", "actor": "https://plu.me/@/admin/",
@@ -500,7 +502,7 @@ mod tests {
}, },
) )
.unwrap(); .unwrap();
let reply_act = reply.create_activity(&conn).unwrap(); let reply_act = reply.create_activity(conn).unwrap();
assert_json_eq!(to_value(&reply_act).unwrap(), json!({ assert_json_eq!(to_value(&reply_act).unwrap(), json!({
"actor": "https://plu.me/@/user/", "actor": "https://plu.me/@/user/",
@@ -522,12 +524,12 @@ mod tests {
})); }));
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, 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);
+126 -6
View File
@@ -5,6 +5,10 @@ use rocket::config::Limits;
use rocket::Config as RocketConfig; use rocket::Config as RocketConfig;
use std::collections::HashSet; use std::collections::HashSet;
use std::env::{self, var}; use std::env::{self, var};
use serde_derive::Serialize;
#[cfg(feature = "s3")]
use s3::{Bucket, Region, creds::Credentials};
#[cfg(not(test))] #[cfg(not(test))]
const DB_NAME: &str = "plume"; const DB_NAME: &str = "plume";
@@ -27,13 +31,23 @@ pub struct Config {
pub mail: Option<MailConfig>, pub mail: Option<MailConfig>,
pub ldap: Option<LdapConfig>, pub ldap: Option<LdapConfig>,
pub proxy: Option<ProxyConfig>, pub proxy: Option<ProxyConfig>,
pub s3: Option<S3Config>,
} }
impl Config { impl Config {
pub fn proxy(&self) -> Option<&reqwest::Proxy> { pub fn proxy(&self) -> Option<&reqwest::Proxy> {
self.proxy.as_ref().map(|p| &p.proxy) self.proxy.as_ref().map(|p| &p.proxy)
} }
} }
fn string_to_bool(val: &str, name: &str) -> bool {
match val {
"1" | "true" | "TRUE" => true,
"0" | "false" | "FALSE" => false,
_ => panic!("Invalid configuration: {} is not boolean", name),
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum InvalidRocketConfig { pub enum InvalidRocketConfig {
Env, Env,
@@ -280,6 +294,7 @@ pub struct LdapConfig {
pub tls: bool, pub tls: bool,
pub user_name_attr: String, pub user_name_attr: String,
pub mail_attr: String, pub mail_attr: String,
pub user: Option<(String, String)>,
} }
fn get_ldap_config() -> Option<LdapConfig> { fn get_ldap_config() -> Option<LdapConfig> {
@@ -288,23 +303,29 @@ fn get_ldap_config() -> Option<LdapConfig> {
match (addr, base_dn) { match (addr, base_dn) {
(Some(addr), Some(base_dn)) => { (Some(addr), Some(base_dn)) => {
let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned()); let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned());
let tls = match tls.as_ref() { let tls = string_to_bool(&tls, "LDAP_TLS");
"1" | "true" | "TRUE" => true,
"0" | "false" | "FALSE" => false,
_ => panic!("Invalid LDAP configuration : tls"),
};
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned()); let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned()); let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
//2023-12-30
let user = var("LDAP_USER").ok();
let password = var("LDAP_PASSWORD").ok();
let user = match (user, password) {
(Some(user), Some(password)) => Some((user, password)),
(None, None) => None,
_ => panic!("Invalid LDAP configuration both or neither of LDAP_USER and LDAP_PASSWORD must be set")
};
//
Some(LdapConfig { Some(LdapConfig {
addr, addr,
base_dn, base_dn,
tls, tls,
user_name_attr, user_name_attr,
mail_attr, mail_attr,
user,
}) })
} }
(None, None) => None, (None, None) => None,
(_, _) => { _ => {
panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set") panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set")
} }
} }
@@ -349,6 +370,104 @@ fn get_proxy_config() -> Option<ProxyConfig> {
}) })
} }
pub struct S3Config {
pub bucket: String,
pub access_key_id: String,
pub access_key_secret: String,
// region? If not set, default to us-east-1
pub region: String,
// hostname for s3. If not set, default to $region.amazonaws.com
pub hostname: String,
// may be useful when using self hosted s3. Won't work with recent AWS buckets
pub path_style: bool,
// http or https
pub protocol: String,
// download directly from s3 to user, wihout going through Plume. Require public read on bucket
pub direct_download: bool,
// use this hostname for downloads, can be used with caching proxy in front of s3 (expected to
// be reachable through https)
pub alias: Option<String>,
}
impl S3Config {
#[cfg(feature = "s3")]
pub fn get_bucket(&self) -> Bucket {
let region = Region::Custom {
region: self.region.clone(),
endpoint: format!("{}://{}", self.protocol, self.hostname),
};
let credentials = Credentials {
access_key: Some(self.access_key_id.clone()),
secret_key: Some(self.access_key_secret.clone()),
security_token: None,
session_token: None,
expiration: None,
};
let bucket = Bucket::new(&self.bucket, region, credentials).unwrap();
if self.path_style {
bucket.with_path_style()
} else {
bucket
}
}
}
fn get_s3_config() -> Option<S3Config> {
let bucket = var("S3_BUCKET").ok();
let access_key_id = var("AWS_ACCESS_KEY_ID").ok();
let access_key_secret = var("AWS_SECRET_ACCESS_KEY").ok();
if bucket.is_none() && access_key_id.is_none() && access_key_secret.is_none() {
return None;
}
#[cfg(not(feature = "s3"))]
panic!("S3 support is not enabled in this build");
#[cfg(feature = "s3")]
{
if bucket.is_none() || access_key_id.is_none() || access_key_secret.is_none() {
panic!("Invalid S3 configuration: some required values are set, but not others");
}
let bucket = bucket.unwrap();
let access_key_id = access_key_id.unwrap();
let access_key_secret = access_key_secret.unwrap();
let region = var("S3_REGION").unwrap_or_else(|_| "us-east-1".to_owned());
let hostname = var("S3_HOSTNAME").unwrap_or_else(|_| format!("{}.amazonaws.com", region));
let protocol = var("S3_PROTOCOL").unwrap_or_else(|_| "https".to_owned());
if protocol != "http" && protocol != "https" {
panic!("Invalid S3 configuration: invalid protocol {}", protocol);
}
let path_style = var("S3_PATH_STYLE").unwrap_or_else(|_| "false".to_owned());
let path_style = string_to_bool(&path_style, "S3_PATH_STYLE");
let direct_download = var("S3_DIRECT_DOWNLOAD").unwrap_or_else(|_| "false".to_owned());
let direct_download = string_to_bool(&direct_download, "S3_DIRECT_DOWNLOAD");
let alias = var("S3_ALIAS_HOST").ok();
if direct_download && protocol == "http" && alias.is_none() {
panic!("S3 direct download is disabled because bucket is accessed through plain HTTP. Use HTTPS or set an alias hostname (S3_ALIAS_HOST).");
}
Some(S3Config {
bucket,
access_key_id,
access_key_secret,
region,
hostname,
protocol,
path_style,
direct_download,
alias,
})
}
}
lazy_static! { lazy_static! {
pub static ref CONFIG: Config = Config { pub static ref CONFIG: Config = Config {
base_url: var("BASE_URL").unwrap_or_else(|_| format!( base_url: var("BASE_URL").unwrap_or_else(|_| format!(
@@ -380,5 +499,6 @@ lazy_static! {
mail: get_mail_config(), mail: get_mail_config(),
ldap: get_ldap_config(), ldap: get_ldap_config(),
proxy: get_proxy_config(), proxy: get_proxy_config(),
s3: get_s3_config(),
}; };
} }
+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(())
} }
} }
} }
+15
View File
@@ -1,4 +1,5 @@
use crate::{ use crate::{
blocklisted_emails::BlocklistedEmail,
db_conn::DbConn, db_conn::DbConn,
schema::email_signups, schema::email_signups,
users::{NewUser, Role, User}, users::{NewUser, Role, User},
@@ -60,6 +61,8 @@ pub struct NewEmailSignup<'a> {
impl EmailSignup { impl EmailSignup {
pub fn start(conn: &DbConn, email: &str) -> Result<Token> { pub fn start(conn: &DbConn, email: &str) -> Result<Token> {
Self::ensure_email_not_blocked(conn, email)?;
conn.transaction(|| { conn.transaction(|| {
Self::ensure_user_not_exist_by_email(conn, email)?; Self::ensure_user_not_exist_by_email(conn, email)?;
let _rows = Self::delete_existings_by_email(conn, email)?; let _rows = Self::delete_existings_by_email(conn, email)?;
@@ -90,6 +93,8 @@ impl EmailSignup {
} }
pub fn confirm(&self, conn: &DbConn) -> Result<()> { pub fn confirm(&self, conn: &DbConn) -> Result<()> {
Self::ensure_email_not_blocked(conn, &self.email)?;
conn.transaction(|| { conn.transaction(|| {
Self::ensure_user_not_exist_by_email(conn, &self.email)?; Self::ensure_user_not_exist_by_email(conn, &self.email)?;
if self.expired() { if self.expired() {
@@ -101,6 +106,8 @@ impl EmailSignup {
} }
pub fn complete(&self, conn: &DbConn, username: String, password: String) -> Result<User> { pub fn complete(&self, conn: &DbConn, username: String, password: String) -> Result<User> {
Self::ensure_email_not_blocked(conn, &self.email)?;
conn.transaction(|| { conn.transaction(|| {
Self::ensure_user_not_exist_by_email(conn, &self.email)?; Self::ensure_user_not_exist_by_email(conn, &self.email)?;
let user = NewUser::new_local( let user = NewUser::new_local(
@@ -122,6 +129,14 @@ impl EmailSignup {
Ok(()) 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<()> { fn ensure_user_not_exist_by_email(conn: &DbConn, email: &str) -> Result<()> {
if User::email_used(conn, email)? { if User::email_used(conn, email)? {
let _rows = Self::delete_existings_by_email(conn, email)?; let _rows = Self::delete_existings_by_email(conn, email)?;
+15 -18
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User, ap_url, instance::Instance, notifications::*, schema::follows, users::User, Connection, Error,
Connection, Error, Result, CONFIG, Result, CONFIG,
}; };
use activitystreams::{ use activitystreams::{
activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo}, activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo},
@@ -107,12 +107,7 @@ impl Follow {
res.notify(conn)?; res.notify(conn)?;
let accept = res.build_accept(from, target, follow)?; let accept = res.build_accept(from, target, follow)?;
broadcast( broadcast(target, accept, vec![from.clone()], CONFIG.proxy().cloned());
&*target,
accept,
vec![from.clone()],
CONFIG.proxy().cloned(),
);
Ok(res) Ok(res)
} }
@@ -155,11 +150,11 @@ impl Follow {
} }
} }
impl AsObject<User, FollowAct, &DbConn> for User { impl AsObject<User, FollowAct, &Connection> for User {
type Error = Error; type Error = Error;
type Output = Follow; type Output = Follow;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Follow> { fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Follow> {
// Mastodon (at least) requires the full Follow object when accepting it, // Mastodon (at least) requires the full Follow object when accepting it,
// so we rebuilt it here // so we rebuilt it here
let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?); let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?);
@@ -167,15 +162,15 @@ impl AsObject<User, FollowAct, &DbConn> for User {
} }
} }
impl FromId<DbConn> for Follow { impl FromId<Connection> for Follow {
type Error = Error; type Error = Error;
type Object = FollowAct; type Object = FollowAct;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Follow::find_by_ap_url(conn, id) Follow::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> { fn from_activity(conn: &Connection, follow: FollowAct) -> Result<Self> {
let actor = User::from_id( let actor = User::from_id(
conn, conn,
follow follow
@@ -207,18 +202,18 @@ impl FromId<DbConn> for Follow {
} }
} }
impl AsObject<User, Undo, &DbConn> for Follow { impl AsObject<User, Undo, &Connection> for Follow {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
let conn = conn; let conn = conn;
if self.follower_id == actor.id { if self.follower_id == actor.id {
diesel::delete(&self).execute(&**conn)?; diesel::delete(&self).execute(conn)?;
// delete associated notification if any // delete associated notification if any
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) { if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
diesel::delete(&notif).execute(&**conn)?; diesel::delete(&notif).execute(conn)?;
} }
Ok(()) Ok(())
@@ -237,7 +232,9 @@ impl IntoId for Follow {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{tests::db, users::tests as user_tests, users::tests::fill_database}; use crate::{
db_conn::DbConn, tests::db, users::tests as user_tests, users::tests::fill_database,
};
use assert_json_diff::assert_json_eq; use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::{json, to_value}; use serde_json::{json, to_value};
+11 -12
View File
@@ -2,12 +2,11 @@ use activitystreams::activity::{Announce, Create, Delete, Follow, Like, Undo, Up
use crate::{ use crate::{
comments::Comment, comments::Comment,
db_conn::DbConn,
follows, likes, follows, likes,
posts::{Post, PostUpdate}, posts::{Post, PostUpdate},
reshares::Reshare, reshares::Reshare,
users::User, users::User,
Error, CONFIG, Connection, Error, CONFIG,
}; };
use plume_common::activity_pub::inbox::Inbox; use plume_common::activity_pub::inbox::Inbox;
@@ -46,7 +45,7 @@ impl_into_inbox_result! {
Reshare => Reshared Reshare => Reshared
} }
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> { pub fn inbox(conn: &Connection, act: serde_json::Value) -> Result<InboxResult, Error> {
Inbox::handle(conn, act) Inbox::handle(conn, act)
.with::<User, Announce, Post>(CONFIG.proxy()) .with::<User, Announce, Post>(CONFIG.proxy())
.with::<User, Create, Comment>(CONFIG.proxy()) .with::<User, Create, Comment>(CONFIG.proxy())
@@ -82,9 +81,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(),
@@ -102,7 +101,7 @@ pub(crate) 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,
@@ -190,7 +189,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 +220,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 +248,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(_),
)) ))
@@ -324,7 +323,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 +361,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 +396,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(_),
)) ))
+4 -4
View File
@@ -9,7 +9,7 @@ use crate::{
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use plume_common::utils::md_to_html; use plume_common::utils::{iri_percent_encode_seg, md_to_html};
use std::sync::RwLock; use std::sync::RwLock;
#[derive(Clone, Identifiable, Queryable)] #[derive(Clone, Identifiable, Queryable)]
@@ -173,8 +173,8 @@ impl Instance {
"{instance}/{prefix}/{name}/{box_name}", "{instance}/{prefix}/{name}/{box_name}",
instance = self.public_domain, instance = self.public_domain,
prefix = prefix, prefix = prefix,
name = name, name = iri_percent_encode_seg(name),
box_name = box_name box_name = iri_percent_encode_seg(box_name)
)) ))
} }
@@ -523,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)"
+15 -6
View File
@@ -69,6 +69,8 @@ pub enum Error {
Webfinger, Webfinger,
Expired, Expired,
UserAlreadyExists, UserAlreadyExists,
#[cfg(feature = "s3")]
S3(s3::error::S3Error),
} }
impl From<bcrypt::BcryptError> for Error { impl From<bcrypt::BcryptError> for Error {
@@ -170,6 +172,13 @@ impl From<request::Error> for Error {
} }
} }
#[cfg(feature = "s3")]
impl From<s3::error::S3Error> for Error {
fn from(err: s3::error::S3Error) -> Error {
Error::S3(err)
}
}
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// Adds a function to a model, that returns the first /// Adds a function to a model, that returns the first
@@ -177,7 +186,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);
/// } /// }
@@ -201,7 +210,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);
/// } /// }
@@ -225,7 +234,7 @@ macro_rules! list_by {
/// ///
/// # Usage /// # Usage
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// get!(model_table); /// get!(model_table);
/// } /// }
@@ -248,7 +257,7 @@ macro_rules! get {
/// ///
/// # Usage /// # Usage
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// insert!(model_table, NewModelType); /// insert!(model_table, NewModelType);
/// } /// }
@@ -280,7 +289,7 @@ macro_rules! insert {
/// ///
/// # Usage /// # Usage
/// ///
/// ```rust /// ```ignore
/// impl Model { /// impl Model {
/// last!(model_table); /// last!(model_table);
/// } /// }
@@ -354,7 +363,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())
} }
+14 -14
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, users::User,
users::User, Connection, Error, Result, CONFIG, Connection, Error, Result, CONFIG,
}; };
use activitystreams::{ use activitystreams::{
activity::{ActorAndObjectRef, Like as LikeAct, Undo}, activity::{ActorAndObjectRef, Like as LikeAct, Undo},
@@ -85,11 +85,11 @@ impl Like {
} }
} }
impl AsObject<User, LikeAct, &DbConn> for Post { impl AsObject<User, LikeAct, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = Like; type Output = Like;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Like> { fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Like> {
let res = Like::insert( let res = Like::insert(
conn, conn,
NewLike { NewLike {
@@ -105,15 +105,15 @@ impl AsObject<User, LikeAct, &DbConn> for Post {
} }
} }
impl FromId<DbConn> for Like { impl FromId<Connection> for Like {
type Error = Error; type Error = Error;
type Object = LikeAct; type Object = LikeAct;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Like::find_by_ap_url(conn, id) Like::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, act: LikeAct) -> Result<Self> { fn from_activity(conn: &Connection, act: LikeAct) -> Result<Self> {
let res = Like::insert( let res = Like::insert(
conn, conn,
NewLike { NewLike {
@@ -154,17 +154,17 @@ impl FromId<DbConn> for Like {
} }
} }
impl AsObject<User, Undo, &DbConn> for Like { impl AsObject<User, Undo, &Connection> for Like {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
if actor.id == self.user_id { if actor.id == self.user_id {
diesel::delete(&self).execute(&**conn)?; diesel::delete(&self).execute(conn)?;
// delete associated notification if any // delete associated notification if any
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) { if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
diesel::delete(&notif).execute(&**conn)?; diesel::delete(&notif).execute(conn)?;
} }
Ok(()) Ok(())
} else { } else {
@@ -199,7 +199,7 @@ mod tests {
let (posts, _users, _blogs) = fill_database(&conn); let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0]; let post = &posts[0];
let user = &post.get_authors(&conn)?[0]; let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&*conn, NewLike::new(post, user))?; let like = Like::insert(&conn, NewLike::new(post, user))?;
let act = like.to_activity(&conn).unwrap(); let act = like.to_activity(&conn).unwrap();
let expected = json!({ let expected = json!({
@@ -223,8 +223,8 @@ mod tests {
let (posts, _users, _blogs) = fill_database(&conn); let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0]; let post = &posts[0];
let user = &post.get_authors(&conn)?[0]; let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&*conn, NewLike::new(post, user))?; let like = Like::insert(&conn, NewLike::new(post, user))?;
let act = like.build_undo(&*conn)?; let act = like.build_undo(&conn)?;
let expected = json!({ let expected = json!({
"actor": "https://plu.me/@/admin/", "actor": "https://plu.me/@/admin/",
+23 -1
View File
@@ -297,6 +297,28 @@ impl List {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn delete(&self, conn: &Connection) -> Result<()> {
if let Some(user_id) = self.user_id {
diesel::delete(
lists::table
.filter(lists::user_id.eq(user_id))
.filter(lists::name.eq(&self.name)),
)
.execute(conn)
.map(|_| ())
.map_err(Error::from)
} else {
diesel::delete(
lists::table
.filter(lists::user_id.is_null())
.filter(lists::name.eq(&self.name)),
)
.execute(conn)
.map(|_| ())
.map_err(Error::from)
}
}
func! {set: set_users, User, add_users} func! {set: set_users, User, add_users}
func! {set: set_blogs, Blog, add_blogs} func! {set: set_blogs, Blog, add_blogs}
func! {set: set_words, Word, add_words} func! {set: set_words, Word, add_words}
@@ -413,7 +435,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(())
+187 -40
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias, ap_url, instance::Instance, safe_string::SafeString, schema::medias, users::User, Connection,
users::User, Connection, Error, Result, CONFIG, Error, Result, CONFIG,
}; };
use activitystreams::{object::Image, prelude::*}; use activitystreams::{object::Image, prelude::*};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
@@ -16,6 +16,9 @@ use std::{
use tracing::warn; use tracing::warn;
use url::Url; use url::Url;
#[cfg(feature = "s3")]
use crate::config::S3Config;
const REMOTE_MEDIA_DIRECTORY: &str = "remote"; const REMOTE_MEDIA_DIRECTORY: &str = "remote";
#[derive(Clone, Identifiable, Queryable, AsChangeset)] #[derive(Clone, Identifiable, Queryable, AsChangeset)]
@@ -42,7 +45,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,
@@ -105,7 +108,7 @@ impl Media {
.file_path .file_path
.rsplit_once('.') .rsplit_once('.')
.map(|x| x.1) .map(|x| x.1)
.expect("Media::category: extension error") .unwrap_or("")
.to_lowercase() .to_lowercase()
{ {
"png" | "jpg" | "jpeg" | "gif" | "svg" => MediaCategory::Image, "png" | "jpg" | "jpeg" | "gif" | "svg" => MediaCategory::Image,
@@ -151,26 +154,99 @@ impl Media {
}) })
} }
/// Returns full file path for medias stored in the local media directory.
pub fn local_path(&self) -> Option<PathBuf> {
if self.file_path.is_empty() {
return None;
}
if CONFIG.s3.is_some() {
#[cfg(feature="s3")]
unreachable!("Called Media::local_path() but media are stored on S3");
#[cfg(not(feature="s3"))]
unreachable!();
}
let relative_path = self
.file_path
.trim_start_matches(&CONFIG.media_directory)
.trim_start_matches(path::MAIN_SEPARATOR)
.trim_start_matches("static/media/");
Some(Path::new(&CONFIG.media_directory).join(relative_path))
}
/// Returns the relative URL to access this file, which is also the key at which
/// it is stored in the S3 bucket if we are using S3 storage.
/// Does not start with a '/', it is of the form "static/media/<...>"
pub fn relative_url(&self) -> Option<String> {
if self.file_path.is_empty() {
return None;
}
let relative_path = self
.file_path
.trim_start_matches(&CONFIG.media_directory)
.replace(path::MAIN_SEPARATOR, "/");
let relative_path = relative_path
.trim_start_matches('/')
.trim_start_matches("static/media/");
Some(format!("static/media/{}", relative_path))
}
/// Returns a public URL through which this media file can be accessed
pub fn url(&self) -> Result<String> { pub fn url(&self) -> Result<String> {
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, "/").replacen( let relative_url = self.relative_url().unwrap_or_default();
&CONFIG.media_directory,
"static/media", #[cfg(feature="s3")]
1, if CONFIG.s3.as_ref().map(|x| x.direct_download).unwrap_or(false) {
); // "static/media" from plume::routs::plume_media_files() let s3_url = match CONFIG.s3.as_ref().unwrap() {
S3Config { alias: Some(alias), .. } => {
format!("https://{}/{}", alias, relative_url)
}
S3Config { path_style: true, hostname, bucket, .. } => {
format!("https://{}/{}/{}",
hostname,
bucket,
relative_url
)
}
S3Config { path_style: false, hostname, bucket, .. } => {
format!("https://{}.{}/{}",
bucket,
hostname,
relative_url
)
}
};
return Ok(s3_url);
}
Ok(ap_url(&format!( Ok(ap_url(&format!(
"{}/{}", "{}/{}",
Instance::get_local()?.public_domain, Instance::get_local()?.public_domain,
&file_path relative_url
))) )))
} }
} }
pub fn delete(&self, conn: &Connection) -> Result<()> { pub fn delete(&self, conn: &Connection) -> Result<()> {
if !self.is_remote { if !self.is_remote {
fs::remove_file(self.file_path.as_str())?; if CONFIG.s3.is_some() {
#[cfg(not(feature="s3"))]
unreachable!();
#[cfg(feature = "s3")]
CONFIG.s3.as_ref().unwrap().get_bucket()
.delete_object_blocking(&self.relative_url().ok_or(Error::NotFound)?)?;
} else {
fs::remove_file(self.local_path().ok_or(Error::NotFound)?)?;
}
} }
diesel::delete(self) diesel::delete(self)
.execute(conn) .execute(conn)
@@ -206,27 +282,65 @@ impl Media {
} }
// TODO: merge with save_remote? // TODO: merge with save_remote?
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> { pub fn from_activity(conn: &Connection, image: &Image) -> Result<Media> {
let remote_url = image let remote_url = image
.url() .url()
.and_then(|url| url.to_as_uri()) .and_then(|url| url.to_as_uri())
.ok_or(Error::MissingApProperty)?; .ok_or(Error::MissingApProperty)?;
let path = determine_mirror_file_path(&remote_url);
let parent = path.parent().ok_or(Error::InvalidValue)?;
if !parent.is_dir() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut dest = fs::File::create(path.clone())?; let file_path = if CONFIG.s3.is_some() {
// TODO: conditional GET #[cfg(not(feature="s3"))]
request::get( unreachable!();
remote_url.as_str(),
User::get_sender(),
CONFIG.proxy().cloned(),
)?
.copy_to(&mut dest)?;
Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?) #[cfg(feature = "s3")]
{
use rocket::http::ContentType;
let dest = determine_mirror_s3_path(&remote_url);
let media = request::get(
remote_url.as_str(),
User::get_sender(),
CONFIG.proxy().cloned(),
)?;
let content_type = media
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|x| x.to_str().ok())
.and_then(ContentType::parse_flexible)
.unwrap_or(ContentType::Binary);
let bytes = media.bytes()?;
let bucket = CONFIG.s3.as_ref().unwrap().get_bucket();
bucket.put_object_with_content_type_blocking(
&dest,
&bytes,
&content_type.to_string()
)?;
dest
}
} else {
let path = determine_mirror_file_path(&remote_url);
let parent = path.parent().ok_or(Error::InvalidValue)?;
if !parent.is_dir() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut dest = fs::File::create(path.clone())?;
// TODO: conditional GET
request::get(
remote_url.as_str(),
User::get_sender(),
CONFIG.proxy().cloned(),
)?
.copy_to(&mut dest)?;
path.to_str().ok_or(Error::InvalidValue)?.to_string()
};
Media::find_by_file_path(conn, &file_path)
.and_then(|mut media| { .and_then(|mut media| {
let mut updated = false; let mut updated = false;
@@ -258,7 +372,7 @@ impl Media {
updated = true; updated = true;
} }
if updated { if updated {
diesel::update(&media).set(&media).execute(&**conn)?; diesel::update(&media).set(&media).execute(conn)?;
} }
Ok(media) Ok(media)
}) })
@@ -267,7 +381,7 @@ impl Media {
Media::insert( Media::insert(
conn, conn,
NewMedia { NewMedia {
file_path: path.to_str().ok_or(Error::InvalidValue)?.to_string(), file_path,
alt_text: image alt_text: image
.content() .content()
.and_then(|content| content.to_as_string()) .and_then(|content| content.to_as_string())
@@ -307,12 +421,10 @@ impl Media {
} }
fn determine_mirror_file_path(url: &str) -> PathBuf { fn determine_mirror_file_path(url: &str) -> PathBuf {
let mut file_path = Path::new(&super::CONFIG.media_directory).join(REMOTE_MEDIA_DIRECTORY); let mut file_path = Path::new(&CONFIG.media_directory).join(REMOTE_MEDIA_DIRECTORY);
Url::parse(url)
.map(|url| { match Url::parse(url) {
if !url.has_host() { Ok(url) if url.has_host() => {
return;
}
file_path.push(url.host_str().unwrap()); file_path.push(url.host_str().unwrap());
for segment in url.path_segments().expect("FIXME") { for segment in url.path_segments().expect("FIXME") {
file_path.push(segment); file_path.push(segment);
@@ -320,19 +432,54 @@ fn determine_mirror_file_path(url: &str) -> PathBuf {
// TODO: handle query // TODO: handle query
// HINT: Use characters which must be percent-encoded in path as separator between path and query // HINT: Use characters which must be percent-encoded in path as separator between path and query
// HINT: handle extension // HINT: handle extension
}) }
.unwrap_or_else(|err| { other => {
warn!("Failed to parse url: {} {}", &url, err); if let Err(err) = other {
warn!("Failed to parse url: {} {}", &url, err);
} else {
warn!("Error without a host: {}", &url);
}
let ext = url let ext = url
.rsplit('.') .rsplit('.')
.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(), ext)); file_path.push(format!("{}.{}", GUID::rand(), ext));
}); }
}
file_path file_path
} }
#[cfg(feature="s3")]
fn determine_mirror_s3_path(url: &str) -> String {
match Url::parse(url) {
Ok(url) if url.has_host() => {
format!("static/media/{}/{}/{}",
REMOTE_MEDIA_DIRECTORY,
url.host_str().unwrap(),
url.path().trim_start_matches('/'),
)
}
other => {
if let Err(err) = other {
warn!("Failed to parse url: {} {}", &url, err);
} else {
warn!("Error without a host: {}", &url);
}
let ext = url
.rsplit('.')
.next()
.map(ToOwned::to_owned)
.unwrap_or_else(|| String::from("png"));
format!("static/media/{}/{}.{}",
REMOTE_MEDIA_DIRECTORY,
GUID::rand(),
ext,
)
}
}
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
@@ -343,7 +490,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();
@@ -456,7 +603,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,
+3 -3
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions, comments::Comment, notifications::*, posts::Post, schema::mentions, users::User, Connection,
users::User, Connection, Error, Result, Error, Result,
}; };
use activitystreams::{ use activitystreams::{
base::BaseExt, base::BaseExt,
@@ -60,7 +60,7 @@ impl Mention {
} }
} }
pub fn build_activity(conn: &DbConn, ment: &str) -> Result<link::Mention> { pub fn build_activity(conn: &Connection, ment: &str) -> Result<link::Mention> {
let user = User::find_by_fqn(conn, ment)?; let user = User::find_by_fqn(conn, ment)?;
let mut mention = link::Mention::new(); let mut mention = link::Mention::new();
mention.set_href(user.ap_url.parse::<IriString>()?); mention.set_href(user.ap_url.parse::<IriString>()?);
+5 -5
View File
@@ -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");
+54 -33
View File
@@ -1,7 +1,7 @@
use crate::{ use crate::{
ap_url, blogs::Blog, db_conn::DbConn, instance::Instance, medias::Media, mentions::Mention, ap_url, blogs::Blog, instance::Instance, medias::Media, mentions::Mention, post_authors::*,
post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, Connection, Error,
Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN, PostEvent::*, Result, CONFIG, POST_CHAN,
}; };
use activitystreams::{ use activitystreams::{
activity::{Create, Delete, Update}, activity::{Create, Delete, Update},
@@ -12,7 +12,7 @@ use activitystreams::{
prelude::*, prelude::*,
time::OffsetDateTime, time::OffsetDateTime,
}; };
use chrono::{NaiveDateTime, Utc}; use chrono::{DateTime, NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plume_common::{ use plume_common::{
@@ -134,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)
} }
@@ -255,7 +255,7 @@ impl Post {
ap_url(&format!( ap_url(&format!(
"{}/~/{}/{}/", "{}/~/{}/{}/",
CONFIG.base_url, CONFIG.base_url,
blog.fqn, iri_percent_encode_seg(&blog.fqn),
iri_percent_encode_seg(slug) iri_percent_encode_seg(slug)
)) ))
} }
@@ -373,7 +373,8 @@ impl Post {
}))?; }))?;
article.set_source(source); article.set_source(source);
article.set_published( article.set_published(
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into()) //EZABATU OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos_opt().unwrap().into())
OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.and_utc().timestamp_nanos_opt().unwrap().into())
.expect("OffsetDateTime"), .expect("OffsetDateTime"),
); );
article.set_summary(&*self.subtitle); article.set_summary(&*self.subtitle);
@@ -465,7 +466,7 @@ impl Post {
.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)?;
} }
} }
@@ -488,7 +489,7 @@ impl Post {
.filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string())) .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| {
@@ -525,7 +526,7 @@ impl Post {
.filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string())) .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| {
@@ -615,15 +616,15 @@ impl Post {
} }
} }
impl FromId<DbConn> for Post { impl FromId<Connection> for Post {
type Error = Error; type Error = Error;
type Object = LicensedArticle; type Object = LicensedArticle;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> { fn from_activity(conn: &Connection, article: LicensedArticle) -> Result<Self> {
let license = article.ext_one.license.unwrap_or_default(); let license = article.ext_one.license.unwrap_or_default();
let article = article.inner; let article = article.inner;
@@ -752,12 +753,31 @@ impl FromId<DbConn> for Post {
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: article.published().map(|published| {
let timestamp_secs = published.unix_timestamp();
let timestamp_nanos = published.unix_timestamp_nanos()
- (timestamp_secs as i128) * 1000i128 * 1000i128 * 1000i128;
DateTime::from_timestamp(
timestamp_secs,
timestamp_nanos as u32,
)
.unwrap()
.naive_utc()
}),
/*
EZABATU
creation_date: article.published().map(|published| { creation_date: article.published().map(|published| {
let timestamp_secs = published.unix_timestamp(); let timestamp_secs = published.unix_timestamp();
let timestamp_nanos = published.unix_timestamp_nanos() let timestamp_nanos = published.unix_timestamp_nanos()
- (timestamp_secs as i128) * 1000i128 * 1000i128 * 1000i128; - (timestamp_secs as i128) * 1000i128 * 1000i128 * 1000i128;
NaiveDateTime::from_timestamp(timestamp_secs, timestamp_nanos as u32) NaiveDateTime::from_timestamp_opt(
timestamp_secs,
timestamp_nanos as u32,
)
.unwrap()
}), }),
*/
subtitle: article subtitle: article
.summary() .summary()
.and_then(|summary| summary.to_as_string()) .and_then(|summary| summary.to_as_string())
@@ -817,21 +837,21 @@ impl FromId<DbConn> for Post {
} }
} }
impl AsObject<User, Create, &DbConn> for Post { impl AsObject<User, Create, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = Self; type Output = Self;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self::Output> { fn activity(self, _conn: &Connection, _actor: User, _id: &str) -> Result<Self::Output> {
// TODO: check that _actor is actually one of the author? // TODO: check that _actor is actually one of the author?
Ok(self) Ok(self)
} }
} }
impl AsObject<User, Delete, &DbConn> for Post { impl AsObject<User, Delete, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<Self::Output> { fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<Self::Output> {
let can_delete = self let can_delete = self
.get_authors(conn)? .get_authors(conn)?
.into_iter() .into_iter()
@@ -855,16 +875,16 @@ pub struct PostUpdate {
pub tags: Option<serde_json::Value>, pub tags: Option<serde_json::Value>,
} }
impl FromId<DbConn> for PostUpdate { impl FromId<Connection> for PostUpdate {
type Error = Error; type Error = Error;
type Object = LicensedArticle; type Object = LicensedArticle;
fn from_db(_: &DbConn, _: &str) -> Result<Self> { fn from_db(_: &Connection, _: &str) -> Result<Self> {
// Always fail because we always want to deserialize the AP object // Always fail because we always want to deserialize the AP object
Err(Error::NotFound) Err(Error::NotFound)
} }
fn from_activity(conn: &DbConn, updated: Self::Object) -> Result<Self> { fn from_activity(conn: &Connection, updated: Self::Object) -> Result<Self> {
let mut post_update = PostUpdate { let mut post_update = PostUpdate {
ap_url: updated ap_url: updated
.ap_object_ref() .ap_object_ref()
@@ -919,11 +939,11 @@ impl FromId<DbConn> for PostUpdate {
} }
} }
impl AsObject<User, Update, &DbConn> for PostUpdate { impl AsObject<User, Update, &Connection> for PostUpdate {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
let mut post = let mut post =
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?; Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
@@ -1023,6 +1043,7 @@ impl From<PostEvent> for Arc<Post> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::db_conn::DbConn;
use crate::inbox::{inbox, tests::fill_database, InboxResult}; use crate::inbox::{inbox, tests::fill_database, InboxResult};
use crate::mentions::{Mention, NewMention}; use crate::mentions::{Mention, NewMention};
use crate::safe_string::SafeString; use crate::safe_string::SafeString;
@@ -1036,7 +1057,7 @@ mod tests {
let post = &posts[0]; let post = &posts[0];
let mentioned = &users[1]; let mentioned = &users[1];
let mention = Mention::insert( let mention = Mention::insert(
&conn, conn,
NewMention { NewMention {
mentioned_id: mentioned.id, mentioned_id: mentioned.id,
post_id: Some(post.id), post_id: Some(post.id),
@@ -1044,7 +1065,7 @@ mod tests {
}, },
) )
.unwrap(); .unwrap();
(post.to_owned(), mention.to_owned(), posts, users, blogs) (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,
@@ -1053,9 +1074,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(),
@@ -1072,19 +1093,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"));
@@ -1221,7 +1242,7 @@ mod tests {
let actual = to_value(act)?; let actual = to_value(act)?;
let id = actual["id"].to_string(); let id = actual["id"].to_string();
let (id_pre, id_post) = id.rsplit_once("-").unwrap(); let (id_pre, id_post) = id.rsplit_once('-').unwrap();
assert_eq!(post.ap_url, "https://plu.me/~/BlogName/testing"); assert_eq!(post.ap_url, "https://plu.me/~/BlogName/testing");
assert_eq!( assert_eq!(
id_pre, id_pre,
+6
View File
@@ -45,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);
+14 -14
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares, instance::Instance, notifications::*, posts::Post, schema::reshares, timeline::*, users::User,
timeline::*, users::User, Connection, Error, Result, CONFIG, Connection, Error, Result, CONFIG,
}; };
use activitystreams::{ use activitystreams::{
activity::{ActorAndObjectRef, Announce, Undo}, activity::{ActorAndObjectRef, Announce, Undo},
@@ -113,11 +113,11 @@ impl Reshare {
} }
} }
impl AsObject<User, Announce, &DbConn> for Post { impl AsObject<User, Announce, &Connection> for Post {
type Error = Error; type Error = Error;
type Output = Reshare; type Output = Reshare;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Reshare> { fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Reshare> {
let conn = conn; let conn = conn;
let reshare = Reshare::insert( let reshare = Reshare::insert(
conn, conn,
@@ -134,15 +134,15 @@ impl AsObject<User, Announce, &DbConn> for Post {
} }
} }
impl FromId<DbConn> for Reshare { impl FromId<Connection> for Reshare {
type Error = Error; type Error = Error;
type Object = Announce; type Object = Announce;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Reshare::find_by_ap_url(conn, id) Reshare::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, act: Announce) -> Result<Self> { fn from_activity(conn: &Connection, act: Announce) -> Result<Self> {
let res = Reshare::insert( let res = Reshare::insert(
conn, conn,
NewReshare { NewReshare {
@@ -183,17 +183,17 @@ impl FromId<DbConn> for Reshare {
} }
} }
impl AsObject<User, Undo, &DbConn> for Reshare { impl AsObject<User, Undo, &Connection> for Reshare {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
if actor.id == self.user_id { if actor.id == self.user_id {
diesel::delete(&self).execute(&**conn)?; diesel::delete(&self).execute(conn)?;
// delete associated notification if any // delete associated notification if any
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) { if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
diesel::delete(&notif).execute(&**conn)?; diesel::delete(&notif).execute(conn)?;
} }
Ok(()) Ok(())
@@ -229,7 +229,7 @@ mod test {
let (posts, _users, _blogs) = fill_database(&conn); let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0]; let post = &posts[0];
let user = &post.get_authors(&conn)?[0]; let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?; let reshare = Reshare::insert(&conn, NewReshare::new(post, user))?;
let act = reshare.to_activity(&conn).unwrap(); let act = reshare.to_activity(&conn).unwrap();
let expected = json!({ let expected = json!({
@@ -253,8 +253,8 @@ mod test {
let (posts, _users, _blogs) = fill_database(&conn); let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0]; let post = &posts[0];
let user = &post.get_authors(&conn)?[0]; let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?; let reshare = Reshare::insert(&conn, NewReshare::new(post, user))?;
let act = reshare.build_undo(&*conn)?; let act = reshare.build_undo(&conn)?;
let expected = json!({ let expected = json!({
"actor": "https://plu.me/@/admin/", "actor": "https://plu.me/@/admin/",
+1 -1
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,
+18 -16
View File
@@ -108,7 +108,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 +161,43 @@ 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 blog = NewBlog {
blog.instance_id = instance.id; instance_id: instance.id,
blog.actor_id = random_hex().to_string(); actor_id: random_hex(),
blog.ap_url = random_hex().to_string(); ap_url: random_hex(),
blog.inbox_url = random_hex().to_string(); inbox_url: random_hex(),
blog.outbox_url = random_hex().to_string(); outbox_url: random_hex(),
..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 -10
View File
@@ -93,8 +93,9 @@ 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"))); if let Some(val) = &$self.$date {
$result.push_str(&format!("{}:{} ", stringify!($date), NaiveDate::from_num_days_from_ce_opt(*val as i32).unwrap().format("%Y-%m-%d")));
} }
)* )*
} }
@@ -180,12 +181,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 +207,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
} }
+1 -1
View File
@@ -92,7 +92,7 @@ mod tests {
let (posts, _users, _blogs) = fill_database(conn); let (posts, _users, _blogs) = fill_database(conn);
let post_id = posts[0].id; let post_id = posts[0].id;
let mut ht = Hashtag::new(); let mut ht = Hashtag::new();
ht.set_href(ap_url(&format!("https://plu.me/tag/a_tag")).parse::<IriString>()?); ht.set_href(ap_url("https://plu.me/tag/a_tag").parse::<IriString>()?);
ht.set_name("a_tag".to_string()); ht.set_name("a_tag".to_string());
let tag = Tag::from_activity(conn, &ht, post_id, true)?; let tag = Tag::from_activity(conn, &ht, post_id, true)?;
+132 -124
View File
@@ -1,5 +1,4 @@
use crate::{ use crate::{
db_conn::DbConn,
lists::List, lists::List,
posts::Post, posts::Post,
schema::{posts, timeline, timeline_definition}, schema::{posts, timeline, timeline_definition},
@@ -7,14 +6,13 @@ use crate::{
}; };
use diesel::{self, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops::Deref;
pub(crate) mod query; pub(crate) mod query;
pub use self::query::Kind; pub use self::query::Kind;
use self::query::{QueryError, TimelineQuery}; pub use self::query::{QueryError, TimelineQuery};
#[derive(Clone, Debug, PartialEq, 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,
@@ -220,9 +218,10 @@ impl Timeline {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn add_to_all_timelines(conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<()> { pub fn add_to_all_timelines(conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<()> {
let timelines = timeline_definition::table let timelines = timeline_definition::table
.load::<Self>(conn.deref()) //.load::<Self>(conn.deref())
.load::<Self>(conn)
.map_err(Error::from)?; .map_err(Error::from)?;
for t in timelines { for t in timelines {
@@ -246,7 +245,26 @@ impl Timeline {
Ok(()) Ok(())
} }
pub fn matches(&self, conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<bool> { pub fn remove_post(&self, conn: &Connection, post: &Post) -> Result<bool> {
if self.includes_post(conn, post)? {
return Ok(false);
}
diesel::delete(
timeline::table
.filter(timeline::timeline_id.eq(self.id))
.filter(timeline::post_id.eq(post.id)),
)
.execute(conn)?;
Ok(true)
}
pub fn remove_all_posts(&self, conn: &Connection) -> Result<u64> {
let count = diesel::delete(timeline::table.filter(timeline::timeline_id.eq(self.id)))
.execute(conn)?;
Ok(count as u64)
}
pub fn matches(&self, conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<bool> {
let query = TimelineQuery::parse(&self.query)?; let query = TimelineQuery::parse(&self.query)?;
query.matches(conn, self, post, kind) query.matches(conn, self, post, kind)
} }
@@ -282,73 +300,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(())
}); });
@@ -358,48 +366,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(),
) )
@@ -413,10 +421,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(),
@@ -424,7 +432,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(),
@@ -440,10 +448,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(),
@@ -460,7 +468,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(())
@@ -471,9 +479,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,
@@ -483,11 +491,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"
@@ -496,7 +504,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(),
@@ -512,10 +520,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(),
@@ -533,7 +541,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(())
}); });
@@ -543,17 +551,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(),
@@ -561,7 +569,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(),
@@ -579,7 +587,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(),
@@ -596,13 +604,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);
@@ -614,10 +622,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(),
@@ -634,63 +642,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!(
@@ -699,50 +707,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(())
}); });
@@ -786,10 +794,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(),
@@ -807,61 +815,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(())
}); });
+9 -10
View File
@@ -1,17 +1,16 @@
use crate::{ use crate::{
blogs::Blog, blogs::Blog,
db_conn::DbConn,
lists::{self, ListType}, lists::{self, ListType},
posts::Post, posts::Post,
tags::Tag, tags::Tag,
timeline::Timeline, timeline::Timeline,
users::User, users::User,
Result, Connection, Result,
}; };
use plume_common::activity_pub::inbox::AsActor; use plume_common::activity_pub::inbox::AsActor;
use whatlang::{self, Lang}; use whatlang::{self, Lang};
#[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,
@@ -20,7 +19,7 @@ pub enum QueryError {
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),
@@ -155,7 +154,7 @@ enum TQ<'a> {
impl<'a> TQ<'a> { impl<'a> TQ<'a> {
fn matches( fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
@@ -200,7 +199,7 @@ enum Arg<'a> {
impl<'a> Arg<'a> { impl<'a> Arg<'a> {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
@@ -225,7 +224,7 @@ enum WithList {
impl WithList { impl WithList {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
list: &List<'_>, list: &List<'_>,
@@ -292,7 +291,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 {
@@ -361,7 +360,7 @@ enum Bool {
impl Bool { impl Bool {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
@@ -654,7 +653,7 @@ impl<'a> TimelineQuery<'a> {
pub fn matches( pub fn matches(
&self, &self,
conn: &DbConn, conn: &Connection,
timeline: &Timeline, timeline: &Timeline,
post: &Post, post: &Post,
kind: Kind<'_>, kind: Kind<'_>,
+135 -71
View File
@@ -1,8 +1,8 @@
use crate::{ use crate::{
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, db_conn::DbConn, follows::Follow, ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, comments::Comment, db_conn::DbConn,
instance::*, medias::Media, notifications::Notification, post_authors::PostAuthor, posts::Post, follows::Follow, instance::*, medias::Media, notifications::Notification,
safe_string::SafeString, schema::users, timeline::Timeline, Connection, Error, Result, post_authors::PostAuthor, posts::Post, safe_string::SafeString, schema::users,
UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN, timeline::Timeline, Connection, Error, Result, UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN,
}; };
use activitystreams::{ use activitystreams::{
activity::Delete, activity::Delete,
@@ -15,7 +15,10 @@ use activitystreams::{
prelude::*, prelude::*,
}; };
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl}; use diesel::{
self, BelongingToDsl, BoolExpressionMethods, ExpressionMethods, OptionalExtension, QueryDsl,
RunQueryDsl, TextExpressionMethods,
};
use ldap3::{LdapConn, Scope, SearchEntry}; use ldap3::{LdapConn, Scope, SearchEntry};
use openssl::{ use openssl::{
hash::MessageDigest, hash::MessageDigest,
@@ -165,6 +168,14 @@ impl User {
notif.delete(conn)? notif.delete(conn)?
} }
for comment in Comment::list_by_author(conn, self.id)? {
let delete_activity = comment.build_delete(conn)?;
crate::inbox::inbox(
conn,
serde_json::to_value(&delete_activity).map_err(Error::from)?,
)?;
}
diesel::delete(self) diesel::delete(self)
.execute(conn) .execute(conn)
.map(|_| ()) .map(|_| ())
@@ -186,15 +197,16 @@ impl User {
pub fn count_local(conn: &Connection) -> Result<i64> { pub fn count_local(conn: &Connection) -> Result<i64> {
users::table users::table
.filter(users::instance_id.eq(Instance::get_local()?.id)) .filter(users::instance_id.eq(Instance::get_local()?.id))
.filter(users::role.ne(Role::Instance as i32))
.count() .count()
.get_result(&*conn) .get_result(conn)
.map_err(Error::from) .map_err(Error::from)
} }
pub fn find_by_fqn(conn: &DbConn, fqn: &str) -> Result<User> { pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<User> {
let from_db = users::table let from_db = users::table
.filter(users::fqn.eq(fqn)) .filter(users::fqn.eq(fqn))
.first(&**conn) .first(conn)
.optional()?; .optional()?;
if let Some(from_db) = from_db { if let Some(from_db) = from_db {
Ok(from_db) Ok(from_db)
@@ -203,6 +215,27 @@ impl User {
} }
} }
pub fn search_local_by_name(
conn: &Connection,
name: &str,
(min, max): (i32, i32),
) -> Result<Vec<User>> {
users::table
.filter(users::instance_id.eq(Instance::get_local()?.id))
.filter(users::role.ne(Role::Instance as i32))
// TODO: use `ilike` instead of `like` for PostgreSQL
.filter(
users::username
.like(format!("%{}%", name))
.or(users::display_name.like(format!("%{}%", name))),
)
.order(users::username.asc())
.offset(min.into())
.limit((max - min).into())
.load::<User>(conn)
.map_err(Error::from)
}
/** /**
* TODO: Should create user record with normalized(lowercased) email * TODO: Should create user record with normalized(lowercased) email
*/ */
@@ -219,7 +252,7 @@ impl User {
.map_err(Error::from) .map_err(Error::from)
} }
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> { fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<User> {
let link = resolve(acct.to_owned(), true)? let link = resolve(acct.to_owned(), true)?
.links .links
.into_iter() .into_iter()
@@ -246,20 +279,7 @@ impl User {
fn fetch(url: &str) -> Result<CustomPerson> { fn fetch(url: &str) -> Result<CustomPerson> {
let res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; let res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
let text = &res.text()?; let text = &res.text()?;
// without this workaround, publicKey is not correctly deserialized let json = serde_json::from_str::<CustomPerson>(text)?;
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
let person = serde_json::from_str::<Person>(text)?;
let json = CustomPerson::new(
ApActor::new(
person
.clone()
.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_owned(),
person,
),
ap_sign,
); // FIXME: Don't clone()
Ok(json) Ok(json)
} }
@@ -269,23 +289,13 @@ impl User {
pub fn refetch(&self, conn: &Connection) -> Result<()> { pub fn refetch(&self, conn: &Connection) -> Result<()> {
User::fetch(&self.ap_url.clone()).and_then(|json| { User::fetch(&self.ap_url.clone()).and_then(|json| {
let avatar = Media::save_remote( let avatar = json
conn, .icon()
json.ap_actor_ref() .and_then(|icon| icon.iter().next())
.icon() .and_then(|i| i.clone().extend::<Image, ImageType>().ok())
.ok_or(Error::MissingApProperty)? // FIXME: Fails when icon is not set .and_then(|image| image)
.iter() .and_then(|image| image.id_unchecked().map(|url| url.to_string()))
.next() .and_then(|url| Media::save_remote(conn, url, self).ok());
.and_then(|i| {
i.clone()
.extend::<Image, ImageType>() // FIXME: Don't clone()
.ok()?
.and_then(|url| Some(url.id_unchecked()?.to_string()))
})
.ok_or(Error::MissingApProperty)?,
self,
)
.ok();
let pub_key = &json.ext_one.public_key.public_key_pem; let pub_key = &json.ext_one.public_key.public_key_pem;
diesel::update(self) diesel::update(self)
@@ -332,6 +342,42 @@ impl User {
bcrypt::hash(pass, 10).map_err(Error::from) bcrypt::hash(pass, 10).map_err(Error::from)
} }
// [[ LDAP non anonymous bind copied from ....
fn ldap_preconn(ldap_conn: &mut LdapConn) -> Result<()> {
let ldap = CONFIG.ldap.as_ref().unwrap();
if let Some((user, password)) = ldap.user.as_ref() {
let bind = ldap_conn
.simple_bind(user, password)
.map_err(|_| Error::NotFound)?;
if bind.success().is_err() {
return Err(Error::NotFound);
}
}
Ok(())
}
fn search_dn(ldap_conn: &mut LdapConn, fil: &str) -> Result<String> {
let mut cn: String = String::new();
let ldap = CONFIG.ldap.as_ref().unwrap();
let (rs, _res) = ldap_conn.search(
&ldap.base_dn,
Scope::Subtree,
fil,
vec!["cn"]
)
.map_err(|_| Error::NotFound)?
.success()
.map_err(|_| Error::NotFound)?;
for entry in rs {
println!("{:?}", SearchEntry::construct(entry.clone()).dn);
cn = SearchEntry::construct(entry).dn;
}
Ok(cn.to_owned())
}
fn ldap_register(conn: &Connection, name: &str, password: &str) -> Result<User> { fn ldap_register(conn: &Connection, name: &str, password: &str) -> Result<User> {
if CONFIG.ldap.is_none() { if CONFIG.ldap.is_none() {
return Err(Error::NotFound); return Err(Error::NotFound);
@@ -339,7 +385,12 @@ impl User {
let ldap = CONFIG.ldap.as_ref().unwrap(); let ldap = CONFIG.ldap.as_ref().unwrap();
let mut ldap_conn = LdapConn::new(&ldap.addr).map_err(|_| Error::NotFound)?; let mut ldap_conn = LdapConn::new(&ldap.addr).map_err(|_| Error::NotFound)?;
let ldap_name = format!("{}={},{}", ldap.user_name_attr, name, ldap.base_dn);
User::ldap_preconn(&mut ldap_conn)?;
let _filter = format!("(&(objectClass=*)(uid={}))", name).to_owned();
let ldap_name = User::search_dn(&mut ldap_conn, &_filter).unwrap();
//let ldap_name = format!("{}={},{}", ldap.user_name_attr, name, ldap.base_dn);
let bind = ldap_conn let bind = ldap_conn
.simple_bind(&ldap_name, password) .simple_bind(&ldap_name, password)
.map_err(|_| Error::NotFound)?; .map_err(|_| Error::NotFound)?;
@@ -385,10 +436,18 @@ impl User {
} else { } else {
return false; return false;
}; };
if User::ldap_preconn(&mut conn).is_err() {
return false;
}
//
let _filter = format!("(&(objectClass=*)(uid={}))", &self.username).to_owned();
let name = User::search_dn(&mut conn, &_filter).unwrap();
/*
let name = format!( let name = format!(
"{}={},{}", "{}={},{}",
ldap.user_name_attr, &self.username, ldap.base_dn ldap.user_name_attr, &self.username, ldap.base_dn
); );
*/
if let Ok(bind) = conn.simple_bind(&name, password) { if let Ok(bind) = conn.simple_bind(&name, password) {
bind.success().is_ok() bind.success().is_ok()
} else { } else {
@@ -443,7 +502,7 @@ impl User {
} }
} }
} }
// ... ldap-non-anon PR https://git.joinplu.me/Plume/Plume/src/branch/ldap-non-anon ]].
pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> { pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> {
diesel::update(self) diesel::update(self)
.set(users::hashed_password.eq(User::hash_pass(pass)?)) .set(users::hashed_password.eq(User::hash_pass(pass)?))
@@ -454,6 +513,7 @@ impl User {
pub fn get_local_page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<User>> { pub fn get_local_page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<User>> {
users::table users::table
.filter(users::instance_id.eq(Instance::get_local()?.id)) .filter(users::instance_id.eq(Instance::get_local()?.id))
.filter(users::role.ne(Role::Instance as i32))
.order(users::username.asc()) .order(users::username.asc())
.offset(min.into()) .offset(min.into())
.limit((max - min).into()) .limit((max - min).into())
@@ -931,7 +991,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
.cookies() .cookies()
.get_private(AUTH_COOKIE) .get_private(AUTH_COOKIE)
.and_then(|cookie| cookie.value().parse().ok()) .and_then(|cookie| cookie.value().parse().ok())
.and_then(|id| User::get(&*conn, id).ok()) .and_then(|id| User::get(&conn, id).ok())
.or_forward(()) .or_forward(())
} }
} }
@@ -944,15 +1004,15 @@ impl IntoId for User {
impl Eq for User {} impl Eq for User {}
impl FromId<DbConn> for User { impl FromId<Connection> for User {
type Error = Error; type Error = Error;
type Object = CustomPerson; type Object = CustomPerson;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &Connection, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> { fn from_activity(conn: &Connection, acct: CustomPerson) -> Result<Self> {
let actor = acct.ap_actor_ref(); let actor = acct.ap_actor_ref();
let username = actor let username = actor
.preferred_username() .preferred_username()
@@ -960,6 +1020,10 @@ impl FromId<DbConn> for User {
.to_string(); .to_string();
if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) { if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
tracing::error!(
"preferredUsername includes invalid character(s): {}",
&username
);
return Err(Error::InvalidValue); return Err(Error::InvalidValue);
} }
@@ -1049,7 +1113,7 @@ impl FromId<DbConn> for User {
} }
} }
impl AsActor<&DbConn> for User { impl AsActor<&Connection> for User {
fn get_inbox_url(&self) -> String { fn get_inbox_url(&self) -> String {
self.inbox_url.clone() self.inbox_url.clone()
} }
@@ -1065,11 +1129,11 @@ impl AsActor<&DbConn> for User {
} }
} }
impl AsObject<User, Delete, &DbConn> for User { impl AsObject<User, Delete, &Connection> for User {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
if self.id == actor.id { if self.id == actor.id {
self.delete(conn).map(|_| ()) self.delete(conn).map(|_| ())
} else { } else {
@@ -1232,7 +1296,7 @@ pub(crate) mod tests {
) )
.unwrap(); .unwrap();
other.avatar_id = Some(avatar.id); other.avatar_id = Some(avatar.id);
let other = other.save_changes::<User>(&*conn).unwrap(); let other = other.save_changes::<User>(conn).unwrap();
vec![admin, user, other] vec![admin, user, other]
} }
@@ -1335,11 +1399,11 @@ pub(crate) mod tests {
fn delete() { fn delete() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let inserted = fill_database(&conn); let inserted = fill_database(conn);
assert!(User::get(&conn, inserted[0].id).is_ok()); assert!(User::get(conn, inserted[0].id).is_ok());
inserted[0].delete(&conn).unwrap(); inserted[0].delete(conn).unwrap();
assert!(User::get(&conn, inserted[0].id).is_err()); assert!(User::get(conn, inserted[0].id).is_err());
Ok(()) Ok(())
}); });
} }
@@ -1348,20 +1412,20 @@ pub(crate) mod tests {
fn admin() { fn admin() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let inserted = fill_database(&conn); let inserted = fill_database(conn);
let local_inst = Instance::get_local().unwrap(); let local_inst = Instance::get_local().unwrap();
let mut i = 0; let mut i = 0;
while local_inst.has_admin(&conn).unwrap() { while local_inst.has_admin(conn).unwrap() {
assert!(i < 100); //prevent from looping indefinitelly assert!(i < 100); //prevent from looping indefinitelly
local_inst local_inst
.main_admin(&conn) .main_admin(conn)
.unwrap() .unwrap()
.set_role(&conn, Role::Normal) .set_role(conn, Role::Normal)
.unwrap(); .unwrap();
i += 1; i += 1;
} }
inserted[0].set_role(&conn, Role::Admin).unwrap(); inserted[0].set_role(conn, Role::Admin).unwrap();
assert_eq!(inserted[0].id, local_inst.main_admin(&conn).unwrap().id); assert_eq!(inserted[0].id, local_inst.main_admin(conn).unwrap().id);
Ok(()) Ok(())
}); });
} }
@@ -1370,9 +1434,9 @@ pub(crate) mod tests {
fn auth() { fn auth() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
fill_database(&conn); fill_database(conn);
let test_user = NewUser::new_local( let test_user = NewUser::new_local(
&conn, conn,
"test".to_owned(), "test".to_owned(),
"test user".to_owned(), "test user".to_owned(),
Role::Normal, Role::Normal,
@@ -1383,10 +1447,10 @@ pub(crate) mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
User::login(&conn, "test", "test_password").unwrap().id, User::login(conn, "test", "test_password").unwrap().id,
test_user.id test_user.id
); );
assert!(User::login(&conn, "test", "other_password").is_err()); assert!(User::login(conn, "test", "other_password").is_err());
Ok(()) Ok(())
}); });
} }
@@ -1395,26 +1459,26 @@ pub(crate) mod tests {
fn get_local_page() { fn get_local_page() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
fill_database(&conn); fill_database(conn);
let page = User::get_local_page(&conn, (0, 2)).unwrap(); let page = User::get_local_page(conn, (0, 2)).unwrap();
assert_eq!(page.len(), 2); assert_eq!(page.len(), 2);
assert!(page[0].username <= page[1].username); assert!(page[0].username <= page[1].username);
let mut last_username = User::get_local_page(&conn, (0, 1)).unwrap()[0] let mut last_username = User::get_local_page(conn, (0, 1)).unwrap()[0]
.username .username
.clone(); .clone();
for i in 1..User::count_local(&conn).unwrap() as i32 { for i in 1..User::count_local(conn).unwrap() as i32 {
let page = User::get_local_page(&conn, (i, i + 1)).unwrap(); let page = User::get_local_page(conn, (i, i + 1)).unwrap();
assert_eq!(page.len(), 1); assert_eq!(page.len(), 1);
assert!(last_username <= page[0].username); assert!(last_username <= page[0].username);
last_username = page[0].username.clone(); last_username = page[0].username.clone();
} }
assert_eq!( assert_eq!(
User::get_local_page(&conn, (0, User::count_local(&conn).unwrap() as i32 + 10)) User::get_local_page(conn, (0, User::count_local(conn).unwrap() as i32 + 10))
.unwrap() .unwrap()
.len() as i64, .len() as i64,
User::count_local(&conn).unwrap() User::count_local(conn).unwrap()
); );
Ok(()) Ok(())
}); });
+3 -6
View File
@@ -1,7 +1,7 @@
msgid ""
msgid "Do you want to load the local autosave last edited at {}?"
msgstr "" msgstr ""
"Project-Id-Version: plume-front\n" \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: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
@@ -12,9 +12,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
msgid "Do you want to load the local autosave last edited at {}?"
msgstr ""
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
-1016
View File
File diff suppressed because it is too large Load Diff
+613 -692
View File
File diff suppressed because it is too large Load Diff
+729 -641
View File
File diff suppressed because it is too large Load Diff
+716 -643
View File
File diff suppressed because it is too large Load Diff
+672 -662
View File
File diff suppressed because it is too large Load Diff
-1088
View File
File diff suppressed because it is too large Load Diff
-1016
View File
File diff suppressed because it is too large Load Diff
+740 -632
View File
File diff suppressed because it is too large Load Diff
-1016
View File
File diff suppressed because it is too large Load Diff
-1016
View File
File diff suppressed because it is too large Load Diff
+259 -860
View File
File diff suppressed because it is too large Load Diff
+739 -632
View File
File diff suppressed because it is too large Load Diff
+730 -632
View File
File diff suppressed because it is too large Load Diff
+732 -632
View File
File diff suppressed because it is too large Load Diff
+129 -918
View File
File diff suppressed because it is too large Load Diff
+741 -632
View File
File diff suppressed because it is too large Load Diff
+730 -632
View File
File diff suppressed because it is too large Load Diff
+2 -1023
View File
File diff suppressed because it is too large Load Diff
+333 -829
View File
File diff suppressed because it is too large Load Diff
+85 -970
View File
File diff suppressed because it is too large Load Diff
-1016
View File
File diff suppressed because it is too large Load Diff
+720 -643
View File
File diff suppressed because it is too large Load Diff
+721 -631
View File
File diff suppressed because it is too large Load Diff
-1013
View File
File diff suppressed because it is too large Load Diff
+415 -1003
View File
File diff suppressed because it is too large Load Diff
+717 -641
View File
File diff suppressed because it is too large Load Diff
+500 -744
View File
File diff suppressed because it is too large Load Diff

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