Compare commits
178 Commits
0.7.2
...
fix-delete-user
| Author | SHA1 | Date | |
|---|---|---|---|
| e1777e9071 | |||
| 613ccbcd94 | |||
| b9a09a2511 | |||
| 213628e400 | |||
| d6bb2bfb72 | |||
| 33bd290679 | |||
| 85ab5393fd | |||
| 98c73bb6df | |||
| 3e9d9a459f | |||
| a394c3f210 | |||
| a1a19e091a | |||
| ec030d500d | |||
| cfa74f84e7 | |||
| 97cbe7f446 | |||
| 7e4d081027 | |||
| 1e5ae92135 | |||
| 036ee6fac4 | |||
| 6028295748 | |||
| aa4cfd374d | |||
| 3303a4af84 | |||
| 37a136787b | |||
| 300ff37694 | |||
| c1d9d39dc1 | |||
| 93d6ee04d4 | |||
| ae7bf2e132 | |||
| 0020242571 | |||
| 4f796e788c | |||
| 3d192c1179 | |||
| 2f8d188d59 | |||
| 19766662f1 | |||
| 301aad3f73 | |||
| 92a8f8aa4c | |||
| 0c856a5252 | |||
| 2df6138ff1 | |||
| b2942f3f47 | |||
| 94f20c8fc2 | |||
| 5d48b93c8b | |||
| bbf2e00920 | |||
| c97361f5f4 | |||
| 7c799e8abf | |||
| d196e1dbd0 | |||
| 1679315322 | |||
| dd3a5f4a5b | |||
| 3580fb04fa | |||
| 699fdc30d9 | |||
| 704e9aa47f | |||
| d741238ccb | |||
| 9776374d17 | |||
| 2d10ddb9fa | |||
| e746a0b03f | |||
| 85cacf4239 | |||
| f138ae6ed9 | |||
| 399af4004a | |||
| d36f13e984 | |||
| 9a3699160d | |||
| 4103e7513d | |||
| ed9970b102 | |||
| afa875366e | |||
| 9696f04c64 | |||
| 40e1a1fc2c | |||
| ee1e553460 | |||
| d20ce6dd0b | |||
| 72f7909a42 | |||
| 4e1fb64868 | |||
| 85c1bfa300 | |||
| 172c78c41d | |||
| 3b08d5b485 | |||
| 832479a706 | |||
| 3b3148fa6b | |||
| b38d55f486 | |||
| 2804a490ed | |||
| 8c098def61 | |||
| e10ddb50c0 | |||
| 4df2c3e6f6 | |||
| 2f53fc78b6 | |||
| fded87654d | |||
| 08cd777f81 | |||
| 96b88353c5 | |||
| 302026feb9 | |||
| ba6d322da7 | |||
| 488563e9c1 | |||
| 130bb4c102 | |||
| 9368aebe70 | |||
| ca2843822e | |||
| bd91b4a346 | |||
| 35b951967d | |||
| 63d2cf91e9 | |||
| 263cf9e04f | |||
| 22ebecba67 | |||
| 903b48ed12 | |||
| a550291c85 | |||
| 47394fc620 | |||
| b180089b1b | |||
| a275aa5965 | |||
| 87edb2486c | |||
| 10617f3144 | |||
| 6654ad28b7 | |||
| 771d4325c2 | |||
| 1536a6d3f3 | |||
| 620726cc25 | |||
| 0eef7c0b89 | |||
| 321e40ea3f | |||
| a218b4ea4f | |||
| 9613ccd0c3 | |||
| 9493c1ad06 | |||
| e92ac1a13f | |||
| 1517b4d91e | |||
| 38cc4c043d | |||
| 05c1d727dc | |||
| 84645c7ed9 | |||
| 7c505bde7f | |||
| 9f543f1b6b | |||
| 0f7b882749 | |||
| f9f4375a40 | |||
| 12c2848cc7 | |||
| 4cfb3e2494 | |||
| 090b0a6f0d | |||
| 4502b77094 | |||
| 8f5a86206a | |||
| 340157f80d | |||
| 16b10695df | |||
| b8eb631aa3 | |||
| 5c9094fede | |||
| 4e2ca515ce | |||
| eccfbd3fbc | |||
| 8408342b5d | |||
| c47921bb25 | |||
| 03f470f04c | |||
| 5770c3b85b | |||
| c92f46b2c9 | |||
| 69eba69528 | |||
| c302d842e0 | |||
| f660220495 | |||
| 155df7bdf0 | |||
| 29055d1957 | |||
| d6ee49b880 | |||
| 248ed265c4 | |||
| a1f958ee7a | |||
| abf352b957 | |||
| 393f8e5e0c | |||
| 4dfe300ee3 | |||
| 65829094c9 | |||
| d5c3e6d6f0 | |||
| d702dd2fae | |||
| 0d855823c9 | |||
| 2dd33769d4 | |||
| 4ea9f6ecf1 | |||
| 3b0b6c4b0b | |||
| 4328fad5a3 | |||
| b26822c045 | |||
| f372282b04 | |||
| 145253ccbf | |||
| 485223a3dd | |||
| 7f75fa74e7 | |||
| 821fce1903 | |||
| ce484de61e | |||
| 35fb57718d | |||
| 35d12d7cae | |||
| 846154efe1 | |||
| 1ec7acbdfe | |||
| e384fdfcff | |||
| ed58e44d2e | |||
| f151dee339 | |||
| 61f25941e8 | |||
| 0628a14be6 | |||
| b46ae83377 | |||
| 70bc7f8edf | |||
| aff481b947 | |||
| 29ef73d307 | |||
| db205d0d9d | |||
| 57ab7edf23 | |||
| e4bd9d65cf | |||
| 17e4ddb32d | |||
| 4fd85b30f1 | |||
| 6148f29c66 | |||
| 1a3fad2d6a | |||
| 0945d3bc53 | |||
| 7f63d2a129 |
@@ -10,7 +10,7 @@ executors:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
docker:
|
docker:
|
||||||
- image: plumeorg/plume-buildenv:v0.4.0
|
- image: plumeorg/plume-buildenv:v0.8.0
|
||||||
- image: <<#parameters.postgres>>cimg/postgres:14.2<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
|
- image: <<#parameters.postgres>>cimg/postgres:14.2<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
@@ -38,7 +38,7 @@ commands:
|
|||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-{{ .Branch }}
|
- v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-{{ .Branch }}
|
||||||
- v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-master
|
- v0-<< parameters.cache >>-{{ checksum "Cargo.lock" }}-main
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
description: push cache
|
description: push cache
|
||||||
@@ -63,6 +63,7 @@ commands:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
steps:
|
steps:
|
||||||
|
- run: rustup component add clippy --toolchain nightly-2022-07-19-x86_64-unknown-linux-gnu
|
||||||
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings
|
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings
|
||||||
|
|
||||||
run_with_coverage:
|
run_with_coverage:
|
||||||
@@ -111,6 +112,7 @@ jobs:
|
|||||||
name: default
|
name: default
|
||||||
steps:
|
steps:
|
||||||
- restore_env
|
- restore_env
|
||||||
|
- run: rustup component add rustfmt --toolchain nightly-2022-07-19-x86_64-unknown-linux-gnu
|
||||||
- run: cargo fmt --all -- --check
|
- run: cargo fmt --all -- --check
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
@@ -258,4 +260,4 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- /^master/
|
- /^main/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM debian:buster-20210208
|
FROM rust:1
|
||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
#install native/circleci/build dependancies
|
#install native/circleci/build dependancies
|
||||||
@@ -6,19 +6,19 @@ RUN apt update &&\
|
|||||||
apt install -y --no-install-recommends git ssh tar gzip ca-certificates default-jre&&\
|
apt install -y --no-install-recommends git ssh tar gzip ca-certificates default-jre&&\
|
||||||
echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \
|
echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \
|
||||||
| tee -a /etc/apt/sources.list.d/caddy-fury.list &&\
|
| tee -a /etc/apt/sources.list.d/caddy-fury.list &&\
|
||||||
|
wget -qO - https://artifacts.crowdin.com/repo/GPG-KEY-crowdin | apt-key add - &&\
|
||||||
|
echo "deb https://artifacts.crowdin.com/repo/deb/ /" > /etc/apt/sources.list.d/crowdin.list &&\
|
||||||
apt update &&\
|
apt update &&\
|
||||||
apt install -y --no-install-recommends binutils-dev build-essential cmake curl gcc gettext git libcurl4-openssl-dev libdw-dev libelf-dev libiberty-dev libpq-dev libsqlite3-dev libssl-dev make openssl pkg-config postgresql postgresql-contrib python zlib1g-dev python3-pip zip unzip libclang-dev clang caddy&&\
|
apt install -y --no-install-recommends binutils-dev build-essential cmake curl gcc gettext git libcurl4-openssl-dev libdw-dev libelf-dev libiberty-dev libpq-dev libsqlite3-dev libssl-dev make openssl pkg-config postgresql postgresql-contrib python zlib1g-dev python3-dev python3-pip python3-setuptools zip unzip libclang-dev clang caddy crowdin3 &&\
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
#install and configure rust
|
#stick rust environment
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-11-27 -y &&\
|
COPY rust-toolchain ./
|
||||||
rustup component add rustfmt clippy &&\
|
RUN rustup component add rustfmt clippy
|
||||||
rustup component add rust-std --target wasm32-unknown-unknown
|
|
||||||
|
|
||||||
#compile some deps
|
#compile some deps
|
||||||
RUN cargo install wasm-pack &&\
|
RUN cargo install wasm-pack &&\
|
||||||
cargo install grcov &&\
|
cargo install grcov &&\
|
||||||
strip /root/.cargo/bin/* &&\
|
|
||||||
rm -fr ~/.cargo/registry
|
rm -fr ~/.cargo/registry
|
||||||
|
|
||||||
#set some compilation parametters
|
#set some compilation parametters
|
||||||
@@ -29,11 +29,3 @@ RUN pip3 install selenium
|
|||||||
|
|
||||||
#configure caddy
|
#configure caddy
|
||||||
COPY Caddyfile /Caddyfile
|
COPY Caddyfile /Caddyfile
|
||||||
|
|
||||||
#install crowdin
|
|
||||||
RUN mkdir /crowdin && cd /crowdin &&\
|
|
||||||
curl -O https://downloads.crowdin.com/cli/v2/crowdin-cli.zip &&\
|
|
||||||
unzip crowdin-cli.zip && rm crowdin-cli.zip &&\
|
|
||||||
cd * && mv crowdin-cli.jar /usr/local/bin && cd && rm -rf /crowdin &&\
|
|
||||||
/bin/echo -e '#!/bin/sh\njava -jar /usr/local/bin/crowdin-cli.jar $@' > /usr/local/bin/crowdin &&\
|
|
||||||
chmod +x /usr/local/bin/crowdin
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
nightly-2022-07-19
|
||||||
@@ -3,3 +3,5 @@ data
|
|||||||
Dockerfile
|
Dockerfile
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
.env
|
.env
|
||||||
|
target
|
||||||
|
data
|
||||||
|
|||||||
@@ -11,20 +11,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
-
|
-
|
||||||
name: Login to DockerHub
|
name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build and push
|
name: Build and push
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: plumeorg/plume:latest
|
tags: plumeorg/plume:latest
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
@@ -23,14 +23,14 @@ jobs:
|
|||||||
images: plumeorg/plume
|
images: plumeorg/plume
|
||||||
-
|
-
|
||||||
name: Login to DockerHub
|
name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Build and push
|
name: Build and push
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
@@ -4,6 +4,25 @@
|
|||||||
|
|
||||||
## [Unreleased] - ReleaseDate
|
## [Unreleased] - ReleaseDate
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add 'My feed' to i18n timeline name (#1084)
|
||||||
|
- Bidirectional support for user page header (#1092)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Use blog title as slug (#1094, #1126, #1127)
|
||||||
|
- Bump Rust to nightly 2022-07-19 (#1119)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Malfunction while creating a blog post in Persian (#1116)
|
||||||
|
- Email block list is ignored when email sign-up (#1122)
|
||||||
|
- Bug that some Activity Sytreams properties are not parsed properly (#1129)
|
||||||
|
- Allow empty avatar for remote users (#1129)
|
||||||
|
- Percent encode blog FQN for federation interoperability (#1129)
|
||||||
|
- The same to `preferredUsername` (#1129)
|
||||||
|
|
||||||
## [[0.7.2]] - 2022-05-11
|
## [[0.7.2]] - 2022-05-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
Generated
+871
-695
File diff suppressed because it is too large
Load Diff
+12
-12
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
name = "plume"
|
name = "plume"
|
||||||
version = "0.7.2"
|
version = "0.7.3-dev"
|
||||||
repository = "https://github.com/Plume-org/Plume"
|
repository = "https://github.com/Plume-org/Plume"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atom_syndication = "0.11.0"
|
atom_syndication = "0.12.0"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
gettext = "0.4.0"
|
gettext = "0.4.0"
|
||||||
@@ -15,19 +15,19 @@ gettext-utils = "0.1.0"
|
|||||||
guid-create = "0.2"
|
guid-create = "0.2"
|
||||||
lettre_email = "0.9.2"
|
lettre_email = "0.9.2"
|
||||||
num_cpus = "1.10"
|
num_cpus = "1.10"
|
||||||
rocket = "0.4.6"
|
rocket = "0.4.11"
|
||||||
rocket_contrib = { version = "0.4.5", features = ["json"] }
|
rocket_contrib = { version = "0.4.11", features = ["json"] }
|
||||||
rocket_i18n = "0.4.1"
|
rocket_i18n = "0.4.1"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.6"
|
||||||
serde = "1.0"
|
serde = "1.0.137"
|
||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
validator = { version = "0.15", features = ["derive"] }
|
validator = { version = "0.15", features = ["derive"] }
|
||||||
webfinger = "0.4.1"
|
webfinger = "0.4.1"
|
||||||
tracing = "0.1.34"
|
tracing = "0.1.35"
|
||||||
tracing-subscriber = "0.3.10"
|
tracing-subscriber = "0.3.10"
|
||||||
riker = "0.4.2"
|
riker = "0.4.2"
|
||||||
activitystreams = "0.7.0-alpha.18"
|
activitystreams = "=0.7.0-alpha.20"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "plume"
|
name = "plume"
|
||||||
@@ -60,12 +60,12 @@ path = "plume-common"
|
|||||||
path = "plume-models"
|
path = "plume-models"
|
||||||
|
|
||||||
[dependencies.rocket_csrf]
|
[dependencies.rocket_csrf]
|
||||||
git = "https://github.com/fdb-hiroshima/rocket_csrf"
|
git = "https://git.joinplu.me/plume/rocket_csrf"
|
||||||
rev = "29910f2829e7e590a540da3804336577b48c7b31"
|
rev = "0.1.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ructe = "0.14.0"
|
ructe = "0.15.0"
|
||||||
rsass = "0.24"
|
rsass = "0.26"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["postgres"]
|
default = ["postgres"]
|
||||||
|
|||||||
+3
-5
@@ -1,4 +1,4 @@
|
|||||||
FROM rust:1-buster as builder
|
FROM rust:1 as builder
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@@ -18,17 +18,15 @@ COPY script/wasm-deps.sh .
|
|||||||
RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh
|
RUN chmod a+x ./wasm-deps.sh && sleep 1 && ./wasm-deps.sh
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY Cargo.toml Cargo.lock rust-toolchain ./
|
|
||||||
RUN cargo install wasm-pack
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN cargo install wasm-pack
|
||||||
RUN chmod a+x ./script/plume-front.sh && sleep 1 && ./script/plume-front.sh
|
RUN chmod a+x ./script/plume-front.sh && sleep 1 && ./script/plume-front.sh
|
||||||
RUN cargo install --path ./ --force --no-default-features --features postgres
|
RUN cargo install --path ./ --force --no-default-features --features postgres
|
||||||
RUN cargo install --path plume-cli --force --no-default-features --features postgres
|
RUN cargo install --path plume-cli --force --no-default-features --features postgres
|
||||||
RUN cargo clean
|
RUN cargo clean
|
||||||
|
|
||||||
FROM debian:buster-slim
|
FROM debian:stable-slim
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<img src="https://raw.githubusercontent.com/Plume-org/Plume/master/assets/icons/trwnh/feather/plumeFeather64.png" alt="Plume's logo">
|
<img src="https://raw.githubusercontent.com/Plume-org/Plume/main/assets/icons/trwnh/feather/plumeFeather64.png" alt="Plume's logo">
|
||||||
Plume
|
Plume
|
||||||
</h1>
|
</h1>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/Plume-org/Plume/"><img alt="CircleCI" src="https://img.shields.io/circleci/build/gh/Plume-org/Plume.svg"></a>
|
<a href="https://github.com/Plume-org/Plume/"><img alt="CircleCI" src="https://img.shields.io/circleci/build/gh/Plume-org/Plume.svg"></a>
|
||||||
<a href="https://codecov.io/gh/Plume-org/Plume"><img src="https://codecov.io/gh/Plume-org/Plume/branch/master/graph/badge.svg" alt="Code coverage"></a>
|
<a href="https://codecov.io/gh/Plume-org/Plume"><img src="https://codecov.io/gh/Plume-org/Plume/branch/main/graph/badge.svg" alt="Code coverage"></a>
|
||||||
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/plume"><img src="https://d322cqt584bo4o.cloudfront.net/plume/localized.svg"></a>
|
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/plume"><img src="https://d322cqt584bo4o.cloudfront.net/plume/localized.svg"></a>
|
||||||
<a href="https://hub.docker.com/r/plumeorg/plume"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/plumeorg/plume.svg"></a>
|
<a href="https://hub.docker.com/r/plumeorg/plume"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/plumeorg/plume.svg"></a>
|
||||||
<a href="https://liberapay.com/Plume"><img alt="Liberapay patrons" src="https://img.shields.io/liberapay/patrons/Plume.svg"></a>
|
<a href="https://liberapay.com/Plume"><img alt="Liberapay patrons" src="https://img.shields.io/liberapay/patrons/Plume.svg"></a>
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ form.new-post {
|
|||||||
|
|
||||||
.button + .button {
|
.button + .button {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
|
margin-inline-start: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.split {
|
.split {
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ authors = ["Plume contributors"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = "1.0"
|
serde = "1.0.137"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pre-release-hook = ["cargo", "fmt"]
|
pre-release-hook = ["cargo", "fmt"]
|
||||||
pre-release-replacements = []
|
pre-release-replacements = []
|
||||||
|
release = false
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pre-release-hook = ["cargo", "fmt"]
|
pre-release-hook = ["cargo", "fmt"]
|
||||||
pre-release-replacements = []
|
pre-release-replacements = []
|
||||||
|
release = false
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
|
use plume_models::{blogs::Blog, instance::Instance, lists::*, users::User, Connection};
|
||||||
|
|
||||||
|
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
||||||
|
SubCommand::with_name("lists")
|
||||||
|
.about("Manage lists")
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("new")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of this list"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("type")
|
||||||
|
.short("t")
|
||||||
|
.long("type")
|
||||||
|
.takes_value(true)
|
||||||
|
.help(
|
||||||
|
r#"The type of this list (one of "user", "blog", "word" or "prefix")"#,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Username of whom this list is for. Empty for an instance list"),
|
||||||
|
)
|
||||||
|
.about("Create a new list"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("delete")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of the list to delete"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Username of whom this list was for. Empty for instance list"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("yes")
|
||||||
|
.short("y")
|
||||||
|
.long("yes")
|
||||||
|
.help("Confirm the deletion"),
|
||||||
|
)
|
||||||
|
.about("Delete a list"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("add")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of the list to add an element to"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Username of whom this list is for. Empty for instance list"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("value")
|
||||||
|
.short("v")
|
||||||
|
.long("value")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The value to add"),
|
||||||
|
)
|
||||||
|
.about("Add element to a list"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("rm")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of the list to remove an element from"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Username of whom this list is for. Empty for instance list"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("value")
|
||||||
|
.short("v")
|
||||||
|
.long("value")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The value to remove"),
|
||||||
|
)
|
||||||
|
.about("Remove element from list"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
||||||
|
let conn = conn;
|
||||||
|
match args.subcommand() {
|
||||||
|
("new", Some(x)) => new(x, conn),
|
||||||
|
("delete", Some(x)) => delete(x, conn),
|
||||||
|
("add", Some(x)) => add(x, conn),
|
||||||
|
("rm", Some(x)) => rm(x, conn),
|
||||||
|
("", None) => command().print_help().unwrap(),
|
||||||
|
_ => println!("Unknown subcommand"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_list_identifier(args: &ArgMatches<'_>) -> (String, Option<String>) {
|
||||||
|
let name = args
|
||||||
|
.value_of("name")
|
||||||
|
.map(String::from)
|
||||||
|
.expect("No name provided for the list");
|
||||||
|
let user = args.value_of("user").map(String::from);
|
||||||
|
(name, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_list_type(args: &ArgMatches<'_>) -> ListType {
|
||||||
|
let typ = args
|
||||||
|
.value_of("type")
|
||||||
|
.map(String::from)
|
||||||
|
.expect("No name type for the list");
|
||||||
|
match typ.as_str() {
|
||||||
|
"user" => ListType::User,
|
||||||
|
"blog" => ListType::Blog,
|
||||||
|
"word" => ListType::Word,
|
||||||
|
"prefix" => ListType::Prefix,
|
||||||
|
_ => panic!("Invalid list type: {}", typ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_value(args: &ArgMatches<'_>) -> String {
|
||||||
|
args.value_of("value")
|
||||||
|
.map(String::from)
|
||||||
|
.expect("No query provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_user(username: &str, conn: &Connection) -> User {
|
||||||
|
let instance = Instance::get_local_uncached(conn).expect("Failed to load local instance");
|
||||||
|
|
||||||
|
User::find_by_name(conn, username, instance.id).expect("User not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_list_identifier(args);
|
||||||
|
let typ = get_list_type(args);
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
List::new(conn, &name, user.as_ref(), typ).expect("failed to create list");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_list_identifier(args);
|
||||||
|
|
||||||
|
if !args.is_present("yes") {
|
||||||
|
panic!("Warning, this operation is destructive. Add --yes to confirm you want to do it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
let list =
|
||||||
|
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
|
||||||
|
|
||||||
|
list.delete(conn).expect("Failed to update list");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_list_identifier(args);
|
||||||
|
let value = get_value(args);
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
let list =
|
||||||
|
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
|
||||||
|
|
||||||
|
match list.kind() {
|
||||||
|
ListType::Blog => {
|
||||||
|
let blog_id = Blog::find_by_fqn(conn, &value).expect("unknown blog").id;
|
||||||
|
if !list.contains_blog(conn, blog_id).unwrap() {
|
||||||
|
list.add_blogs(conn, &[blog_id]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType::User => {
|
||||||
|
let user_id = User::find_by_fqn(conn, &value).expect("unknown user").id;
|
||||||
|
if !list.contains_user(conn, user_id).unwrap() {
|
||||||
|
list.add_users(conn, &[user_id]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType::Word => {
|
||||||
|
if !list.contains_word(conn, &value).unwrap() {
|
||||||
|
list.add_words(conn, &[&value]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType::Prefix => {
|
||||||
|
if !list.contains_prefix(conn, &value).unwrap() {
|
||||||
|
list.add_prefixes(conn, &[&value]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rm(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_list_identifier(args);
|
||||||
|
let value = get_value(args);
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
let list =
|
||||||
|
List::find_for_user_by_name(conn, user.map(|u| u.id), &name).expect("list not found");
|
||||||
|
|
||||||
|
match list.kind() {
|
||||||
|
ListType::Blog => {
|
||||||
|
let blog_id = Blog::find_by_fqn(conn, &value).expect("unknown blog").id;
|
||||||
|
let mut blogs = list.list_blogs(conn).unwrap();
|
||||||
|
if let Some(index) = blogs.iter().position(|b| b.id == blog_id) {
|
||||||
|
blogs.swap_remove(index);
|
||||||
|
let blogs = blogs.iter().map(|b| b.id).collect::<Vec<_>>();
|
||||||
|
list.set_blogs(conn, &blogs).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType::User => {
|
||||||
|
let user_id = User::find_by_fqn(conn, &value).expect("unknown user").id;
|
||||||
|
let mut users = list.list_users(conn).unwrap();
|
||||||
|
if let Some(index) = users.iter().position(|u| u.id == user_id) {
|
||||||
|
users.swap_remove(index);
|
||||||
|
let users = users.iter().map(|u| u.id).collect::<Vec<_>>();
|
||||||
|
list.set_users(conn, &users).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType::Word => {
|
||||||
|
let mut words = list.list_words(conn).unwrap();
|
||||||
|
if let Some(index) = words.iter().position(|w| *w == value) {
|
||||||
|
words.swap_remove(index);
|
||||||
|
let words = words.iter().map(String::as_str).collect::<Vec<_>>();
|
||||||
|
list.set_words(conn, &words).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType::Prefix => {
|
||||||
|
let mut prefixes = list.list_prefixes(conn).unwrap();
|
||||||
|
if let Some(index) = prefixes.iter().position(|p| *p == value) {
|
||||||
|
prefixes.swap_remove(index);
|
||||||
|
let prefixes = prefixes.iter().map(String::as_str).collect::<Vec<_>>();
|
||||||
|
list.set_prefixes(conn, &prefixes).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ use plume_models::{instance::Instance, Connection as Conn, CONFIG};
|
|||||||
use std::io::{self, prelude::*};
|
use std::io::{self, prelude::*};
|
||||||
|
|
||||||
mod instance;
|
mod instance;
|
||||||
|
mod list;
|
||||||
mod migration;
|
mod migration;
|
||||||
mod search;
|
mod search;
|
||||||
|
mod timeline;
|
||||||
mod users;
|
mod users;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -16,6 +18,8 @@ fn main() {
|
|||||||
.subcommand(instance::command())
|
.subcommand(instance::command())
|
||||||
.subcommand(migration::command())
|
.subcommand(migration::command())
|
||||||
.subcommand(search::command())
|
.subcommand(search::command())
|
||||||
|
.subcommand(timeline::command())
|
||||||
|
.subcommand(list::command())
|
||||||
.subcommand(users::command());
|
.subcommand(users::command());
|
||||||
let matches = app.clone().get_matches();
|
let matches = app.clone().get_matches();
|
||||||
|
|
||||||
@@ -37,6 +41,10 @@ fn main() {
|
|||||||
("search", Some(args)) => {
|
("search", Some(args)) => {
|
||||||
search::run(args, &conn.expect("Couldn't connect to the database."))
|
search::run(args, &conn.expect("Couldn't connect to the database."))
|
||||||
}
|
}
|
||||||
|
("timeline", Some(args)) => {
|
||||||
|
timeline::run(args, &conn.expect("Couldn't connect to the database."))
|
||||||
|
}
|
||||||
|
("lists", Some(args)) => list::run(args, &conn.expect("Couldn't connect to the database.")),
|
||||||
("users", Some(args)) => {
|
("users", Some(args)) => {
|
||||||
users::run(args, &conn.expect("Couldn't connect to the database."))
|
users::run(args, &conn.expect("Couldn't connect to the database."))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
|
use plume_models::{instance::Instance, posts::Post, timeline::*, users::*, Connection};
|
||||||
|
|
||||||
|
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
||||||
|
SubCommand::with_name("timeline")
|
||||||
|
.about("Manage public timeline")
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("new")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of this timeline"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("query")
|
||||||
|
.short("q")
|
||||||
|
.long("query")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The query posts in this timelines have to match"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help(
|
||||||
|
"Username of whom this timeline is for. Empty for an instance timeline",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("preload-count")
|
||||||
|
.short("p")
|
||||||
|
.long("preload-count")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Number of posts to try to preload in this timeline at its creation"),
|
||||||
|
)
|
||||||
|
.about("Create a new timeline"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("delete")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of the timeline to delete"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help(
|
||||||
|
"Username of whom this timeline was for. Empty for instance timeline",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("yes")
|
||||||
|
.short("y")
|
||||||
|
.long("yes")
|
||||||
|
.help("Confirm the deletion"),
|
||||||
|
)
|
||||||
|
.about("Delete a timeline"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("edit")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of the timeline to edit"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Username of whom this timeline is for. Empty for instance timeline"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("query")
|
||||||
|
.short("q")
|
||||||
|
.long("query")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The query posts in this timelines have to match"),
|
||||||
|
)
|
||||||
|
.about("Edit the query of a timeline"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("repopulate")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("name")
|
||||||
|
.short("n")
|
||||||
|
.long("name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("The name of the timeline to repopulate"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("user")
|
||||||
|
.short("u")
|
||||||
|
.long("user")
|
||||||
|
.takes_value(true)
|
||||||
|
.help(
|
||||||
|
"Username of whom this timeline was for. Empty for instance timeline",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("preload-count")
|
||||||
|
.short("p")
|
||||||
|
.long("preload-count")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Number of posts to try to preload in this timeline at its creation"),
|
||||||
|
)
|
||||||
|
.about("Repopulate a timeline. Run this after modifying a list the timeline depends on."),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
||||||
|
let conn = conn;
|
||||||
|
match args.subcommand() {
|
||||||
|
("new", Some(x)) => new(x, conn),
|
||||||
|
("edit", Some(x)) => edit(x, conn),
|
||||||
|
("delete", Some(x)) => delete(x, conn),
|
||||||
|
("repopulate", Some(x)) => repopulate(x, conn),
|
||||||
|
("", None) => command().print_help().unwrap(),
|
||||||
|
_ => println!("Unknown subcommand"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_timeline_identifier(args: &ArgMatches<'_>) -> (String, Option<String>) {
|
||||||
|
let name = args
|
||||||
|
.value_of("name")
|
||||||
|
.map(String::from)
|
||||||
|
.expect("No name provided for the timeline");
|
||||||
|
let user = args.value_of("user").map(String::from);
|
||||||
|
(name, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_query(args: &ArgMatches<'_>) -> String {
|
||||||
|
let query = args
|
||||||
|
.value_of("query")
|
||||||
|
.map(String::from)
|
||||||
|
.expect("No query provided");
|
||||||
|
|
||||||
|
match TimelineQuery::parse(&query) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(QueryError::SyntaxError(start, end, message)) => panic!(
|
||||||
|
"Query parsing error between {} and {}: {}",
|
||||||
|
start, end, message
|
||||||
|
),
|
||||||
|
Err(QueryError::UnexpectedEndOfQuery) => {
|
||||||
|
panic!("Query parsing error: unexpected end of query")
|
||||||
|
}
|
||||||
|
Err(QueryError::RuntimeError(message)) => panic!("Query parsing error: {}", message),
|
||||||
|
}
|
||||||
|
|
||||||
|
query
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_preload_count(args: &ArgMatches<'_>) -> usize {
|
||||||
|
args.value_of("preload-count")
|
||||||
|
.map(|arg| arg.parse().expect("invalid preload-count"))
|
||||||
|
.unwrap_or(plume_models::ITEMS_PER_PAGE as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_user(username: &str, conn: &Connection) -> User {
|
||||||
|
let instance = Instance::get_local_uncached(conn).expect("Failed to load local instance");
|
||||||
|
|
||||||
|
User::find_by_name(conn, username, instance.id).expect("User not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preload(timeline: Timeline, count: usize, conn: &Connection) {
|
||||||
|
timeline.remove_all_posts(conn).unwrap();
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut posts = Vec::with_capacity(count as usize);
|
||||||
|
for post in Post::list_filtered(conn, None, None, None)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
{
|
||||||
|
if timeline.matches(conn, &post, Kind::Original).unwrap() {
|
||||||
|
posts.push(post);
|
||||||
|
if posts.len() >= count {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for post in posts.iter().rev() {
|
||||||
|
timeline.add_post(conn, post).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_timeline_identifier(args);
|
||||||
|
let query = get_query(args);
|
||||||
|
let preload_count = get_preload_count(args);
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
let timeline = if let Some(user) = user {
|
||||||
|
Timeline::new_for_user(conn, user.id, name, query)
|
||||||
|
} else {
|
||||||
|
Timeline::new_for_instance(conn, name, query)
|
||||||
|
}
|
||||||
|
.expect("Failed to create new timeline");
|
||||||
|
|
||||||
|
preload(timeline, preload_count, conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_timeline_identifier(args);
|
||||||
|
let query = get_query(args);
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
let mut timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
|
||||||
|
.expect("timeline not found");
|
||||||
|
|
||||||
|
timeline.query = query;
|
||||||
|
|
||||||
|
timeline.update(conn).expect("Failed to update timeline");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_timeline_identifier(args);
|
||||||
|
|
||||||
|
if !args.is_present("yes") {
|
||||||
|
panic!("Warning, this operation is destructive. Add --yes to confirm you want to do it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
let timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
|
||||||
|
.expect("timeline not found");
|
||||||
|
|
||||||
|
timeline.delete(conn).expect("Failed to update timeline");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repopulate(args: &ArgMatches<'_>, conn: &Connection) {
|
||||||
|
let (name, user) = get_timeline_identifier(args);
|
||||||
|
let preload_count = get_preload_count(args);
|
||||||
|
|
||||||
|
let user = user.map(|user| resolve_user(&user, conn));
|
||||||
|
|
||||||
|
let timeline = Timeline::find_for_user_by_name(conn, user.map(|u| u.id), &name)
|
||||||
|
.expect("timeline not found");
|
||||||
|
preload(timeline, preload_count, conn);
|
||||||
|
}
|
||||||
+10
-11
@@ -7,25 +7,24 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
array_tool = "1.0"
|
array_tool = "1.0"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
heck = "0.4.0"
|
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
openssl = "0.10.40"
|
openssl = "0.10.40"
|
||||||
rocket = "0.4.6"
|
rocket = "0.4.11"
|
||||||
reqwest = { version = "0.11.10", features = ["blocking", "json", "socks"] }
|
reqwest = { version = "0.11.11", features = ["blocking", "json", "socks"] }
|
||||||
serde = "1.0"
|
serde = "1.0.137"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
syntect = "4.5.0"
|
syntect = "4.5.0"
|
||||||
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
|
regex-syntax = { version = "0.6.26", default-features = false, features = ["unicode-perl"] }
|
||||||
tracing = "0.1.34"
|
tracing = "0.1.35"
|
||||||
askama_escape = "0.10.3"
|
askama_escape = "0.10.3"
|
||||||
activitystreams = "0.7.0-alpha.18"
|
activitystreams = "=0.7.0-alpha.20"
|
||||||
activitystreams-ext = "0.1.0-alpha.2"
|
activitystreams-ext = "0.1.0-alpha.2"
|
||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
flume = "0.10.12"
|
flume = "0.10.13"
|
||||||
tokio = { version = "1.18.2", features = ["full"] }
|
tokio = { version = "1.19.2", features = ["full"] }
|
||||||
futures = "0.3.21"
|
futures = "0.3.25"
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
@@ -38,6 +37,6 @@ branch = "bidi-plume"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert-json-diff = "2.0.1"
|
assert-json-diff = "2.0.1"
|
||||||
once_cell = "1.10.0"
|
once_cell = "1.12.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pre-release-hook = ["cargo", "fmt"]
|
pre-release-hook = ["cargo", "fmt"]
|
||||||
pre-release-replacements = []
|
pre-release-replacements = []
|
||||||
|
release = false
|
||||||
|
|||||||
@@ -561,7 +561,7 @@ mod tests {
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
|
||||||
static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
static MY_SIGNER: Lazy<MySigner> = Lazy::new(MySigner::new);
|
||||||
|
|
||||||
struct MySigner {
|
struct MySigner {
|
||||||
public_key: String,
|
public_key: String,
|
||||||
@@ -596,7 +596,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
verifier.update(data.as_bytes()).unwrap();
|
verifier.update(data.as_bytes()).unwrap();
|
||||||
verifier.verify(&signature).map_err(|_| SignError())
|
verifier.verify(signature).map_err(|_| SignError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,7 +782,7 @@ mod tests {
|
|||||||
.done();
|
.done();
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
|
let res: Result<(), ()> = Inbox::handle(&(), act)
|
||||||
.with::<FailingActor, Create, MyObject>(None)
|
.with::<FailingActor, Create, MyObject>(None)
|
||||||
.with::<MyActor, Create, MyObject>(None)
|
.with::<MyActor, Create, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
|
|||||||
@@ -518,7 +518,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{ActorAndObjectRef, Create},
|
activity::{ActorAndObjectRef, Create},
|
||||||
object::kind::ArticleType,
|
object::{kind::ArticleType, Image},
|
||||||
|
prelude::{ApActorExt, BaseExt, ExtendsExt, ObjectExt},
|
||||||
};
|
};
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use serde_json::{from_str, json, to_value};
|
use serde_json::{from_str, json, to_value};
|
||||||
@@ -592,7 +593,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn de_custom_group() {
|
fn se_custom_group() {
|
||||||
let group = CustomGroup::new(
|
let group = CustomGroup::new(
|
||||||
ApActor::new("https://example.com/inbox".parse().unwrap(), Group::new()),
|
ApActor::new("https://example.com/inbox".parse().unwrap(), Group::new()),
|
||||||
ApSignature {
|
ApSignature {
|
||||||
@@ -625,6 +626,71 @@ mod tests {
|
|||||||
assert_eq!(to_value(group).unwrap(), expected);
|
assert_eq!(to_value(group).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn de_custom_group() {
|
||||||
|
let value: CustomGroup = from_str(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "Image"
|
||||||
|
},
|
||||||
|
"id": "https://plume01.localhost/~/Plume01%20Blog%202/",
|
||||||
|
"image": {
|
||||||
|
"type": "Image"
|
||||||
|
},
|
||||||
|
"inbox": "https://plume01.localhost/~/Plume01%20Blog%202/inbox",
|
||||||
|
"name": "Plume01 Blog 2",
|
||||||
|
"outbox": "https://plume01.localhost/~/Plume01%20Blog%202/outbox",
|
||||||
|
"preferredUsername": "Plume01 Blog 2",
|
||||||
|
"publicKey": {
|
||||||
|
"id": "https://plume01.localhost/~/Plume01%20Blog%202/#main-key",
|
||||||
|
"owner": "https://plume01.localhost/~/Plume01%20Blog%202/",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPGtKkl/iMsNAyeVaJGz\noEz5PoNkjRnKK7G97MFvb4zw9zs5SpzWW7b/pKHa4dODcGDJXmkCJ1H5JWyguzN8\n2GNoFjtEOJHxEGwBHSYDsTmhuLNB0DKxMU2iu55g8iIiXhZiIW1FBNGs/Geaymvr\nh/TEtzdReN8wzloRR55kOVcU49xBkqx8cfDSk/lrrDLlpveHdqgaFnIvuw2vycK0\nxFzS3xlEUpzJk9kHxoR1uEAfZ+gCv26Sgo/HqOAhqSD5IU3QZC3kdkr/hwVqtr8U\nXGkGG6Mo1rgzhkYiCFkWrV2WoKkcEHD4nEzbgoZZ5MyuSoloxnyF3NiScqmqW+Yx\nkQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"content": "",
|
||||||
|
"mediaType": "text/markdown"
|
||||||
|
},
|
||||||
|
"summary": "",
|
||||||
|
"type": "Group"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
).unwrap();
|
||||||
|
let mut expected = CustomGroup::new(
|
||||||
|
ApActor::new("https://plume01.localhost/~/Plume01%20Blog%202/inbox".parse().unwrap(), Group::new()),
|
||||||
|
ApSignature {
|
||||||
|
public_key: PublicKey {
|
||||||
|
id: "https://plume01.localhost/~/Plume01%20Blog%202/#main-key".parse().unwrap(),
|
||||||
|
owner: "https://plume01.localhost/~/Plume01%20Blog%202/".parse().unwrap(),
|
||||||
|
public_key_pem: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPGtKkl/iMsNAyeVaJGz\noEz5PoNkjRnKK7G97MFvb4zw9zs5SpzWW7b/pKHa4dODcGDJXmkCJ1H5JWyguzN8\n2GNoFjtEOJHxEGwBHSYDsTmhuLNB0DKxMU2iu55g8iIiXhZiIW1FBNGs/Geaymvr\nh/TEtzdReN8wzloRR55kOVcU49xBkqx8cfDSk/lrrDLlpveHdqgaFnIvuw2vycK0\nxFzS3xlEUpzJk9kHxoR1uEAfZ+gCv26Sgo/HqOAhqSD5IU3QZC3kdkr/hwVqtr8U\nXGkGG6Mo1rgzhkYiCFkWrV2WoKkcEHD4nEzbgoZZ5MyuSoloxnyF3NiScqmqW+Yx\nkQIDAQAB\n-----END PUBLIC KEY-----\n".into(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SourceProperty {
|
||||||
|
source: Source {
|
||||||
|
content: String::from(""),
|
||||||
|
media_type: String::from("text/markdown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expected.set_icon(Image::new().into_any_base().unwrap());
|
||||||
|
expected.set_id(
|
||||||
|
"https://plume01.localhost/~/Plume01%20Blog%202/"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
expected.set_image(Image::new().into_any_base().unwrap());
|
||||||
|
expected.set_name("Plume01 Blog 2");
|
||||||
|
expected.set_outbox(
|
||||||
|
"https://plume01.localhost/~/Plume01%20Blog%202/outbox"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
expected.set_preferred_username("Plume01 Blog 2");
|
||||||
|
expected.set_summary("");
|
||||||
|
|
||||||
|
assert_json_eq!(value, expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn se_licensed_article() {
|
fn se_licensed_article() {
|
||||||
let object = ApObject::new(Article::new());
|
let object = ApObject::new(Article::new());
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
verifier.update(data.as_bytes()).unwrap();
|
verifier.update(data.as_bytes()).unwrap();
|
||||||
verifier.verify(&signature).map_err(|_| Error())
|
verifier.verify(signature).map_err(|_| Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ mod tests {
|
|||||||
let signer = MySigner::new();
|
let signer = MySigner::new();
|
||||||
let headers = HeaderMap::new();
|
let headers = HeaderMap::new();
|
||||||
let result = signature(&signer, &headers, ("post", "/inbox", None)).unwrap();
|
let result = signature(&signer, &headers, ("post", "/inbox", None)).unwrap();
|
||||||
let fields: Vec<&str> = result.to_str().unwrap().split(",").collect();
|
let fields: Vec<&str> = result.to_str().unwrap().split(',').collect();
|
||||||
assert_eq!(r#"headers="(request-target)""#, fields[2]);
|
assert_eq!(r#"headers="(request-target)""#, fields[2]);
|
||||||
let sign = &fields[3][11..(fields[3].len() - 1)];
|
let sign = &fields[3][11..(fields[3].len() - 1)];
|
||||||
assert!(signer.verify("post /inbox", sign.as_bytes()).is_ok());
|
assert!(signer.verify("post /inbox", sign.as_bytes()).is_ok());
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ impl Signable for serde_json::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum SignatureValidity {
|
pub enum SignatureValidity {
|
||||||
Invalid,
|
Invalid,
|
||||||
ValidNoDigest,
|
ValidNoDigest,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use heck::ToUpperCamelCase;
|
|
||||||
use openssl::rand::rand_bytes;
|
use openssl::rand::rand_bytes;
|
||||||
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag};
|
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag};
|
||||||
use regex_syntax::is_word_character;
|
use regex_syntax::is_word_character;
|
||||||
@@ -16,14 +15,6 @@ pub fn random_hex() -> String {
|
|||||||
.fold(String::new(), |res, byte| format!("{}{:x}", res, byte))
|
.fold(String::new(), |res, byte| format!("{}{:x}", res, byte))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove non alphanumeric characters and CamelCase a string
|
|
||||||
pub fn make_actor_id(name: &str) -> String {
|
|
||||||
name.to_upper_camel_case()
|
|
||||||
.chars()
|
|
||||||
.filter(|c| c.is_alphanumeric())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Percent-encode characters which are not allowed in IRI path segments.
|
* Percent-encode characters which are not allowed in IRI path segments.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ version = "0.7.2"
|
|||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata.wasm-pack.profile.release]
|
||||||
|
wasm-opt = false
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
@@ -12,15 +15,15 @@ gettext = "0.4.0"
|
|||||||
gettext-macros = "0.6.1"
|
gettext-macros = "0.6.1"
|
||||||
gettext-utils = "0.1.0"
|
gettext-utils = "0.1.0"
|
||||||
lazy_static = "1.3"
|
lazy_static = "1.3"
|
||||||
serde = "1.0"
|
serde = "1.0.137"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
wasm-bindgen = "0.2.80"
|
wasm-bindgen = "0.2.81"
|
||||||
js-sys = "0.3.57"
|
js-sys = "0.3.58"
|
||||||
serde_derive = "1.0.123"
|
serde_derive = "1.0.123"
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.57"
|
version = "0.3.58"
|
||||||
features = [
|
features = [
|
||||||
'console',
|
'console',
|
||||||
'ClipboardEvent',
|
'ClipboardEvent',
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pre-release-hook = ["cargo", "fmt"]
|
pre-release-hook = ["cargo", "fmt"]
|
||||||
pre-release-replacements = []
|
pre-release-replacements = []
|
||||||
|
release = false
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pre-release-hook = ["cargo", "fmt"]
|
pre-release-hook = ["cargo", "fmt"]
|
||||||
pre-release-replacements = []
|
pre-release-replacements = []
|
||||||
|
release = false
|
||||||
|
|||||||
+11
-11
@@ -10,31 +10,31 @@ bcrypt = "0.12.1"
|
|||||||
guid-create = "0.2"
|
guid-create = "0.2"
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
ldap3 = "0.10.4"
|
ldap3 = "0.11.1"
|
||||||
migrations_internals= "1.4.0"
|
migrations_internals= "1.4.0"
|
||||||
openssl = "0.10.40"
|
openssl = "0.10.40"
|
||||||
rocket = "0.4.6"
|
rocket = "0.4.11"
|
||||||
rocket_i18n = "0.4.1"
|
rocket_i18n = "0.4.1"
|
||||||
reqwest = "0.11.10"
|
reqwest = "0.11.11"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.6"
|
||||||
serde = "1.0"
|
serde = "1.0.137"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
tantivy = "0.13.3"
|
tantivy = "0.13.3"
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
webfinger = "0.4.1"
|
webfinger = "0.4.1"
|
||||||
whatlang = "0.16.0"
|
whatlang = "0.16.2"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
diesel-derive-newtype = "0.1.2"
|
diesel-derive-newtype = "1.0.0"
|
||||||
glob = "0.3.0"
|
glob = "0.3.1"
|
||||||
lindera-tantivy = { version = "0.7.1", optional = true }
|
lindera-tantivy = { version = "0.7.1", optional = true }
|
||||||
tracing = "0.1.34"
|
tracing = "0.1.35"
|
||||||
riker = "0.4.2"
|
riker = "0.4.2"
|
||||||
once_cell = "1.10.0"
|
once_cell = "1.12.0"
|
||||||
lettre = "0.9.6"
|
lettre = "0.9.6"
|
||||||
native-tls = "0.2.10"
|
native-tls = "0.2.10"
|
||||||
activitystreams = "0.7.0-alpha.18"
|
activitystreams = "=0.7.0-alpha.20"
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pre-release-hook = ["cargo", "fmt"]
|
pre-release-hook = ["cargo", "fmt"]
|
||||||
pre-release-replacements = []
|
pre-release-replacements = []
|
||||||
|
release = false
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
|||||||
let conn = request
|
let conn = request
|
||||||
.guard::<DbConn>()
|
.guard::<DbConn>()
|
||||||
.map_failure(|_| (Status::InternalServerError, TokenError::DbError))?;
|
.map_failure(|_| (Status::InternalServerError, TokenError::DbError))?;
|
||||||
if let Ok(token) = ApiToken::find_by_value(&*conn, val) {
|
if let Ok(token) = ApiToken::find_by_value(&conn, val) {
|
||||||
return Outcome::Success(token);
|
return Outcome::Success(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,12 +126,9 @@ pub(crate) mod tests {
|
|||||||
.id,
|
.id,
|
||||||
various[1].id
|
various[1].id
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert!(BlocklistedEmail::matches_blocklist(&conn, no_match)
|
||||||
BlocklistedEmail::matches_blocklist(&conn, no_match)
|
.unwrap()
|
||||||
.unwrap()
|
.is_none());
|
||||||
.is_none(),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-61
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn, instance::*, medias::Media, posts::Post, safe_string::SafeString,
|
instance::*, medias::Media, posts::Post, safe_string::SafeString, schema::blogs, users::User,
|
||||||
schema::blogs, users::User, Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
|
Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
actor::{ApActor, ApActorExt, AsApActor, Group},
|
actor::{ApActor, ApActorExt, AsApActor, Group},
|
||||||
@@ -18,10 +18,13 @@ use openssl::{
|
|||||||
rsa::Rsa,
|
rsa::Rsa,
|
||||||
sign::{Signer, Verifier},
|
sign::{Signer, Verifier},
|
||||||
};
|
};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::{
|
||||||
inbox::{AsActor, FromId},
|
activity_pub::{
|
||||||
sign, ActivityStream, ApSignature, CustomGroup, Id, IntoId, PublicKey, Source, SourceProperty,
|
inbox::{AsActor, FromId},
|
||||||
ToAsString, ToAsUri,
|
sign, ActivityStream, ApSignature, CustomGroup, Id, IntoId, PublicKey, Source,
|
||||||
|
SourceProperty, ToAsString, ToAsUri,
|
||||||
|
},
|
||||||
|
utils::iri_percent_encode_seg,
|
||||||
};
|
};
|
||||||
use webfinger::*;
|
use webfinger::*;
|
||||||
|
|
||||||
@@ -83,9 +86,13 @@ impl Blog {
|
|||||||
|
|
||||||
if inserted.fqn.is_empty() {
|
if inserted.fqn.is_empty() {
|
||||||
if instance.local {
|
if instance.local {
|
||||||
inserted.fqn = inserted.actor_id.clone();
|
inserted.fqn = iri_percent_encode_seg(&inserted.actor_id);
|
||||||
} else {
|
} else {
|
||||||
inserted.fqn = format!("{}@{}", inserted.actor_id, instance.public_domain);
|
inserted.fqn = format!(
|
||||||
|
"{}@{}",
|
||||||
|
iri_percent_encode_seg(&inserted.actor_id),
|
||||||
|
instance.public_domain
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +102,10 @@ impl Blog {
|
|||||||
find_by!(blogs, find_by_ap_url, ap_url as &str);
|
find_by!(blogs, find_by_ap_url, ap_url as &str);
|
||||||
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
|
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
|
||||||
|
|
||||||
|
pub fn slug(title: &str) -> &str {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
|
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
|
||||||
Instance::get(conn, self.instance_id)
|
Instance::get(conn, self.instance_id)
|
||||||
}
|
}
|
||||||
@@ -131,10 +142,10 @@ impl Blog {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_fqn(conn: &DbConn, fqn: &str) -> Result<Blog> {
|
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<Blog> {
|
||||||
let from_db = blogs::table
|
let from_db = blogs::table
|
||||||
.filter(blogs::fqn.eq(fqn))
|
.filter(blogs::fqn.eq(fqn))
|
||||||
.first(&**conn)
|
.first(conn)
|
||||||
.optional()?;
|
.optional()?;
|
||||||
if let Some(from_db) = from_db {
|
if let Some(from_db) = from_db {
|
||||||
Ok(from_db)
|
Ok(from_db)
|
||||||
@@ -143,7 +154,7 @@ impl Blog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<Blog> {
|
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<Blog> {
|
||||||
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
|
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
|
||||||
.links
|
.links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -162,7 +173,7 @@ impl Blog {
|
|||||||
|
|
||||||
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
||||||
let mut blog = ApActor::new(self.inbox_url.parse()?, Group::new());
|
let mut blog = ApActor::new(self.inbox_url.parse()?, Group::new());
|
||||||
blog.set_preferred_username(self.actor_id.clone());
|
blog.set_preferred_username(iri_percent_encode_seg(&self.actor_id));
|
||||||
blog.set_name(self.title.clone());
|
blog.set_name(self.title.clone());
|
||||||
blog.set_outbox(self.outbox_url.parse()?);
|
blog.set_outbox(self.outbox_url.parse()?);
|
||||||
blog.set_summary(self.summary_html.to_string());
|
blog.set_summary(self.summary_html.to_string());
|
||||||
@@ -361,15 +372,15 @@ impl IntoId for Blog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<DbConn> for Blog {
|
impl FromId<Connection> for Blog {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = CustomGroup;
|
type Object = CustomGroup;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> {
|
fn from_activity(conn: &Connection, acct: CustomGroup) -> Result<Self> {
|
||||||
let (name, outbox_url, inbox_url) = {
|
let (name, outbox_url, inbox_url) = {
|
||||||
let actor = acct.ap_actor_ref();
|
let actor = acct.ap_actor_ref();
|
||||||
let name = actor
|
let name = actor
|
||||||
@@ -377,6 +388,7 @@ impl FromId<DbConn> for Blog {
|
|||||||
.ok_or(Error::MissingApProperty)?
|
.ok_or(Error::MissingApProperty)?
|
||||||
.to_string();
|
.to_string();
|
||||||
if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
|
if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
|
||||||
|
tracing::error!("preferredUsername includes invalid character(s): {}", &name);
|
||||||
return Err(Error::InvalidValue);
|
return Err(Error::InvalidValue);
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
@@ -656,7 +668,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.id,
|
.id,
|
||||||
);
|
);
|
||||||
let _: Blog = blog1.save_changes(&*conn).unwrap();
|
let _: Blog = blog1.save_changes(conn).unwrap();
|
||||||
|
|
||||||
(users, vec![blog1, blog2, blog3])
|
(users, vec![blog1, blog2, blog3])
|
||||||
}
|
}
|
||||||
@@ -665,10 +677,10 @@ pub(crate) mod tests {
|
|||||||
fn get_instance() {
|
fn get_instance() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
fill_database(&conn);
|
fill_database(conn);
|
||||||
|
|
||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"SomeName".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
@@ -680,7 +692,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
blog.get_instance(&conn).unwrap().id,
|
blog.get_instance(conn).unwrap().id,
|
||||||
Instance::get_local().unwrap().id
|
Instance::get_local().unwrap().id
|
||||||
);
|
);
|
||||||
// TODO add tests for remote instance
|
// TODO add tests for remote instance
|
||||||
@@ -692,10 +704,10 @@ pub(crate) mod tests {
|
|||||||
fn authors() {
|
fn authors() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (user, _) = fill_database(&conn);
|
let (user, _) = fill_database(conn);
|
||||||
|
|
||||||
let b1 = Blog::insert(
|
let b1 = Blog::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"SomeName".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
@@ -706,7 +718,7 @@ pub(crate) mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let b2 = Blog::insert(
|
let b2 = Blog::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"Blog".to_owned(),
|
"Blog".to_owned(),
|
||||||
"Blog".to_owned(),
|
"Blog".to_owned(),
|
||||||
@@ -719,7 +731,7 @@ pub(crate) mod tests {
|
|||||||
let blog = vec![b1, b2];
|
let blog = vec![b1, b2];
|
||||||
|
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlogAuthor {
|
NewBlogAuthor {
|
||||||
blog_id: blog[0].id,
|
blog_id: blog[0].id,
|
||||||
author_id: user[0].id,
|
author_id: user[0].id,
|
||||||
@@ -729,7 +741,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlogAuthor {
|
NewBlogAuthor {
|
||||||
blog_id: blog[0].id,
|
blog_id: blog[0].id,
|
||||||
author_id: user[1].id,
|
author_id: user[1].id,
|
||||||
@@ -739,7 +751,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlogAuthor {
|
NewBlogAuthor {
|
||||||
blog_id: blog[1].id,
|
blog_id: blog[1].id,
|
||||||
author_id: user[0].id,
|
author_id: user[0].id,
|
||||||
@@ -749,39 +761,39 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(blog[0]
|
assert!(blog[0]
|
||||||
.list_authors(&conn)
|
.list_authors(conn)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| a.id == user[0].id));
|
.any(|a| a.id == user[0].id));
|
||||||
assert!(blog[0]
|
assert!(blog[0]
|
||||||
.list_authors(&conn)
|
.list_authors(conn)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| a.id == user[1].id));
|
.any(|a| a.id == user[1].id));
|
||||||
assert!(blog[1]
|
assert!(blog[1]
|
||||||
.list_authors(&conn)
|
.list_authors(conn)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| a.id == user[0].id));
|
.any(|a| a.id == user[0].id));
|
||||||
assert!(!blog[1]
|
assert!(!blog[1]
|
||||||
.list_authors(&conn)
|
.list_authors(conn)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| a.id == user[1].id));
|
.any(|a| a.id == user[1].id));
|
||||||
|
|
||||||
assert!(Blog::find_for_author(&conn, &user[0])
|
assert!(Blog::find_for_author(conn, &user[0])
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| b.id == blog[0].id));
|
.any(|b| b.id == blog[0].id));
|
||||||
assert!(Blog::find_for_author(&conn, &user[1])
|
assert!(Blog::find_for_author(conn, &user[1])
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| b.id == blog[0].id));
|
.any(|b| b.id == blog[0].id));
|
||||||
assert!(Blog::find_for_author(&conn, &user[0])
|
assert!(Blog::find_for_author(conn, &user[0])
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| b.id == blog[1].id));
|
.any(|b| b.id == blog[1].id));
|
||||||
assert!(!Blog::find_for_author(&conn, &user[1])
|
assert!(!Blog::find_for_author(conn, &user[1])
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| b.id == blog[1].id));
|
.any(|b| b.id == blog[1].id));
|
||||||
@@ -793,10 +805,10 @@ pub(crate) mod tests {
|
|||||||
fn find_local() {
|
fn find_local() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
fill_database(&conn);
|
fill_database(conn);
|
||||||
|
|
||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"SomeName".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
@@ -807,7 +819,7 @@ pub(crate) mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(Blog::find_by_fqn(&conn, "SomeName").unwrap().id, blog.id);
|
assert_eq!(Blog::find_by_fqn(conn, "SomeName").unwrap().id, blog.id);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -816,10 +828,10 @@ pub(crate) mod tests {
|
|||||||
fn get_fqn() {
|
fn get_fqn() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
fill_database(&conn);
|
fill_database(conn);
|
||||||
|
|
||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"SomeName".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
@@ -839,10 +851,10 @@ pub(crate) mod tests {
|
|||||||
fn delete() {
|
fn delete() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (_, blogs) = fill_database(&conn);
|
let (_, blogs) = fill_database(conn);
|
||||||
|
|
||||||
blogs[0].delete(&conn).unwrap();
|
blogs[0].delete(conn).unwrap();
|
||||||
assert!(Blog::get(&conn, blogs[0].id).is_err());
|
assert!(Blog::get(conn, blogs[0].id).is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -851,10 +863,10 @@ pub(crate) mod tests {
|
|||||||
fn delete_via_user() {
|
fn delete_via_user() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (user, _) = fill_database(&conn);
|
let (user, _) = fill_database(conn);
|
||||||
|
|
||||||
let b1 = Blog::insert(
|
let b1 = Blog::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"SomeName".to_owned(),
|
"SomeName".to_owned(),
|
||||||
"Some name".to_owned(),
|
"Some name".to_owned(),
|
||||||
@@ -865,7 +877,7 @@ pub(crate) mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let b2 = Blog::insert(
|
let b2 = Blog::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
"Blog".to_owned(),
|
"Blog".to_owned(),
|
||||||
"Blog".to_owned(),
|
"Blog".to_owned(),
|
||||||
@@ -878,7 +890,7 @@ pub(crate) mod tests {
|
|||||||
let blog = vec![b1, b2];
|
let blog = vec![b1, b2];
|
||||||
|
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlogAuthor {
|
NewBlogAuthor {
|
||||||
blog_id: blog[0].id,
|
blog_id: blog[0].id,
|
||||||
author_id: user[0].id,
|
author_id: user[0].id,
|
||||||
@@ -888,7 +900,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlogAuthor {
|
NewBlogAuthor {
|
||||||
blog_id: blog[0].id,
|
blog_id: blog[0].id,
|
||||||
author_id: user[1].id,
|
author_id: user[1].id,
|
||||||
@@ -898,7 +910,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlogAuthor {
|
NewBlogAuthor {
|
||||||
blog_id: blog[1].id,
|
blog_id: blog[1].id,
|
||||||
author_id: user[0].id,
|
author_id: user[0].id,
|
||||||
@@ -907,11 +919,11 @@ pub(crate) mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
user[0].delete(&conn).unwrap();
|
user[0].delete(conn).unwrap();
|
||||||
assert!(Blog::get(&conn, blog[0].id).is_ok());
|
assert!(Blog::get(conn, blog[0].id).is_ok());
|
||||||
assert!(Blog::get(&conn, blog[1].id).is_err());
|
assert!(Blog::get(conn, blog[1].id).is_err());
|
||||||
user[1].delete(&conn).unwrap();
|
user[1].delete(conn).unwrap();
|
||||||
assert!(Blog::get(&conn, blog[0].id).is_err());
|
assert!(Blog::get(conn, blog[0].id).is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -920,10 +932,10 @@ pub(crate) mod tests {
|
|||||||
fn self_federation() {
|
fn self_federation() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (users, mut blogs) = fill_database(&conn);
|
let (users, mut blogs) = fill_database(conn);
|
||||||
blogs[0].icon_id = Some(
|
blogs[0].icon_id = Some(
|
||||||
Media::insert(
|
Media::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewMedia {
|
NewMedia {
|
||||||
file_path: "aaa.png".into(),
|
file_path: "aaa.png".into(),
|
||||||
alt_text: String::new(),
|
alt_text: String::new(),
|
||||||
@@ -939,7 +951,7 @@ pub(crate) mod tests {
|
|||||||
);
|
);
|
||||||
blogs[0].banner_id = Some(
|
blogs[0].banner_id = Some(
|
||||||
Media::insert(
|
Media::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewMedia {
|
NewMedia {
|
||||||
file_path: "bbb.png".into(),
|
file_path: "bbb.png".into(),
|
||||||
alt_text: String::new(),
|
alt_text: String::new(),
|
||||||
@@ -954,9 +966,9 @@ pub(crate) mod tests {
|
|||||||
.id,
|
.id,
|
||||||
);
|
);
|
||||||
let _: Blog = blogs[0].save_changes(&**conn).unwrap();
|
let _: Blog = blogs[0].save_changes(&**conn).unwrap();
|
||||||
let ap_repr = blogs[0].to_activity(&conn).unwrap();
|
let ap_repr = blogs[0].to_activity(conn).unwrap();
|
||||||
blogs[0].delete(&conn).unwrap();
|
blogs[0].delete(conn).unwrap();
|
||||||
let blog = Blog::from_activity(&conn, ap_repr).unwrap();
|
let blog = Blog::from_activity(conn, ap_repr).unwrap();
|
||||||
|
|
||||||
assert_eq!(blog.actor_id, blogs[0].actor_id);
|
assert_eq!(blog.actor_id, blogs[0].actor_id);
|
||||||
assert_eq!(blog.title, blogs[0].title);
|
assert_eq!(blog.title, blogs[0].title);
|
||||||
@@ -968,8 +980,8 @@ pub(crate) mod tests {
|
|||||||
assert_eq!(blog.public_key, blogs[0].public_key);
|
assert_eq!(blog.public_key, blogs[0].public_key);
|
||||||
assert_eq!(blog.fqn, blogs[0].fqn);
|
assert_eq!(blog.fqn, blogs[0].fqn);
|
||||||
assert_eq!(blog.summary_html, blogs[0].summary_html);
|
assert_eq!(blog.summary_html, blogs[0].summary_html);
|
||||||
assert_eq!(blog.icon_url(&conn), blogs[0].icon_url(&conn));
|
assert_eq!(blog.icon_url(conn), blogs[0].icon_url(conn));
|
||||||
assert_eq!(blog.banner_url(&conn), blogs[0].banner_url(&conn));
|
assert_eq!(blog.banner_url(conn), blogs[0].banner_url(conn));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -979,7 +991,7 @@ pub(crate) mod tests {
|
|||||||
fn to_activity() {
|
fn to_activity() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, Error, _>(|| {
|
conn.test_transaction::<_, Error, _>(|| {
|
||||||
let (_users, blogs) = fill_database(&conn);
|
let (_users, blogs) = fill_database(conn);
|
||||||
let blog = &blogs[0];
|
let blog = &blogs[0];
|
||||||
let act = blog.to_activity(conn)?;
|
let act = blog.to_activity(conn)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comment_seers::{CommentSeers, NewCommentSeers},
|
comment_seers::{CommentSeers, NewCommentSeers},
|
||||||
db_conn::DbConn,
|
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
medias::Media,
|
medias::Media,
|
||||||
mentions::Mention,
|
mentions::Mention,
|
||||||
@@ -74,6 +73,7 @@ impl Comment {
|
|||||||
});
|
});
|
||||||
get!(comments);
|
get!(comments);
|
||||||
list_by!(comments, list_by_post, post_id as i32);
|
list_by!(comments, list_by_post, post_id as i32);
|
||||||
|
list_by!(comments, list_by_author, author_id as i32);
|
||||||
find_by!(comments, find_by_ap_url, ap_url as &str);
|
find_by!(comments, find_by_ap_url, ap_url as &str);
|
||||||
|
|
||||||
pub fn get_author(&self, conn: &Connection) -> Result<User> {
|
pub fn get_author(&self, conn: &Connection) -> Result<User> {
|
||||||
@@ -111,7 +111,7 @@ impl Comment {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_activity(&self, conn: &DbConn) -> Result<Note> {
|
pub fn to_activity(&self, conn: &Connection) -> Result<Note> {
|
||||||
let author = User::get(conn, self.author_id)?;
|
let author = User::get(conn, self.author_id)?;
|
||||||
let (html, mentions, _hashtags) = utils::md_to_html(
|
let (html, mentions, _hashtags) = utils::md_to_html(
|
||||||
self.content.get().as_ref(),
|
self.content.get().as_ref(),
|
||||||
@@ -149,7 +149,7 @@ impl Comment {
|
|||||||
Ok(note)
|
Ok(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> {
|
pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
|
||||||
let author = User::get(conn, self.author_id)?;
|
let author = User::get(conn, self.author_id)?;
|
||||||
|
|
||||||
let note = self.to_activity(conn)?;
|
let note = self.to_activity(conn)?;
|
||||||
@@ -217,15 +217,15 @@ impl Comment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<DbConn> for Comment {
|
impl FromId<Connection> for Comment {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = Note;
|
type Object = Note;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
|
fn from_activity(conn: &Connection, note: Note) -> Result<Self> {
|
||||||
let comm = {
|
let comm = {
|
||||||
let previous_url = note
|
let previous_url = note
|
||||||
.in_reply_to()
|
.in_reply_to()
|
||||||
@@ -354,21 +354,21 @@ impl FromId<DbConn> for Comment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Comment {
|
impl AsObject<User, Create, &Connection> for Comment {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self> {
|
fn activity(self, _conn: &Connection, _actor: User, _id: &str) -> Result<Self> {
|
||||||
// The actual creation takes place in the FromId impl
|
// The actual creation takes place in the FromId impl
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Delete, &DbConn> for Comment {
|
impl AsObject<User, Delete, &Connection> for Comment {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
||||||
if self.author_id != actor.id {
|
if self.author_id != actor.id {
|
||||||
return Err(Error::Unauthorized);
|
return Err(Error::Unauthorized);
|
||||||
}
|
}
|
||||||
@@ -381,14 +381,14 @@ impl AsObject<User, Delete, &DbConn> for Comment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for n in Notification::find_for_comment(conn, &self)? {
|
for n in Notification::find_for_comment(conn, &self)? {
|
||||||
n.delete(&**conn)?;
|
n.delete(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::update(comments::table)
|
diesel::update(comments::table)
|
||||||
.filter(comments::in_response_to_id.eq(self.id))
|
.filter(comments::in_response_to_id.eq(self.id))
|
||||||
.set(comments::in_response_to_id.eq(self.in_response_to_id))
|
.set(comments::in_response_to_id.eq(self.in_response_to_id))
|
||||||
.execute(&**conn)?;
|
.execute(conn)?;
|
||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(conn)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,6 +423,7 @@ impl CommentTree {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::blogs::Blog;
|
use crate::blogs::Blog;
|
||||||
|
use crate::db_conn::DbConn;
|
||||||
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
||||||
use crate::safe_string::SafeString;
|
use crate::safe_string::SafeString;
|
||||||
use crate::tests::{db, format_datetime};
|
use crate::tests::{db, format_datetime};
|
||||||
@@ -431,7 +432,7 @@ mod tests {
|
|||||||
use serde_json::{json, to_value};
|
use serde_json::{json, to_value};
|
||||||
|
|
||||||
fn prepare_activity(conn: &DbConn) -> (Comment, Vec<Post>, Vec<User>, Vec<Blog>) {
|
fn prepare_activity(conn: &DbConn) -> (Comment, Vec<Post>, Vec<User>, Vec<Blog>) {
|
||||||
let (posts, users, blogs) = fill_database(&conn);
|
let (posts, users, blogs) = fill_database(conn);
|
||||||
|
|
||||||
let comment = Comment::insert(
|
let comment = Comment::insert(
|
||||||
conn,
|
conn,
|
||||||
@@ -457,8 +458,8 @@ mod tests {
|
|||||||
fn self_federation() {
|
fn self_federation() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (original_comm, posts, users, _blogs) = prepare_activity(&conn);
|
let (original_comm, posts, users, _blogs) = prepare_activity(conn);
|
||||||
let act = original_comm.create_activity(&conn).unwrap();
|
let act = original_comm.create_activity(conn).unwrap();
|
||||||
|
|
||||||
assert_json_eq!(to_value(&act).unwrap(), json!({
|
assert_json_eq!(to_value(&act).unwrap(), json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
@@ -500,7 +501,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let reply_act = reply.create_activity(&conn).unwrap();
|
let reply_act = reply.create_activity(conn).unwrap();
|
||||||
|
|
||||||
assert_json_eq!(to_value(&reply_act).unwrap(), json!({
|
assert_json_eq!(to_value(&reply_act).unwrap(), json!({
|
||||||
"actor": "https://plu.me/@/user/",
|
"actor": "https://plu.me/@/user/",
|
||||||
@@ -522,12 +523,12 @@ mod tests {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
inbox(
|
inbox(
|
||||||
&conn,
|
conn,
|
||||||
serde_json::to_value(original_comm.build_delete(&conn).unwrap()).unwrap(),
|
serde_json::to_value(original_comm.build_delete(conn).unwrap()).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match inbox(&conn, to_value(act).unwrap()).unwrap() {
|
match inbox(conn, to_value(act).unwrap()).unwrap() {
|
||||||
InboxResult::Commented(c) => {
|
InboxResult::Commented(c) => {
|
||||||
// TODO: one is HTML, the other markdown: assert_eq!(c.content, original_comm.content);
|
// TODO: one is HTML, the other markdown: assert_eq!(c.content, original_comm.content);
|
||||||
assert_eq!(c.in_response_to_id, original_comm.in_response_to_id);
|
assert_eq!(c.in_response_to_id, original_comm.in_response_to_id);
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ pub(crate) mod tests {
|
|||||||
impl CustomizeConnection<Connection, ConnError> for TestConnectionCustomizer {
|
impl CustomizeConnection<Connection, ConnError> for TestConnectionCustomizer {
|
||||||
fn on_acquire(&self, conn: &mut Connection) -> Result<(), ConnError> {
|
fn on_acquire(&self, conn: &mut Connection) -> Result<(), ConnError> {
|
||||||
PragmaForeignKey.on_acquire(conn)?;
|
PragmaForeignKey.on_acquire(conn)?;
|
||||||
Ok(conn.begin_test_transaction().unwrap())
|
conn.begin_test_transaction().unwrap();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
blocklisted_emails::BlocklistedEmail,
|
||||||
db_conn::DbConn,
|
db_conn::DbConn,
|
||||||
schema::email_signups,
|
schema::email_signups,
|
||||||
users::{NewUser, Role, User},
|
users::{NewUser, Role, User},
|
||||||
@@ -60,6 +61,8 @@ pub struct NewEmailSignup<'a> {
|
|||||||
|
|
||||||
impl EmailSignup {
|
impl EmailSignup {
|
||||||
pub fn start(conn: &DbConn, email: &str) -> Result<Token> {
|
pub fn start(conn: &DbConn, email: &str) -> Result<Token> {
|
||||||
|
Self::ensure_email_not_blocked(conn, email)?;
|
||||||
|
|
||||||
conn.transaction(|| {
|
conn.transaction(|| {
|
||||||
Self::ensure_user_not_exist_by_email(conn, email)?;
|
Self::ensure_user_not_exist_by_email(conn, email)?;
|
||||||
let _rows = Self::delete_existings_by_email(conn, email)?;
|
let _rows = Self::delete_existings_by_email(conn, email)?;
|
||||||
@@ -90,6 +93,8 @@ impl EmailSignup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm(&self, conn: &DbConn) -> Result<()> {
|
pub fn confirm(&self, conn: &DbConn) -> Result<()> {
|
||||||
|
Self::ensure_email_not_blocked(conn, &self.email)?;
|
||||||
|
|
||||||
conn.transaction(|| {
|
conn.transaction(|| {
|
||||||
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
||||||
if self.expired() {
|
if self.expired() {
|
||||||
@@ -101,6 +106,8 @@ impl EmailSignup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(&self, conn: &DbConn, username: String, password: String) -> Result<User> {
|
pub fn complete(&self, conn: &DbConn, username: String, password: String) -> Result<User> {
|
||||||
|
Self::ensure_email_not_blocked(conn, &self.email)?;
|
||||||
|
|
||||||
conn.transaction(|| {
|
conn.transaction(|| {
|
||||||
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
||||||
let user = NewUser::new_local(
|
let user = NewUser::new_local(
|
||||||
@@ -122,6 +129,14 @@ impl EmailSignup {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_email_not_blocked(conn: &DbConn, email: &str) -> Result<()> {
|
||||||
|
if let Some(x) = BlocklistedEmail::matches_blocklist(conn, email)? {
|
||||||
|
Err(Error::Blocklisted(x.notify_user, x.notification_text))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn ensure_user_not_exist_by_email(conn: &DbConn, email: &str) -> Result<()> {
|
fn ensure_user_not_exist_by_email(conn: &DbConn, email: &str) -> Result<()> {
|
||||||
if User::email_used(conn, email)? {
|
if User::email_used(conn, email)? {
|
||||||
let _rows = Self::delete_existings_by_email(conn, email)?;
|
let _rows = Self::delete_existings_by_email(conn, email)?;
|
||||||
|
|||||||
+15
-18
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User,
|
ap_url, instance::Instance, notifications::*, schema::follows, users::User, Connection, Error,
|
||||||
Connection, Error, Result, CONFIG,
|
Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo},
|
activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo},
|
||||||
@@ -107,12 +107,7 @@ impl Follow {
|
|||||||
res.notify(conn)?;
|
res.notify(conn)?;
|
||||||
|
|
||||||
let accept = res.build_accept(from, target, follow)?;
|
let accept = res.build_accept(from, target, follow)?;
|
||||||
broadcast(
|
broadcast(target, accept, vec![from.clone()], CONFIG.proxy().cloned());
|
||||||
&*target,
|
|
||||||
accept,
|
|
||||||
vec![from.clone()],
|
|
||||||
CONFIG.proxy().cloned(),
|
|
||||||
);
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,11 +150,11 @@ impl Follow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, FollowAct, &DbConn> for User {
|
impl AsObject<User, FollowAct, &Connection> for User {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Follow;
|
type Output = Follow;
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Follow> {
|
fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Follow> {
|
||||||
// Mastodon (at least) requires the full Follow object when accepting it,
|
// Mastodon (at least) requires the full Follow object when accepting it,
|
||||||
// so we rebuilt it here
|
// so we rebuilt it here
|
||||||
let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?);
|
let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?);
|
||||||
@@ -167,15 +162,15 @@ impl AsObject<User, FollowAct, &DbConn> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<DbConn> for Follow {
|
impl FromId<Connection> for Follow {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = FollowAct;
|
type Object = FollowAct;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
||||||
Follow::find_by_ap_url(conn, id)
|
Follow::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
|
fn from_activity(conn: &Connection, follow: FollowAct) -> Result<Self> {
|
||||||
let actor = User::from_id(
|
let actor = User::from_id(
|
||||||
conn,
|
conn,
|
||||||
follow
|
follow
|
||||||
@@ -207,18 +202,18 @@ impl FromId<DbConn> for Follow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Follow {
|
impl AsObject<User, Undo, &Connection> for Follow {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
||||||
let conn = conn;
|
let conn = conn;
|
||||||
if self.follower_id == actor.id {
|
if self.follower_id == actor.id {
|
||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -237,7 +232,9 @@ impl IntoId for Follow {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{tests::db, users::tests as user_tests, users::tests::fill_database};
|
use crate::{
|
||||||
|
db_conn::DbConn, tests::db, users::tests as user_tests, users::tests::fill_database,
|
||||||
|
};
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use diesel::Connection;
|
use diesel::Connection;
|
||||||
use serde_json::{json, to_value};
|
use serde_json::{json, to_value};
|
||||||
|
|||||||
+11
-11
@@ -7,7 +7,7 @@ use crate::{
|
|||||||
posts::{Post, PostUpdate},
|
posts::{Post, PostUpdate},
|
||||||
reshares::Reshare,
|
reshares::Reshare,
|
||||||
users::User,
|
users::User,
|
||||||
Error, CONFIG,
|
Connection, Error, CONFIG,
|
||||||
};
|
};
|
||||||
use plume_common::activity_pub::inbox::Inbox;
|
use plume_common::activity_pub::inbox::Inbox;
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ impl_into_inbox_result! {
|
|||||||
Reshare => Reshared
|
Reshare => Reshared
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
pub fn inbox(conn: &Connection, act: serde_json::Value) -> Result<InboxResult, Error> {
|
||||||
Inbox::handle(conn, act)
|
Inbox::handle(conn, act)
|
||||||
.with::<User, Announce, Post>(CONFIG.proxy())
|
.with::<User, Announce, Post>(CONFIG.proxy())
|
||||||
.with::<User, Create, Comment>(CONFIG.proxy())
|
.with::<User, Create, Comment>(CONFIG.proxy())
|
||||||
@@ -82,9 +82,9 @@ pub(crate) mod tests {
|
|||||||
use crate::post_authors::*;
|
use crate::post_authors::*;
|
||||||
use crate::posts::*;
|
use crate::posts::*;
|
||||||
|
|
||||||
let (users, blogs) = blog_fill_db(&conn);
|
let (users, blogs) = blog_fill_db(conn);
|
||||||
let post = Post::insert(
|
let post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "testing".to_owned(),
|
slug: "testing".to_owned(),
|
||||||
@@ -102,7 +102,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
PostAuthor::insert(
|
PostAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPostAuthor {
|
NewPostAuthor {
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
author_id: users[0].id,
|
author_id: users[0].id,
|
||||||
@@ -190,7 +190,7 @@ pub(crate) mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
super::inbox(&conn, act.clone()),
|
super::inbox(&conn, act),
|
||||||
Err(super::Error::Inbox(
|
Err(super::Error::Inbox(
|
||||||
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
||||||
))
|
))
|
||||||
@@ -221,7 +221,7 @@ pub(crate) mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
super::inbox(&conn, act.clone()),
|
super::inbox(&conn, act),
|
||||||
Err(super::Error::Inbox(
|
Err(super::Error::Inbox(
|
||||||
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
||||||
))
|
))
|
||||||
@@ -249,7 +249,7 @@ pub(crate) mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
super::inbox(&conn, act.clone()),
|
super::inbox(&conn, act),
|
||||||
Err(super::Error::Inbox(
|
Err(super::Error::Inbox(
|
||||||
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
||||||
))
|
))
|
||||||
@@ -324,7 +324,7 @@ pub(crate) mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
super::inbox(&conn, act.clone()),
|
super::inbox(&conn, act),
|
||||||
Err(super::Error::Inbox(
|
Err(super::Error::Inbox(
|
||||||
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
||||||
))
|
))
|
||||||
@@ -362,7 +362,7 @@ pub(crate) mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
super::inbox(&conn, act.clone()),
|
super::inbox(&conn, act),
|
||||||
Err(super::Error::Inbox(
|
Err(super::Error::Inbox(
|
||||||
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
||||||
))
|
))
|
||||||
@@ -397,7 +397,7 @@ pub(crate) mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
super::inbox(&conn, act.clone()),
|
super::inbox(&conn, act),
|
||||||
Err(super::Error::Inbox(
|
Err(super::Error::Inbox(
|
||||||
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use plume_common::utils::md_to_html;
|
use plume_common::utils::{iri_percent_encode_seg, md_to_html};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
#[derive(Clone, Identifiable, Queryable)]
|
#[derive(Clone, Identifiable, Queryable)]
|
||||||
@@ -173,8 +173,8 @@ impl Instance {
|
|||||||
"{instance}/{prefix}/{name}/{box_name}",
|
"{instance}/{prefix}/{name}/{box_name}",
|
||||||
instance = self.public_domain,
|
instance = self.public_domain,
|
||||||
prefix = prefix,
|
prefix = prefix,
|
||||||
name = name,
|
name = iri_percent_encode_seg(name),
|
||||||
box_name = box_name
|
box_name = iri_percent_encode_seg(box_name)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +523,7 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let inst = Instance::get(conn, inst.id).unwrap();
|
let inst = Instance::get(conn, inst.id).unwrap();
|
||||||
assert_eq!(inst.name, "NewName".to_owned());
|
assert_eq!(inst.name, "NewName".to_owned());
|
||||||
assert_eq!(inst.open_registrations, false);
|
assert!(!inst.open_registrations);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
inst.long_description.get(),
|
inst.long_description.get(),
|
||||||
"[long_description](/with_link)"
|
"[long_description](/with_link)"
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
///
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```ignore
|
||||||
/// impl Model {
|
/// impl Model {
|
||||||
/// find_by!(model_table, name_of_the_function, field1 as String, field2 as i32);
|
/// find_by!(model_table, name_of_the_function, field1 as String, field2 as i32);
|
||||||
/// }
|
/// }
|
||||||
@@ -201,7 +201,7 @@ macro_rules! find_by {
|
|||||||
///
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```ignore
|
||||||
/// impl Model {
|
/// impl Model {
|
||||||
/// list_by!(model_table, name_of_the_function, field1 as String);
|
/// list_by!(model_table, name_of_the_function, field1 as String);
|
||||||
/// }
|
/// }
|
||||||
@@ -225,7 +225,7 @@ macro_rules! list_by {
|
|||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```ignore
|
||||||
/// impl Model {
|
/// impl Model {
|
||||||
/// get!(model_table);
|
/// get!(model_table);
|
||||||
/// }
|
/// }
|
||||||
@@ -248,7 +248,7 @@ macro_rules! get {
|
|||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```ignore
|
||||||
/// impl Model {
|
/// impl Model {
|
||||||
/// insert!(model_table, NewModelType);
|
/// insert!(model_table, NewModelType);
|
||||||
/// }
|
/// }
|
||||||
@@ -280,7 +280,7 @@ macro_rules! insert {
|
|||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```ignore
|
||||||
/// impl Model {
|
/// impl Model {
|
||||||
/// last!(model_table);
|
/// last!(model_table);
|
||||||
/// }
|
/// }
|
||||||
@@ -354,7 +354,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn db<'a>() -> db_conn::DbConn {
|
pub fn db() -> db_conn::DbConn {
|
||||||
db_conn::DbConn((*DB_POOL).get().unwrap())
|
db_conn::DbConn((*DB_POOL).get().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*,
|
instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, users::User,
|
||||||
users::User, Connection, Error, Result, CONFIG,
|
Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{ActorAndObjectRef, Like as LikeAct, Undo},
|
activity::{ActorAndObjectRef, Like as LikeAct, Undo},
|
||||||
@@ -85,11 +85,11 @@ impl Like {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, LikeAct, &DbConn> for Post {
|
impl AsObject<User, LikeAct, &Connection> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Like;
|
type Output = Like;
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Like> {
|
fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Like> {
|
||||||
let res = Like::insert(
|
let res = Like::insert(
|
||||||
conn,
|
conn,
|
||||||
NewLike {
|
NewLike {
|
||||||
@@ -105,15 +105,15 @@ impl AsObject<User, LikeAct, &DbConn> for Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<DbConn> for Like {
|
impl FromId<Connection> for Like {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = LikeAct;
|
type Object = LikeAct;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
||||||
Like::find_by_ap_url(conn, id)
|
Like::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, act: LikeAct) -> Result<Self> {
|
fn from_activity(conn: &Connection, act: LikeAct) -> Result<Self> {
|
||||||
let res = Like::insert(
|
let res = Like::insert(
|
||||||
conn,
|
conn,
|
||||||
NewLike {
|
NewLike {
|
||||||
@@ -154,17 +154,17 @@ impl FromId<DbConn> for Like {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Like {
|
impl AsObject<User, Undo, &Connection> for Like {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
||||||
if actor.id == self.user_id {
|
if actor.id == self.user_id {
|
||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(conn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -199,7 +199,7 @@ mod tests {
|
|||||||
let (posts, _users, _blogs) = fill_database(&conn);
|
let (posts, _users, _blogs) = fill_database(&conn);
|
||||||
let post = &posts[0];
|
let post = &posts[0];
|
||||||
let user = &post.get_authors(&conn)?[0];
|
let user = &post.get_authors(&conn)?[0];
|
||||||
let like = Like::insert(&*conn, NewLike::new(post, user))?;
|
let like = Like::insert(&conn, NewLike::new(post, user))?;
|
||||||
let act = like.to_activity(&conn).unwrap();
|
let act = like.to_activity(&conn).unwrap();
|
||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
@@ -223,8 +223,8 @@ mod tests {
|
|||||||
let (posts, _users, _blogs) = fill_database(&conn);
|
let (posts, _users, _blogs) = fill_database(&conn);
|
||||||
let post = &posts[0];
|
let post = &posts[0];
|
||||||
let user = &post.get_authors(&conn)?[0];
|
let user = &post.get_authors(&conn)?[0];
|
||||||
let like = Like::insert(&*conn, NewLike::new(post, user))?;
|
let like = Like::insert(&conn, NewLike::new(post, user))?;
|
||||||
let act = like.build_undo(&*conn)?;
|
let act = like.build_undo(&conn)?;
|
||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
|
|||||||
@@ -297,6 +297,28 @@ impl List {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||||
|
if let Some(user_id) = self.user_id {
|
||||||
|
diesel::delete(
|
||||||
|
lists::table
|
||||||
|
.filter(lists::user_id.eq(user_id))
|
||||||
|
.filter(lists::name.eq(&self.name)),
|
||||||
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(Error::from)
|
||||||
|
} else {
|
||||||
|
diesel::delete(
|
||||||
|
lists::table
|
||||||
|
.filter(lists::user_id.is_null())
|
||||||
|
.filter(lists::name.eq(&self.name)),
|
||||||
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func! {set: set_users, User, add_users}
|
func! {set: set_users, User, add_users}
|
||||||
func! {set: set_blogs, Blog, add_blogs}
|
func! {set: set_blogs, Blog, add_blogs}
|
||||||
func! {set: set_words, Word, add_words}
|
func! {set: set_words, Word, add_words}
|
||||||
@@ -413,7 +435,7 @@ mod tests {
|
|||||||
&List::find_for_user_by_name(conn, l1.user_id, &l1.name).unwrap(),
|
&List::find_for_user_by_name(conn, l1.user_id, &l1.name).unwrap(),
|
||||||
);
|
);
|
||||||
l_eq(
|
l_eq(
|
||||||
&&l1u,
|
&l1u,
|
||||||
&List::find_for_user_by_name(conn, l1u.user_id, &l1u.name).unwrap(),
|
&List::find_for_user_by_name(conn, l1u.user_id, &l1u.name).unwrap(),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias,
|
ap_url, instance::Instance, safe_string::SafeString, schema::medias, users::User, Connection,
|
||||||
users::User, Connection, Error, Result, CONFIG,
|
Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{object::Image, prelude::*};
|
use activitystreams::{object::Image, prelude::*};
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
@@ -42,7 +42,7 @@ pub struct NewMedia {
|
|||||||
pub owner_id: i32,
|
pub owner_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum MediaCategory {
|
pub enum MediaCategory {
|
||||||
Image,
|
Image,
|
||||||
Audio,
|
Audio,
|
||||||
@@ -206,7 +206,7 @@ impl Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge with save_remote?
|
// TODO: merge with save_remote?
|
||||||
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> {
|
pub fn from_activity(conn: &Connection, image: &Image) -> Result<Media> {
|
||||||
let remote_url = image
|
let remote_url = image
|
||||||
.url()
|
.url()
|
||||||
.and_then(|url| url.to_as_uri())
|
.and_then(|url| url.to_as_uri())
|
||||||
@@ -258,7 +258,7 @@ impl Media {
|
|||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
if updated {
|
if updated {
|
||||||
diesel::update(&media).set(&media).execute(&**conn)?;
|
diesel::update(&media).set(&media).execute(conn)?;
|
||||||
}
|
}
|
||||||
Ok(media)
|
Ok(media)
|
||||||
})
|
})
|
||||||
@@ -343,7 +343,7 @@ pub(crate) mod tests {
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub(crate) fn fill_database(conn: &Conn) -> (Vec<User>, Vec<Media>) {
|
pub(crate) fn fill_database(conn: &Conn) -> (Vec<User>, Vec<Media>) {
|
||||||
let mut wd = current_dir().unwrap().to_path_buf();
|
let mut wd = current_dir().unwrap();
|
||||||
while wd.pop() {
|
while wd.pop() {
|
||||||
if wd.join(".git").exists() {
|
if wd.join(".git").exists() {
|
||||||
set_current_dir(wd).unwrap();
|
set_current_dir(wd).unwrap();
|
||||||
@@ -456,7 +456,7 @@ pub(crate) mod tests {
|
|||||||
let media = Media::insert(
|
let media = Media::insert(
|
||||||
conn,
|
conn,
|
||||||
NewMedia {
|
NewMedia {
|
||||||
file_path: path.clone(),
|
file_path: path,
|
||||||
alt_text: "alt message".to_owned(),
|
alt_text: "alt message".to_owned(),
|
||||||
is_remote: false,
|
is_remote: false,
|
||||||
remote_url: None,
|
remote_url: None,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions,
|
comments::Comment, notifications::*, posts::Post, schema::mentions, users::User, Connection,
|
||||||
users::User, Connection, Error, Result,
|
Error, Result,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
base::BaseExt,
|
base::BaseExt,
|
||||||
@@ -60,7 +60,7 @@ impl Mention {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_activity(conn: &DbConn, ment: &str) -> Result<link::Mention> {
|
pub fn build_activity(conn: &Connection, ment: &str) -> Result<link::Mention> {
|
||||||
let user = User::find_by_fqn(conn, ment)?;
|
let user = User::find_by_fqn(conn, ment)?;
|
||||||
let mut mention = link::Mention::new();
|
let mut mention = link::Mention::new();
|
||||||
mention.set_href(user.ap_url.parse::<IriString>()?);
|
mention.set_href(user.ap_url.parse::<IriString>()?);
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ mod tests {
|
|||||||
let request = PasswordResetRequest::find_by_token(&conn, &token)
|
let request = PasswordResetRequest::find_by_token(&conn, &token)
|
||||||
.expect("couldn't retrieve request");
|
.expect("couldn't retrieve request");
|
||||||
|
|
||||||
assert!(&token.len() > &32);
|
assert!(token.len() > 32);
|
||||||
assert_eq!(&request.email, &admin_email);
|
assert_eq!(&request.email, &admin_email);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -103,8 +103,8 @@ mod tests {
|
|||||||
user_tests::fill_database(&conn);
|
user_tests::fill_database(&conn);
|
||||||
let admin_email = "admin@example.com";
|
let admin_email = "admin@example.com";
|
||||||
|
|
||||||
PasswordResetRequest::insert(&conn, &admin_email).expect("couldn't insert new request");
|
PasswordResetRequest::insert(&conn, admin_email).expect("couldn't insert new request");
|
||||||
PasswordResetRequest::insert(&conn, &admin_email)
|
PasswordResetRequest::insert(&conn, admin_email)
|
||||||
.expect("couldn't insert second request");
|
.expect("couldn't insert second request");
|
||||||
|
|
||||||
let count = password_reset_requests::table.count().get_result(&*conn);
|
let count = password_reset_requests::table.count().get_result(&*conn);
|
||||||
@@ -132,7 +132,7 @@ mod tests {
|
|||||||
.execute(&*conn)
|
.execute(&*conn)
|
||||||
.expect("could not insert request");
|
.expect("could not insert request");
|
||||||
|
|
||||||
match PasswordResetRequest::find_by_token(&conn, &token) {
|
match PasswordResetRequest::find_by_token(&conn, token) {
|
||||||
Err(Error::Expired) => (),
|
Err(Error::Expired) => (),
|
||||||
_ => panic!("Received unexpected result finding expired token"),
|
_ => panic!("Received unexpected result finding expired token"),
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ mod tests {
|
|||||||
user_tests::fill_database(&conn);
|
user_tests::fill_database(&conn);
|
||||||
let admin_email = "admin@example.com";
|
let admin_email = "admin@example.com";
|
||||||
|
|
||||||
let token = PasswordResetRequest::insert(&conn, &admin_email)
|
let token = PasswordResetRequest::insert(&conn, admin_email)
|
||||||
.expect("couldn't insert new request");
|
.expect("couldn't insert new request");
|
||||||
PasswordResetRequest::find_and_delete_by_token(&conn, &token)
|
PasswordResetRequest::find_and_delete_by_token(&conn, &token)
|
||||||
.expect("couldn't find and delete request");
|
.expect("couldn't find and delete request");
|
||||||
|
|||||||
+36
-31
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, blogs::Blog, db_conn::DbConn, instance::Instance, medias::Media, mentions::Mention,
|
ap_url, blogs::Blog, instance::Instance, medias::Media, mentions::Mention, post_authors::*,
|
||||||
post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User,
|
safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, Connection, Error,
|
||||||
Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN,
|
PostEvent::*, Result, CONFIG, POST_CHAN,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{Create, Delete, Update},
|
activity::{Create, Delete, Update},
|
||||||
@@ -134,7 +134,7 @@ impl Post {
|
|||||||
.filter(posts::published.eq(true))
|
.filter(posts::published.eq(true))
|
||||||
.count()
|
.count()
|
||||||
.load(conn)?
|
.load(conn)?
|
||||||
.get(0)
|
.first()
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(Error::NotFound)
|
.ok_or(Error::NotFound)
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ impl Post {
|
|||||||
ap_url(&format!(
|
ap_url(&format!(
|
||||||
"{}/~/{}/{}/",
|
"{}/~/{}/{}/",
|
||||||
CONFIG.base_url,
|
CONFIG.base_url,
|
||||||
blog.fqn,
|
iri_percent_encode_seg(&blog.fqn),
|
||||||
iri_percent_encode_seg(slug)
|
iri_percent_encode_seg(slug)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -465,7 +465,7 @@ impl Post {
|
|||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
for (m, id) in &mentions {
|
for (m, id) in &mentions {
|
||||||
if !old_user_mentioned.contains(id) {
|
if !old_user_mentioned.contains(id) {
|
||||||
Mention::from_activity(&*conn, m, self.id, true, true)?;
|
Mention::from_activity(conn, m, self.id, true, true)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ impl Post {
|
|||||||
.filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
|
.filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
let old_tags = Tag::for_post(&*conn, self.id)?;
|
let old_tags = Tag::for_post(conn, self.id)?;
|
||||||
let old_tags_name = old_tags
|
let old_tags_name = old_tags
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|tag| {
|
.filter_map(|tag| {
|
||||||
@@ -525,7 +525,7 @@ impl Post {
|
|||||||
.filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
|
.filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
let old_tags = Tag::for_post(&*conn, self.id)?;
|
let old_tags = Tag::for_post(conn, self.id)?;
|
||||||
let old_tags_name = old_tags
|
let old_tags_name = old_tags
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|tag| {
|
.filter_map(|tag| {
|
||||||
@@ -615,15 +615,15 @@ impl Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<DbConn> for Post {
|
impl FromId<Connection> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = LicensedArticle;
|
type Object = LicensedArticle;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> {
|
fn from_activity(conn: &Connection, article: LicensedArticle) -> Result<Self> {
|
||||||
let license = article.ext_one.license.unwrap_or_default();
|
let license = article.ext_one.license.unwrap_or_default();
|
||||||
let article = article.inner;
|
let article = article.inner;
|
||||||
|
|
||||||
@@ -756,7 +756,11 @@ impl FromId<DbConn> for Post {
|
|||||||
let timestamp_secs = published.unix_timestamp();
|
let timestamp_secs = published.unix_timestamp();
|
||||||
let timestamp_nanos = published.unix_timestamp_nanos()
|
let timestamp_nanos = published.unix_timestamp_nanos()
|
||||||
- (timestamp_secs as i128) * 1000i128 * 1000i128 * 1000i128;
|
- (timestamp_secs as i128) * 1000i128 * 1000i128 * 1000i128;
|
||||||
NaiveDateTime::from_timestamp(timestamp_secs, timestamp_nanos as u32)
|
NaiveDateTime::from_timestamp_opt(
|
||||||
|
timestamp_secs,
|
||||||
|
timestamp_nanos as u32,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
}),
|
}),
|
||||||
subtitle: article
|
subtitle: article
|
||||||
.summary()
|
.summary()
|
||||||
@@ -817,21 +821,21 @@ impl FromId<DbConn> for Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Post {
|
impl AsObject<User, Create, &Connection> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self::Output> {
|
fn activity(self, _conn: &Connection, _actor: User, _id: &str) -> Result<Self::Output> {
|
||||||
// TODO: check that _actor is actually one of the author?
|
// TODO: check that _actor is actually one of the author?
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Delete, &DbConn> for Post {
|
impl AsObject<User, Delete, &Connection> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<Self::Output> {
|
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<Self::Output> {
|
||||||
let can_delete = self
|
let can_delete = self
|
||||||
.get_authors(conn)?
|
.get_authors(conn)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -855,16 +859,16 @@ pub struct PostUpdate {
|
|||||||
pub tags: Option<serde_json::Value>,
|
pub tags: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<DbConn> for PostUpdate {
|
impl FromId<Connection> for PostUpdate {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = LicensedArticle;
|
type Object = LicensedArticle;
|
||||||
|
|
||||||
fn from_db(_: &DbConn, _: &str) -> Result<Self> {
|
fn from_db(_: &Connection, _: &str) -> Result<Self> {
|
||||||
// Always fail because we always want to deserialize the AP object
|
// Always fail because we always want to deserialize the AP object
|
||||||
Err(Error::NotFound)
|
Err(Error::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, updated: Self::Object) -> Result<Self> {
|
fn from_activity(conn: &Connection, updated: Self::Object) -> Result<Self> {
|
||||||
let mut post_update = PostUpdate {
|
let mut post_update = PostUpdate {
|
||||||
ap_url: updated
|
ap_url: updated
|
||||||
.ap_object_ref()
|
.ap_object_ref()
|
||||||
@@ -919,11 +923,11 @@ impl FromId<DbConn> for PostUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Update, &DbConn> for PostUpdate {
|
impl AsObject<User, Update, &Connection> for PostUpdate {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
||||||
let mut post =
|
let mut post =
|
||||||
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
|
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
|
||||||
|
|
||||||
@@ -1023,6 +1027,7 @@ impl From<PostEvent> for Arc<Post> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::db_conn::DbConn;
|
||||||
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
use crate::inbox::{inbox, tests::fill_database, InboxResult};
|
||||||
use crate::mentions::{Mention, NewMention};
|
use crate::mentions::{Mention, NewMention};
|
||||||
use crate::safe_string::SafeString;
|
use crate::safe_string::SafeString;
|
||||||
@@ -1036,7 +1041,7 @@ mod tests {
|
|||||||
let post = &posts[0];
|
let post = &posts[0];
|
||||||
let mentioned = &users[1];
|
let mentioned = &users[1];
|
||||||
let mention = Mention::insert(
|
let mention = Mention::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewMention {
|
NewMention {
|
||||||
mentioned_id: mentioned.id,
|
mentioned_id: mentioned.id,
|
||||||
post_id: Some(post.id),
|
post_id: Some(post.id),
|
||||||
@@ -1044,7 +1049,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
(post.to_owned(), mention.to_owned(), posts, users, blogs)
|
(post.to_owned(), mention, posts, users, blogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a post, get it's Create activity, delete the post,
|
// creates a post, get it's Create activity, delete the post,
|
||||||
@@ -1053,9 +1058,9 @@ mod tests {
|
|||||||
fn self_federation() {
|
fn self_federation() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (_, users, blogs) = fill_database(&conn);
|
let (_, users, blogs) = fill_database(conn);
|
||||||
let post = Post::insert(
|
let post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "yo".into(),
|
slug: "yo".into(),
|
||||||
@@ -1072,19 +1077,19 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
PostAuthor::insert(
|
PostAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPostAuthor {
|
NewPostAuthor {
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
author_id: users[0].id,
|
author_id: users[0].id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let create = post.create_activity(&conn).unwrap();
|
let create = post.create_activity(conn).unwrap();
|
||||||
post.delete(&conn).unwrap();
|
post.delete(conn).unwrap();
|
||||||
|
|
||||||
match inbox(&conn, serde_json::to_value(create).unwrap()).unwrap() {
|
match inbox(conn, serde_json::to_value(create).unwrap()).unwrap() {
|
||||||
InboxResult::Post(p) => {
|
InboxResult::Post(p) => {
|
||||||
assert!(p.is_author(&conn, users[0].id).unwrap());
|
assert!(p.is_author(conn, users[0].id).unwrap());
|
||||||
assert_eq!(p.source, "Hello".to_owned());
|
assert_eq!(p.source, "Hello".to_owned());
|
||||||
assert_eq!(p.blog_id, blogs[0].id);
|
assert_eq!(p.blog_id, blogs[0].id);
|
||||||
assert_eq!(p.content, SafeString::new("Hello"));
|
assert_eq!(p.content, SafeString::new("Hello"));
|
||||||
@@ -1221,7 +1226,7 @@ mod tests {
|
|||||||
let actual = to_value(act)?;
|
let actual = to_value(act)?;
|
||||||
|
|
||||||
let id = actual["id"].to_string();
|
let id = actual["id"].to_string();
|
||||||
let (id_pre, id_post) = id.rsplit_once("-").unwrap();
|
let (id_pre, id_post) = id.rsplit_once('-').unwrap();
|
||||||
assert_eq!(post.ap_url, "https://plu.me/~/BlogName/testing");
|
assert_eq!(post.ap_url, "https://plu.me/~/BlogName/testing");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
id_pre,
|
id_pre,
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ impl Actor for RemoteFetchActor {
|
|||||||
RemoteUserFound(user) => match self.conn.get() {
|
RemoteUserFound(user) => match self.conn.get() {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
let conn = DbConn(conn);
|
let conn = DbConn(conn);
|
||||||
|
if user
|
||||||
|
.get_instance(&conn)
|
||||||
|
.map_or(false, |instance| instance.blocked)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Don't call these functions in parallel
|
// Don't call these functions in parallel
|
||||||
// for the case database connections limit is too small
|
// for the case database connections limit is too small
|
||||||
fetch_and_cache_articles(&user, &conn);
|
fetch_and_cache_articles(&user, &conn);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares,
|
instance::Instance, notifications::*, posts::Post, schema::reshares, timeline::*, users::User,
|
||||||
timeline::*, users::User, Connection, Error, Result, CONFIG,
|
Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{ActorAndObjectRef, Announce, Undo},
|
activity::{ActorAndObjectRef, Announce, Undo},
|
||||||
@@ -113,11 +113,11 @@ impl Reshare {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Announce, &DbConn> for Post {
|
impl AsObject<User, Announce, &Connection> for Post {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = Reshare;
|
type Output = Reshare;
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Reshare> {
|
fn activity(self, conn: &Connection, actor: User, id: &str) -> Result<Reshare> {
|
||||||
let conn = conn;
|
let conn = conn;
|
||||||
let reshare = Reshare::insert(
|
let reshare = Reshare::insert(
|
||||||
conn,
|
conn,
|
||||||
@@ -134,15 +134,15 @@ impl AsObject<User, Announce, &DbConn> for Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromId<DbConn> for Reshare {
|
impl FromId<Connection> for Reshare {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = Announce;
|
type Object = Announce;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
||||||
Reshare::find_by_ap_url(conn, id)
|
Reshare::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, act: Announce) -> Result<Self> {
|
fn from_activity(conn: &Connection, act: Announce) -> Result<Self> {
|
||||||
let res = Reshare::insert(
|
let res = Reshare::insert(
|
||||||
conn,
|
conn,
|
||||||
NewReshare {
|
NewReshare {
|
||||||
@@ -183,17 +183,17 @@ impl FromId<DbConn> for Reshare {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Reshare {
|
impl AsObject<User, Undo, &Connection> for Reshare {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
||||||
if actor.id == self.user_id {
|
if actor.id == self.user_id {
|
||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -229,7 +229,7 @@ mod test {
|
|||||||
let (posts, _users, _blogs) = fill_database(&conn);
|
let (posts, _users, _blogs) = fill_database(&conn);
|
||||||
let post = &posts[0];
|
let post = &posts[0];
|
||||||
let user = &post.get_authors(&conn)?[0];
|
let user = &post.get_authors(&conn)?[0];
|
||||||
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?;
|
let reshare = Reshare::insert(&conn, NewReshare::new(post, user))?;
|
||||||
let act = reshare.to_activity(&conn).unwrap();
|
let act = reshare.to_activity(&conn).unwrap();
|
||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
@@ -253,8 +253,8 @@ mod test {
|
|||||||
let (posts, _users, _blogs) = fill_database(&conn);
|
let (posts, _users, _blogs) = fill_database(&conn);
|
||||||
let post = &posts[0];
|
let post = &posts[0];
|
||||||
let user = &post.get_authors(&conn)?[0];
|
let user = &post.get_authors(&conn)?[0];
|
||||||
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?;
|
let reshare = Reshare::insert(&conn, NewReshare::new(post, user))?;
|
||||||
let act = reshare.build_undo(&*conn)?;
|
let act = reshare.build_undo(&conn)?;
|
||||||
|
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
"actor": "https://plu.me/@/admin/",
|
"actor": "https://plu.me/@/admin/",
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ fn url_add_prefix(url: &str) -> Option<Cow<'_, str>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, AsExpression, FromSqlRow, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow, Default)]
|
||||||
#[sql_type = "Text"]
|
#[sql_type = "Text"]
|
||||||
pub struct SafeString {
|
pub struct SafeString {
|
||||||
value: String,
|
value: String,
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ mod tests {
|
|||||||
|
|
||||||
let searcher = Arc::new(get_searcher(&CONFIG.search_tokenizers));
|
let searcher = Arc::new(get_searcher(&CONFIG.search_tokenizers));
|
||||||
SearchActor::init(searcher.clone(), db_pool.clone());
|
SearchActor::init(searcher.clone(), db_pool.clone());
|
||||||
let conn = db_pool.clone().get().unwrap();
|
let conn = db_pool.get().unwrap();
|
||||||
|
|
||||||
let title = random_hex()[..8].to_owned();
|
let title = random_hex()[..8].to_owned();
|
||||||
let (_instance, _user, blog) = fill_database(&conn);
|
let (_instance, _user, blog) = fill_database(&conn);
|
||||||
@@ -161,41 +161,43 @@ mod tests {
|
|||||||
long_description_html: "<p>Good morning</p>".to_string(),
|
long_description_html: "<p>Good morning</p>".to_string(),
|
||||||
short_description: SafeString::new("Hello"),
|
short_description: SafeString::new("Hello"),
|
||||||
short_description_html: "<p>Hello</p>".to_string(),
|
short_description_html: "<p>Hello</p>".to_string(),
|
||||||
name: random_hex().to_string(),
|
name: random_hex(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: random_hex().to_string(),
|
public_domain: random_hex(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let user = User::insert(
|
let user = User::insert(
|
||||||
conn,
|
conn,
|
||||||
NewUser {
|
NewUser {
|
||||||
username: random_hex().to_string(),
|
username: random_hex(),
|
||||||
display_name: random_hex().to_string(),
|
display_name: random_hex(),
|
||||||
outbox_url: random_hex().to_string(),
|
outbox_url: random_hex(),
|
||||||
inbox_url: random_hex().to_string(),
|
inbox_url: random_hex(),
|
||||||
summary: "".to_string(),
|
summary: "".to_string(),
|
||||||
email: None,
|
email: None,
|
||||||
hashed_password: None,
|
hashed_password: None,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
ap_url: random_hex().to_string(),
|
ap_url: random_hex(),
|
||||||
private_key: None,
|
private_key: None,
|
||||||
public_key: "".to_string(),
|
public_key: "".to_string(),
|
||||||
shared_inbox_url: None,
|
shared_inbox_url: None,
|
||||||
followers_endpoint: random_hex().to_string(),
|
followers_endpoint: random_hex(),
|
||||||
avatar_id: None,
|
avatar_id: None,
|
||||||
summary_html: SafeString::new(""),
|
summary_html: SafeString::new(""),
|
||||||
role: 0,
|
role: 0,
|
||||||
fqn: random_hex().to_string(),
|
fqn: random_hex(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut blog = NewBlog::default();
|
let blog = NewBlog {
|
||||||
blog.instance_id = instance.id;
|
instance_id: instance.id,
|
||||||
blog.actor_id = random_hex().to_string();
|
actor_id: random_hex(),
|
||||||
blog.ap_url = random_hex().to_string();
|
ap_url: random_hex(),
|
||||||
blog.inbox_url = random_hex().to_string();
|
inbox_url: random_hex(),
|
||||||
blog.outbox_url = random_hex().to_string();
|
outbox_url: random_hex(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let blog = Blog::insert(conn, blog).unwrap();
|
let blog = Blog::insert(conn, blog).unwrap();
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
conn,
|
conn,
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ pub(crate) mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
searcher.add_document(&conn, &post).unwrap();
|
searcher.add_document(conn, &post).unwrap();
|
||||||
searcher.commit();
|
searcher.commit();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
searcher.search_document(conn, Query::from_str(&title).unwrap(), (0, 1))[0].id,
|
searcher.search_document(conn, Query::from_str(&title).unwrap(), (0, 1))[0].id,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ macro_rules! gen_to_string {
|
|||||||
)*
|
)*
|
||||||
$(
|
$(
|
||||||
for val in &$self.$date {
|
for val in &$self.$date {
|
||||||
$result.push_str(&format!("{}:{} ", stringify!($date), NaiveDate::from_num_days_from_ce(*val as i32).format("%Y-%m-%d")));
|
$result.push_str(&format!("{}:{} ", stringify!($date), NaiveDate::from_num_days_from_ce_opt(*val as i32).unwrap().format("%Y-%m-%d")));
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
@@ -180,12 +180,16 @@ impl PlumeQuery {
|
|||||||
|
|
||||||
if self.before.is_some() || self.after.is_some() {
|
if self.before.is_some() || self.after.is_some() {
|
||||||
// if at least one range bound is provided
|
// if at least one range bound is provided
|
||||||
let after = self
|
let after = self.after.unwrap_or_else(|| {
|
||||||
.after
|
i64::from(
|
||||||
.unwrap_or_else(|| i64::from(NaiveDate::from_ymd(2000, 1, 1).num_days_from_ce()));
|
NaiveDate::from_ymd_opt(2000, 1, 1)
|
||||||
|
.unwrap()
|
||||||
|
.num_days_from_ce(),
|
||||||
|
)
|
||||||
|
});
|
||||||
let before = self
|
let before = self
|
||||||
.before
|
.before
|
||||||
.unwrap_or_else(|| i64::from(Utc::today().num_days_from_ce()));
|
.unwrap_or_else(|| i64::from(Utc::now().date_naive().num_days_from_ce()));
|
||||||
let field = Searcher::schema().get_field("creation_date").unwrap();
|
let field = Searcher::schema().get_field("creation_date").unwrap();
|
||||||
let range =
|
let range =
|
||||||
RangeQuery::new_i64_bounds(field, Bound::Included(after), Bound::Included(before));
|
RangeQuery::new_i64_bounds(field, Bound::Included(after), Bound::Included(before));
|
||||||
@@ -202,16 +206,20 @@ impl PlumeQuery {
|
|||||||
pub fn before<D: Datelike>(&mut self, date: &D) -> &mut Self {
|
pub fn before<D: Datelike>(&mut self, date: &D) -> &mut Self {
|
||||||
let before = self
|
let before = self
|
||||||
.before
|
.before
|
||||||
.unwrap_or_else(|| i64::from(Utc::today().num_days_from_ce()));
|
.unwrap_or_else(|| i64::from(Utc::now().date_naive().num_days_from_ce()));
|
||||||
self.before = Some(cmp::min(before, i64::from(date.num_days_from_ce())));
|
self.before = Some(cmp::min(before, i64::from(date.num_days_from_ce())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// documents older than the provided date will be ignored
|
// documents older than the provided date will be ignored
|
||||||
pub fn after<D: Datelike>(&mut self, date: &D) -> &mut Self {
|
pub fn after<D: Datelike>(&mut self, date: &D) -> &mut Self {
|
||||||
let after = self
|
let after = self.after.unwrap_or_else(|| {
|
||||||
.after
|
i64::from(
|
||||||
.unwrap_or_else(|| i64::from(NaiveDate::from_ymd(2000, 1, 1).num_days_from_ce()));
|
NaiveDate::from_ymd_opt(2000, 1, 1)
|
||||||
|
.unwrap()
|
||||||
|
.num_days_from_ce(),
|
||||||
|
)
|
||||||
|
});
|
||||||
self.after = Some(cmp::max(after, i64::from(date.num_days_from_ce())));
|
self.after = Some(cmp::max(after, i64::from(date.num_days_from_ce())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ mod tests {
|
|||||||
let (posts, _users, _blogs) = fill_database(conn);
|
let (posts, _users, _blogs) = fill_database(conn);
|
||||||
let post_id = posts[0].id;
|
let post_id = posts[0].id;
|
||||||
let mut ht = Hashtag::new();
|
let mut ht = Hashtag::new();
|
||||||
ht.set_href(ap_url(&format!("https://plu.me/tag/a_tag")).parse::<IriString>()?);
|
ht.set_href(ap_url("https://plu.me/tag/a_tag").parse::<IriString>()?);
|
||||||
ht.set_name("a_tag".to_string());
|
ht.set_name("a_tag".to_string());
|
||||||
let tag = Tag::from_activity(conn, &ht, post_id, true)?;
|
let tag = Tag::from_activity(conn, &ht, post_id, true)?;
|
||||||
|
|
||||||
|
|||||||
+130
-122
@@ -1,5 +1,4 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn,
|
|
||||||
lists::List,
|
lists::List,
|
||||||
posts::Post,
|
posts::Post,
|
||||||
schema::{posts, timeline, timeline_definition},
|
schema::{posts, timeline, timeline_definition},
|
||||||
@@ -12,9 +11,9 @@ use std::ops::Deref;
|
|||||||
pub(crate) mod query;
|
pub(crate) mod query;
|
||||||
|
|
||||||
pub use self::query::Kind;
|
pub use self::query::Kind;
|
||||||
use self::query::{QueryError, TimelineQuery};
|
pub use self::query::{QueryError, TimelineQuery};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Queryable, Identifiable, AsChangeset)]
|
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset)]
|
||||||
#[table_name = "timeline_definition"]
|
#[table_name = "timeline_definition"]
|
||||||
pub struct Timeline {
|
pub struct Timeline {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
@@ -220,7 +219,7 @@ impl Timeline {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_to_all_timelines(conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<()> {
|
pub fn add_to_all_timelines(conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<()> {
|
||||||
let timelines = timeline_definition::table
|
let timelines = timeline_definition::table
|
||||||
.load::<Self>(conn.deref())
|
.load::<Self>(conn.deref())
|
||||||
.map_err(Error::from)?;
|
.map_err(Error::from)?;
|
||||||
@@ -246,7 +245,26 @@ impl Timeline {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches(&self, conn: &DbConn, post: &Post, kind: Kind<'_>) -> Result<bool> {
|
pub fn remove_post(&self, conn: &Connection, post: &Post) -> Result<bool> {
|
||||||
|
if self.includes_post(conn, post)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
diesel::delete(
|
||||||
|
timeline::table
|
||||||
|
.filter(timeline::timeline_id.eq(self.id))
|
||||||
|
.filter(timeline::post_id.eq(post.id)),
|
||||||
|
)
|
||||||
|
.execute(conn)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_all_posts(&self, conn: &Connection) -> Result<u64> {
|
||||||
|
let count = diesel::delete(timeline::table.filter(timeline::timeline_id.eq(self.id)))
|
||||||
|
.execute(conn)?;
|
||||||
|
Ok(count as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches(&self, conn: &Connection, post: &Post, kind: Kind<'_>) -> Result<bool> {
|
||||||
let query = TimelineQuery::parse(&self.query)?;
|
let query = TimelineQuery::parse(&self.query)?;
|
||||||
query.matches(conn, self, post, kind)
|
query.matches(conn, self, post, kind)
|
||||||
}
|
}
|
||||||
@@ -282,73 +300,63 @@ mod tests {
|
|||||||
fn test_timeline() {
|
fn test_timeline() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let users = userTests::fill_database(&conn);
|
let users = userTests::fill_database(conn);
|
||||||
|
|
||||||
let mut tl1_u1 = Timeline::new_for_user(
|
let mut tl1_u1 = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"my timeline".to_owned(),
|
"my timeline".to_owned(),
|
||||||
"all".to_owned(),
|
"all".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
List::new(
|
List::new(conn, "languages I speak", Some(&users[1]), ListType::Prefix).unwrap();
|
||||||
&conn,
|
|
||||||
"languages I speak",
|
|
||||||
Some(&users[1]),
|
|
||||||
ListType::Prefix,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let tl2_u1 = Timeline::new_for_user(
|
let tl2_u1 = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"another timeline".to_owned(),
|
"another timeline".to_owned(),
|
||||||
"followed".to_owned(),
|
"followed".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tl1_u2 = Timeline::new_for_user(
|
let tl1_u2 = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[1].id,
|
users[1].id,
|
||||||
"english posts".to_owned(),
|
"english posts".to_owned(),
|
||||||
"lang in \"languages I speak\"".to_owned(),
|
"lang in \"languages I speak\"".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tl1_instance = Timeline::new_for_instance(
|
let tl1_instance = Timeline::new_for_instance(
|
||||||
&conn,
|
conn,
|
||||||
"english posts".to_owned(),
|
"english posts".to_owned(),
|
||||||
"license in [cc]".to_owned(),
|
"license in [cc]".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(tl1_u1, Timeline::get(&conn, tl1_u1.id).unwrap());
|
assert_eq!(tl1_u1, Timeline::get(conn, tl1_u1.id).unwrap());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tl2_u1,
|
tl2_u1,
|
||||||
Timeline::find_for_user_by_name(&conn, Some(users[0].id), "another timeline")
|
Timeline::find_for_user_by_name(conn, Some(users[0].id), "another timeline")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tl1_instance,
|
tl1_instance,
|
||||||
Timeline::find_for_user_by_name(&conn, None, "english posts").unwrap()
|
Timeline::find_for_user_by_name(conn, None, "english posts").unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let tl_u1 = Timeline::list_for_user(&conn, Some(users[0].id)).unwrap();
|
let tl_u1 = Timeline::list_for_user(conn, Some(users[0].id)).unwrap();
|
||||||
assert_eq!(3, tl_u1.len()); // it is not 2 because there is a "Your feed" tl created for each user automatically
|
assert_eq!(3, tl_u1.len()); // it is not 2 because there is a "Your feed" tl created for each user automatically
|
||||||
assert!(tl_u1.iter().fold(false, |res, tl| { res || *tl == tl1_u1 }));
|
assert!(tl_u1.iter().any(|tl| *tl == tl1_u1));
|
||||||
assert!(tl_u1.iter().fold(false, |res, tl| { res || *tl == tl2_u1 }));
|
assert!(tl_u1.iter().any(|tl| *tl == tl2_u1));
|
||||||
|
|
||||||
let tl_instance = Timeline::list_for_user(&conn, None).unwrap();
|
let tl_instance = Timeline::list_for_user(conn, None).unwrap();
|
||||||
assert_eq!(3, tl_instance.len()); // there are also the local and federated feed by default
|
assert_eq!(3, tl_instance.len()); // there are also the local and federated feed by default
|
||||||
assert!(tl_instance
|
assert!(tl_instance.iter().any(|tl| *tl == tl1_instance));
|
||||||
.iter()
|
|
||||||
.fold(false, |res, tl| { res || *tl == tl1_instance }));
|
|
||||||
|
|
||||||
tl1_u1.name = "My Super TL".to_owned();
|
tl1_u1.name = "My Super TL".to_owned();
|
||||||
let new_tl1_u2 = tl1_u2.update(&conn).unwrap();
|
let new_tl1_u2 = tl1_u2.update(conn).unwrap();
|
||||||
|
|
||||||
let tl_u2 = Timeline::list_for_user(&conn, Some(users[1].id)).unwrap();
|
let tl_u2 = Timeline::list_for_user(conn, Some(users[1].id)).unwrap();
|
||||||
assert_eq!(2, tl_u2.len()); // same here
|
assert_eq!(2, tl_u2.len()); // same here
|
||||||
assert!(tl_u2
|
assert!(tl_u2.iter().any(|tl| *tl == new_tl1_u2));
|
||||||
.iter()
|
|
||||||
.fold(false, |res, tl| { res || *tl == new_tl1_u2 }));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -358,48 +366,48 @@ mod tests {
|
|||||||
fn test_timeline_creation_error() {
|
fn test_timeline_creation_error() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let users = userTests::fill_database(&conn);
|
let users = userTests::fill_database(conn);
|
||||||
|
|
||||||
assert!(Timeline::new_for_user(
|
assert!(Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"my timeline".to_owned(),
|
"my timeline".to_owned(),
|
||||||
"invalid keyword".to_owned(),
|
"invalid keyword".to_owned(),
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(Timeline::new_for_instance(
|
assert!(Timeline::new_for_instance(
|
||||||
&conn,
|
conn,
|
||||||
"my timeline".to_owned(),
|
"my timeline".to_owned(),
|
||||||
"invalid keyword".to_owned(),
|
"invalid keyword".to_owned(),
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
assert!(Timeline::new_for_user(
|
assert!(Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"my timeline".to_owned(),
|
"my timeline".to_owned(),
|
||||||
"author in non_existant_list".to_owned(),
|
"author in non_existant_list".to_owned(),
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(Timeline::new_for_instance(
|
assert!(Timeline::new_for_instance(
|
||||||
&conn,
|
conn,
|
||||||
"my timeline".to_owned(),
|
"my timeline".to_owned(),
|
||||||
"lang in dont-exist".to_owned(),
|
"lang in dont-exist".to_owned(),
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
List::new(&conn, "friends", Some(&users[0]), ListType::User).unwrap();
|
List::new(conn, "friends", Some(&users[0]), ListType::User).unwrap();
|
||||||
List::new(&conn, "idk", None, ListType::Blog).unwrap();
|
List::new(conn, "idk", None, ListType::Blog).unwrap();
|
||||||
|
|
||||||
assert!(Timeline::new_for_user(
|
assert!(Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"my timeline".to_owned(),
|
"my timeline".to_owned(),
|
||||||
"blog in friends".to_owned(),
|
"blog in friends".to_owned(),
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(Timeline::new_for_instance(
|
assert!(Timeline::new_for_instance(
|
||||||
&conn,
|
conn,
|
||||||
"my timeline".to_owned(),
|
"my timeline".to_owned(),
|
||||||
"not author in idk".to_owned(),
|
"not author in idk".to_owned(),
|
||||||
)
|
)
|
||||||
@@ -413,10 +421,10 @@ mod tests {
|
|||||||
fn test_simple_match() {
|
fn test_simple_match() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (users, blogs) = blogTests::fill_database(&conn);
|
let (users, blogs) = blogTests::fill_database(conn);
|
||||||
|
|
||||||
let gnu_tl = Timeline::new_for_user(
|
let gnu_tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"GNU timeline".to_owned(),
|
"GNU timeline".to_owned(),
|
||||||
"license in [AGPL, LGPL, GPL]".to_owned(),
|
"license in [AGPL, LGPL, GPL]".to_owned(),
|
||||||
@@ -424,7 +432,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let gnu_post = Post::insert(
|
let gnu_post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "slug".to_string(),
|
slug: "slug".to_string(),
|
||||||
@@ -440,10 +448,10 @@ mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(gnu_tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(gnu_tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
|
|
||||||
let non_free_post = Post::insert(
|
let non_free_post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "slug2".to_string(),
|
slug: "slug2".to_string(),
|
||||||
@@ -460,7 +468,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!gnu_tl
|
assert!(!gnu_tl
|
||||||
.matches(&conn, &non_free_post, Kind::Original)
|
.matches(conn, &non_free_post, Kind::Original)
|
||||||
.unwrap());
|
.unwrap());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -471,9 +479,9 @@ mod tests {
|
|||||||
fn test_complex_match() {
|
fn test_complex_match() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (users, blogs) = blogTests::fill_database(&conn);
|
let (users, blogs) = blogTests::fill_database(conn);
|
||||||
Follow::insert(
|
Follow::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewFollow {
|
NewFollow {
|
||||||
follower_id: users[0].id,
|
follower_id: users[0].id,
|
||||||
following_id: users[1].id,
|
following_id: users[1].id,
|
||||||
@@ -483,11 +491,11 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let fav_blogs_list =
|
let fav_blogs_list =
|
||||||
List::new(&conn, "fav_blogs", Some(&users[0]), ListType::Blog).unwrap();
|
List::new(conn, "fav_blogs", Some(&users[0]), ListType::Blog).unwrap();
|
||||||
fav_blogs_list.add_blogs(&conn, &[blogs[0].id]).unwrap();
|
fav_blogs_list.add_blogs(conn, &[blogs[0].id]).unwrap();
|
||||||
|
|
||||||
let my_tl = Timeline::new_for_user(
|
let my_tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"My timeline".to_owned(),
|
"My timeline".to_owned(),
|
||||||
"blog in fav_blogs and not has_cover or local and followed exclude likes"
|
"blog in fav_blogs and not has_cover or local and followed exclude likes"
|
||||||
@@ -496,7 +504,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let post = Post::insert(
|
let post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "about-linux".to_string(),
|
slug: "about-linux".to_string(),
|
||||||
@@ -512,10 +520,10 @@ mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(my_tl.matches(&conn, &post, Kind::Original).unwrap()); // matches because of "blog in fav_blogs" (and there is no cover)
|
assert!(my_tl.matches(conn, &post, Kind::Original).unwrap()); // matches because of "blog in fav_blogs" (and there is no cover)
|
||||||
|
|
||||||
let post = Post::insert(
|
let post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[1].id,
|
blog_id: blogs[1].id,
|
||||||
slug: "about-linux-2".to_string(),
|
slug: "about-linux-2".to_string(),
|
||||||
@@ -533,7 +541,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!my_tl.matches(&conn, &post, Kind::Like(&users[1])).unwrap());
|
assert!(!my_tl.matches(conn, &post, Kind::Like(&users[1])).unwrap());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -543,17 +551,17 @@ mod tests {
|
|||||||
fn test_add_to_all_timelines() {
|
fn test_add_to_all_timelines() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (users, blogs) = blogTests::fill_database(&conn);
|
let (users, blogs) = blogTests::fill_database(conn);
|
||||||
|
|
||||||
let gnu_tl = Timeline::new_for_user(
|
let gnu_tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"GNU timeline".to_owned(),
|
"GNU timeline".to_owned(),
|
||||||
"license in [AGPL, LGPL, GPL]".to_owned(),
|
"license in [AGPL, LGPL, GPL]".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let non_gnu_tl = Timeline::new_for_user(
|
let non_gnu_tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"Stallman disapproved timeline".to_owned(),
|
"Stallman disapproved timeline".to_owned(),
|
||||||
"not license in [AGPL, LGPL, GPL]".to_owned(),
|
"not license in [AGPL, LGPL, GPL]".to_owned(),
|
||||||
@@ -561,7 +569,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let gnu_post = Post::insert(
|
let gnu_post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "slug".to_string(),
|
slug: "slug".to_string(),
|
||||||
@@ -579,7 +587,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let non_free_post = Post::insert(
|
let non_free_post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "slug2".to_string(),
|
slug: "slug2".to_string(),
|
||||||
@@ -596,13 +604,13 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Timeline::add_to_all_timelines(&conn, &gnu_post, Kind::Original).unwrap();
|
Timeline::add_to_all_timelines(conn, &gnu_post, Kind::Original).unwrap();
|
||||||
Timeline::add_to_all_timelines(&conn, &non_free_post, Kind::Original).unwrap();
|
Timeline::add_to_all_timelines(conn, &non_free_post, Kind::Original).unwrap();
|
||||||
|
|
||||||
let res = gnu_tl.get_latest(&conn, 2).unwrap();
|
let res = gnu_tl.get_latest(conn, 2).unwrap();
|
||||||
assert_eq!(res.len(), 1);
|
assert_eq!(res.len(), 1);
|
||||||
assert_eq!(res[0].id, gnu_post.id);
|
assert_eq!(res[0].id, gnu_post.id);
|
||||||
let res = non_gnu_tl.get_latest(&conn, 2).unwrap();
|
let res = non_gnu_tl.get_latest(conn, 2).unwrap();
|
||||||
assert_eq!(res.len(), 1);
|
assert_eq!(res.len(), 1);
|
||||||
assert_eq!(res[0].id, non_free_post.id);
|
assert_eq!(res[0].id, non_free_post.id);
|
||||||
|
|
||||||
@@ -614,10 +622,10 @@ mod tests {
|
|||||||
fn test_matches_lists_direct() {
|
fn test_matches_lists_direct() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (users, blogs) = blogTests::fill_database(&conn);
|
let (users, blogs) = blogTests::fill_database(conn);
|
||||||
|
|
||||||
let gnu_post = Post::insert(
|
let gnu_post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "slug".to_string(),
|
slug: "slug".to_string(),
|
||||||
@@ -634,63 +642,63 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
gnu_post
|
gnu_post
|
||||||
.update_tags(&conn, vec![Tag::build_activity("free".to_owned()).unwrap()])
|
.update_tags(conn, vec![Tag::build_activity("free".to_owned()).unwrap()])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
PostAuthor::insert(
|
PostAuthor::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPostAuthor {
|
NewPostAuthor {
|
||||||
post_id: gnu_post.id,
|
post_id: gnu_post.id,
|
||||||
author_id: blogs[0].list_authors(&conn).unwrap()[0].id,
|
author_id: blogs[0].list_authors(conn).unwrap()[0].id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"blog timeline".to_owned(),
|
"blog timeline".to_owned(),
|
||||||
format!("blog in [{}]", blogs[0].fqn),
|
format!("blog in [{}]", blogs[0].fqn),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"blog timeline".to_owned(),
|
"blog timeline".to_owned(),
|
||||||
"blog in [no_one@nowhere]".to_owned(),
|
"blog in [no_one@nowhere]".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
|
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"author timeline".to_owned(),
|
"author timeline".to_owned(),
|
||||||
format!(
|
format!(
|
||||||
"author in [{}]",
|
"author in [{}]",
|
||||||
blogs[0].list_authors(&conn).unwrap()[0].fqn
|
blogs[0].list_authors(conn).unwrap()[0].fqn
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"author timeline".to_owned(),
|
"author timeline".to_owned(),
|
||||||
format!("author in [{}]", users[2].fqn),
|
format!("author in [{}]", users[2].fqn),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
assert!(tl
|
assert!(tl
|
||||||
.matches(&conn, &gnu_post, Kind::Reshare(&users[2]))
|
.matches(conn, &gnu_post, Kind::Reshare(&users[2]))
|
||||||
.unwrap());
|
.unwrap());
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Like(&users[2])).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Like(&users[2])).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"author timeline".to_owned(),
|
"author timeline".to_owned(),
|
||||||
format!(
|
format!(
|
||||||
@@ -699,50 +707,50 @@ mod tests {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
assert!(!tl
|
assert!(!tl
|
||||||
.matches(&conn, &gnu_post, Kind::Reshare(&users[2]))
|
.matches(conn, &gnu_post, Kind::Reshare(&users[2]))
|
||||||
.unwrap());
|
.unwrap());
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Like(&users[2])).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Like(&users[2])).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
|
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"tag timeline".to_owned(),
|
"tag timeline".to_owned(),
|
||||||
"tags in [free]".to_owned(),
|
"tags in [free]".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"tag timeline".to_owned(),
|
"tag timeline".to_owned(),
|
||||||
"tags in [private]".to_owned(),
|
"tags in [private]".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
|
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"english timeline".to_owned(),
|
"english timeline".to_owned(),
|
||||||
"lang in [en]".to_owned(),
|
"lang in [en]".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"franco-italian timeline".to_owned(),
|
"franco-italian timeline".to_owned(),
|
||||||
"lang in [fr, it]".to_owned(),
|
"lang in [fr, it]".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -786,10 +794,10 @@ mod tests {
|
|||||||
fn test_matches_keyword() {
|
fn test_matches_keyword() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let (users, blogs) = blogTests::fill_database(&conn);
|
let (users, blogs) = blogTests::fill_database(conn);
|
||||||
|
|
||||||
let gnu_post = Post::insert(
|
let gnu_post = Post::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blogs[0].id,
|
blog_id: blogs[0].id,
|
||||||
slug: "slug".to_string(),
|
slug: "slug".to_string(),
|
||||||
@@ -807,61 +815,61 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"Linux title".to_owned(),
|
"Linux title".to_owned(),
|
||||||
"title contains Linux".to_owned(),
|
"title contains Linux".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"Microsoft title".to_owned(),
|
"Microsoft title".to_owned(),
|
||||||
"title contains Microsoft".to_owned(),
|
"title contains Microsoft".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
|
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"Linux subtitle".to_owned(),
|
"Linux subtitle".to_owned(),
|
||||||
"subtitle contains Stallman".to_owned(),
|
"subtitle contains Stallman".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"Microsoft subtitle".to_owned(),
|
"Microsoft subtitle".to_owned(),
|
||||||
"subtitle contains Nadella".to_owned(),
|
"subtitle contains Nadella".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
|
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"Linux content".to_owned(),
|
"Linux content".to_owned(),
|
||||||
"content contains Linux".to_owned(),
|
"content contains Linux".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
let tl = Timeline::new_for_user(
|
let tl = Timeline::new_for_user(
|
||||||
&conn,
|
conn,
|
||||||
users[0].id,
|
users[0].id,
|
||||||
"Microsoft content".to_owned(),
|
"Microsoft content".to_owned(),
|
||||||
"subtitle contains Windows".to_owned(),
|
"subtitle contains Windows".to_owned(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!tl.matches(&conn, &gnu_post, Kind::Original).unwrap());
|
assert!(!tl.matches(conn, &gnu_post, Kind::Original).unwrap());
|
||||||
tl.delete(&conn).unwrap();
|
tl.delete(conn).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
blogs::Blog,
|
blogs::Blog,
|
||||||
db_conn::DbConn,
|
|
||||||
lists::{self, ListType},
|
lists::{self, ListType},
|
||||||
posts::Post,
|
posts::Post,
|
||||||
tags::Tag,
|
tags::Tag,
|
||||||
timeline::Timeline,
|
timeline::Timeline,
|
||||||
users::User,
|
users::User,
|
||||||
Result,
|
Connection, Result,
|
||||||
};
|
};
|
||||||
use plume_common::activity_pub::inbox::AsActor;
|
use plume_common::activity_pub::inbox::AsActor;
|
||||||
use whatlang::{self, Lang};
|
use whatlang::{self, Lang};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum QueryError {
|
pub enum QueryError {
|
||||||
SyntaxError(usize, usize, String),
|
SyntaxError(usize, usize, String),
|
||||||
UnexpectedEndOfQuery,
|
UnexpectedEndOfQuery,
|
||||||
@@ -20,7 +19,7 @@ pub enum QueryError {
|
|||||||
|
|
||||||
pub type QueryResult<T> = std::result::Result<T, QueryError>;
|
pub type QueryResult<T> = std::result::Result<T, QueryError>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Kind<'a> {
|
pub enum Kind<'a> {
|
||||||
Original,
|
Original,
|
||||||
Reshare(&'a User),
|
Reshare(&'a User),
|
||||||
@@ -155,7 +154,7 @@ enum TQ<'a> {
|
|||||||
impl<'a> TQ<'a> {
|
impl<'a> TQ<'a> {
|
||||||
fn matches(
|
fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &DbConn,
|
conn: &Connection,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
@@ -200,7 +199,7 @@ enum Arg<'a> {
|
|||||||
impl<'a> Arg<'a> {
|
impl<'a> Arg<'a> {
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &DbConn,
|
conn: &Connection,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
@@ -225,7 +224,7 @@ enum WithList {
|
|||||||
impl WithList {
|
impl WithList {
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &DbConn,
|
conn: &Connection,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
list: &List<'_>,
|
list: &List<'_>,
|
||||||
@@ -292,7 +291,7 @@ impl WithList {
|
|||||||
WithList::Author { boosts, likes } => match kind {
|
WithList::Author { boosts, likes } => match kind {
|
||||||
Kind::Original => Ok(list
|
Kind::Original => Ok(list
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|a| User::find_by_fqn(&*conn, a).ok())
|
.filter_map(|a| User::find_by_fqn(conn, a).ok())
|
||||||
.any(|a| post.is_author(conn, a.id).unwrap_or(false))),
|
.any(|a| post.is_author(conn, a.id).unwrap_or(false))),
|
||||||
Kind::Reshare(u) => {
|
Kind::Reshare(u) => {
|
||||||
if *boosts {
|
if *boosts {
|
||||||
@@ -361,7 +360,7 @@ enum Bool {
|
|||||||
impl Bool {
|
impl Bool {
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &DbConn,
|
conn: &Connection,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
@@ -654,7 +653,7 @@ impl<'a> TimelineQuery<'a> {
|
|||||||
|
|
||||||
pub fn matches(
|
pub fn matches(
|
||||||
&self,
|
&self,
|
||||||
conn: &DbConn,
|
conn: &Connection,
|
||||||
timeline: &Timeline,
|
timeline: &Timeline,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
kind: Kind<'_>,
|
kind: Kind<'_>,
|
||||||
|
|||||||
+85
-70
@@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, db_conn::DbConn, follows::Follow,
|
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, comments::Comment, db_conn::DbConn,
|
||||||
instance::*, medias::Media, notifications::Notification, post_authors::PostAuthor, posts::Post,
|
follows::Follow, instance::*, medias::Media, notifications::Notification,
|
||||||
safe_string::SafeString, schema::users, timeline::Timeline, Connection, Error, Result,
|
post_authors::PostAuthor, posts::Post, safe_string::SafeString, schema::users,
|
||||||
UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN,
|
timeline::Timeline, Connection, Error, Result, UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::Delete,
|
activity::Delete,
|
||||||
@@ -15,7 +15,10 @@ use activitystreams::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
use diesel::{
|
||||||
|
self, BelongingToDsl, BoolExpressionMethods, ExpressionMethods, OptionalExtension, QueryDsl,
|
||||||
|
RunQueryDsl, TextExpressionMethods,
|
||||||
|
};
|
||||||
use ldap3::{LdapConn, Scope, SearchEntry};
|
use ldap3::{LdapConn, Scope, SearchEntry};
|
||||||
use openssl::{
|
use openssl::{
|
||||||
hash::MessageDigest,
|
hash::MessageDigest,
|
||||||
@@ -165,6 +168,14 @@ impl User {
|
|||||||
notif.delete(conn)?
|
notif.delete(conn)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for comment in Comment::list_by_author(conn, self.id)? {
|
||||||
|
let delete_activity = comment.build_delete(&conn)?;
|
||||||
|
crate::inbox::inbox(
|
||||||
|
conn,
|
||||||
|
serde_json::to_value(&delete_activity).map_err(Error::from)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
diesel::delete(self)
|
diesel::delete(self)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
@@ -186,15 +197,16 @@ impl User {
|
|||||||
pub fn count_local(conn: &Connection) -> Result<i64> {
|
pub fn count_local(conn: &Connection) -> Result<i64> {
|
||||||
users::table
|
users::table
|
||||||
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
||||||
|
.filter(users::role.ne(Role::Instance as i32))
|
||||||
.count()
|
.count()
|
||||||
.get_result(&*conn)
|
.get_result(conn)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_fqn(conn: &DbConn, fqn: &str) -> Result<User> {
|
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<User> {
|
||||||
let from_db = users::table
|
let from_db = users::table
|
||||||
.filter(users::fqn.eq(fqn))
|
.filter(users::fqn.eq(fqn))
|
||||||
.first(&**conn)
|
.first(conn)
|
||||||
.optional()?;
|
.optional()?;
|
||||||
if let Some(from_db) = from_db {
|
if let Some(from_db) = from_db {
|
||||||
Ok(from_db)
|
Ok(from_db)
|
||||||
@@ -203,6 +215,27 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn search_local_by_name(
|
||||||
|
conn: &Connection,
|
||||||
|
name: &str,
|
||||||
|
(min, max): (i32, i32),
|
||||||
|
) -> Result<Vec<User>> {
|
||||||
|
users::table
|
||||||
|
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
||||||
|
.filter(users::role.ne(Role::Instance as i32))
|
||||||
|
// TODO: use `ilike` instead of `like` for PostgreSQL
|
||||||
|
.filter(
|
||||||
|
users::username
|
||||||
|
.like(format!("%{}%", name))
|
||||||
|
.or(users::display_name.like(format!("%{}%", name))),
|
||||||
|
)
|
||||||
|
.order(users::username.asc())
|
||||||
|
.offset(min.into())
|
||||||
|
.limit((max - min).into())
|
||||||
|
.load::<User>(conn)
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Should create user record with normalized(lowercased) email
|
* TODO: Should create user record with normalized(lowercased) email
|
||||||
*/
|
*/
|
||||||
@@ -219,7 +252,7 @@ impl User {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> {
|
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<User> {
|
||||||
let link = resolve(acct.to_owned(), true)?
|
let link = resolve(acct.to_owned(), true)?
|
||||||
.links
|
.links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -246,20 +279,7 @@ impl User {
|
|||||||
fn fetch(url: &str) -> Result<CustomPerson> {
|
fn fetch(url: &str) -> Result<CustomPerson> {
|
||||||
let res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
let res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
// without this workaround, publicKey is not correctly deserialized
|
let json = serde_json::from_str::<CustomPerson>(text)?;
|
||||||
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
|
||||||
let person = serde_json::from_str::<Person>(text)?;
|
|
||||||
let json = CustomPerson::new(
|
|
||||||
ApActor::new(
|
|
||||||
person
|
|
||||||
.clone()
|
|
||||||
.id_unchecked()
|
|
||||||
.ok_or(Error::MissingApProperty)?
|
|
||||||
.to_owned(),
|
|
||||||
person,
|
|
||||||
),
|
|
||||||
ap_sign,
|
|
||||||
); // FIXME: Don't clone()
|
|
||||||
Ok(json)
|
Ok(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,23 +289,13 @@ impl User {
|
|||||||
|
|
||||||
pub fn refetch(&self, conn: &Connection) -> Result<()> {
|
pub fn refetch(&self, conn: &Connection) -> Result<()> {
|
||||||
User::fetch(&self.ap_url.clone()).and_then(|json| {
|
User::fetch(&self.ap_url.clone()).and_then(|json| {
|
||||||
let avatar = Media::save_remote(
|
let avatar = json
|
||||||
conn,
|
.icon()
|
||||||
json.ap_actor_ref()
|
.and_then(|icon| icon.iter().next())
|
||||||
.icon()
|
.and_then(|i| i.clone().extend::<Image, ImageType>().ok())
|
||||||
.ok_or(Error::MissingApProperty)? // FIXME: Fails when icon is not set
|
.and_then(|image| image)
|
||||||
.iter()
|
.and_then(|image| image.id_unchecked().map(|url| url.to_string()))
|
||||||
.next()
|
.and_then(|url| Media::save_remote(conn, url, self).ok());
|
||||||
.and_then(|i| {
|
|
||||||
i.clone()
|
|
||||||
.extend::<Image, ImageType>() // FIXME: Don't clone()
|
|
||||||
.ok()?
|
|
||||||
.and_then(|url| Some(url.id_unchecked()?.to_string()))
|
|
||||||
})
|
|
||||||
.ok_or(Error::MissingApProperty)?,
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
let pub_key = &json.ext_one.public_key.public_key_pem;
|
let pub_key = &json.ext_one.public_key.public_key_pem;
|
||||||
diesel::update(self)
|
diesel::update(self)
|
||||||
@@ -435,7 +445,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
// if no user was found, and we were unable to auto-register from ldap
|
// if no user was found, and we were unable to auto-register from ldap
|
||||||
// fake-verify a password, and return an error.
|
// fake-verify a password, and return an error.
|
||||||
let other = User::get(&*conn, 1)
|
let other = User::get(conn, 1)
|
||||||
.expect("No user is registered")
|
.expect("No user is registered")
|
||||||
.hashed_password;
|
.hashed_password;
|
||||||
other.map(|pass| bcrypt::verify(password, &pass));
|
other.map(|pass| bcrypt::verify(password, &pass));
|
||||||
@@ -454,6 +464,7 @@ impl User {
|
|||||||
pub fn get_local_page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<User>> {
|
pub fn get_local_page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<User>> {
|
||||||
users::table
|
users::table
|
||||||
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
||||||
|
.filter(users::role.ne(Role::Instance as i32))
|
||||||
.order(users::username.asc())
|
.order(users::username.asc())
|
||||||
.offset(min.into())
|
.offset(min.into())
|
||||||
.limit((max - min).into())
|
.limit((max - min).into())
|
||||||
@@ -931,7 +942,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
|
|||||||
.cookies()
|
.cookies()
|
||||||
.get_private(AUTH_COOKIE)
|
.get_private(AUTH_COOKIE)
|
||||||
.and_then(|cookie| cookie.value().parse().ok())
|
.and_then(|cookie| cookie.value().parse().ok())
|
||||||
.and_then(|id| User::get(&*conn, id).ok())
|
.and_then(|id| User::get(&conn, id).ok())
|
||||||
.or_forward(())
|
.or_forward(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -944,15 +955,15 @@ impl IntoId for User {
|
|||||||
|
|
||||||
impl Eq for User {}
|
impl Eq for User {}
|
||||||
|
|
||||||
impl FromId<DbConn> for User {
|
impl FromId<Connection> for User {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Object = CustomPerson;
|
type Object = CustomPerson;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &Connection, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> {
|
fn from_activity(conn: &Connection, acct: CustomPerson) -> Result<Self> {
|
||||||
let actor = acct.ap_actor_ref();
|
let actor = acct.ap_actor_ref();
|
||||||
let username = actor
|
let username = actor
|
||||||
.preferred_username()
|
.preferred_username()
|
||||||
@@ -960,6 +971,10 @@ impl FromId<DbConn> for User {
|
|||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
|
if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
|
||||||
|
tracing::error!(
|
||||||
|
"preferredUsername includes invalid character(s): {}",
|
||||||
|
&username
|
||||||
|
);
|
||||||
return Err(Error::InvalidValue);
|
return Err(Error::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1049,7 +1064,7 @@ impl FromId<DbConn> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&DbConn> for User {
|
impl AsActor<&Connection> for User {
|
||||||
fn get_inbox_url(&self) -> String {
|
fn get_inbox_url(&self) -> String {
|
||||||
self.inbox_url.clone()
|
self.inbox_url.clone()
|
||||||
}
|
}
|
||||||
@@ -1065,11 +1080,11 @@ impl AsActor<&DbConn> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Delete, &DbConn> for User {
|
impl AsObject<User, Delete, &Connection> for User {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &Connection, actor: User, _id: &str) -> Result<()> {
|
||||||
if self.id == actor.id {
|
if self.id == actor.id {
|
||||||
self.delete(conn).map(|_| ())
|
self.delete(conn).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
@@ -1232,7 +1247,7 @@ pub(crate) mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
other.avatar_id = Some(avatar.id);
|
other.avatar_id = Some(avatar.id);
|
||||||
let other = other.save_changes::<User>(&*conn).unwrap();
|
let other = other.save_changes::<User>(conn).unwrap();
|
||||||
|
|
||||||
vec![admin, user, other]
|
vec![admin, user, other]
|
||||||
}
|
}
|
||||||
@@ -1335,11 +1350,11 @@ pub(crate) mod tests {
|
|||||||
fn delete() {
|
fn delete() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let inserted = fill_database(&conn);
|
let inserted = fill_database(conn);
|
||||||
|
|
||||||
assert!(User::get(&conn, inserted[0].id).is_ok());
|
assert!(User::get(conn, inserted[0].id).is_ok());
|
||||||
inserted[0].delete(&conn).unwrap();
|
inserted[0].delete(conn).unwrap();
|
||||||
assert!(User::get(&conn, inserted[0].id).is_err());
|
assert!(User::get(conn, inserted[0].id).is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1348,20 +1363,20 @@ pub(crate) mod tests {
|
|||||||
fn admin() {
|
fn admin() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
let inserted = fill_database(&conn);
|
let inserted = fill_database(conn);
|
||||||
let local_inst = Instance::get_local().unwrap();
|
let local_inst = Instance::get_local().unwrap();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while local_inst.has_admin(&conn).unwrap() {
|
while local_inst.has_admin(conn).unwrap() {
|
||||||
assert!(i < 100); //prevent from looping indefinitelly
|
assert!(i < 100); //prevent from looping indefinitelly
|
||||||
local_inst
|
local_inst
|
||||||
.main_admin(&conn)
|
.main_admin(conn)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_role(&conn, Role::Normal)
|
.set_role(conn, Role::Normal)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
inserted[0].set_role(&conn, Role::Admin).unwrap();
|
inserted[0].set_role(conn, Role::Admin).unwrap();
|
||||||
assert_eq!(inserted[0].id, local_inst.main_admin(&conn).unwrap().id);
|
assert_eq!(inserted[0].id, local_inst.main_admin(conn).unwrap().id);
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1370,9 +1385,9 @@ pub(crate) mod tests {
|
|||||||
fn auth() {
|
fn auth() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
fill_database(&conn);
|
fill_database(conn);
|
||||||
let test_user = NewUser::new_local(
|
let test_user = NewUser::new_local(
|
||||||
&conn,
|
conn,
|
||||||
"test".to_owned(),
|
"test".to_owned(),
|
||||||
"test user".to_owned(),
|
"test user".to_owned(),
|
||||||
Role::Normal,
|
Role::Normal,
|
||||||
@@ -1383,10 +1398,10 @@ pub(crate) mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
User::login(&conn, "test", "test_password").unwrap().id,
|
User::login(conn, "test", "test_password").unwrap().id,
|
||||||
test_user.id
|
test_user.id
|
||||||
);
|
);
|
||||||
assert!(User::login(&conn, "test", "other_password").is_err());
|
assert!(User::login(conn, "test", "other_password").is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1395,26 +1410,26 @@ pub(crate) mod tests {
|
|||||||
fn get_local_page() {
|
fn get_local_page() {
|
||||||
let conn = &db();
|
let conn = &db();
|
||||||
conn.test_transaction::<_, (), _>(|| {
|
conn.test_transaction::<_, (), _>(|| {
|
||||||
fill_database(&conn);
|
fill_database(conn);
|
||||||
|
|
||||||
let page = User::get_local_page(&conn, (0, 2)).unwrap();
|
let page = User::get_local_page(conn, (0, 2)).unwrap();
|
||||||
assert_eq!(page.len(), 2);
|
assert_eq!(page.len(), 2);
|
||||||
assert!(page[0].username <= page[1].username);
|
assert!(page[0].username <= page[1].username);
|
||||||
|
|
||||||
let mut last_username = User::get_local_page(&conn, (0, 1)).unwrap()[0]
|
let mut last_username = User::get_local_page(conn, (0, 1)).unwrap()[0]
|
||||||
.username
|
.username
|
||||||
.clone();
|
.clone();
|
||||||
for i in 1..User::count_local(&conn).unwrap() as i32 {
|
for i in 1..User::count_local(conn).unwrap() as i32 {
|
||||||
let page = User::get_local_page(&conn, (i, i + 1)).unwrap();
|
let page = User::get_local_page(conn, (i, i + 1)).unwrap();
|
||||||
assert_eq!(page.len(), 1);
|
assert_eq!(page.len(), 1);
|
||||||
assert!(last_username <= page[0].username);
|
assert!(last_username <= page[0].username);
|
||||||
last_username = page[0].username.clone();
|
last_username = page[0].username.clone();
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
User::get_local_page(&conn, (0, User::count_local(&conn).unwrap() as i32 + 10))
|
User::get_local_page(conn, (0, User::count_local(conn).unwrap() as i32 + 10))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.len() as i64,
|
.len() as i64,
|
||||||
User::count_local(&conn).unwrap()
|
User::count_local(conn).unwrap()
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
nightly-2022-01-27
|
nightly-2022-07-19
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ set -euo pipefail
|
|||||||
|
|
||||||
version="$1"
|
version="$1"
|
||||||
|
|
||||||
docker run --rm -v $PWD:/repo -v $PWD/pkg:/pkg -v $PWD/script/prebuild.sh:/prebuild.sh plumeorg/plume-buildenv:v0.4.0 /prebuild.sh "$version" /repo /prebuild /pkg
|
docker run --rm -v $PWD:/repo -v $PWD/pkg:/pkg -v $PWD/script/prebuild.sh:/prebuild.sh plumeorg/plume-buildenv:v0.7.0 /prebuild.sh "$version" /repo /prebuild /pkg
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ plm instance new -d plume-test.local -n plume-test
|
|||||||
plm users new -n admin -N 'Admin' -e 'email@exemple.com' -p 'password'
|
plm users new -n admin -N 'Admin' -e 'email@exemple.com' -p 'password'
|
||||||
|
|
||||||
plume &
|
plume &
|
||||||
caddy run -config /Caddyfile &
|
caddy run --config /Caddyfile &
|
||||||
|
|
||||||
until curl http://localhost:7878/test/health -f; do sleep 1; done 2>/dev/null >/dev/null
|
until curl http://localhost:7878/test/health -f; do sleep 1; done 2>/dev/null >/dev/null
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ pub fn create(conn: DbConn, data: Json<NewAppData>) -> Api<App> {
|
|||||||
let client_id = random_hex();
|
let client_id = random_hex();
|
||||||
let client_secret = random_hex();
|
let client_secret = random_hex();
|
||||||
let app = App::insert(
|
let app = App::insert(
|
||||||
&*conn,
|
&conn,
|
||||||
NewApp {
|
NewApp {
|
||||||
name: data.name.clone(),
|
name: data.name.clone(),
|
||||||
client_id,
|
client_id,
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ Then try to restart Plume.
|
|||||||
routes::instance::admin_mod,
|
routes::instance::admin_mod,
|
||||||
routes::instance::admin_instances,
|
routes::instance::admin_instances,
|
||||||
routes::instance::admin_users,
|
routes::instance::admin_users,
|
||||||
|
routes::instance::admin_search_users,
|
||||||
routes::instance::admin_email_blocklist,
|
routes::instance::admin_email_blocklist,
|
||||||
routes::instance::add_email_blocklist,
|
routes::instance::add_email_blocklist,
|
||||||
routes::instance::delete_email_blocklist,
|
routes::instance::delete_email_blocklist,
|
||||||
|
|||||||
+32
-21
@@ -79,7 +79,7 @@ pub struct NewBlogForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
fn valid_slug(title: &str) -> Result<(), ValidationError> {
|
||||||
let slug = utils::make_actor_id(title);
|
let slug = Blog::slug(title);
|
||||||
if slug.is_empty() {
|
if slug.is_empty() {
|
||||||
Err(ValidationError::new("empty_slug"))
|
Err(ValidationError::new("empty_slug"))
|
||||||
} else {
|
} else {
|
||||||
@@ -93,7 +93,7 @@ pub fn create(
|
|||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> RespondOrRedirect {
|
) -> RespondOrRedirect {
|
||||||
let slug = utils::make_actor_id(&form.title);
|
let slug = Blog::slug(&form.title);
|
||||||
let intl = &rockets.intl.catalog;
|
let intl = &rockets.intl.catalog;
|
||||||
let user = rockets.user.clone().unwrap();
|
let user = rockets.user.clone().unwrap();
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ pub fn create(
|
|||||||
Ok(_) => ValidationErrors::new(),
|
Ok(_) => ValidationErrors::new(),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
if Blog::find_by_fqn(&conn, &slug).is_ok() {
|
if Blog::find_by_fqn(&conn, slug).is_ok() {
|
||||||
errors.add(
|
errors.add(
|
||||||
"title",
|
"title",
|
||||||
ValidationError {
|
ValidationError {
|
||||||
@@ -122,7 +122,7 @@ pub fn create(
|
|||||||
let blog = Blog::insert(
|
let blog = Blog::insert(
|
||||||
&conn,
|
&conn,
|
||||||
NewBlog::new_local(
|
NewBlog::new_local(
|
||||||
slug.clone(),
|
slug.into(),
|
||||||
form.title.to_string(),
|
form.title.to_string(),
|
||||||
String::from(""),
|
String::from(""),
|
||||||
Instance::get_local()
|
Instance::get_local()
|
||||||
@@ -160,7 +160,7 @@ pub fn delete(name: String, conn: DbConn, rockets: PlumeRocket) -> RespondOrRedi
|
|||||||
.and_then(|u| u.is_author_in(&conn, &blog).ok())
|
.and_then(|u| u.is_author_in(&conn, &blog).ok())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
blog.delete(&*conn).expect("blog::expect: deletion error");
|
blog.delete(&conn).expect("blog::expect: deletion error");
|
||||||
Flash::success(
|
Flash::success(
|
||||||
Redirect::to(uri!(super::instance::index)),
|
Redirect::to(uri!(super::instance::index)),
|
||||||
i18n!(rockets.intl.catalog, "Your blog was deleted."),
|
i18n!(rockets.intl.catalog, "Your blog was deleted."),
|
||||||
@@ -364,7 +364,7 @@ pub fn outbox_page(
|
|||||||
#[get("/~/<name>/atom.xml")]
|
#[get("/~/<name>/atom.xml")]
|
||||||
pub fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
|
pub fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
|
||||||
let blog = Blog::find_by_fqn(&conn, &name).ok()?;
|
let blog = Blog::find_by_fqn(&conn, &name).ok()?;
|
||||||
let entries = Post::get_recents_for_blog(&*conn, &blog, 15).ok()?;
|
let entries = Post::get_recents_for_blog(&conn, &blog, 15).ok()?;
|
||||||
let uri = Instance::get_local()
|
let uri = Instance::get_local()
|
||||||
.ok()?
|
.ok()?
|
||||||
.compute_box("~", &name, "atom.xml");
|
.compute_box("~", &name, "atom.xml");
|
||||||
@@ -379,6 +379,7 @@ pub fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::valid_slug;
|
||||||
use crate::init_rocket;
|
use crate::init_rocket;
|
||||||
use diesel::Connection;
|
use diesel::Connection;
|
||||||
use plume_common::utils::random_hex;
|
use plume_common::utils::random_hex;
|
||||||
@@ -453,29 +454,33 @@ mod tests {
|
|||||||
long_description_html: "<p>Good morning</p>".to_string(),
|
long_description_html: "<p>Good morning</p>".to_string(),
|
||||||
short_description: SafeString::new("Hello"),
|
short_description: SafeString::new("Hello"),
|
||||||
short_description_html: "<p>Hello</p>".to_string(),
|
short_description_html: "<p>Hello</p>".to_string(),
|
||||||
name: random_hex().to_string(),
|
name: random_hex(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: random_hex().to_string(),
|
public_domain: random_hex(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Instance::cache_local(conn);
|
Instance::cache_local(conn);
|
||||||
instance
|
instance
|
||||||
});
|
});
|
||||||
let mut user = NewUser::default();
|
let user = NewUser {
|
||||||
user.instance_id = instance.id;
|
instance_id: instance.id,
|
||||||
user.username = random_hex().to_string();
|
username: random_hex(),
|
||||||
user.ap_url = random_hex().to_string();
|
ap_url: random_hex(),
|
||||||
user.inbox_url = random_hex().to_string();
|
inbox_url: random_hex(),
|
||||||
user.outbox_url = random_hex().to_string();
|
outbox_url: random_hex(),
|
||||||
user.followers_endpoint = random_hex().to_string();
|
followers_endpoint: random_hex(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let user = User::insert(conn, user).unwrap();
|
let user = User::insert(conn, user).unwrap();
|
||||||
let mut blog = NewBlog::default();
|
let blog = NewBlog {
|
||||||
blog.instance_id = instance.id;
|
instance_id: instance.id,
|
||||||
blog.actor_id = random_hex().to_string();
|
actor_id: random_hex(),
|
||||||
blog.ap_url = random_hex().to_string();
|
ap_url: random_hex(),
|
||||||
blog.inbox_url = random_hex().to_string();
|
inbox_url: random_hex(),
|
||||||
blog.outbox_url = random_hex().to_string();
|
outbox_url: random_hex(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let blog = Blog::insert(conn, blog).unwrap();
|
let blog = Blog::insert(conn, blog).unwrap();
|
||||||
BlogAuthor::insert(
|
BlogAuthor::insert(
|
||||||
conn,
|
conn,
|
||||||
@@ -524,4 +529,10 @@ mod tests {
|
|||||||
.finish(),
|
.finish(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_slug() {
|
||||||
|
assert!(valid_slug("Blog Title").is_ok());
|
||||||
|
assert!(valid_slug("ブログ タイトル").is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ pub fn create(
|
|||||||
user.has_reshared(&conn, &post)
|
user.has_reshared(&conn, &post)
|
||||||
.expect("comments::create: reshared error"),
|
.expect("comments::create: reshared error"),
|
||||||
user.is_following(
|
user.is_following(
|
||||||
&*conn,
|
&conn,
|
||||||
post.get_authors(&conn)
|
post.get_authors(&conn)
|
||||||
.expect("comments::create: authors error")[0]
|
.expect("comments::create: authors error")[0]
|
||||||
.id
|
.id
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::{
|
|||||||
routes::{errors::ErrorPage, RespondOrRedirect},
|
routes::{errors::ErrorPage, RespondOrRedirect},
|
||||||
template_utils::{IntoContext, Ructe},
|
template_utils::{IntoContext, Ructe},
|
||||||
};
|
};
|
||||||
|
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
db_conn::DbConn, email_signups::EmailSignup, instance::Instance, lettre::Transport, signups,
|
db_conn::DbConn, email_signups::EmailSignup, instance::Instance, lettre::Transport, signups,
|
||||||
Error, PlumeRocket, CONFIG,
|
Error, PlumeRocket, CONFIG,
|
||||||
@@ -13,7 +14,11 @@ use rocket::{
|
|||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
State,
|
State,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use validator::{Validate, ValidationError, ValidationErrors};
|
use validator::{Validate, ValidationError, ValidationErrors};
|
||||||
|
|
||||||
@@ -105,6 +110,26 @@ pub fn create(
|
|||||||
render!(email_signups::create(&(&conn, &rockets).to_context())).into()
|
render!(email_signups::create(&(&conn, &rockets).to_context())).into()
|
||||||
}
|
}
|
||||||
Error::NotFound => render!(errors::not_found(&(&conn, &rockets).to_context())).into(),
|
Error::NotFound => render!(errors::not_found(&(&conn, &rockets).to_context())).into(),
|
||||||
|
Error::Blocklisted(show, msg) => {
|
||||||
|
let mut errors = ValidationErrors::new();
|
||||||
|
if *show {
|
||||||
|
errors.add(
|
||||||
|
"email",
|
||||||
|
ValidationError {
|
||||||
|
code: Cow::from("blocklisted"),
|
||||||
|
message: Some(Cow::from(msg.clone())),
|
||||||
|
params: HashMap::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render!(email_signups::new(
|
||||||
|
&(&conn, &rockets).to_context(),
|
||||||
|
registration_open,
|
||||||
|
&form,
|
||||||
|
errors
|
||||||
|
))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
_ => render!(errors::not_found(&(&conn, &rockets).to_context())).into(), // FIXME
|
_ => render!(errors::not_found(&(&conn, &rockets).to_context())).into(), // FIXME
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -153,6 +178,28 @@ pub fn show(
|
|||||||
)))
|
)))
|
||||||
} // TODO: Flash and redirect
|
} // TODO: Flash and redirect
|
||||||
Error::NotFound => return Err(Error::NotFound.into()),
|
Error::NotFound => return Err(Error::NotFound.into()),
|
||||||
|
Error::Blocklisted(show, msg) => {
|
||||||
|
let mut errors = ValidationErrors::new();
|
||||||
|
if show {
|
||||||
|
errors.add(
|
||||||
|
"email",
|
||||||
|
ValidationError {
|
||||||
|
code: Cow::from("blocklisted"),
|
||||||
|
message: Some(Cow::from(msg)),
|
||||||
|
params: HashMap::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Ok(render!(email_signups::new(
|
||||||
|
&(&conn, &rockets).to_context(),
|
||||||
|
Instance::get_local()?.open_registrations,
|
||||||
|
&EmailSignupForm {
|
||||||
|
email: signup.email.clone(),
|
||||||
|
email_confirmation: signup.email
|
||||||
|
},
|
||||||
|
errors
|
||||||
|
)));
|
||||||
|
}
|
||||||
_ => return Err(Error::NotFound.into()), // FIXME
|
_ => return Err(Error::NotFound.into()), // FIXME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,12 +254,38 @@ pub fn signup(
|
|||||||
err
|
err
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
let _user = signup
|
let user = signup.complete(&conn, form.username.clone(), form.password.clone());
|
||||||
.complete(&conn, form.username.clone(), form.password.clone())
|
match user {
|
||||||
.map_err(|e| {
|
Err(Error::Blocklisted(show, msg)) => {
|
||||||
|
let instance = Instance::get_local().map_err(|_| Status::UnprocessableEntity)?;
|
||||||
|
let mut errors = ValidationErrors::new();
|
||||||
|
if show {
|
||||||
|
errors.add(
|
||||||
|
"email",
|
||||||
|
ValidationError {
|
||||||
|
code: Cow::from("blocklisted"),
|
||||||
|
message: Some(Cow::from(msg)),
|
||||||
|
params: HashMap::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Ok(render!(email_signups::new(
|
||||||
|
&(&conn, &rockets).to_context(),
|
||||||
|
instance.open_registrations,
|
||||||
|
&EmailSignupForm {
|
||||||
|
email: signup.email.clone(),
|
||||||
|
email_confirmation: signup.email
|
||||||
|
},
|
||||||
|
errors
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
warn!("{:?}", e);
|
warn!("{:?}", e);
|
||||||
Status::UnprocessableEntity
|
return Err(Status::UnprocessableEntity);
|
||||||
})?;
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
Ok(FlashRedirect(Flash::success(
|
Ok(FlashRedirect(Flash::success(
|
||||||
Redirect::to(uri!(super::session::new: m = _)),
|
Redirect::to(uri!(super::session::new: m = _)),
|
||||||
i18n!(
|
i18n!(
|
||||||
|
|||||||
+30
-6
@@ -105,7 +105,7 @@ pub fn update_settings(
|
|||||||
Instance::get_local().expect("instance::update_settings: local instance error");
|
Instance::get_local().expect("instance::update_settings: local instance error");
|
||||||
instance
|
instance
|
||||||
.update(
|
.update(
|
||||||
&*conn,
|
&conn,
|
||||||
form.name.clone(),
|
form.name.clone(),
|
||||||
form.open_registrations,
|
form.open_registrations,
|
||||||
form.short_description.clone(),
|
form.short_description.clone(),
|
||||||
@@ -160,7 +160,7 @@ pub fn toggle_block(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/admin/users?<page>")]
|
#[get("/admin/users?<page>", rank = 2)]
|
||||||
pub fn admin_users(
|
pub fn admin_users(
|
||||||
_mod: Moderator,
|
_mod: Moderator,
|
||||||
page: Option<Page>,
|
page: Option<Page>,
|
||||||
@@ -171,6 +171,30 @@ pub fn admin_users(
|
|||||||
Ok(render!(instance::users(
|
Ok(render!(instance::users(
|
||||||
&(&conn, &rockets).to_context(),
|
&(&conn, &rockets).to_context(),
|
||||||
User::get_local_page(&conn, page.limits())?,
|
User::get_local_page(&conn, page.limits())?,
|
||||||
|
None,
|
||||||
|
page.0,
|
||||||
|
Page::total(User::count_local(&conn)? as i32)
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
#[get("/admin/users?<user>&<page>", rank = 1)]
|
||||||
|
pub fn admin_search_users(
|
||||||
|
_mod: Moderator,
|
||||||
|
user: String,
|
||||||
|
page: Option<Page>,
|
||||||
|
conn: DbConn,
|
||||||
|
rockets: PlumeRocket,
|
||||||
|
) -> Result<Ructe, ErrorPage> {
|
||||||
|
let page = page.unwrap_or_default();
|
||||||
|
let users = if user.is_empty() {
|
||||||
|
User::get_local_page(&conn, page.limits())?
|
||||||
|
} else {
|
||||||
|
User::search_local_by_name(&conn, &user, page.limits())?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(render!(instance::users(
|
||||||
|
&(&conn, &rockets).to_context(),
|
||||||
|
users,
|
||||||
|
Some(user.as_str()),
|
||||||
page.0,
|
page.0,
|
||||||
Page::total(User::count_local(&conn)? as i32)
|
Page::total(User::count_local(&conn)? as i32)
|
||||||
)))
|
)))
|
||||||
@@ -366,8 +390,8 @@ pub fn edit_users(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ban(id: i32, conn: &Connection, worker: &ScheduledThreadPool) -> Result<(), ErrorPage> {
|
fn ban(id: i32, conn: &Connection, worker: &ScheduledThreadPool) -> Result<(), ErrorPage> {
|
||||||
let u = User::get(&*conn, id)?;
|
let u = User::get(conn, id)?;
|
||||||
u.delete(&*conn)?;
|
u.delete(conn)?;
|
||||||
if Instance::get_local()
|
if Instance::get_local()
|
||||||
.map(|i| u.instance_id == i.id)
|
.map(|i| u.instance_id == i.id)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
@@ -382,8 +406,8 @@ fn ban(id: i32, conn: &Connection, worker: &ScheduledThreadPool) -> Result<(), E
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let target = User::one_by_instance(&*conn)?;
|
let target = User::one_by_instance(conn)?;
|
||||||
let delete_act = u.delete_activity(&*conn)?;
|
let delete_act = u.delete_activity(conn)?;
|
||||||
worker.execute(move || broadcast(&u, delete_act, target, CONFIG.proxy().cloned()));
|
worker.execute(move || broadcast(&u, delete_act, target, CONFIG.proxy().cloned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -20,14 +20,14 @@ pub fn create(
|
|||||||
let b = Blog::find_by_fqn(&conn, &blog)?;
|
let b = Blog::find_by_fqn(&conn, &blog)?;
|
||||||
let post = Post::find_by_slug(&conn, &slug, b.id)?;
|
let post = Post::find_by_slug(&conn, &slug, b.id)?;
|
||||||
|
|
||||||
if !user.has_liked(&*conn, &post)? {
|
if !user.has_liked(&conn, &post)? {
|
||||||
let like = likes::Like::insert(&*conn, likes::NewLike::new(&post, &user))?;
|
let like = likes::Like::insert(&conn, likes::NewLike::new(&post, &user))?;
|
||||||
like.notify(&*conn)?;
|
like.notify(&conn)?;
|
||||||
|
|
||||||
Timeline::add_to_all_timelines(&conn, &post, Kind::Like(&user))?;
|
Timeline::add_to_all_timelines(&conn, &post, Kind::Like(&user))?;
|
||||||
|
|
||||||
let dest = User::one_by_instance(&*conn)?;
|
let dest = User::one_by_instance(&conn)?;
|
||||||
let act = like.to_activity(&*conn)?;
|
let act = like.to_activity(&conn)?;
|
||||||
rockets
|
rockets
|
||||||
.worker
|
.worker
|
||||||
.execute(move || broadcast(&user, act, dest, CONFIG.proxy().cloned()));
|
.execute(move || broadcast(&user, act, dest, CONFIG.proxy().cloned()));
|
||||||
|
|||||||
@@ -145,9 +145,9 @@ pub fn details(
|
|||||||
|
|
||||||
#[post("/medias/<id>/delete")]
|
#[post("/medias/<id>/delete")]
|
||||||
pub fn delete(id: i32, user: User, conn: DbConn, intl: I18n) -> Result<Flash<Redirect>, ErrorPage> {
|
pub fn delete(id: i32, user: User, conn: DbConn, intl: I18n) -> Result<Flash<Redirect>, ErrorPage> {
|
||||||
let media = Media::get(&*conn, id)?;
|
let media = Media::get(&conn, id)?;
|
||||||
if media.owner_id == user.id {
|
if media.owner_id == user.id {
|
||||||
media.delete(&*conn)?;
|
media.delete(&conn)?;
|
||||||
Ok(Flash::success(
|
Ok(Flash::success(
|
||||||
Redirect::to(uri!(list: page = _)),
|
Redirect::to(uri!(list: page = _)),
|
||||||
i18n!(intl.catalog, "Your media have been deleted."),
|
i18n!(intl.catalog, "Your media have been deleted."),
|
||||||
@@ -167,9 +167,9 @@ pub fn set_avatar(
|
|||||||
conn: DbConn,
|
conn: DbConn,
|
||||||
intl: I18n,
|
intl: I18n,
|
||||||
) -> Result<Flash<Redirect>, ErrorPage> {
|
) -> Result<Flash<Redirect>, ErrorPage> {
|
||||||
let media = Media::get(&*conn, id)?;
|
let media = Media::get(&conn, id)?;
|
||||||
if media.owner_id == user.id {
|
if media.owner_id == user.id {
|
||||||
user.set_avatar(&*conn, media.id)?;
|
user.set_avatar(&conn, media.id)?;
|
||||||
Ok(Flash::success(
|
Ok(Flash::success(
|
||||||
Redirect::to(uri!(details: id = id)),
|
Redirect::to(uri!(details: id = id)),
|
||||||
i18n!(intl.catalog, "Your avatar has been updated."),
|
i18n!(intl.catalog, "Your avatar has been updated."),
|
||||||
|
|||||||
+1
-1
@@ -165,7 +165,7 @@ fn post_to_atom(post: Post, conn: &Connection) -> Entry {
|
|||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.authors(
|
.authors(
|
||||||
post.get_authors(&*conn)
|
post.get_authors(conn)
|
||||||
.expect("Atom feed: author error")
|
.expect("Atom feed: author error")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|a| {
|
.map(|a| {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ pub fn search(query: Option<Form<SearchQuery>>, conn: DbConn, rockets: PlumeRock
|
|||||||
if str_query.is_empty() {
|
if str_query.is_empty() {
|
||||||
render!(search::index(
|
render!(search::index(
|
||||||
&(&conn, &rockets).to_context(),
|
&(&conn, &rockets).to_context(),
|
||||||
&format!("{}", Utc::today().format("%Y-%m-d"))
|
&format!("{}", Utc::now().date_naive().format("%Y-%m-d"))
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let res = rockets
|
let res = rockets
|
||||||
|
|||||||
+6
-6
@@ -48,10 +48,10 @@ pub fn me(user: Option<User>) -> RespondOrRedirect {
|
|||||||
#[get("/@/<name>", rank = 2)]
|
#[get("/@/<name>", rank = 2)]
|
||||||
pub fn details(name: String, rockets: PlumeRocket, conn: DbConn) -> Result<Ructe, ErrorPage> {
|
pub fn details(name: String, rockets: PlumeRocket, conn: DbConn) -> Result<Ructe, ErrorPage> {
|
||||||
let user = User::find_by_fqn(&conn, &name)?;
|
let user = User::find_by_fqn(&conn, &name)?;
|
||||||
let recents = Post::get_recents_for_author(&*conn, &user, 6)?;
|
let recents = Post::get_recents_for_author(&conn, &user, 6)?;
|
||||||
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6)?;
|
let reshares = Reshare::get_recents_for_author(&conn, &user, 6)?;
|
||||||
|
|
||||||
if !user.get_instance(&*conn)?.local {
|
if !user.get_instance(&conn)?.local {
|
||||||
tracing::trace!("remote user found");
|
tracing::trace!("remote user found");
|
||||||
user.remote_user_found(); // Doesn't block
|
user.remote_user_found(); // Doesn't block
|
||||||
}
|
}
|
||||||
@@ -62,14 +62,14 @@ pub fn details(name: String, rockets: PlumeRocket, conn: DbConn) -> Result<Ructe
|
|||||||
rockets
|
rockets
|
||||||
.user
|
.user
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|x| x.is_following(&*conn, user.id).ok())
|
.and_then(|x| x.is_following(&conn, user.id).ok())
|
||||||
.unwrap_or(false),
|
.unwrap_or(false),
|
||||||
user.instance_id != Instance::get_local()?.id,
|
user.instance_id != Instance::get_local()?.id,
|
||||||
user.get_instance(&*conn)?.public_domain,
|
user.get_instance(&conn)?.public_domain,
|
||||||
recents,
|
recents,
|
||||||
reshares
|
reshares
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|r| r.get_post(&*conn).ok())
|
.filter_map(|r| r.get_post(&conn).ok())
|
||||||
.collect()
|
.collect()
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ impl Resolver<DbConn> for WebfingerResolver {
|
|||||||
fn find(prefix: Prefix, acct: String, conn: DbConn) -> Result<Webfinger, ResolverError> {
|
fn find(prefix: Prefix, acct: String, conn: DbConn) -> Result<Webfinger, ResolverError> {
|
||||||
match prefix {
|
match prefix {
|
||||||
Prefix::Acct => User::find_by_fqn(&conn, &acct)
|
Prefix::Acct => User::find_by_fqn(&conn, &acct)
|
||||||
.and_then(|usr| usr.webfinger(&*conn))
|
.and_then(|usr| usr.webfinger(&conn))
|
||||||
.or(Err(ResolverError::NotFound)),
|
.or(Err(ResolverError::NotFound)),
|
||||||
Prefix::Group => Blog::find_by_fqn(&conn, &acct)
|
Prefix::Group => Blog::find_by_fqn(&conn, &acct)
|
||||||
.and_then(|blog| blog.webfinger(&*conn))
|
.and_then(|blog| blog.webfinger(&conn))
|
||||||
.or(Err(ResolverError::NotFound)),
|
.or(Err(ResolverError::NotFound)),
|
||||||
Prefix::Custom(_) => Err(ResolverError::NotFound),
|
Prefix::Custom(_) => Err(ResolverError::NotFound),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ impl<'r> Responder<'r> for Ructe {
|
|||||||
macro_rules! render {
|
macro_rules! render {
|
||||||
($group:tt :: $page:tt ( $( $param:expr ),* ) ) => {
|
($group:tt :: $page:tt ( $( $param:expr ),* ) ) => {
|
||||||
{
|
{
|
||||||
use crate::templates;
|
use $crate::templates;
|
||||||
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
templates::$group::$page(
|
templates::$group::$page(
|
||||||
@@ -116,6 +116,7 @@ pub fn translate_notification(ctx: BaseContext<'_>, notif: Notification) -> Stri
|
|||||||
pub fn i18n_timeline_name(cat: &Catalog, tl: &str) -> String {
|
pub fn i18n_timeline_name(cat: &Catalog, tl: &str) -> String {
|
||||||
match tl {
|
match tl {
|
||||||
"Your feed" => i18n!(cat, "Your feed"),
|
"Your feed" => i18n!(cat, "Your feed"),
|
||||||
|
"My feed" => i18n!(cat, "My feed"),
|
||||||
"Local feed" => i18n!(cat, "Local feed"),
|
"Local feed" => i18n!(cat, "Local feed"),
|
||||||
"Federated feed" => i18n!(cat, "Federated feed"),
|
"Federated feed" => i18n!(cat, "Federated feed"),
|
||||||
n => n.to_string(),
|
n => n.to_string(),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
@use crate::template_utils::*;
|
@use crate::template_utils::*;
|
||||||
@use crate::routes::*;
|
@use crate::routes::*;
|
||||||
|
|
||||||
@(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32)
|
@(ctx: BaseContext, users: Vec<User>, user: Option<&str>, page: i32, n_pages: i32)
|
||||||
|
|
||||||
@:base(ctx, i18n!(ctx.1, "Users"), {}, {}, {
|
@:base(ctx, i18n!(ctx.1, "Users"), {}, {}, {
|
||||||
<h1>@i18n!(ctx.1, "Users")</h1>
|
<h1>@i18n!(ctx.1, "Users")</h1>
|
||||||
@@ -15,6 +15,13 @@
|
|||||||
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), false)
|
(&uri!(instance::admin_email_blocklist: page=_).to_string(), i18n!(ctx.1, "Email blocklist"), false)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
<form method="get" action="@uri!(instance::admin_search_users: page = _, user = user.unwrap_or_default())">
|
||||||
|
<header>
|
||||||
|
<input type="search" name="user" value="@user.unwrap_or_default()">
|
||||||
|
<input type="submit" value="@i18n!(ctx.1, "Search users")">
|
||||||
|
</header>
|
||||||
|
</form>
|
||||||
|
|
||||||
<form method="post" action="@uri!(instance::edit_users)">
|
<form method="post" action="@uri!(instance::edit_users)">
|
||||||
<header>
|
<header>
|
||||||
<select name="action">
|
<select name="action">
|
||||||
@@ -46,5 +53,9 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@paginate(ctx.1, page, n_pages)
|
@if user.is_some() {
|
||||||
|
@paginate_param(ctx.1, page, n_pages, Some(format!("user={}", encode_query_param(user.unwrap_or_default()))))
|
||||||
|
} else {
|
||||||
|
@paginate(ctx.1, page, n_pages)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div class="h-card">
|
<div class="h-card">
|
||||||
<div class="user">
|
<div class="user">
|
||||||
<div class="flex wrap">
|
<div class="flex wrap" dir="auto">
|
||||||
@avatar(ctx.0, user, Size::Medium, false, ctx.1)
|
@avatar(ctx.0, user, Size::Medium, false, ctx.1)
|
||||||
|
|
||||||
<h1 class="grow flex vertical">
|
<h1 class="grow flex vertical">
|
||||||
|
|||||||
Reference in New Issue
Block a user