mirror of https://github.com/LemmyNet/lemmy.git
Merge branch 'main' into markdown-link-rule
commit
3d698dde7c
|
@ -1,2 +0,0 @@
|
|||
[build]
|
||||
rustflags = ["--cfg", "tokio_unstable"]
|
|
@ -5,4 +5,4 @@ api_tests
|
|||
ansible
|
||||
tests
|
||||
*.sh
|
||||
pictrs
|
||||
pictrs
|
||||
|
|
|
@ -20,7 +20,6 @@ query_testing/**/reports/*.json
|
|||
api_tests/node_modules
|
||||
api_tests/.yalc
|
||||
api_tests/yalc.lock
|
||||
api_tests/test.png
|
||||
api_tests/pict-rs
|
||||
|
||||
# pictrs data
|
||||
|
|
|
@ -6,7 +6,8 @@ variables:
|
|||
- &slow_check_paths
|
||||
- path:
|
||||
# rust source code
|
||||
- "**/*.rs"
|
||||
- "crates/**"
|
||||
- "src/**"
|
||||
- "**/Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
# database migrations
|
||||
|
@ -60,7 +61,7 @@ steps:
|
|||
image: rustlang/rust:nightly
|
||||
environment:
|
||||
# store cargo data in repo folder so that it gets cached between steps
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
# need make existing toolchain available
|
||||
- cargo +nightly fmt -- --check
|
||||
|
@ -75,6 +76,14 @@ steps:
|
|||
- cargo binstall -y cargo-machete
|
||||
- cargo machete
|
||||
|
||||
ignored_files:
|
||||
group: format
|
||||
image: alpine:3
|
||||
commands:
|
||||
- apk add git
|
||||
- IGNORED=$(git ls-files --cached -i --exclude-standard)
|
||||
- if [[ "$IGNORED" ]]; then echo "Ignored files present:\n$IGNORED\n"; exit 1; fi
|
||||
|
||||
restore-cache:
|
||||
image: meltwater/drone-cache:v1
|
||||
pull: true
|
||||
|
@ -92,7 +101,7 @@ steps:
|
|||
cache_key: "rust-cache"
|
||||
path-style: true
|
||||
mount:
|
||||
- ".cargo"
|
||||
- ".cargo_home"
|
||||
- "target"
|
||||
- "api_tests/node_modules"
|
||||
secrets:
|
||||
|
@ -103,7 +112,7 @@ steps:
|
|||
check_api_common_default_features:
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
- cargo check --package lemmy_api_common
|
||||
when: *slow_check_paths
|
||||
|
@ -111,7 +120,7 @@ steps:
|
|||
lemmy_api_common_doesnt_depend_on_diesel:
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
- "! cargo tree -p lemmy_api_common --no-default-features -i diesel"
|
||||
when: *slow_check_paths
|
||||
|
@ -119,7 +128,7 @@ steps:
|
|||
lemmy_api_common_works_with_wasm:
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
- "rustup target add wasm32-unknown-unknown"
|
||||
- "cargo check --target wasm32-unknown-unknown -p lemmy_api_common"
|
||||
|
@ -128,7 +137,7 @@ steps:
|
|||
check_defaults_hjson_updated:
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
- export LEMMY_CONFIG_LOCATION=./config/config.hjson
|
||||
- ./scripts/update_config_defaults.sh config/defaults_current.hjson
|
||||
|
@ -138,7 +147,7 @@ steps:
|
|||
check_diesel_schema:
|
||||
image: willsquire/diesel-cli
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
commands:
|
||||
- diesel migration run
|
||||
|
@ -149,7 +158,7 @@ steps:
|
|||
check_diesel_migration_revertable:
|
||||
image: willsquire/diesel-cli
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
commands:
|
||||
- diesel migration run
|
||||
|
@ -159,7 +168,7 @@ steps:
|
|||
cargo_clippy:
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
# when adding new clippy lints, make sure to also add them in scripts/lint.sh
|
||||
- rustup component add clippy
|
||||
|
@ -169,7 +178,7 @@ steps:
|
|||
cargo_build:
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
- cargo build
|
||||
- mv target/debug/lemmy_server target/lemmy_server
|
||||
|
@ -181,7 +190,7 @@ steps:
|
|||
environment:
|
||||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
RUST_BACKTRACE: "1"
|
||||
CARGO_HOME: .cargo
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
||||
- cargo test --workspace --no-fail-fast
|
||||
|
@ -218,7 +227,7 @@ steps:
|
|||
region: us-east-1
|
||||
path-style: true
|
||||
mount:
|
||||
- ".cargo"
|
||||
- ".cargo_home"
|
||||
- "target"
|
||||
- "api_tests/node_modules"
|
||||
secrets:
|
||||
|
@ -233,9 +242,7 @@ steps:
|
|||
settings:
|
||||
repo: dessalines/lemmy
|
||||
dockerfile: docker/Dockerfile
|
||||
# TODO fix arm build: see: https://woodpecker.join-lemmy.org/repos/129/pipeline/2888/20
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64, linux/arm64
|
||||
build_args:
|
||||
- RUST_RELEASE_MODE=release
|
||||
tag: ${CI_COMMIT_TAG}
|
||||
|
@ -255,6 +262,19 @@ steps:
|
|||
when:
|
||||
event: cron
|
||||
|
||||
# using https://github.com/pksunkara/cargo-workspaces
|
||||
publish_to_crates_io:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- 'echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"'
|
||||
- cargo install cargo-workspaces
|
||||
- cp -r migrations crates/db_schema/
|
||||
- cargo login "$CARGO_API_TOKEN"
|
||||
- cargo workspaces publish --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
|
||||
secrets: [cargo_api_token]
|
||||
when:
|
||||
event: tag
|
||||
|
||||
notify_on_failure:
|
||||
image: alpine:3
|
||||
commands:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
68
Cargo.toml
68
Cargo.toml
|
@ -1,5 +1,6 @@
|
|||
[workspace.package]
|
||||
version = "0.19.0-rc.6"
|
||||
version = "0.19.1-rc.2"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
description = "A link aggregator for the fediverse"
|
||||
license = "AGPL-3.0"
|
||||
|
@ -84,23 +85,23 @@ unused_self = "deny"
|
|||
unwrap_used = "deny"
|
||||
|
||||
[workspace.dependencies]
|
||||
lemmy_api = { version = "=0.19.0-rc.6", path = "./crates/api" }
|
||||
lemmy_api_crud = { version = "=0.19.0-rc.6", path = "./crates/api_crud" }
|
||||
lemmy_apub = { version = "=0.19.0-rc.6", path = "./crates/apub" }
|
||||
lemmy_utils = { version = "=0.19.0-rc.6", path = "./crates/utils" }
|
||||
lemmy_db_schema = { version = "=0.19.0-rc.6", path = "./crates/db_schema" }
|
||||
lemmy_api_common = { version = "=0.19.0-rc.6", path = "./crates/api_common" }
|
||||
lemmy_routes = { version = "=0.19.0-rc.6", path = "./crates/routes" }
|
||||
lemmy_db_views = { version = "=0.19.0-rc.6", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.19.0-rc.6", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.19.0-rc.6", path = "./crates/db_views_moderator" }
|
||||
activitypub_federation = { version = "0.5.0-beta.5", default-features = false, features = [
|
||||
lemmy_api = { version = "=0.19.1-rc.2", path = "./crates/api" }
|
||||
lemmy_api_crud = { version = "=0.19.1-rc.2", path = "./crates/api_crud" }
|
||||
lemmy_apub = { version = "=0.19.1-rc.2", path = "./crates/apub" }
|
||||
lemmy_utils = { version = "=0.19.1-rc.2", path = "./crates/utils" }
|
||||
lemmy_db_schema = { version = "=0.19.1-rc.2", path = "./crates/db_schema" }
|
||||
lemmy_api_common = { version = "=0.19.1-rc.2", path = "./crates/api_common" }
|
||||
lemmy_routes = { version = "=0.19.1-rc.2", path = "./crates/routes" }
|
||||
lemmy_db_views = { version = "=0.19.1-rc.2", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.19.1-rc.2", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.19.1-rc.2", path = "./crates/db_views_moderator" }
|
||||
activitypub_federation = { version = "0.5.0-beta.6", default-features = false, features = [
|
||||
"actix-web",
|
||||
] }
|
||||
diesel = "2.1.3"
|
||||
diesel = "2.1.4"
|
||||
diesel_migrations = "2.1.0"
|
||||
diesel-async = "0.3.2"
|
||||
serde = { version = "1.0.189", features = ["derive"] }
|
||||
diesel-async = "0.4.1"
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
serde_with = "3.4.0"
|
||||
actix-web = { version = "4.4.0", default-features = false, features = [
|
||||
"macros",
|
||||
|
@ -111,11 +112,11 @@ actix-web = { version = "4.4.0", default-features = false, features = [
|
|||
"cookies",
|
||||
] }
|
||||
tracing = "0.1.40"
|
||||
tracing-actix-web = { version = "0.7.8", default-features = false }
|
||||
tracing-actix-web = { version = "0.7.9", default-features = false }
|
||||
tracing-error = "0.2.0"
|
||||
tracing-log = "0.1.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
tracing-log = "0.2.0"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
reqwest = { version = "0.11.22", features = ["json", "blocking", "gzip"] }
|
||||
reqwest-middleware = "0.2.4"
|
||||
reqwest-tracing = "0.4.6"
|
||||
|
@ -123,9 +124,9 @@ clokwerk = "0.4.0"
|
|||
doku = { version = "0.21.1", features = ["url-2"] }
|
||||
bcrypt = "0.15.0"
|
||||
chrono = { version = "0.4.31", features = ["serde"], default-features = false }
|
||||
serde_json = { version = "1.0.107", features = ["preserve_order"] }
|
||||
serde_json = { version = "1.0.108", features = ["preserve_order"] }
|
||||
base64 = "0.21.5"
|
||||
uuid = { version = "1.5.0", features = ["serde", "v4"] }
|
||||
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
||||
async-trait = "0.1.74"
|
||||
captcha = "0.0.9"
|
||||
anyhow = { version = "1.0.75", features = [
|
||||
|
@ -133,26 +134,28 @@ anyhow = { version = "1.0.75", features = [
|
|||
] } # backtrace is on by default on nightly, but not stable rust
|
||||
diesel_ltree = "0.3.0"
|
||||
serial_test = "2.0.0"
|
||||
tokio = { version = "1.33.0", features = ["full"] }
|
||||
tokio = { version = "1.35.0", features = ["full"] }
|
||||
regex = "1.10.2"
|
||||
once_cell = "1.18.0"
|
||||
once_cell = "1.19.0"
|
||||
diesel-derive-newtype = "2.1.0"
|
||||
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||
strum = "0.25.0"
|
||||
strum_macros = "0.25.3"
|
||||
itertools = "0.11.0"
|
||||
futures = "0.3.28"
|
||||
http = "0.2.9"
|
||||
itertools = "0.12.0"
|
||||
futures = "0.3.29"
|
||||
http = "0.2.11"
|
||||
percent-encoding = "2.3.1"
|
||||
rosetta-i18n = "0.1.3"
|
||||
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
|
||||
tracing-opentelemetry = { version = "0.19.0" }
|
||||
ts-rs = { version = "7.0.0", features = ["serde-compat", "chrono-impl"] }
|
||||
rustls = { version = "0.21.8", features = ["dangerous_configuration"] }
|
||||
futures-util = "0.3.28"
|
||||
rustls = { version = "0.21.10", features = ["dangerous_configuration"] }
|
||||
futures-util = "0.3.29"
|
||||
tokio-postgres = "0.7.10"
|
||||
tokio-postgres-rustls = "0.10.0"
|
||||
urlencoding = "2.1.3"
|
||||
enum-map = "2.7"
|
||||
moka = { version = "0.12.1", features = ["future"] }
|
||||
|
||||
[dependencies]
|
||||
lemmy_api = { workspace = true }
|
||||
|
@ -162,7 +165,7 @@ lemmy_utils = { workspace = true }
|
|||
lemmy_db_schema = { workspace = true }
|
||||
lemmy_api_common = { workspace = true }
|
||||
lemmy_routes = { workspace = true }
|
||||
lemmy_federate = { version = "0.19.0-rc.6", path = "crates/federate" }
|
||||
lemmy_federate = { version = "0.19.1-rc.2", path = "crates/federate" }
|
||||
activitypub_federation = { workspace = true }
|
||||
diesel = { workspace = true }
|
||||
diesel-async = { workspace = true }
|
||||
|
@ -182,11 +185,12 @@ tracing-opentelemetry = { workspace = true, optional = true }
|
|||
opentelemetry = { workspace = true, optional = true }
|
||||
console-subscriber = { version = "0.1.10", optional = true }
|
||||
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
||||
pict-rs = { version = "0.4.5", optional = true }
|
||||
pict-rs = { version = "0.5.0-rc.2", optional = true }
|
||||
tokio.workspace = true
|
||||
actix-cors = "0.6.4"
|
||||
actix-cors = "0.6.5"
|
||||
futures-util = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
prometheus = { version = "0.13.3", features = ["process"] }
|
||||
serial_test = { workspace = true }
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
clap = { version = "4.4.11", features = ["derive"] }
|
||||
actix-web-prom = "0.7.0"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
[![Translation status](http://weblate.join-lemmy.org/widgets/lemmy/-/lemmy/svg-badge.svg)](http://weblate.join-lemmy.org/engage/lemmy/)
|
||||
[![License](https://img.shields.io/github/license/LemmyNet/lemmy.svg)](LICENSE)
|
||||
![GitHub stars](https://img.shields.io/github/stars/LemmyNet/lemmy?style=social)
|
||||
[![Delightful Humane Tech](https://codeberg.org/teaserbot-labs/delightful-humane-design/raw/branch/main/humane-tech-badge.svg)](https://codeberg.org/teaserbot-labs/delightful-humane-design)
|
||||
<a href="https://endsoftwarepatents.org/innovating-without-patents"><img style="height: 20px;" src="https://static.fsf.org/nosvn/esp/logos/patent-free.svg"></a>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"arrow-body-style": 0,
|
||||
"curly": 0,
|
||||
"eol-last": 0,
|
||||
|
|
|
@ -19,17 +19,17 @@
|
|||
"api-test-image": "jest -i image.spec.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.8",
|
||||
"@types/node": "^20.9.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"download-file-sync": "^1.0.4",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"jest": "^29.5.0",
|
||||
"lemmy-js-client": "0.19.0-alpha.18",
|
||||
"prettier": "^3.0.0",
|
||||
"lemmy-js-client": "0.19.0",
|
||||
"prettier": "^3.1.1",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.4"
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -9,7 +9,7 @@ export RUST_LOG="warn,lemmy_server=debug,lemmy_federate=debug,lemmy_api=debug,le
|
|||
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
|
||||
|
||||
# pictrs setup
|
||||
if ! [ -f "pict-rs" ]; then
|
||||
if [ ! -f "pict-rs" ]; then
|
||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.0-beta.2/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||
chmod +x api_tests/pict-rs
|
||||
fi
|
||||
|
|
|
@ -39,7 +39,6 @@ import {
|
|||
delay,
|
||||
} from "./shared";
|
||||
import { CommentView, CommunityView } from "lemmy-js-client";
|
||||
import { LemmyHttp } from "lemmy-js-client";
|
||||
|
||||
let betaCommunity: CommunityView | undefined;
|
||||
let postOnAlphaRes: PostResponse;
|
||||
|
@ -345,17 +344,26 @@ test("Federated comment like", async () => {
|
|||
test("Reply to a comment from another instance, get notification", async () => {
|
||||
await alpha.markAllAsRead();
|
||||
|
||||
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||
let betaCommunity = (
|
||||
await waitUntil(
|
||||
() => resolveBetaCommunity(alpha),
|
||||
c => !!c.community?.community.instance_id,
|
||||
)
|
||||
).community;
|
||||
if (!betaCommunity) {
|
||||
throw "Missing beta community";
|
||||
}
|
||||
|
||||
const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
|
||||
|
||||
// Create a root-level trunk-branch comment on alpha
|
||||
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
|
||||
// find that comment id on beta
|
||||
let betaComment = (
|
||||
await resolveComment(beta, commentRes.comment_view.comment)
|
||||
await waitUntil(
|
||||
() => resolveComment(beta, commentRes.comment_view.comment),
|
||||
c => c.comment?.counts.score === 1,
|
||||
)
|
||||
).comment;
|
||||
|
||||
if (!betaComment) {
|
||||
|
@ -406,7 +414,10 @@ test("Reply to a comment from another instance, get notification", async () => {
|
|||
expect(alphaUnreadCountRes.replies).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// check inbox of replies on alpha, fetching read/unread both
|
||||
let alphaRepliesRes = await getReplies(alpha);
|
||||
let alphaRepliesRes = await waitUntil(
|
||||
() => getReplies(alpha),
|
||||
r => r.replies.length > 0,
|
||||
);
|
||||
const alphaReply = alphaRepliesRes.replies.find(
|
||||
r => r.comment.id === alphaComment.comment.id,
|
||||
);
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
resolveBetaCommunity,
|
||||
longDelay,
|
||||
} from "./shared";
|
||||
import { EditSite, LemmyHttp } from "lemmy-js-client";
|
||||
import { EditSite } from "lemmy-js-client";
|
||||
|
||||
beforeAll(setupLogins);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
PurgePost,
|
||||
} from "lemmy-js-client";
|
||||
import {
|
||||
alpha,
|
||||
alphaImage,
|
||||
alphaUrl,
|
||||
beta,
|
||||
betaUrl,
|
||||
|
@ -18,22 +18,22 @@ import {
|
|||
setupLogins,
|
||||
unfollowRemotes,
|
||||
} from "./shared";
|
||||
import fs = require("fs");
|
||||
const downloadFileSync = require("download-file-sync");
|
||||
|
||||
beforeAll(setupLogins);
|
||||
|
||||
afterAll(() => {
|
||||
unfollowRemotes(alpha);
|
||||
unfollowRemotes(alphaImage);
|
||||
});
|
||||
|
||||
test("Upload image and delete it", async () => {
|
||||
// upload test image
|
||||
const upload_image = fs.readFileSync("test.png");
|
||||
// Upload test image. We use a simple string buffer as pictrs doesnt require an actual image
|
||||
// in testing mode.
|
||||
const upload_image = Buffer.from("test");
|
||||
const upload_form: UploadImage = {
|
||||
image: upload_image,
|
||||
};
|
||||
const upload = await alpha.uploadImage(upload_form);
|
||||
const upload = await alphaImage.uploadImage(upload_form);
|
||||
expect(upload.files![0].file).toBeDefined();
|
||||
expect(upload.files![0].delete_token).toBeDefined();
|
||||
expect(upload.url).toBeDefined();
|
||||
|
@ -48,7 +48,7 @@ test("Upload image and delete it", async () => {
|
|||
token: upload.files![0].delete_token,
|
||||
filename: upload.files![0].file,
|
||||
};
|
||||
const delete_ = await alpha.deleteImage(delete_form);
|
||||
const delete_ = await alphaImage.deleteImage(delete_form);
|
||||
expect(delete_).toBe(true);
|
||||
|
||||
// ensure that image is deleted
|
||||
|
@ -57,10 +57,10 @@ test("Upload image and delete it", async () => {
|
|||
});
|
||||
|
||||
test("Purge user, uploaded image removed", async () => {
|
||||
let user = await registerUser(alpha, alphaUrl);
|
||||
let user = await registerUser(alphaImage, alphaUrl);
|
||||
|
||||
// upload test image
|
||||
const upload_image = fs.readFileSync("test.png");
|
||||
const upload_image = Buffer.from("test");
|
||||
const upload_form: UploadImage = {
|
||||
image: upload_image,
|
||||
};
|
||||
|
@ -79,7 +79,7 @@ test("Purge user, uploaded image removed", async () => {
|
|||
const purge_form: PurgePerson = {
|
||||
person_id: site.my_user!.local_user_view.person.id,
|
||||
};
|
||||
const delete_ = await alpha.purgePerson(purge_form);
|
||||
const delete_ = await alphaImage.purgePerson(purge_form);
|
||||
expect(delete_.success).toBe(true);
|
||||
|
||||
// ensure that image is deleted
|
||||
|
@ -91,7 +91,7 @@ test("Purge post, linked image removed", async () => {
|
|||
let user = await registerUser(beta, betaUrl);
|
||||
|
||||
// upload test image
|
||||
const upload_image = fs.readFileSync("test.png");
|
||||
const upload_image = Buffer.from("test");
|
||||
const upload_form: UploadImage = {
|
||||
image: upload_image,
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@ import {
|
|||
loginUser,
|
||||
} from "./shared";
|
||||
import { PostView } from "lemmy-js-client/dist/types/PostView";
|
||||
import { LemmyHttp, ResolveObject } from "lemmy-js-client";
|
||||
import { ResolveObject } from "lemmy-js-client";
|
||||
|
||||
let betaCommunity: CommunityView | undefined;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
deletePrivateMessage,
|
||||
unfollowRemotes,
|
||||
waitUntil,
|
||||
reportPrivateMessage,
|
||||
} from "./shared";
|
||||
|
||||
let recipient_id: number;
|
||||
|
@ -109,3 +110,42 @@ test("Delete a private message", async () => {
|
|||
betaPms1.private_messages.length,
|
||||
);
|
||||
});
|
||||
|
||||
test("Create a private message report", async () => {
|
||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||
let betaPms1 = await waitUntil(
|
||||
() => listPrivateMessages(beta),
|
||||
m =>
|
||||
!!m.private_messages.find(
|
||||
e =>
|
||||
e.private_message.ap_id ===
|
||||
pmRes.private_message_view.private_message.ap_id,
|
||||
),
|
||||
);
|
||||
let betaPm = betaPms1.private_messages[0];
|
||||
expect(betaPm).toBeDefined();
|
||||
|
||||
// Make sure that only the recipient can report it, so this should fail
|
||||
await expect(
|
||||
reportPrivateMessage(
|
||||
alpha,
|
||||
pmRes.private_message_view.private_message.id,
|
||||
"a reason",
|
||||
),
|
||||
).rejects.toStrictEqual(Error("couldnt_create_report"));
|
||||
|
||||
// This one should pass
|
||||
let reason = "another reason";
|
||||
let report = await reportPrivateMessage(
|
||||
beta,
|
||||
betaPm.private_message.id,
|
||||
reason,
|
||||
);
|
||||
|
||||
expect(report.private_message_report_view.private_message.id).toBe(
|
||||
betaPm.private_message.id,
|
||||
);
|
||||
expect(report.private_message_report_view.private_message_report.reason).toBe(
|
||||
reason,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -4,12 +4,14 @@ import {
|
|||
BlockInstance,
|
||||
BlockInstanceResponse,
|
||||
CommunityId,
|
||||
CreatePrivateMessageReport,
|
||||
GetReplies,
|
||||
GetRepliesResponse,
|
||||
GetUnreadCountResponse,
|
||||
InstanceId,
|
||||
LemmyHttp,
|
||||
PostView,
|
||||
PrivateMessageReportResponse,
|
||||
SuccessResponse,
|
||||
} from "lemmy-js-client";
|
||||
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
||||
|
@ -75,17 +77,20 @@ import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDe
|
|||
import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails";
|
||||
import { ListingType } from "lemmy-js-client/dist/types/ListingType";
|
||||
|
||||
export const fetchFunction = fetch;
|
||||
|
||||
export let alphaUrl = "http://127.0.0.1:8541";
|
||||
export let betaUrl = "http://127.0.0.1:8551";
|
||||
export let gammaUrl = "http://127.0.0.1:8561";
|
||||
export let deltaUrl = "http://127.0.0.1:8571";
|
||||
export let epsilonUrl = "http://127.0.0.1:8581";
|
||||
|
||||
export let alpha = new LemmyHttp(alphaUrl);
|
||||
export let beta = new LemmyHttp(betaUrl);
|
||||
export let gamma = new LemmyHttp(gammaUrl);
|
||||
export let delta = new LemmyHttp(deltaUrl);
|
||||
export let epsilon = new LemmyHttp(epsilonUrl);
|
||||
export let alpha = new LemmyHttp(alphaUrl, { fetchFunction });
|
||||
export let alphaImage = new LemmyHttp(alphaUrl);
|
||||
export let beta = new LemmyHttp(betaUrl, { fetchFunction });
|
||||
export let gamma = new LemmyHttp(gammaUrl, { fetchFunction });
|
||||
export let delta = new LemmyHttp(deltaUrl, { fetchFunction });
|
||||
export let epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
|
||||
|
||||
export let betaAllowedInstances = [
|
||||
"lemmy-alpha",
|
||||
|
@ -135,6 +140,7 @@ export async function setupLogins() {
|
|||
resEpsilon,
|
||||
]);
|
||||
alpha.setHeaders({ Authorization: `Bearer ${res[0].jwt ?? ""}` });
|
||||
alphaImage.setHeaders({ Authorization: `Bearer ${res[0].jwt ?? ""}` });
|
||||
beta.setHeaders({ Authorization: `Bearer ${res[1].jwt ?? ""}` });
|
||||
gamma.setHeaders({ Authorization: `Bearer ${res[2].jwt ?? ""}` });
|
||||
delta.setHeaders({ Authorization: `Bearer ${res[3].jwt ?? ""}` });
|
||||
|
@ -325,6 +331,7 @@ export async function getComments(
|
|||
post_id: post_id,
|
||||
type_: listingType,
|
||||
sort: "New",
|
||||
limit: 50,
|
||||
};
|
||||
return api.getComments(form);
|
||||
}
|
||||
|
@ -776,6 +783,18 @@ export async function reportComment(
|
|||
return api.createCommentReport(form);
|
||||
}
|
||||
|
||||
export async function reportPrivateMessage(
|
||||
api: LemmyHttp,
|
||||
private_message_id: number,
|
||||
reason: string,
|
||||
): Promise<PrivateMessageReportResponse> {
|
||||
let form: CreatePrivateMessageReport = {
|
||||
private_message_id,
|
||||
reason,
|
||||
};
|
||||
return api.createPrivateMessageReport(form);
|
||||
}
|
||||
|
||||
export async function listCommentReports(
|
||||
api: LemmyHttp,
|
||||
): Promise<ListCommentReportsResponse> {
|
||||
|
@ -789,6 +808,7 @@ export function getPosts(
|
|||
): Promise<GetPostsResponse> {
|
||||
let form: GetPosts = {
|
||||
type_: listingType,
|
||||
limit: 50,
|
||||
};
|
||||
return api.getPosts(form);
|
||||
}
|
||||
|
@ -857,6 +877,7 @@ export function getCommentParentId(comment: Comment): number | undefined {
|
|||
if (split.length > 1) {
|
||||
return Number(split[split.length - 2]);
|
||||
} else {
|
||||
console.log(`Failed to extract comment parent id from ${comment.path}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
saveUserSettings,
|
||||
getPost,
|
||||
getComments,
|
||||
fetchFunction,
|
||||
} from "./shared";
|
||||
import { LemmyHttp, SaveUserSettings } from "lemmy-js-client";
|
||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||
|
@ -114,6 +115,7 @@ test("Delete user", async () => {
|
|||
test("Requests with invalid auth should be treated as unauthenticated", async () => {
|
||||
let invalid_auth = new LemmyHttp(alphaUrl, {
|
||||
headers: { Authorization: "Bearer foobar" },
|
||||
fetchFunction,
|
||||
});
|
||||
let site = await getSite(invalid_auth);
|
||||
expect(site.my_user).toBeUndefined();
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
|
@ -314,10 +314,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4"
|
||||
integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==
|
||||
|
||||
"@eslint/eslintrc@^2.1.3":
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.3.tgz#797470a75fe0fbd5a53350ee715e85e87baff22d"
|
||||
integrity sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==
|
||||
"@eslint/eslintrc@^2.1.4":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
|
||||
integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
debug "^4.3.2"
|
||||
|
@ -329,10 +329,10 @@
|
|||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@8.53.0":
|
||||
version "8.53.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d"
|
||||
integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==
|
||||
"@eslint/js@8.55.0":
|
||||
version "8.55.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.55.0.tgz#b721d52060f369aa259cf97392403cb9ce892ec6"
|
||||
integrity sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==
|
||||
|
||||
"@humanwhocodes/config-array@^0.11.13":
|
||||
version "0.11.13"
|
||||
|
@ -704,10 +704,10 @@
|
|||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest@^29.5.8":
|
||||
version "29.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.8.tgz#ed5c256fe2bc7c38b1915ee5ef1ff24a3427e120"
|
||||
integrity sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==
|
||||
"@types/jest@^29.5.11":
|
||||
version "29.5.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c"
|
||||
integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==
|
||||
dependencies:
|
||||
expect "^29.0.0"
|
||||
pretty-format "^29.0.0"
|
||||
|
@ -722,10 +722,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.0.tgz#10ddf0119cf20028781c06d7115562934e53f745"
|
||||
integrity sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==
|
||||
|
||||
"@types/node@^20.9.0":
|
||||
version "20.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
|
||||
integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
|
||||
"@types/node@^20.10.4":
|
||||
version "20.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.4.tgz#b246fd84d55d5b1b71bf51f964bd514409347198"
|
||||
integrity sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
|
@ -751,16 +751,16 @@
|
|||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz#cfe2bd34e26d2289212946b96ab19dcad64b661a"
|
||||
integrity sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==
|
||||
"@typescript-eslint/eslint-plugin@^6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz#fc1ab5f23618ba590c87e8226ff07a760be3dd7b"
|
||||
integrity sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.5.1"
|
||||
"@typescript-eslint/scope-manager" "6.10.0"
|
||||
"@typescript-eslint/type-utils" "6.10.0"
|
||||
"@typescript-eslint/utils" "6.10.0"
|
||||
"@typescript-eslint/visitor-keys" "6.10.0"
|
||||
"@typescript-eslint/scope-manager" "6.14.0"
|
||||
"@typescript-eslint/type-utils" "6.14.0"
|
||||
"@typescript-eslint/utils" "6.14.0"
|
||||
"@typescript-eslint/visitor-keys" "6.14.0"
|
||||
debug "^4.3.4"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.2.4"
|
||||
|
@ -768,72 +768,72 @@
|
|||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/parser@^6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.10.0.tgz#578af79ae7273193b0b6b61a742a2bc8e02f875a"
|
||||
integrity sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==
|
||||
"@typescript-eslint/parser@^6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.14.0.tgz#a2d6a732e0d2b95c73f6a26ae7362877cc1b4212"
|
||||
integrity sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "6.10.0"
|
||||
"@typescript-eslint/types" "6.10.0"
|
||||
"@typescript-eslint/typescript-estree" "6.10.0"
|
||||
"@typescript-eslint/visitor-keys" "6.10.0"
|
||||
"@typescript-eslint/scope-manager" "6.14.0"
|
||||
"@typescript-eslint/types" "6.14.0"
|
||||
"@typescript-eslint/typescript-estree" "6.14.0"
|
||||
"@typescript-eslint/visitor-keys" "6.14.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz#b0276118b13d16f72809e3cecc86a72c93708540"
|
||||
integrity sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==
|
||||
"@typescript-eslint/scope-manager@6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz#53d24363fdb5ee0d1d8cda4ed5e5321272ab3d48"
|
||||
integrity sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.10.0"
|
||||
"@typescript-eslint/visitor-keys" "6.10.0"
|
||||
"@typescript-eslint/types" "6.14.0"
|
||||
"@typescript-eslint/visitor-keys" "6.14.0"
|
||||
|
||||
"@typescript-eslint/type-utils@6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz#1007faede067c78bdbcef2e8abb31437e163e2e1"
|
||||
integrity sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==
|
||||
"@typescript-eslint/type-utils@6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz#ac9cb5ba0615c837f1a6b172feeb273d36e4f8af"
|
||||
integrity sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "6.10.0"
|
||||
"@typescript-eslint/utils" "6.10.0"
|
||||
"@typescript-eslint/typescript-estree" "6.14.0"
|
||||
"@typescript-eslint/utils" "6.14.0"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/types@6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.10.0.tgz#f4f0a84aeb2ac546f21a66c6e0da92420e921367"
|
||||
integrity sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==
|
||||
"@typescript-eslint/types@6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.14.0.tgz#935307f7a931016b7a5eb25d494ea3e1f613e929"
|
||||
integrity sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==
|
||||
|
||||
"@typescript-eslint/typescript-estree@6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz#667381eed6f723a1a8ad7590a31f312e31e07697"
|
||||
integrity sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==
|
||||
"@typescript-eslint/typescript-estree@6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz#90c7ddd45cd22139adf3d4577580d04c9189ac13"
|
||||
integrity sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.10.0"
|
||||
"@typescript-eslint/visitor-keys" "6.10.0"
|
||||
"@typescript-eslint/types" "6.14.0"
|
||||
"@typescript-eslint/visitor-keys" "6.14.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/utils@6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.10.0.tgz#4d76062d94413c30e402c9b0df8c14aef8d77336"
|
||||
integrity sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==
|
||||
"@typescript-eslint/utils@6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.14.0.tgz#856a9e274367d99ffbd39c48128b93a86c4261e3"
|
||||
integrity sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@types/json-schema" "^7.0.12"
|
||||
"@types/semver" "^7.5.0"
|
||||
"@typescript-eslint/scope-manager" "6.10.0"
|
||||
"@typescript-eslint/types" "6.10.0"
|
||||
"@typescript-eslint/typescript-estree" "6.10.0"
|
||||
"@typescript-eslint/scope-manager" "6.14.0"
|
||||
"@typescript-eslint/types" "6.14.0"
|
||||
"@typescript-eslint/typescript-estree" "6.14.0"
|
||||
semver "^7.5.4"
|
||||
|
||||
"@typescript-eslint/visitor-keys@6.10.0":
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz#b9eaf855a1ac7e95633ae1073af43d451e8f84e3"
|
||||
integrity sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==
|
||||
"@typescript-eslint/visitor-keys@6.14.0":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz#1d1d486581819287de824a56c22f32543561138e"
|
||||
integrity sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.10.0"
|
||||
"@typescript-eslint/types" "6.14.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
|
@ -1338,15 +1338,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
|
|||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
|
||||
eslint@^8.53.0:
|
||||
version "8.53.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce"
|
||||
integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==
|
||||
eslint@^8.55.0:
|
||||
version "8.55.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.55.0.tgz#078cb7b847d66f2c254ea1794fa395bf8e7e03f8"
|
||||
integrity sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/regexpp" "^4.6.1"
|
||||
"@eslint/eslintrc" "^2.1.3"
|
||||
"@eslint/js" "8.53.0"
|
||||
"@eslint/eslintrc" "^2.1.4"
|
||||
"@eslint/js" "8.55.0"
|
||||
"@humanwhocodes/config-array" "^0.11.13"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@nodelib/fs.walk" "^1.2.8"
|
||||
|
@ -2286,10 +2286,10 @@ kleur@^3.0.3:
|
|||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
lemmy-js-client@0.19.0-alpha.18:
|
||||
version "0.19.0-alpha.18"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-alpha.18.tgz#f94841681cabdf9d5c4ce7048eacb57557f68724"
|
||||
integrity sha512-cKJfKKnjK+ijk0Yd6ydtne3Y4FILp2RbQg05pCru9n6PCyPAa85eQL4QxPB1PPed20ckSZRcHLcnr/bYFDgpaw==
|
||||
lemmy-js-client@0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0.tgz#50098183264fa176784857f45665b06994b31e18"
|
||||
integrity sha512-h+E8wC9RKjlToWw9+kuGFAzk4Fiaf61KqAwzvoCDAfj2L1r+YNt5EDMOggGCoRx5PlqLuIVr7BNEU46KxJfmHA==
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
form-data "^4.0.0"
|
||||
|
@ -2619,10 +2619,10 @@ prettier-linter-helpers@^1.0.0:
|
|||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643"
|
||||
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
|
||||
prettier@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848"
|
||||
integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==
|
||||
|
||||
pretty-format@^29.0.0, pretty-format@^29.7.0:
|
||||
version "29.7.0"
|
||||
|
@ -2952,10 +2952,10 @@ type-fest@^0.21.3:
|
|||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
|
||||
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
|
||||
|
||||
typescript@^5.0.4:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
|
||||
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||
typescript@^5.3.3:
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
|
||||
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[package]
|
||||
name = "lemmy_api"
|
||||
publish = false
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
|
|
|
@ -26,6 +26,11 @@ pub async fn distinguish_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the creator can distinguish
|
||||
if local_user_view.person.id != orig_comment.creator.id {
|
||||
Err(LemmyErrorType::NoCommentEditAllowed)?
|
||||
}
|
||||
|
||||
// Verify that only a mod or admin can distinguish a comment
|
||||
check_community_mod_action(
|
||||
&local_user_view.person,
|
||||
|
|
|
@ -82,16 +82,7 @@ pub fn read_auth_token(req: &HttpRequest) -> Result<Option<String>, LemmyError>
|
|||
}
|
||||
// If that fails, try to read from cookie
|
||||
else if let Some(cookie) = &req.cookie(AUTH_COOKIE_NAME) {
|
||||
// ensure that its marked as httponly and secure
|
||||
let secure = cookie.secure().unwrap_or_default();
|
||||
let http_only = cookie.http_only().unwrap_or_default();
|
||||
let is_debug_mode = cfg!(debug_assertions);
|
||||
|
||||
if !is_debug_mode && (!secure || !http_only) {
|
||||
Err(LemmyError::from(LemmyErrorType::AuthCookieInsecure))
|
||||
} else {
|
||||
Ok(Some(cookie.value().to_string()))
|
||||
}
|
||||
Ok(Some(cookie.value().to_string()))
|
||||
}
|
||||
// Otherwise, there's no auth
|
||||
else {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
post::{GetSiteMetadata, GetSiteMetadataResponse},
|
||||
|
@ -8,7 +8,7 @@ use lemmy_utils::error::LemmyError;
|
|||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_link_metadata(
|
||||
data: Json<GetSiteMetadata>,
|
||||
data: Query<GetSiteMetadata>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<GetSiteMetadataResponse>, LemmyError> {
|
||||
let metadata = fetch_link_metadata(&data.url, false, &context).await?;
|
||||
|
|
|
@ -31,6 +31,11 @@ pub async fn create_pm_report(
|
|||
let private_message_id = data.private_message_id;
|
||||
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
||||
|
||||
// Make sure that only the recipient of the private message can create a report
|
||||
if person_id != private_message.recipient_id {
|
||||
Err(LemmyErrorType::CouldntCreateReport)?
|
||||
}
|
||||
|
||||
let report_form = PrivateMessageReportForm {
|
||||
creator_id: person_id,
|
||||
private_message_id,
|
||||
|
|
|
@ -19,6 +19,10 @@ pub async fn block_instance(
|
|||
) -> Result<Json<BlockInstanceResponse>, LemmyError> {
|
||||
let instance_id = data.instance_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
if local_user_view.person.instance_id == instance_id {
|
||||
return Err(LemmyErrorType::CantBlockLocalInstance)?;
|
||||
}
|
||||
|
||||
let instance_block_form = InstanceBlockForm {
|
||||
person_id,
|
||||
instance_id,
|
||||
|
|
|
@ -26,7 +26,7 @@ async fn generate_urlset(
|
|||
}
|
||||
|
||||
pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpResponse> {
|
||||
info!("Generating sitemap with posts from last {} hours...", 24);
|
||||
info!("Generating sitemap...",);
|
||||
let posts = Post::list_for_sitemap(&mut context.pool()).await?;
|
||||
info!("Loaded latest {} posts", posts.len());
|
||||
|
||||
|
@ -36,7 +36,7 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
|
|||
Ok(
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/xml")
|
||||
.insert_header(header::CacheControl(vec![CacheDirective::MaxAge(86_400)])) // 24 h
|
||||
.insert_header(header::CacheControl(vec![CacheDirective::MaxAge(3_600)])) // 1 h
|
||||
.body(buf),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ webpage = { version = "1.6", default-features = false, features = [
|
|||
encoding = { version = "0.2.33", optional = true }
|
||||
jsonwebtoken = { version = "8.3.0", optional = true }
|
||||
# necessary for wasmt compilation
|
||||
getrandom = { version = "0.2.10", features = ["js"] }
|
||||
getrandom = { version = "0.2.11", features = ["js"] }
|
||||
task-local-extensions = "0.1.4"
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
|
|
|
@ -98,9 +98,9 @@ impl ActivityChannel {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn close(outgoing_activities_task: JoinHandle<LemmyResult<()>>) -> LemmyResult<()> {
|
||||
pub async fn close(outgoing_activities_task: JoinHandle<()>) -> LemmyResult<()> {
|
||||
ACTIVITY_CHANNEL.keepalive_sender.lock().await.take();
|
||||
outgoing_activities_task.await??;
|
||||
outgoing_activities_task.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ use tracing::warn;
|
|||
use url::{ParseError, Url};
|
||||
use urlencoding::encode;
|
||||
|
||||
pub static AUTH_COOKIE_NAME: &str = "auth";
|
||||
pub static AUTH_COOKIE_NAME: &str = "jwt";
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn is_mod_or_admin(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[package]
|
||||
name = "lemmy_api_crud"
|
||||
publish = false
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
|
@ -22,5 +23,12 @@ bcrypt = { workspace = true }
|
|||
actix-web = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
webmention = "0.5.0"
|
||||
futures.workspace = true
|
||||
uuid = { workspace = true }
|
||||
moka.workspace = true
|
||||
once_cell.workspace = true
|
||||
anyhow.workspace = true
|
||||
webmention = "0.5.0"
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["futures"]
|
||||
|
|
|
@ -21,46 +21,68 @@ use lemmy_utils::{
|
|||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
version,
|
||||
};
|
||||
use moka::future::Cache;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_site(
|
||||
local_user_view: Option<LocalUserView>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<GetSiteResponse>, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
static CACHE: Lazy<Cache<(), GetSiteResponse>> = Lazy::new(|| {
|
||||
Cache::builder()
|
||||
.max_capacity(1)
|
||||
.time_to_live(Duration::from_secs(1))
|
||||
.build()
|
||||
});
|
||||
|
||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||
// This data is independent from the user account so we can cache it across requests
|
||||
let mut site_response = CACHE
|
||||
.try_get_with::<_, LemmyError>((), async {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||
let custom_emojis =
|
||||
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||
Ok(GetSiteResponse {
|
||||
site_view,
|
||||
admins,
|
||||
version: version::VERSION.to_string(),
|
||||
my_user: None,
|
||||
all_languages,
|
||||
discussion_languages,
|
||||
taglines,
|
||||
custom_emojis,
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to construct site response: {e}"))?;
|
||||
|
||||
// Build the local user
|
||||
let my_user = if let Some(local_user_view) = local_user_view {
|
||||
// Build the local user with parallel queries and add it to site response
|
||||
site_response.my_user = if let Some(local_user_view) = local_user_view {
|
||||
let person_id = local_user_view.person.id;
|
||||
let local_user_id = local_user_view.local_user.id;
|
||||
let pool = &mut context.pool();
|
||||
|
||||
let follows = CommunityFollowerView::for_person(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_blocks = CommunityBlockView::for_person(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
|
||||
let instance_blocks = InstanceBlockView::for_person(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let person_blocks = PersonBlockView::for_person(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
|
||||
let moderates = CommunityModeratorView::for_person(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
|
||||
let discussion_languages = LocalUserLanguage::read(&mut context.pool(), local_user_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
let (
|
||||
follows,
|
||||
community_blocks,
|
||||
instance_blocks,
|
||||
person_blocks,
|
||||
moderates,
|
||||
discussion_languages,
|
||||
) = lemmy_db_schema::try_join_with_pool!(pool => (
|
||||
|pool| CommunityFollowerView::for_person(pool, person_id),
|
||||
|pool| CommunityBlockView::for_person(pool, person_id),
|
||||
|pool| InstanceBlockView::for_person(pool, person_id),
|
||||
|pool| PersonBlockView::for_person(pool, person_id),
|
||||
|pool| CommunityModeratorView::for_person(pool, person_id),
|
||||
|pool| LocalUserLanguage::read(pool, local_user_id)
|
||||
))
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
|
||||
Some(MyUserInfo {
|
||||
local_user_view,
|
||||
|
@ -75,20 +97,5 @@ pub async fn get_site(
|
|||
None
|
||||
};
|
||||
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||
let custom_emojis =
|
||||
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||
|
||||
Ok(Json(GetSiteResponse {
|
||||
site_view,
|
||||
admins,
|
||||
version: version::VERSION.to_string(),
|
||||
my_user,
|
||||
all_languages,
|
||||
discussion_languages,
|
||||
taglines,
|
||||
custom_emojis,
|
||||
}))
|
||||
Ok(Json(site_response))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[package]
|
||||
name = "lemmy_apub"
|
||||
publish = false
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
|
@ -40,12 +41,12 @@ async-trait = { workspace = true }
|
|||
anyhow = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
moka.workspace = true
|
||||
serde_with.workspace = true
|
||||
html2md = "0.2.14"
|
||||
html2text = "0.6.0"
|
||||
stringreader = "0.1.1"
|
||||
enum_delegate = "0.2.0"
|
||||
moka = { version = "0.11", features = ["future"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
|
|
|
@ -1,30 +1,4 @@
|
|||
{
|
||||
"type": "Group",
|
||||
"id": "https://framatube.org/video-channels/joinpeertube",
|
||||
"following": "https://framatube.org/video-channels/joinpeertube/following",
|
||||
"followers": "https://framatube.org/video-channels/joinpeertube/followers",
|
||||
"playlists": "https://framatube.org/video-channels/joinpeertube/playlists",
|
||||
"inbox": "https://framatube.org/video-channels/joinpeertube/inbox",
|
||||
"outbox": "https://framatube.org/video-channels/joinpeertube/outbox",
|
||||
"preferredUsername": "joinpeertube",
|
||||
"url": "https://framatube.org/video-channels/joinpeertube",
|
||||
"name": "A propos de PeerTube",
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://framatube.org/inbox"
|
||||
},
|
||||
"publicKey": {
|
||||
"id": "https://framatube.org/video-channels/joinpeertube#main-key",
|
||||
"owner": "https://framatube.org/video-channels/joinpeertube",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsJCIZJga+4Kumb9Wrmpy\ntyV7kWdINImoXBiFkGG+6OHreHN2C3UPwTu9IkX/e20NaX6Ly6c0busieW7yh//q\nomHl2U8zz2Z5xQHUN/2ljQjUNO+89OV6cFIGyEvcwc6QhuqGvrcxonjrEkux7xSv\nxQM4kZ3YW1Sii4piFpGGIm1pcUkOxFab8PWVB5Hzpg/df2/XOmH8UECT5vaMRPE6\ns6hNiQNE34z9QmPiG6nUlaWb/WDcMYbma3sUVWW3DI008ukLlwLaLIm30ax8CEYt\nHEv2jOQb1E1sXtBPe1FI+dXRgTIk40KF50KLqcgwJH1y5ck7c8IEeooj+tYGVqPr\npQIDAQAB\n-----END PUBLIC KEY-----"
|
||||
},
|
||||
"published": "2021-08-09T14:26:09.514Z",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/png",
|
||||
"height": 120,
|
||||
"width": 120,
|
||||
"url": "https://framatube.org/lazy-static/avatars/a2c2ff10-9da6-4c6c-9b25-2e557fa74b66.png"
|
||||
},
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
|
@ -33,99 +7,66 @@
|
|||
},
|
||||
{
|
||||
"pt": "https://joinpeertube.org/ns#",
|
||||
"sc": "http://schema.org#",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"uuid": "sc:identifier",
|
||||
"category": "sc:category",
|
||||
"licence": "sc:license",
|
||||
"subtitleLanguage": "sc:subtitleLanguage",
|
||||
"sensitive": "as:sensitive",
|
||||
"language": "sc:inLanguage",
|
||||
"isLiveBroadcast": "sc:isLiveBroadcast",
|
||||
"liveSaveReplay": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:liveSaveReplay"
|
||||
},
|
||||
"permanentLive": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:permanentLive"
|
||||
},
|
||||
"Infohash": "pt:Infohash",
|
||||
"Playlist": "pt:Playlist",
|
||||
"PlaylistElement": "pt:PlaylistElement",
|
||||
"originallyPublishedAt": "sc:datePublished",
|
||||
"views": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:views"
|
||||
},
|
||||
"state": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:state"
|
||||
},
|
||||
"size": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:size"
|
||||
},
|
||||
"fps": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:fps"
|
||||
},
|
||||
"startTimestamp": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:startTimestamp"
|
||||
},
|
||||
"stopTimestamp": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:stopTimestamp"
|
||||
},
|
||||
"position": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:position"
|
||||
},
|
||||
"commentsEnabled": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:commentsEnabled"
|
||||
},
|
||||
"downloadEnabled": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:downloadEnabled"
|
||||
},
|
||||
"waitTranscoding": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:waitTranscoding"
|
||||
"sc": "http://schema.org/",
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
},
|
||||
"support": {
|
||||
"@type": "sc:Text",
|
||||
"@id": "pt:support"
|
||||
},
|
||||
"likes": {
|
||||
"@id": "as:likes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"dislikes": {
|
||||
"@id": "as:dislikes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
},
|
||||
"shares": {
|
||||
"@id": "as:shares",
|
||||
"@type": "@id"
|
||||
},
|
||||
"comments": {
|
||||
"@id": "as:comments",
|
||||
"@type": "@id"
|
||||
}
|
||||
"icons": "as:icon"
|
||||
}
|
||||
],
|
||||
"summary": "Un logiciel libre pour reprendre le contrôle de vos vidéos",
|
||||
"support": null,
|
||||
"type": "Group",
|
||||
"id": "https://peertube.stream/video-channels/vu",
|
||||
"following": "https://peertube.stream/video-channels/vu/following",
|
||||
"followers": "https://peertube.stream/video-channels/vu/followers",
|
||||
"playlists": "https://peertube.stream/video-channels/vu/playlists",
|
||||
"inbox": "https://peertube.stream/video-channels/vu/inbox",
|
||||
"outbox": "https://peertube.stream/video-channels/vu/outbox",
|
||||
"preferredUsername": "vu",
|
||||
"url": "https://peertube.stream/video-channels/vu",
|
||||
"name": "VU",
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://peertube.stream/inbox"
|
||||
},
|
||||
"publicKey": {
|
||||
"id": "https://peertube.stream/video-channels/vu#main-key",
|
||||
"owner": "https://peertube.stream/video-channels/vu",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtcWpN7efQx5C7ecWkw3r\nX4ViPy/bl3d3iyVLyP6z/3+WAUKJxqR+QKlNzxM7NglzB0B48NYu2cg4iuwKkSK9\ntrfMC/Ze0H10Wo/5kUH5YQKzLo4syHOuuM+1rbZFBbzVFwk4k0qqLFTXQ+Y6WNSS\nG9OlFYZNpRaUkgF8Q/KCsngn68qsZ0gLly9FJb+6+j3IppLJNXrBpFB5qulWibL+\neN+3XMnaTm6ge6X+rFti5r6dh10grL0KU/eZKmGyadgdwYdvR/LLtBWwFIwSJShk\nuIPhcz2zbkwrV3AixLe76TLGXX5M9qczfsVYLupyU7TwPlFM2ENDtDdfp41sWaZa\nxQIDAQAB\n-----END PUBLIC KEY-----"
|
||||
},
|
||||
"published": "2020-12-10T16:07:08.406Z",
|
||||
"icon": [
|
||||
{
|
||||
"type": "Image",
|
||||
"mediaType": "image/jpeg",
|
||||
"height": 48,
|
||||
"width": 48,
|
||||
"url": "https://peertube.stream/lazy-static/avatars/45ec87d5-c8ec-4fcf-948f-d5a928b56496.jpg"
|
||||
},
|
||||
{
|
||||
"type": "Image",
|
||||
"mediaType": "image/jpeg",
|
||||
"height": 120,
|
||||
"width": 120,
|
||||
"url": "https://peertube.stream/lazy-static/avatars/3296c098-abbb-4fda-a67a-ab88e447ca19.jpg"
|
||||
}
|
||||
],
|
||||
"image": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/jpeg",
|
||||
"height": 317,
|
||||
"width": 1920,
|
||||
"url": "https://peertube.stream/lazy-static/banners/550c0541-3021-4d4b-8654-54d0c4cda96d.jpg"
|
||||
},
|
||||
"summary": "VU c'est du lundi au samedi sur France 5 à 20h00 \nRetrouvez les meilleurs moments de la télévision, en 6 minutes.\n\nChaîne PeerTube non-officielle.",
|
||||
"support": "Suivre VU :\n- Twitter : https://twitter.com/vufrancetv\n- Facebook :https://www.facebook.com/vufrancetv/\n- Site : https://www.france.tv/france-5/vu/",
|
||||
"attributedTo": [
|
||||
{
|
||||
"type": "Person",
|
||||
"id": "https://framatube.org/accounts/framasoft"
|
||||
"id": "https://peertube.stream/accounts/createurs"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,30 +1,4 @@
|
|||
{
|
||||
"type": "Person",
|
||||
"id": "https://framatube.org/accounts/framasoft",
|
||||
"following": "https://framatube.org/accounts/framasoft/following",
|
||||
"followers": "https://framatube.org/accounts/framasoft/followers",
|
||||
"playlists": "https://framatube.org/accounts/framasoft/playlists",
|
||||
"inbox": "https://framatube.org/accounts/framasoft/inbox",
|
||||
"outbox": "https://framatube.org/accounts/framasoft/outbox",
|
||||
"preferredUsername": "framasoft",
|
||||
"url": "https://framatube.org/accounts/framasoft",
|
||||
"name": "Framasoft",
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://framatube.org/inbox"
|
||||
},
|
||||
"publicKey": {
|
||||
"id": "https://framatube.org/accounts/framasoft#main-key",
|
||||
"owner": "https://framatube.org/accounts/framasoft",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuRh3frgIg866D0y0FThp\nSUkJImMcHGkUvpYQYv2iUgarZZtEbwT8PfQf0bJazy+cP8KqQmMDf5PBhT7dfdny\nf/GKGMw9Olc+QISeKDj3sqZ3Csrm4KV4avMGCfth6eSU7LozojeSGCXdUFz/8UgE\nfhV4mJjEX/FbwRYoKlagv5rY9mkX5XomzZU+z9j6ZVXyofwOwJvmI1hq0SYDv2bc\neB/RgIh/H0nyMtF8o+0CT42FNEET9j9m1BKOBtPzwZHmitKRkEmui5cK256s1laB\nT61KHpcD9gQKkQ+I3sFEzCBUJYfVo6fUe+GehBZuAfq4qDhd15SfE4K9veDscDFI\nTwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
},
|
||||
"published": "2018-03-01T15:16:17.118Z",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/png",
|
||||
"height": null,
|
||||
"width": null,
|
||||
"url": "https://framatube.org/lazy-static/avatars/f73876f5-1d45-4f8a-942a-d3d5d5ac5dc1.png"
|
||||
},
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
|
@ -33,92 +7,52 @@
|
|||
},
|
||||
{
|
||||
"pt": "https://joinpeertube.org/ns#",
|
||||
"sc": "http://schema.org#",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"uuid": "sc:identifier",
|
||||
"category": "sc:category",
|
||||
"licence": "sc:license",
|
||||
"subtitleLanguage": "sc:subtitleLanguage",
|
||||
"sensitive": "as:sensitive",
|
||||
"language": "sc:inLanguage",
|
||||
"isLiveBroadcast": "sc:isLiveBroadcast",
|
||||
"liveSaveReplay": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:liveSaveReplay"
|
||||
},
|
||||
"permanentLive": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:permanentLive"
|
||||
},
|
||||
"Infohash": "pt:Infohash",
|
||||
"Playlist": "pt:Playlist",
|
||||
"PlaylistElement": "pt:PlaylistElement",
|
||||
"originallyPublishedAt": "sc:datePublished",
|
||||
"views": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:views"
|
||||
},
|
||||
"state": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:state"
|
||||
},
|
||||
"size": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:size"
|
||||
},
|
||||
"fps": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:fps"
|
||||
},
|
||||
"startTimestamp": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:startTimestamp"
|
||||
},
|
||||
"stopTimestamp": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:stopTimestamp"
|
||||
},
|
||||
"position": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:position"
|
||||
},
|
||||
"commentsEnabled": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:commentsEnabled"
|
||||
},
|
||||
"downloadEnabled": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:downloadEnabled"
|
||||
},
|
||||
"waitTranscoding": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:waitTranscoding"
|
||||
"sc": "http://schema.org/",
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
},
|
||||
"support": {
|
||||
"@type": "sc:Text",
|
||||
"@id": "pt:support"
|
||||
},
|
||||
"likes": {
|
||||
"@id": "as:likes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"dislikes": {
|
||||
"@id": "as:dislikes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
},
|
||||
"shares": {
|
||||
"@id": "as:shares",
|
||||
"@type": "@id"
|
||||
},
|
||||
"comments": {
|
||||
"@id": "as:comments",
|
||||
"@type": "@id"
|
||||
}
|
||||
"icons": "as:icon"
|
||||
}
|
||||
],
|
||||
"summary": null
|
||||
"type": "Person",
|
||||
"id": "https://peertube.stream/accounts/createurs",
|
||||
"following": "https://peertube.stream/accounts/createurs/following",
|
||||
"followers": "https://peertube.stream/accounts/createurs/followers",
|
||||
"playlists": "https://peertube.stream/accounts/createurs/playlists",
|
||||
"inbox": "https://peertube.stream/accounts/createurs/inbox",
|
||||
"outbox": "https://peertube.stream/accounts/createurs/outbox",
|
||||
"preferredUsername": "createurs",
|
||||
"url": "https://peertube.stream/accounts/createurs",
|
||||
"name": "Créateurs",
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://peertube.stream/inbox"
|
||||
},
|
||||
"publicKey": {
|
||||
"id": "https://peertube.stream/accounts/createurs#main-key",
|
||||
"owner": "https://peertube.stream/accounts/createurs",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkQhbRYbA81+WTYjorR\n2lEMad3kYCnzDjGTLr4I92eanzFHxyELGnjzP6TpEvjOiB9NrCRrqU/iFPLdgrq2\nwIFcXPWdCq6Gcg7QLlaeMM0JoJmr0KTEhzg0XKCo96UsyTzaF4DISxqi8RyoyWeU\nEkgiOzlkdYTlouq3MlQH+p1PBAsNUQfIEUsU+l6k1vzbm8JRwlT+D1bNde4I/Lqs\n4uB5ru3zzInwZ2hz9+heiriNoGEBv74rZHYn966tZVX8iMGx2+m6okozEdEQbqCl\n0ekqDcd8P6CoFqqeeu8coh82OUtuFI/XsbetdWA55YQmSHyMiTsIwVbeoogIETbI\n4QIDAQAB\n-----END PUBLIC KEY-----"
|
||||
},
|
||||
"published": "2020-11-11T17:12:37.243Z",
|
||||
"icon": [
|
||||
{
|
||||
"type": "Image",
|
||||
"mediaType": "image/png",
|
||||
"height": 48,
|
||||
"width": 48,
|
||||
"url": "https://peertube.stream/lazy-static/avatars/1760df9a-3c96-45fc-9342-c313a3bf2210.png"
|
||||
},
|
||||
{
|
||||
"type": "Image",
|
||||
"mediaType": "image/png",
|
||||
"height": 120,
|
||||
"width": 120,
|
||||
"url": "https://peertube.stream/lazy-static/avatars/c27b672d-ad8f-498a-adbe-553af8da56f9.png"
|
||||
}
|
||||
],
|
||||
"summary": "Centralisation de miroirs de chaînes. La grande majorité a été contactée ou diffuse sous licence avec paternité.\n\nCompte maintenu par [Raph](https://tooter.social/@raph)."
|
||||
}
|
||||
|
|
|
@ -1,372 +1,4 @@
|
|||
{
|
||||
"type": "Video",
|
||||
"id": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277",
|
||||
"name": "What is the Fediverse?",
|
||||
"duration": "PT98S",
|
||||
"uuid": "4294a720-f263-4ea4-9392-cf9cea4d5277",
|
||||
"tag": [
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"name": "fediverse"
|
||||
},
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"name": "framasoft"
|
||||
},
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"name": "Mastodon"
|
||||
},
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"name": "PeerTube "
|
||||
}
|
||||
],
|
||||
"category": {
|
||||
"identifier": "15",
|
||||
"name": "Science & Technology"
|
||||
},
|
||||
"licence": {
|
||||
"identifier": "2",
|
||||
"name": "Attribution - Share Alike"
|
||||
},
|
||||
"language": {
|
||||
"identifier": "en",
|
||||
"name": "English"
|
||||
},
|
||||
"views": 4805,
|
||||
"sensitive": false,
|
||||
"waitTranscoding": true,
|
||||
"isLiveBroadcast": false,
|
||||
"liveSaveReplay": null,
|
||||
"permanentLive": null,
|
||||
"state": 1,
|
||||
"commentsEnabled": true,
|
||||
"downloadEnabled": true,
|
||||
"published": "2022-04-28T11:51:16.293Z",
|
||||
"originallyPublishedAt": null,
|
||||
"updated": "2022-05-03T11:39:02.489Z",
|
||||
"mediaType": "text/markdown",
|
||||
"content": "Help us translate the subtitles [on our translation tool](https://weblate.framasoft.org/projects/what-is-the-fediverse-video/subtitles/).\r\n\r\n**Animation Produced by** [LILA](https://libreart.info/) - [ZeMarmot Team](https://film.zemarmot.net/)\r\n**Direction & Animation** by Aryeom\r\n**Script & Technology** by Jehan\r\n**Voice by** Paul Peterson\r\n**Licence**: [CC-By-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)\r\n\r\n**Sponsored by** [Framasoft](https://framasoft.org/)\r\n\r\n**Sound by** ORL - [AMMD](https://ammd.net/)\r\n\r\n**Music**: \"Dolling\" by CyberSDF - [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/)",
|
||||
"support": null,
|
||||
"subtitleLanguage": [
|
||||
{
|
||||
"identifier": "ca",
|
||||
"name": "Catalan",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/6f8aedd2-c61b-47f6-a2c9-75b15af24d14-ca.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "en",
|
||||
"name": "English",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/2f199e59-5cf8-4529-a033-9d6dd4a858ca-en.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "es",
|
||||
"name": "Spanish",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/3f74c16b-925f-45e1-8388-e358428c2436-es.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "eu",
|
||||
"name": "Basque",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/c4c88e7e-b9d4-4192-bcf2-caf025ddc9fd-eu.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "fr",
|
||||
"name": "French",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/c18906e3-6257-43e7-90e4-fa2c8ded258b-fr.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "hu",
|
||||
"name": "Hungarian",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/0a8a295d-a288-404b-b7b3-a2272bc2a6fb-hu.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "it",
|
||||
"name": "Italian",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/cf857bd9-8b04-4018-af9a-23fa1ff7662d-it.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "nb",
|
||||
"name": "Norwegian Bokmål",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/12e3a0e9-a29e-4b06-8538-91bed2a11242-nb.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "oc",
|
||||
"name": "Occitan",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/d841af30-97bf-4a0c-b1f9-e163ba77f23f-oc.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "sh",
|
||||
"name": "Serbo-Croatian",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/7afe4dae-745f-4769-9f17-9c3a079235cf-sh.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "tr",
|
||||
"name": "Turkish",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/1b2ea189-760c-4a3e-98d3-16f596c151f0-tr.vtt"
|
||||
},
|
||||
{
|
||||
"identifier": "vi",
|
||||
"name": "Vietnamese",
|
||||
"url": "https://framatube.org/lazy-static/video-captions/552b4086-54ab-4eb3-a8b3-7611a2175e77-vi.vtt"
|
||||
}
|
||||
],
|
||||
"icon": [
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "https://framatube.org/static/thumbnails/1f9eb76e-c089-4bdd-af14-602935a6db72.jpg",
|
||||
"mediaType": "image/jpeg",
|
||||
"width": 280,
|
||||
"height": 157
|
||||
},
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "https://framatube.org/lazy-static/previews/8f89d4d8-696f-4512-9a1a-72f1d12caede.jpg",
|
||||
"mediaType": "image/jpeg",
|
||||
"width": 850,
|
||||
"height": 480
|
||||
}
|
||||
],
|
||||
"url": [
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "text/html",
|
||||
"href": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-mpegURL",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/adc259cb-06f7-496c-8a50-599e58358b29-master.m3u8",
|
||||
"tag": [
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "caf7178ddd2013e28c9fbcbb7be28df25d03a023"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "cc18bb140f51f64090ba41c951fba85705cafa38"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "595513d823a1aecc18abacac94a1ebb0c31ec009"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "6ae0ce749a57d0f8ff70286878ea7661f85eebf7"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "4eb799f42d461929ed8dd4befae274c9a4404b99"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "b48d1ea795657668783544fd1c9baf637198a323"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"name": "sha256",
|
||||
"mediaType": "application/json",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/b414eda3-c8af-4271-8dde-253db28aacd1-segments-sha256.json"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/64147344-1957-480d-9106-59dd7bbf5661-1080-fragmented.mp4",
|
||||
"height": 1080,
|
||||
"size": 14653991,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421492",
|
||||
"height": 1080,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://framatube.org/lazy-static/torrents/83fa27e3-aba7-4e01-9e66-931086374176-1080-hls.torrent",
|
||||
"height": 1080
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2F83fa27e3-aba7-4e01-9e66-931086374176-1080-hls.torrent&xt=urn:btih:5651916e4301c812412f51381c5af0c1f627bfcb&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F64147344-1957-480d-9106-59dd7bbf5661-1080-fragmented.mp4",
|
||||
"height": 1080
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/0efaeae5-7468-4c45-ade5-d3b6c732621f-720-fragmented.mp4",
|
||||
"height": 720,
|
||||
"size": 9939723,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421496",
|
||||
"height": 720,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://framatube.org/lazy-static/torrents/b325c824-c052-46e2-9b46-887595055521-720-hls.torrent",
|
||||
"height": 720
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2Fb325c824-c052-46e2-9b46-887595055521-720-hls.torrent&xt=urn:btih:b5a1db245fe156edab7f1981693178dcd47075d2&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F0efaeae5-7468-4c45-ade5-d3b6c732621f-720-fragmented.mp4",
|
||||
"height": 720
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/201f9772-4971-4bc3-8356-9b85b405ae5d-480-fragmented.mp4",
|
||||
"height": 480,
|
||||
"size": 7398758,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421494",
|
||||
"height": 480,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://framatube.org/lazy-static/torrents/bd99f84e-e9bc-4d36-bea6-6f06000f87c5-480-hls.torrent",
|
||||
"height": 480
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2Fbd99f84e-e9bc-4d36-bea6-6f06000f87c5-480-hls.torrent&xt=urn:btih:6cbe09b50cf7788923a2ec4852a3b2bfd1cd1907&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F201f9772-4971-4bc3-8356-9b85b405ae5d-480-fragmented.mp4",
|
||||
"height": 480
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/b2313ae6-da36-4fe3-bec5-aa352824a38a-360-fragmented.mp4",
|
||||
"height": 360,
|
||||
"size": 6133890,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421495",
|
||||
"height": 360,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://framatube.org/lazy-static/torrents/b939430a-fdfd-4da7-a030-759ecafa6ac7-360-hls.torrent",
|
||||
"height": 360
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2Fb939430a-fdfd-4da7-a030-759ecafa6ac7-360-hls.torrent&xt=urn:btih:16693f14ad9e53fc41d335e3fa409c2f943d7b68&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2Fb2313ae6-da36-4fe3-bec5-aa352824a38a-360-fragmented.mp4",
|
||||
"height": 360
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/06a866f2-0527-4d68-93b7-c656d7374e86-240-fragmented.mp4",
|
||||
"height": 240,
|
||||
"size": 4861464,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421497",
|
||||
"height": 240,
|
||||
"fps": 24
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://framatube.org/lazy-static/torrents/072001ee-18ad-4859-af10-9d7bf12d640c-240-hls.torrent",
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2F072001ee-18ad-4859-af10-9d7bf12d640c-240-hls.torrent&xt=urn:btih:b823f54d8cd73f9d7a55266ce683f43bf772d26a&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2F06a866f2-0527-4d68-93b7-c656d7374e86-240-fragmented.mp4",
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://framatube.org/static/streaming-playlists/hls/4294a720-f263-4ea4-9392-cf9cea4d5277/f8a1caed-057f-4700-a28e-004efc158b15-0-fragmented.mp4",
|
||||
"height": 0,
|
||||
"size": 3141179,
|
||||
"fps": 0
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://framatube.org/api/v1/videos/4294a720-f263-4ea4-9392-cf9cea4d5277/metadata/1421493",
|
||||
"height": 0,
|
||||
"fps": 0
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://framatube.org/lazy-static/torrents/77cb6940-7e90-48d1-a391-bfa463b9600c-0-hls.torrent",
|
||||
"height": 0
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fframatube.org%2Flazy-static%2Ftorrents%2F77cb6940-7e90-48d1-a391-bfa463b9600c-0-hls.torrent&xt=urn:btih:9bc7717ed01869507041e31a7e65baffa78ba651&dn=What+is+the+Fediverse%3F&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F4294a720-f263-4ea4-9392-cf9cea4d5277%2Ff8a1caed-057f-4700-a28e-004efc158b15-0-fragmented.mp4",
|
||||
"height": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"name": "tracker-http",
|
||||
"rel": ["tracker", "http"],
|
||||
"href": "https://framatube.org/tracker/announce"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"name": "tracker-websocket",
|
||||
"rel": ["tracker", "websocket"],
|
||||
"href": "wss://framatube.org:443/tracker/socket"
|
||||
}
|
||||
],
|
||||
"likes": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/likes",
|
||||
"dislikes": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/dislikes",
|
||||
"shares": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/announces",
|
||||
"comments": "https://framatube.org/videos/watch/4294a720-f263-4ea4-9392-cf9cea4d5277/comments",
|
||||
"attributedTo": [
|
||||
{
|
||||
"type": "Person",
|
||||
"id": "https://framatube.org/accounts/framasoft"
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"id": "https://framatube.org/video-channels/joinpeertube"
|
||||
}
|
||||
],
|
||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc": ["https://framatube.org/accounts/framasoft/followers"],
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
|
@ -375,7 +7,7 @@
|
|||
},
|
||||
{
|
||||
"pt": "https://joinpeertube.org/ns#",
|
||||
"sc": "http://schema.org#",
|
||||
"sc": "http://schema.org/",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"uuid": "sc:identifier",
|
||||
"category": "sc:category",
|
||||
|
@ -383,6 +15,7 @@
|
|||
"subtitleLanguage": "sc:subtitleLanguage",
|
||||
"sensitive": "as:sensitive",
|
||||
"language": "sc:inLanguage",
|
||||
"identifier": "sc:identifier",
|
||||
"isLiveBroadcast": "sc:isLiveBroadcast",
|
||||
"liveSaveReplay": {
|
||||
"@type": "sc:Boolean",
|
||||
|
@ -392,10 +25,26 @@
|
|||
"@type": "sc:Boolean",
|
||||
"@id": "pt:permanentLive"
|
||||
},
|
||||
"latencyMode": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:latencyMode"
|
||||
},
|
||||
"Infohash": "pt:Infohash",
|
||||
"Playlist": "pt:Playlist",
|
||||
"PlaylistElement": "pt:PlaylistElement",
|
||||
"tileWidth": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:tileWidth"
|
||||
},
|
||||
"tileHeight": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:tileHeight"
|
||||
},
|
||||
"tileDuration": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:tileDuration"
|
||||
},
|
||||
"originallyPublishedAt": "sc:datePublished",
|
||||
"uploadDate": "sc:uploadDate",
|
||||
"hasParts": "sc:hasParts",
|
||||
"views": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:views"
|
||||
|
@ -412,18 +61,6 @@
|
|||
"@type": "sc:Number",
|
||||
"@id": "pt:fps"
|
||||
},
|
||||
"startTimestamp": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:startTimestamp"
|
||||
},
|
||||
"stopTimestamp": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:stopTimestamp"
|
||||
},
|
||||
"position": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:position"
|
||||
},
|
||||
"commentsEnabled": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:commentsEnabled"
|
||||
|
@ -448,10 +85,6 @@
|
|||
"@id": "as:dislikes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
},
|
||||
"shares": {
|
||||
"@id": "as:shares",
|
||||
"@type": "@id"
|
||||
|
@ -461,5 +94,348 @@
|
|||
"@type": "@id"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc": ["https://peertube.stream/accounts/createurs/followers"],
|
||||
"type": "Video",
|
||||
"id": "https://peertube.stream/videos/watch/46cc7342-fdd5-4583-ae16-2eeb340d3b60",
|
||||
"name": "VU du 12/12/23 : Démission \"refrusée\"",
|
||||
"duration": "PT383S",
|
||||
"uuid": "46cc7342-fdd5-4583-ae16-2eeb340d3b60",
|
||||
"category": {
|
||||
"identifier": "11",
|
||||
"name": "News & Politics"
|
||||
},
|
||||
"views": 83,
|
||||
"sensitive": false,
|
||||
"waitTranscoding": true,
|
||||
"state": 1,
|
||||
"commentsEnabled": true,
|
||||
"downloadEnabled": true,
|
||||
"published": "2023-12-12T17:02:02.188Z",
|
||||
"originallyPublishedAt": "2023-12-11T23:00:00.000Z",
|
||||
"updated": "2023-12-14T06:40:34.279Z",
|
||||
"tag": [
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"name": "France3"
|
||||
},
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"name": "lezapping"
|
||||
}
|
||||
],
|
||||
"mediaType": "text/markdown",
|
||||
"content": "Un regard impertinent et libre, orchestré par Patrick Menais et son équipe, sur le monde de l’image.\n\nEn avant-première du lundi au samedi à17h00 sur Facebook, Twitter et YouTube.\n\nDu lundi au samedi à 20h00 sur France 5.\n\nhttps://www.facebook.com/vufrancetv\nhttps://twitter.com/VuFrancetv",
|
||||
"support": null,
|
||||
"subtitleLanguage": [],
|
||||
"icon": [
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "https://peertube.stream/lazy-static/thumbnails/208d2248-6fa3-4a58-a2e6-c6f176559457.jpg",
|
||||
"mediaType": "image/jpeg",
|
||||
"width": 280,
|
||||
"height": 157
|
||||
},
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "https://peertube.stream/lazy-static/previews/73d34e91-0233-443b-a1c3-d98a7ec6a87c.jpg",
|
||||
"mediaType": "image/jpeg",
|
||||
"width": 850,
|
||||
"height": 480
|
||||
}
|
||||
],
|
||||
"preview": [
|
||||
{
|
||||
"type": "Image",
|
||||
"rel": ["storyboard"],
|
||||
"url": [
|
||||
{
|
||||
"mediaType": "image/jpeg",
|
||||
"href": "https://peertube.stream/lazy-static/storyboards/fb103d5f-8f76-4c8b-bc81-f952961cacfd.jpg",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"tileWidth": 192,
|
||||
"tileHeight": 108,
|
||||
"tileDuration": "PT4S"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"url": [
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "text/html",
|
||||
"href": "https://peertube.stream/videos/watch/46cc7342-fdd5-4583-ae16-2eeb340d3b60"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-mpegURL",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/7847c00b-17f0-4cd9-b788-94283bd96d5b-master.m3u8",
|
||||
"tag": [
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "f50d9a3e851756a1fc1da7fe8b6e40f849c1f3a1"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "fdddadfcf01c52808a5716ac9c0f09e379a1ca69"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "c309597f071c6ab59e1a6935be3dc1ceb58c9250"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "5c28ed3e05102a678dc047a126650fe53d45ded4"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "085f2c72c69af02913177534ec601349ca2b4f01"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "37b9dbeab6f433e94f80a614f888e9a1e9ee3534"
|
||||
},
|
||||
{
|
||||
"type": "Infohash",
|
||||
"name": "cc15513891e63a92743730ba65ab256f8825f071"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"name": "sha256",
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/a3f5af94-ba6b-4349-a4b0-151cebdf9af6-segments-sha256.json"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/5a3db28f-a4b2-49ae-963e-7fd9414efe7c-1080-fragmented.mp4",
|
||||
"height": 1080,
|
||||
"size": 90186372,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/api/v1/videos/46cc7342-fdd5-4583-ae16-2eeb340d3b60/metadata/1570438",
|
||||
"height": 1080,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://peertube.stream/lazy-static/torrents/c3dd78f2-ff9b-41f1-899d-55440f512e09-1080-hls.torrent",
|
||||
"height": 1080
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.stream%2Flazy-static%2Ftorrents%2Fc3dd78f2-ff9b-41f1-899d-55440f512e09-1080-hls.torrent&xt=urn:btih:944323d8a38e077cdea5c1b1aa82300d1f49076a&dn=VU+du+12%2F12%2F23+%3A+D%C3%A9mission+%22refrus%C3%A9e%22&tr=https%3A%2F%2Fpeertube.stream%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube.stream%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube.stream%2Fstatic%2Fstreaming-playlists%2Fhls%2F46cc7342-fdd5-4583-ae16-2eeb340d3b60%2F5a3db28f-a4b2-49ae-963e-7fd9414efe7c-1080-fragmented.mp4",
|
||||
"height": 1080
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/557f45f0-60b7-418c-bddd-e55701b387bb-720-fragmented.mp4",
|
||||
"height": 720,
|
||||
"size": 50950797,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/api/v1/videos/46cc7342-fdd5-4583-ae16-2eeb340d3b60/metadata/1570447",
|
||||
"height": 720,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://peertube.stream/lazy-static/torrents/0529c736-0c49-4efd-a9ff-c4989b4c2071-720-hls.torrent",
|
||||
"height": 720
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.stream%2Flazy-static%2Ftorrents%2F0529c736-0c49-4efd-a9ff-c4989b4c2071-720-hls.torrent&xt=urn:btih:a2662d0714edf3882193f782814441eb904460be&dn=VU+du+12%2F12%2F23+%3A+D%C3%A9mission+%22refrus%C3%A9e%22&tr=https%3A%2F%2Fpeertube.stream%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube.stream%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube.stream%2Fstatic%2Fstreaming-playlists%2Fhls%2F46cc7342-fdd5-4583-ae16-2eeb340d3b60%2F557f45f0-60b7-418c-bddd-e55701b387bb-720-fragmented.mp4",
|
||||
"height": 720
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/097e6338-4c6e-4c21-8fed-7df0a245c9b3-480-fragmented.mp4",
|
||||
"height": 480,
|
||||
"size": 31542462,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/api/v1/videos/46cc7342-fdd5-4583-ae16-2eeb340d3b60/metadata/1570441",
|
||||
"height": 480,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://peertube.stream/lazy-static/torrents/56b47f85-b2de-44b1-9089-db13c8534e1c-480-hls.torrent",
|
||||
"height": 480
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.stream%2Flazy-static%2Ftorrents%2F56b47f85-b2de-44b1-9089-db13c8534e1c-480-hls.torrent&xt=urn:btih:9d1cc84a448ba531d2f5422a8910fd79580768ff&dn=VU+du+12%2F12%2F23+%3A+D%C3%A9mission+%22refrus%C3%A9e%22&tr=https%3A%2F%2Fpeertube.stream%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube.stream%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube.stream%2Fstatic%2Fstreaming-playlists%2Fhls%2F46cc7342-fdd5-4583-ae16-2eeb340d3b60%2F097e6338-4c6e-4c21-8fed-7df0a245c9b3-480-fragmented.mp4",
|
||||
"height": 480
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/b6db1f0c-0b6f-4f26-b811-d38631f4c42b-360-fragmented.mp4",
|
||||
"height": 360,
|
||||
"size": 23389554,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/api/v1/videos/46cc7342-fdd5-4583-ae16-2eeb340d3b60/metadata/1570442",
|
||||
"height": 360,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://peertube.stream/lazy-static/torrents/89df203a-586e-4d09-b645-21c321ae81c2-360-hls.torrent",
|
||||
"height": 360
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.stream%2Flazy-static%2Ftorrents%2F89df203a-586e-4d09-b645-21c321ae81c2-360-hls.torrent&xt=urn:btih:40dbe1b6fb96d87d0750b32b26fd52913f22c84e&dn=VU+du+12%2F12%2F23+%3A+D%C3%A9mission+%22refrus%C3%A9e%22&tr=https%3A%2F%2Fpeertube.stream%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube.stream%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube.stream%2Fstatic%2Fstreaming-playlists%2Fhls%2F46cc7342-fdd5-4583-ae16-2eeb340d3b60%2Fb6db1f0c-0b6f-4f26-b811-d38631f4c42b-360-fragmented.mp4",
|
||||
"height": 360
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/d0d23e04-a7b2-47f9-8072-94a06dc0c402-240-fragmented.mp4",
|
||||
"height": 240,
|
||||
"size": 16040535,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/api/v1/videos/46cc7342-fdd5-4583-ae16-2eeb340d3b60/metadata/1570448",
|
||||
"height": 240,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://peertube.stream/lazy-static/torrents/29c43d5c-b26f-404c-a286-7aff2e2bb139-240-hls.torrent",
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.stream%2Flazy-static%2Ftorrents%2F29c43d5c-b26f-404c-a286-7aff2e2bb139-240-hls.torrent&xt=urn:btih:f3f102c22d48b8a0aec19be463d8f04fb3a3f499&dn=VU+du+12%2F12%2F23+%3A+D%C3%A9mission+%22refrus%C3%A9e%22&tr=https%3A%2F%2Fpeertube.stream%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube.stream%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube.stream%2Fstatic%2Fstreaming-playlists%2Fhls%2F46cc7342-fdd5-4583-ae16-2eeb340d3b60%2Fd0d23e04-a7b2-47f9-8072-94a06dc0c402-240-fragmented.mp4",
|
||||
"height": 240
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/6f3b1939-67c4-45f0-bd93-2508721dda69-144-fragmented.mp4",
|
||||
"height": 144,
|
||||
"size": 10969421,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/api/v1/videos/46cc7342-fdd5-4583-ae16-2eeb340d3b60/metadata/1570449",
|
||||
"height": 144,
|
||||
"fps": 25
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://peertube.stream/lazy-static/torrents/e39095d9-8fa2-4543-a66f-b4b9d6165a4e-144-hls.torrent",
|
||||
"height": 144
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.stream%2Flazy-static%2Ftorrents%2Fe39095d9-8fa2-4543-a66f-b4b9d6165a4e-144-hls.torrent&xt=urn:btih:8b263d7e814d611597a36dcd9655d959c86605a4&dn=VU+du+12%2F12%2F23+%3A+D%C3%A9mission+%22refrus%C3%A9e%22&tr=https%3A%2F%2Fpeertube.stream%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube.stream%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube.stream%2Fstatic%2Fstreaming-playlists%2Fhls%2F46cc7342-fdd5-4583-ae16-2eeb340d3b60%2F6f3b1939-67c4-45f0-bd93-2508721dda69-144-fragmented.mp4",
|
||||
"height": 144
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "video/mp4",
|
||||
"href": "https://peertube.stream/static/streaming-playlists/hls/46cc7342-fdd5-4583-ae16-2eeb340d3b60/86ab6cca-46e5-4c6e-9c2c-8aef803b85f2-0-fragmented.mp4",
|
||||
"height": 0,
|
||||
"size": 6074306,
|
||||
"fps": 0
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"rel": ["metadata", "video/mp4"],
|
||||
"mediaType": "application/json",
|
||||
"href": "https://peertube.stream/api/v1/videos/46cc7342-fdd5-4583-ae16-2eeb340d3b60/metadata/1570439",
|
||||
"height": 0,
|
||||
"fps": 0
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"href": "https://peertube.stream/lazy-static/torrents/25ae194d-c3ec-412a-886f-3b0d02599ca7-0-hls.torrent",
|
||||
"height": 0
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.stream%2Flazy-static%2Ftorrents%2F25ae194d-c3ec-412a-886f-3b0d02599ca7-0-hls.torrent&xt=urn:btih:e4458f2445732a228e9a83e2ae53a103f5e1097e&dn=VU+du+12%2F12%2F23+%3A+D%C3%A9mission+%22refrus%C3%A9e%22&tr=https%3A%2F%2Fpeertube.stream%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube.stream%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube.stream%2Fstatic%2Fstreaming-playlists%2Fhls%2F46cc7342-fdd5-4583-ae16-2eeb340d3b60%2F86ab6cca-46e5-4c6e-9c2c-8aef803b85f2-0-fragmented.mp4",
|
||||
"height": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"name": "tracker-http",
|
||||
"rel": ["tracker", "http"],
|
||||
"href": "https://peertube.stream/tracker/announce"
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"name": "tracker-websocket",
|
||||
"rel": ["tracker", "websocket"],
|
||||
"href": "wss://peertube.stream:443/tracker/socket"
|
||||
}
|
||||
],
|
||||
"likes": "https://peertube.stream/videos/watch/46cc7342-fdd5-4583-ae16-2eeb340d3b60/likes",
|
||||
"dislikes": "https://peertube.stream/videos/watch/46cc7342-fdd5-4583-ae16-2eeb340d3b60/dislikes",
|
||||
"shares": "https://peertube.stream/videos/watch/46cc7342-fdd5-4583-ae16-2eeb340d3b60/announces",
|
||||
"comments": "https://peertube.stream/videos/watch/46cc7342-fdd5-4583-ae16-2eeb340d3b60/comments",
|
||||
"hasParts": "https://peertube.stream/videos/watch/46cc7342-fdd5-4583-ae16-2eeb340d3b60/chapters",
|
||||
"attributedTo": [
|
||||
{
|
||||
"type": "Person",
|
||||
"id": "https://peertube.stream/accounts/createurs"
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"id": "https://peertube.stream/video-channels/vu"
|
||||
}
|
||||
],
|
||||
"isLiveBroadcast": false,
|
||||
"liveSaveReplay": null,
|
||||
"permanentLive": null,
|
||||
"latencyMode": null,
|
||||
"peertubeLiveChat": false
|
||||
}
|
||||
|
|
|
@ -225,11 +225,12 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_outgoing_activities(context: Data<LemmyContext>) -> LemmyResult<()> {
|
||||
pub async fn handle_outgoing_activities(context: Data<LemmyContext>) {
|
||||
while let Some(data) = ActivityChannel::retrieve_activity().await {
|
||||
match_outgoing_activities(data, &context.reset_request_count()).await?
|
||||
if let Err(e) = match_outgoing_activities(data, &context.reset_request_count()).await {
|
||||
tracing::warn!("error while saving outgoing activity to db: {e}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn match_outgoing_activities(
|
||||
|
|
|
@ -65,7 +65,6 @@ pub async fn read_person(
|
|||
saved_only,
|
||||
local_user: local_user_view.as_ref(),
|
||||
community_id,
|
||||
is_profile_view: true,
|
||||
page,
|
||||
limit,
|
||||
creator_id,
|
||||
|
@ -79,7 +78,6 @@ pub async fn read_person(
|
|||
sort: sort.map(post_to_comment_sort_type),
|
||||
saved_only,
|
||||
community_id,
|
||||
is_profile_view: true,
|
||||
page,
|
||||
limit,
|
||||
creator_id,
|
||||
|
|
|
@ -96,6 +96,13 @@ pub(crate) async fn get_activity(
|
|||
if sensitive {
|
||||
Ok(HttpResponse::Forbidden().finish())
|
||||
} else {
|
||||
create_apub_response(&activity.data)
|
||||
// Don't use create_apub_response() to avoid duplicate context (the activity stored in db
|
||||
// already includes context).
|
||||
let json = serde_json::to_string_pretty(&activity.data)?;
|
||||
Ok(
|
||||
HttpResponse::Ok()
|
||||
.content_type(FEDERATION_CONTENT_TYPE)
|
||||
.body(json),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ pub struct Group {
|
|||
pub(crate) summary: Option<String>,
|
||||
#[serde(deserialize_with = "deserialize_skip_error", default)]
|
||||
pub(crate) source: Option<Source>,
|
||||
#[serde(deserialize_with = "deserialize_skip_error", default)]
|
||||
pub(crate) icon: Option<ImageObject>,
|
||||
/// banner
|
||||
pub(crate) image: Option<ImageObject>,
|
||||
|
|
|
@ -38,6 +38,7 @@ pub struct Person {
|
|||
#[serde(deserialize_with = "deserialize_skip_error", default)]
|
||||
pub(crate) source: Option<Source>,
|
||||
/// user avatar
|
||||
#[serde(deserialize_with = "deserialize_skip_error", default)]
|
||||
pub(crate) icon: Option<ImageObject>,
|
||||
/// user banner
|
||||
pub(crate) image: Option<ImageObject>,
|
||||
|
|
|
@ -67,7 +67,7 @@ once_cell = { workspace = true, optional = true }
|
|||
diesel_ltree = { workspace = true, optional = true }
|
||||
async-trait = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
deadpool = { version = "0.9.5", features = ["rt_tokio_1"], optional = true }
|
||||
deadpool = { version = "0.10.0", features = ["rt_tokio_1"], optional = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
futures-util = { workspace = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
|
|
|
@ -59,52 +59,55 @@ impl Comment {
|
|||
) -> Result<Comment, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// Insert, to get the id
|
||||
let inserted_comment = insert_into(comment)
|
||||
.values(comment_form)
|
||||
.on_conflict(ap_id)
|
||||
.do_update()
|
||||
.set(comment_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await;
|
||||
conn
|
||||
.build_transaction()
|
||||
.run(|conn| {
|
||||
Box::pin(async move {
|
||||
// Insert, to get the id
|
||||
let inserted_comment = insert_into(comment)
|
||||
.values(comment_form)
|
||||
.on_conflict(ap_id)
|
||||
.do_update()
|
||||
.set(comment_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await?;
|
||||
|
||||
if let Ok(comment_insert) = inserted_comment {
|
||||
let comment_id = comment_insert.id;
|
||||
let comment_id = inserted_comment.id;
|
||||
|
||||
// You need to update the ltree column
|
||||
let ltree = Ltree(if let Some(parent_path) = parent_path {
|
||||
// The previous parent will already have 0 in it
|
||||
// Append this comment id
|
||||
format!("{}.{}", parent_path.0, comment_id)
|
||||
} else {
|
||||
// '0' is always the first path, append to that
|
||||
format!("{}.{}", 0, comment_id)
|
||||
});
|
||||
// You need to update the ltree column
|
||||
let ltree = Ltree(if let Some(parent_path) = parent_path {
|
||||
// The previous parent will already have 0 in it
|
||||
// Append this comment id
|
||||
format!("{}.{}", parent_path.0, comment_id)
|
||||
} else {
|
||||
// '0' is always the first path, append to that
|
||||
format!("{}.{}", 0, comment_id)
|
||||
});
|
||||
|
||||
let updated_comment = diesel::update(comment.find(comment_id))
|
||||
.set(path.eq(ltree))
|
||||
.get_result::<Self>(conn)
|
||||
.await;
|
||||
let updated_comment = diesel::update(comment.find(comment_id))
|
||||
.set(path.eq(ltree))
|
||||
.get_result::<Self>(conn)
|
||||
.await?;
|
||||
|
||||
// Update the child count for the parent comment_aggregates
|
||||
// You could do this with a trigger, but since you have to do this manually anyway,
|
||||
// you can just have it here
|
||||
if let Some(parent_path) = parent_path {
|
||||
// You have to update counts for all parents, not just the immediate one
|
||||
// TODO if the performance of this is terrible, it might be better to do this as part of a
|
||||
// scheduled query... although the counts would often be wrong.
|
||||
//
|
||||
// The child_count query for reference:
|
||||
// select c.id, c.path, count(c2.id) as child_count from comment c
|
||||
// left join comment c2 on c2.path <@ c.path and c2.path != c.path
|
||||
// group by c.id
|
||||
// Update the child count for the parent comment_aggregates
|
||||
// You could do this with a trigger, but since you have to do this manually anyway,
|
||||
// you can just have it here
|
||||
if let Some(parent_path) = parent_path {
|
||||
// You have to update counts for all parents, not just the immediate one
|
||||
// TODO if the performance of this is terrible, it might be better to do this as part of a
|
||||
// scheduled query... although the counts would often be wrong.
|
||||
//
|
||||
// The child_count query for reference:
|
||||
// select c.id, c.path, count(c2.id) as child_count from comment c
|
||||
// left join comment c2 on c2.path <@ c.path and c2.path != c.path
|
||||
// group by c.id
|
||||
|
||||
let parent_id = parent_path.0.split('.').nth(1);
|
||||
let parent_id = parent_path.0.split('.').nth(1);
|
||||
|
||||
if let Some(parent_id) = parent_id {
|
||||
let top_parent = format!("0.{}", parent_id);
|
||||
let update_child_count_stmt = format!(
|
||||
"
|
||||
if let Some(parent_id) = parent_id {
|
||||
let top_parent = format!("0.{}", parent_id);
|
||||
let update_child_count_stmt = format!(
|
||||
"
|
||||
update comment_aggregates ca set child_count = c.child_count
|
||||
from (
|
||||
select c.id, c.path, count(c2.id) as child_count from comment c
|
||||
|
@ -113,15 +116,15 @@ from (
|
|||
group by c.id
|
||||
) as c
|
||||
where ca.comment_id = c.id"
|
||||
);
|
||||
);
|
||||
|
||||
sql_query(update_child_count_stmt).execute(conn).await?;
|
||||
}
|
||||
}
|
||||
updated_comment
|
||||
} else {
|
||||
inserted_comment
|
||||
}
|
||||
sql_query(update_child_count_stmt).execute(conn).await?;
|
||||
}
|
||||
}
|
||||
Ok(updated_comment)
|
||||
}) as _
|
||||
})
|
||||
.await
|
||||
}
|
||||
pub async fn read_from_apub_id(
|
||||
pool: &mut DbPool<'_>,
|
||||
|
|
|
@ -13,12 +13,17 @@ use crate::{
|
|||
federation_queue_state::FederationQueueState,
|
||||
instance::{Instance, InstanceForm},
|
||||
},
|
||||
utils::{functions::lower, get_conn, naive_now, now, DbPool},
|
||||
utils::{
|
||||
functions::{coalesce, lower},
|
||||
get_conn,
|
||||
naive_now,
|
||||
now,
|
||||
DbPool,
|
||||
},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{count_star, insert_into},
|
||||
result::Error,
|
||||
sql_types::{Nullable, Timestamptz},
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
|
@ -157,5 +162,3 @@ impl Instance {
|
|||
.await
|
||||
}
|
||||
}
|
||||
|
||||
sql_function! { fn coalesce(x: Nullable<Timestamptz>, y: Timestamptz) -> Timestamptz; }
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use super::instance::coalesce;
|
||||
use crate::{
|
||||
newtypes::{CommunityId, DbUrl, PersonId, PostId},
|
||||
schema::post::dsl::{
|
||||
|
@ -29,7 +28,16 @@ use crate::{
|
|||
PostUpdateForm,
|
||||
},
|
||||
traits::{Crud, Likeable, Saveable},
|
||||
utils::{get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX},
|
||||
utils::{
|
||||
functions::coalesce,
|
||||
get_conn,
|
||||
naive_now,
|
||||
DbPool,
|
||||
DELETED_REPLACEMENT_TEXT,
|
||||
FETCH_LIMIT_MAX,
|
||||
SITEMAP_DAYS,
|
||||
SITEMAP_LIMIT,
|
||||
},
|
||||
};
|
||||
use ::url::Url;
|
||||
use chrono::{Duration, Utc};
|
||||
|
@ -109,8 +117,9 @@ impl Post {
|
|||
.filter(local.eq(true))
|
||||
.filter(deleted.eq(false))
|
||||
.filter(removed.eq(false))
|
||||
.filter(published.ge(Utc::now().naive_utc() - Duration::days(1)))
|
||||
.filter(published.ge(Utc::now().naive_utc() - Duration::days(SITEMAP_DAYS)))
|
||||
.order(published.desc())
|
||||
.limit(SITEMAP_LIMIT)
|
||||
.load::<(DbUrl, chrono::DateTime<Utc>)>(conn)
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -31,7 +31,11 @@ pub mod schema;
|
|||
#[cfg(feature = "full")]
|
||||
pub mod aliases {
|
||||
use crate::schema::{community_moderator, person};
|
||||
diesel::alias!(person as person1: Person1, person as person2: Person2, community_moderator as community_moderator1: CommunityModerator1);
|
||||
diesel::alias!(
|
||||
person as person1: Person1,
|
||||
person as person2: Person2,
|
||||
community_moderator as community_moderator1: CommunityModerator1
|
||||
);
|
||||
}
|
||||
pub mod source;
|
||||
#[cfg(feature = "full")]
|
||||
|
|
|
@ -24,6 +24,7 @@ use diesel_async::{
|
|||
pooled_connection::{
|
||||
deadpool::{Object as PooledConnection, Pool},
|
||||
AsyncDieselConnectionManager,
|
||||
ManagerConfig,
|
||||
},
|
||||
};
|
||||
use diesel_migrations::EmbeddedMigrations;
|
||||
|
@ -45,7 +46,10 @@ use url::Url;
|
|||
|
||||
const FETCH_LIMIT_DEFAULT: i64 = 10;
|
||||
pub const FETCH_LIMIT_MAX: i64 = 50;
|
||||
pub const SITEMAP_LIMIT: i64 = 50000;
|
||||
pub const SITEMAP_DAYS: i64 = 31;
|
||||
const POOL_TIMEOUT: Option<Duration> = Some(Duration::from_secs(5));
|
||||
pub const RANK_DEFAULT: f64 = 0.0001;
|
||||
|
||||
pub type ActualDbPool = Pool<AsyncPgConnection>;
|
||||
|
||||
|
@ -261,7 +265,9 @@ pub async fn build_db_pool() -> Result<ActualDbPool, LemmyError> {
|
|||
let manager = if tls_enabled {
|
||||
// diesel-async does not support any TLS connections out of the box, so we need to manually
|
||||
// provide a setup function which handles creating the connection
|
||||
AsyncDieselConnectionManager::<AsyncPgConnection>::new_with_setup(&db_url, establish_connection)
|
||||
let mut config = ManagerConfig::default();
|
||||
config.custom_setup = Box::new(establish_connection);
|
||||
AsyncDieselConnectionManager::<AsyncPgConnection>::new_with_config(&db_url, config)
|
||||
} else {
|
||||
AsyncDieselConnectionManager::<AsyncPgConnection>::new(&db_url)
|
||||
};
|
||||
|
|
|
@ -41,3 +41,4 @@ actix-web = { workspace = true, optional = true }
|
|||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
|
|
|
@ -105,16 +105,18 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(post::community_id.eq(community_id));
|
||||
}
|
||||
|
||||
// If viewing all reports, order by newest, but if viewing unresolved only, show the oldest first (FIFO)
|
||||
if options.unresolved_only {
|
||||
query = query.filter(comment_report::resolved.eq(false));
|
||||
query = query
|
||||
.filter(comment_report::resolved.eq(false))
|
||||
.order_by(comment_report::published.asc());
|
||||
} else {
|
||||
query = query.order_by(comment_report::published.desc());
|
||||
}
|
||||
|
||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||
|
||||
query = query
|
||||
.order_by(comment_report::published.asc())
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
query = query.limit(limit).offset(offset);
|
||||
|
||||
// If its not an admin, get only the ones you mod
|
||||
if !user.local_user.admin {
|
||||
|
@ -230,7 +232,7 @@ mod tests {
|
|||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::{Crud, Joinable, Reportable},
|
||||
utils::build_db_pool_for_tests,
|
||||
utils::{build_db_pool_for_tests, RANK_DEFAULT},
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
|
@ -431,7 +433,7 @@ mod tests {
|
|||
downvotes: 0,
|
||||
published: agg.published,
|
||||
child_count: 0,
|
||||
hot_rank: 0.1728,
|
||||
hot_rank: RANK_DEFAULT,
|
||||
controversy_rank: 0.0,
|
||||
},
|
||||
my_vote: None,
|
||||
|
@ -475,8 +477,8 @@ mod tests {
|
|||
assert_eq!(
|
||||
reports,
|
||||
[
|
||||
expected_sara_report_view.clone(),
|
||||
expected_jessica_report_view.clone(),
|
||||
expected_sara_report_view.clone(),
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -177,14 +177,18 @@ fn queries<'a>() -> Queries<
|
|||
};
|
||||
|
||||
let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move {
|
||||
let person_id = options.local_user.map(|l| l.person.id);
|
||||
let local_user_id = options.local_user.map(|l| l.local_user.id);
|
||||
let my_person_id = options.local_user.map(|l| l.person.id);
|
||||
let my_local_user_id = options.local_user.map(|l| l.local_user.id);
|
||||
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = person_id.unwrap_or(PersonId(-1));
|
||||
let local_user_id_join = local_user_id.unwrap_or(LocalUserId(-1));
|
||||
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
|
||||
let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1));
|
||||
|
||||
let mut query = all_joins(comment::table.into_boxed(), person_id, options.saved_only);
|
||||
let mut query = all_joins(
|
||||
comment::table.into_boxed(),
|
||||
my_person_id,
|
||||
options.saved_only,
|
||||
);
|
||||
|
||||
if let Some(creator_id) = options.creator_id {
|
||||
query = query.filter(comment::creator_id.eq(creator_id));
|
||||
|
@ -373,7 +377,6 @@ pub struct CommentQuery<'a> {
|
|||
pub saved_only: bool,
|
||||
pub liked_only: bool,
|
||||
pub disliked_only: bool,
|
||||
pub is_profile_view: bool,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub max_depth: Option<i32>,
|
||||
|
@ -410,7 +413,7 @@ mod tests {
|
|||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::{Blockable, Crud, Joinable, Likeable},
|
||||
utils::build_db_pool_for_tests,
|
||||
utils::{build_db_pool_for_tests, RANK_DEFAULT},
|
||||
SubscribedType,
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
@ -1045,7 +1048,7 @@ mod tests {
|
|||
downvotes: 0,
|
||||
published: agg.published,
|
||||
child_count: 5,
|
||||
hot_rank: 0.1728,
|
||||
hot_rank: RANK_DEFAULT,
|
||||
controversy_rank: 0.0,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -5,7 +5,14 @@ use diesel_async::RunQueryDsl;
|
|||
use lemmy_db_schema::{
|
||||
newtypes::{LocalUserId, PersonId},
|
||||
schema::{local_user, person, person_aggregates},
|
||||
utils::{functions::lower, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
utils::{
|
||||
functions::{coalesce, lower},
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||
use std::future::{ready, Ready};
|
||||
|
@ -34,7 +41,9 @@ fn queries<'a>(
|
|||
let mut query = local_user::table.into_boxed();
|
||||
query = match search {
|
||||
ReadBy::Id(local_user_id) => query.filter(local_user::id.eq(local_user_id)),
|
||||
ReadBy::Email(from_email) => query.filter(local_user::email.eq(from_email)),
|
||||
ReadBy::Email(from_email) => {
|
||||
query.filter(lower(coalesce(local_user::email, "")).eq(from_email.to_lowercase()))
|
||||
}
|
||||
_ => query,
|
||||
};
|
||||
let mut query = query.inner_join(person::table);
|
||||
|
@ -43,8 +52,8 @@ fn queries<'a>(
|
|||
ReadBy::Name(name) => query.filter(lower(person::name).eq(name.to_lowercase())),
|
||||
ReadBy::NameOrEmail(name_or_email) => query.filter(
|
||||
lower(person::name)
|
||||
.eq(lower(name_or_email))
|
||||
.or(local_user::email.eq(name_or_email)),
|
||||
.eq(lower(name_or_email.to_lowercase()))
|
||||
.or(lower(coalesce(local_user::email, "")).eq(name_or_email.to_lowercase())),
|
||||
),
|
||||
_ => query,
|
||||
};
|
||||
|
|
|
@ -83,16 +83,18 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(post::community_id.eq(community_id));
|
||||
}
|
||||
|
||||
// If viewing all reports, order by newest, but if viewing unresolved only, show the oldest first (FIFO)
|
||||
if options.unresolved_only {
|
||||
query = query.filter(post_report::resolved.eq(false));
|
||||
query = query
|
||||
.filter(post_report::resolved.eq(false))
|
||||
.order_by(post_report::published.asc());
|
||||
} else {
|
||||
query = query.order_by(post_report::published.desc());
|
||||
}
|
||||
|
||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||
|
||||
query = query
|
||||
.order_by(post_report::published.asc())
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
query = query.limit(limit).offset(offset);
|
||||
|
||||
// If its not an admin, get only the ones you mod
|
||||
if !user.local_user.admin {
|
||||
|
@ -337,8 +339,8 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(reports[0].creator.id, inserted_sara.id);
|
||||
assert_eq!(reports[1].creator.id, inserted_jessica.id);
|
||||
assert_eq!(reports[1].creator.id, inserted_sara.id);
|
||||
assert_eq!(reports[0].creator.id, inserted_jessica.id);
|
||||
|
||||
// Make sure the counts are correct
|
||||
let report_count = PostReportView::get_report_count(pool, inserted_timmy.id, false, None)
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
use crate::structs::{LocalUserView, PaginationCursor, PostView};
|
||||
use diesel::{
|
||||
debug_query,
|
||||
dsl::{self, exists, not, IntervalDsl},
|
||||
expression::AsExpression,
|
||||
dsl::{exists, not, IntervalDsl},
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
sql_function,
|
||||
sql_types::{self, SingleValue, SqlType, Timestamptz},
|
||||
sql_types,
|
||||
BoolExpressionMethods,
|
||||
BoxableExpression,
|
||||
Expression,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
JoinOnDsl,
|
||||
|
@ -35,66 +32,54 @@ use lemmy_db_schema::{
|
|||
person_block,
|
||||
person_post_aggregates,
|
||||
post,
|
||||
post_aggregates::{self, newest_comment_time},
|
||||
post_aggregates,
|
||||
post_like,
|
||||
post_read,
|
||||
post_saved,
|
||||
},
|
||||
utils::{fuzzy_search, get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
utils::{
|
||||
functions::coalesce,
|
||||
fuzzy_search,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
now,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
sql_function!(fn coalesce(x: sql_types::Nullable<sql_types::BigInt>, y: sql_types::BigInt) -> sql_types::BigInt);
|
||||
|
||||
fn order_and_page_filter_desc<Q, C, T>(
|
||||
query: Q,
|
||||
column: C,
|
||||
options: &PostQuery,
|
||||
getter: impl Fn(&PostAggregates) -> T,
|
||||
) -> Q
|
||||
where
|
||||
Q: diesel::query_dsl::methods::ThenOrderDsl<dsl::Desc<C>, Output = Q>
|
||||
+ diesel::query_dsl::methods::ThenOrderDsl<dsl::Asc<C>, Output = Q>
|
||||
+ diesel::query_dsl::methods::FilterDsl<dsl::GtEq<C, T>, Output = Q>
|
||||
+ diesel::query_dsl::methods::FilterDsl<dsl::LtEq<C, T>, Output = Q>,
|
||||
C: Expression + Copy,
|
||||
C::SqlType: SingleValue + SqlType,
|
||||
T: AsExpression<C::SqlType>,
|
||||
{
|
||||
let mut query = query.then_order_by(column.desc());
|
||||
if let Some(before) = &options.page_before_or_equal {
|
||||
query = query.filter(column.ge(getter(&before.0)));
|
||||
}
|
||||
if let Some(after) = &options.page_after {
|
||||
query = query.filter(column.le(getter(&after.0)));
|
||||
}
|
||||
query
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Ord {
|
||||
Desc,
|
||||
Asc,
|
||||
}
|
||||
|
||||
fn order_and_page_filter_asc<Q, C, T>(
|
||||
query: Q,
|
||||
column: C,
|
||||
options: &PostQuery,
|
||||
getter: impl Fn(&PostAggregates) -> T,
|
||||
) -> Q
|
||||
where
|
||||
Q: diesel::query_dsl::methods::ThenOrderDsl<dsl::Asc<C>, Output = Q>
|
||||
+ diesel::query_dsl::methods::FilterDsl<dsl::LtEq<C, T>, Output = Q>
|
||||
+ diesel::query_dsl::methods::FilterDsl<dsl::GtEq<C, T>, Output = Q>,
|
||||
C: Expression + Copy,
|
||||
C::SqlType: SingleValue + SqlType,
|
||||
T: AsExpression<C::SqlType>,
|
||||
{
|
||||
let mut query = query.then_order_by(column.asc());
|
||||
if let Some(before) = &options.page_before_or_equal {
|
||||
query = query.filter(column.le(getter(&before.0)));
|
||||
}
|
||||
if let Some(after) = &options.page_after {
|
||||
query = query.filter(column.ge(getter(&after.0)));
|
||||
}
|
||||
query
|
||||
struct PaginationCursorField<Q, QS> {
|
||||
then_order_by_desc: fn(Q) -> Q,
|
||||
then_order_by_asc: fn(Q) -> Q,
|
||||
le: fn(&PostAggregates) -> Box<dyn BoxableExpression<QS, Pg, SqlType = sql_types::Bool>>,
|
||||
ge: fn(&PostAggregates) -> Box<dyn BoxableExpression<QS, Pg, SqlType = sql_types::Bool>>,
|
||||
ne: fn(&PostAggregates) -> Box<dyn BoxableExpression<QS, Pg, SqlType = sql_types::Bool>>,
|
||||
}
|
||||
|
||||
/// Returns `PaginationCursorField<_, _>` for the given name
|
||||
macro_rules! field {
|
||||
($name:ident) => {
|
||||
// Type inference doesn't work if normal method call syntax is used
|
||||
PaginationCursorField {
|
||||
then_order_by_desc: |query| QueryDsl::then_order_by(query, post_aggregates::$name.desc()),
|
||||
then_order_by_asc: |query| QueryDsl::then_order_by(query, post_aggregates::$name.asc()),
|
||||
le: |e| Box::new(post_aggregates::$name.le(e.$name)),
|
||||
ge: |e| Box::new(post_aggregates::$name.ge(e.$name)),
|
||||
ne: |e| Box::new(post_aggregates::$name.ne(e.$name)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn queries<'a>() -> Queries<
|
||||
|
@ -274,8 +259,16 @@ fn queries<'a>() -> Queries<
|
|||
// Hide deleted and removed for non-admins or mods
|
||||
if !is_mod_or_admin {
|
||||
query = query
|
||||
.filter(community::removed.eq(false))
|
||||
.filter(post::removed.eq(false))
|
||||
.filter(
|
||||
community::removed
|
||||
.eq(false)
|
||||
.or(post::creator_id.eq(person_id_join)),
|
||||
)
|
||||
.filter(
|
||||
post::removed
|
||||
.eq(false)
|
||||
.or(post::creator_id.eq(person_id_join)),
|
||||
)
|
||||
// users can see their own deleted posts
|
||||
.filter(
|
||||
community::deleted
|
||||
|
@ -293,16 +286,16 @@ fn queries<'a>() -> Queries<
|
|||
};
|
||||
|
||||
let list = move |mut conn: DbConn<'a>, options: PostQuery<'a>| async move {
|
||||
let person_id = options.local_user.map(|l| l.person.id);
|
||||
let local_user_id = options.local_user.map(|l| l.local_user.id);
|
||||
let my_person_id = options.local_user.map(|l| l.person.id);
|
||||
let my_local_user_id = options.local_user.map(|l| l.local_user.id);
|
||||
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = person_id.unwrap_or(PersonId(-1));
|
||||
let local_user_id_join = local_user_id.unwrap_or(LocalUserId(-1));
|
||||
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
|
||||
let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1));
|
||||
|
||||
let mut query = all_joins(
|
||||
post_aggregates::table.into_boxed(),
|
||||
person_id,
|
||||
my_person_id,
|
||||
options.saved_only,
|
||||
);
|
||||
|
||||
|
@ -310,7 +303,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(community::deleted.eq(false));
|
||||
|
||||
// only show deleted posts to creator
|
||||
if let Some(person_id) = person_id {
|
||||
if let Some(person_id) = my_person_id {
|
||||
query = query.filter(post::deleted.eq(false).or(post::creator_id.eq(person_id)));
|
||||
} else {
|
||||
query = query.filter(post::deleted.eq(false));
|
||||
|
@ -321,21 +314,11 @@ fn queries<'a>() -> Queries<
|
|||
.map(|l| l.local_user.admin)
|
||||
.unwrap_or(false);
|
||||
// only show removed posts to admin when viewing user profile
|
||||
if !(options.is_profile_view && is_admin) {
|
||||
if !(options.creator_id.is_some() && is_admin) {
|
||||
query = query
|
||||
.filter(community::removed.eq(false))
|
||||
.filter(post::removed.eq(false));
|
||||
}
|
||||
if options.community_id.is_none() || options.community_id_just_for_prefetch {
|
||||
query = order_and_page_filter_desc(query, post_aggregates::featured_local, &options, |e| {
|
||||
e.featured_local
|
||||
});
|
||||
} else {
|
||||
query =
|
||||
order_and_page_filter_desc(query, post_aggregates::featured_community, &options, |e| {
|
||||
e.featured_community
|
||||
});
|
||||
}
|
||||
if let Some(community_id) = options.community_id {
|
||||
query = query.filter(post_aggregates::community_id.eq(community_id));
|
||||
}
|
||||
|
@ -344,41 +327,47 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(post_aggregates::creator_id.eq(creator_id));
|
||||
}
|
||||
|
||||
if let Some(person_id) = person_id {
|
||||
let is_subscribed = exists(
|
||||
community_follower::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id)),
|
||||
),
|
||||
);
|
||||
match options.listing_type.unwrap_or_default() {
|
||||
ListingType::Subscribed => query = query.filter(is_subscribed),
|
||||
ListingType::Local => {
|
||||
query = query
|
||||
.filter(community::local.eq(true))
|
||||
.filter(community::hidden.eq(false).or(is_subscribed));
|
||||
if let Some(listing_type) = options.listing_type {
|
||||
if let Some(person_id) = my_person_id {
|
||||
let is_subscribed = exists(
|
||||
community_follower::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id)),
|
||||
),
|
||||
);
|
||||
match listing_type {
|
||||
ListingType::Subscribed => query = query.filter(is_subscribed),
|
||||
ListingType::Local => {
|
||||
query = query
|
||||
.filter(community::local.eq(true))
|
||||
.filter(community::hidden.eq(false).or(is_subscribed));
|
||||
}
|
||||
ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
|
||||
ListingType::ModeratorView => {
|
||||
query = query.filter(exists(
|
||||
community_moderator::table.filter(
|
||||
post::community_id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(person_id)),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
|
||||
ListingType::ModeratorView => {
|
||||
query = query.filter(exists(
|
||||
community_moderator::table.filter(
|
||||
post::community_id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(person_id)),
|
||||
),
|
||||
));
|
||||
}
|
||||
// If your person_id is missing, only show local
|
||||
else {
|
||||
match listing_type {
|
||||
ListingType::Local => {
|
||||
query = query
|
||||
.filter(community::local.eq(true))
|
||||
.filter(community::hidden.eq(false));
|
||||
}
|
||||
_ => query = query.filter(community::hidden.eq(false)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match options.listing_type.unwrap_or_default() {
|
||||
ListingType::Local => {
|
||||
query = query
|
||||
.filter(community::local.eq(true))
|
||||
.filter(community::hidden.eq(false));
|
||||
}
|
||||
_ => query = query.filter(community::hidden.eq(false)),
|
||||
}
|
||||
query = query.filter(community::hidden.eq(false));
|
||||
}
|
||||
|
||||
if let Some(url_search) = &options.url_search {
|
||||
|
@ -412,7 +401,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(person::bot_account.eq(false));
|
||||
};
|
||||
|
||||
if let (true, Some(person_id)) = (options.saved_only, person_id) {
|
||||
if let (true, Some(person_id)) = (options.saved_only, my_person_id) {
|
||||
query = query.filter(is_saved(person_id));
|
||||
}
|
||||
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
|
||||
|
@ -423,12 +412,13 @@ fn queries<'a>() -> Queries<
|
|||
.unwrap_or(true)
|
||||
{
|
||||
// Do not hide read posts when it is a user profile view
|
||||
if let (false, Some(person_id)) = (options.is_profile_view, person_id) {
|
||||
// Or, only hide read posts on non-profile views
|
||||
if let (None, Some(person_id)) = (options.creator_id, my_person_id) {
|
||||
query = query.filter(not(is_read(person_id)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(person_id) = person_id {
|
||||
if let Some(person_id) = my_person_id {
|
||||
if options.liked_only {
|
||||
query = query.filter(score(person_id).eq(1));
|
||||
} else if options.disliked_only {
|
||||
|
@ -438,7 +428,7 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
// Dont filter blocks or missing languages for moderator view type
|
||||
if let (Some(person_id), false) = (
|
||||
person_id,
|
||||
my_person_id,
|
||||
options.listing_type.unwrap_or_default() == ListingType::ModeratorView,
|
||||
) {
|
||||
// Filter out the rows with missing languages
|
||||
|
@ -467,86 +457,96 @@ fn queries<'a>() -> Queries<
|
|||
)));
|
||||
query = query.filter(not(is_creator_blocked(person_id)));
|
||||
}
|
||||
let now = diesel::dsl::now.into_sql::<Timestamptz>();
|
||||
|
||||
let featured_field = if options.community_id.is_none() || options.community_id_just_for_prefetch
|
||||
{
|
||||
use post_aggregates::{
|
||||
comments,
|
||||
controversy_rank,
|
||||
hot_rank,
|
||||
hot_rank_active,
|
||||
published,
|
||||
scaled_rank,
|
||||
score,
|
||||
};
|
||||
match options.sort.as_ref().unwrap_or(&SortType::Hot) {
|
||||
SortType::Active => {
|
||||
query =
|
||||
order_and_page_filter_desc(query, hot_rank_active, &options, |e| e.hot_rank_active);
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published);
|
||||
}
|
||||
SortType::Hot => {
|
||||
query = order_and_page_filter_desc(query, hot_rank, &options, |e| e.hot_rank);
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published);
|
||||
}
|
||||
SortType::Scaled => {
|
||||
query = order_and_page_filter_desc(query, scaled_rank, &options, |e| e.scaled_rank);
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published);
|
||||
}
|
||||
SortType::Controversial => {
|
||||
query =
|
||||
order_and_page_filter_desc(query, controversy_rank, &options, |e| e.controversy_rank);
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published);
|
||||
}
|
||||
SortType::New => {
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published)
|
||||
}
|
||||
SortType::Old => {
|
||||
query = order_and_page_filter_asc(query, published, &options, |e| e.published)
|
||||
}
|
||||
SortType::NewComments => {
|
||||
query = order_and_page_filter_desc(query, newest_comment_time, &options, |e| {
|
||||
e.newest_comment_time
|
||||
})
|
||||
}
|
||||
SortType::MostComments => {
|
||||
query = order_and_page_filter_desc(query, comments, &options, |e| e.comments);
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published);
|
||||
}
|
||||
SortType::TopAll => {
|
||||
query = order_and_page_filter_desc(query, score, &options, |e| e.score);
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published);
|
||||
}
|
||||
o @ (SortType::TopYear
|
||||
| SortType::TopMonth
|
||||
| SortType::TopWeek
|
||||
| SortType::TopDay
|
||||
| SortType::TopHour
|
||||
| SortType::TopSixHour
|
||||
| SortType::TopTwelveHour
|
||||
| SortType::TopThreeMonths
|
||||
| SortType::TopSixMonths
|
||||
| SortType::TopNineMonths) => {
|
||||
let interval = match o {
|
||||
SortType::TopYear => 1.years(),
|
||||
SortType::TopMonth => 1.months(),
|
||||
SortType::TopWeek => 1.weeks(),
|
||||
SortType::TopDay => 1.days(),
|
||||
SortType::TopHour => 1.hours(),
|
||||
SortType::TopSixHour => 6.hours(),
|
||||
SortType::TopTwelveHour => 12.hours(),
|
||||
SortType::TopThreeMonths => 3.months(),
|
||||
SortType::TopSixMonths => 6.months(),
|
||||
SortType::TopNineMonths => 9.months(),
|
||||
_ => return Err(Error::NotFound),
|
||||
};
|
||||
query = query.filter(post_aggregates::published.gt(now - interval));
|
||||
query = order_and_page_filter_desc(query, score, &options, |e| e.score);
|
||||
query = order_and_page_filter_desc(query, published, &options, |e| e.published);
|
||||
}
|
||||
}
|
||||
field!(featured_local)
|
||||
} else {
|
||||
field!(featured_community)
|
||||
};
|
||||
|
||||
let (main_sort, top_sort_interval) = match options.sort.unwrap_or(SortType::Hot) {
|
||||
SortType::Active => ((Ord::Desc, field!(hot_rank_active)), None),
|
||||
SortType::Hot => ((Ord::Desc, field!(hot_rank)), None),
|
||||
SortType::Scaled => ((Ord::Desc, field!(scaled_rank)), None),
|
||||
SortType::Controversial => ((Ord::Desc, field!(controversy_rank)), None),
|
||||
SortType::New => ((Ord::Desc, field!(published)), None),
|
||||
SortType::Old => ((Ord::Asc, field!(published)), None),
|
||||
SortType::NewComments => ((Ord::Desc, field!(newest_comment_time)), None),
|
||||
SortType::MostComments => ((Ord::Desc, field!(comments)), None),
|
||||
SortType::TopAll => ((Ord::Desc, field!(score)), None),
|
||||
SortType::TopYear => ((Ord::Desc, field!(score)), Some(1.years())),
|
||||
SortType::TopMonth => ((Ord::Desc, field!(score)), Some(1.months())),
|
||||
SortType::TopWeek => ((Ord::Desc, field!(score)), Some(1.weeks())),
|
||||
SortType::TopDay => ((Ord::Desc, field!(score)), Some(1.days())),
|
||||
SortType::TopHour => ((Ord::Desc, field!(score)), Some(1.hours())),
|
||||
SortType::TopSixHour => ((Ord::Desc, field!(score)), Some(6.hours())),
|
||||
SortType::TopTwelveHour => ((Ord::Desc, field!(score)), Some(12.hours())),
|
||||
SortType::TopThreeMonths => ((Ord::Desc, field!(score)), Some(3.months())),
|
||||
SortType::TopSixMonths => ((Ord::Desc, field!(score)), Some(6.months())),
|
||||
SortType::TopNineMonths => ((Ord::Desc, field!(score)), Some(9.months())),
|
||||
};
|
||||
|
||||
if let Some(interval) = top_sort_interval {
|
||||
query = query.filter(post_aggregates::published.gt(now() - interval));
|
||||
}
|
||||
|
||||
let sorts = [
|
||||
Some((Ord::Desc, featured_field)),
|
||||
Some(main_sort),
|
||||
Some((Ord::Desc, field!(post_id))),
|
||||
];
|
||||
let sorts_iter = sorts.iter().flatten();
|
||||
|
||||
// This loop does almost the same thing as sorting by and comparing tuples. If the rows were
|
||||
// only sorted by 1 field called `foo` in descending order, then it would be like this:
|
||||
//
|
||||
// ```
|
||||
// query = query.then_order_by(foo.desc());
|
||||
// if let Some(first) = &options.page_after {
|
||||
// query = query.filter(foo.le(first.foo));
|
||||
// }
|
||||
// if let Some(last) = &page_before_or_equal {
|
||||
// query = query.filter(foo.ge(last.foo));
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// If multiple rows have the same value for a sorted field, then they are
|
||||
// grouped together, and the rows in that group are sorted by the next fields.
|
||||
// When checking if a row is within the range determined by the cursors, a field
|
||||
// that's sorted after other fields is only compared if the row and the cursor
|
||||
// are in the same group created by the previous sort, which is checked by using
|
||||
// `or` to skip the comparison if any previously sorted field is not equal.
|
||||
for (i, (order, field)) in sorts_iter.clone().enumerate() {
|
||||
// Both cursors are treated as inclusive here. `page_after` is made exclusive
|
||||
// by adding `1` to the offset.
|
||||
let (then_order_by_field, compare_first, compare_last) = match order {
|
||||
Ord::Desc => (field.then_order_by_desc, field.le, field.ge),
|
||||
Ord::Asc => (field.then_order_by_asc, field.ge, field.le),
|
||||
};
|
||||
|
||||
query = then_order_by_field(query);
|
||||
|
||||
for (cursor_data, compare) in [
|
||||
(&options.page_after, compare_first),
|
||||
(&options.page_before_or_equal, compare_last),
|
||||
] {
|
||||
let Some(cursor_data) = cursor_data else {
|
||||
continue;
|
||||
};
|
||||
let mut condition: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
Box::new(compare(&cursor_data.0));
|
||||
|
||||
// For each field that was sorted before the current one, skip the filter by changing
|
||||
// `condition` to `true` if the row's value doesn't equal the cursor's value.
|
||||
for (_, other_field) in sorts_iter.clone().take(i) {
|
||||
condition = Box::new(condition.or((other_field.ne)(&cursor_data.0)));
|
||||
}
|
||||
|
||||
query = query.filter(condition);
|
||||
}
|
||||
}
|
||||
|
||||
let (limit, mut offset) = limit_and_offset(options.page, options.limit)?;
|
||||
if options.page_after.is_some() {
|
||||
// always skip exactly one post because that's the last post of the previous page
|
||||
|
@ -621,8 +621,6 @@ pub struct PostQuery<'a> {
|
|||
pub saved_only: bool,
|
||||
pub liked_only: bool,
|
||||
pub disliked_only: bool,
|
||||
pub moderator_view: bool,
|
||||
pub is_profile_view: bool,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub page_after: Option<PaginationCursorData>,
|
||||
|
@ -725,15 +723,17 @@ mod tests {
|
|||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
post_view::{PostQuery, PostView},
|
||||
post_view::{PaginationCursorData, PostQuery, PostView},
|
||||
structs::LocalUserView,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::PostAggregates,
|
||||
impls::actor_language::UNDETERMINED_ID,
|
||||
newtypes::LanguageId,
|
||||
source::{
|
||||
actor_language::LocalUserLanguage,
|
||||
comment::{Comment, CommentInsertForm},
|
||||
community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
|
||||
community_block::{CommunityBlock, CommunityBlockForm},
|
||||
instance::Instance,
|
||||
|
@ -742,22 +742,24 @@ mod tests {
|
|||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||
person::{Person, PersonInsertForm},
|
||||
person_block::{PersonBlock, PersonBlockForm},
|
||||
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
|
||||
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostRead, PostUpdateForm},
|
||||
},
|
||||
traits::{Blockable, Crud, Joinable, Likeable},
|
||||
utils::{build_db_pool_for_tests, DbPool},
|
||||
utils::{build_db_pool_for_tests, DbPool, RANK_DEFAULT},
|
||||
SortType,
|
||||
SubscribedType,
|
||||
};
|
||||
use serial_test::serial;
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
|
||||
struct Data {
|
||||
inserted_instance: Instance,
|
||||
local_user_view: LocalUserView,
|
||||
inserted_blocked_person: Person,
|
||||
blocked_local_user_view: LocalUserView,
|
||||
inserted_bot: Person,
|
||||
inserted_community: Community,
|
||||
inserted_post: Post,
|
||||
inserted_bot_post: Post,
|
||||
}
|
||||
|
||||
async fn init_data(pool: &mut DbPool<'_>) -> Data {
|
||||
|
@ -809,6 +811,14 @@ mod tests {
|
|||
|
||||
let inserted_blocked_person = Person::create(pool, &blocked_person).await.unwrap();
|
||||
|
||||
let blocked_local_user_form = LocalUserInsertForm::builder()
|
||||
.person_id(inserted_blocked_person.id)
|
||||
.password_encrypted(String::new())
|
||||
.build();
|
||||
let inserted_blocked_local_user = LocalUser::create(pool, &blocked_local_user_form)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let post_from_blocked_person = PostInsertForm::builder()
|
||||
.name("blocked_person_post".to_string())
|
||||
.creator_id(inserted_blocked_person.id)
|
||||
|
@ -842,20 +852,26 @@ mod tests {
|
|||
.community_id(inserted_community.id)
|
||||
.build();
|
||||
|
||||
let _inserted_bot_post = Post::create(pool, &new_bot_post).await.unwrap();
|
||||
let inserted_bot_post = Post::create(pool, &new_bot_post).await.unwrap();
|
||||
let local_user_view = LocalUserView {
|
||||
local_user: inserted_local_user,
|
||||
person: inserted_person,
|
||||
counts: Default::default(),
|
||||
};
|
||||
let blocked_local_user_view = LocalUserView {
|
||||
local_user: inserted_blocked_local_user,
|
||||
person: inserted_blocked_person,
|
||||
counts: Default::default(),
|
||||
};
|
||||
|
||||
Data {
|
||||
inserted_instance,
|
||||
local_user_view,
|
||||
inserted_blocked_person,
|
||||
blocked_local_user_view,
|
||||
inserted_bot,
|
||||
inserted_community,
|
||||
inserted_post,
|
||||
inserted_bot_post,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1245,7 +1261,7 @@ mod tests {
|
|||
// Remove the post
|
||||
Post::update(
|
||||
pool,
|
||||
data.inserted_post.id,
|
||||
data.inserted_bot_post.id,
|
||||
&PostUpdateForm {
|
||||
removed: Some(true),
|
||||
..Default::default()
|
||||
|
@ -1265,18 +1281,21 @@ mod tests {
|
|||
.unwrap();
|
||||
assert_eq!(1, post_listings_no_admin.len());
|
||||
|
||||
// Removed post is shown to admins on profile page
|
||||
// Removed bot post is shown to admins on its profile page
|
||||
data.local_user_view.local_user.admin = true;
|
||||
let post_listings_is_admin = PostQuery {
|
||||
sort: Some(SortType::New),
|
||||
creator_id: Some(data.inserted_bot.id),
|
||||
local_user: Some(&data.local_user_view),
|
||||
is_profile_view: true,
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(2, post_listings_is_admin.len());
|
||||
assert_eq!(
|
||||
data.inserted_bot.id,
|
||||
post_listings_is_admin[0].post.creator_id
|
||||
);
|
||||
|
||||
cleanup(data, pool).await;
|
||||
}
|
||||
|
@ -1300,34 +1319,25 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure you don't see the deleted post in the results
|
||||
let post_listings_no_creator = PostQuery {
|
||||
sort: Some(SortType::New),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let not_contains_deleted = post_listings_no_creator
|
||||
// Deleted post is only shown to creator
|
||||
for (local_user, expect_contains_deleted) in [
|
||||
(None, false),
|
||||
(Some(&data.blocked_local_user_view), false),
|
||||
(Some(&data.local_user_view), true),
|
||||
] {
|
||||
let contains_deleted = PostQuery {
|
||||
sort: Some(SortType::New),
|
||||
local_user,
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|p| p.post.id)
|
||||
.all(|p| p != data.inserted_post.id);
|
||||
assert!(not_contains_deleted);
|
||||
.any(|p| p.post.id == data.inserted_post.id);
|
||||
|
||||
// Deleted post is shown to creator
|
||||
let post_listings_is_creator = PostQuery {
|
||||
sort: Some(SortType::New),
|
||||
local_user: Some(&data.local_user_view),
|
||||
..Default::default()
|
||||
assert_eq!(expect_contains_deleted, contains_deleted);
|
||||
}
|
||||
.list(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let contains_deleted = post_listings_is_creator
|
||||
.iter()
|
||||
.map(|p| p.post.id)
|
||||
.any(|p| p == data.inserted_post.id);
|
||||
assert!(contains_deleted);
|
||||
|
||||
cleanup(data, pool).await;
|
||||
}
|
||||
|
@ -1410,6 +1420,125 @@ mod tests {
|
|||
cleanup(data, pool).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn pagination_includes_each_post_once() {
|
||||
let pool = &build_db_pool_for_tests().await;
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await;
|
||||
|
||||
let community_form = CommunityInsertForm::builder()
|
||||
.name("yes".to_string())
|
||||
.title("yes".to_owned())
|
||||
.public_key("pubkey".to_string())
|
||||
.instance_id(data.inserted_instance.id)
|
||||
.build();
|
||||
let inserted_community = Community::create(pool, &community_form).await.unwrap();
|
||||
|
||||
let mut inserted_post_ids = vec![];
|
||||
let mut inserted_comment_ids = vec![];
|
||||
|
||||
// Create 150 posts with varying non-correlating values for publish date, number of comments, and featured
|
||||
for comments in 0..10 {
|
||||
for _ in 0..15 {
|
||||
let post_form = PostInsertForm::builder()
|
||||
.name("keep Christ in Christmas".to_owned())
|
||||
.creator_id(data.local_user_view.person.id)
|
||||
.community_id(inserted_community.id)
|
||||
.featured_local(Some((comments % 2) == 0))
|
||||
.featured_community(Some((comments % 2) == 0))
|
||||
.published(Some(Utc::now() - Duration::from_secs(comments % 3)))
|
||||
.build();
|
||||
let inserted_post = Post::create(pool, &post_form).await.unwrap();
|
||||
inserted_post_ids.push(inserted_post.id);
|
||||
|
||||
for _ in 0..comments {
|
||||
let comment_form = CommentInsertForm::builder()
|
||||
.creator_id(data.local_user_view.person.id)
|
||||
.post_id(inserted_post.id)
|
||||
.content("yes".to_owned())
|
||||
.build();
|
||||
let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap();
|
||||
inserted_comment_ids.push(inserted_comment.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut listed_post_ids = vec![];
|
||||
let mut page_after = None;
|
||||
loop {
|
||||
let post_listings = PostQuery {
|
||||
community_id: Some(inserted_community.id),
|
||||
sort: Some(SortType::MostComments),
|
||||
limit: Some(10),
|
||||
page_after,
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
listed_post_ids.extend(post_listings.iter().map(|p| p.post.id));
|
||||
|
||||
if let Some(p) = post_listings.into_iter().last() {
|
||||
page_after = Some(PaginationCursorData(p.counts));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inserted_post_ids.sort_unstable_by_key(|id| id.0);
|
||||
listed_post_ids.sort_unstable_by_key(|id| id.0);
|
||||
|
||||
assert_eq!(inserted_post_ids, listed_post_ids);
|
||||
|
||||
Community::delete(pool, inserted_community.id)
|
||||
.await
|
||||
.unwrap();
|
||||
cleanup(data, pool).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn post_listings_hide_read() {
|
||||
let pool = &build_db_pool_for_tests().await;
|
||||
let pool = &mut pool.into();
|
||||
let mut data = init_data(pool).await;
|
||||
|
||||
// Make sure local user hides read posts
|
||||
let local_user_form = LocalUserUpdateForm {
|
||||
show_read_posts: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
let inserted_local_user =
|
||||
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form)
|
||||
.await
|
||||
.unwrap();
|
||||
data.local_user_view.local_user = inserted_local_user;
|
||||
|
||||
// Mark a post as read
|
||||
PostRead::mark_as_read(
|
||||
pool,
|
||||
HashSet::from([data.inserted_bot_post.id]),
|
||||
data.local_user_view.person.id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure you don't see the read post in the results
|
||||
let post_listings_hide_read = PostQuery {
|
||||
sort: Some(SortType::New),
|
||||
local_user: Some(&data.local_user_view),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(1, post_listings_hide_read.len());
|
||||
|
||||
cleanup(data, pool).await;
|
||||
}
|
||||
|
||||
async fn cleanup(data: Data, pool: &mut DbPool<'_>) {
|
||||
let num_deleted = Post::delete(pool, data.inserted_post.id).await.unwrap();
|
||||
Community::delete(pool, data.inserted_community.id)
|
||||
|
@ -1419,7 +1548,7 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
Person::delete(pool, data.inserted_bot.id).await.unwrap();
|
||||
Person::delete(pool, data.inserted_blocked_person.id)
|
||||
Person::delete(pool, data.blocked_local_user_view.person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
Instance::delete(pool, data.inserted_instance.id)
|
||||
|
@ -1526,10 +1655,10 @@ mod tests {
|
|||
newest_comment_time: inserted_post.published,
|
||||
featured_community: false,
|
||||
featured_local: false,
|
||||
hot_rank: 0.1728,
|
||||
hot_rank_active: 0.1728,
|
||||
hot_rank: RANK_DEFAULT,
|
||||
hot_rank_active: RANK_DEFAULT,
|
||||
controversy_rank: 0.0,
|
||||
scaled_rank: 0.3621,
|
||||
scaled_rank: RANK_DEFAULT,
|
||||
community_id: inserted_post.community_id,
|
||||
creator_id: inserted_post.creator_id,
|
||||
instance_id: data.inserted_instance.id,
|
||||
|
|
|
@ -210,7 +210,7 @@ mod tests {
|
|||
.recipient_id(timmy.id)
|
||||
.content(message_content.clone())
|
||||
.build();
|
||||
let _inserted_sara_timmy_message_form = PrivateMessage::create(pool, &sara_timmy_message_form)
|
||||
PrivateMessage::create(pool, &sara_timmy_message_form)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -219,7 +219,7 @@ mod tests {
|
|||
.recipient_id(jess.id)
|
||||
.content(message_content.clone())
|
||||
.build();
|
||||
let _inserted_sara_jess_message_form = PrivateMessage::create(pool, &sara_jess_message_form)
|
||||
PrivateMessage::create(pool, &sara_jess_message_form)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -228,7 +228,7 @@ mod tests {
|
|||
.recipient_id(sara.id)
|
||||
.content(message_content.clone())
|
||||
.build();
|
||||
let _inserted_timmy_sara_message_form = PrivateMessage::create(pool, &timmy_sara_message_form)
|
||||
PrivateMessage::create(pool, &timmy_sara_message_form)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -237,13 +237,13 @@ mod tests {
|
|||
.recipient_id(timmy.id)
|
||||
.content(message_content.clone())
|
||||
.build();
|
||||
let _inserted_jess_timmy_message_form = PrivateMessage::create(pool, &jess_timmy_message_form)
|
||||
PrivateMessage::create(pool, &jess_timmy_message_form)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let timmy_messages = PrivateMessageQuery {
|
||||
unread_only: false,
|
||||
creator_id: Option::None,
|
||||
creator_id: None,
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, timmy.id)
|
||||
|
@ -260,7 +260,7 @@ mod tests {
|
|||
|
||||
let timmy_unread_messages = PrivateMessageQuery {
|
||||
unread_only: true,
|
||||
creator_id: Option::None,
|
||||
creator_id: None,
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, timmy.id)
|
||||
|
@ -320,7 +320,7 @@ mod tests {
|
|||
|
||||
let timmy_messages = PrivateMessageQuery {
|
||||
unread_only: true,
|
||||
creator_id: Option::None,
|
||||
creator_id: None,
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, timmy.id)
|
||||
|
@ -333,5 +333,8 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
assert_eq!(timmy_unread_messages, 1);
|
||||
|
||||
// This also deletes all persons and private messages thanks to sql `on delete cascade`
|
||||
Instance::delete(pool, instance.id).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,8 +49,13 @@ fn queries<'a>() -> Queries<
|
|||
let list = move |mut conn: DbConn<'a>, options: RegistrationApplicationQuery| async move {
|
||||
let mut query = all_joins(registration_application::table.into_boxed());
|
||||
|
||||
// If viewing all applications, order by newest, but if viewing unresolved only, show the oldest first (FIFO)
|
||||
if options.unread_only {
|
||||
query = query.filter(registration_application::admin_id.is_null())
|
||||
query = query
|
||||
.filter(registration_application::admin_id.is_null())
|
||||
.order_by(registration_application::published.asc());
|
||||
} else {
|
||||
query = query.order_by(registration_application::published.desc());
|
||||
}
|
||||
|
||||
if options.verified_email_only {
|
||||
|
@ -59,10 +64,7 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||
|
||||
query = query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.order_by(registration_application::published.asc());
|
||||
query = query.limit(limit).offset(offset);
|
||||
|
||||
query.load::<RegistrationApplicationView>(&mut conn).await
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::structs::PersonView;
|
||||
use diesel::{
|
||||
dsl::exists,
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
|
@ -13,7 +12,17 @@ use diesel_async::RunQueryDsl;
|
|||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
schema::{local_user, person, person_aggregates},
|
||||
utils::{fuzzy_search, limit_and_offset, now, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
utils::{
|
||||
functions::coalesce,
|
||||
fuzzy_search,
|
||||
limit_and_offset,
|
||||
now,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
SortType,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -48,21 +57,15 @@ fn post_to_person_sort_type(sort: SortType) -> PersonSortType {
|
|||
|
||||
fn queries<'a>(
|
||||
) -> Queries<impl ReadFn<'a, PersonView, PersonId>, impl ListFn<'a, PersonView, ListMode>> {
|
||||
let creator_is_admin = exists(
|
||||
local_user::table.filter(
|
||||
person::id
|
||||
.eq(local_user::person_id)
|
||||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
);
|
||||
let all_joins = move |query: person::BoxedQuery<'a, Pg>| {
|
||||
query
|
||||
.inner_join(person_aggregates::table)
|
||||
.left_join(local_user::table)
|
||||
.filter(person::deleted.eq(false))
|
||||
.select((
|
||||
person::all_columns,
|
||||
person_aggregates::all_columns,
|
||||
creator_is_admin,
|
||||
coalesce(local_user::admin.nullable(), false),
|
||||
))
|
||||
};
|
||||
|
||||
|
@ -77,7 +80,7 @@ fn queries<'a>(
|
|||
match mode {
|
||||
ListMode::Admins => {
|
||||
query = query
|
||||
.filter(creator_is_admin.eq(true))
|
||||
.filter(local_user::admin.eq(true))
|
||||
.filter(person::deleted.eq(false))
|
||||
.order_by(person::published);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[package]
|
||||
name = "lemmy_federate"
|
||||
publish = false
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
|
@ -30,5 +31,5 @@ reqwest.workspace = true
|
|||
serde_json.workspace = true
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tracing.workspace = true
|
||||
moka = { version = "0.11.3", features = ["future"] }
|
||||
tokio-util = "0.7.9"
|
||||
moka.workspace = true
|
||||
tokio-util = "0.7.10"
|
||||
|
|
|
@ -154,7 +154,6 @@ async fn receive_print_stats(
|
|||
tokio::select! {
|
||||
ele = receiver.recv() => {
|
||||
let Some((domain, ele)) = ele else {
|
||||
tracing::info!("done. quitting");
|
||||
print_stats(pool, &stats).await;
|
||||
return;
|
||||
};
|
||||
|
@ -181,9 +180,9 @@ async fn print_stats(pool: &mut DbPool<'_>, stats: &HashMap<String, FederationQu
|
|||
.expect("0 is valid nanos")
|
||||
.to_rfc3339()
|
||||
);
|
||||
// todo: less noisy output (only output failing instances and summary for successful)
|
||||
// todo: more stats (act/sec, avg http req duration)
|
||||
let mut ok_count = 0;
|
||||
let mut behind_count = 0;
|
||||
for (domain, stat) in stats {
|
||||
let behind = last_id.0 - stat.last_successful_id.map(|e| e.0).unwrap_or(0);
|
||||
if stat.fail_count > 0 {
|
||||
|
@ -195,10 +194,11 @@ async fn print_stats(pool: &mut DbPool<'_>, stats: &HashMap<String, FederationQu
|
|||
federate_retry_sleep_duration(stat.fail_count)
|
||||
);
|
||||
} else if behind > 0 {
|
||||
tracing::info!("{}: Ok. {} behind", domain, behind);
|
||||
tracing::debug!("{}: Ok. {} activities behind", domain, behind);
|
||||
behind_count += 1;
|
||||
} else {
|
||||
ok_count += 1;
|
||||
}
|
||||
}
|
||||
tracing::info!("{ok_count} others up to date");
|
||||
tracing::info!("{ok_count} others up to date. {behind_count} instances behind.");
|
||||
}
|
||||
|
|
|
@ -171,6 +171,7 @@ impl InstanceWorker {
|
|||
.await
|
||||
.context("failed reading activity from db")?
|
||||
else {
|
||||
tracing::debug!("{}: {:?} does not exist", self.instance.domain, id);
|
||||
self.state.last_successful_id = Some(id);
|
||||
continue;
|
||||
};
|
||||
|
@ -221,7 +222,7 @@ impl InstanceWorker {
|
|||
SendActivityTask::prepare(object, actor.as_ref(), inbox_urls, &self.context).await?;
|
||||
for task in requests {
|
||||
// usually only one due to shared inbox
|
||||
tracing::info!("sending out {}", task);
|
||||
tracing::debug!("sending out {}", task);
|
||||
while let Err(e) = task.sign_and_send(&self.context).await {
|
||||
self.state.fail_count += 1;
|
||||
self.state.last_retry = Some(Utc::now());
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[package]
|
||||
name = "lemmy_routes"
|
||||
publish = false
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
|
|
|
@ -25,13 +25,7 @@ use lemmy_utils::{
|
|||
utils::markdown::{markdown_to_html, sanitize_html},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use rss::{
|
||||
extension::dublincore::DublinCoreExtensionBuilder,
|
||||
ChannelBuilder,
|
||||
GuidBuilder,
|
||||
Item,
|
||||
ItemBuilder,
|
||||
};
|
||||
use rss::{extension::dublincore::DublinCoreExtension, Channel, Guid, Item};
|
||||
use serde::Deserialize;
|
||||
use std::{collections::BTreeMap, str::FromStr};
|
||||
|
||||
|
@ -146,18 +140,19 @@ async fn get_feed_data(
|
|||
|
||||
let items = create_post_items(posts, &context.settings().get_protocol_and_hostname())?;
|
||||
|
||||
let mut channel_builder = ChannelBuilder::default();
|
||||
channel_builder
|
||||
.namespaces(RSS_NAMESPACE.clone())
|
||||
.title(&format!("{} - {}", site_view.site.name, listing_type))
|
||||
.link(context.settings().get_protocol_and_hostname())
|
||||
.items(items);
|
||||
let mut channel = Channel {
|
||||
namespaces: RSS_NAMESPACE.clone(),
|
||||
title: format!("{} - {}", site_view.site.name, listing_type),
|
||||
link: context.settings().get_protocol_and_hostname(),
|
||||
items,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(site_desc) = site_view.site.description {
|
||||
channel_builder.description(&site_desc);
|
||||
channel.set_description(&site_desc);
|
||||
}
|
||||
|
||||
let rss = channel_builder.build().to_string();
|
||||
let rss = channel.to_string();
|
||||
Ok(
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/rss+xml")
|
||||
|
@ -217,7 +212,7 @@ async fn get_feed(
|
|||
}
|
||||
.map_err(ErrorBadRequest)?;
|
||||
|
||||
let rss = builder.build().to_string();
|
||||
let rss = builder.to_string();
|
||||
|
||||
Ok(
|
||||
HttpResponse::Ok()
|
||||
|
@ -233,7 +228,7 @@ async fn get_feed_user(
|
|||
limit: &i64,
|
||||
page: &i64,
|
||||
user_name: &str,
|
||||
) -> Result<ChannelBuilder, LemmyError> {
|
||||
) -> Result<Channel, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let person = Person::read_from_name(&mut context.pool(), user_name, false).await?;
|
||||
|
||||
|
@ -252,14 +247,15 @@ async fn get_feed_user(
|
|||
|
||||
let items = create_post_items(posts, &context.settings().get_protocol_and_hostname())?;
|
||||
|
||||
let mut channel_builder = ChannelBuilder::default();
|
||||
channel_builder
|
||||
.namespaces(RSS_NAMESPACE.clone())
|
||||
.title(&format!("{} - {}", site_view.site.name, person.name))
|
||||
.link(person.actor_id.to_string())
|
||||
.items(items);
|
||||
let channel = Channel {
|
||||
namespaces: RSS_NAMESPACE.clone(),
|
||||
title: format!("{} - {}", site_view.site.name, person.name),
|
||||
link: person.actor_id.to_string(),
|
||||
items,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(channel_builder)
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -269,7 +265,7 @@ async fn get_feed_community(
|
|||
limit: &i64,
|
||||
page: &i64,
|
||||
community_name: &str,
|
||||
) -> Result<ChannelBuilder, LemmyError> {
|
||||
) -> Result<Channel, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let community = Community::read_from_name(&mut context.pool(), community_name, false).await?;
|
||||
|
||||
|
@ -287,18 +283,19 @@ async fn get_feed_community(
|
|||
|
||||
let items = create_post_items(posts, &context.settings().get_protocol_and_hostname())?;
|
||||
|
||||
let mut channel_builder = ChannelBuilder::default();
|
||||
channel_builder
|
||||
.namespaces(RSS_NAMESPACE.clone())
|
||||
.title(&format!("{} - {}", site_view.site.name, community.name))
|
||||
.link(community.actor_id.to_string())
|
||||
.items(items);
|
||||
let mut channel = Channel {
|
||||
namespaces: RSS_NAMESPACE.clone(),
|
||||
title: format!("{} - {}", site_view.site.name, community.name),
|
||||
link: community.actor_id.to_string(),
|
||||
items,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(community_desc) = community.description {
|
||||
channel_builder.description(markdown_to_html(&community_desc));
|
||||
channel.set_description(markdown_to_html(&community_desc));
|
||||
}
|
||||
|
||||
Ok(channel_builder)
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -308,7 +305,7 @@ async fn get_feed_front(
|
|||
limit: &i64,
|
||||
page: &i64,
|
||||
jwt: &str,
|
||||
) -> Result<ChannelBuilder, LemmyError> {
|
||||
) -> Result<Channel, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let local_user = local_user_view_from_jwt(jwt, context).await?;
|
||||
|
||||
|
@ -328,22 +325,23 @@ async fn get_feed_front(
|
|||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let items = create_post_items(posts, &protocol_and_hostname)?;
|
||||
|
||||
let mut channel_builder = ChannelBuilder::default();
|
||||
channel_builder
|
||||
.namespaces(RSS_NAMESPACE.clone())
|
||||
.title(&format!("{} - Subscribed", site_view.site.name))
|
||||
.link(protocol_and_hostname)
|
||||
.items(items);
|
||||
let mut channel = Channel {
|
||||
namespaces: RSS_NAMESPACE.clone(),
|
||||
title: format!("{} - Subscribed", site_view.site.name),
|
||||
link: protocol_and_hostname,
|
||||
items,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(site_desc) = site_view.site.description {
|
||||
channel_builder.description(markdown_to_html(&site_desc));
|
||||
channel.set_description(markdown_to_html(&site_desc));
|
||||
}
|
||||
|
||||
Ok(channel_builder)
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> Result<ChannelBuilder, LemmyError> {
|
||||
async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> Result<Channel, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let local_user = local_user_view_from_jwt(jwt, context).await?;
|
||||
let person_id = local_user.local_user.person_id;
|
||||
|
@ -378,18 +376,19 @@ async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> Result<ChannelBuil
|
|||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let items = create_reply_and_mention_items(replies, mentions, &protocol_and_hostname)?;
|
||||
|
||||
let mut channel_builder = ChannelBuilder::default();
|
||||
channel_builder
|
||||
.namespaces(RSS_NAMESPACE.clone())
|
||||
.title(&format!("{} - Inbox", site_view.site.name))
|
||||
.link(format!("{protocol_and_hostname}/inbox",))
|
||||
.items(items);
|
||||
let mut channel = Channel {
|
||||
namespaces: RSS_NAMESPACE.clone(),
|
||||
title: format!("{} - Inbox", site_view.site.name),
|
||||
link: format!("{protocol_and_hostname}/inbox"),
|
||||
items,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(site_desc) = site_view.site.description {
|
||||
channel_builder.description(&site_desc);
|
||||
channel.set_description(&site_desc);
|
||||
}
|
||||
|
||||
Ok(channel_builder)
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -438,22 +437,26 @@ fn build_item(
|
|||
content: &str,
|
||||
protocol_and_hostname: &str,
|
||||
) -> Result<Item, LemmyError> {
|
||||
let mut i = ItemBuilder::default();
|
||||
i.title(format!("Reply from {creator_name}"));
|
||||
let author_url = format!("{protocol_and_hostname}/u/{creator_name}");
|
||||
i.author(format!(
|
||||
"/u/{creator_name} <a href=\"{author_url}\">(link)</a>"
|
||||
));
|
||||
let dt = published;
|
||||
i.pub_date(dt.to_rfc2822());
|
||||
i.comments(url.to_owned());
|
||||
let guid = GuidBuilder::default().permalink(true).value(url).build();
|
||||
i.guid(guid);
|
||||
i.link(url.to_owned());
|
||||
// TODO add images
|
||||
let html = markdown_to_html(content);
|
||||
i.description(html);
|
||||
Ok(i.build())
|
||||
let author_url = format!("{protocol_and_hostname}/u/{creator_name}");
|
||||
let guid = Some(Guid {
|
||||
permalink: true,
|
||||
value: url.to_owned(),
|
||||
});
|
||||
let description = Some(markdown_to_html(content));
|
||||
|
||||
Ok(Item {
|
||||
title: Some(format!("Reply from {creator_name}")),
|
||||
author: Some(format!(
|
||||
"/u/{creator_name} <a href=\"{author_url}\">(link)</a>"
|
||||
)),
|
||||
pub_date: Some(published.to_rfc2822()),
|
||||
comments: Some(url.to_owned()),
|
||||
link: Some(url.to_owned()),
|
||||
guid,
|
||||
description,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -464,31 +467,21 @@ fn create_post_items(
|
|||
let mut items: Vec<Item> = Vec::new();
|
||||
|
||||
for p in posts {
|
||||
let mut i = ItemBuilder::default();
|
||||
let mut dc_extension = DublinCoreExtensionBuilder::default();
|
||||
|
||||
i.title(sanitize_html(&p.post.name));
|
||||
|
||||
dc_extension.creators(vec![p.creator.actor_id.to_string()]);
|
||||
|
||||
let dt = p.post.published;
|
||||
i.pub_date(dt.to_rfc2822());
|
||||
|
||||
// TODO add images
|
||||
let post_url = format!("{}/post/{}", protocol_and_hostname, p.post.id);
|
||||
i.comments(post_url.clone());
|
||||
let guid = GuidBuilder::default()
|
||||
.permalink(true)
|
||||
.value(&post_url)
|
||||
.build();
|
||||
i.guid(guid);
|
||||
|
||||
let community_url = format!(
|
||||
"{}/c/{}",
|
||||
protocol_and_hostname,
|
||||
sanitize_html(&p.community.name)
|
||||
);
|
||||
|
||||
// TODO add images
|
||||
let dublin_core_ext = Some(DublinCoreExtension {
|
||||
creators: vec![p.creator.actor_id.to_string()],
|
||||
..DublinCoreExtension::default()
|
||||
});
|
||||
let guid = Some(Guid {
|
||||
permalink: true,
|
||||
value: post_url.clone(),
|
||||
});
|
||||
let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
|
||||
p.creator.actor_id,
|
||||
sanitize_html(&p.creator.name),
|
||||
|
@ -499,23 +492,31 @@ fn create_post_items(
|
|||
p.counts.comments);
|
||||
|
||||
// If its a url post, add it to the description
|
||||
if let Some(url) = p.post.url {
|
||||
let link = Some(if let Some(url) = p.post.url {
|
||||
let link_html = format!("<br><a href=\"{url}\">{url}</a>");
|
||||
description.push_str(&link_html);
|
||||
i.link(url.to_string());
|
||||
url.to_string()
|
||||
} else {
|
||||
i.link(post_url.clone());
|
||||
}
|
||||
post_url.clone()
|
||||
});
|
||||
|
||||
if let Some(body) = p.post.body {
|
||||
let html = markdown_to_html(&body);
|
||||
description.push_str(&html);
|
||||
}
|
||||
|
||||
i.description(description);
|
||||
let i = Item {
|
||||
title: Some(sanitize_html(&p.post.name)),
|
||||
pub_date: Some(p.post.published.to_rfc2822()),
|
||||
comments: Some(post_url.clone()),
|
||||
guid,
|
||||
description: Some(description),
|
||||
dublin_core_ext,
|
||||
link,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
i.dublin_core_ext(dc_extension.build());
|
||||
items.push(i.build());
|
||||
items.push(i);
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
|
|
|
@ -37,12 +37,11 @@ async fn get_webfinger_response(
|
|||
) -> Result<HttpResponse, LemmyError> {
|
||||
let name = extract_webfinger_name(&info.resource, &context)?;
|
||||
|
||||
let name_ = name.clone();
|
||||
let user_id: Option<Url> = Person::read_from_name(&mut context.pool(), &name_, false)
|
||||
let user_id: Option<Url> = Person::read_from_name(&mut context.pool(), name, false)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id.into());
|
||||
let community_id: Option<Url> = Community::read_from_name(&mut context.pool(), &name, false)
|
||||
let community_id: Option<Url> = Community::read_from_name(&mut context.pool(), name, false)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id.into());
|
||||
|
|
|
@ -41,12 +41,12 @@ uuid = { workspace = true, features = ["serde", "v4"] }
|
|||
rosetta-i18n = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
openssl = "0.10.57"
|
||||
openssl = "0.10.61"
|
||||
html2text = "0.6.0"
|
||||
deser-hjson = "1.2.0"
|
||||
deser-hjson = "2.2.4"
|
||||
smart-default = "0.7.1"
|
||||
lettre = { version = "0.10.4", features = ["tokio1", "tokio1-native-tls"] }
|
||||
markdown-it = "0.5.1"
|
||||
lettre = { version = "0.11.2", features = ["tokio1", "tokio1-native-tls"] }
|
||||
markdown-it = "0.6.0"
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
enum-map = { workspace = true }
|
||||
|
||||
|
|
|
@ -221,14 +221,13 @@ pub enum LemmyErrorType {
|
|||
CouldntSendWebmention,
|
||||
ContradictingFilters,
|
||||
InstanceBlockAlreadyExists,
|
||||
/// `jwt` cookie must be marked secure and httponly
|
||||
AuthCookieInsecure,
|
||||
/// Thrown when an API call is submitted with more than 1000 array elements, see [[MAX_API_PARAM_ELEMENTS]]
|
||||
TooManyItems,
|
||||
CommunityHasNoFollowers,
|
||||
BanExpirationInPast,
|
||||
InvalidUnixTime,
|
||||
InvalidBotAction,
|
||||
CantBlockLocalInstance,
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
|
|
|
@ -36,11 +36,11 @@ impl RateLimitCell {
|
|||
let state_weak_ref = Arc::downgrade(&state);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let hour = Duration::from_secs(3600);
|
||||
let interval = Duration::from_secs(120);
|
||||
|
||||
// This loop stops when all other references to `state` are dropped
|
||||
while let Some(state) = state_weak_ref.upgrade() {
|
||||
tokio::time::sleep(hour).await;
|
||||
tokio::time::sleep(interval).await;
|
||||
state
|
||||
.lock()
|
||||
.expect("Failed to lock rate limit mutex for reading")
|
||||
|
|
|
@ -4,9 +4,6 @@ use once_cell::sync::Lazy;
|
|||
use regex::{Regex, RegexBuilder};
|
||||
use url::Url;
|
||||
|
||||
static VALID_POST_TITLE_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r".*\S{3,200}.*").expect("compile regex"));
|
||||
|
||||
// From here: https://github.com/vector-im/element-android/blob/develop/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt#L35
|
||||
static VALID_MATRIX_ID_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"^@[A-Za-z0-9\\x21-\\x39\\x3B-\\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$")
|
||||
|
@ -150,7 +147,8 @@ pub fn is_valid_matrix_id(matrix_id: &str) -> LemmyResult<()> {
|
|||
}
|
||||
|
||||
pub fn is_valid_post_title(title: &str) -> LemmyResult<()> {
|
||||
let check = VALID_POST_TITLE_REGEX.is_match(title) && !has_newline(title);
|
||||
let length = title.trim().len();
|
||||
let check = (3..=200).contains(&length) && !has_newline(title);
|
||||
if !check {
|
||||
Err(LemmyErrorType::InvalidPostTitle.into())
|
||||
} else {
|
||||
|
@ -330,9 +328,13 @@ mod tests {
|
|||
fn regex_checks() {
|
||||
assert!(is_valid_post_title("hi").is_err());
|
||||
assert!(is_valid_post_title("him").is_ok());
|
||||
assert!(is_valid_post_title(" him ").is_ok());
|
||||
assert!(is_valid_post_title("n\n\n\n\nanother").is_err());
|
||||
assert!(is_valid_post_title("hello there!\n this is a test.").is_err());
|
||||
assert!(is_valid_post_title("hello there! this is a test.").is_ok());
|
||||
assert!(is_valid_post_title(("12345".repeat(40) + "x").as_str()).is_err());
|
||||
assert!(is_valid_post_title("12345".repeat(40).as_str()).is_ok());
|
||||
assert!(is_valid_post_title((("12345".repeat(40)) + " ").as_str()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a36865ee8ca3658fea31ba948b67b75a812e84fc
|
||||
Subproject commit 15815aea74fe97360afc03496b3ad62588649af0
|
|
@ -1,10 +1,17 @@
|
|||
# syntax=docker/dockerfile:1.6
|
||||
ARG RUST_VERSION=1.72.1
|
||||
ARG CARGO_BUILD_FEATURES=default
|
||||
ARG RUST_RELEASE_MODE=debug
|
||||
|
||||
ARG AMD_BUILDER_IMAGE=rust:${RUST_VERSION}
|
||||
ARG ARM_BUILDER_IMAGE=blackdex/rust-musl:aarch64-musl-stable-${RUST_VERSION}-openssl3
|
||||
ARG ARM_BUILDER_IMAGE="ghcr.io/raskyld/aarch64-lemmy-linux-gnu:v0.1.0"
|
||||
|
||||
ARG AMD_RUNNER_IMAGE=debian:bookworm-slim
|
||||
ARG ARM_RUNNER_IMAGE=alpine:3.18
|
||||
ARG ARM_RUNNER_IMAGE=debian:bookworm-slim
|
||||
|
||||
ARG UNAME=lemmy
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
|
||||
# AMD64 builder
|
||||
FROM --platform=${BUILDPLATFORM} ${AMD_BUILDER_IMAGE} AS build-amd64
|
||||
|
@ -21,91 +28,83 @@ RUN --mount=type=cache,target=/lemmy/target set -ex; \
|
|||
if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}"; \
|
||||
mv target/debug/lemmy_server ./lemmy; \
|
||||
mv target/"${RUST_RELEASE_MODE}"/lemmy_server ./lemmy_server; \
|
||||
fi
|
||||
|
||||
# Release build
|
||||
RUN set -ex; \
|
||||
RUN --mount=type=cache,target=/lemmy/target set -ex; \
|
||||
if [ "${RUST_RELEASE_MODE}" = "release" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
[ -z "$USE_RELEASE_CACHE" ] && cargo clean --release; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}" --release; \
|
||||
mv target/release/lemmy_server ./lemmy; \
|
||||
mv target/"${RUST_RELEASE_MODE}"/lemmy_server ./lemmy_server; \
|
||||
fi
|
||||
|
||||
# ARM64 builder
|
||||
# TODO currently broken
|
||||
# FROM --platform=${BUILDPLATFORM} ${ARM_BUILDER_IMAGE} as build-arm64
|
||||
# NB(raskyld): this is a hack to be able to COPY --from= this image, because the variable doesn't
|
||||
# seem to be expended in --form arg of COPY :(
|
||||
FROM --platform=linux/amd64 ${ARM_BUILDER_IMAGE} AS build-arm64
|
||||
|
||||
# ENV DEBIAN_FRONTEND=noninteractive
|
||||
# ENV CARGO_HOME=/root/.cargo
|
||||
# ENV PQ_LIB_DIR=/usr/local/musl/pq15/lib
|
||||
ARG RUST_RELEASE_MODE
|
||||
ARG CARGO_BUILD_FEATURES
|
||||
|
||||
# RUN apt update && apt install -y \
|
||||
# --no-install-recommends \
|
||||
# git
|
||||
WORKDIR /home/lemmy/src
|
||||
USER 10001:10001
|
||||
|
||||
# RUN mkdir -pv "${CARGO_HOME}" && \
|
||||
# rustup set profile minimal && \
|
||||
# rustup target add aarch64-unknown-linux-musl
|
||||
COPY --chown=lemmy:lemmy . ./
|
||||
|
||||
# ARG CARGO_BUILD_FEATURES
|
||||
# ARG RUST_RELEASE_MODE
|
||||
ENV PATH="/home/lemmy/.cargo/bin:${PATH}"
|
||||
ENV RUST_RELEASE_MODE=${RUST_RELEASE_MODE} \
|
||||
CARGO_BUILD_FEATURES=${CARGO_BUILD_FEATURES}
|
||||
|
||||
# WORKDIR /lemmy
|
||||
# Debug build
|
||||
RUN --mount=type=cache,target=./target,uid=10001,gid=10001 set -ex; \
|
||||
if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}"; \
|
||||
mv "./target/$CARGO_BUILD_TARGET/$RUST_RELEASE_MODE/lemmy_server" /home/lemmy/lemmy_server; \
|
||||
fi
|
||||
|
||||
# COPY . ./
|
||||
# Release build
|
||||
RUN --mount=type=cache,target=./target,uid=10001,gid=10001 set -ex; \
|
||||
if [ "${RUST_RELEASE_MODE}" = "release" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
[ -z "$USE_RELEASE_CACHE" ] && cargo clean --release; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}" --release; \
|
||||
mv "./target/$CARGO_BUILD_TARGET/$RUST_RELEASE_MODE/lemmy_server" /home/lemmy/lemmy_server; \
|
||||
fi
|
||||
|
||||
# # Debug build
|
||||
# RUN --mount=type=cache,target=/lemmy/target set -ex; \
|
||||
# if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \
|
||||
# echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
# cargo build --target=aarch64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}"; \
|
||||
# mv target/aarch64-unknown-linux-musl/debug/lemmy_server ./lemmy; \
|
||||
# fi
|
||||
|
||||
# # Release build
|
||||
# RUN set -ex; \
|
||||
# if [ "${RUST_RELEASE_MODE}" = "release" ]; then \
|
||||
# echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
# cargo build --target=aarch64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}" --release; \
|
||||
# mv target/aarch64-unknown-linux-musl/release/lemmy_server ./lemmy; \
|
||||
# fi
|
||||
|
||||
## Final image
|
||||
FROM ${AMD_RUNNER_IMAGE}
|
||||
# amd64 base runner
|
||||
FROM ${AMD_RUNNER_IMAGE} AS runner-linux-amd64
|
||||
|
||||
# Federation needs CA certificates
|
||||
RUN apt update && apt install -y libssl-dev libpq-dev ca-certificates
|
||||
|
||||
# Debian / Ubuntu non-root user creds
|
||||
ARG UNAME=lemmy
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
RUN groupadd -g $GID -o $UNAME
|
||||
RUN useradd -m -u $UID -g $GID -o -s /bin/bash $UNAME
|
||||
COPY --from=build-amd64 --chmod=0755 /lemmy/lemmy_server /usr/local/bin
|
||||
|
||||
# arm base runner
|
||||
FROM ${ARM_RUNNER_IMAGE} AS runner-linux-arm64
|
||||
|
||||
RUN apt update && apt install -y ca-certificates libssl-dev libpq-dev
|
||||
|
||||
COPY --from=build-arm64 --chmod=0755 /home/lemmy/lemmy_server /usr/local/bin
|
||||
|
||||
# Final image that use a base runner based on the target OS and ARCH
|
||||
FROM runner-${TARGETOS}-${TARGETARCH}
|
||||
|
||||
LABEL org.opencontainers.image.authors="The Lemmy Authors"
|
||||
LABEL org.opencontainers.image.source="https://github.com/LemmyNet/lemmy"
|
||||
LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later"
|
||||
LABEL org.opencontainers.image.description="A link aggregator and forum for the fediverse"
|
||||
|
||||
ARG UNAME
|
||||
ARG GID
|
||||
ARG UID
|
||||
|
||||
RUN groupadd -g ${GID} -o ${UNAME} && \
|
||||
useradd -m -u ${UID} -g ${GID} -o -s /bin/bash ${UNAME}
|
||||
USER $UNAME
|
||||
|
||||
COPY --from=build-amd64 /lemmy/lemmy ./
|
||||
CMD ["./lemmy"]
|
||||
ENTRYPOINT ["lemmy_server"]
|
||||
EXPOSE 8536
|
||||
STOPSIGNAL SIGTERM
|
||||
|
||||
## Arm Runner
|
||||
# FROM --platform=${BUILDPLATFORM} ${ARM_RUNNER_IMAGE}
|
||||
|
||||
# ARG UNAME=lemmy
|
||||
# ARG UID=1000
|
||||
# ARG GID=1000
|
||||
|
||||
# RUN apk add --no-cache ca-certificates
|
||||
|
||||
# COPY --from=build-arm64 --chmod=0755 /lemmy/lemmy /usr/local/bin
|
||||
|
||||
# RUN addgroup -S -g ${GID} ${UNAME} && \
|
||||
# adduser -S -H -D -G ${UNAME} -u ${UID} -g "" -s /sbin/nologin ${UNAME}
|
||||
# USER $UNAME
|
||||
|
||||
# CMD ["lemmy"]
|
||||
# EXPOSE 8536
|
||||
# STOPSIGNAL SIGTERM
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Building Lemmy Images
|
||||
|
||||
Lemmy's images are meant to be **built** on `linux/amd64`,
|
||||
but they can be **executed** on both `linux/amd64` and `linux/arm64`.
|
||||
|
||||
To do so we need to use a _cross toolchain_ whose goal is to build
|
||||
**from** amd64 **to** arm64.
|
||||
|
||||
Namely, we need to link the _lemmy_server_ with `pq` and `openssl`
|
||||
shared libraries and a few others, and they need to be in `arm64`,
|
||||
indeed.
|
||||
|
||||
The toolchain we use to cross-compile is specifically tailored for
|
||||
Lemmy's needs, see [the image repository][image-repo].
|
||||
|
||||
#### References
|
||||
|
||||
- [The Linux Documentation Project on Shared Libraries][tldp-lib]
|
||||
|
||||
[tldp-lib]: https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
|
||||
[image-repo]: https://github.com/raskyld/lemmy-cross-toolchains
|
|
@ -25,7 +25,7 @@ services:
|
|||
|
||||
lemmy:
|
||||
# use "image" to pull down an already compiled lemmy. make sure to comment out "build".
|
||||
# image: dessalines/lemmy:0.18.4
|
||||
# image: dessalines/lemmy:0.19.0
|
||||
# platform: linux/x86_64 # no arm64 support. uncomment platform if using m1.
|
||||
# use "build" to build your local lemmy server image for development. make sure to comment out "image".
|
||||
# run: docker compose up --build
|
||||
|
@ -43,8 +43,8 @@ services:
|
|||
- RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
|
||||
- RUST_BACKTRACE=full
|
||||
ports:
|
||||
# prometheus metrics available at the path /metrics on port 10002 by default
|
||||
# enable prometheus metrics by setting the CARGO_BUILD_FEATURES build arg above to "prometheus-metrics"
|
||||
# prometheus metrics can be enabled with the `prometheus` config option. they are available on
|
||||
# port 10002, path /metrics by default
|
||||
- "10002:10002"
|
||||
volumes:
|
||||
- ./lemmy.hjson:/config/config.hjson:Z
|
||||
|
@ -55,7 +55,7 @@ services:
|
|||
|
||||
lemmy-ui:
|
||||
# use "image" to pull down an already compiled lemmy-ui. make sure to comment out "build".
|
||||
image: dessalines/lemmy-ui:0.19.0-rc.3
|
||||
image: dessalines/lemmy-ui:0.19.0
|
||||
# platform: linux/x86_64 # no arm64 support. uncomment platform if using m1.
|
||||
# use "build" to build your local lemmy ui image for development. make sure to comment out "image".
|
||||
# run: docker compose up --build
|
||||
|
@ -77,7 +77,7 @@ services:
|
|||
init: true
|
||||
|
||||
pictrs:
|
||||
image: asonix/pictrs:0.5.0-alpha.20
|
||||
image: asonix/pictrs:0.5.0-rc.2
|
||||
# this needs to match the pictrs url in lemmy.hjson
|
||||
hostname: pictrs
|
||||
# we can set options to pictrs like this, here we set max. image size and forced format for conversion
|
||||
|
|
|
@ -2,7 +2,7 @@ version: "3.7"
|
|||
|
||||
x-ui-default: &ui-default
|
||||
init: true
|
||||
image: dessalines/lemmy-ui:0.19.0-rc.3
|
||||
image: dessalines/lemmy-ui:0.19.0
|
||||
# assuming lemmy-ui is cloned besides lemmy directory
|
||||
# build:
|
||||
# context: ../../../lemmy-ui
|
||||
|
@ -49,7 +49,7 @@ services:
|
|||
|
||||
pictrs:
|
||||
restart: always
|
||||
image: asonix/pictrs:0.4.0-beta.19
|
||||
image: asonix/pictrs:0.5.0-rc.2
|
||||
user: 991:991
|
||||
volumes:
|
||||
- ./volumes/pictrs_alpha:/mnt:Z
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
ALTER TABLE community_aggregates
|
||||
ALTER COLUMN hot_rank SET DEFAULT 0.1728;
|
||||
|
||||
ALTER TABLE comment_aggregates
|
||||
ALTER COLUMN hot_rank SET DEFAULT 0.1728;
|
||||
|
||||
ALTER TABLE post_aggregates
|
||||
ALTER COLUMN hot_rank SET DEFAULT 0.1728,
|
||||
ALTER COLUMN hot_rank_active SET DEFAULT 0.1728,
|
||||
ALTER COLUMN scaled_rank SET DEFAULT 0.3621;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-- Change the hot_ranks to a miniscule number, so that new / fetched content
|
||||
-- won't crowd out existing content.
|
||||
--
|
||||
-- They must be non-zero, in order for them to be picked up by the hot_ranks updater.
|
||||
-- See https://github.com/LemmyNet/lemmy/issues/4178
|
||||
ALTER TABLE community_aggregates
|
||||
ALTER COLUMN hot_rank SET DEFAULT 0.0001;
|
||||
|
||||
ALTER TABLE comment_aggregates
|
||||
ALTER COLUMN hot_rank SET DEFAULT 0.0001;
|
||||
|
||||
ALTER TABLE post_aggregates
|
||||
ALTER COLUMN hot_rank SET DEFAULT 0.0001,
|
||||
ALTER COLUMN hot_rank_active SET DEFAULT 0.0001,
|
||||
ALTER COLUMN scaled_rank SET DEFAULT 0.0001;
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
CREATE OR REPLACE FUNCTION community_aggregates_activity (i text)
|
||||
RETURNS TABLE (
|
||||
count_ bigint,
|
||||
community_id_ integer)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN query
|
||||
SELECT
|
||||
count(*),
|
||||
community_id
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN post p ON c.post_id = p.id
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE) a
|
||||
GROUP BY
|
||||
community_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION site_aggregates_activity (i text)
|
||||
RETURNS integer
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
count_ integer;
|
||||
BEGIN
|
||||
SELECT
|
||||
count(*) INTO count_
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN person u ON c.creator_id = u.id
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND u.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person u ON p.creator_id = u.id
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND u.local = TRUE
|
||||
AND pe.bot_account = FALSE) a;
|
||||
RETURN count_;
|
||||
END;
|
||||
$$;
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
-- Edit community aggregates to include voters as active users
|
||||
CREATE OR REPLACE FUNCTION community_aggregates_activity (i text)
|
||||
RETURNS TABLE (
|
||||
count_ bigint,
|
||||
community_id_ integer)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN query
|
||||
SELECT
|
||||
count(*),
|
||||
community_id
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN post p ON c.post_id = p.id
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
pl.person_id,
|
||||
p.community_id
|
||||
FROM
|
||||
post_like pl
|
||||
INNER JOIN post p ON pl.post_id = p.id
|
||||
INNER JOIN person pe ON pl.person_id = pe.id
|
||||
WHERE
|
||||
pl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
cl.person_id,
|
||||
p.community_id
|
||||
FROM
|
||||
comment_like cl
|
||||
INNER JOIN post p ON cl.post_id = p.id
|
||||
INNER JOIN person pe ON cl.person_id = pe.id
|
||||
WHERE
|
||||
cl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE) a
|
||||
GROUP BY
|
||||
community_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Edit site aggregates to include voters and people who have read posts as active users
|
||||
CREATE OR REPLACE FUNCTION site_aggregates_activity (i text)
|
||||
RETURNS integer
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
count_ integer;
|
||||
BEGIN
|
||||
SELECT
|
||||
count(*) INTO count_
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
pl.person_id
|
||||
FROM
|
||||
post_like pl
|
||||
INNER JOIN person pe ON pl.person_id = pe.id
|
||||
WHERE
|
||||
pl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
cl.person_id
|
||||
FROM
|
||||
comment_like cl
|
||||
INNER JOIN person pe ON cl.person_id = pe.id
|
||||
WHERE
|
||||
cl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE) a;
|
||||
RETURN count_;
|
||||
END;
|
||||
$$;
|
||||
|
|
@ -7,9 +7,12 @@ CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|||
|
||||
cd $CWD/../
|
||||
|
||||
find migrations -type f -name "*.sql" -print0 | while read -d $'\0' FILE
|
||||
do
|
||||
TMP_FILE="/tmp/tmp_pg_format.sql"
|
||||
pg_format $FILE > $TMP_FILE
|
||||
diff -u $FILE $TMP_FILE
|
||||
done
|
||||
# Copy the files to a temp dir
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cp -a migrations/. $TMP_DIR
|
||||
|
||||
# Format the new files
|
||||
find $TMP_DIR -type f -name '*.sql' -exec pg_format -i {} +
|
||||
|
||||
# Diff the directories
|
||||
diff -r migrations $TMP_DIR
|
||||
|
|
56
src/lib.rs
56
src/lib.rs
|
@ -16,14 +16,15 @@ use activitypub_federation::config::{FederationConfig, FederationMiddleware};
|
|||
use actix_cors::Cors;
|
||||
use actix_web::{
|
||||
dev::{ServerHandle, ServiceResponse},
|
||||
middleware::{self, ErrorHandlerResponse, ErrorHandlers},
|
||||
middleware::{self, Condition, ErrorHandlerResponse, ErrorHandlers},
|
||||
web::Data,
|
||||
App,
|
||||
HttpResponse,
|
||||
HttpServer,
|
||||
Result,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
use actix_web_prom::PrometheusMetricsBuilder;
|
||||
use clap::Parser;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
lemmy_db_views::structs::SiteView,
|
||||
|
@ -48,7 +49,9 @@ use lemmy_utils::{
|
|||
rate_limit::RateLimitCell,
|
||||
response::jsonify_plain_text_errors,
|
||||
settings::{structs::Settings, SETTINGS},
|
||||
version,
|
||||
};
|
||||
use prometheus::default_registry;
|
||||
use prometheus_metrics::serve_prometheus;
|
||||
use reqwest_middleware::ClientBuilder;
|
||||
use reqwest_tracing::TracingMiddleware;
|
||||
|
@ -69,24 +72,23 @@ use url::Url;
|
|||
long_about = "A link aggregator for the fediverse.\n\nThis is the Lemmy backend API server. This will connect to a PostgreSQL database, run any pending migrations and start accepting API requests."
|
||||
)]
|
||||
pub struct CmdArgs {
|
||||
/// Don't run scheduled tasks.
|
||||
///
|
||||
/// If you are running multiple Lemmy server processes, you probably want to disable scheduled tasks on
|
||||
/// all but one of the processes, to avoid running the tasks more often than intended.
|
||||
#[arg(long, default_value_t = false)]
|
||||
/// Disables running scheduled tasks.
|
||||
///
|
||||
/// If you are running multiple Lemmy server processes,
|
||||
/// you probably want to disable scheduled tasks on all but one of the processes,
|
||||
/// to avoid running the tasks more often than intended.
|
||||
disable_scheduled_tasks: bool,
|
||||
/// Whether or not to run the HTTP server.
|
||||
/// Disables the HTTP server.
|
||||
///
|
||||
/// This can be used to run a Lemmy server process that only runs scheduled tasks.
|
||||
#[arg(long, default_value_t = true, action=ArgAction::Set)]
|
||||
http_server: bool,
|
||||
/// Whether or not to emit outgoing ActivityPub messages.
|
||||
/// This can be used to run a Lemmy server process that only performs scheduled tasks or activity sending.
|
||||
#[arg(long, default_value_t = false)]
|
||||
disable_http_server: bool,
|
||||
/// Disable sending outgoing ActivityPub messages.
|
||||
///
|
||||
/// Set to true for a simple setup. Only set to false for horizontally scaled setups.
|
||||
/// See https://join-lemmy.org/docs/administration/horizontal_scaling.html for detail.
|
||||
#[arg(long, default_value_t = true, action=ArgAction::Set)]
|
||||
federate_activities: bool,
|
||||
/// Only pass this for horizontally scaled setups.
|
||||
/// See https://join-lemmy.org/docs/administration/horizontal_scaling.html for details.
|
||||
#[arg(long, default_value_t = false)]
|
||||
disable_activity_sending: bool,
|
||||
/// The index of this outgoing federation process.
|
||||
///
|
||||
/// Defaults to 1/1. If you want to split the federation workload onto n servers, run each server 1≤i≤n with these args:
|
||||
|
@ -106,9 +108,12 @@ pub struct CmdArgs {
|
|||
|
||||
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
|
||||
pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||
// Print version number to log
|
||||
println!("Lemmy v{}", version::VERSION);
|
||||
|
||||
// return error 503 while running db migrations and startup tasks
|
||||
let mut startup_server_handle = None;
|
||||
if args.http_server {
|
||||
if !args.disable_http_server {
|
||||
startup_server_handle = Some(create_startup_server()?);
|
||||
}
|
||||
|
||||
|
@ -131,7 +136,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
|||
let federation_enabled = local_site.federation_enabled;
|
||||
|
||||
if federation_enabled {
|
||||
println!("federation enabled, host is {}", &SETTINGS.hostname);
|
||||
println!("Federation enabled, host is {}", &SETTINGS.hostname);
|
||||
}
|
||||
|
||||
check_private_instance_and_federation_enabled(&local_site)?;
|
||||
|
@ -142,7 +147,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
|||
let rate_limit_cell = RateLimitCell::new(rate_limit_config);
|
||||
|
||||
println!(
|
||||
"Starting http server at {}:{}",
|
||||
"Starting HTTP server at {}:{}",
|
||||
SETTINGS.bind, SETTINGS.port
|
||||
);
|
||||
|
||||
|
@ -188,7 +193,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
|||
let request_data = federation_config.to_request_data();
|
||||
let outgoing_activities_task = tokio::task::spawn(handle_outgoing_activities(request_data));
|
||||
|
||||
let server = if args.http_server {
|
||||
let server = if !args.disable_http_server {
|
||||
if let Some(startup_server_handle) = startup_server_handle {
|
||||
startup_server_handle.stop(true).await;
|
||||
}
|
||||
|
@ -201,7 +206,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let federate = args.federate_activities.then(|| {
|
||||
let federate = (!args.disable_activity_sending).then(|| {
|
||||
start_stop_federation_workers_cancellable(
|
||||
Opts {
|
||||
process_index: args.federate_process_index,
|
||||
|
@ -265,7 +270,6 @@ fn create_http_server(
|
|||
) -> Result<ServerHandle, LemmyError> {
|
||||
// this must come before the HttpServer creation
|
||||
// creates a middleware that populates http metrics for each path, method, and status code
|
||||
#[cfg(feature = "prometheus-metrics")]
|
||||
let prom_api_metrics = PrometheusMetricsBuilder::new("lemmy_api")
|
||||
.registry(default_registry().clone())
|
||||
.build()
|
||||
|
@ -295,7 +299,11 @@ fn create_http_server(
|
|||
.app_data(Data::new(context.clone()))
|
||||
.app_data(Data::new(rate_limit_cell.clone()))
|
||||
.wrap(FederationMiddleware::new(federation_config.clone()))
|
||||
.wrap(SessionMiddleware::new(context.clone()));
|
||||
.wrap(SessionMiddleware::new(context.clone()))
|
||||
.wrap(Condition::new(
|
||||
SETTINGS.prometheus.is_some(),
|
||||
prom_api_metrics.clone(),
|
||||
));
|
||||
|
||||
// The routes
|
||||
app
|
||||
|
@ -325,7 +333,7 @@ fn cors_config(settings: &Settings) -> Cors {
|
|||
(Some(origin), false) => {
|
||||
// Need to call send_wildcard() explicitly, passing this into allowed_origin() results in error
|
||||
if cors_origin_setting.as_deref() == Some("*") {
|
||||
Cors::default().send_wildcard()
|
||||
Cors::default().allow_any_origin().send_wildcard()
|
||||
} else {
|
||||
Cors::default()
|
||||
.allowed_origin(&origin)
|
||||
|
|
|
@ -18,13 +18,10 @@ pub async fn main() -> Result<(), LemmyError> {
|
|||
.port()
|
||||
.unwrap_or(8080);
|
||||
let pictrs_address = ["127.0.0.1", &pictrs_port.to_string()].join(":");
|
||||
pict_rs::ConfigSource::memory(serde_json::json!({
|
||||
let pictrs_config = pict_rs::ConfigSource::memory(serde_json::json!({
|
||||
"server": {
|
||||
"address": pictrs_address
|
||||
},
|
||||
"old_db": {
|
||||
"path": "./pictrs/old"
|
||||
},
|
||||
"repo": {
|
||||
"type": "sled",
|
||||
"path": "./pictrs/sled-repo"
|
||||
|
@ -36,7 +33,7 @@ pub async fn main() -> Result<(), LemmyError> {
|
|||
}))
|
||||
.init::<&str>(None)
|
||||
.expect("initialize pictrs config");
|
||||
let (lemmy, pictrs) = tokio::join!(start_lemmy_server(args), pict_rs::run());
|
||||
let (lemmy, pictrs) = tokio::join!(start_lemmy_server(args), pictrs_config.run_on_localset());
|
||||
lemmy?;
|
||||
pictrs.expect("run pictrs");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue