Merge branch 'main' into image-api-rework

This commit is contained in:
Felix Ableitner 2025-01-13 12:32:14 +01:00
commit 4b940431b2
88 changed files with 640 additions and 281 deletions

View file

@ -95,6 +95,15 @@ steps:
when: when:
- event: pull_request - event: pull_request
cargo_clippy:
image: *rust_image
environment:
CARGO_HOME: .cargo_home
commands:
- rustup component add clippy
- cargo clippy --workspace --tests --all-targets -- -D warnings
when: *slow_check_paths
# `DROP OWNED` doesn't work for default user # `DROP OWNED` doesn't work for default user
create_database_user: create_database_user:
image: postgres:16-alpine image: postgres:16-alpine
@ -107,6 +116,68 @@ steps:
- psql -c "CREATE USER lemmy WITH PASSWORD 'password' SUPERUSER;" - psql -c "CREATE USER lemmy WITH PASSWORD 'password' SUPERUSER;"
when: *slow_check_paths when: *slow_check_paths
cargo_test:
image: *rust_image
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1"
CARGO_HOME: .cargo_home
LEMMY_TEST_FAST_FEDERATION: "1"
LEMMY_CONFIG_LOCATION: ../../config/config.hjson
commands:
# Install pg_dump for the schema setup test (must match server version)
- apt update && apt install -y lsb-release
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
- apt update && apt install -y postgresql-client-16
# Run tests
- cargo test --workspace --no-fail-fast
when: *slow_check_paths
check_ts_bindings:
image: *rust_image
environment:
CARGO_HOME: .cargo_home
commands:
- ./scripts/ts_bindings_check.sh
when:
- event: pull_request
# make sure api builds with default features (used by other crates relying on lemmy api)
check_api_common_default_features:
image: *rust_image
environment:
CARGO_HOME: .cargo_home
commands:
- cargo check --package lemmy_api_common
when: *slow_check_paths
lemmy_api_common_doesnt_depend_on_diesel:
image: *rust_image
environment:
CARGO_HOME: .cargo_home
commands:
- "! cargo tree -p lemmy_api_common --no-default-features -i diesel"
when: *slow_check_paths
lemmy_api_common_works_with_wasm:
image: *rust_image
environment:
CARGO_HOME: .cargo_home
commands:
- "rustup target add wasm32-unknown-unknown"
- "cargo check --target wasm32-unknown-unknown -p lemmy_api_common"
when: *slow_check_paths
check_defaults_hjson_updated:
image: *rust_image
environment:
CARGO_HOME: .cargo_home
commands:
- ./scripts/update_config_defaults.sh config/defaults_current.hjson
- diff config/defaults.hjson config/defaults_current.hjson
when: *slow_check_paths
cargo_build: cargo_build:
image: *rust_image image: *rust_image
environment: environment:
@ -116,6 +187,32 @@ steps:
- mv target/debug/lemmy_server target/lemmy_server - mv target/debug/lemmy_server target/lemmy_server
when: *slow_check_paths when: *slow_check_paths
check_diesel_schema:
image: *rust_image
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1"
CARGO_HOME: .cargo_home
commands:
- cp crates/db_schema/src/schema.rs tmp.schema
- target/lemmy_server migration --all run
- <<: *install_diesel_cli
- diesel print-schema
- diff tmp.schema crates/db_schema/src/schema.rs
when: *slow_check_paths
check_db_perf_tool:
image: *rust_image
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1"
CARGO_HOME: .cargo_home
commands:
# same as scripts/db_perf.sh but without creating a new database server
- cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1
when: *slow_check_paths
run_federation_tests: run_federation_tests:
image: node:22-bookworm-slim image: node:22-bookworm-slim
environment: environment:
@ -127,7 +224,7 @@ steps:
- bash api_tests/prepare-drone-federation-test.sh - bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/ - cd api_tests/
- pnpm i - pnpm i
- pnpm api-test-image - pnpm api-test
when: *slow_check_paths when: *slow_check_paths
federation_tests_server_output: federation_tests_server_output:

View file

@ -11,7 +11,7 @@ killall -s1 lemmy_server || true
popd popd
pnpm i pnpm i
pnpm api-test-image || true pnpm api-test || true
killall -s1 lemmy_server || true killall -s1 lemmy_server || true
killall -s1 pict-rs || true killall -s1 pict-rs || true

View file

@ -104,7 +104,7 @@ test("Upload image and delete it", async () => {
expect(deletedListAllMediaRes.images.length).toBe(previousThumbnails - 1); expect(deletedListAllMediaRes.images.length).toBe(previousThumbnails - 1);
}); });
test.only("Purge user, uploaded image removed", async () => { test("Purge user, uploaded image removed", async () => {
let user = await registerUser(alphaImage, alphaUrl); let user = await registerUser(alphaImage, alphaUrl);
// upload test image // upload test image
@ -128,7 +128,6 @@ test.only("Purge user, uploaded image removed", async () => {
}; };
const delete_ = await alphaImage.purgePerson(purgeForm); const delete_ = await alphaImage.purgePerson(purgeForm);
expect(delete_.success).toBe(true); expect(delete_.success).toBe(true);
console.log(upload.image_url + " should be purged");
// ensure that image is deleted // ensure that image is deleted
const response2 = await fetch(upload.image_url ?? ""); const response2 = await fetch(upload.image_url ?? "");

View file

@ -5,7 +5,7 @@ import {
CommunityId, CommunityId,
CommunityVisibility, CommunityVisibility,
CreatePrivateMessageReport, CreatePrivateMessageReport,
DeleteImageParams, DeleteImage,
EditCommunity, EditCommunity,
GetCommunityPendingFollowsCountResponse, GetCommunityPendingFollowsCountResponse,
GetReplies, GetReplies,
@ -714,6 +714,8 @@ export async function saveUserSettingsBio(
export async function saveUserSettingsFederated( export async function saveUserSettingsFederated(
api: LemmyHttp, api: LemmyHttp,
): Promise<SuccessResponse> { ): Promise<SuccessResponse> {
let avatar = sampleImage;
let banner = sampleImage;
let bio = "a changed bio"; let bio = "a changed bio";
let form: SaveUserSettings = { let form: SaveUserSettings = {
show_nsfw: false, show_nsfw: false,
@ -721,6 +723,8 @@ export async function saveUserSettingsFederated(
default_post_sort_type: "Hot", default_post_sort_type: "Hot",
default_listing_type: "All", default_listing_type: "All",
interface_language: "", interface_language: "",
avatar,
banner,
display_name: "user321", display_name: "user321",
show_avatars: false, show_avatars: false,
send_notifications_to_email: false, send_notifications_to_email: false,
@ -932,19 +936,17 @@ export async function deleteAllImages(api: LemmyHttp) {
const imagesRes = await api.listAllMedia({ const imagesRes = await api.listAllMedia({
limit: imageFetchLimit, limit: imageFetchLimit,
}); });
const forms = imagesRes.images.map(image => { Promise.all(
const form: DeleteImageParams = { imagesRes.images
.map(image => {
const form: DeleteImage = {
token: image.local_image.pictrs_delete_token, token: image.local_image.pictrs_delete_token,
filename: image.local_image.pictrs_alias, filename: image.local_image.pictrs_alias,
}; };
return form; return form;
}); })
for (const form of forms) { .map(form => api.deleteImage(form)),
console.log(
"delete image: token=" + form.token + ", name=" + form.filename,
); );
await api.deleteImage(form);
}
} }
export async function unfollows() { export async function unfollows() {

View file

@ -20,11 +20,6 @@
url: "http://localhost:8080/" url: "http://localhost:8080/"
# Set a custom pictrs API key. ( Required for deleting images ) # Set a custom pictrs API key. ( Required for deleting images )
api_key: "string" api_key: "string"
# Backwards compatibility with 0.18.1. False is equivalent to `image_mode: None`, true is
# equivalent to `image_mode: StoreLinkPreviews`.
#
# To be removed in 0.20
cache_external_link_previews: true
# Specifies how to handle remote images, so that users don't have to connect directly to remote # Specifies how to handle remote images, so that users don't have to connect directly to remote
# servers. # servers.
image_mode: image_mode:
@ -38,7 +33,7 @@
# ensures that they can be reliably retrieved and can be resized using pict-rs APIs. However # ensures that they can be reliably retrieved and can be resized using pict-rs APIs. However
# it also increases storage usage. # it also increases storage usage.
# #
# This is the default behaviour, and also matches Lemmy 0.18. # This behaviour matches Lemmy 0.18.
"StoreLinkPreviews" "StoreLinkPreviews"
# or # or

View file

@ -385,7 +385,7 @@ pub async fn delete_image_from_pictrs(
async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> LemmyResult<Url> { async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> LemmyResult<Url> {
let pictrs_config = context.settings().pictrs()?; let pictrs_config = context.settings().pictrs()?;
match pictrs_config.image_mode() { match pictrs_config.image_mode {
PictrsImageMode::None => return Ok(image_url.clone()), PictrsImageMode::None => return Ok(image_url.clone()),
PictrsImageMode::ProxyAllImages => { PictrsImageMode::ProxyAllImages => {
return Ok(proxy_image_link(image_url.clone(), context).await?.into()) return Ok(proxy_image_link(image_url.clone(), context).await?.into())

View file

@ -1060,7 +1060,7 @@ pub async fn process_markdown(
markdown_check_for_blocked_urls(&text, url_blocklist)?; markdown_check_for_blocked_urls(&text, url_blocklist)?;
if context.settings().pictrs()?.image_mode() == PictrsImageMode::ProxyAllImages { if context.settings().pictrs()?.image_mode == PictrsImageMode::ProxyAllImages {
let (text, links) = markdown_rewrite_image_links(text); let (text, links) = markdown_rewrite_image_links(text);
RemoteImage::create(&mut context.pool(), links.clone()).await?; RemoteImage::create(&mut context.pool(), links.clone()).await?;
@ -1128,7 +1128,7 @@ async fn proxy_image_link_internal(
/// Rewrite a link to go through `/api/v4/image_proxy` endpoint. This is only for remote urls and /// Rewrite a link to go through `/api/v4/image_proxy` endpoint. This is only for remote urls and
/// if image_proxy setting is enabled. /// if image_proxy setting is enabled.
pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> { pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
proxy_image_link_internal(link, context.settings().pictrs()?.image_mode(), context).await proxy_image_link_internal(link, context.settings().pictrs()?.image_mode, context).await
} }
pub async fn proxy_image_link_opt_apub( pub async fn proxy_image_link_opt_apub(

View file

@ -3,7 +3,6 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://ds9.lemmy.ml/u/lemmy_alpha", "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"target": "http://enterprise.lemmy.ml/c/main", "target": "http://enterprise.lemmy.ml/c/main",
"type": "Block", "type": "Block",
"removeData": true, "removeData": true,

View file

@ -6,7 +6,6 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://ds9.lemmy.ml/u/lemmy_alpha", "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"target": "http://enterprise.lemmy.ml/c/main", "target": "http://enterprise.lemmy.ml/c/main",
"type": "Block", "type": "Block",
"removeData": true, "removeData": true,
@ -15,7 +14,6 @@
"id": "http://enterprise.lemmy.ml/activities/block/726f43ab-bd0e-4ab3-89c8-627e976f553c" "id": "http://enterprise.lemmy.ml/activities/block/726f43ab-bd0e-4ab3-89c8-627e976f553c"
}, },
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Undo", "type": "Undo",
"id": "http://enterprise.lemmy.ml/activities/undo/06a20ffb-3e32-42fb-8f4c-674b36d7c557" "id": "http://enterprise.lemmy.ml/activities/undo/06a20ffb-3e32-42fb-8f4c-674b36d7c557"
} }

View file

@ -5,6 +5,5 @@
"type": "Add", "type": "Add",
"actor": "https://ds9.lemmy.ml/u/lemmy_alpha", "actor": "https://ds9.lemmy.ml/u/lemmy_alpha",
"object": "https://ds9.lemmy.ml/post/2", "object": "https://ds9.lemmy.ml/post/2",
"target": "https://ds9.lemmy.ml/c/main/featured", "target": "https://ds9.lemmy.ml/c/main/featured"
"audience": "https://ds9.lemmy.ml/c/main"
} }

View file

@ -4,7 +4,6 @@
"object": "http://ds9.lemmy.ml/u/lemmy_alpha", "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
"target": "http://enterprise.lemmy.ml/c/main/moderators", "target": "http://enterprise.lemmy.ml/c/main/moderators",
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Add", "type": "Add",
"id": "http://enterprise.lemmy.ml/activities/add/ec069147-77c3-447f-88c8-0ef1df10403f" "id": "http://enterprise.lemmy.ml/activities/add/ec069147-77c3-447f-88c8-0ef1df10403f"
} }

View file

@ -4,6 +4,5 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://lemmy-alpha:8541/post/2", "object": "http://lemmy-alpha:8541/post/2",
"cc": ["http://lemmy-alpha:8541/c/main"], "cc": ["http://lemmy-alpha:8541/c/main"],
"type": "Lock", "type": "Lock"
"audience": "http://lemmy-alpha:8541/c/main"
} }

View file

@ -5,6 +5,5 @@
"type": "Remove", "type": "Remove",
"actor": "https://ds9.lemmy.ml/u/lemmy_alpha", "actor": "https://ds9.lemmy.ml/u/lemmy_alpha",
"object": "https://ds9.lemmy.ml/post/2", "object": "https://ds9.lemmy.ml/post/2",
"target": "https://ds9.lemmy.ml/c/main/featured", "target": "https://ds9.lemmy.ml/c/main/featured"
"audience": "https://ds9.lemmy.ml/c/main"
} }

View file

@ -5,6 +5,5 @@
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"type": "Remove", "type": "Remove",
"target": "http://enterprise.lemmy.ml/c/main/moderators", "target": "http://enterprise.lemmy.ml/c/main/moderators",
"audience": "http://enterprise.lemmy.ml/u/main",
"id": "http://enterprise.lemmy.ml/activities/remove/aab114f8-cfbd-4935-a5b7-e1a64603650d" "id": "http://enterprise.lemmy.ml/activities/remove/aab114f8-cfbd-4935-a5b7-e1a64603650d"
} }

View file

@ -1,7 +1,6 @@
{ {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha", "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": ["http://enterprise.lemmy.ml/c/main"], "to": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"object": "http://enterprise.lemmy.ml/post/7", "object": "http://enterprise.lemmy.ml/post/7",
"summary": "report this post", "summary": "report this post",
"type": "Flag", "type": "Flag",

View file

@ -8,10 +8,8 @@
"object": "http://lemmy-alpha:8541/post/2", "object": "http://lemmy-alpha:8541/post/2",
"cc": ["http://lemmy-alpha:8541/c/main"], "cc": ["http://lemmy-alpha:8541/c/main"],
"type": "Lock", "type": "Lock",
"id": "http://lemmy-alpha:8541/activities/lock/08b6fd3e-9ef3-4358-a987-8bb641f3e2c3", "id": "http://lemmy-alpha:8541/activities/lock/08b6fd3e-9ef3-4358-a987-8bb641f3e2c3"
"audience": "http://lemmy-alpha:8541/c/main"
}, },
"cc": ["http://lemmy-alpha:8541/c/main"], "cc": ["http://lemmy-alpha:8541/c/main"],
"type": "Undo", "type": "Undo"
"audience": "http://lemmy-alpha:8541/c/main"
} }

View file

@ -39,7 +39,6 @@
"updated": "2021-11-01T12:23:50.151874Z" "updated": "2021-11-01T12:23:50.151874Z"
}, },
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Update", "type": "Update",
"id": "http://ds9.lemmy.ml/activities/update/d3717cf5-096d-473f-9530-5d52f9d51f5f" "id": "http://ds9.lemmy.ml/activities/update/d3717cf5-096d-473f-9530-5d52f9d51f5f"
} }

View file

@ -10,7 +10,6 @@
"http://enterprise.lemmy.ml/c/main", "http://enterprise.lemmy.ml/c/main",
"http://ds9.lemmy.ml/u/lemmy_alpha" "http://ds9.lemmy.ml/u/lemmy_alpha"
], ],
"audience": "http://ds9.lemmy.ml/u/lemmy_alpha",
"content": "hello", "content": "hello",
"mediaType": "text/html", "mediaType": "text/html",
"source": { "source": {
@ -24,7 +23,6 @@
"http://enterprise.lemmy.ml/c/main", "http://enterprise.lemmy.ml/c/main",
"http://ds9.lemmy.ml/u/lemmy_alpha" "http://ds9.lemmy.ml/u/lemmy_alpha"
], ],
"audience": "http://ds9.lemmy.ml/u/lemmy_alpha",
"tag": [ "tag": [
{ {
"href": "http://ds9.lemmy.ml/u/lemmy_alpha", "href": "http://ds9.lemmy.ml/u/lemmy_alpha",

View file

@ -9,7 +9,6 @@
"http://enterprise.lemmy.ml/c/main", "http://enterprise.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public" "https://www.w3.org/ns/activitystreams#Public"
], ],
"audience": "https://enterprise.lemmy.ml/c/main",
"name": "test post", "name": "test post",
"content": "<p>test body</p>\n", "content": "<p>test body</p>\n",
"mediaType": "text/html", "mediaType": "text/html",
@ -31,7 +30,6 @@
"published": "2021-10-29T15:10:51.557399Z" "published": "2021-10-29T15:10:51.557399Z"
}, },
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "https://enterprise.lemmy.ml/c/main",
"type": "Create", "type": "Create",
"id": "http://ds9.lemmy.ml/activities/create/eee6a57a-622f-464d-b560-73ae1fcd3ddf" "id": "http://ds9.lemmy.ml/activities/create/eee6a57a-622f-464d-b560-73ae1fcd3ddf"
} }

View file

@ -9,7 +9,6 @@
"http://enterprise.lemmy.ml/c/main", "http://enterprise.lemmy.ml/c/main",
"https://www.w3.org/ns/activitystreams#Public" "https://www.w3.org/ns/activitystreams#Public"
], ],
"audience": "https://enterprise.lemmy.ml/c/main",
"name": "test post 1", "name": "test post 1",
"content": "<p>test body</p>\n", "content": "<p>test body</p>\n",
"mediaType": "text/html", "mediaType": "text/html",
@ -28,7 +27,6 @@
"updated": "2021-10-29T15:11:35.976374Z" "updated": "2021-10-29T15:11:35.976374Z"
}, },
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "https://enterprise.lemmy.ml/c/main",
"type": "Update", "type": "Update",
"id": "http://ds9.lemmy.ml/activities/update/ab360117-e165-4de4-b7fc-906b62c98631" "id": "http://ds9.lemmy.ml/activities/update/ab360117-e165-4de4-b7fc-906b62c98631"
} }

View file

@ -3,7 +3,6 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://ds9.lemmy.ml/post/1", "object": "http://ds9.lemmy.ml/post/1",
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Delete", "type": "Delete",
"id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12" "id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12"
} }

View file

@ -3,7 +3,6 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://ds9.lemmy.ml/comment/1", "object": "http://ds9.lemmy.ml/comment/1",
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Delete", "type": "Delete",
"summary": "bad comment", "summary": "bad comment",
"id": "http://enterprise.lemmy.ml/activities/delete/42ca1a79-f99e-4518-a2ca-ba2df221eb5e" "id": "http://enterprise.lemmy.ml/activities/delete/42ca1a79-f99e-4518-a2ca-ba2df221eb5e"

View file

@ -6,12 +6,10 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://ds9.lemmy.ml/post/1", "object": "http://ds9.lemmy.ml/post/1",
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Delete", "type": "Delete",
"id": "http://ds9.lemmy.ml/activities/delete/b13cca96-7737-41e1-9769-8fbf972b3509" "id": "http://ds9.lemmy.ml/activities/delete/b13cca96-7737-41e1-9769-8fbf972b3509"
}, },
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Undo", "type": "Undo",
"id": "http://ds9.lemmy.ml/activities/undo/5e939cfb-b8a1-4de8-950f-9d684e9162b9" "id": "http://ds9.lemmy.ml/activities/undo/5e939cfb-b8a1-4de8-950f-9d684e9162b9"
} }

View file

@ -6,13 +6,11 @@
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": "http://ds9.lemmy.ml/comment/1", "object": "http://ds9.lemmy.ml/comment/1",
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Delete", "type": "Delete",
"summary": "bad comment", "summary": "bad comment",
"id": "http://enterprise.lemmy.ml/activities/delete/2598435c-87a3-49cd-81f3-a44b03b7af9d" "id": "http://enterprise.lemmy.ml/activities/delete/2598435c-87a3-49cd-81f3-a44b03b7af9d"
}, },
"cc": ["http://enterprise.lemmy.ml/c/main"], "cc": ["http://enterprise.lemmy.ml/c/main"],
"audience": "http://enterprise.lemmy.ml/u/main",
"type": "Undo", "type": "Undo",
"id": "http://enterprise.lemmy.ml/activities/undo/a850cf21-3866-4b3a-b80b-56aa00997fee" "id": "http://enterprise.lemmy.ml/activities/undo/a850cf21-3866-4b3a-b80b-56aa00997fee"
} }

View file

@ -1,7 +1,6 @@
{ {
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta", "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"object": "http://ds9.lemmy.ml/post/1", "object": "http://ds9.lemmy.ml/post/1",
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Dislike", "type": "Dislike",
"id": "http://enterprise.lemmy.ml/activities/dislike/64d40d40-a829-43a5-8247-1fb595b3ca1c" "id": "http://enterprise.lemmy.ml/activities/dislike/64d40d40-a829-43a5-8247-1fb595b3ca1c"
} }

View file

@ -1,7 +1,6 @@
{ {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha", "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"object": "http://ds9.lemmy.ml/comment/1", "object": "http://ds9.lemmy.ml/comment/1",
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Like", "type": "Like",
"id": "http://ds9.lemmy.ml/activities/like/fd61d070-7382-46a9-b2b7-6bb253732877" "id": "http://ds9.lemmy.ml/activities/like/fd61d070-7382-46a9-b2b7-6bb253732877"
} }

View file

@ -3,11 +3,9 @@
"object": { "object": {
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta", "actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
"object": "http://ds9.lemmy.ml/post/1", "object": "http://ds9.lemmy.ml/post/1",
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Like", "type": "Like",
"id": "http://enterprise.lemmy.ml/activities/like/2227ab2c-79e2-4fca-a1d2-1d67dacf2457" "id": "http://enterprise.lemmy.ml/activities/like/2227ab2c-79e2-4fca-a1d2-1d67dacf2457"
}, },
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Undo", "type": "Undo",
"id": "http://enterprise.lemmy.ml/activities/undo/6cc6fb71-39fe-49ea-9506-f0423b101e98" "id": "http://enterprise.lemmy.ml/activities/undo/6cc6fb71-39fe-49ea-9506-f0423b101e98"
} }

View file

@ -3,11 +3,9 @@
"object": { "object": {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha", "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"object": "http://ds9.lemmy.ml/comment/1", "object": "http://ds9.lemmy.ml/comment/1",
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Like", "type": "Like",
"id": "http://ds9.lemmy.ml/activities/like/efcf7ae2-dfcc-4ff4-9ce4-6adf251ff004" "id": "http://ds9.lemmy.ml/activities/like/efcf7ae2-dfcc-4ff4-9ce4-6adf251ff004"
}, },
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Undo", "type": "Undo",
"id": "http://ds9.lemmy.ml/activities/undo/3518565c-24a7-4d9e-8e0a-f7a2f45ac618" "id": "http://ds9.lemmy.ml/activities/undo/3518565c-24a7-4d9e-8e0a-f7a2f45ac618"
} }

View file

@ -20,8 +20,7 @@
"language": { "language": {
"identifier": "de", "identifier": "de",
"name": "Deutsch" "name": "Deutsch"
}, }
"audience": "https://ds9.lemmy.ml/c/main"
}, },
{ {
"type": "Page", "type": "Page",
@ -40,8 +39,7 @@
"language": { "language": {
"identifier": "de", "identifier": "de",
"name": "Deutsch" "name": "Deutsch"
}, }
"audience": "https://ds9.lemmy.ml/c/main"
} }
] ]
} }

View file

@ -2,12 +2,11 @@
"id": "https://enterprise.lemmy.ml/comment/38741", "id": "https://enterprise.lemmy.ml/comment/38741",
"type": "Note", "type": "Note",
"attributedTo": "https://enterprise.lemmy.ml/u/picard", "attributedTo": "https://enterprise.lemmy.ml/u/picard",
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": [
"cc": [
"https://enterprise.lemmy.ml/c/tenforward", "https://enterprise.lemmy.ml/c/tenforward",
"https://enterprise.lemmy.ml/u/picard" "https://www.w3.org/ns/activitystreams#Public"
], ],
"audience": "https://enterprise.lemmy.ml/c/tenforward", "cc": ["https://enterprise.lemmy.ml/u/picard"],
"inReplyTo": "https://enterprise.lemmy.ml/post/55143", "inReplyTo": "https://enterprise.lemmy.ml/post/55143",
"content": "<p>first comment!</p>\n", "content": "<p>first comment!</p>\n",
"mediaType": "text/html", "mediaType": "text/html",

View file

@ -6,7 +6,6 @@
"https://enterprise.lemmy.ml/c/tenforward", "https://enterprise.lemmy.ml/c/tenforward",
"https://www.w3.org/ns/activitystreams#Public" "https://www.w3.org/ns/activitystreams#Public"
], ],
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"name": "Post title", "name": "Post title",
"content": "<p>This is a post in the /c/tenforward community</p>\n", "content": "<p>This is a post in the /c/tenforward community</p>\n",
"mediaType": "text/html", "mediaType": "text/html",

View file

@ -1,4 +1,4 @@
use super::to_and_audience; use super::to;
use crate::{ use crate::{
activities::{ activities::{
block::{generate_cc, SiteOrCommunity}, block::{generate_cc, SiteOrCommunity},
@ -54,7 +54,7 @@ impl BlockUser {
expires: Option<DateTime<Utc>>, expires: Option<DateTime<Utc>>,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<BlockUser> { ) -> LemmyResult<BlockUser> {
let (to, audience) = to_and_audience(target)?; let to = to(target)?;
Ok(BlockUser { Ok(BlockUser {
actor: mod_.id().into(), actor: mod_.id().into(),
to, to,
@ -68,7 +68,6 @@ impl BlockUser {
BlockType::Block, BlockType::Block,
&context.settings().get_protocol_and_hostname(), &context.settings().get_protocol_and_hostname(),
)?, )?,
audience,
end_time: expires, end_time: expires,
}) })
} }

View file

@ -208,12 +208,10 @@ pub(crate) async fn send_ban_from_community(
} }
} }
fn to_and_audience( fn to(target: &SiteOrCommunity) -> LemmyResult<Vec<Url>> {
target: &SiteOrCommunity,
) -> LemmyResult<(Vec<Url>, Option<ObjectId<ApubCommunity>>)> {
Ok(if let SiteOrCommunity::Community(c) = target { Ok(if let SiteOrCommunity::Community(c) = target {
(vec![generate_to(c)?], Some(c.id().into())) generate_to(c)?
} else { } else {
(vec![public()], None) vec![public()]
}) })
} }

View file

@ -1,4 +1,4 @@
use super::to_and_audience; use super::to;
use crate::{ use crate::{
activities::{ activities::{
block::{generate_cc, SiteOrCommunity}, block::{generate_cc, SiteOrCommunity},
@ -46,7 +46,7 @@ impl UndoBlockUser {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let block = BlockUser::new(target, user, mod_, None, reason, None, context).await?; let block = BlockUser::new(target, user, mod_, None, reason, None, context).await?;
let (to, audience) = to_and_audience(target)?; let to = to(target)?;
let id = generate_activity_id( let id = generate_activity_id(
UndoType::Undo, UndoType::Undo,
@ -59,7 +59,6 @@ impl UndoBlockUser {
cc: generate_cc(target, &mut context.pool()).await?, cc: generate_cc(target, &mut context.pool()).await?,
kind: UndoType::Undo, kind: UndoType::Undo,
id: id.clone(), id: id.clone(),
audience,
restore_data: Some(restore_data), restore_data: Some(restore_data),
}; };

View file

@ -94,7 +94,7 @@ impl AnnounceActivity {
generate_announce_activity_id(inner_kind, &context.settings().get_protocol_and_hostname())?; generate_announce_activity_id(inner_kind, &context.settings().get_protocol_and_hostname())?;
Ok(AnnounceActivity { Ok(AnnounceActivity {
actor: community.id().into(), actor: community.id().into(),
to: vec![generate_to(community)?], to: generate_to(community)?,
object: IdOrNestedObject::NestedObject(object), object: IdOrNestedObject::NestedObject(object),
cc: community cc: community
.followers_url .followers_url

View file

@ -54,13 +54,12 @@ impl CollectionAdd {
)?; )?;
let add = CollectionAdd { let add = CollectionAdd {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![generate_to(community)?], to: generate_to(community)?,
object: added_mod.id(), object: added_mod.id(),
target: generate_moderators_url(&community.actor_id)?.into(), target: generate_moderators_url(&community.actor_id)?.into(),
cc: vec![community.id()], cc: vec![community.id()],
kind: AddType::Add, kind: AddType::Add,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()),
}; };
let activity = AnnouncableActivities::CollectionAdd(add); let activity = AnnouncableActivities::CollectionAdd(add);
@ -80,13 +79,12 @@ impl CollectionAdd {
)?; )?;
let add = CollectionAdd { let add = CollectionAdd {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![generate_to(community)?], to: generate_to(community)?,
object: featured_post.ap_id.clone().into(), object: featured_post.ap_id.clone().into(),
target: generate_featured_url(&community.actor_id)?.into(), target: generate_featured_url(&community.actor_id)?.into(),
cc: vec![community.id()], cc: vec![community.id()],
kind: AddType::Add, kind: AddType::Add,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()),
}; };
let activity = AnnouncableActivities::CollectionAdd(add); let activity = AnnouncableActivities::CollectionAdd(add);
send_activity_in_community( send_activity_in_community(

View file

@ -49,13 +49,12 @@ impl CollectionRemove {
)?; )?;
let remove = CollectionRemove { let remove = CollectionRemove {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![generate_to(community)?], to: generate_to(community)?,
object: removed_mod.id(), object: removed_mod.id(),
target: generate_moderators_url(&community.actor_id)?.into(), target: generate_moderators_url(&community.actor_id)?.into(),
id: id.clone(), id: id.clone(),
cc: vec![community.id()], cc: vec![community.id()],
kind: RemoveType::Remove, kind: RemoveType::Remove,
audience: Some(community.id().into()),
}; };
let activity = AnnouncableActivities::CollectionRemove(remove); let activity = AnnouncableActivities::CollectionRemove(remove);
@ -75,13 +74,12 @@ impl CollectionRemove {
)?; )?;
let remove = CollectionRemove { let remove = CollectionRemove {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![generate_to(community)?], to: generate_to(community)?,
object: featured_post.ap_id.clone().into(), object: featured_post.ap_id.clone().into(),
target: generate_featured_url(&community.actor_id)?.into(), target: generate_featured_url(&community.actor_id)?.into(),
cc: vec![community.id()], cc: vec![community.id()],
kind: RemoveType::Remove, kind: RemoveType::Remove,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()),
}; };
let activity = AnnouncableActivities::CollectionRemove(remove); let activity = AnnouncableActivities::CollectionRemove(remove);
send_activity_in_community( send_activity_in_community(

View file

@ -138,12 +138,11 @@ pub(crate) async fn send_lock_post(
let community_id = community.actor_id.inner().clone(); let community_id = community.actor_id.inner().clone();
let lock = LockPage { let lock = LockPage {
actor: actor.actor_id.clone().into(), actor: actor.actor_id.clone().into(),
to: vec![generate_to(&community)?], to: generate_to(&community)?,
object: ObjectId::from(post.ap_id), object: ObjectId::from(post.ap_id),
cc: vec![community_id.clone()], cc: vec![community_id.clone()],
kind: LockType::Lock, kind: LockType::Lock,
id, id,
audience: Some(community_id.into()),
}; };
let activity = if locked { let activity = if locked {
AnnouncableActivities::LockPost(lock) AnnouncableActivities::LockPost(lock)
@ -154,11 +153,10 @@ pub(crate) async fn send_lock_post(
)?; )?;
let undo = UndoLockPage { let undo = UndoLockPage {
actor: lock.actor.clone(), actor: lock.actor.clone(),
to: vec![generate_to(&community)?], to: generate_to(&community)?,
cc: lock.cc.clone(), cc: lock.cc.clone(),
kind: UndoType::Undo, kind: UndoType::Undo,
id, id,
audience: lock.audience.clone(),
object: lock, object: lock,
}; };
AnnouncableActivities::UndoLockPost(undo) AnnouncableActivities::UndoLockPost(undo)

View file

@ -56,7 +56,6 @@ impl Report {
content: None, content: None,
kind, kind,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()),
}; };
// send report to the community where object was posted // send report to the community where object was posted

View file

@ -43,12 +43,11 @@ pub(crate) async fn send_update_community(
)?; )?;
let update = UpdateCommunity { let update = UpdateCommunity {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![generate_to(&community)?], to: generate_to(&community)?,
object: Box::new(community.clone().into_json(&context).await?), object: Box::new(community.clone().into_json(&context).await?),
cc: vec![community.id()], cc: vec![community.id()],
kind: UpdateType::Update, kind: UpdateType::Update,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()),
}; };
let activity = AnnouncableActivities::UpdateCommunity(update); let activity = AnnouncableActivities::UpdateCommunity(update);

View file

@ -71,13 +71,12 @@ impl CreateOrUpdateNote {
let create_or_update = CreateOrUpdateNote { let create_or_update = CreateOrUpdateNote {
actor: person.id().into(), actor: person.id().into(),
to: vec![generate_to(&community)?], to: generate_to(&community)?,
cc: note.cc.clone(), cc: note.cc.clone(),
tag: note.tag.clone(), tag: note.tag.clone(),
object: note, object: note,
kind, kind,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()),
}; };
let tagged_users: Vec<ObjectId<ApubPerson>> = create_or_update let tagged_users: Vec<ObjectId<ApubPerson>> = create_or_update

View file

@ -49,12 +49,11 @@ impl CreateOrUpdatePage {
)?; )?;
Ok(CreateOrUpdatePage { Ok(CreateOrUpdatePage {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![generate_to(community)?], to: generate_to(community)?,
object: post.into_json(context).await?, object: post.into_json(context).await?,
cc: vec![community.id()], cc: vec![community.id()],
kind, kind,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()),
}) })
} }

View file

@ -102,7 +102,6 @@ impl Delete {
kind: DeleteType::Delete, kind: DeleteType::Delete,
summary, summary,
id, id,
audience: community.map(|c| c.actor_id.clone().into()),
remove_data: None, remove_data: None,
}) })
} }

View file

@ -60,7 +60,7 @@ pub(crate) async fn send_apub_delete_in_community(
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let actor = ApubPerson::from(actor); let actor = ApubPerson::from(actor);
let is_mod_action = reason.is_some(); let is_mod_action = reason.is_some();
let to = vec![generate_to(&community)?]; let to = generate_to(&community)?;
let activity = if deleted { let activity = if deleted {
let delete = Delete::new(&actor, object, to, Some(&community), reason, context)?; let delete = Delete::new(&actor, object, to, Some(&community), reason, context)?;
AnnouncableActivities::Delete(delete) AnnouncableActivities::Delete(delete)

View file

@ -87,7 +87,6 @@ impl UndoDelete {
cc: cc.into_iter().collect(), cc: cc.into_iter().collect(),
kind: UndoType::Undo, kind: UndoType::Undo,
id, id,
audience: community.map(|c| c.actor_id.clone().into()),
}) })
} }

View file

@ -136,23 +136,15 @@ pub(crate) fn verify_visibility(to: &[Url], cc: &[Url], community: &Community) -
} }
/// Marks object as public only if the community is public /// Marks object as public only if the community is public
pub(crate) fn generate_to(community: &Community) -> LemmyResult<Url> { pub(crate) fn generate_to(community: &Community) -> LemmyResult<Vec<Url>> {
let actor_id = community.actor_id.clone().into();
if community.visibility == CommunityVisibility::Public { if community.visibility == CommunityVisibility::Public {
Ok(public()) Ok(vec![actor_id, public()])
} else { } else {
Ok(Url::parse(&format!("{}/followers", community.actor_id))?) Ok(vec![
} actor_id.clone(),
} Url::parse(&format!("{}/followers", actor_id))?,
])
pub(crate) fn verify_community_matches<T>(a: &ObjectId<ApubCommunity>, b: T) -> LemmyResult<()>
where
T: Into<ObjectId<ApubCommunity>>,
{
let b: ObjectId<ApubCommunity> = b.into();
if a != &b {
Err(FederationError::InvalidCommunity)?
} else {
Ok(())
} }
} }

View file

@ -40,13 +40,13 @@ pub(crate) async fn send_like_activity(
let empty = ActivitySendTargets::empty(); let empty = ActivitySendTargets::empty();
// score of 1 means upvote, -1 downvote, 0 undo a previous vote // score of 1 means upvote, -1 downvote, 0 undo a previous vote
if score != 0 { if score != 0 {
let vote = Vote::new(object_id, &actor, &community, score.try_into()?, &context)?; let vote = Vote::new(object_id, &actor, score.try_into()?, &context)?;
let activity = AnnouncableActivities::Vote(vote); let activity = AnnouncableActivities::Vote(vote);
send_activity_in_community(activity, &actor, &community, empty, false, &context).await send_activity_in_community(activity, &actor, &community, empty, false, &context).await
} else { } else {
// Lemmy API doesn't distinguish between Undo/Like and Undo/Dislike, so we hardcode it here. // Lemmy API doesn't distinguish between Undo/Like and Undo/Dislike, so we hardcode it here.
let vote = Vote::new(object_id, &actor, &community, VoteType::Like, &context)?; let vote = Vote::new(object_id, &actor, VoteType::Like, &context)?;
let undo_vote = UndoVote::new(vote, &actor, &community, &context)?; let undo_vote = UndoVote::new(vote, &actor, &context)?;
let activity = AnnouncableActivities::UndoVote(undo_vote); let activity = AnnouncableActivities::UndoVote(undo_vote);
send_activity_in_community(activity, &actor, &community, empty, false, &context).await send_activity_in_community(activity, &actor, &community, empty, false, &context).await
} }

View file

@ -5,7 +5,7 @@ use crate::{
voting::{undo_vote_comment, undo_vote_post}, voting::{undo_vote_comment, undo_vote_post},
}, },
insert_received_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::person::ApubPerson,
protocol::{ protocol::{
activities::voting::{undo_vote::UndoVote, vote::Vote}, activities::voting::{undo_vote::UndoVote, vote::Vote},
InCommunity, InCommunity,
@ -26,7 +26,6 @@ impl UndoVote {
pub(in crate::activities::voting) fn new( pub(in crate::activities::voting) fn new(
vote: Vote, vote: Vote,
actor: &ApubPerson, actor: &ApubPerson,
community: &ApubCommunity,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<Self> { ) -> LemmyResult<Self> {
Ok(UndoVote { Ok(UndoVote {
@ -37,7 +36,6 @@ impl UndoVote {
UndoType::Undo, UndoType::Undo,
&context.settings().get_protocol_and_hostname(), &context.settings().get_protocol_and_hostname(),
)?, )?,
audience: Some(community.id().into()),
}) })
} }
} }

View file

@ -5,7 +5,7 @@ use crate::{
voting::{undo_vote_comment, undo_vote_post, vote_comment, vote_post}, voting::{undo_vote_comment, undo_vote_post, vote_comment, vote_post},
}, },
insert_received_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::person::ApubPerson,
protocol::{ protocol::{
activities::voting::vote::{Vote, VoteType}, activities::voting::vote::{Vote, VoteType},
InCommunity, InCommunity,
@ -26,7 +26,6 @@ impl Vote {
pub(in crate::activities::voting) fn new( pub(in crate::activities::voting) fn new(
object_id: ObjectId<PostOrComment>, object_id: ObjectId<PostOrComment>,
actor: &ApubPerson, actor: &ApubPerson,
community: &ApubCommunity,
kind: VoteType, kind: VoteType,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<Vote> { ) -> LemmyResult<Vote> {
@ -35,7 +34,6 @@ impl Vote {
object: object_id, object: object_id,
kind: kind.clone(), kind: kind.clone(),
id: generate_activity_id(kind, &context.settings().get_protocol_and_hostname())?, id: generate_activity_id(kind, &context.settings().get_protocol_and_hostname())?,
audience: Some(community.id().into()),
}) })
} }
} }

View file

@ -110,7 +110,7 @@ pub(crate) async fn get_activity(
.into(); .into();
let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id) let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id)
.await .await
.with_lemmy_type(FederationError::CouldntFindActivity.into())?; .with_lemmy_type(LemmyErrorType::NotFound)?;
let sensitive = activity.sensitive; let sensitive = activity.sensitive;
if sensitive { if sensitive {

View file

@ -1,7 +1,7 @@
use crate::objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson}; use crate::objects::{comment::ApubComment, person::ApubPerson};
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor}, fetch::webfinger::webfinger_resolve_actor,
kinds::link::MentionType, kinds::link::MentionType,
traits::Actor, traits::Actor,
}; };
@ -42,14 +42,13 @@ pub struct MentionsAndAddresses {
/// This takes a comment, and builds a list of to_addresses, inboxes, /// This takes a comment, and builds a list of to_addresses, inboxes,
/// and mention tags, so they know where to be sent to. /// and mention tags, so they know where to be sent to.
/// Addresses are the persons / addresses that go in the cc field. /// Addresses are the persons / addresses that go in the cc field.
#[tracing::instrument(skip(comment, community_id, context))] #[tracing::instrument(skip(comment, context))]
pub async fn collect_non_local_mentions( pub async fn collect_non_local_mentions(
comment: &ApubComment, comment: &ApubComment,
community_id: ObjectId<ApubCommunity>,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<MentionsAndAddresses> { ) -> LemmyResult<MentionsAndAddresses> {
let parent_creator = get_comment_parent_creator(&mut context.pool(), comment).await?; let parent_creator = get_comment_parent_creator(&mut context.pool(), comment).await?;
let mut addressed_ccs: Vec<Url> = vec![community_id.into(), parent_creator.id()]; let mut addressed_ccs: Vec<Url> = vec![parent_creator.id()];
// Add the mention tag // Add the mention tag
let parent_creator_tag = Mention { let parent_creator_tag = Mention {

View file

@ -105,13 +105,13 @@ impl Object for ApubComment {
post.ap_id.into() post.ap_id.into()
}; };
let language = Some(LanguageTag::new_single(self.language_id, &mut context.pool()).await?); let language = Some(LanguageTag::new_single(self.language_id, &mut context.pool()).await?);
let maa = collect_non_local_mentions(&self, community.actor_id.clone().into(), context).await?; let maa = collect_non_local_mentions(&self, context).await?;
let note = Note { let note = Note {
r#type: NoteType::Note, r#type: NoteType::Note,
id: self.ap_id.clone().into(), id: self.ap_id.clone().into(),
attributed_to: creator.actor_id.into(), attributed_to: creator.actor_id.into(),
to: vec![generate_to(&community)?], to: generate_to(&community)?,
cc: maa.ccs, cc: maa.ccs,
content: markdown_to_html(&self.content), content: markdown_to_html(&self.content),
media_type: Some(MediaTypeMarkdownOrHtml::Html), media_type: Some(MediaTypeMarkdownOrHtml::Html),
@ -122,7 +122,6 @@ impl Object for ApubComment {
tag: maa.tags, tag: maa.tags,
distinguished: Some(self.distinguished), distinguished: Some(self.distinguished),
language, language,
audience: Some(community.actor_id.into()),
attachment: vec![], attachment: vec![],
}; };

View file

@ -133,7 +133,7 @@ impl Object for ApubPost {
kind: PageType::Page, kind: PageType::Page,
id: self.ap_id.clone().into(), id: self.ap_id.clone().into(),
attributed_to: AttributedTo::Lemmy(creator.actor_id.into()), attributed_to: AttributedTo::Lemmy(creator.actor_id.into()),
to: vec![generate_to(&community)?], to: generate_to(&community)?,
cc: vec![], cc: vec![],
name: Some(self.name.clone()), name: Some(self.name.clone()),
content: self.body.as_ref().map(|b| markdown_to_html(b)), content: self.body.as_ref().map(|b| markdown_to_html(b)),
@ -145,7 +145,6 @@ impl Object for ApubPost {
language, language,
published: Some(self.published), published: Some(self.published),
updated: self.updated, updated: self.updated,
audience: Some(community.actor_id.into()),
in_reply_to: None, in_reply_to: None,
tag: vec![hashtag], tag: vec![hashtag],
}; };

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
activities::{block::SiteOrCommunity, verify_community_matches}, activities::block::SiteOrCommunity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity, protocol::InCommunity,
}; };
@ -31,7 +31,6 @@ pub struct BlockUser {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: BlockType, pub(crate) kind: BlockType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
/// Quick and dirty solution. /// Quick and dirty solution.
/// TODO: send a separate Delete activity instead /// TODO: send a separate Delete activity instead
@ -49,9 +48,6 @@ impl InCommunity for BlockUser {
SiteOrCommunity::Community(c) => c, SiteOrCommunity::Community(c) => c,
SiteOrCommunity::Site(_) => return Err(anyhow!("activity is not in community").into()), SiteOrCommunity::Site(_) => return Err(anyhow!("activity is not in community").into()),
}; };
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::block::block_user::BlockUser, InCommunity}, protocol::{activities::block::block_user::BlockUser, InCommunity},
}; };
@ -28,7 +27,6 @@ pub struct UndoBlockUser {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: UndoType, pub(crate) kind: UndoType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
/// Quick and dirty solution. /// Quick and dirty solution.
/// TODO: send a separate Delete activity instead /// TODO: send a separate Delete activity instead
@ -39,9 +37,6 @@ pub struct UndoBlockUser {
impl InCommunity for UndoBlockUser { impl InCommunity for UndoBlockUser {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community = self.object.community(context).await?; let community = self.object.community(context).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity, protocol::InCommunity,
}; };
@ -28,7 +27,6 @@ pub struct CollectionAdd {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: AddType, pub(crate) kind: AddType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -36,9 +34,6 @@ impl InCommunity for CollectionAdd {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let (community, _) = let (community, _) =
Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?; Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community.into()) Ok(community.into())
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity, protocol::InCommunity,
}; };
@ -28,7 +27,6 @@ pub struct CollectionRemove {
pub(crate) kind: RemoveType, pub(crate) kind: RemoveType,
pub(crate) target: Url, pub(crate) target: Url,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -36,9 +34,6 @@ impl InCommunity for CollectionRemove {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let (community, _) = let (community, _) =
Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?; Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community.into()) Ok(community.into())
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::InCommunity, protocol::InCommunity,
}; };
@ -33,7 +32,6 @@ pub struct LockPage {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: LockType, pub(crate) kind: LockType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -48,7 +46,6 @@ pub struct UndoLockPage {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: UndoType, pub(crate) kind: UndoType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -56,9 +53,6 @@ impl InCommunity for LockPage {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let post = self.object.dereference(context).await?; let post = self.object.dereference(context).await?;
let community = Community::read(&mut context.pool(), post.community_id).await?; let community = Community::read(&mut context.pool(), post.community_id).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community.into()) Ok(community.into())
} }
} }
@ -67,9 +61,6 @@ impl InCommunity for LockPage {
impl InCommunity for UndoLockPage { impl InCommunity for UndoLockPage {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community = self.object.community(context).await?; let community = self.object.community(context).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
fetcher::post_or_comment::PostOrComment, fetcher::post_or_comment::PostOrComment,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity, protocol::InCommunity,
@ -29,7 +28,6 @@ pub struct Report {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: FlagType, pub(crate) kind: FlagType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
impl Report { impl Report {
@ -73,9 +71,6 @@ impl ReportObject {
impl InCommunity for Report { impl InCommunity for Report {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community = self.to[0].dereference(context).await?; let community = self.to[0].dereference(context).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{objects::group::Group, InCommunity}, protocol::{objects::group::Group, InCommunity},
}; };
@ -29,16 +28,12 @@ pub struct UpdateCommunity {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: UpdateType, pub(crate) kind: UpdateType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl InCommunity for UpdateCommunity { impl InCommunity for UpdateCommunity {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community: ApubCommunity = self.object.id.clone().dereference(context).await?; let community: ApubCommunity = self.object.id.clone().dereference(context).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
mentions::MentionOrValue, mentions::MentionOrValue,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::CreateOrUpdateType, objects::note::Note, InCommunity}, protocol::{activities::CreateOrUpdateType, objects::note::Note, InCommunity},
@ -29,7 +28,6 @@ pub struct CreateOrUpdateNote {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: CreateOrUpdateType, pub(crate) kind: CreateOrUpdateType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -37,9 +35,6 @@ impl InCommunity for CreateOrUpdateNote {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let post = self.object.get_parents(context).await?.0; let post = self.object.get_parents(context).await?.0;
let community = Community::read(&mut context.pool(), post.community_id).await?; let community = Community::read(&mut context.pool(), post.community_id).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community.into()) Ok(community.into())
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::CreateOrUpdateType, objects::page::Page, InCommunity}, protocol::{activities::CreateOrUpdateType, objects::page::Page, InCommunity},
}; };
@ -25,16 +24,12 @@ pub struct CreateOrUpdatePage {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: CreateOrUpdateType, pub(crate) kind: CreateOrUpdateType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl InCommunity for CreateOrUpdatePage { impl InCommunity for CreateOrUpdatePage {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community = self.object.community(context).await?; let community = self.object.community(context).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
activities::{deletion::DeletableObjects, verify_community_matches}, activities::deletion::DeletableObjects,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{objects::tombstone::Tombstone, IdOrNestedObject, InCommunity}, protocol::{objects::tombstone::Tombstone, IdOrNestedObject, InCommunity},
}; };
@ -31,7 +31,6 @@ pub struct Delete {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: DeleteType, pub(crate) kind: DeleteType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
#[serde(deserialize_with = "deserialize_one_or_many")] #[serde(deserialize_with = "deserialize_one_or_many")]
#[serde(default)] #[serde(default)]
@ -61,9 +60,6 @@ impl InCommunity for Delete {
} }
}; };
let community = Community::read(&mut context.pool(), community_id).await?; let community = Community::read(&mut context.pool(), community_id).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community.into()) Ok(community.into())
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::deletion::delete::Delete, InCommunity}, protocol::{activities::deletion::delete::Delete, InCommunity},
}; };
@ -26,7 +25,6 @@ pub struct UndoDelete {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: UndoType, pub(crate) kind: UndoType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
#[serde(deserialize_with = "deserialize_one_or_many", default)] #[serde(deserialize_with = "deserialize_one_or_many", default)]
#[serde(skip_serializing_if = "Vec::is_empty")] #[serde(skip_serializing_if = "Vec::is_empty")]
@ -37,9 +35,6 @@ pub struct UndoDelete {
impl InCommunity for UndoDelete { impl InCommunity for UndoDelete {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community = self.object.community(context).await?; let community = self.object.community(context).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::voting::vote::Vote, InCommunity}, protocol::{activities::voting::vote::Vote, InCommunity},
}; };
@ -17,16 +16,12 @@ pub struct UndoVote {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: UndoType, pub(crate) kind: UndoType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl InCommunity for UndoVote { impl InCommunity for UndoVote {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community = self.object.community(context).await?; let community = self.object.community(context).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
fetcher::post_or_comment::PostOrComment, fetcher::post_or_comment::PostOrComment,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity, protocol::InCommunity,
@ -19,7 +18,6 @@ pub struct Vote {
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: VoteType, pub(crate) kind: VoteType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
#[derive(Clone, Debug, Display, Deserialize, Serialize, PartialEq, Eq)] #[derive(Clone, Debug, Display, Deserialize, Serialize, PartialEq, Eq)]
@ -58,9 +56,6 @@ impl InCommunity for Vote {
.await? .await?
.community(context) .community(context)
.await?; .await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -82,8 +82,6 @@ impl<Kind: Id + DeserializeOwned + Send> IdOrNestedObject<Kind> {
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait InCommunity { pub trait InCommunity {
// TODO: after we use audience field and remove backwards compat, it should be possible to change
// this to simply `fn community(&self) -> LemmyResult<ObjectId<ApubCommunity>>`
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity>; async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity>;
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
fetcher::post_or_comment::PostOrComment, fetcher::post_or_comment::PostOrComment,
mentions::MentionOrValue, mentions::MentionOrValue,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
@ -56,7 +55,6 @@ pub struct Note {
// lemmy extension // lemmy extension
pub(crate) distinguished: Option<bool>, pub(crate) distinguished: Option<bool>,
pub(crate) language: Option<LanguageTag>, pub(crate) language: Option<LanguageTag>,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
#[serde(default)] #[serde(default)]
pub(crate) attachment: Vec<Attachment>, pub(crate) attachment: Vec<Attachment>,
} }
@ -94,9 +92,6 @@ impl InCommunity for Note {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let (post, _) = self.get_parents(context).await?; let (post, _) = self.get_parents(context).await?;
let community = Community::read(&mut context.pool(), post.community_id).await?; let community = Community::read(&mut context.pool(), post.community_id).await?;
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community.into()) Ok(community.into())
} }
} }

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
activities::verify_community_matches,
fetcher::user_or_community::{PersonOrGroupType, UserOrCommunity}, fetcher::user_or_community::{PersonOrGroupType, UserOrCommunity},
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{objects::LanguageTag, ImageObject, InCommunity, Source}, protocol::{objects::LanguageTag, ImageObject, InCommunity, Source},
@ -64,7 +63,6 @@ pub struct Page {
pub(crate) published: Option<DateTime<Utc>>, pub(crate) published: Option<DateTime<Utc>>,
pub(crate) updated: Option<DateTime<Utc>>, pub(crate) updated: Option<DateTime<Utc>>,
pub(crate) language: Option<LanguageTag>, pub(crate) language: Option<LanguageTag>,
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
#[serde(deserialize_with = "deserialize_skip_error", default)] #[serde(deserialize_with = "deserialize_skip_error", default)]
pub(crate) tag: Vec<Hashtag>, pub(crate) tag: Vec<Hashtag>,
} }
@ -231,10 +229,6 @@ impl ActivityHandler for Page {
#[async_trait::async_trait] #[async_trait::async_trait]
impl InCommunity for Page { impl InCommunity for Page {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> { async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
if let Some(audience) = &self.audience {
return audience.dereference(context).await;
}
let community = match &self.attributed_to { let community = match &self.attributed_to {
AttributedTo::Lemmy(_) => { AttributedTo::Lemmy(_) => {
let mut iter = self.to.iter().merge(self.cc.iter()); let mut iter = self.to.iter().merge(self.cc.iter());
@ -259,9 +253,6 @@ impl InCommunity for Page {
} }
}; };
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}
Ok(community) Ok(community)
} }
} }

View file

@ -422,6 +422,25 @@ END;
$$); $$);
CALL r.create_triggers ('community_report', $$
BEGIN
UPDATE
community_aggregates AS a
SET
report_count = a.report_count + diff.report_count, unresolved_report_count = a.unresolved_report_count + diff.unresolved_report_count
FROM (
SELECT
(community_report).community_id, coalesce(sum(count_diff), 0) AS report_count, coalesce(sum(count_diff) FILTER (WHERE NOT (community_report).resolved), 0) AS unresolved_report_count
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (community_report).community_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.community_id = diff.community_id;
RETURN NULL;
END;
$$);
-- These triggers create and update rows in each aggregates table to match its associated table's rows. -- These triggers create and update rows in each aggregates table to match its associated table's rows.
-- Deleting rows and updating IDs are already handled by `CASCADE` in foreign key constraints. -- Deleting rows and updating IDs are already handled by `CASCADE` in foreign key constraints.
CREATE FUNCTION r.comment_aggregates_from_comment () CREATE FUNCTION r.comment_aggregates_from_comment ()
@ -685,6 +704,8 @@ CALL r.create_report_combined_trigger ('comment_report');
CALL r.create_report_combined_trigger ('private_message_report'); CALL r.create_report_combined_trigger ('private_message_report');
CALL r.create_report_combined_trigger ('community_report');
-- person_content (comment, post) -- person_content (comment, post)
CREATE PROCEDURE r.create_person_content_combined_trigger (table_name text) CREATE PROCEDURE r.create_person_content_combined_trigger (table_name text)
LANGUAGE plpgsql LANGUAGE plpgsql

View file

@ -73,6 +73,8 @@ pub struct CommunityAggregates {
#[serde(skip)] #[serde(skip)]
pub hot_rank: f64, pub hot_rank: f64,
pub subscribers_local: i64, pub subscribers_local: i64,
pub report_count: i16,
pub unresolved_report_count: i16,
} }
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]

View file

@ -0,0 +1,97 @@
use crate::{
newtypes::{CommunityId, CommunityReportId, PersonId},
schema::community_report::{
community_id,
dsl::{community_report, resolved, resolver_id, updated},
},
source::community_report::{CommunityReport, CommunityReportForm},
traits::Reportable,
utils::{get_conn, DbPool},
};
use chrono::Utc;
use diesel::{
dsl::{insert_into, update},
result::Error,
ExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
#[async_trait]
impl Reportable for CommunityReport {
type Form = CommunityReportForm;
type IdType = CommunityReportId;
type ObjectIdType = CommunityId;
/// creates a community report and returns it
///
/// * `conn` - the postgres connection
/// * `community_report_form` - the filled CommunityReportForm to insert
async fn report(
pool: &mut DbPool<'_>,
community_report_form: &CommunityReportForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(community_report)
.values(community_report_form)
.get_result::<Self>(conn)
.await
}
/// resolve a community report
///
/// * `conn` - the postgres connection
/// * `report_id` - the id of the report to resolve
/// * `by_resolver_id` - the id of the user resolving the report
async fn resolve(
pool: &mut DbPool<'_>,
report_id_: Self::IdType,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.find(report_id_))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
))
.execute(conn)
.await
}
async fn resolve_all_for_object(
pool: &mut DbPool<'_>,
community_id_: CommunityId,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.filter(community_id.eq(community_id_)))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
))
.execute(conn)
.await
}
/// unresolve a community report
///
/// * `conn` - the postgres connection
/// * `report_id` - the id of the report to unresolve
/// * `by_resolver_id` - the id of the user unresolving the report
async fn unresolve(
pool: &mut DbPool<'_>,
report_id_: Self::IdType,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.find(report_id_))
.set((
resolved.eq(false),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
))
.execute(conn)
.await
}
}

View file

@ -6,6 +6,7 @@ pub mod comment_reply;
pub mod comment_report; pub mod comment_report;
pub mod community; pub mod community;
pub mod community_block; pub mod community_block;
pub mod community_report;
pub mod custom_emoji; pub mod custom_emoji;
pub mod email_verification; pub mod email_verification;
pub mod federation_allowlist; pub mod federation_allowlist;

View file

@ -91,6 +91,12 @@ pub struct PersonMentionId(i32);
/// The comment report id. /// The comment report id.
pub struct CommentReportId(pub i32); pub struct CommentReportId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))]
/// The community report id.
pub struct CommunityReportId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]

View file

@ -253,6 +253,8 @@ diesel::table! {
users_active_half_year -> Int8, users_active_half_year -> Int8,
hot_rank -> Float8, hot_rank -> Float8,
subscribers_local -> Int8, subscribers_local -> Int8,
report_count -> Int2,
unresolved_report_count -> Int2,
} }
} }
@ -263,6 +265,25 @@ diesel::table! {
} }
} }
diesel::table! {
community_report (id) {
id -> Int4,
creator_id -> Int4,
community_id -> Int4,
original_community_name -> Text,
original_community_title -> Text,
original_community_description -> Nullable<Text>,
original_community_sidebar -> Nullable<Text>,
original_community_icon -> Nullable<Text>,
original_community_banner -> Nullable<Text>,
reason -> Text,
resolved -> Bool,
resolver_id -> Nullable<Int4>,
published -> Timestamptz,
updated -> Nullable<Timestamptz>,
}
}
diesel::table! { diesel::table! {
custom_emoji (id) { custom_emoji (id) {
id -> Int4, id -> Int4,
@ -922,6 +943,7 @@ diesel::table! {
post_report_id -> Nullable<Int4>, post_report_id -> Nullable<Int4>,
comment_report_id -> Nullable<Int4>, comment_report_id -> Nullable<Int4>,
private_message_report_id -> Nullable<Int4>, private_message_report_id -> Nullable<Int4>,
community_report_id -> Nullable<Int4>,
} }
} }
@ -1040,6 +1062,7 @@ diesel::joinable!(community_actions -> community (community_id));
diesel::joinable!(community_aggregates -> community (community_id)); diesel::joinable!(community_aggregates -> community (community_id));
diesel::joinable!(community_language -> community (community_id)); diesel::joinable!(community_language -> community (community_id));
diesel::joinable!(community_language -> language (language_id)); diesel::joinable!(community_language -> language (language_id));
diesel::joinable!(community_report -> community (community_id));
diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id)); diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
diesel::joinable!(email_verification -> local_user (local_user_id)); diesel::joinable!(email_verification -> local_user (local_user_id));
diesel::joinable!(federation_allowlist -> instance (instance_id)); diesel::joinable!(federation_allowlist -> instance (instance_id));
@ -1099,6 +1122,7 @@ diesel::joinable!(private_message_report -> private_message (private_message_id)
diesel::joinable!(registration_application -> local_user (local_user_id)); diesel::joinable!(registration_application -> local_user (local_user_id));
diesel::joinable!(registration_application -> person (admin_id)); diesel::joinable!(registration_application -> person (admin_id));
diesel::joinable!(report_combined -> comment_report (comment_report_id)); diesel::joinable!(report_combined -> comment_report (comment_report_id));
diesel::joinable!(report_combined -> community_report (community_report_id));
diesel::joinable!(report_combined -> post_report (post_report_id)); diesel::joinable!(report_combined -> post_report (post_report_id));
diesel::joinable!(report_combined -> private_message_report (private_message_report_id)); diesel::joinable!(report_combined -> private_message_report (private_message_report_id));
diesel::joinable!(site -> instance (instance_id)); diesel::joinable!(site -> instance (instance_id));
@ -1124,6 +1148,7 @@ diesel::allow_tables_to_appear_in_same_query!(
community_actions, community_actions,
community_aggregates, community_aggregates,
community_language, community_language,
community_report,
custom_emoji, custom_emoji,
custom_emoji_keyword, custom_emoji_keyword,
email_verification, email_verification,

View file

@ -0,0 +1,60 @@
use crate::newtypes::{CommunityId, CommunityReportId, DbUrl, PersonId};
#[cfg(feature = "full")]
use crate::schema::community_report;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
#[skip_serializing_none]
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::community::Community))
)]
#[cfg_attr(feature = "full", diesel(table_name = community_report))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// A comment report.
pub struct CommunityReport {
pub id: CommunityReportId,
pub creator_id: PersonId,
pub community_id: CommunityId,
pub original_community_name: String,
pub original_community_title: String,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_description: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_sidebar: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_icon: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_banner: Option<String>,
pub reason: String,
pub resolved: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub resolver_id: Option<PersonId>,
pub published: DateTime<Utc>,
#[cfg_attr(feature = "full", ts(optional))]
pub updated: Option<DateTime<Utc>>,
}
#[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = community_report))]
pub struct CommunityReportForm {
pub creator_id: PersonId,
pub community_id: CommunityId,
pub original_community_name: String,
pub original_community_title: String,
pub original_community_description: Option<String>,
pub original_community_sidebar: Option<String>,
pub original_community_icon: Option<DbUrl>,
pub original_community_banner: Option<DbUrl>,
pub reason: String,
}

View file

@ -11,6 +11,7 @@ pub mod comment_reply;
pub mod comment_report; pub mod comment_report;
pub mod community; pub mod community;
pub mod community_block; pub mod community_block;
pub mod community_report;
pub mod custom_emoji; pub mod custom_emoji;
pub mod custom_emoji_keyword; pub mod custom_emoji_keyword;
pub mod email_verification; pub mod email_verification;

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
structs::{ structs::{
CommentReportView, CommentReportView,
CommunityReportView,
LocalUserView, LocalUserView,
PostReportView, PostReportView,
PrivateMessageReportView, PrivateMessageReportView,
@ -32,6 +33,8 @@ use lemmy_db_schema::{
comment_report, comment_report,
community, community,
community_actions, community_actions,
community_aggregates,
community_report,
local_user, local_user,
person, person,
person_actions, person_actions,
@ -67,6 +70,7 @@ impl ReportCombinedViewInternal {
.left_join(post_report::table) .left_join(post_report::table)
.left_join(comment_report::table) .left_join(comment_report::table)
.left_join(private_message_report::table) .left_join(private_message_report::table)
.left_join(community_report::table)
// Need to join to comment and post to get the community // Need to join to comment and post to get the community
.left_join(comment::table.on(comment_report::comment_id.eq(comment::id))) .left_join(comment::table.on(comment_report::comment_id.eq(comment::id)))
// The post // The post
@ -87,6 +91,7 @@ impl ReportCombinedViewInternal {
post_report::resolved post_report::resolved
.or(comment_report::resolved) .or(comment_report::resolved)
.or(private_message_report::resolved) .or(private_message_report::resolved)
.or(community_report::resolved)
.is_distinct_from(true), .is_distinct_from(true),
) )
.into_boxed(); .into_boxed();
@ -114,6 +119,7 @@ impl ReportCombinedPaginationCursor {
ReportCombinedView::Comment(v) => ('C', v.comment_report.id.0), ReportCombinedView::Comment(v) => ('C', v.comment_report.id.0),
ReportCombinedView::Post(v) => ('P', v.post_report.id.0), ReportCombinedView::Post(v) => ('P', v.post_report.id.0),
ReportCombinedView::PrivateMessage(v) => ('M', v.private_message_report.id.0), ReportCombinedView::PrivateMessage(v) => ('M', v.private_message_report.id.0),
ReportCombinedView::Community(v) => ('Y', v.community_report.id.0),
}; };
// hex encoding to prevent ossification // hex encoding to prevent ossification
ReportCombinedPaginationCursor(format!("{prefix}{id:x}")) ReportCombinedPaginationCursor(format!("{prefix}{id:x}"))
@ -130,6 +136,7 @@ impl ReportCombinedPaginationCursor {
"C" => query.filter(report_combined::comment_report_id.eq(id)), "C" => query.filter(report_combined::comment_report_id.eq(id)),
"P" => query.filter(report_combined::post_report_id.eq(id)), "P" => query.filter(report_combined::post_report_id.eq(id)),
"M" => query.filter(report_combined::private_message_report_id.eq(id)), "M" => query.filter(report_combined::private_message_report_id.eq(id)),
"Y" => query.filter(report_combined::community_report_id.eq(id)),
_ => return Err(err_msg()), _ => return Err(err_msg()),
}; };
let token = query.first(&mut get_conn(pool).await?).await?; let token = query.first(&mut get_conn(pool).await?).await?;
@ -171,13 +178,15 @@ impl ReportCombinedQuery {
.left_join(post_report::table) .left_join(post_report::table)
.left_join(comment_report::table) .left_join(comment_report::table)
.left_join(private_message_report::table) .left_join(private_message_report::table)
.left_join(community_report::table)
// The report creator // The report creator
.inner_join( .inner_join(
person::table.on( person::table.on(
post_report::creator_id post_report::creator_id
.eq(report_creator) .eq(report_creator)
.or(comment_report::creator_id.eq(report_creator)) .or(comment_report::creator_id.eq(report_creator))
.or(private_message_report::creator_id.eq(report_creator)), .or(private_message_report::creator_id.eq(report_creator))
.or(community_report::creator_id.eq(report_creator)),
), ),
) )
// The comment // The comment
@ -196,7 +205,7 @@ impl ReportCombinedQuery {
), ),
) )
// The item creator (`item_creator` is the id of this person) // The item creator (`item_creator` is the id of this person)
.inner_join( .left_join(
aliases::person1.on( aliases::person1.on(
post::creator_id post::creator_id
.eq(item_creator) .eq(item_creator)
@ -205,7 +214,13 @@ impl ReportCombinedQuery {
), ),
) )
// The community // The community
.left_join(community::table.on(post::community_id.eq(community::id))) .left_join(
community::table.on(
post::community_id
.eq(community::id)
.or(community_report::community_id.eq(community::id)),
),
)
.left_join(actions_alias( .left_join(actions_alias(
creator_community_actions, creator_community_actions,
item_creator, item_creator,
@ -221,7 +236,7 @@ impl ReportCombinedQuery {
.left_join(actions( .left_join(actions(
community_actions::table, community_actions::table,
Some(my_person_id), Some(my_person_id),
post::community_id, community::id,
)) ))
.left_join(actions(post_actions::table, Some(my_person_id), post::id)) .left_join(actions(post_actions::table, Some(my_person_id), post::id))
.left_join(actions( .left_join(actions(
@ -233,13 +248,18 @@ impl ReportCombinedQuery {
.left_join( .left_join(
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
) )
.left_join(
community_aggregates::table
.on(community_report::community_id.eq(community_aggregates::community_id)),
)
// The resolver // The resolver
.left_join( .left_join(
aliases::person2.on( aliases::person2.on(
private_message_report::resolver_id private_message_report::resolver_id
.eq(resolver) .eq(resolver)
.or(post_report::resolver_id.eq(resolver)) .or(post_report::resolver_id.eq(resolver))
.or(comment_report::resolver_id.eq(resolver)), .or(comment_report::resolver_id.eq(resolver))
.or(community_report::resolver_id.eq(resolver)),
), ),
) )
.left_join(actions( .left_join(actions(
@ -270,9 +290,12 @@ impl ReportCombinedQuery {
// Private-message-specific // Private-message-specific
private_message_report::all_columns.nullable(), private_message_report::all_columns.nullable(),
private_message::all_columns.nullable(), private_message::all_columns.nullable(),
// Community-specific
community_report::all_columns.nullable(),
community_aggregates::all_columns.nullable(),
// Shared // Shared
person::all_columns, person::all_columns,
aliases::person1.fields(person::all_columns), aliases::person1.fields(person::all_columns.nullable()),
community::all_columns.nullable(), community::all_columns.nullable(),
CommunityFollower::select_subscribed_type(), CommunityFollower::select_subscribed_type(),
aliases::person2.fields(person::all_columns.nullable()), aliases::person2.fields(person::all_columns.nullable()),
@ -290,12 +313,20 @@ impl ReportCombinedQuery {
.into_boxed(); .into_boxed();
if let Some(community_id) = self.community_id { if let Some(community_id) = self.community_id {
query = query.filter(community::id.eq(community_id)); query = query.filter(
community::id
.eq(community_id)
.and(report_combined::community_report_id.is_null()),
);
} }
// If its not an admin, get only the ones you mod // If its not an admin, get only the ones you mod
if !user.local_user.admin { if !user.local_user.admin {
query = query.filter(community_actions::became_moderator.is_not_null()); query = query.filter(
community_actions::became_moderator
.is_not_null()
.and(report_combined::community_report_id.is_null()),
);
} }
let mut query = PaginatedQueryBuilder::new(query); let mut query = PaginatedQueryBuilder::new(query);
@ -316,6 +347,7 @@ impl ReportCombinedQuery {
post_report::resolved post_report::resolved
.or(comment_report::resolved) .or(comment_report::resolved)
.or(private_message_report::resolved) .or(private_message_report::resolved)
.or(community_report::resolved)
.is_distinct_from(true), .is_distinct_from(true),
) )
// TODO: when a `then_asc` method is added, use it here, make the id sort direction match, // TODO: when a `then_asc` method is added, use it here, make the id sort direction match,
@ -344,12 +376,20 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
// Use for a short alias // Use for a short alias
let v = self.clone(); let v = self.clone();
if let (Some(post_report), Some(post), Some(community), Some(unread_comments), Some(counts)) = ( if let (
Some(post_report),
Some(post),
Some(community),
Some(unread_comments),
Some(counts),
Some(post_creator),
) = (
v.post_report, v.post_report,
v.post.clone(), v.post.clone(),
v.community.clone(), v.community.clone(),
v.post_unread_comments, v.post_unread_comments,
v.post_counts, v.post_counts,
v.item_creator.clone(),
) { ) {
Some(ReportCombinedView::Post(PostReportView { Some(ReportCombinedView::Post(PostReportView {
post_report, post_report,
@ -358,7 +398,7 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
unread_comments, unread_comments,
counts, counts,
creator: v.report_creator, creator: v.report_creator,
post_creator: v.item_creator, post_creator,
creator_banned_from_community: v.item_creator_banned_from_community, creator_banned_from_community: v.item_creator_banned_from_community,
creator_is_moderator: v.item_creator_is_moderator, creator_is_moderator: v.item_creator_is_moderator,
creator_is_admin: v.item_creator_is_admin, creator_is_admin: v.item_creator_is_admin,
@ -370,12 +410,20 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
my_vote: v.my_post_vote, my_vote: v.my_post_vote,
resolver: v.resolver, resolver: v.resolver,
})) }))
} else if let (Some(comment_report), Some(comment), Some(counts), Some(post), Some(community)) = ( } else if let (
Some(comment_report),
Some(comment),
Some(counts),
Some(post),
Some(community),
Some(comment_creator),
) = (
v.comment_report, v.comment_report,
v.comment, v.comment,
v.comment_counts, v.comment_counts,
v.post, v.post,
v.community, v.community.clone(),
v.item_creator.clone(),
) { ) {
Some(ReportCombinedView::Comment(CommentReportView { Some(ReportCombinedView::Comment(CommentReportView {
comment_report, comment_report,
@ -384,7 +432,7 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
post, post,
community, community,
creator: v.report_creator, creator: v.report_creator,
comment_creator: v.item_creator, comment_creator,
creator_banned_from_community: v.item_creator_banned_from_community, creator_banned_from_community: v.item_creator_banned_from_community,
creator_is_moderator: v.item_creator_is_moderator, creator_is_moderator: v.item_creator_is_moderator,
creator_is_admin: v.item_creator_is_admin, creator_is_admin: v.item_creator_is_admin,
@ -394,18 +442,32 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
my_vote: v.my_comment_vote, my_vote: v.my_comment_vote,
resolver: v.resolver, resolver: v.resolver,
})) }))
} else if let (Some(private_message_report), Some(private_message)) = } else if let (
(v.private_message_report, v.private_message) Some(private_message_report),
Some(private_message),
Some(private_message_creator),
) = (v.private_message_report, v.private_message, v.item_creator)
{ {
Some(ReportCombinedView::PrivateMessage( Some(ReportCombinedView::PrivateMessage(
PrivateMessageReportView { PrivateMessageReportView {
private_message_report, private_message_report,
private_message, private_message,
creator: v.report_creator, creator: v.report_creator,
private_message_creator: v.item_creator, private_message_creator,
resolver: v.resolver, resolver: v.resolver,
}, },
)) ))
} else if let (Some(community), Some(community_report), Some(counts)) =
(v.community, v.community_report, v.community_counts)
{
Some(ReportCombinedView::Community(CommunityReportView {
community_report,
community,
creator: v.report_creator,
counts,
subscribed: v.subscribed,
resolver: v.resolver,
}))
} else { } else {
None None
} }
@ -433,6 +495,7 @@ mod tests {
comment::{Comment, CommentInsertForm}, comment::{Comment, CommentInsertForm},
comment_report::{CommentReport, CommentReportForm}, comment_report::{CommentReport, CommentReportForm},
community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm}, community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
community_report::{CommunityReport, CommunityReportForm},
instance::Instance, instance::Instance,
local_user::{LocalUser, LocalUserInsertForm}, local_user::{LocalUser, LocalUserInsertForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode, local_user_vote_display_mode::LocalUserVoteDisplayMode,
@ -558,6 +621,20 @@ mod tests {
let pool = &mut pool.into(); let pool = &mut pool.into();
let data = init_data(pool).await?; let data = init_data(pool).await?;
// Sara reports the community
let sara_report_community_form = CommunityReportForm {
creator_id: data.sara.id,
community_id: data.community.id,
original_community_name: data.community.name.clone(),
original_community_title: data.community.title.clone(),
original_community_banner: None,
original_community_description: None,
original_community_sidebar: None,
original_community_icon: None,
reason: "from sara".into(),
};
CommunityReport::report(pool, &sara_report_community_form).await?;
// sara reports the post // sara reports the post
let sara_report_post_form = PostReportForm { let sara_report_post_form = PostReportForm {
creator_id: data.sara.id, creator_id: data.sara.id,
@ -599,9 +676,14 @@ mod tests {
let reports = ReportCombinedQuery::default() let reports = ReportCombinedQuery::default()
.list(pool, &data.admin_view) .list(pool, &data.admin_view)
.await?; .await?;
assert_eq!(3, reports.len()); assert_eq!(4, reports.len());
// Make sure the report types are correct // Make sure the report types are correct
if let ReportCombinedView::Community(v) = &reports[3] {
assert_eq!(data.community.id, v.community.id);
} else {
panic!("wrong type");
}
if let ReportCombinedView::Post(v) = &reports[2] { if let ReportCombinedView::Post(v) = &reports[2] {
assert_eq!(data.post.id, v.post.id); assert_eq!(data.post.id, v.post.id);
assert_eq!(data.sara.id, v.creator.id); assert_eq!(data.sara.id, v.creator.id);
@ -624,7 +706,7 @@ mod tests {
let report_count_admin = let report_count_admin =
ReportCombinedViewInternal::get_report_count(pool, &data.admin_view, None).await?; ReportCombinedViewInternal::get_report_count(pool, &data.admin_view, None).await?;
assert_eq!(3, report_count_admin); assert_eq!(4, report_count_admin);
// Timmy should only see 2 reports, since they're not an admin, // Timmy should only see 2 reports, since they're not an admin,
// but they do mod the community // but they do mod the community
@ -971,4 +1053,62 @@ mod tests {
Ok(()) Ok(())
} }
#[tokio::test]
#[serial]
async fn test_community_reports() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let data = init_data(pool).await?;
// jessica reports community
let community_report_form = CommunityReportForm {
creator_id: data.jessica.id,
community_id: data.community.id,
original_community_name: data.community.name.clone(),
original_community_title: data.community.title.clone(),
original_community_banner: None,
original_community_description: None,
original_community_sidebar: None,
original_community_icon: None,
reason: "the ice cream incident".into(),
};
let community_report = CommunityReport::report(pool, &community_report_form).await?;
let reports = ReportCombinedQuery::default()
.list(pool, &data.admin_view)
.await?;
assert_length!(1, reports);
if let ReportCombinedView::Community(v) = &reports[0] {
assert!(!v.community_report.resolved);
assert_eq!(data.jessica.name, v.creator.name);
assert_eq!(community_report.reason, v.community_report.reason);
assert_eq!(data.community.name, v.community.name);
assert_eq!(data.community.title, v.community.title);
} else {
panic!("wrong type");
}
// admin resolves the report (after taking appropriate action)
CommunityReport::resolve(pool, community_report.id, data.admin_view.person.id).await?;
let reports = ReportCombinedQuery::default()
.list(pool, &data.admin_view)
.await?;
assert_length!(1, reports);
if let ReportCombinedView::Community(v) = &reports[0] {
assert!(v.community_report.resolved);
assert!(v.resolver.is_some());
assert_eq!(
Some(&data.admin_view.person.name),
v.resolver.as_ref().map(|r| &r.name)
);
} else {
panic!("wrong type");
}
cleanup(data, pool).await?;
Ok(())
}
} }

View file

@ -3,11 +3,18 @@ use diesel::Queryable;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types}; use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types};
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::{CommentAggregates, PersonAggregates, PostAggregates, SiteAggregates}, aggregates::structs::{
CommentAggregates,
CommunityAggregates,
PersonAggregates,
PostAggregates,
SiteAggregates,
},
source::{ source::{
comment::Comment, comment::Comment,
comment_report::CommentReport, comment_report::CommentReport,
community::Community, community::Community,
community_report::CommunityReport,
custom_emoji::CustomEmoji, custom_emoji::CustomEmoji,
custom_emoji_keyword::CustomEmojiKeyword, custom_emoji_keyword::CustomEmojiKeyword,
images::{ImageDetails, LocalImage}, images::{ImageDetails, LocalImage},
@ -80,6 +87,22 @@ pub struct CommentView {
pub my_vote: Option<i16>, pub my_vote: Option<i16>,
} }
#[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// A community report view.
pub struct CommunityReportView {
pub community_report: CommunityReport,
pub community: Community,
pub creator: Person,
pub counts: CommunityAggregates,
pub subscribed: SubscribedType,
#[cfg_attr(feature = "full", ts(optional))]
pub resolver: Option<Person>,
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))] #[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
@ -283,9 +306,12 @@ pub struct ReportCombinedViewInternal {
// Private-message-specific // Private-message-specific
pub private_message_report: Option<PrivateMessageReport>, pub private_message_report: Option<PrivateMessageReport>,
pub private_message: Option<PrivateMessage>, pub private_message: Option<PrivateMessage>,
// Community-specific
pub community_report: Option<CommunityReport>,
pub community_counts: Option<CommunityAggregates>,
// Shared // Shared
pub report_creator: Person, pub report_creator: Person,
pub item_creator: Person, pub item_creator: Option<Person>,
pub community: Option<Community>, pub community: Option<Community>,
pub subscribed: SubscribedType, pub subscribed: SubscribedType,
pub resolver: Option<Person>, pub resolver: Option<Person>,
@ -304,6 +330,7 @@ pub enum ReportCombinedView {
Post(PostReportView), Post(PostReportView),
Comment(CommentReportView), Comment(CommentReportView),
PrivateMessage(PrivateMessageReportView), PrivateMessage(PrivateMessageReportView),
Community(CommunityReportView),
} }
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]

View file

@ -710,7 +710,6 @@ mod test {
let data = json!({ let data = json!({
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha", "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"object": "http://ds9.lemmy.ml/comment/1", "object": "http://ds9.lemmy.ml/comment/1",
"audience": "https://enterprise.lemmy.ml/c/tenforward",
"type": "Like", "type": "Like",
"id": format!("http://ds9.lemmy.ml/activities/like/{}", uuid::Uuid::new_v4()), "id": format!("http://ds9.lemmy.ml/activities/like/{}", uuid::Uuid::new_v4()),
}); });

View file

@ -165,8 +165,6 @@ pub enum LemmyErrorType {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
#[non_exhaustive] #[non_exhaustive]
pub enum FederationError { pub enum FederationError {
// TODO: merge into a single NotFound error
CouldntFindActivity,
InvalidCommunity, InvalidCommunity,
CannotCreatePostOrCommentInDeletedOrRemovedCommunity, CannotCreatePostOrCommentInDeletedOrRemovedCommunity,
CannotReceivePage, CannotReceivePage,
@ -247,6 +245,9 @@ cfg_if! {
if self.error_type == LemmyErrorType::IncorrectLogin { if self.error_type == LemmyErrorType::IncorrectLogin {
return actix_web::http::StatusCode::UNAUTHORIZED; return actix_web::http::StatusCode::UNAUTHORIZED;
} }
if self.error_type == LemmyErrorType::NotFound {
return actix_web::http::StatusCode::NOT_FOUND;
}
match self.inner.downcast_ref::<diesel::result::Error>() { match self.inner.downcast_ref::<diesel::result::Error>() {
Some(diesel::result::Error::NotFound) => actix_web::http::StatusCode::NOT_FOUND, Some(diesel::result::Error::NotFound) => actix_web::http::StatusCode::NOT_FOUND,
_ => actix_web::http::StatusCode::BAD_REQUEST, _ => actix_web::http::StatusCode::BAD_REQUEST,

View file

@ -3,12 +3,12 @@ use anyhow::{anyhow, Context};
use deser_hjson::from_str; use deser_hjson::from_str;
use regex::Regex; use regex::Regex;
use std::{env, fs, io::Error, sync::LazyLock}; use std::{env, fs, io::Error, sync::LazyLock};
use structs::{PictrsConfig, PictrsImageMode, Settings}; use structs::{PictrsConfig, Settings};
use url::Url; use url::Url;
pub mod structs; pub mod structs;
const DEFAULT_CONFIG_FILE: &str = "config/config.hjson"; static DEFAULT_CONFIG_FILE: &str = "config/config.hjson";
#[allow(clippy::expect_used)] #[allow(clippy::expect_used)]
pub static SETTINGS: LazyLock<Settings> = LazyLock::new(|| { pub static SETTINGS: LazyLock<Settings> = LazyLock::new(|| {
@ -104,21 +104,6 @@ impl Settings {
.ok_or_else(|| anyhow!("images_disabled").into()) .ok_or_else(|| anyhow!("images_disabled").into())
} }
} }
impl PictrsConfig {
pub fn image_mode(&self) -> PictrsImageMode {
if let Some(cache_external_link_previews) = self.cache_external_link_previews {
if cache_external_link_previews {
PictrsImageMode::StoreLinkPreviews
} else {
PictrsImageMode::None
}
} else {
self.image_mode.clone()
}
}
}
#[allow(clippy::expect_used)] #[allow(clippy::expect_used)]
/// Necessary to avoid URL expect failures /// Necessary to avoid URL expect failures
fn pictrs_placeholder_url() -> Url { fn pictrs_placeholder_url() -> Url {

View file

@ -77,16 +77,10 @@ pub struct PictrsConfig {
#[default(None)] #[default(None)]
pub api_key: Option<String>, pub api_key: Option<String>,
/// Backwards compatibility with 0.18.1. False is equivalent to `image_mode: None`, true is
/// equivalent to `image_mode: StoreLinkPreviews`.
///
/// To be removed in 0.20
pub(super) cache_external_link_previews: Option<bool>,
/// Specifies how to handle remote images, so that users don't have to connect directly to remote /// Specifies how to handle remote images, so that users don't have to connect directly to remote
/// servers. /// servers.
#[default(PictrsImageMode::StoreLinkPreviews)] #[default(PictrsImageMode::ProxyAllImages)]
pub(super) image_mode: PictrsImageMode, pub image_mode: PictrsImageMode,
/// Allows bypassing proxy for specific image hosts when using ProxyAllImages. /// Allows bypassing proxy for specific image hosts when using ProxyAllImages.
/// ///
@ -130,8 +124,7 @@ pub enum PictrsImageMode {
/// ensures that they can be reliably retrieved and can be resized using pict-rs APIs. However /// ensures that they can be reliably retrieved and can be resized using pict-rs APIs. However
/// it also increases storage usage. /// it also increases storage usage.
/// ///
/// This is the default behaviour, and also matches Lemmy 0.18. /// This behaviour matches Lemmy 0.18.
#[default]
StoreLinkPreviews, StoreLinkPreviews,
/// If enabled, all images from remote domains are rewritten to pass through /// If enabled, all images from remote domains are rewritten to pass through
/// `/api/v4/image/proxy`, including embedded images in markdown. Images are stored temporarily /// `/api/v4/image/proxy`, including embedded images in markdown. Images are stored temporarily
@ -140,6 +133,7 @@ pub enum PictrsImageMode {
/// local server. /// local server.
/// ///
/// Requires pict-rs 0.5 /// Requires pict-rs 0.5
#[default]
ProxyAllImages, ProxyAllImages,
} }

View file

@ -12,5 +12,6 @@
} }
pictrs: { pictrs: {
api_key: "my-pictrs-key" api_key: "my-pictrs-key"
image_mode: StoreLinkPreviews
} }
} }

View file

@ -12,5 +12,6 @@
} }
pictrs: { pictrs: {
api_key: "my-pictrs-key" api_key: "my-pictrs-key"
image_mode: StoreLinkPreviews
} }
} }

View file

@ -0,0 +1,14 @@
DELETE FROM report_combined
WHERE community_report_id IS NOT NULL;
ALTER TABLE report_combined
DROP CONSTRAINT report_combined_check,
ADD CHECK (num_nonnulls (post_report_id, comment_report_id, private_message_report_id) = 1),
DROP COLUMN community_report_id;
DROP TABLE community_report CASCADE;
ALTER TABLE community_aggregates
DROP COLUMN report_count,
DROP COLUMN unresolved_report_count;

View file

@ -0,0 +1,29 @@
CREATE TABLE community_report (
id serial PRIMARY KEY,
creator_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
original_community_name text NOT NULL,
original_community_title text NOT NULL,
original_community_description text,
original_community_sidebar text,
original_community_icon text,
original_community_banner text,
reason text NOT NULL,
resolved bool NOT NULL DEFAULT FALSE,
resolver_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE,
published timestamptz NOT NULL DEFAULT now(),
updated timestamptz NULL,
UNIQUE (community_id, creator_id)
);
CREATE INDEX idx_community_report_published ON community_report (published DESC);
ALTER TABLE report_combined
ADD COLUMN community_report_id int UNIQUE REFERENCES community_report ON UPDATE CASCADE ON DELETE CASCADE,
DROP CONSTRAINT report_combined_check,
ADD CHECK (num_nonnulls (post_report_id, comment_report_id, private_message_report_id, community_report_id) = 1);
ALTER TABLE community_aggregates
ADD COLUMN report_count smallint NOT NULL DEFAULT 0,
ADD COLUMN unresolved_report_count smallint NOT NULL DEFAULT 0;

View file

@ -3,6 +3,7 @@ use chrono::{DateTime, TimeZone, Utc};
use clokwerk::{AsyncScheduler, TimeUnits as CTimeUnits}; use clokwerk::{AsyncScheduler, TimeUnits as CTimeUnits};
use diesel::{ use diesel::{
dsl::{exists, not, IntervalDsl}, dsl::{exists, not, IntervalDsl},
query_builder::AsQuery,
sql_query, sql_query,
sql_types::{Integer, Timestamptz}, sql_types::{Integer, Timestamptz},
BoolExpressionMethods, BoolExpressionMethods,
@ -37,7 +38,15 @@ use lemmy_db_schema::{
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
}, },
traits::Crud, traits::Crud,
utils::{find_action, functions::coalesce, get_conn, now, DbPool, DELETED_REPLACEMENT_TEXT}, utils::{
find_action,
functions::coalesce,
get_conn,
now,
uplete,
DbPool,
DELETED_REPLACEMENT_TEXT,
},
}; };
use lemmy_routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}; use lemmy_routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorType, LemmyResult};
@ -389,9 +398,10 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) -> LemmyResult<()> {
.execute(&mut conn) .execute(&mut conn)
.await?; .await?;
diesel::delete( uplete::new(community_actions::table.filter(community_actions::ban_expires.lt(now().nullable())))
community_actions::table.filter(community_actions::ban_expires.lt(now().nullable())), .set_null(community_actions::received_ban)
) .set_null(community_actions::ban_expires)
.as_query()
.execute(&mut conn) .execute(&mut conn)
.await?; .await?;
Ok(()) Ok(())