Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f904b7ac7 | |||
| e322d9509a | |||
| b596e77f03 | |||
| 41f97b01f0 | |||
| a508a4150c | |||
| 25c40adf20 | |||
| 7490567a21 | |||
| 492bbb1ba6 | |||
| cf3708e1c6 | |||
| df442002c2 | |||
| 07036b5fad | |||
| 0726375859 | |||
| cb1c260692 | |||
| de6bfca084 | |||
| 7aabb9661e | |||
| 18bb413011 | |||
| d2881ee3f7 | |||
| 850b3c1337 | |||
| 44ebce516c | |||
| 3c830ab0ce | |||
| 097d0ea9ce | |||
| 6fe16c9f84 | |||
| 43cb9f700c | |||
| 2c285b9aca | |||
| e4bb73d22e | |||
| e9c7259ffb | |||
| be8c67ee9a | |||
| 65b2c38c29 | |||
| 8aa99cea35 | |||
| a010025074 | |||
| 82088596a8 | |||
| 87ce3a7b51 | |||
| 3472a58299 | |||
| a3f165f9f4 | |||
| 25c5da1a7c | |||
| 022e037eea | |||
| 45c335e17b | |||
| b51551973a | |||
| 59e5c49aa8 | |||
| ce119ffe50 | |||
| 944f8c42fa | |||
| 909f677bdd | |||
| fd9764ff17 | |||
| 75722abc9e | |||
| ec9b699c6e | |||
| bb5c2b69a7 | |||
| e52944e477 | |||
| 928470610e |
@@ -10,7 +10,7 @@ executors:
|
||||
type: boolean
|
||||
default: false
|
||||
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>>
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
localhost {
|
||||
reverse_proxy localhost:7878
|
||||
localhost:443 {
|
||||
proxy / localhost:7878 {
|
||||
transparent
|
||||
}
|
||||
tls self_signed
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
FROM debian:buster-20201117
|
||||
FROM debian:stretch-20190326
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
#install native/circleci/build dependancies
|
||||
RUN apt update &&\
|
||||
apt install -y --no-install-recommends git ssh tar gzip ca-certificates default-jre&&\
|
||||
echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \
|
||||
| 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&&\
|
||||
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&&\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#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 rust-std --target wasm32-unknown-unknown
|
||||
|
||||
@@ -27,7 +24,8 @@ COPY cargo_config /root/.cargo/config
|
||||
#install selenium for front end tests
|
||||
RUN pip3 install selenium
|
||||
|
||||
#configure caddy
|
||||
#install and configure caddy
|
||||
RUN curl https://getcaddy.com | bash -s personal
|
||||
COPY Caddyfile /Caddyfile
|
||||
|
||||
#install crowdin
|
||||
|
||||
@@ -45,12 +45,3 @@ ROCKET_ADDRESS=127.0.0.1
|
||||
#PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png
|
||||
#PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.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
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
|
||||
@@ -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.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+13
-11
@@ -1,13 +1,14 @@
|
||||
[package]
|
||||
authors = ["Plume contributors"]
|
||||
name = "plume"
|
||||
version = "0.6.0"
|
||||
version = "0.4.0"
|
||||
repository = "https://github.com/Plume-org/Plume"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
activitypub = "0.1.3"
|
||||
askama_escape = "0.1"
|
||||
async-trait = "*"
|
||||
atom_syndication = "0.6"
|
||||
clap = "2.33"
|
||||
colored = "1.8"
|
||||
@@ -20,9 +21,8 @@ heck = "0.3.0"
|
||||
lettre = "0.9.2"
|
||||
lettre_email = "0.9.2"
|
||||
num_cpus = "1.10"
|
||||
rocket = "0.4.5"
|
||||
rocket_contrib = { version = "0.4.5", features = ["json"] }
|
||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
|
||||
rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" , features = ["json"] }
|
||||
rpassword = "4.0"
|
||||
scheduled-thread-pool = "0.2.2"
|
||||
serde = "1.0"
|
||||
@@ -30,9 +30,10 @@ serde_json = "1.0"
|
||||
serde_qs = "0.5"
|
||||
shrinkwraprs = "0.2.1"
|
||||
syntect = "3.3"
|
||||
validator = "0.8"
|
||||
validator_derive = "0.8"
|
||||
webfinger = "0.4.1"
|
||||
tokio = "0.2"
|
||||
validator = "0.10"
|
||||
validator_derive = "0.10"
|
||||
webfinger = { git = "https://github.com/Plume-org/webfinger", rev = "4e8f12810c4a7ba7a07bbcb722cd265fdff512b6", features = ["async"] }
|
||||
|
||||
[[bin]]
|
||||
name = "plume"
|
||||
@@ -64,9 +65,11 @@ path = "plume-common"
|
||||
[dependencies.plume-models]
|
||||
path = "plume-models"
|
||||
|
||||
[dependencies.rocket_csrf]
|
||||
git = "https://github.com/fdb-hiroshima/rocket_csrf"
|
||||
rev = "29910f2829e7e590a540da3804336577b48c7b31"
|
||||
[dependencies.rocket_i18n]
|
||||
git = "https://github.com/Plume-org/rocket_i18n"
|
||||
branch = "go-async"
|
||||
default-features = false
|
||||
features = ["rocket"]
|
||||
|
||||
[build-dependencies]
|
||||
ructe = "0.9.0"
|
||||
@@ -78,7 +81,6 @@ postgres = ["plume-models/postgres", "diesel/postgres"]
|
||||
sqlite = ["plume-models/sqlite", "diesel/sqlite"]
|
||||
debug-mailer = []
|
||||
test = []
|
||||
search-lindera = ["plume-models/search-lindera"]
|
||||
|
||||
[workspace]
|
||||
members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"]
|
||||
|
||||
+2
-2
@@ -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 \
|
||||
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 clean
|
||||
|
||||
FROM debian:buster-slim
|
||||
FROM debian:stretch-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
|
||||
+2
-3
@@ -1,4 +1,4 @@
|
||||
FROM rust:1-buster
|
||||
FROM rust:1-stretch
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
@@ -10,8 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
make \
|
||||
openssl \
|
||||
libssl-dev\
|
||||
clang
|
||||
libssl-dev
|
||||
|
||||
WORKDIR /scratch
|
||||
COPY script/wasm-deps.sh .
|
||||
|
||||
@@ -34,7 +34,7 @@ rules and thematics, but they can all communicate with each other.
|
||||
|
||||
## 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:
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ main header.article {
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
justify-content: end;
|
||||
|
||||
h1, .article-info {
|
||||
text-align: center;
|
||||
@@ -490,30 +490,3 @@ input:checked ~ .cw-container > .cw-text {
|
||||
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%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,10 +490,6 @@ figure {
|
||||
|
||||
/// Small screens
|
||||
@media screen and (max-width: 600px) {
|
||||
body > main > *, .h-feed > * {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
main .article-meta {
|
||||
> *, .comments {
|
||||
margin: 0 5%;
|
||||
@@ -539,7 +535,7 @@ figure {
|
||||
margin: 0;
|
||||
|
||||
& > * {
|
||||
max-width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,6 @@ body > header {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
transform: translateZ(0);
|
||||
opacity: 0;
|
||||
font-size: 0.9em;
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* Color Scheme */
|
||||
$gray: #f3f3f3;
|
||||
$gray: #F3F3F3;
|
||||
$black: #242424;
|
||||
$white: #f8f8f8;
|
||||
$purple: #7765e3;
|
||||
$white: #F8F8F8;
|
||||
$purple: #7765E3;
|
||||
$lightpurple: #c2bbee;
|
||||
$red: #e92f2f;
|
||||
$red: #E92F2F;
|
||||
$yellow: #ffe347;
|
||||
$green: #23f0c7;
|
||||
|
||||
@@ -24,14 +24,14 @@ $margin: 0 $horizontal-margin;
|
||||
|
||||
/* Fonts */
|
||||
|
||||
$route159: "Shabnam", "Route159", serif;
|
||||
$playfair: "Vazir", "Playfair Display", serif;
|
||||
$lora: "Vazir", "Lora", serif;
|
||||
$route159: "Route159", serif;
|
||||
$playfair: "Playfair Display", serif;
|
||||
$lora: "Lora", serif;
|
||||
|
||||
//Code Highlighting
|
||||
$code-keyword-color: #45244a;
|
||||
$code-source-color: #4c588c;
|
||||
$code-constant-color: scale-color(magenta, $lightness: -5%);
|
||||
$code-operator-color: scale-color($code-source-color, $lightness: -5%);
|
||||
$code-constant-color: scale-color(magenta,$lightness:-5%);
|
||||
$code-operator-color: scale-color($code-source-color,$lightness:-5%);
|
||||
$code-string-color: #8a571c;
|
||||
$code-comment-color: #1c4c8a;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
/* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */
|
||||
|
||||
@import url("./feather.css");
|
||||
@import url("./fonts/Route159/Route159.css");
|
||||
@import url("./fonts/Lora/Lora.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 url('./feather.css');
|
||||
@import url('./fonts/Route159/Route159.css');
|
||||
@import url('./fonts/Lora/Lora.css');
|
||||
@import url('./fonts/Playfair_Display/PlayfairDisplay.css');
|
||||
|
||||
@import "dark_variables";
|
||||
@import "global";
|
||||
@import "header";
|
||||
@import "article";
|
||||
@import "forms";
|
||||
@import 'dark_variables';
|
||||
@import 'global';
|
||||
@import 'header';
|
||||
@import 'article';
|
||||
@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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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;
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
/* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */
|
||||
|
||||
@import url("./feather.css");
|
||||
@import url("./fonts/Route159/Route159.css");
|
||||
@import url("./fonts/Lora/Lora.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 url('./feather.css');
|
||||
@import url('./fonts/Route159/Route159.css');
|
||||
@import url('./fonts/Lora/Lora.css');
|
||||
@import url('./fonts/Playfair_Display/PlayfairDisplay.css');
|
||||
|
||||
@import "variables";
|
||||
@import "global";
|
||||
@import "header";
|
||||
@import "article";
|
||||
@import "forms";
|
||||
@import 'variables';
|
||||
@import 'global';
|
||||
@import 'header';
|
||||
@import 'article';
|
||||
@import 'forms';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use rsass;
|
||||
|
||||
use ructe::Ructe;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::{ffi::OsStr, fs::*, io::Write, path::*};
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
"project_id": 352097
|
||||
"api_token_env": "CROWDIN_API_KEY"
|
||||
"project_identifier": "plume"
|
||||
"api_key_env": CROWDIN_API_KEY
|
||||
preserve_hierarchy: true
|
||||
files:
|
||||
- source: /po/plume/plume.pot
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "plume-api"
|
||||
version = "0.6.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Plume contributors"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pre-release-hook = ["cargo", "fmt"]
|
||||
pre-release-replacements = []
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "plume-cli"
|
||||
version = "0.6.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Plume contributors"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -23,4 +23,3 @@ path = "../plume-models"
|
||||
[features]
|
||||
postgres = ["plume-models/postgres", "diesel/postgres"]
|
||||
sqlite = ["plume-models/sqlite", "diesel/sqlite"]
|
||||
search-lindera = ["plume-models/search-lindera"]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pre-release-hook = ["cargo", "fmt"]
|
||||
pre-release-replacements = []
|
||||
@@ -82,7 +82,7 @@ fn init<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
||||
}
|
||||
};
|
||||
if can_do || force {
|
||||
let searcher = Searcher::create(&path, &CONFIG.search_tokenizers).unwrap();
|
||||
let searcher = Searcher::create(&path).unwrap();
|
||||
refill(args, conn, Some(searcher));
|
||||
} else {
|
||||
eprintln!(
|
||||
@@ -98,8 +98,7 @@ fn refill<'a>(args: &ArgMatches<'a>, conn: &Connection, searcher: Option<Searche
|
||||
Some(path) => Path::new(path).join("search_index"),
|
||||
None => Path::new(&CONFIG.search_index).to_path_buf(),
|
||||
};
|
||||
let searcher =
|
||||
searcher.unwrap_or_else(|| Searcher::open(&path, &CONFIG.search_tokenizers).unwrap());
|
||||
let searcher = searcher.unwrap_or_else(|| Searcher::open(&path).unwrap());
|
||||
|
||||
searcher.fill(conn).expect("Couldn't import post");
|
||||
println!("Commiting result");
|
||||
|
||||
@@ -132,7 +132,7 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
||||
role,
|
||||
&bio,
|
||||
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");
|
||||
}
|
||||
|
||||
+11
-8
@@ -1,27 +1,27 @@
|
||||
[package]
|
||||
name = "plume-common"
|
||||
version = "0.6.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Plume contributors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
activitypub = "0.1.1"
|
||||
activitystreams-derive = "0.1.1"
|
||||
activitystreams-derive = "0.2"
|
||||
activitystreams-traits = "0.1.0"
|
||||
array_tool = "1.0"
|
||||
base64 = "0.10"
|
||||
futures-util = "*"
|
||||
heck = "0.3.0"
|
||||
hex = "0.3"
|
||||
hyper = "0.12.33"
|
||||
hyper = "0.13"
|
||||
openssl = "0.10.22"
|
||||
rocket = "0.4.5"
|
||||
reqwest = "0.9"
|
||||
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
shrinkwraprs = "0.2.1"
|
||||
syntect = "3.3"
|
||||
tokio = "0.1.22"
|
||||
tokio = "0.2"
|
||||
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
|
||||
|
||||
[dependencies.chrono]
|
||||
@@ -30,5 +30,8 @@ version = "0.4"
|
||||
|
||||
[dependencies.pulldown-cmark]
|
||||
default-features = false
|
||||
git = "https://git.joinplu.me/Plume/pulldown-cmark"
|
||||
branch = "bidi-plume"
|
||||
version = "0.2.0"
|
||||
|
||||
[dependencies.reqwest]
|
||||
features = ["json", "blocking"]
|
||||
version = "0.10"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pre-release-hook = ["cargo", "fmt"]
|
||||
pre-release-replacements = []
|
||||
@@ -86,7 +86,7 @@ where
|
||||
/// - the context to be passed to each handler.
|
||||
/// - the activity
|
||||
/// - the reason it has not been handled yet
|
||||
NotHandled(&'a C, serde_json::Value, InboxError<E>),
|
||||
NotHandled(&'a mut C, serde_json::Value, InboxError<E>),
|
||||
|
||||
/// A matching handler have been found but failed
|
||||
///
|
||||
@@ -139,16 +139,16 @@ where
|
||||
///
|
||||
/// - `ctx`: the context to pass to each handler
|
||||
/// - `json`: the JSON representation of the incoming activity
|
||||
pub fn handle(ctx: &'a C, json: serde_json::Value) -> Inbox<'a, C, E, R> {
|
||||
pub fn handle(ctx: &'a mut C, json: serde_json::Value) -> Inbox<'a, C, E, R> {
|
||||
Inbox::NotHandled(ctx, json, InboxError::NoMatch)
|
||||
}
|
||||
|
||||
/// Registers an handler on this Inbox.
|
||||
pub fn with<A, V, M>(self) -> Inbox<'a, C, E, R>
|
||||
where
|
||||
A: AsActor<&'a C> + FromId<C, Error = E>,
|
||||
A: AsActor<&'a mut C> + FromId<C, Error = E>,
|
||||
V: activitypub::Activity,
|
||||
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
|
||||
M: AsObject<A, V, &'a mut C, Error = E> + FromId<C, Error = E>,
|
||||
M::Output: Into<R>,
|
||||
{
|
||||
if let Inbox::NotHandled(ctx, mut act, e) = self {
|
||||
@@ -164,11 +164,6 @@ where
|
||||
Some(x) => x,
|
||||
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)
|
||||
let actor = match A::from_id(
|
||||
ctx,
|
||||
@@ -227,26 +222,6 @@ where
|
||||
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.
|
||||
@@ -289,7 +264,7 @@ pub trait FromId<C>: Sized {
|
||||
/// - `object`: optional object that will be used if the object was not found in the database
|
||||
/// If absent, the ID will be dereferenced.
|
||||
fn from_id(
|
||||
ctx: &C,
|
||||
ctx: &mut C,
|
||||
id: &str,
|
||||
object: Option<Self::Object>,
|
||||
) -> Result<Self, (Option<serde_json::Value>, Self::Error)> {
|
||||
@@ -304,8 +279,9 @@ pub trait FromId<C>: Sized {
|
||||
|
||||
/// Dereferences an ID
|
||||
fn deref(id: &str) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
||||
reqwest::ClientBuilder::new()
|
||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
||||
// Use blocking reqwest API here, since defer cannot be async (yet)
|
||||
reqwest::blocking::Client::builder()
|
||||
.connect_timeout(std::time::Duration::from_secs(5))
|
||||
.build()
|
||||
.map_err(|_| (None, InboxError::DerefError.into()))?
|
||||
.get(id)
|
||||
@@ -321,7 +297,7 @@ pub trait FromId<C>: Sized {
|
||||
)
|
||||
.send()
|
||||
.map_err(|_| (None, InboxError::DerefError))
|
||||
.and_then(|mut r| {
|
||||
.and_then(|r| {
|
||||
let json: serde_json::Value = r
|
||||
.json()
|
||||
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
||||
@@ -332,10 +308,10 @@ pub trait FromId<C>: Sized {
|
||||
}
|
||||
|
||||
/// Builds a `Self` from its ActivityPub representation
|
||||
fn from_activity(ctx: &C, activity: Self::Object) -> Result<Self, Self::Error>;
|
||||
fn from_activity(ctx: &mut C, activity: Self::Object) -> Result<Self, Self::Error>;
|
||||
|
||||
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
|
||||
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
|
||||
fn from_db(ctx: &mut C, id: &str) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
/// Should be implemented by anything representing an ActivityPub actor.
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use activitypub::{Activity, Link, Object};
|
||||
use array_tool::vec::Uniq;
|
||||
use reqwest::r#async::ClientBuilder;
|
||||
use reqwest::ClientBuilder;
|
||||
use rocket::{
|
||||
http::Status,
|
||||
request::{FromRequest, Request},
|
||||
response::{Responder, Response},
|
||||
response::{Responder, Response, Result},
|
||||
Outcome,
|
||||
};
|
||||
use serde_json;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use self::sign::Signable;
|
||||
|
||||
@@ -62,39 +60,45 @@ impl<T> ActivityStream<T> {
|
||||
ActivityStream(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, O: Object> Responder<'r> for ActivityStream<O> {
|
||||
fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> {
|
||||
#[rocket::async_trait]
|
||||
impl<'r, O: Object + Send + 'r> Responder<'r> for ActivityStream<O> {
|
||||
async fn respond_to(self, request: &'r Request<'_>) -> Result<'r> {
|
||||
let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?;
|
||||
json["@context"] = context();
|
||||
serde_json::to_string(&json).respond_to(request).map(|r| {
|
||||
Response::build_from(r)
|
||||
let result = serde_json::to_string(&json).map_err(rocket::response::Debug);
|
||||
match result.respond_to(request).await {
|
||||
Ok(r) => Response::build_from(r)
|
||||
.raw_header("Content-Type", "application/activity+json")
|
||||
.finalize()
|
||||
})
|
||||
.ok(),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApRequest;
|
||||
#[rocket::async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
|
||||
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
|
||||
.headers()
|
||||
.get_one("Accept")
|
||||
.map(|header| {
|
||||
header
|
||||
.split(',')
|
||||
.map(|ct| match ct.trim() {
|
||||
// bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise
|
||||
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\""
|
||||
| "application/ld+json;profile=\"https://w3.org/ns/activitystreams\""
|
||||
| "application/activity+json"
|
||||
| "application/ld+json" => Outcome::Success(ApRequest),
|
||||
"text/html" => Outcome::Forward(true),
|
||||
_ => Outcome::Forward(false),
|
||||
.map(|ct| {
|
||||
match ct.trim() {
|
||||
// bool for Forward: true if found a valid Content-Type for Plume first (HTML),
|
||||
// false otherwise
|
||||
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\""
|
||||
| "application/ld+json;profile=\"https://w3.org/ns/activitystreams\""
|
||||
| "application/activity+json"
|
||||
| "application/ld+json" => Outcome::Success(ApRequest),
|
||||
"text/html" => Outcome::Forward(true),
|
||||
_ => Outcome::Forward(false),
|
||||
}
|
||||
})
|
||||
.fold(Outcome::Forward(false), |out, ct| {
|
||||
if out.clone().forwarded().unwrap_or_else(|| out.is_success()) {
|
||||
@@ -130,36 +134,38 @@ where
|
||||
.sign(sender)
|
||||
.expect("activity_pub::broadcast: signature error");
|
||||
|
||||
let mut rt = tokio::runtime::current_thread::Runtime::new()
|
||||
.expect("Error while initializing tokio runtime for federation");
|
||||
let client = ClientBuilder::new()
|
||||
.connect_timeout(std::time::Duration::from_secs(5))
|
||||
let rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.expect("Can't build client");
|
||||
.expect("Error while initializing tokio runtime for federation");
|
||||
for inbox in boxes {
|
||||
let body = signed.to_string();
|
||||
let mut headers = request::headers();
|
||||
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
|
||||
.post(&inbox)
|
||||
.headers(headers.clone())
|
||||
.header(
|
||||
"Signature",
|
||||
request::signature(sender, &headers)
|
||||
.expect("activity_pub::broadcast: request signature error"),
|
||||
)
|
||||
.header("Signature", sig)
|
||||
.body(body)
|
||||
.send()
|
||||
.and_then(|r| r.into_body().concat2())
|
||||
.await
|
||||
.unwrap()
|
||||
.text()
|
||||
.await
|
||||
.map(move |response| {
|
||||
println!("Successfully sent activity to inbox ({})", inbox);
|
||||
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)]
|
||||
@@ -203,8 +209,7 @@ pub struct PublicKey {
|
||||
pub public_key_pem: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, UnitString)]
|
||||
#[activitystreams(Hashtag)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct HashtagType;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use base64;
|
||||
use chrono::{offset::Utc, DateTime};
|
||||
use openssl::hash::{Hasher, MessageDigest};
|
||||
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use super::request;
|
||||
use base64;
|
||||
use chrono::{naive::NaiveDateTime, DateTime, Duration, Utc};
|
||||
use hex;
|
||||
use openssl::{pkey::PKey, rsa::Rsa, sha::sha256};
|
||||
use rocket::http::HeaderMap;
|
||||
use serde_json;
|
||||
|
||||
/// Returns (public key, private key)
|
||||
pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
#[macro_use]
|
||||
extern crate activitystreams_derive;
|
||||
use activitystreams_traits;
|
||||
|
||||
use serde;
|
||||
#[macro_use]
|
||||
extern crate shrinkwraprs;
|
||||
#[macro_use]
|
||||
|
||||
+37
-51
@@ -1,11 +1,12 @@
|
||||
use heck::CamelCase;
|
||||
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 rocket::{
|
||||
http::uri::Uri,
|
||||
response::{Flash, Redirect},
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use syntect::html::ClassedHTMLGenerator;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
@@ -50,10 +51,10 @@ enum State {
|
||||
|
||||
fn to_inline(tag: Tag<'_>) -> 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::Image(typ, url, title) => Tag::Link(typ, url, title),
|
||||
Tag::Image(url, title) => Tag::Link(url, title),
|
||||
t => t,
|
||||
}
|
||||
}
|
||||
@@ -65,31 +66,21 @@ fn highlight_code<'a>(
|
||||
evt: Event<'a>,
|
||||
) -> Option<Vec<Event<'a>>> {
|
||||
match evt {
|
||||
Event::Start(Tag::CodeBlock(kind)) => {
|
||||
match &kind {
|
||||
CodeBlockKind::Fenced(lang) if !lang.is_empty() => {
|
||||
*context = Some(HighlighterContext { content: vec![] });
|
||||
}
|
||||
_ => {}
|
||||
Event::Start(Tag::CodeBlock(lang)) => {
|
||||
if lang.is_empty() {
|
||||
Some(vec![Event::Start(Tag::CodeBlock(lang))])
|
||||
} 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![];
|
||||
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 = syntax_set.find_syntax_by_token(&lang).unwrap_or_else(|| {
|
||||
let syntax = syntax_set.find_syntax_by_token(&x).unwrap_or_else(|| {
|
||||
syntax_set
|
||||
.find_syntax_by_name(&lang)
|
||||
.find_syntax_by_name(&x)
|
||||
.unwrap_or_else(|| syntax_set.find_syntax_plain_text())
|
||||
});
|
||||
let mut html = ClassedHTMLGenerator::new(&syntax, &syntax_set);
|
||||
@@ -99,7 +90,7 @@ fn highlight_code<'a>(
|
||||
let q = html.finalize();
|
||||
result.push(Event::Html(q.into()));
|
||||
}
|
||||
result.push(Event::End(Tag::CodeBlock(kind)));
|
||||
result.push(Event::End(Tag::CodeBlock(x)));
|
||||
*context = None;
|
||||
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);
|
||||
(Some(prev_txt), vec![])
|
||||
}
|
||||
None => (Some(txt.into_string()), vec![]),
|
||||
None => (Some(txt.into_owned()), vec![]),
|
||||
},
|
||||
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]),
|
||||
},
|
||||
};
|
||||
@@ -165,45 +156,42 @@ fn process_image<'a, 'b>(
|
||||
) -> Event<'a> {
|
||||
if let Some(ref processor) = *processor {
|
||||
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(cw), false) = (cw, inline) {
|
||||
// there is a cw, and where are not inline
|
||||
Event::Html(CowStr::Boxed(
|
||||
format!(
|
||||
r#"<label for="postcontent-cw-{id}">
|
||||
Event::Html(Cow::Owned(format!(
|
||||
r#"<label for="postcontent-cw-{id}">
|
||||
<input type="checkbox" id="postcontent-cw-{id}" checked="checked" class="cw-checkbox">
|
||||
<span class="cw-container">
|
||||
<span class="cw-text">
|
||||
{cw}
|
||||
</span>
|
||||
<img src="{url}" alt=""#,
|
||||
id = random_hex(),
|
||||
cw = cw,
|
||||
url = url
|
||||
)
|
||||
.into(),
|
||||
))
|
||||
id = random_hex(),
|
||||
cw = cw,
|
||||
url = url
|
||||
)))
|
||||
} else {
|
||||
Event::Start(Tag::Image(typ, CowStr::Boxed(url.into()), title))
|
||||
Event::Start(Tag::Image(Cow::Owned(url), title))
|
||||
}
|
||||
} 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 inline || cw.is_none() {
|
||||
Event::End(Tag::Image(typ, CowStr::Boxed(url.into()), title))
|
||||
Event::End(Tag::Image(Cow::Owned(url), title))
|
||||
} else {
|
||||
Event::Html(CowStr::Borrowed(
|
||||
Event::Html(Cow::Borrowed(
|
||||
r#""/>
|
||||
</span>
|
||||
</label>"#,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Event::End(Tag::Image(typ, id, title))
|
||||
Event::End(Tag::Image(id, title))
|
||||
}
|
||||
}
|
||||
e => e,
|
||||
@@ -243,19 +231,19 @@ pub fn md_to_html<'a>(
|
||||
// Ignore headings, images, and tables if inline = true
|
||||
.scan((vec![], inline), inline_tags)
|
||||
.scan(&mut DocumentContext::default(), |ctx, evt| match evt {
|
||||
Event::Start(Tag::CodeBlock(_)) => {
|
||||
Event::Start(Tag::CodeBlock(_)) | Event::Start(Tag::Code) => {
|
||||
ctx.in_code = true;
|
||||
Some((vec![evt], vec![], vec![]))
|
||||
}
|
||||
Event::End(Tag::CodeBlock(_)) => {
|
||||
Event::End(Tag::CodeBlock(_)) | Event::End(Tag::Code) => {
|
||||
ctx.in_code = false;
|
||||
Some((vec![evt], vec![], vec![]))
|
||||
}
|
||||
Event::Start(Tag::Link(_, _, _)) => {
|
||||
Event::Start(Tag::Link(_, _)) => {
|
||||
ctx.in_link = true;
|
||||
Some((vec![evt], vec![], vec![]))
|
||||
}
|
||||
Event::End(Tag::Link(_, _, _)) => {
|
||||
Event::End(Tag::Link(_, _)) => {
|
||||
ctx.in_link = false;
|
||||
Some((vec![evt], vec![], vec![]))
|
||||
}
|
||||
@@ -276,7 +264,6 @@ pub fn md_to_html<'a>(
|
||||
let mention = text_acc;
|
||||
let short_mention = mention.splitn(1, '@').next().unwrap_or("");
|
||||
let link = Tag::Link(
|
||||
LinkType::Inline,
|
||||
format!("{}@/{}/", base_url, &mention).into(),
|
||||
short_mention.to_owned().into(),
|
||||
);
|
||||
@@ -307,8 +294,8 @@ pub fn md_to_html<'a>(
|
||||
}
|
||||
let hashtag = text_acc;
|
||||
let link = Tag::Link(
|
||||
LinkType::Inline,
|
||||
format!("{}tag/{}", base_url, &hashtag).into(),
|
||||
format!("{}tag/{}", base_url, &hashtag.to_camel_case())
|
||||
.into(),
|
||||
hashtag.to_owned().into(),
|
||||
);
|
||||
|
||||
@@ -455,7 +442,6 @@ mod tests {
|
||||
("not_a#hashtag", vec![]),
|
||||
("#نرمافزار_آزاد", vec!["نرمافزار_آزاد"]),
|
||||
("[#hash in link](https://example.org/)", vec![]),
|
||||
("#zwsp\u{200b}inhash", vec!["zwsp"]),
|
||||
];
|
||||
|
||||
for (md, mentions) in tests {
|
||||
@@ -473,11 +459,11 @@ mod tests {
|
||||
fn test_inline() {
|
||||
assert_eq!(
|
||||
md_to_html("# Hello", None, false, None).0,
|
||||
String::from("<h1 dir=\"auto\">Hello</h1>\n")
|
||||
String::from("<h1>Hello</h1>\n")
|
||||
);
|
||||
assert_eq!(
|
||||
md_to_html("# Hello", None, true, None).0,
|
||||
String::from("<p dir=\"auto\">Hello</p>\n")
|
||||
String::from("<p>Hello</p>\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "plume-front"
|
||||
version = "0.6.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Plume contributors"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pre-release-hook = ["cargo", "fmt"]
|
||||
pre-release-replacements = []
|
||||
@@ -269,13 +269,7 @@ pub fn init() -> Result<(), EditorError> {
|
||||
let editor_button = document().create_element("a")?;
|
||||
js! { @{&editor_button}.href = "#"; }
|
||||
editor_button.add_event_listener(|_: ClickEvent| {
|
||||
if window()
|
||||
.local_storage()
|
||||
.insert("basic-editor", "false")
|
||||
.is_err()
|
||||
{
|
||||
console!(log, "Failed to write into local storage");
|
||||
}
|
||||
window().local_storage().remove("basic-editor");
|
||||
window().history().go(0).ok(); // refresh
|
||||
});
|
||||
editor_button.append_child(
|
||||
|
||||
@@ -19,7 +19,6 @@ init_i18n!(
|
||||
en,
|
||||
eo,
|
||||
es,
|
||||
fa,
|
||||
fr,
|
||||
gl,
|
||||
hi,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "plume-macro"
|
||||
version = "0.6.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
|
||||
edition = "2018"
|
||||
description = "Plume procedural macros"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pre-release-hook = ["cargo", "fmt"]
|
||||
pre-release-replacements = []
|
||||
+19
-11
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "plume-models"
|
||||
version = "0.6.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Plume contributors"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -10,28 +10,26 @@ ammonia = "2.1.1"
|
||||
askama_escape = "0.1"
|
||||
bcrypt = "0.5"
|
||||
guid-create = "0.1"
|
||||
futures = "0.3"
|
||||
heck = "0.3.0"
|
||||
itertools = "0.8.0"
|
||||
lazy_static = "1.0"
|
||||
ldap3 = "0.7.1"
|
||||
migrations_internals= "1.4.0"
|
||||
openssl = "0.10.22"
|
||||
rocket = "0.4.5"
|
||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||
reqwest = "0.9"
|
||||
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "async" }
|
||||
scheduled-thread-pool = "0.2.2"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
tantivy = "0.12.0"
|
||||
tantivy = "0.10.1"
|
||||
tokio = "0.2"
|
||||
url = "2.1"
|
||||
walkdir = "2.2"
|
||||
webfinger = "0.4.1"
|
||||
webfinger = { git = "https://github.com/Plume-org/webfinger", rev = "4e8f12810c4a7ba7a07bbcb722cd265fdff512b6", features = ["async"] }
|
||||
whatlang = "0.7.1"
|
||||
shrinkwraprs = "0.2.1"
|
||||
shrinkwraprs = "0.3"
|
||||
diesel-derive-newtype = "0.1.2"
|
||||
glob = "0.3.0"
|
||||
lindera-tantivy = { version = "0.1.3", optional = true }
|
||||
|
||||
[dependencies.chrono]
|
||||
features = ["serde"]
|
||||
@@ -50,10 +48,20 @@ path = "../plume-common"
|
||||
[dependencies.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]
|
||||
diesel_migrations = "1.3.0"
|
||||
diesel_migrations = "1.4.0"
|
||||
|
||||
[features]
|
||||
postgres = ["diesel/postgres", "plume-macro/postgres" ]
|
||||
sqlite = ["diesel/sqlite", "plume-macro/sqlite" ]
|
||||
search-lindera = ["lindera-tantivy"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
pre-release-hook = ["cargo", "fmt"]
|
||||
pre-release-replacements = []
|
||||
@@ -8,11 +8,12 @@ use rocket::{
|
||||
/// Wrapper around User to use as a request guard on pages reserved to admins.
|
||||
pub struct Admin(pub User);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Admin, ()> {
|
||||
let user = request.guard::<User>()?;
|
||||
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||
let user = try_outcome!(User::from_request(request).await);
|
||||
if user.is_admin() {
|
||||
Outcome::Success(Admin(user))
|
||||
} else {
|
||||
@@ -24,11 +25,12 @@ impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
||||
/// Same as `Admin` but for moderators.
|
||||
pub struct Moderator(pub User);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for Moderator {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Moderator, ()> {
|
||||
let user = request.guard::<User>()?;
|
||||
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||
let user = try_outcome!(User::from_request(request).await);
|
||||
if user.is_moderator() {
|
||||
Outcome::Success(Moderator(user))
|
||||
} else {
|
||||
|
||||
@@ -76,32 +76,36 @@ pub enum TokenError {
|
||||
DbError,
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
||||
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();
|
||||
if headers.len() != 1 {
|
||||
return Outcome::Failure((Status::BadRequest, TokenError::NoHeader));
|
||||
}
|
||||
|
||||
let mut parsed_header = headers[0].split(' ');
|
||||
let auth_type = parsed_header.next().map_or_else(
|
||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)),
|
||||
Outcome::Success,
|
||||
)?;
|
||||
let val = parsed_header.next().map_or_else(
|
||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)),
|
||||
Outcome::Success,
|
||||
)?;
|
||||
|
||||
if auth_type == "Bearer" {
|
||||
let conn = request
|
||||
.guard::<DbConn>()
|
||||
.map_failure(|_| (Status::InternalServerError, TokenError::DbError))?;
|
||||
if let Ok(token) = ApiToken::find_by_value(&*conn, val) {
|
||||
return Outcome::Success(token);
|
||||
if let Some(auth_type) = parsed_header.next() {
|
||||
if let Some(val) = parsed_header.next() {
|
||||
if auth_type == "Bearer" {
|
||||
if let Outcome::Success(conn) = DbConn::from_request(request).await {
|
||||
if let Ok(token) = ApiToken::find_by_value(&*conn, val) {
|
||||
return Outcome::Success(token);
|
||||
}
|
||||
} else {
|
||||
return Outcome::Failure((
|
||||
Status::InternalServerError,
|
||||
TokenError::DbError,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Outcome::Failure((Status::BadRequest, TokenError::NoValue));
|
||||
}
|
||||
} else {
|
||||
return Outcome::Failure((Status::BadRequest, TokenError::NoType));
|
||||
}
|
||||
|
||||
Outcome::Forward(())
|
||||
|
||||
+12
-14
@@ -20,7 +20,6 @@ use plume_common::activity_pub::{
|
||||
inbox::{AsActor, FromId},
|
||||
sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
|
||||
};
|
||||
use serde_json;
|
||||
use url::Url;
|
||||
use webfinger::*;
|
||||
|
||||
@@ -71,7 +70,8 @@ impl Blog {
|
||||
insert!(blogs, NewBlog, |inserted, conn| {
|
||||
let instance = inserted.get_instance(conn)?;
|
||||
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() {
|
||||
@@ -132,7 +132,7 @@ impl Blog {
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn find_by_fqn(c: &PlumeRocket, fqn: &str) -> Result<Blog> {
|
||||
pub async fn find_by_fqn(c: &mut PlumeRocket, fqn: &str) -> Result<Blog> {
|
||||
let from_db = blogs::table
|
||||
.filter(blogs::fqn.eq(fqn))
|
||||
.first(&*c.conn)
|
||||
@@ -140,12 +140,13 @@ impl Blog {
|
||||
if let Some(from_db) = from_db {
|
||||
Ok(from_db)
|
||||
} else {
|
||||
Blog::fetch_from_webfinger(c, fqn)
|
||||
Blog::fetch_from_webfinger(c, fqn).await
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<Blog> {
|
||||
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
|
||||
async fn fetch_from_webfinger(c: &mut PlumeRocket, acct: &str) -> Result<Blog> {
|
||||
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)
|
||||
.await?
|
||||
.links
|
||||
.into_iter()
|
||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||
@@ -339,11 +340,11 @@ impl FromId<PlumeRocket> for Blog {
|
||||
type Error = Error;
|
||||
type Object = CustomGroup;
|
||||
|
||||
fn from_db(c: &PlumeRocket, id: &str) -> Result<Self> {
|
||||
fn from_db(c: &mut PlumeRocket, id: &str) -> Result<Self> {
|
||||
Self::find_by_ap_url(&c.conn, id)
|
||||
}
|
||||
|
||||
fn from_activity(c: &PlumeRocket, acct: CustomGroup) -> Result<Self> {
|
||||
fn from_activity(c: &mut PlumeRocket, acct: CustomGroup) -> Result<Self> {
|
||||
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
||||
let inst = url.host_str()?;
|
||||
let instance = Instance::find_by_domain(&c.conn, inst).or_else(|_| {
|
||||
@@ -435,7 +436,7 @@ impl FromId<PlumeRocket> for Blog {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsActor<&PlumeRocket> for Blog {
|
||||
impl AsActor<&mut PlumeRocket> for Blog {
|
||||
fn get_inbox_url(&self) -> String {
|
||||
self.inbox_url.clone()
|
||||
}
|
||||
@@ -498,7 +499,6 @@ pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
blog_authors::*,
|
||||
config::CONFIG,
|
||||
instance::tests as instance_tests,
|
||||
medias::NewMedia,
|
||||
search::tests::get_searcher,
|
||||
@@ -768,9 +768,7 @@ pub(crate) mod tests {
|
||||
conn.test_transaction::<_, (), _>(|| {
|
||||
let (_, blogs) = fill_database(conn);
|
||||
|
||||
blogs[0]
|
||||
.delete(conn, &get_searcher(&CONFIG.search_tokenizers))
|
||||
.unwrap();
|
||||
blogs[0].delete(conn, &get_searcher()).unwrap();
|
||||
assert!(Blog::get(conn, blogs[0].id).is_err());
|
||||
Ok(())
|
||||
})
|
||||
@@ -780,7 +778,7 @@ pub(crate) mod tests {
|
||||
fn delete_via_user() {
|
||||
let conn = &db();
|
||||
conn.test_transaction::<_, (), _>(|| {
|
||||
let searcher = get_searcher(&CONFIG.search_tokenizers);
|
||||
let searcher = get_searcher();
|
||||
let (user, _) = fill_database(conn);
|
||||
|
||||
let b1 = Blog::insert(
|
||||
|
||||
@@ -24,7 +24,6 @@ use plume_common::{
|
||||
},
|
||||
utils,
|
||||
};
|
||||
use serde_json;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Queryable, Identifiable, Clone, AsChangeset)]
|
||||
@@ -105,7 +104,7 @@ impl Comment {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, c: &PlumeRocket) -> Result<Note> {
|
||||
pub async fn to_activity(&self, c: &mut PlumeRocket) -> Result<Note> {
|
||||
let author = User::get(&c.conn, self.author_id)?;
|
||||
let (html, mentions, _hashtags) = utils::md_to_html(
|
||||
self.content.get().as_ref(),
|
||||
@@ -131,19 +130,23 @@ impl Comment {
|
||||
.set_published_string(chrono::Utc::now().to_rfc3339())?;
|
||||
note.object_props.set_attributed_to_link(author.into_id())?;
|
||||
note.object_props.set_to_link_vec(to)?;
|
||||
note.object_props.set_tag_link_vec(
|
||||
mentions
|
||||
.into_iter()
|
||||
.filter_map(|m| Mention::build_activity(c, &m).ok())
|
||||
.collect::<Vec<link::Mention>>(),
|
||||
)?;
|
||||
|
||||
let mut tag_link_vec = vec![];
|
||||
let mut iter = mentions.into_iter();
|
||||
while let Some(m) = iter.next() {
|
||||
if let Ok(a) = Mention::build_activity(c, &m).await {
|
||||
tag_link_vec.push(a);
|
||||
}
|
||||
}
|
||||
note.object_props.set_tag_link_vec(tag_link_vec)?;
|
||||
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
pub fn create_activity(&self, c: &PlumeRocket) -> Result<Create> {
|
||||
pub async fn create_activity(&self, c: &mut PlumeRocket) -> Result<Create> {
|
||||
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();
|
||||
act.create_props.set_actor_link(author.into_id())?;
|
||||
act.create_props.set_object_object(note.clone())?;
|
||||
@@ -198,11 +201,11 @@ impl FromId<PlumeRocket> for Comment {
|
||||
type Error = Error;
|
||||
type Object = Note;
|
||||
|
||||
fn from_db(c: &PlumeRocket, id: &str) -> Result<Self> {
|
||||
fn from_db(c: &mut PlumeRocket, id: &str) -> Result<Self> {
|
||||
Self::find_by_ap_url(&c.conn, id)
|
||||
}
|
||||
|
||||
fn from_activity(c: &PlumeRocket, note: Note) -> Result<Self> {
|
||||
fn from_activity(c: &mut PlumeRocket, note: Note) -> Result<Self> {
|
||||
let conn = &*c.conn;
|
||||
let comm = {
|
||||
let previous_url = note.object_props.in_reply_to.as_ref()?.as_str()?;
|
||||
@@ -322,21 +325,21 @@ impl FromId<PlumeRocket> for Comment {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsObject<User, Create, &PlumeRocket> for Comment {
|
||||
impl AsObject<User, Create, &mut PlumeRocket> for Comment {
|
||||
type Error = Error;
|
||||
type Output = Self;
|
||||
|
||||
fn activity(self, _c: &PlumeRocket, _actor: User, _id: &str) -> Result<Self> {
|
||||
fn activity(self, _c: &mut PlumeRocket, _actor: User, _id: &str) -> Result<Self> {
|
||||
// The actual creation takes place in the FromId impl
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsObject<User, Delete, &PlumeRocket> for Comment {
|
||||
impl AsObject<User, Delete, &mut PlumeRocket> for Comment {
|
||||
type Error = Error;
|
||||
type Output = ();
|
||||
|
||||
fn activity(self, c: &PlumeRocket, actor: User, _id: &str) -> Result<()> {
|
||||
fn activity(self, c: &mut PlumeRocket, actor: User, _id: &str) -> Result<()> {
|
||||
if self.author_id != actor.id {
|
||||
return Err(Error::Unauthorized);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::search::TokenizerKind as SearchTokenizer;
|
||||
use rocket::config::Limits;
|
||||
use rocket::Config as RocketConfig;
|
||||
use std::env::{self, var};
|
||||
@@ -15,12 +14,10 @@ pub struct Config {
|
||||
pub db_max_size: Option<u32>,
|
||||
pub db_min_idle: Option<u32>,
|
||||
pub search_index: String,
|
||||
pub search_tokenizers: SearchTokenizerConfig,
|
||||
pub rocket: Result<RocketConfig, RocketError>,
|
||||
pub logo: LogoConfig,
|
||||
pub default_theme: String,
|
||||
pub media_directory: String,
|
||||
pub ldap: Option<LdapConfig>,
|
||||
}
|
||||
|
||||
#[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! {
|
||||
pub static ref CONFIG: Config = Config {
|
||||
base_url: var("BASE_URL").unwrap_or_else(|_| format!(
|
||||
@@ -298,12 +209,10 @@ lazy_static! {
|
||||
#[cfg(feature = "sqlite")]
|
||||
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_tokenizers: SearchTokenizerConfig::init(),
|
||||
rocket: get_rocket_config(),
|
||||
logo: LogoConfig::default(),
|
||||
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
|
||||
media_directory: var("MEDIA_UPLOAD_DIRECTORY")
|
||||
.unwrap_or_else(|_| "static/media".to_owned()),
|
||||
ldap: get_ldap_config(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use diesel::{dsl::sql_query, ConnectionError, RunQueryDsl};
|
||||
use rocket::{
|
||||
http::Status,
|
||||
request::{self, FromRequest},
|
||||
Outcome, Request, State,
|
||||
Outcome, Request,
|
||||
};
|
||||
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
|
||||
/// no pool is currently managed, fails with an `InternalServerError` status. If
|
||||
/// no connections are available, fails with a `ServiceUnavailable` status.
|
||||
#[rocket::async_trait]
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||
let pool = request.guard::<State<'_, DbPool>>()?;
|
||||
match pool.get() {
|
||||
Ok(conn) => Outcome::Success(DbConn(conn)),
|
||||
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
|
||||
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||
match DbConn::from_request(request).await {
|
||||
Outcome::Success(a) => Outcome::Success(a),
|
||||
_ => Outcome::Failure((Status::ServiceUnavailable, ())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,11 +136,11 @@ impl Follow {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsObject<User, FollowAct, &PlumeRocket> for User {
|
||||
impl AsObject<User, FollowAct, &mut PlumeRocket> for User {
|
||||
type Error = Error;
|
||||
type Output = Follow;
|
||||
|
||||
fn activity(self, c: &PlumeRocket, actor: User, id: &str) -> Result<Follow> {
|
||||
fn activity(self, c: &mut PlumeRocket, actor: User, id: &str) -> Result<Follow> {
|
||||
// Mastodon (at least) requires the full Follow object when accepting it,
|
||||
// so we rebuilt it here
|
||||
let mut follow = FollowAct::default();
|
||||
@@ -156,11 +156,11 @@ impl FromId<PlumeRocket> for Follow {
|
||||
type Error = Error;
|
||||
type Object = FollowAct;
|
||||
|
||||
fn from_db(c: &PlumeRocket, id: &str) -> Result<Self> {
|
||||
fn from_db(c: &mut PlumeRocket, id: &str) -> Result<Self> {
|
||||
Follow::find_by_ap_url(&c.conn, id)
|
||||
}
|
||||
|
||||
fn from_activity(c: &PlumeRocket, follow: FollowAct) -> Result<Self> {
|
||||
fn from_activity(c: &mut PlumeRocket, follow: FollowAct) -> Result<Self> {
|
||||
let actor =
|
||||
User::from_id(c, &follow.follow_props.actor_link::<Id>()?, None).map_err(|(_, e)| e)?;
|
||||
|
||||
@@ -170,11 +170,11 @@ impl FromId<PlumeRocket> for Follow {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsObject<User, Undo, &PlumeRocket> for Follow {
|
||||
impl AsObject<User, Undo, &mut PlumeRocket> for Follow {
|
||||
type Error = Error;
|
||||
type Output = ();
|
||||
|
||||
fn activity(self, c: &PlumeRocket, actor: User, _id: &str) -> Result<()> {
|
||||
fn activity(self, c: &mut PlumeRocket, actor: User, _id: &str) -> Result<()> {
|
||||
let conn = &*c.conn;
|
||||
if self.follower_id == actor.id {
|
||||
diesel::delete(&self).execute(conn)?;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user