Compare commits

..

46 Commits

Author SHA1 Message Date
Mina Galić b596e77f03 remove redundant use statements 2020-05-25 20:00:57 +02:00
Mina Galić 41f97b01f0 unroll filter_map() to easier .await 2020-05-25 20:00:28 +02:00
Mina Galić a508a4150c remove redundant use statements 2020-05-25 19:18:24 +02:00
Mina Galić 25c40adf20 yet another dubious clippy warnings fix re returns 2020-05-25 19:17:32 +02:00
Mina Galić 7490567a21 fix warnings about unused doc comments
we do this by making the macro parse and generate doc comments
2020-05-25 15:44:32 +02:00
Mina Galić 492bbb1ba6 make clippy happy with a weird quirk wrt return
not happy about this lint or rust behaviour where the return statement
must not have a `;`
2020-05-25 15:23:55 +02:00
Mina Galić cf3708e1c6 make clippy happy by removing unused imports 2020-05-25 13:40:35 +02:00
Mina Galić df442002c2 replace .map().map_err() with a match
so we can .await without worries.
plus, it changes nothing about the code, other than making the intent
clearer at first sight.
2020-05-24 22:16:42 +02:00
Mina Galić 07036b5fad upgrade validator: it now uses types! in macros!! 2020-05-24 22:03:26 +02:00
Mina Galić 0726375859 add another async (and correctly convert followers_count) 2020-05-24 21:03:59 +02:00
Mina Galić cb1c260692 remove an experiment of disabling Send… it makes no sense 2020-05-24 21:03:02 +02:00
Mina Galić de6bfca084 removed a few unused imports 2020-05-24 21:01:16 +02:00
Mina Galić 7aabb9661e upgrade webfinger everywhere, and implement async 2020-05-24 20:27:39 +02:00
Mina Galić 18bb413011 add async/.await until all our errors are the same:
that our Connection is not Send-safe.
2020-05-24 19:40:02 +02:00
Mina Galić d2881ee3f7 add async/.await until all our errors are the same: that our Connection is not Send-safe. 2020-05-23 23:32:10 +02:00
Mina Galić 850b3c1337 add async/.await until all our errors are the same:
that our Connection is not Send-safe.
once we get there, we can start thinking about restructing the way we
pass along our connection, or consider using #[database].
2020-05-22 21:41:33 +02:00
Igor Galić 44ebce516c fix some, break some compiling by adding async/await in front of it
i forgot that we can't `Send` diesel connections over threads, so this
is going to require some rethinking.
2020-05-16 12:06:58 +02:00
Igor Galić 3c830ab0ce move towards using #[rocket::async_trait]
this also upgrades some dependencies
some of that fixes stuff, others breaks stuff.
2020-05-15 22:38:21 +02:00
Igor Galić 097d0ea9ce make plume-models async (again) 2020-05-06 22:33:10 +02:00
Igor Galić 6fe16c9f84 upgrade and use futures… then block_on .await in a trait? 2020-05-06 22:32:59 +02:00
Igor Galić 43cb9f700c update webfinger
as requested in https://github.com/Plume-org/webfinger/issues/8
and provided in https://github.com/Plume-org/webfinger/pull/9

```
meena@76ix ~/s/a/plume (go/async) [101]> cargo update --package webfinger
    Updating crates.io index
    Updating git repository `https://github.com/Plume-org/webfinger`
    Removing bytes v0.4.12
    Removing cookie v0.12.0
    Removing cookie_store v0.7.0
    Removing crossbeam-deque v0.7.2
    Removing crossbeam-epoch v0.8.0
    Removing crossbeam-queue v0.1.2
    Removing crossbeam-utils v0.7.0
    Removing h2 v0.1.26
    Removing http v0.1.21
    Removing http-body v0.1.0
    Removing hyper v0.12.35
    Removing hyper-tls v0.3.2
    Removing publicsuffix v1.5.4
    Removing reqwest v0.9.24
    Removing serde_urlencoded v0.5.5
    Removing string v0.2.1
    Removing tokio v0.1.22
    Removing tokio-buf v0.1.1
    Removing tokio-current-thread v0.1.6
    Removing tokio-executor v0.1.9
    Removing tokio-io v0.1.12
    Removing tokio-reactor v0.1.11
    Removing tokio-sync v0.1.7
    Removing tokio-tcp v0.1.3
    Removing tokio-threadpool v0.1.17
    Removing tokio-timer v0.2.12
    Removing try_from v0.3.2
    Removing want v0.2.0
      Adding webfinger v0.5.0 (https://github.com/Plume-org/webfinger?rev=update-deps#cdaab95e)
    Removing webfinger v0.5.0
meena@76ix ~/s/a/plume (go/async)>
```
2020-05-06 22:30:33 +02:00
Igor Galić 2c285b9aca start async-ifying routes
template utils and error routes
2020-05-06 22:29:36 +02:00
Igor Galić e4bb73d22e cargo clippy 2020-05-06 22:29:36 +02:00
Igor Galić e9c7259ffb cargo fmt 2020-05-06 22:29:35 +02:00
Igor Galić be8c67ee9a move reqwest client out of thread spawning
this way, we only spawn one, and reuse that.
2020-05-06 22:29:34 +02:00
Igor Galić 65b2c38c29 .await? result from read_to_string() 2020-05-06 22:29:33 +02:00
Jeb Rosen 8aa99cea35 move signature outside the spawning
this allows us to actually move stuff into the async block
and we can drop the 'static life-time.
2020-05-06 22:29:32 +02:00
Igor Galić a010025074 asyncify reqwest calls (again?) 2020-05-06 22:29:32 +02:00
Igor Galić 82088596a8 asyncify from_activity calls (i.e.: block_on()) 2020-05-06 22:29:31 +02:00
Igor Galić 87ce3a7b51 asyncify plume-models: media upload is now async
including the use of tokio!
2020-05-06 22:29:27 +02:00
Igor Galić 3472a58299 move ClientBuilder into thread, since we cannot Copy it 2020-05-06 22:26:37 +02:00
Igor Galić a3f165f9f4 Use blocking reqwest API in defer
defer, or, trait functions such as it in general(?) cannot be async (yet)
2020-05-06 22:26:32 +02:00
Igor Galić 25c5da1a7c add tokio (0.2) as dependency to further async-ify our FromData code
i'm using this opportunity to also update reqwest (0.10), but it's
turning out to be a little trickier, as it requires more modern async
setup, and that appears to need a lot of thinking…
2020-05-06 22:23:35 +02:00
Igor Galić 022e037eea when using macros!() we need to import the things that they use 2020-05-06 22:20:49 +02:00
Igor Galić 45c335e17b "manually" create ETag and Cache-Control headers 2020-05-06 22:20:48 +02:00
Igor Galić b51551973a start fixing tests in plume-models 2020-05-06 22:20:47 +02:00
Igor Galić 59e5c49aa8 convert plume-models to all async
where sensible! note that State has no asynchronous work to do, so
wrapping it up in async makes no sense.
2020-05-06 22:20:47 +02:00
Igor Galić ce119ffe50 start making PlumeRocket async 2020-05-06 22:20:46 +02:00
Igor Galić 944f8c42fa plume-models: convert api-tokens. use DbConn::from_request() directly
there doesn't seem to be a request.guard_async (yet?)
2020-05-06 22:17:25 +02:00
Igor Galić 909f677bdd plume-models: convert admin & api-tokens to async
n.b.: I do *not* like the error handling in api_tokens 😒
2020-05-06 22:17:24 +02:00
Igor Galić fd9764ff17 plume-common: also make requests async 2020-05-06 22:17:24 +02:00
Igor Galić 75722abc9e rocket does not need decl_macro anymore 2020-05-06 22:17:23 +02:00
Igor Galić ec9b699c6e convert plume-common to rocket async
it only took 3 hours of @jebrosen's patient help.
2020-05-06 22:17:22 +02:00
Igor Galić bb5c2b69a7 update rocket* everywhere!
and run cargo update
2020-05-06 22:17:17 +02:00
Igor Galić e52944e477 update rocket*; which gets us stuck in dependency conflicts 2020-05-06 21:01:25 +02:00
Igor Galić 928470610e remove csrf for now, so we can update the rest 2020-05-06 21:01:18 +02:00
216 changed files with 22390 additions and 29456 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ executors:
type: boolean type: boolean
default: false default: false
docker: docker:
- image: plumeorg/plume-buildenv:v0.3.0 - image: plumeorg/plume-buildenv:v0.0.9
- image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>> - image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
+5 -2
View File
@@ -1,3 +1,6 @@
localhost { localhost:443 {
reverse_proxy localhost:7878 proxy / localhost:7878 {
transparent
}
tls self_signed
} }
+5 -7
View File
@@ -1,17 +1,14 @@
FROM debian:buster-20201117 FROM debian:stretch-20190326
ENV PATH="/root/.cargo/bin:${PATH}" ENV PATH="/root/.cargo/bin:${PATH}"
#install native/circleci/build dependancies #install native/circleci/build dependancies
RUN apt update &&\ 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/ /" \ 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&&\
| tee -a /etc/apt/sources.list.d/caddy-fury.list &&\
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&&\
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
#install and configure rust #install and configure rust
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2020-12-07 -y &&\ RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2020-01-15 -y &&\
rustup component add rustfmt clippy &&\ rustup component add rustfmt clippy &&\
rustup component add rust-std --target wasm32-unknown-unknown rustup component add rust-std --target wasm32-unknown-unknown
@@ -27,7 +24,8 @@ COPY cargo_config /root/.cargo/config
#install selenium for front end tests #install selenium for front end tests
RUN pip3 install selenium RUN pip3 install selenium
#configure caddy #install and configure caddy
RUN curl https://getcaddy.com | bash -s personal
COPY Caddyfile /Caddyfile COPY Caddyfile /Caddyfile
#install crowdin #install crowdin
-9
View File
@@ -45,12 +45,3 @@ ROCKET_ADDRESS=127.0.0.1
#PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png #PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png
#PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.png #PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.png
#PLUME_LOGO_512=icons/trwnh/paragraphs/plumeParagraphs512.png #PLUME_LOGO_512=icons/trwnh/paragraphs/plumeParagraphs512.png
## LDAP CONFIG ##
# the object that will be bound is "${USER_NAME_ATTR}=${username},${BASE_DN}"
#LDAP_ADDR=ldap://127.0.0.1:1389
#LDAP_BASE_DN="ou=users,dc=your-org,dc=eu"
#LDAP_USER_NAME_ATTR=cn
#LDAP_USER_MAIL_ATTR=mail
#LDAP_TLS=false
-10
View File
@@ -7,16 +7,6 @@ assignees: ''
--- ---
<!--
We would appreciated if you report a bug at our Gitea instance's issue page:
https://git.joinplu.me/Plume/Plume/issues
You can login to the Gitea with your GitHub account.
We welcome to receive bug reports here, GitHub, too.
-->
<!-- Describe your bug, explaining how to reproduce it, and what was expected --> <!-- Describe your bug, explaining how to reproduce it, and what was expected -->
@@ -7,15 +7,6 @@ assignees: ''
--- ---
<!--
We would appreciated if you request a feature at our Gitea instance's issue page:
https://git.joinplu.me/Plume/Plume/issues
You can login to the Gitea with your GitHub account.
We welcome to receive feature requests here, GitHub, too.
-->
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-7
View File
@@ -1,7 +0,0 @@
<!--
We would appreciated if you report a bug at our Gitea instance's pull request page:
https://git.joinplu.me/Plume/Plume/pulls
You can login to the Gitea with your GitHub account.
We welcome to receive pull requests here, GitHub, too.
-->
-191
View File
@@ -1,191 +0,0 @@
# Changelog
<!-- next-header -->
## [Unreleased] - ReleaseDate
## [[0.6.0]] - 2020-12-29
### Added
- Vazir font for better support of languages written in Arabic script (#787)
- Login via LDAP (#826)
- cargo-release (#835)
- Care about weak ETag header for better caching (#840)
- Support for right to left languages in post content (#853)
### Changed
- Bump Docker base images to Buster flavor (#797)
- Upgrade Rocket to 0.4.5 (#800)
- Keep tags as-is (#832)
- Update Docker image for testing (#838)
- Update Dockerfile.dev (#841)
### Fixed
- Recreate search index if its format is outdated (#802)
- Make it possible to switch to rich text editor (#808)
- Fix margins for the mobile devices (#817)
- GPU acceleration for the mobile menu (#818)
- Natural title position for RtoL languages (#825)
- Remove link to unimplemented page (#827)
- Fix displaying not found page when submitting a duplicated blocklist email (#831)
### Security
- Validate spoofing of Create activity
## [0.5.0] - 2020-06-21
### Added
- Email blocklisting (#718)
- Syntax highlighting (#691)
- Persian localization (#782)
- Switchable tokenizer - enables Japanese full-text search (#776)
- Make database connections configurable by environment variables (#768)
### Changed
- Display likes and boost on post cards (#744)
- Rust 2018 (#726)
- Bump to LLVM to 9.0.0 to fix ARM builds (#737)
- Remove dependency on runtime-fmt (#773)
- Drop the -alpha suffix in release names, it is implied that Plume is not stable yet because of the 0 major version (Plume 1.0.0 will be the first stable release).
### Fixed
- Fix parsing of mentions inside a Markdown code block (be430c6)
- Fix RSS issues (#720)
- Fix Atom feed (#764)
- Fix default theme (#746)
- Fix shown password on remote interact pages (#741)
- Allow unicode hashtags (#757)
- Fix French grammar for for 0 (#760)
- Don't show boosts and likes for "all" and "local" in timelines (#781)
- Fix liking and boosting posts on remote instances (#762)
## [0.4.0] - 2019-12-23
### Added
- Add support for generic timeline (#525)
- Federate user deletion (#551)
- import migrations and don't require diesel_cli for admins (#555)
- Cache local instance (#572)
- Initial RTL support #575 (#577)
- Confirm deletion of blog (#602)
- Make a distinction between moderators and admins (#619)
- Theming (#624)
- Add clap to plume in order to print help and version (#631)
- Add Snapcraft metadata and install/maintenance hooks (#666)
- Add environmental variable to control path of media (#683)
- Add autosaving to the editor (#688)
- CI: Upload artifacts to pull request deploy environment (#539)
- CI: Upload artifact of wasm binary (#571)
### Changed
- Update follow_remote.rs.html grammar (#548)
- Add some feedback when performing some actions (#552)
- Theme update (#553)
- Remove the new index lock tantivy uses (#556)
- Reduce reqwest timeout to 5s (#557)
- Improve notification management (#561)
- Fix occurrences of 'have been' to 'has been' (#578) + Direct follow-up to #578 (#603)
- Store password reset requests in database (#610)
- Use futures and tokio to send activities (#620)
- Don't ignore dotenv errors (#630)
- Replace the input! macro with an Input builder (#646)
- Update default license (#659)
- Paginate the outbox responses. Fixes #669 (#681)
- Use the "classic" editor by default (#697)
- Fix issue #705 (#708)
- Make comments in styleshhets a bit clearer (#545)
- Rewrite circleci config (#558)
- Use openssl instead of sha256sum for build.rs (#568)
- Update dependencies (#574)
- Refactor code to use Shrinkwraprs and diesel-derive-newtype (#598)
- Add enum containing all successful route returns (#614)
- Update dependencies which depended on nix -- fixes arm32 builds (#615)
- Update some documents (#616)
- Update dependencies (#643)
- Make the comment syntax consistent across all CSS (#487)
### Fixed
- Remove r (#535)
- Fix certain improper rendering of forms (#560)
- make hashtags work in profile summary (#562)
- Fix some federation issue (#573)
- Prevent comment form submit button distortion on iOS (#592)
- Update textarea overflow to scroll (#609)
- Fix arm builds (#612)
- Fix theme caching (#647)
- Fix issue #642, frontend not in English if the user language does not exist (#648)
- Don't index drafts (#656)
- Fill entirely user on creation (#657)
- Delete notification on user deletion (#658)
- Order media so that latest added are top (#660)
- Fix logo URL (#664)
- Snap: Ensure cargo-web doesn't erroneously adopt our workspace. (#667)
- Snap: Another fix for building (#668)
- Snap: Fix build for non-Tier-1 Rust platforms (#672)
- Don't split sentences for translations (#677)
- Escape href quotation marks (#678)
- Re-add empty strings in translation (#682)
- Make the search index creation during migration respect SEARCH_INDEX (#689)
- Fix the navigation menu not opening on touch (#690)
- Make search items optional (#693)
- Various snap fixes (#698)
- Fix #637 : Markdown footnotes (#700)
- Fix lettre (#706)
- CI: Fix Crowdin upload (#576)
### Removed
- Remove the Canapi dependency (#540)
- Remove use of Rust in migrations (#704)
## [0.3.0] - 2019-04-19
### Added
- Cover for articles (#299, #387)
- Password reset (#448)
- New editor (#293, #458, #482, #483, #486, #530)
- Search (#324, #375, #445)
- Edit blogs (#460, #494, #497)
- Hashtags in articles (#283, #295)
- API endpoints (#245, #285, #307)
- A bunch of new translations! (#479, #501, #506, #510, #512, #514)
### Changed
- Federation improvements (#216, #217, #357, #364, #399, #443, #446, #455, #502, #519)
- Improved build process (#281, #374, #392, #402, #489, #498, #503, #511, #513, #515, #528)
### Fixes
- UI usability fixes (#370, #386, #401, #417, #418, #444, #452, #480, #516, #518, #522, #532)
## [0.2.0] - 2018-09-12
### Added
- Article publishing, or save as a draft
- Like, or boost an article
- Basic Markdown editor
- Federated commenting system
- User account creation
- Limited federation on other platforms and subscribing to users
- Ability to create multiple blogs
<!-- next-url -->
[Unreleased]: https://github.com/Plume-org/Plume/compare/0.6.0...HEAD
[[0.6.0]]: https://github.com/Plume-org/Plume/compare/0.5.0...0.6.0
[0.5.0]: https://github.com/Plume-org/Plume/compare/0.4.0-alpha-4...0.5.0
[0.4.0]: https://github.com/Plume-org/Plume/compare/0.3.0-alpha-2...0.4.0-alpha-4
[0.3.0]: https://github.com/Plume-org/Plume/compare/0.2.0-alpha-1...0.3.0-alpha-2
[0.2.0]: https://github.com/Plume-org/Plume/releases/tag/0.2.0-alpha-1
Generated
+1781 -2586
View File
File diff suppressed because it is too large Load Diff
+13 -11
View File
@@ -1,13 +1,14 @@
[package] [package]
authors = ["Plume contributors"] authors = ["Plume contributors"]
name = "plume" name = "plume"
version = "0.6.0" version = "0.4.0"
repository = "https://github.com/Plume-org/Plume" repository = "https://github.com/Plume-org/Plume"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.3" activitypub = "0.1.3"
askama_escape = "0.1" askama_escape = "0.1"
async-trait = "*"
atom_syndication = "0.6" atom_syndication = "0.6"
clap = "2.33" clap = "2.33"
colored = "1.8" colored = "1.8"
@@ -20,9 +21,8 @@ heck = "0.3.0"
lettre = "0.9.2" lettre = "0.9.2"
lettre_email = "0.9.2" lettre_email = "0.9.2"
num_cpus = "1.10" num_cpus = "1.10"
rocket = "0.4.5" rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
rocket_contrib = { version = "0.4.5", features = ["json"] } rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" , features = ["json"] }
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
rpassword = "4.0" rpassword = "4.0"
scheduled-thread-pool = "0.2.2" scheduled-thread-pool = "0.2.2"
serde = "1.0" serde = "1.0"
@@ -30,9 +30,10 @@ serde_json = "1.0"
serde_qs = "0.5" serde_qs = "0.5"
shrinkwraprs = "0.2.1" shrinkwraprs = "0.2.1"
syntect = "3.3" syntect = "3.3"
validator = "0.8" tokio = "0.2"
validator_derive = "0.8" validator = "0.10"
webfinger = "0.4.1" validator_derive = "0.10"
webfinger = { git = "https://github.com/Plume-org/webfinger", rev = "4e8f12810c4a7ba7a07bbcb722cd265fdff512b6", features = ["async"] }
[[bin]] [[bin]]
name = "plume" name = "plume"
@@ -64,9 +65,11 @@ path = "plume-common"
[dependencies.plume-models] [dependencies.plume-models]
path = "plume-models" path = "plume-models"
[dependencies.rocket_csrf] [dependencies.rocket_i18n]
git = "https://github.com/fdb-hiroshima/rocket_csrf" git = "https://github.com/Plume-org/rocket_i18n"
rev = "29910f2829e7e590a540da3804336577b48c7b31" branch = "go-async"
default-features = false
features = ["rocket"]
[build-dependencies] [build-dependencies]
ructe = "0.9.0" ructe = "0.9.0"
@@ -78,7 +81,6 @@ 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"]
[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"]
+2 -2
View File
@@ -1,4 +1,4 @@
FROM rust:1-buster as builder FROM rust:1-stretch 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 \
@@ -28,7 +28,7 @@ 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:stretch-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 \
+2 -3
View File
@@ -1,4 +1,4 @@
FROM rust:1-buster FROM rust:1-stretch
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 \
@@ -10,8 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \ gcc \
make \ make \
openssl \ openssl \
libssl-dev\ libssl-dev
clang
WORKDIR /scratch WORKDIR /scratch
COPY script/wasm-deps.sh . COPY script/wasm-deps.sh .
+1 -1
View File
@@ -34,7 +34,7 @@ rules and thematics, but they can all communicate with each other.
## Get involved ## Get involved
If you want to have regular news about the project, the best place is probably [our blog](https://fediverse.blog/~/PlumeDev), or our Matrix room: [`#plume-blog:matrix.org`](https://matrix.to/#/#plume-blog:matrix.org). If you want to have regular news about the project, the best place is probably [our blog](https://fediverse.blog/~/PlumeDev), or our Matrix room: [`#plume:disroot.org`](https://riot.im/app/#/room/#plume:disroot.org).
If you want to contribute more, a good first step is to read [our contribution guides](https://docs.joinplu.me/contribute). We accept all kind of contribution: If you want to contribute more, a good first step is to read [our contribution guides](https://docs.joinplu.me/contribute). We accept all kind of contribution:
+1 -28
View File
@@ -40,7 +40,7 @@ main header.article {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: end;
h1, .article-info { h1, .article-info {
text-align: center; text-align: center;
@@ -490,30 +490,3 @@ input:checked ~ .cw-container > .cw-text {
display: inline; display: inline;
} }
} }
// Small screens
@media screen and (max-width: 600px) {
#plume-editor header {
flex-direction: column-reverse;
button {
flex: 0 0 0;
}
}
.popup {
top: 10vh;
bottom: 10vh;
left: 1vw;
right: 1vw;
}
main article {
margin: 2.5em .5em;
max-width: none;
}
main .article-meta > *, main .article-meta .comments, main .article-meta > .banner > * {
margin: 0 5%;
}
}
+1 -5
View File
@@ -490,10 +490,6 @@ figure {
/// Small screens /// Small screens
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
body > main > *, .h-feed > * {
margin: 1em;
}
main .article-meta { main .article-meta {
> *, .comments { > *, .comments {
margin: 0 5%; margin: 0 5%;
@@ -539,7 +535,7 @@ figure {
margin: 0; margin: 0;
& > * { & > * {
max-width: 100% !important; max-width: 100%;
} }
} }
-91
View File
@@ -205,7 +205,6 @@ body > header {
position: absolute; position: absolute;
left: 50%; left: 50%;
transform: translate(-50%, 0); transform: translate(-50%, 0);
transform: translateZ(0);
opacity: 0; opacity: 0;
font-size: 0.9em; font-size: 0.9em;
white-space: nowrap; white-space: nowrap;
@@ -222,93 +221,3 @@ body > header {
} }
} }
} }
// Small screens
@media screen and (max-width: 600px) {
@keyframes menuOpening {
from {
transform: scaleX(0);
transform-origin: left;
opacity: 0;
}
to {
transform: scaleX(1);
transform-origin: left;
opacity: 1;
}
}
body > header {
flex-direction: column;
nav#menu {
display: inline-flex;
z-index: 21;
}
#content {
display: none;
appearance: none;
text-align: center;
z-index: 20;
}
}
body > header:focus-within #content, #content.show {
position: fixed;
display: flex;
flex-direction: column;
justify-content: flex-start;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
animation: 0.2s menuOpening;
&::before {
content: "";
position: absolute;
transform: skewX(-10deg);
top: 0;
left: -20%;
width: 100%;
height: 100%;
z-index: -10;
background: $primary;
}
> nav {
flex-direction: column;
align-items: flex-start;
a {
display: flex;
flex-direction: row;
align-items: center;
margin: 0;
padding: 1rem 1.5rem;
color: $background;
font-size: 1.4em;
font-weight: 300;
&.title { font-size: 1.8em; }
> *:first-child { width: 3rem; }
> img:first-child { height: 3rem; }
> *:last-child { margin-left: 1rem; }
> nav hr {
display: block;
margin: 0;
width: 100%;
border: solid $background 0.1rem;
}
.mobile-label { display: initial; }
}
}
}
}
+9 -9
View File
@@ -1,10 +1,10 @@
/* Color Scheme */ /* Color Scheme */
$gray: #f3f3f3; $gray: #F3F3F3;
$black: #242424; $black: #242424;
$white: #f8f8f8; $white: #F8F8F8;
$purple: #7765e3; $purple: #7765E3;
$lightpurple: #c2bbee; $lightpurple: #c2bbee;
$red: #e92f2f; $red: #E92F2F;
$yellow: #ffe347; $yellow: #ffe347;
$green: #23f0c7; $green: #23f0c7;
@@ -24,14 +24,14 @@ $margin: 0 $horizontal-margin;
/* Fonts */ /* Fonts */
$route159: "Shabnam", "Route159", serif; $route159: "Route159", serif;
$playfair: "Vazir", "Playfair Display", serif; $playfair: "Playfair Display", serif;
$lora: "Vazir", "Lora", serif; $lora: "Lora", serif;
//Code Highlighting //Code Highlighting
$code-keyword-color: #45244a; $code-keyword-color: #45244a;
$code-source-color: #4c588c; $code-source-color: #4c588c;
$code-constant-color: scale-color(magenta, $lightness: -5%); $code-constant-color: scale-color(magenta,$lightness:-5%);
$code-operator-color: scale-color($code-source-color, $lightness: -5%); $code-operator-color: scale-color($code-source-color,$lightness:-5%);
$code-string-color: #8a571c; $code-string-color: #8a571c;
$code-comment-color: #1c4c8a; $code-comment-color: #1c4c8a;
+9 -11
View File
@@ -1,14 +1,12 @@
/* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */ /* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */
@import url("./feather.css"); @import url('./feather.css');
@import url("./fonts/Route159/Route159.css"); @import url('./fonts/Route159/Route159.css');
@import url("./fonts/Lora/Lora.css"); @import url('./fonts/Lora/Lora.css');
@import url("./fonts/Playfair_Display/PlayfairDisplay.css"); @import url('./fonts/Playfair_Display/PlayfairDisplay.css');
@import url("./fonts/Vazir_WOL/Vazir_WOL.css");
@import url("./fonts/Shabnam_WOL/Shabnam_WOL.css");
@import "dark_variables"; @import 'dark_variables';
@import "global"; @import 'global';
@import "header"; @import 'header';
@import "article"; @import 'article';
@import "forms"; @import 'forms';
@@ -1,94 +0,0 @@
Copyright (c) 2015, Saber Rastikerdar (saber.rastikerdar@gmail.com),
Glyphs and data from Roboto font are licensed under the Apache License, Version 2.0.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -1,49 +0,0 @@
@font-face {
font-family: Shabnam;
src: url("Shabnam-WOL.eot");
src: url("Shabnam-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-WOL.woff2") format("woff2"),
url("Shabnam-WOL.woff") format("woff"),
url("Shabnam-WOL.ttf") format("truetype");
font-weight: normal;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Bold-WOL.eot");
src: url("Shabnam-Bold-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Bold-WOL.woff2") format("woff2"),
url("Shabnam-Bold-WOL.woff") format("woff"),
url("Shabnam-Bold-WOL.ttf") format("truetype");
font-weight: bold;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Thin-WOL.eot");
src: url("Shabnam-Thin-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Thin-WOL.woff2") format("woff2"),
url("Shabnam-Thin-WOL.woff") format("woff"),
url("Shabnam-Thin-WOL.ttf") format("truetype");
font-weight: 100;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Light-WOL.eot");
src: url("Shabnam-Light-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Light-WOL.woff2") format("woff2"),
url("Shabnam-Light-WOL.woff") format("woff"),
url("Shabnam-Light-WOL.ttf") format("truetype");
font-weight: 300;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Medium-WOL.eot");
src: url("Shabnam-Medium-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Medium-WOL.woff2") format("woff2"),
url("Shabnam-Medium-WOL.woff") format("woff"),
url("Shabnam-Medium-WOL.ttf") format("truetype");
font-weight: 500;
}
@@ -1,51 +0,0 @@
Changes by Saber Rastikerdar (saber.rastikerdar@gmail.com) are in public domain.
Glyphs and data from Roboto font are licensed under the Apache License, Version 2.0.
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,65 +0,0 @@
@font-face {
font-family: Vazir;
src: url('Vazir-WOL.eot');
src: url('Vazir-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-WOL.woff2') format('woff2'),
url('Vazir-WOL.woff') format('woff'),
url('Vazir-WOL.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Bold-WOL.eot');
src: url('Vazir-Bold-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Bold-WOL.woff2') format('woff2'),
url('Vazir-Bold-WOL.woff') format('woff'),
url('Vazir-Bold-WOL.ttf') format('truetype');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Black-WOL.eot');
src: url('Vazir-Black-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Black-WOL.woff2') format('woff2'),
url('Vazir-Black-WOL.woff') format('woff'),
url('Vazir-Black-WOL.ttf') format('truetype');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Medium-WOL.eot');
src: url('Vazir-Medium-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Medium-WOL.woff2') format('woff2'),
url('Vazir-Medium-WOL.woff') format('woff'),
url('Vazir-Medium-WOL.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Light-WOL.eot');
src: url('Vazir-Light-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Light-WOL.woff2') format('woff2'),
url('Vazir-Light-WOL.woff') format('woff'),
url('Vazir-Light-WOL.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Thin-WOL.eot');
src: url('Vazir-Thin-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Thin-WOL.woff2') format('woff2'),
url('Vazir-Thin-WOL.woff') format('woff'),
url('Vazir-Thin-WOL.ttf') format('truetype');
font-weight: 100;
font-style: normal;
}
+9 -11
View File
@@ -1,14 +1,12 @@
/* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */ /* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */
@import url("./feather.css"); @import url('./feather.css');
@import url("./fonts/Route159/Route159.css"); @import url('./fonts/Route159/Route159.css');
@import url("./fonts/Lora/Lora.css"); @import url('./fonts/Lora/Lora.css');
@import url("./fonts/Playfair_Display/PlayfairDisplay.css"); @import url('./fonts/Playfair_Display/PlayfairDisplay.css');
@import url("./fonts/Vazir_WOL/Vazir_WOL.css");
@import url("./fonts/Shabnam_WOL/Shabnam_WOL.css");
@import "variables"; @import 'variables';
@import "global"; @import 'global';
@import "header"; @import 'header';
@import "article"; @import 'article';
@import "forms"; @import 'forms';
-2
View File
@@ -1,5 +1,3 @@
use rsass;
use ructe::Ructe; use ructe::Ructe;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::{ffi::OsStr, fs::*, io::Write, path::*}; use std::{ffi::OsStr, fs::*, io::Write, path::*};
+2 -2
View File
@@ -1,5 +1,5 @@
"project_id": 352097 "project_identifier": "plume"
"api_token_env": "CROWDIN_API_KEY" "api_key_env": CROWDIN_API_KEY
preserve_hierarchy: true preserve_hierarchy: true
files: files:
- source: /po/plume/plume.pot - source: /po/plume/plume.pot
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-api" name = "plume-api"
version = "0.6.0" version = "0.4.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
-2
View File
@@ -1,2 +0,0 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+1 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-cli" name = "plume-cli"
version = "0.6.0" version = "0.4.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -23,4 +23,3 @@ path = "../plume-models"
[features] [features]
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"]
-2
View File
@@ -1,2 +0,0 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+2 -3
View File
@@ -82,7 +82,7 @@ fn init<'a>(args: &ArgMatches<'a>, conn: &Connection) {
} }
}; };
if can_do || force { if can_do || force {
let searcher = Searcher::create(&path, &CONFIG.search_tokenizers).unwrap(); let searcher = Searcher::create(&path).unwrap();
refill(args, conn, Some(searcher)); refill(args, conn, Some(searcher));
} else { } else {
eprintln!( eprintln!(
@@ -98,8 +98,7 @@ fn refill<'a>(args: &ArgMatches<'a>, conn: &Connection, searcher: Option<Searche
Some(path) => Path::new(path).join("search_index"), Some(path) => Path::new(path).join("search_index"),
None => Path::new(&CONFIG.search_index).to_path_buf(), None => Path::new(&CONFIG.search_index).to_path_buf(),
}; };
let searcher = let searcher = searcher.unwrap_or_else(|| Searcher::open(&path).unwrap());
searcher.unwrap_or_else(|| Searcher::open(&path, &CONFIG.search_tokenizers).unwrap());
searcher.fill(conn).expect("Couldn't import post"); searcher.fill(conn).expect("Couldn't import post");
println!("Commiting result"); println!("Commiting result");
+1 -1
View File
@@ -132,7 +132,7 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
role, role,
&bio, &bio,
email, email,
Some(User::hash_pass(&password).expect("Couldn't hash password")), User::hash_pass(&password).expect("Couldn't hash password"),
) )
.expect("Couldn't save new user"); .expect("Couldn't save new user");
} }
+11 -8
View File
@@ -1,27 +1,27 @@
[package] [package]
name = "plume-common" name = "plume-common"
version = "0.6.0" version = "0.4.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.1" activitypub = "0.1.1"
activitystreams-derive = "0.1.1" activitystreams-derive = "0.2"
activitystreams-traits = "0.1.0" activitystreams-traits = "0.1.0"
array_tool = "1.0" array_tool = "1.0"
base64 = "0.10" base64 = "0.10"
futures-util = "*"
heck = "0.3.0" heck = "0.3.0"
hex = "0.3" hex = "0.3"
hyper = "0.12.33" hyper = "0.13"
openssl = "0.10.22" openssl = "0.10.22"
rocket = "0.4.5" rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
reqwest = "0.9"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
shrinkwraprs = "0.2.1" shrinkwraprs = "0.2.1"
syntect = "3.3" syntect = "3.3"
tokio = "0.1.22" tokio = "0.2"
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] } regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
[dependencies.chrono] [dependencies.chrono]
@@ -30,5 +30,8 @@ version = "0.4"
[dependencies.pulldown-cmark] [dependencies.pulldown-cmark]
default-features = false default-features = false
git = "https://git.joinplu.me/Plume/pulldown-cmark" version = "0.2.0"
branch = "bidi-plume"
[dependencies.reqwest]
features = ["json", "blocking"]
version = "0.10"
-2
View File
@@ -1,2 +0,0 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+4 -28
View File
@@ -164,11 +164,6 @@ where
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)), None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)),
}; };
if Self::is_spoofed_activity(&actor_id, &act) {
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None));
}
// Transform this actor to a model (see FromId for details about the from_id function) // Transform this actor to a model (see FromId for details about the from_id function)
let actor = match A::from_id( let actor = match A::from_id(
ctx, ctx,
@@ -227,26 +222,6 @@ where
Inbox::Failed(err) => Err(err), Inbox::Failed(err) => Err(err),
} }
} }
fn is_spoofed_activity(actor_id: &str, act: &serde_json::Value) -> bool {
use serde_json::Value::{Array, Object, String};
let attributed_to = act["object"].get("attributedTo");
if attributed_to.is_none() {
return false;
}
let attributed_to = attributed_to.unwrap();
match attributed_to {
Array(v) => v.iter().all(|i| match i {
String(s) => s != actor_id,
Object(obj) => obj.get("id").map_or(true, |s| s != actor_id),
_ => false,
}),
String(s) => s != actor_id,
Object(obj) => obj.get("id").map_or(true, |s| s != actor_id),
_ => false,
}
}
} }
/// Get the ActivityPub ID of a JSON value. /// Get the ActivityPub ID of a JSON value.
@@ -304,8 +279,9 @@ pub trait FromId<C>: Sized {
/// Dereferences an ID /// Dereferences an ID
fn deref(id: &str) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> { fn deref(id: &str) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
reqwest::ClientBuilder::new() // Use blocking reqwest API here, since defer cannot be async (yet)
.connect_timeout(Some(std::time::Duration::from_secs(5))) reqwest::blocking::Client::builder()
.connect_timeout(std::time::Duration::from_secs(5))
.build() .build()
.map_err(|_| (None, InboxError::DerefError.into()))? .map_err(|_| (None, InboxError::DerefError.into()))?
.get(id) .get(id)
@@ -321,7 +297,7 @@ pub trait FromId<C>: Sized {
) )
.send() .send()
.map_err(|_| (None, InboxError::DerefError)) .map_err(|_| (None, InboxError::DerefError))
.and_then(|mut r| { .and_then(|r| {
let json: serde_json::Value = r let json: serde_json::Value = r
.json() .json()
.map_err(|_| (None, InboxError::InvalidObject(None)))?; .map_err(|_| (None, InboxError::InvalidObject(None)))?;
+42 -37
View File
@@ -1,14 +1,12 @@
use activitypub::{Activity, Link, Object}; use activitypub::{Activity, Link, Object};
use array_tool::vec::Uniq; use array_tool::vec::Uniq;
use reqwest::r#async::ClientBuilder; use reqwest::ClientBuilder;
use rocket::{ use rocket::{
http::Status, http::Status,
request::{FromRequest, Request}, request::{FromRequest, Request},
response::{Responder, Response}, response::{Responder, Response, Result},
Outcome, Outcome,
}; };
use serde_json;
use tokio::prelude::*;
use self::sign::Signable; use self::sign::Signable;
@@ -62,39 +60,45 @@ impl<T> ActivityStream<T> {
ActivityStream(t) ActivityStream(t)
} }
} }
#[rocket::async_trait]
impl<'r, O: Object> Responder<'r> for ActivityStream<O> { impl<'r, O: Object + Send + 'r> Responder<'r> for ActivityStream<O> {
fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> { async fn respond_to(self, request: &'r Request<'_>) -> Result<'r> {
let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?; let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?;
json["@context"] = context(); json["@context"] = context();
serde_json::to_string(&json).respond_to(request).map(|r| { let result = serde_json::to_string(&json).map_err(rocket::response::Debug);
Response::build_from(r) match result.respond_to(request).await {
Ok(r) => Response::build_from(r)
.raw_header("Content-Type", "application/activity+json") .raw_header("Content-Type", "application/activity+json")
.finalize() .ok(),
}) Err(e) => Err(e),
}
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct ApRequest; pub struct ApRequest;
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for ApRequest { impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
type Error = (); type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, (Status, Self::Error), ()> { async fn from_request(request: &'a Request<'r>) -> Outcome<Self, (Status, Self::Error), ()> {
request request
.headers() .headers()
.get_one("Accept") .get_one("Accept")
.map(|header| { .map(|header| {
header header
.split(',') .split(',')
.map(|ct| match ct.trim() { .map(|ct| {
// bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise match ct.trim() {
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\"" // bool for Forward: true if found a valid Content-Type for Plume first (HTML),
| "application/ld+json;profile=\"https://w3.org/ns/activitystreams\"" // false otherwise
| "application/activity+json" "application/ld+json; profile=\"https://w3.org/ns/activitystreams\""
| "application/ld+json" => Outcome::Success(ApRequest), | "application/ld+json;profile=\"https://w3.org/ns/activitystreams\""
"text/html" => Outcome::Forward(true), | "application/activity+json"
_ => Outcome::Forward(false), | "application/ld+json" => Outcome::Success(ApRequest),
"text/html" => Outcome::Forward(true),
_ => Outcome::Forward(false),
}
}) })
.fold(Outcome::Forward(false), |out, ct| { .fold(Outcome::Forward(false), |out, ct| {
if out.clone().forwarded().unwrap_or_else(|| out.is_success()) { if out.clone().forwarded().unwrap_or_else(|| out.is_success()) {
@@ -130,36 +134,38 @@ where
.sign(sender) .sign(sender)
.expect("activity_pub::broadcast: signature error"); .expect("activity_pub::broadcast: signature error");
let mut rt = tokio::runtime::current_thread::Runtime::new() let rt = tokio::runtime::Builder::new()
.expect("Error while initializing tokio runtime for federation"); .threaded_scheduler()
let client = ClientBuilder::new()
.connect_timeout(std::time::Duration::from_secs(5))
.build() .build()
.expect("Can't build client"); .expect("Error while initializing tokio runtime for federation");
for inbox in boxes { for inbox in boxes {
let body = signed.to_string(); let body = signed.to_string();
let mut headers = request::headers(); let mut headers = request::headers();
headers.insert("Digest", request::Digest::digest(&body)); headers.insert("Digest", request::Digest::digest(&body));
rt.spawn( let sig = request::signature(sender, &headers)
.expect("activity_pub::broadcast: request signature error");
let client = ClientBuilder::new()
.connect_timeout(std::time::Duration::from_secs(5))
.build()
.expect("Can't build client");
rt.spawn(async move {
client client
.post(&inbox) .post(&inbox)
.headers(headers.clone()) .headers(headers.clone())
.header( .header("Signature", sig)
"Signature",
request::signature(sender, &headers)
.expect("activity_pub::broadcast: request signature error"),
)
.body(body) .body(body)
.send() .send()
.and_then(|r| r.into_body().concat2()) .await
.unwrap()
.text()
.await
.map(move |response| { .map(move |response| {
println!("Successfully sent activity to inbox ({})", inbox); println!("Successfully sent activity to inbox ({})", inbox);
println!("Response: \"{:?}\"\n", response) println!("Response: \"{:?}\"\n", response)
}) })
.map_err(|e| println!("Error while sending to inbox ({:?})", e)), .map_err(|e| println!("Error while sending to inbox ({:?})", e))
); });
} }
rt.run().unwrap();
} }
#[derive(Shrinkwrap, Clone, Serialize, Deserialize)] #[derive(Shrinkwrap, Clone, Serialize, Deserialize)]
@@ -203,8 +209,7 @@ pub struct PublicKey {
pub public_key_pem: Option<serde_json::Value>, pub public_key_pem: Option<serde_json::Value>,
} }
#[derive(Clone, Debug, Default, UnitString)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[activitystreams(Hashtag)]
pub struct HashtagType; pub struct HashtagType;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] #[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)]
-1
View File
@@ -1,4 +1,3 @@
use base64;
use chrono::{offset::Utc, DateTime}; use chrono::{offset::Utc, DateTime};
use openssl::hash::{Hasher, MessageDigest}; use openssl::hash::{Hasher, MessageDigest};
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT}; use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
-3
View File
@@ -1,10 +1,7 @@
use super::request; use super::request;
use base64;
use chrono::{naive::NaiveDateTime, DateTime, Duration, Utc}; use chrono::{naive::NaiveDateTime, DateTime, Duration, Utc};
use hex;
use openssl::{pkey::PKey, rsa::Rsa, sha::sha256}; use openssl::{pkey::PKey, rsa::Rsa, sha::sha256};
use rocket::http::HeaderMap; use rocket::http::HeaderMap;
use serde_json;
/// Returns (public key, private key) /// Returns (public key, private key)
pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) { pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
-2
View File
@@ -2,9 +2,7 @@
#[macro_use] #[macro_use]
extern crate activitystreams_derive; extern crate activitystreams_derive;
use activitystreams_traits;
use serde;
#[macro_use] #[macro_use]
extern crate shrinkwraprs; extern crate shrinkwraprs;
#[macro_use] #[macro_use]
+37 -51
View File
@@ -1,11 +1,12 @@
use heck::CamelCase; use heck::CamelCase;
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, Event, Options, Parser, Tag};
use regex_syntax::is_word_character; use regex_syntax::is_word_character;
use rocket::{ use rocket::{
http::uri::Uri, http::uri::Uri,
response::{Flash, Redirect}, response::{Flash, Redirect},
}; };
use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use syntect::html::ClassedHTMLGenerator; use syntect::html::ClassedHTMLGenerator;
use syntect::parsing::SyntaxSet; use syntect::parsing::SyntaxSet;
@@ -50,10 +51,10 @@ enum State {
fn to_inline(tag: Tag<'_>) -> Tag<'_> { fn to_inline(tag: Tag<'_>) -> Tag<'_> {
match tag { match tag {
Tag::Heading(_) | Tag::Table(_) | Tag::TableHead | Tag::TableRow | Tag::TableCell => { Tag::Header(_) | Tag::Table(_) | Tag::TableHead | Tag::TableRow | Tag::TableCell => {
Tag::Paragraph Tag::Paragraph
} }
Tag::Image(typ, url, title) => Tag::Link(typ, url, title), Tag::Image(url, title) => Tag::Link(url, title),
t => t, t => t,
} }
} }
@@ -65,31 +66,21 @@ fn highlight_code<'a>(
evt: Event<'a>, evt: Event<'a>,
) -> Option<Vec<Event<'a>>> { ) -> Option<Vec<Event<'a>>> {
match evt { match evt {
Event::Start(Tag::CodeBlock(kind)) => { Event::Start(Tag::CodeBlock(lang)) => {
match &kind { if lang.is_empty() {
CodeBlockKind::Fenced(lang) if !lang.is_empty() => { Some(vec![Event::Start(Tag::CodeBlock(lang))])
*context = Some(HighlighterContext { content: vec![] }); } else {
} *context = Some(HighlighterContext { content: vec![] });
_ => {} Some(vec![Event::Start(Tag::CodeBlock(lang))])
} }
Some(vec![Event::Start(Tag::CodeBlock(kind))])
} }
Event::End(Tag::CodeBlock(kind)) => { Event::End(Tag::CodeBlock(x)) => {
let mut result = vec![]; let mut result = vec![];
if let Some(ctx) = context.take() { if let Some(ctx) = context.take() {
let lang = if let CodeBlockKind::Fenced(lang) = &kind {
if lang.is_empty() {
unreachable!();
} else {
lang
}
} else {
unreachable!();
};
let syntax_set = SyntaxSet::load_defaults_newlines(); let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set.find_syntax_by_token(&lang).unwrap_or_else(|| { let syntax = syntax_set.find_syntax_by_token(&x).unwrap_or_else(|| {
syntax_set syntax_set
.find_syntax_by_name(&lang) .find_syntax_by_name(&x)
.unwrap_or_else(|| syntax_set.find_syntax_plain_text()) .unwrap_or_else(|| syntax_set.find_syntax_plain_text())
}); });
let mut html = ClassedHTMLGenerator::new(&syntax, &syntax_set); let mut html = ClassedHTMLGenerator::new(&syntax, &syntax_set);
@@ -99,7 +90,7 @@ fn highlight_code<'a>(
let q = html.finalize(); let q = html.finalize();
result.push(Event::Html(q.into())); result.push(Event::Html(q.into()));
} }
result.push(Event::End(Tag::CodeBlock(kind))); result.push(Event::End(Tag::CodeBlock(x)));
*context = None; *context = None;
Some(result) Some(result)
} }
@@ -122,10 +113,10 @@ fn flatten_text<'a>(state: &mut Option<String>, evt: Event<'a>) -> Option<Vec<Ev
prev_txt.push_str(&txt); prev_txt.push_str(&txt);
(Some(prev_txt), vec![]) (Some(prev_txt), vec![])
} }
None => (Some(txt.into_string()), vec![]), None => (Some(txt.into_owned()), vec![]),
}, },
e => match state.take() { e => match state.take() {
Some(prev) => (None, vec![Event::Text(CowStr::Boxed(prev.into())), e]), Some(prev) => (None, vec![Event::Text(Cow::Owned(prev)), e]),
None => (None, vec![e]), None => (None, vec![e]),
}, },
}; };
@@ -165,45 +156,42 @@ fn process_image<'a, 'b>(
) -> Event<'a> { ) -> Event<'a> {
if let Some(ref processor) = *processor { if let Some(ref processor) = *processor {
match evt { match evt {
Event::Start(Tag::Image(typ, id, title)) => { Event::Start(Tag::Image(id, title)) => {
if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) { if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) {
if let (Some(cw), false) = (cw, inline) { if let (Some(cw), false) = (cw, inline) {
// there is a cw, and where are not inline // there is a cw, and where are not inline
Event::Html(CowStr::Boxed( Event::Html(Cow::Owned(format!(
format!( r#"<label for="postcontent-cw-{id}">
r#"<label for="postcontent-cw-{id}">
<input type="checkbox" id="postcontent-cw-{id}" checked="checked" class="cw-checkbox"> <input type="checkbox" id="postcontent-cw-{id}" checked="checked" class="cw-checkbox">
<span class="cw-container"> <span class="cw-container">
<span class="cw-text"> <span class="cw-text">
{cw} {cw}
</span> </span>
<img src="{url}" alt=""#, <img src="{url}" alt=""#,
id = random_hex(), id = random_hex(),
cw = cw, cw = cw,
url = url url = url
) )))
.into(),
))
} else { } else {
Event::Start(Tag::Image(typ, CowStr::Boxed(url.into()), title)) Event::Start(Tag::Image(Cow::Owned(url), title))
} }
} else { } else {
Event::Start(Tag::Image(typ, id, title)) Event::Start(Tag::Image(id, title))
} }
} }
Event::End(Tag::Image(typ, id, title)) => { Event::End(Tag::Image(id, title)) => {
if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) { if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) {
if inline || cw.is_none() { if inline || cw.is_none() {
Event::End(Tag::Image(typ, CowStr::Boxed(url.into()), title)) Event::End(Tag::Image(Cow::Owned(url), title))
} else { } else {
Event::Html(CowStr::Borrowed( Event::Html(Cow::Borrowed(
r#""/> r#""/>
</span> </span>
</label>"#, </label>"#,
)) ))
} }
} else { } else {
Event::End(Tag::Image(typ, id, title)) Event::End(Tag::Image(id, title))
} }
} }
e => e, e => e,
@@ -243,19 +231,19 @@ pub fn md_to_html<'a>(
// Ignore headings, images, and tables if inline = true // Ignore headings, images, and tables if inline = true
.scan((vec![], inline), inline_tags) .scan((vec![], inline), inline_tags)
.scan(&mut DocumentContext::default(), |ctx, evt| match evt { .scan(&mut DocumentContext::default(), |ctx, evt| match evt {
Event::Start(Tag::CodeBlock(_)) => { Event::Start(Tag::CodeBlock(_)) | Event::Start(Tag::Code) => {
ctx.in_code = true; ctx.in_code = true;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
Event::End(Tag::CodeBlock(_)) => { Event::End(Tag::CodeBlock(_)) | Event::End(Tag::Code) => {
ctx.in_code = false; ctx.in_code = false;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
Event::Start(Tag::Link(_, _, _)) => { Event::Start(Tag::Link(_, _)) => {
ctx.in_link = true; ctx.in_link = true;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
Event::End(Tag::Link(_, _, _)) => { Event::End(Tag::Link(_, _)) => {
ctx.in_link = false; ctx.in_link = false;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
@@ -276,7 +264,6 @@ pub fn md_to_html<'a>(
let mention = text_acc; let mention = text_acc;
let short_mention = mention.splitn(1, '@').next().unwrap_or(""); let short_mention = mention.splitn(1, '@').next().unwrap_or("");
let link = Tag::Link( let link = Tag::Link(
LinkType::Inline,
format!("{}@/{}/", base_url, &mention).into(), format!("{}@/{}/", base_url, &mention).into(),
short_mention.to_owned().into(), short_mention.to_owned().into(),
); );
@@ -307,8 +294,8 @@ pub fn md_to_html<'a>(
} }
let hashtag = text_acc; let hashtag = text_acc;
let link = Tag::Link( let link = Tag::Link(
LinkType::Inline, format!("{}tag/{}", base_url, &hashtag.to_camel_case())
format!("{}tag/{}", base_url, &hashtag).into(), .into(),
hashtag.to_owned().into(), hashtag.to_owned().into(),
); );
@@ -455,7 +442,6 @@ mod tests {
("not_a#hashtag", vec![]), ("not_a#hashtag", vec![]),
("#نرم‌افزار_آزاد", vec!["نرم‌افزار_آزاد"]), ("#نرم‌افزار_آزاد", vec!["نرم‌افزار_آزاد"]),
("[#hash in link](https://example.org/)", vec![]), ("[#hash in link](https://example.org/)", vec![]),
("#zwsp\u{200b}inhash", vec!["zwsp"]),
]; ];
for (md, mentions) in tests { for (md, mentions) in tests {
@@ -473,11 +459,11 @@ mod tests {
fn test_inline() { fn test_inline() {
assert_eq!( assert_eq!(
md_to_html("# Hello", None, false, None).0, md_to_html("# Hello", None, false, None).0,
String::from("<h1 dir=\"auto\">Hello</h1>\n") String::from("<h1>Hello</h1>\n")
); );
assert_eq!( assert_eq!(
md_to_html("# Hello", None, true, None).0, md_to_html("# Hello", None, true, None).0,
String::from("<p dir=\"auto\">Hello</p>\n") String::from("<p>Hello</p>\n")
); );
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-front" name = "plume-front"
version = "0.6.0" version = "0.4.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
-2
View File
@@ -1,2 +0,0 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+1 -7
View File
@@ -269,13 +269,7 @@ pub fn init() -> Result<(), EditorError> {
let editor_button = document().create_element("a")?; let editor_button = document().create_element("a")?;
js! { @{&editor_button}.href = "#"; } js! { @{&editor_button}.href = "#"; }
editor_button.add_event_listener(|_: ClickEvent| { editor_button.add_event_listener(|_: ClickEvent| {
if window() window().local_storage().remove("basic-editor");
.local_storage()
.insert("basic-editor", "false")
.is_err()
{
console!(log, "Failed to write into local storage");
}
window().history().go(0).ok(); // refresh window().history().go(0).ok(); // refresh
}); });
editor_button.append_child( editor_button.append_child(
-1
View File
@@ -19,7 +19,6 @@ init_i18n!(
en, en,
eo, eo,
es, es,
fa,
fr, fr,
gl, gl,
hi, hi,
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-macro" name = "plume-macro"
version = "0.6.0" version = "0.4.0"
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"] authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
edition = "2018" edition = "2018"
description = "Plume procedural macros" description = "Plume procedural macros"
-2
View File
@@ -1,2 +0,0 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+19 -11
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-models" name = "plume-models"
version = "0.6.0" version = "0.4.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -10,28 +10,26 @@ ammonia = "2.1.1"
askama_escape = "0.1" askama_escape = "0.1"
bcrypt = "0.5" bcrypt = "0.5"
guid-create = "0.1" guid-create = "0.1"
futures = "0.3"
heck = "0.3.0" heck = "0.3.0"
itertools = "0.8.0" itertools = "0.8.0"
lazy_static = "1.0" lazy_static = "1.0"
ldap3 = "0.7.1"
migrations_internals= "1.4.0" migrations_internals= "1.4.0"
openssl = "0.10.22" openssl = "0.10.22"
rocket = "0.4.5" rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
reqwest = "0.9"
scheduled-thread-pool = "0.2.2" scheduled-thread-pool = "0.2.2"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
tantivy = "0.12.0" tantivy = "0.10.1"
tokio = "0.2"
url = "2.1" url = "2.1"
walkdir = "2.2" walkdir = "2.2"
webfinger = "0.4.1" webfinger = { git = "https://github.com/Plume-org/webfinger", rev = "4e8f12810c4a7ba7a07bbcb722cd265fdff512b6", features = ["async"] }
whatlang = "0.7.1" whatlang = "0.7.1"
shrinkwraprs = "0.2.1" shrinkwraprs = "0.3"
diesel-derive-newtype = "0.1.2" diesel-derive-newtype = "0.1.2"
glob = "0.3.0" glob = "0.3.0"
lindera-tantivy = { version = "0.1.3", optional = true }
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
@@ -50,10 +48,20 @@ path = "../plume-common"
[dependencies.plume-macro] [dependencies.plume-macro]
path = "../plume-macro" path = "../plume-macro"
[dependencies.reqwest]
features = ["json", "blocking"]
version = "0.10"
[dependencies.rocket_i18n]
git = "https://github.com/Plume-org/rocket_i18n"
branch = "go-async"
default-features = false
features = ["rocket"]
[dev-dependencies] [dev-dependencies]
diesel_migrations = "1.3.0" diesel_migrations = "1.4.0"
[features] [features]
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"]
Binary file not shown.
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+6 -4
View File
@@ -8,11 +8,12 @@ use rocket::{
/// 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 reserved to admins.
pub struct Admin(pub User); pub struct Admin(pub User);
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for Admin { impl<'a, 'r> FromRequest<'a, 'r> for Admin {
type Error = (); type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Admin, ()> { async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let user = request.guard::<User>()?; let user = try_outcome!(User::from_request(request).await);
if user.is_admin() { if user.is_admin() {
Outcome::Success(Admin(user)) Outcome::Success(Admin(user))
} else { } else {
@@ -24,11 +25,12 @@ impl<'a, 'r> FromRequest<'a, 'r> for Admin {
/// Same as `Admin` but for moderators. /// Same as `Admin` but for moderators.
pub struct Moderator(pub User); pub struct Moderator(pub User);
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for Moderator { impl<'a, 'r> FromRequest<'a, 'r> for Moderator {
type Error = (); type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Moderator, ()> { async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let user = request.guard::<User>()?; let user = try_outcome!(User::from_request(request).await);
if user.is_moderator() { if user.is_moderator() {
Outcome::Success(Moderator(user)) Outcome::Success(Moderator(user))
} else { } else {
+20 -16
View File
@@ -76,32 +76,36 @@ pub enum TokenError {
DbError, DbError,
} }
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for ApiToken { impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
type Error = TokenError; type Error = TokenError;
fn from_request(request: &'a Request<'r>) -> request::Outcome<ApiToken, TokenError> { async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let headers: Vec<_> = request.headers().get("Authorization").collect(); let headers: Vec<_> = request.headers().get("Authorization").collect();
if headers.len() != 1 { if headers.len() != 1 {
return Outcome::Failure((Status::BadRequest, TokenError::NoHeader)); return Outcome::Failure((Status::BadRequest, TokenError::NoHeader));
} }
let mut parsed_header = headers[0].split(' '); let mut parsed_header = headers[0].split(' ');
let auth_type = parsed_header.next().map_or_else( if let Some(auth_type) = parsed_header.next() {
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)), if let Some(val) = parsed_header.next() {
Outcome::Success, if auth_type == "Bearer" {
)?; if let Outcome::Success(conn) = DbConn::from_request(request).await {
let val = parsed_header.next().map_or_else( if let Ok(token) = ApiToken::find_by_value(&*conn, val) {
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)), return Outcome::Success(token);
Outcome::Success, }
)?; } else {
return Outcome::Failure((
if auth_type == "Bearer" { Status::InternalServerError,
let conn = request TokenError::DbError,
.guard::<DbConn>() ));
.map_failure(|_| (Status::InternalServerError, TokenError::DbError))?; }
if let Ok(token) = ApiToken::find_by_value(&*conn, val) { }
return Outcome::Success(token); } else {
return Outcome::Failure((Status::BadRequest, TokenError::NoValue));
} }
} else {
return Outcome::Failure((Status::BadRequest, TokenError::NoType));
} }
Outcome::Forward(()) Outcome::Forward(())
+9 -11
View File
@@ -20,7 +20,6 @@ use plume_common::activity_pub::{
inbox::{AsActor, FromId}, inbox::{AsActor, FromId},
sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source, sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
}; };
use serde_json;
use url::Url; use url::Url;
use webfinger::*; use webfinger::*;
@@ -71,7 +70,8 @@ impl Blog {
insert!(blogs, NewBlog, |inserted, conn| { insert!(blogs, NewBlog, |inserted, conn| {
let instance = inserted.get_instance(conn)?; let instance = inserted.get_instance(conn)?;
if inserted.outbox_url.is_empty() { if inserted.outbox_url.is_empty() {
inserted.outbox_url = instance.compute_box(BLOG_PREFIX, &inserted.actor_id, "outbox"); inserted.outbox_url =
instance.compute_box(BLOG_PREFIX, &inserted.actor_id, r#"outbox"#);
} }
if inserted.inbox_url.is_empty() { if inserted.inbox_url.is_empty() {
@@ -132,7 +132,7 @@ impl Blog {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn find_by_fqn(c: &PlumeRocket, fqn: &str) -> Result<Blog> { pub async fn find_by_fqn(c: &PlumeRocket, 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(&*c.conn) .first(&*c.conn)
@@ -140,12 +140,13 @@ impl Blog {
if let Some(from_db) = from_db { if let Some(from_db) = from_db {
Ok(from_db) Ok(from_db)
} else { } else {
Blog::fetch_from_webfinger(c, fqn) Blog::fetch_from_webfinger(c, fqn).await
} }
} }
fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<Blog> { async fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<Blog> {
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)? resolve_with_prefix(Prefix::Group, acct.to_owned(), true)
.await?
.links .links
.into_iter() .into_iter()
.find(|l| l.mime_type == Some(String::from("application/activity+json"))) .find(|l| l.mime_type == Some(String::from("application/activity+json")))
@@ -498,7 +499,6 @@ pub(crate) mod tests {
use super::*; use super::*;
use crate::{ use crate::{
blog_authors::*, blog_authors::*,
config::CONFIG,
instance::tests as instance_tests, instance::tests as instance_tests,
medias::NewMedia, medias::NewMedia,
search::tests::get_searcher, search::tests::get_searcher,
@@ -768,9 +768,7 @@ pub(crate) mod tests {
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (_, blogs) = fill_database(conn); let (_, blogs) = fill_database(conn);
blogs[0] blogs[0].delete(conn, &get_searcher()).unwrap();
.delete(conn, &get_searcher(&CONFIG.search_tokenizers))
.unwrap();
assert!(Blog::get(conn, blogs[0].id).is_err()); assert!(Blog::get(conn, blogs[0].id).is_err());
Ok(()) Ok(())
}) })
@@ -780,7 +778,7 @@ 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 searcher = get_searcher(&CONFIG.search_tokenizers); let searcher = get_searcher();
let (user, _) = fill_database(conn); let (user, _) = fill_database(conn);
let b1 = Blog::insert( let b1 = Blog::insert(
+8 -8
View File
@@ -17,6 +17,7 @@ use activitypub::{
}; };
use chrono::{self, NaiveDateTime}; use chrono::{self, NaiveDateTime};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use futures::stream::{self, StreamExt};
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
@@ -24,7 +25,6 @@ use plume_common::{
}, },
utils, utils,
}; };
use serde_json;
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Queryable, Identifiable, Clone, AsChangeset)] #[derive(Queryable, Identifiable, Clone, AsChangeset)]
@@ -105,7 +105,7 @@ impl Comment {
.unwrap_or(false) .unwrap_or(false)
} }
pub fn to_activity(&self, c: &PlumeRocket) -> Result<Note> { pub async fn to_activity(&self, c: &PlumeRocket) -> Result<Note> {
let author = User::get(&c.conn, self.author_id)?; let author = User::get(&c.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(),
@@ -132,18 +132,18 @@ impl Comment {
note.object_props.set_attributed_to_link(author.into_id())?; note.object_props.set_attributed_to_link(author.into_id())?;
note.object_props.set_to_link_vec(to)?; note.object_props.set_to_link_vec(to)?;
note.object_props.set_tag_link_vec( note.object_props.set_tag_link_vec(
mentions stream::iter(mentions)
.into_iter() .filter_map(|m| async move { Mention::build_activity(c, &m).await.ok() })
.filter_map(|m| Mention::build_activity(c, &m).ok()) .collect::<Vec<link::Mention>>()
.collect::<Vec<link::Mention>>(), .await,
)?; )?;
Ok(note) Ok(note)
} }
pub fn create_activity(&self, c: &PlumeRocket) -> Result<Create> { pub async fn create_activity(&self, c: &PlumeRocket) -> Result<Create> {
let author = User::get(&c.conn, self.author_id)?; let author = User::get(&c.conn, self.author_id)?;
let note = self.to_activity(c)?; let note = self.to_activity(c).await?;
let mut act = Create::default(); let mut act = Create::default();
act.create_props.set_actor_link(author.into_id())?; act.create_props.set_actor_link(author.into_id())?;
act.create_props.set_object_object(note.clone())?; act.create_props.set_object_object(note.clone())?;
-91
View File
@@ -1,4 +1,3 @@
use crate::search::TokenizerKind as SearchTokenizer;
use rocket::config::Limits; use rocket::config::Limits;
use rocket::Config as RocketConfig; use rocket::Config as RocketConfig;
use std::env::{self, var}; use std::env::{self, var};
@@ -15,12 +14,10 @@ pub struct Config {
pub db_max_size: Option<u32>, pub db_max_size: Option<u32>,
pub db_min_idle: Option<u32>, pub db_min_idle: Option<u32>,
pub search_index: String, pub search_index: String,
pub search_tokenizers: SearchTokenizerConfig,
pub rocket: Result<RocketConfig, RocketError>, pub rocket: Result<RocketConfig, RocketError>,
pub logo: LogoConfig, pub logo: LogoConfig,
pub default_theme: String, pub default_theme: String,
pub media_directory: String, pub media_directory: String,
pub ldap: Option<LdapConfig>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -191,92 +188,6 @@ impl Default for LogoConfig {
} }
} }
pub struct SearchTokenizerConfig {
pub tag_tokenizer: SearchTokenizer,
pub content_tokenizer: SearchTokenizer,
pub property_tokenizer: SearchTokenizer,
}
impl SearchTokenizerConfig {
pub fn init() -> Self {
use SearchTokenizer::*;
match var("SEARCH_LANG").ok().as_deref() {
Some("ja") => {
#[cfg(not(feature = "search-lindera"))]
panic!("You need build Plume with search-lindera feature, or execute it with SEARCH_TAG_TOKENIZER=ngram and SEARCH_CONTENT_TOKENIZER=ngram to enable Japanese search feature");
#[cfg(feature = "search-lindera")]
Self {
tag_tokenizer: Self::determine_tokenizer("SEARCH_TAG_TOKENIZER", Lindera),
content_tokenizer: Self::determine_tokenizer(
"SEARCH_CONTENT_TOKENIZER",
Lindera,
),
property_tokenizer: Ngram,
}
}
_ => Self {
tag_tokenizer: Self::determine_tokenizer("SEARCH_TAG_TOKENIZER", Whitespace),
content_tokenizer: Self::determine_tokenizer("SEARCH_CONTENT_TOKENIZER", Simple),
property_tokenizer: Ngram,
},
}
}
fn determine_tokenizer(var_name: &str, default: SearchTokenizer) -> SearchTokenizer {
use SearchTokenizer::*;
match var(var_name).ok().as_deref() {
Some("simple") => Simple,
Some("ngram") => Ngram,
Some("whitespace") => Whitespace,
Some("lindera") => {
#[cfg(not(feature = "search-lindera"))]
panic!("You need build Plume with search-lindera feature to use Lindera tokenizer");
#[cfg(feature = "search-lindera")]
Lindera
}
_ => default,
}
}
}
pub struct LdapConfig {
pub addr: String,
pub base_dn: String,
pub tls: bool,
pub user_name_attr: String,
pub mail_attr: String,
}
fn get_ldap_config() -> Option<LdapConfig> {
let addr = var("LDAP_ADDR").ok();
let base_dn = var("LDAP_BASE_DN").ok();
match (addr, base_dn) {
(Some(addr), Some(base_dn)) => {
let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned());
let tls = match tls.as_ref() {
"1" | "true" | "TRUE" => true,
"0" | "false" | "FALSE" => false,
_ => panic!("Invalid LDAP configuration : tls"),
};
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
Some(LdapConfig {
addr,
base_dn,
tls,
user_name_attr,
mail_attr,
})
}
(None, None) => None,
(_, _) => {
panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set")
}
}
}
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!(
@@ -298,12 +209,10 @@ lazy_static! {
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
database_url: var("DATABASE_URL").unwrap_or_else(|_| format!("{}.sqlite", DB_NAME)), database_url: var("DATABASE_URL").unwrap_or_else(|_| format!("{}.sqlite", DB_NAME)),
search_index: var("SEARCH_INDEX").unwrap_or_else(|_| "search_index".to_owned()), search_index: var("SEARCH_INDEX").unwrap_or_else(|_| "search_index".to_owned()),
search_tokenizers: SearchTokenizerConfig::init(),
rocket: get_rocket_config(), rocket: get_rocket_config(),
logo: LogoConfig::default(), logo: LogoConfig::default(),
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()), default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
media_directory: var("MEDIA_UPLOAD_DIRECTORY") media_directory: var("MEDIA_UPLOAD_DIRECTORY")
.unwrap_or_else(|_| "static/media".to_owned()), .unwrap_or_else(|_| "static/media".to_owned()),
ldap: get_ldap_config(),
}; };
} }
+6 -6
View File
@@ -7,7 +7,7 @@ use diesel::{dsl::sql_query, ConnectionError, RunQueryDsl};
use rocket::{ use rocket::{
http::Status, http::Status,
request::{self, FromRequest}, request::{self, FromRequest},
Outcome, Request, State, Outcome, Request,
}; };
use std::ops::Deref; use std::ops::Deref;
@@ -21,14 +21,14 @@ pub struct DbConn(pub PooledConnection<ConnectionManager<Connection>>);
/// Attempts to retrieve a single connection from the managed database pool. If /// Attempts to retrieve a single connection from the managed database pool. If
/// no pool is currently managed, fails with an `InternalServerError` status. If /// no pool is currently managed, fails with an `InternalServerError` status. If
/// no connections are available, fails with a `ServiceUnavailable` status. /// no connections are available, fails with a `ServiceUnavailable` status.
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for DbConn { impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
type Error = (); type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let pool = request.guard::<State<'_, DbPool>>()?; match DbConn::from_request(request).await {
match pool.get() { Outcome::Success(a) => Outcome::Success(a),
Ok(conn) => Outcome::Success(DbConn(conn)), _ => Outcome::Failure((Status::ServiceUnavailable, ())),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
} }
} }
} }
+2 -1
View File
@@ -6,10 +6,11 @@ use rocket::{
pub struct Headers<'r>(pub HeaderMap<'r>); pub struct Headers<'r>(pub HeaderMap<'r>);
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for Headers<'r> { impl<'a, 'r> FromRequest<'a, 'r> for Headers<'r> {
type Error = (); type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> { async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
for header in request.headers().clone().into_iter() { for header in request.headers().clone().into_iter() {
headers.add(header); headers.add(header);

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