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> + Send + 'static, + F: Future + Send + 'static, { let stop = CancellationToken::new(); - let task = task(stop.clone()); - let ended = Arc::new(RwLock::new(false)); - let ended_write = ended.clone(); - let task: JoinHandle> = tokio::spawn(async move { - match task.await { - Ok(o) => Ok(o), - Err(e) => { - *ended_write.write().expect("poisoned") = true; - Err(e) + let stop2 = stop.clone(); + let task: JoinHandle<()> = tokio::spawn(async move { + loop { + let res = task(stop2.clone()).await; + if stop2.is_cancelled() { + return; + } else { + tracing::warn!("task exited, restarting: {res:?}"); } } }); let abort = task.abort_handle(); CancellableTask { - ended, f: Box::pin(async move { stop.cancel(); tokio::select! { r = task => { - Ok(r.context("could not join")??) + r.context("could not join")?; + Ok(()) }, _ = sleep(timeout) => { abort.abort(); @@ -96,12 +91,9 @@ impl CancellableTask { } /// cancel the cancel signal, wait for timeout for the task to stop gracefully, otherwise abort it - pub async fn cancel(self) -> Result { + pub async fn cancel(self) -> Result<(), anyhow::Error> { self.f.await } - pub fn has_ended(&self) -> bool { - *self.ended.read().expect("poisoned") - } } /// assuming apub priv key and ids are immutable, then we don't need to have TTL diff --git a/crates/federate/src/worker.rs b/crates/federate/src/worker.rs index 963814ad9..6dff325b6 100644 --- a/crates/federate/src/worker.rs +++ b/crates/federate/src/worker.rs @@ -206,6 +206,7 @@ impl InstanceWorker { .await .context("failed figuring out inbox urls")?; if inbox_urls.is_empty() { + tracing::debug!("{}: {:?} no inboxes", self.instance.domain, activity.id); self.state.last_successful_id = Some(activity.id); self.state.last_successful_published_time = Some(activity.published); return Ok(()); diff --git a/crates/routes/Cargo.toml b/crates/routes/Cargo.toml index 40fa2ab01..82c786576 100644 --- a/crates/routes/Cargo.toml +++ b/crates/routes/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "lemmy_routes" +publish = false version.workspace = true edition.workspace = true description.workspace = true diff --git a/crates/routes/src/lib.rs b/crates/routes/src/lib.rs index ec28fda45..b1e97cd3c 100644 --- a/crates/routes/src/lib.rs +++ b/crates/routes/src/lib.rs @@ -1,6 +1,6 @@ use lemmy_api_common::{claims::Claims, context::LemmyContext, utils::check_user_valid}; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; pub mod feeds; pub mod images; @@ -12,7 +12,9 @@ async fn local_user_view_from_jwt( jwt: &str, context: &LemmyContext, ) -> Result { - let local_user_id = Claims::validate(jwt, context).await?; + 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)?; diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 654232a46..e762ec1d3 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -6,7 +6,7 @@ use url::Url; // From here: https://github.com/vector-im/element-android/blob/develop/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt#L35 static VALID_MATRIX_ID_REGEX: Lazy = Lazy::new(|| { - Regex::new(r"^@[A-Za-z0-9\\x21-\\x39\\x3B-\\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$") + Regex::new(r"^@[A-Za-z0-9\x21-\x39\x3B-\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$") .expect("compile regex") }); // taken from https://en.wikipedia.org/wiki/UTM_parameters @@ -386,6 +386,7 @@ mod tests { #[test] fn test_valid_matrix_id() { assert!(is_valid_matrix_id("@dess:matrix.org").is_ok()); + assert!(is_valid_matrix_id("@dess_:matrix.org").is_ok()); assert!(is_valid_matrix_id("@dess:matrix.org:443").is_ok()); assert!(is_valid_matrix_id("dess:matrix.org").is_err()); assert!(is_valid_matrix_id(" @dess:matrix.org").is_err()); diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4c1a67411..eb81766cc 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -77,7 +77,7 @@ services: init: true pictrs: - image: asonix/pictrs:0.4.0-beta.19 + image: asonix/pictrs:0.5.0-rc.2 # this needs to match the pictrs url in lemmy.hjson hostname: pictrs # we can set options to pictrs like this, here we set max. image size and forced format for conversion diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index 6e0e7f2d2..a8c6160df 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -49,7 +49,7 @@ services: pictrs: restart: always - image: asonix/pictrs:0.4.0-beta.19 + image: asonix/pictrs:0.5.0-rc.2 user: 991:991 volumes: - ./volumes/pictrs_alpha:/mnt:Z diff --git a/migrations/2024-01-02-094916_site-name-not-unique/down.sql b/migrations/2024-01-02-094916_site-name-not-unique/down.sql new file mode 100644 index 000000000..eef68d2fe --- /dev/null +++ b/migrations/2024-01-02-094916_site-name-not-unique/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE site + ADD CONSTRAINT site_name_key UNIQUE (name); + diff --git a/migrations/2024-01-02-094916_site-name-not-unique/up.sql b/migrations/2024-01-02-094916_site-name-not-unique/up.sql new file mode 100644 index 000000000..02f07d67a --- /dev/null +++ b/migrations/2024-01-02-094916_site-name-not-unique/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE site + DROP CONSTRAINT site_name_key; + diff --git a/readmes/README.es.md b/readmes/README.es.md index b53b2b020..56fc908b2 100644 --- a/readmes/README.es.md +++ b/readmes/README.es.md @@ -45,9 +45,9 @@ ## Sobre El Proyecto -| Escritorio | Móvil | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| Escritorio | Móvil | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) es similar a sitios como [Menéame](https://www.meneame.net/), [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), o [Hacker News](https://news.ycombinator.com/): te subscribes a los foros que te interesan, publicas enlaces y debates, luego votas y comentas en ellos. Entre bastidores, es muy diferente; cualquiera puede gestionar fácilmente un servidor, y todos estos servidores son federados (piensa en el correo electrónico), y conectados al mismo universo, llamado [Fediverso](https://es.wikipedia.org/wiki/Fediverso). diff --git a/readmes/README.ja.md b/readmes/README.ja.md index e026a9091..0a64cd055 100644 --- a/readmes/README.ja.md +++ b/readmes/README.ja.md @@ -47,9 +47,9 @@ ## プロジェクトについて -| デスクトップ | モバイル | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| デスクトップ | モバイル | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) は、[Reddit](https://reddit.com)、[Lobste.rs](https://lobste.rs)、[Hacker News](https://news.ycombinator.com/) といったサイトに似ています。興味のあるフォーラムを購読してリンクや議論を掲載し、投票したり、コメントしたりしています。誰でも簡単にサーバーを運営することができ、これらのサーバーはすべて連合しており(電子メールを考えてください)、[Fediverse](https://en.wikipedia.org/wiki/Fediverse) と呼ばれる同じ宇宙に接続されています。 diff --git a/readmes/README.ru.md b/readmes/README.ru.md index d9693901d..1eb585e69 100644 --- a/readmes/README.ru.md +++ b/readmes/README.ru.md @@ -45,9 +45,9 @@ ## О проекте -| Десктоп | Мобильный | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| Десктоп | Мобильный | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) это аналог таких сайтов как [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), или [Hacker News](https://news.ycombinator.com/): вы подписываетесь на форумы, которые вас интересуют , размещаете ссылки и дискутируете, затем голосуете и комментируете их. Однако за кулисами всё совсем по-другому; любой может легко запустить сервер, и все эти серверы объединены (например электронная почта) и подключены к одной вселенной, именуемой [Федиверс](https://ru.wikipedia.org/wiki/Fediverse). diff --git a/readmes/README.zh.hans.md b/readmes/README.zh.hans.md index 3c21e1fa4..56ab1111d 100644 --- a/readmes/README.zh.hans.md +++ b/readmes/README.zh.hans.md @@ -47,9 +47,9 @@ ## 关于项目 -| 桌面应用 | 移动应用 | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| 桌面应用 | 移动应用 | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) 与 [Reddit](https://reddit.com)、[Lobste.rs](https://lobste.rs) 或 [Hacker News](https://news.ycombinator.com/) 等网站类似:你可以订阅你感兴趣的论坛,发布链接和讨论,然后进行投票或评论。但在幕后,Lemmy 和他们不同——任何人都可以很容易地运行一个服务器,所有服务器都是联邦式的(想想电子邮件),并连接到 [联邦宇宙](https://zh.wikipedia.org/wiki/%E8%81%94%E9%82%A6%E5%AE%87%E5%AE%99)。 diff --git a/readmes/README.zh.hant.md b/readmes/README.zh.hant.md index aa2c0ff7b..3b08e0cf1 100644 --- a/readmes/README.zh.hant.md +++ b/readmes/README.zh.hant.md @@ -48,9 +48,9 @@ ## 關於專案 -| 桌面設備 | 行動裝置 | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| 桌面設備 | 行動裝置 | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) 與 [Reddit](https://reddit.com)、[Lobste.rs](https://lobste.rs) 或 [Hacker News](https://news.ycombinator.com/) 等網站類似:你可以訂閱你感興趣的論壇,釋出連結和討論,然後進行投票或評論。但在幕後,Lemmy 和他們不同——任何人都可以很容易地架設一個伺服器,所有伺服器都是聯邦式的(想想電子郵件),並與 [聯邦宇宙](https://zh.wikipedia.org/wiki/%E8%81%94%E9%82%A6%E5%AE%87%E5%AE%99) 互聯。 diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 5a1bb346f..018a445b1 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -1,6 +1,11 @@ use actix_web::{guard, web}; use lemmy_api::{ - comment::{distinguish::distinguish_comment, like::like_comment, save::save_comment}, + comment::{ + distinguish::distinguish_comment, + like::like_comment, + list_comment_likes::list_comment_likes, + save::save_comment, + }, comment_report::{ create::create_comment_report, list::list_comment_reports, @@ -45,6 +50,7 @@ use lemmy_api::{ feature::feature_post, get_link_metadata::get_link_metadata, like::like_post, + list_post_likes::list_post_likes, lock::lock_post, mark_read::mark_post_as_read, save::save_post, @@ -202,6 +208,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/feature", web::post().to(feature_post)) .route("/list", web::get().to(list_posts)) .route("/like", web::post().to(like_post)) + .route("/like/list", web::get().to(list_post_likes)) .route("/save", web::put().to(save_post)) .route("/report", web::post().to(create_post_report)) .route("/report/resolve", web::put().to(resolve_post_report)) @@ -226,6 +233,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/mark_as_read", web::post().to(mark_reply_as_read)) .route("/distinguish", web::post().to(distinguish_comment)) .route("/like", web::post().to(like_comment)) + .route("/like/list", web::get().to(list_comment_likes)) .route("/save", web::put().to(save_comment)) .route("/list", web::get().to(list_comments)) .route("/report", web::post().to(create_comment_report)) diff --git a/src/session_middleware.rs b/src/session_middleware.rs index f50e0eccd..429c47636 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -7,8 +7,14 @@ use actix_web::{ }; use core::future::Ready; use futures_util::future::LocalBoxFuture; -use lemmy_api::{local_user_view_from_jwt, read_auth_token}; -use lemmy_api_common::context::LemmyContext; +use lemmy_api::read_auth_token; +use lemmy_api_common::{ + claims::Claims, + context::LemmyContext, + lemmy_db_views::structs::LocalUserView, + utils::check_user_valid, +}; +use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; use reqwest::header::HeaderValue; use std::{future::ready, rc::Rc}; @@ -67,14 +73,14 @@ where let jwt = read_auth_token(req.request())?; if let Some(jwt) = &jwt { - // Ignore any invalid auth so the site can still be used - // TODO: this means it will be impossible to get any error message for invalid jwt. Need - // to add a separate endpoint for that. - // https://github.com/LemmyNet/lemmy/issues/3702 - let local_user_view = local_user_view_from_jwt(jwt, &context).await.ok(); - if let Some(local_user_view) = local_user_view { - req.extensions_mut().insert(local_user_view); - } + let local_user_id = Claims::validate(jwt, &context) + .await + .with_lemmy_type(LemmyErrorType::IncorrectLogin)?; + let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id) + .await + .map_err(LemmyError::from)?; + check_user_valid(&local_user_view.person)?; + req.extensions_mut().insert(local_user_view); } let mut res = svc.call(req).await?;