diff --git a/.woodpecker.yml b/.woodpecker.yml
index 6e30293de..1ae2df457 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -262,6 +262,19 @@ steps:
when:
event: cron
+ # using https://github.com/pksunkara/cargo-workspaces
+ publish_to_crates_io:
+ image: *rust_image
+ commands:
+ - 'echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"'
+ - cargo install cargo-workspaces
+ - cp -r migrations crates/db_schema/
+ - cargo login "$CARGO_API_TOKEN"
+ - cargo workspaces publish --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
+ secrets: [cargo_api_token]
+ when:
+ event: tag
+
notify_on_failure:
image: alpine:3
commands:
diff --git a/Cargo.lock b/Cargo.lock
index 9952b7aae..86342b923 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2523,7 +2523,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lemmy_api"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"actix-web",
@@ -2551,7 +2551,7 @@ dependencies = [
[[package]]
name = "lemmy_api_common"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"actix-web",
@@ -2585,7 +2585,7 @@ dependencies = [
[[package]]
name = "lemmy_api_crud"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"actix-web",
@@ -2607,7 +2607,7 @@ dependencies = [
[[package]]
name = "lemmy_apub"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"actix-web",
@@ -2646,7 +2646,7 @@ dependencies = [
[[package]]
name = "lemmy_db_schema"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"async-trait",
@@ -2682,7 +2682,7 @@ dependencies = [
[[package]]
name = "lemmy_db_views"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"actix-web",
"chrono",
@@ -2701,7 +2701,7 @@ dependencies = [
[[package]]
name = "lemmy_db_views_actor"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"chrono",
"diesel",
@@ -2718,7 +2718,7 @@ dependencies = [
[[package]]
name = "lemmy_db_views_moderator"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"diesel",
"diesel-async",
@@ -2730,7 +2730,7 @@ dependencies = [
[[package]]
name = "lemmy_federate"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"anyhow",
@@ -2753,7 +2753,7 @@ dependencies = [
[[package]]
name = "lemmy_routes"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"actix-web",
@@ -2777,7 +2777,7 @@ dependencies = [
[[package]]
name = "lemmy_server"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"activitypub_federation",
"actix-cors",
@@ -2819,7 +2819,7 @@ dependencies = [
[[package]]
name = "lemmy_utils"
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
dependencies = [
"actix-web",
"anyhow",
diff --git a/Cargo.toml b/Cargo.toml
index 655f2c3a9..a2cb87ff9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,6 @@
[workspace.package]
-version = "0.19.1-rc.1"
+version = "0.19.1-rc.2"
+publish = false
edition = "2021"
description = "A link aggregator for the fediverse"
license = "AGPL-3.0"
@@ -84,16 +85,16 @@ unused_self = "deny"
unwrap_used = "deny"
[workspace.dependencies]
-lemmy_api = { version = "=0.19.1-rc.1", path = "./crates/api" }
-lemmy_api_crud = { version = "=0.19.1-rc.1", path = "./crates/api_crud" }
-lemmy_apub = { version = "=0.19.1-rc.1", path = "./crates/apub" }
-lemmy_utils = { version = "=0.19.1-rc.1", path = "./crates/utils" }
-lemmy_db_schema = { version = "=0.19.1-rc.1", path = "./crates/db_schema" }
-lemmy_api_common = { version = "=0.19.1-rc.1", path = "./crates/api_common" }
-lemmy_routes = { version = "=0.19.1-rc.1", path = "./crates/routes" }
-lemmy_db_views = { version = "=0.19.1-rc.1", path = "./crates/db_views" }
-lemmy_db_views_actor = { version = "=0.19.1-rc.1", path = "./crates/db_views_actor" }
-lemmy_db_views_moderator = { version = "=0.19.1-rc.1", path = "./crates/db_views_moderator" }
+lemmy_api = { version = "=0.19.1-rc.2", path = "./crates/api" }
+lemmy_api_crud = { version = "=0.19.1-rc.2", path = "./crates/api_crud" }
+lemmy_apub = { version = "=0.19.1-rc.2", path = "./crates/apub" }
+lemmy_utils = { version = "=0.19.1-rc.2", path = "./crates/utils" }
+lemmy_db_schema = { version = "=0.19.1-rc.2", path = "./crates/db_schema" }
+lemmy_api_common = { version = "=0.19.1-rc.2", path = "./crates/api_common" }
+lemmy_routes = { version = "=0.19.1-rc.2", path = "./crates/routes" }
+lemmy_db_views = { version = "=0.19.1-rc.2", path = "./crates/db_views" }
+lemmy_db_views_actor = { version = "=0.19.1-rc.2", path = "./crates/db_views_actor" }
+lemmy_db_views_moderator = { version = "=0.19.1-rc.2", path = "./crates/db_views_moderator" }
activitypub_federation = { version = "0.5.0-beta.6", default-features = false, features = [
"actix-web",
] }
@@ -164,7 +165,7 @@ lemmy_utils = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_api_common = { workspace = true }
lemmy_routes = { workspace = true }
-lemmy_federate = { version = "0.19.1-rc.1", path = "crates/federate" }
+lemmy_federate = { version = "0.19.1-rc.2", path = "crates/federate" }
activitypub_federation = { workspace = true }
diesel = { workspace = true }
diesel-async = { workspace = true }
diff --git a/README.md b/README.md
index a3f6ceb2a..3227f74d8 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
[![Translation status](http://weblate.join-lemmy.org/widgets/lemmy/-/lemmy/svg-badge.svg)](http://weblate.join-lemmy.org/engage/lemmy/)
[![License](https://img.shields.io/github/license/LemmyNet/lemmy.svg)](LICENSE)
![GitHub stars](https://img.shields.io/github/stars/LemmyNet/lemmy?style=social)
-[![Delightful Humane Tech](https://codeberg.org/teaserbot-labs/delightful-humane-design/raw/branch/main/humane-tech-badge.svg)](https://codeberg.org/teaserbot-labs/delightful-humane-design)
+
diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts
index ccfc5e1fe..527ab74d4 100644
--- a/api_tests/src/user.spec.ts
+++ b/api_tests/src/user.spec.ts
@@ -112,18 +112,19 @@ test("Delete user", async () => {
).toBe(true);
});
-test("Requests with invalid auth should be treated as unauthenticated", async () => {
+test("Requests with invalid auth should throw error", async () => {
let invalid_auth = new LemmyHttp(alphaUrl, {
headers: { Authorization: "Bearer foobar" },
fetchFunction,
});
- let site = await getSite(invalid_auth);
- expect(site.my_user).toBeUndefined();
- expect(site.site_view).toBeDefined();
+ await expect(getSite(invalid_auth)).rejects.toStrictEqual(
+ Error("incorrect_login"),
+ );
let form: GetPosts = {};
- let posts = invalid_auth.getPosts(form);
- expect((await posts).posts).toBeDefined();
+ await expect(invalid_auth.getPosts(form)).rejects.toStrictEqual(
+ Error("incorrect_login"),
+ );
});
test("Create user with Arabic name", async () => {
diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml
index 6327e45fd..f22c9192a 100644
--- a/crates/api/Cargo.toml
+++ b/crates/api/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "lemmy_api"
+publish = false
version.workspace = true
edition.workspace = true
description.workspace = true
diff --git a/crates/api/src/comment/list_comment_likes.rs b/crates/api/src/comment/list_comment_likes.rs
new file mode 100644
index 000000000..cb487c9f1
--- /dev/null
+++ b/crates/api/src/comment/list_comment_likes.rs
@@ -0,0 +1,24 @@
+use actix_web::web::{Data, Json, Query};
+use lemmy_api_common::{
+ comment::{ListCommentLikes, ListCommentLikesResponse},
+ context::LemmyContext,
+ utils::is_admin,
+};
+use lemmy_db_views::structs::{LocalUserView, VoteView};
+use lemmy_utils::error::LemmyError;
+
+/// Lists likes for a comment
+#[tracing::instrument(skip(context))]
+pub async fn list_comment_likes(
+ data: Query,
+ context: Data,
+ local_user_view: LocalUserView,
+) -> Result, LemmyError> {
+ // Make sure user is an admin
+ is_admin(&local_user_view)?;
+
+ let comment_likes =
+ VoteView::list_for_comment(&mut context.pool(), data.comment_id, data.page, data.limit).await?;
+
+ Ok(Json(ListCommentLikesResponse { comment_likes }))
+}
diff --git a/crates/api/src/comment/mod.rs b/crates/api/src/comment/mod.rs
index 8caeaf8b0..9830e295d 100644
--- a/crates/api/src/comment/mod.rs
+++ b/crates/api/src/comment/mod.rs
@@ -1,3 +1,4 @@
pub mod distinguish;
pub mod like;
+pub mod list_comment_likes;
pub mod save;
diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/comment_report/resolve.rs
index 41ebe0d00..70e7d89f6 100644
--- a/crates/api/src/comment_report/resolve.rs
+++ b/crates/api/src/comment_report/resolve.rs
@@ -23,7 +23,7 @@ pub async fn resolve_comment_report(
check_community_mod_action(
&local_user_view.person,
report.community.id,
- false,
+ true,
&mut context.pool(),
)
.await?;
diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs
index faa74824e..7d85cc78f 100644
--- a/crates/api/src/lib.rs
+++ b/crates/api/src/lib.rs
@@ -2,15 +2,11 @@ use actix_web::{http::header::Header, HttpRequest};
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
use captcha::Captcha;
-use lemmy_api_common::{
- claims::Claims,
- context::LemmyContext,
- utils::{check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME},
-};
+use lemmy_api_common::utils::{local_site_to_slur_regex, AUTH_COOKIE_NAME};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{
- error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
+ error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::slurs::check_slurs,
};
use std::io::Cursor;
@@ -141,20 +137,6 @@ pub(crate) fn build_totp_2fa(
.with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
}
-#[tracing::instrument(skip_all)]
-pub async fn local_user_view_from_jwt(
- jwt: &str,
- context: &LemmyContext,
-) -> Result {
- let local_user_id = Claims::validate(jwt, context)
- .await
- .with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
- let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
- check_user_valid(&local_user_view.person)?;
-
- Ok(local_user_view)
-}
-
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
diff --git a/crates/api/src/local_user/validate_auth.rs b/crates/api/src/local_user/validate_auth.rs
index d95195dc9..65c63e585 100644
--- a/crates/api/src/local_user/validate_auth.rs
+++ b/crates/api/src/local_user/validate_auth.rs
@@ -1,10 +1,10 @@
-use crate::{local_user_view_from_jwt, read_auth_token};
+use crate::read_auth_token;
use actix_web::{
web::{Data, Json},
HttpRequest,
};
-use lemmy_api_common::{context::LemmyContext, SuccessResponse};
-use lemmy_utils::error::{LemmyError, LemmyErrorType};
+use lemmy_api_common::{claims::Claims, context::LemmyContext, SuccessResponse};
+use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType};
/// Returns an error message if the auth token is invalid for any reason. Necessary because other
/// endpoints silently treat any call with invalid auth as unauthenticated.
@@ -15,7 +15,9 @@ pub async fn validate_auth(
) -> Result, LemmyError> {
let jwt = read_auth_token(&req)?;
if let Some(jwt) = jwt {
- local_user_view_from_jwt(&jwt, &context).await?;
+ Claims::validate(&jwt, &context)
+ .await
+ .with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
} else {
Err(LemmyErrorType::NotLoggedIn)?;
}
diff --git a/crates/api/src/post/list_post_likes.rs b/crates/api/src/post/list_post_likes.rs
new file mode 100644
index 000000000..0e52052df
--- /dev/null
+++ b/crates/api/src/post/list_post_likes.rs
@@ -0,0 +1,24 @@
+use actix_web::web::{Data, Json, Query};
+use lemmy_api_common::{
+ context::LemmyContext,
+ post::{ListPostLikes, ListPostLikesResponse},
+ utils::is_admin,
+};
+use lemmy_db_views::structs::{LocalUserView, VoteView};
+use lemmy_utils::error::LemmyError;
+
+/// Lists likes for a post
+#[tracing::instrument(skip(context))]
+pub async fn list_post_likes(
+ data: Query,
+ context: Data,
+ local_user_view: LocalUserView,
+) -> Result, LemmyError> {
+ // Make sure user is an admin
+ is_admin(&local_user_view)?;
+
+ let post_likes =
+ VoteView::list_for_post(&mut context.pool(), data.post_id, data.page, data.limit).await?;
+
+ Ok(Json(ListPostLikesResponse { post_likes }))
+}
diff --git a/crates/api/src/post/mod.rs b/crates/api/src/post/mod.rs
index a3b84134f..6a6ed9d21 100644
--- a/crates/api/src/post/mod.rs
+++ b/crates/api/src/post/mod.rs
@@ -1,6 +1,7 @@
pub mod feature;
pub mod get_link_metadata;
pub mod like;
+pub mod list_post_likes;
pub mod lock;
pub mod mark_read;
pub mod save;
diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/post_report/resolve.rs
index 3604055fd..ab6688012 100644
--- a/crates/api/src/post_report/resolve.rs
+++ b/crates/api/src/post_report/resolve.rs
@@ -23,7 +23,7 @@ pub async fn resolve_post_report(
check_community_mod_action(
&local_user_view.person,
report.community.id,
- false,
+ true,
&mut context.pool(),
)
.await?;
diff --git a/crates/api_common/src/claims.rs b/crates/api_common/src/claims.rs
index 09191ad71..4e55f44aa 100644
--- a/crates/api_common/src/claims.rs
+++ b/crates/api_common/src/claims.rs
@@ -6,7 +6,7 @@ use lemmy_db_schema::{
newtypes::LocalUserId,
source::login_token::{LoginToken, LoginTokenCreateForm},
};
-use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
+use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
@@ -25,8 +25,7 @@ impl Claims {
validation.required_spec_claims.remove("exp");
let jwt_secret = &context.secret().jwt_secret;
let key = DecodingKey::from_secret(jwt_secret.as_ref());
- let claims =
- decode::(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
+ let claims = decode::(jwt, &key, &validation)?;
let user_id = LocalUserId(claims.claims.sub.parse()?);
let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
if !is_valid {
diff --git a/crates/api_common/src/comment.rs b/crates/api_common/src/comment.rs
index c2589fb2a..003e84d38 100644
--- a/crates/api_common/src/comment.rs
+++ b/crates/api_common/src/comment.rs
@@ -3,7 +3,7 @@ use lemmy_db_schema::{
CommentSortType,
ListingType,
};
-use lemmy_db_views::structs::{CommentReportView, CommentView};
+use lemmy_db_views::structs::{CommentReportView, CommentView, VoteView};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@@ -176,3 +176,22 @@ pub struct ListCommentReports {
pub struct ListCommentReportsResponse {
pub comment_reports: Vec,
}
+
+#[skip_serializing_none]
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+/// List comment likes. Admins-only.
+pub struct ListCommentLikes {
+ pub comment_id: CommentId,
+ pub page: Option,
+ pub limit: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+/// The comment likes response
+pub struct ListCommentLikesResponse {
+ pub comment_likes: Vec,
+}
diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs
index a82b9327e..2b69a13ff 100644
--- a/crates/api_common/src/lib.rs
+++ b/crates/api_common/src/lib.rs
@@ -43,5 +43,5 @@ impl Default for SuccessResponse {
/// how long to sleep based on how many retries have already happened
pub fn federate_retry_sleep_duration(retry_count: i32) -> Duration {
- Duration::from_secs_f64(10.0 * 2.0_f64.powf(f64::from(retry_count)))
+ Duration::from_secs_f64(2.0_f64.powf(f64::from(retry_count)))
}
diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs
index c7ee08983..5b3a76110 100644
--- a/crates/api_common/src/post.rs
+++ b/crates/api_common/src/post.rs
@@ -4,7 +4,7 @@ use lemmy_db_schema::{
PostFeatureType,
SortType,
};
-use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView};
+use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView};
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
@@ -252,3 +252,22 @@ pub struct SiteMetadata {
pub(crate) image: Option,
pub embed_video_url: Option,
}
+
+#[skip_serializing_none]
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+/// List post likes. Admins-only.
+pub struct ListPostLikes {
+ pub post_id: PostId,
+ pub page: Option,
+ pub limit: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+/// The post likes response
+pub struct ListPostLikesResponse {
+ pub post_likes: Vec,
+}
diff --git a/crates/api_crud/Cargo.toml b/crates/api_crud/Cargo.toml
index ea7c81399..f4f89c2e5 100644
--- a/crates/api_crud/Cargo.toml
+++ b/crates/api_crud/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "lemmy_api_crud"
+publish = false
version.workspace = true
edition.workspace = true
description.workspace = true
diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml
index d0e226aa4..82f9812e6 100644
--- a/crates/apub/Cargo.toml
+++ b/crates/apub/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "lemmy_apub"
+publish = false
version.workspace = true
edition.workspace = true
description.workspace = true
diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs
index 7da5ac8ae..974810f61 100644
--- a/crates/apub/src/activities/community/report.rs
+++ b/crates/apub/src/activities/community/report.rs
@@ -1,7 +1,7 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community},
insert_received_activity,
- objects::{community::ApubCommunity, person::ApubPerson},
+ objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
protocol::{activities::community::report::Report, InCommunity},
PostOrComment,
};
@@ -19,8 +19,9 @@ use lemmy_db_schema::{
community::Community,
person::Person,
post_report::{PostReport, PostReportForm},
+ site::Site,
},
- traits::Reportable,
+ traits::{Crud, Reportable},
};
use lemmy_utils::error::LemmyError;
use url::Url;
@@ -44,18 +45,31 @@ impl Report {
let report = Report {
actor: actor.id().into(),
to: [community.id().into()],
- object: object_id,
+ object: object_id.clone(),
summary: reason,
kind,
id: id.clone(),
audience: Some(community.id().into()),
};
- let inbox = if community.local {
- ActivitySendTargets::empty()
- } else {
- ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox())
+
+ // send report to the community where object was posted
+ let mut inboxes = ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox());
+
+ // also send report to user's home instance if possible
+ let object_creator_id = match object_id.dereference_local(&context).await? {
+ PostOrComment::Post(p) => p.creator_id,
+ PostOrComment::Comment(c) => c.creator_id,
};
- send_lemmy_activity(&context, report, &actor, inbox, false).await
+ let object_creator = Person::read(&mut context.pool(), object_creator_id).await?;
+ let object_creator_site: Option =
+ Site::read_from_instance_id(&mut context.pool(), object_creator.instance_id)
+ .await?
+ .map(Into::into);
+ if let Some(inbox) = object_creator_site.map(|s| s.shared_inbox_or_inbox()) {
+ inboxes.add_inbox(inbox);
+ }
+
+ send_lemmy_activity(&context, report, &actor, inboxes, false).await
}
}
diff --git a/crates/db_views/src/lib.rs b/crates/db_views/src/lib.rs
index 8abf776ba..73310d743 100644
--- a/crates/db_views/src/lib.rs
+++ b/crates/db_views/src/lib.rs
@@ -22,3 +22,5 @@ pub mod registration_application_view;
#[cfg(feature = "full")]
pub mod site_view;
pub mod structs;
+#[cfg(feature = "full")]
+pub mod vote_view;
diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs
index 54bae9482..26d97038c 100644
--- a/crates/db_views/src/private_message_view.rs
+++ b/crates/db_views/src/private_message_view.rs
@@ -210,7 +210,7 @@ mod tests {
.recipient_id(timmy.id)
.content(message_content.clone())
.build();
- let _inserted_sara_timmy_message_form = PrivateMessage::create(pool, &sara_timmy_message_form)
+ PrivateMessage::create(pool, &sara_timmy_message_form)
.await
.unwrap();
@@ -219,7 +219,7 @@ mod tests {
.recipient_id(jess.id)
.content(message_content.clone())
.build();
- let _inserted_sara_jess_message_form = PrivateMessage::create(pool, &sara_jess_message_form)
+ PrivateMessage::create(pool, &sara_jess_message_form)
.await
.unwrap();
@@ -228,7 +228,7 @@ mod tests {
.recipient_id(sara.id)
.content(message_content.clone())
.build();
- let _inserted_timmy_sara_message_form = PrivateMessage::create(pool, &timmy_sara_message_form)
+ PrivateMessage::create(pool, &timmy_sara_message_form)
.await
.unwrap();
@@ -237,13 +237,13 @@ mod tests {
.recipient_id(timmy.id)
.content(message_content.clone())
.build();
- let _inserted_jess_timmy_message_form = PrivateMessage::create(pool, &jess_timmy_message_form)
+ PrivateMessage::create(pool, &jess_timmy_message_form)
.await
.unwrap();
let timmy_messages = PrivateMessageQuery {
unread_only: false,
- creator_id: Option::None,
+ creator_id: None,
..Default::default()
}
.list(pool, timmy.id)
@@ -260,7 +260,7 @@ mod tests {
let timmy_unread_messages = PrivateMessageQuery {
unread_only: true,
- creator_id: Option::None,
+ creator_id: None,
..Default::default()
}
.list(pool, timmy.id)
@@ -320,7 +320,7 @@ mod tests {
let timmy_messages = PrivateMessageQuery {
unread_only: true,
- creator_id: Option::None,
+ creator_id: None,
..Default::default()
}
.list(pool, timmy.id)
@@ -333,5 +333,8 @@ mod tests {
.await
.unwrap();
assert_eq!(timmy_unread_messages, 1);
+
+ // This also deletes all persons and private messages thanks to sql `on delete cascade`
+ Instance::delete(pool, instance.id).await.unwrap();
}
}
diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs
index d0f7fcfc9..a68001a52 100644
--- a/crates/db_views/src/structs.rs
+++ b/crates/db_views/src/structs.rs
@@ -184,3 +184,14 @@ pub struct CustomEmojiView {
pub custom_emoji: CustomEmoji,
pub keywords: Vec,
}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Eq, 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 vote view for checking a post or comments votes.
+pub struct VoteView {
+ pub creator: Person,
+ pub score: i16,
+}
diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs
new file mode 100644
index 000000000..e8f5429e4
--- /dev/null
+++ b/crates/db_views/src/vote_view.rs
@@ -0,0 +1,195 @@
+use crate::structs::VoteView;
+use diesel::{result::Error, ExpressionMethods, QueryDsl};
+use diesel_async::RunQueryDsl;
+use lemmy_db_schema::{
+ newtypes::{CommentId, PostId},
+ schema::{comment_like, person, post_like},
+ utils::{get_conn, limit_and_offset, DbPool},
+};
+
+impl VoteView {
+ pub async fn list_for_post(
+ pool: &mut DbPool<'_>,
+ post_id: PostId,
+ page: Option,
+ limit: Option,
+ ) -> Result, Error> {
+ let conn = &mut get_conn(pool).await?;
+ let (limit, offset) = limit_and_offset(page, limit)?;
+
+ post_like::table
+ .inner_join(person::table)
+ .filter(post_like::post_id.eq(post_id))
+ .select((person::all_columns, post_like::score))
+ .order_by(post_like::score)
+ .limit(limit)
+ .offset(offset)
+ .load::(conn)
+ .await
+ }
+
+ pub async fn list_for_comment(
+ pool: &mut DbPool<'_>,
+ comment_id: CommentId,
+ page: Option,
+ limit: Option,
+ ) -> Result, Error> {
+ let conn = &mut get_conn(pool).await?;
+ let (limit, offset) = limit_and_offset(page, limit)?;
+
+ comment_like::table
+ .inner_join(person::table)
+ .filter(comment_like::comment_id.eq(comment_id))
+ .select((person::all_columns, comment_like::score))
+ .order_by(comment_like::score)
+ .limit(limit)
+ .offset(offset)
+ .load::(conn)
+ .await
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #![allow(clippy::unwrap_used)]
+ #![allow(clippy::indexing_slicing)]
+
+ use crate::structs::VoteView;
+ use lemmy_db_schema::{
+ source::{
+ comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
+ community::{Community, CommunityInsertForm},
+ instance::Instance,
+ person::{Person, PersonInsertForm},
+ post::{Post, PostInsertForm, PostLike, PostLikeForm},
+ },
+ traits::{Crud, Likeable},
+ utils::build_db_pool_for_tests,
+ };
+ use serial_test::serial;
+
+ #[tokio::test]
+ #[serial]
+ async fn post_and_comment_vote_views() {
+ let pool = &build_db_pool_for_tests().await;
+ let pool = &mut pool.into();
+
+ let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
+ .await
+ .unwrap();
+
+ let new_person = PersonInsertForm::builder()
+ .name("timmy_vv".into())
+ .public_key("pubkey".to_string())
+ .instance_id(inserted_instance.id)
+ .build();
+
+ let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
+
+ let new_person_2 = PersonInsertForm::builder()
+ .name("sara_vv".into())
+ .public_key("pubkey".to_string())
+ .instance_id(inserted_instance.id)
+ .build();
+
+ let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
+
+ let new_community = CommunityInsertForm::builder()
+ .name("test community vv".to_string())
+ .title("nada".to_owned())
+ .public_key("pubkey".to_string())
+ .instance_id(inserted_instance.id)
+ .build();
+
+ let inserted_community = Community::create(pool, &new_community).await.unwrap();
+
+ let new_post = PostInsertForm::builder()
+ .name("A test post vv".into())
+ .creator_id(inserted_timmy.id)
+ .community_id(inserted_community.id)
+ .build();
+
+ let inserted_post = Post::create(pool, &new_post).await.unwrap();
+
+ let comment_form = CommentInsertForm::builder()
+ .content("A test comment vv".into())
+ .creator_id(inserted_timmy.id)
+ .post_id(inserted_post.id)
+ .build();
+
+ let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap();
+
+ // Timmy upvotes his own post
+ let timmy_post_vote_form = PostLikeForm {
+ post_id: inserted_post.id,
+ person_id: inserted_timmy.id,
+ score: 1,
+ };
+ PostLike::like(pool, &timmy_post_vote_form).await.unwrap();
+
+ // Sara downvotes timmy's post
+ let sara_post_vote_form = PostLikeForm {
+ post_id: inserted_post.id,
+ person_id: inserted_sara.id,
+ score: -1,
+ };
+ PostLike::like(pool, &sara_post_vote_form).await.unwrap();
+
+ let expected_post_vote_views = [
+ VoteView {
+ creator: inserted_sara.clone(),
+ score: -1,
+ },
+ VoteView {
+ creator: inserted_timmy.clone(),
+ score: 1,
+ },
+ ];
+
+ let read_post_vote_views = VoteView::list_for_post(pool, inserted_post.id, None, None)
+ .await
+ .unwrap();
+ assert_eq!(read_post_vote_views, expected_post_vote_views);
+
+ // Timothy votes down his own comment
+ let timmy_comment_vote_form = CommentLikeForm {
+ post_id: inserted_post.id,
+ comment_id: inserted_comment.id,
+ person_id: inserted_timmy.id,
+ score: -1,
+ };
+ CommentLike::like(pool, &timmy_comment_vote_form)
+ .await
+ .unwrap();
+
+ // Sara upvotes timmy's comment
+ let sara_comment_vote_form = CommentLikeForm {
+ post_id: inserted_post.id,
+ comment_id: inserted_comment.id,
+ person_id: inserted_sara.id,
+ score: 1,
+ };
+ CommentLike::like(pool, &sara_comment_vote_form)
+ .await
+ .unwrap();
+
+ let expected_comment_vote_views = [
+ VoteView {
+ creator: inserted_timmy.clone(),
+ score: -1,
+ },
+ VoteView {
+ creator: inserted_sara.clone(),
+ score: 1,
+ },
+ ];
+
+ let read_comment_vote_views = VoteView::list_for_comment(pool, inserted_comment.id, None, None)
+ .await
+ .unwrap();
+ assert_eq!(read_comment_vote_views, expected_comment_vote_views);
+
+ // Cleanup
+ Instance::delete(pool, inserted_instance.id).await.unwrap();
+ }
+}
diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs
index f42fa15cf..16e9b3bc6 100644
--- a/crates/db_views_actor/src/person_view.rs
+++ b/crates/db_views_actor/src/person_view.rs
@@ -230,7 +230,6 @@ mod tests {
#[tokio::test]
#[serial]
- #[allow(clippy::dbg_macro)]
async fn exclude_deleted() {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
@@ -257,7 +256,6 @@ mod tests {
.list(pool)
.await
.unwrap();
- dbg!(&list);
assert_eq!(list.len(), 1);
assert_eq!(list[0].person.id, data.bob.id);
diff --git a/crates/federate/Cargo.toml b/crates/federate/Cargo.toml
index 38a0e1433..9ccd8adc3 100644
--- a/crates/federate/Cargo.toml
+++ b/crates/federate/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "lemmy_federate"
+publish = false
version.workspace = true
edition.workspace = true
description.workspace = true
diff --git a/crates/federate/src/lib.rs b/crates/federate/src/lib.rs
index 382aa59b8..a4dc49536 100644
--- a/crates/federate/src/lib.rs
+++ b/crates/federate/src/lib.rs
@@ -23,6 +23,7 @@ static INSTANCES_RECHECK_DELAY: Duration = Duration::from_secs(5);
#[cfg(not(debug_assertions))]
static INSTANCES_RECHECK_DELAY: Duration = Duration::from_secs(60);
+#[derive(Clone)]
pub struct Opts {
/// how many processes you are starting in total
pub process_count: i32,
@@ -36,7 +37,7 @@ async fn start_stop_federation_workers(
federation_config: FederationConfig,
cancel: CancellationToken,
) -> anyhow::Result<()> {
- let mut workers = HashMap::>::new();
+ let mut workers = HashMap::::new();
let (stats_sender, stats_receiver) = unbounded_channel();
let exit_print = tokio::spawn(receive_print_stats(pool.clone(), stats_receiver));
@@ -66,40 +67,30 @@ async fn start_stop_federation_workers(
let should_federate = allowed && !is_dead;
if should_federate {
if workers.contains_key(&instance.id) {
- if workers
- .get(&instance.id)
- .map(util::CancellableTask::has_ended)
- .unwrap_or(false)
- {
- // task must have errored out, remove and recreated it
- let worker = workers
- .remove(&instance.id)
- .expect("just checked contains_key");
- tracing::error!(
- "worker for {} has stopped, recreating: {:?}",
- instance.domain,
- worker.cancel().await
- );
- } else {
- continue;
- }
+ // worker already running
+ continue;
}
// create new worker
+ let config = federation_config.clone();
let stats_sender = stats_sender.clone();
- let context = federation_config.to_request_data();
let pool = pool.clone();
workers.insert(
instance.id,
- CancellableTask::spawn(WORKER_EXIT_TIMEOUT, |stop| async move {
- InstanceWorker::init_and_loop(
- instance,
- context,
- &mut DbPool::Pool(&pool),
- stop,
- stats_sender,
- )
- .await?;
- Ok(())
+ CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |stop| {
+ let instance = instance.clone();
+ let req_data = config.clone().to_request_data();
+ let stats_sender = stats_sender.clone();
+ let pool = pool.clone();
+ async move {
+ InstanceWorker::init_and_loop(
+ instance,
+ req_data,
+ &mut DbPool::Pool(&pool),
+ stop,
+ stats_sender,
+ )
+ .await
+ }
}),
);
} else if !should_federate {
@@ -135,9 +126,12 @@ pub fn start_stop_federation_workers_cancellable(
opts: Opts,
pool: ActualDbPool,
config: FederationConfig,
-) -> CancellableTask<()> {
- CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |c| {
- start_stop_federation_workers(opts, pool, config, c)
+) -> CancellableTask {
+ CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |stop| {
+ let opts = opts.clone();
+ let pool = pool.clone();
+ let config = config.clone();
+ async move { start_stop_federation_workers(opts, pool, config, stop).await }
})
}
diff --git a/crates/federate/src/util.rs b/crates/federate/src/util.rs
index 1775e4153..848785836 100644
--- a/crates/federate/src/util.rs
+++ b/crates/federate/src/util.rs
@@ -20,12 +20,7 @@ use moka::future::Cache;
use once_cell::sync::Lazy;
use reqwest::Url;
use serde_json::Value;
-use std::{
- future::Future,
- pin::Pin,
- sync::{Arc, RwLock},
- time::Duration,
-};
+use std::{fmt::Debug, future::Future, pin::Pin, sync::Arc, time::Duration};
use tokio::{task::JoinHandle, time::sleep};
use tokio_util::sync::CancellationToken;
@@ -49,41 +44,41 @@ pub(crate) static WORK_FINISHED_RECHECK_DELAY: Lazy = Lazy::new(|| {
}
});
-pub struct CancellableTask {
- f: Pin> + Send + 'static>>,
- ended: Arc>,
+/// A task that will be run in an infinite loop, unless it is cancelled.
+/// If the task exits without being cancelled, an error will be logged and the task will be restarted.
+pub struct CancellableTask {
+ f: Pin> + Send + 'static>>,
}
-impl CancellableTask {
+impl CancellableTask {
/// spawn a task but with graceful shutdown
- pub fn spawn(
+ pub fn spawn(
timeout: Duration,
- task: impl FnOnce(CancellationToken) -> F,
- ) -> CancellableTask
+ task: impl Fn(CancellationToken) -> F + Send + 'static,
+ ) -> CancellableTask
where
- F: Future