"memchr",
]
-[[package]]
-name = "ansi_term"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
-dependencies = [
- "winapi 0.3.9",
-]
-
[[package]]
name = "anyhow"
version = "1.0.33"
"winapi 0.3.9",
]
-[[package]]
-name = "clap"
-version = "2.33.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
-dependencies = [
- "ansi_term",
- "atty",
- "bitflags 1.2.1",
- "strsim 0.8.0",
- "textwrap",
- "unicode-width",
- "vec_map",
-]
-
[[package]]
name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d325e4f2ffff52ca77d995bb675494d5364aa332499d5f7c7fbb28c25e671f6"
dependencies = [
- "clap",
"entities",
"lazy_static",
"pest",
"ident_case",
"proc-macro2",
"quote",
- "strsim 0.9.3",
+ "strsim",
"syn",
]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
-[[package]]
-name = "strsim"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-
[[package]]
name = "strsim"
version = "0.9.3"
"winapi-util",
]
-[[package]]
-name = "textwrap"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-dependencies = [
- "unicode-width",
-]
-
[[package]]
name = "thiserror"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
-[[package]]
-name = "unicode-width"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
-
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
-[[package]]
-name = "vec_map"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
-
[[package]]
name = "version_check"
version = "0.1.5"
If the ***`host`*** supports secure connections, you can use <code>wss://***host***/api/v1/ws</code>.
-To receive websocket messages, you must join a room / context. The three available are:
+To receive websocket messages, you must join a room / context. The four available are:
- [UserJoin](#user-join). Receives replies, private messages, etc.
- [PostJoin](#post-join). Receives new comments on a post.
- [CommunityJoin](#community-join). Receives front page / community posts.
+- [ModJoin](#mod-join). Receives community moderator updates like reports.
#### Testing with Websocat
`POST /user/join`
+#### Get Report Count
+
+If a community is supplied, returns the report count for only that community, otherwise returns the report count for all communities the user moderates.
+
+##### Request
+```rust
+{
+ op: "GetReportCount",
+ data: {
+ community: Option<i32>,
+ auth: String
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "GetReportCount",
+ data: {
+ community: Option<i32>,
+ comment_reports: i64,
+ post_reports: i64,
+ }
+}
+```
+##### HTTP
+
+`GET /user/report_count`
+
### Site
#### List Categories
##### Request
`POST /community/join`
+#### Mod Join
+##### Request
+```rust
+{
+ op: "ModJoin",
+ data: {
+ community_id: i32
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "ModJoin",
+ data: {
+ joined: bool,
+ }
+}
+```
+##### HTTP
+
+`POST /community/mod/join`
+
### Post
#### Create Post
##### Request
`POST /post/join`
+#### Create Post Report
+##### Request
+```rust
+{
+ op: "CreatePostReport",
+ data: {
+ post_id: i32,
+ reason: String,
+ auth: String
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "CreatePostReport",
+ data: {
+ success: bool
+ }
+}
+```
+##### HTTP
+
+`POST /post/report`
+
+#### Resolve Post Report
+##### Request
+```rust
+{
+ op: "ResolvePostReport",
+ data: {
+ report_id: i32,
+ resolved: bool,
+ auth: String
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "ResolvePostReport",
+ data: {
+ report_id: i32,
+ resolved: bool
+ }
+}
+```
+##### HTTP
+
+`PUT /post/report/resolve`
+
+#### List Post Reports
+
+If a community is supplied, returns reports for only that community, otherwise returns the reports for all communities the user moderates
+
+##### Request
+```rust
+{
+ op: "ListPostReports",
+ data: {
+ page: Option<i64>,
+ limit: Option<i64>,
+ community: Option<i32>,
+ auth: String
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "ListPostReports",
+ data: {
+ posts: Vec<PostReportView>
+ }
+}
+```
+##### HTTP
+
+`GET /post/report/list`
+
### Comment
#### Create Comment
##### Request
`POST /comment/like`
+#### Create Comment Report
+##### Request
+```rust
+{
+ op: "CreateCommentReport",
+ data: {
+ comment_id: i32,
+ reason: String,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "CreateCommentReport",
+ data: {
+ success: bool,
+ }
+}
+```
+##### HTTP
+
+`POST /comment/report`
+
+#### Resolve Comment Report
+##### Request
+```rust
+{
+ op: "ResolveCommentReport",
+ data: {
+ report_id: i32,
+ resolved: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "ResolveCommentReport",
+ data: {
+ report_id: i32,
+ resolved: bool,
+ }
+}
+```
+##### HTTP
+
+`PUT /comment/report/resolve`
+
+#### List Comment Reports
+
+If a community is supplied, returns reports for only that community, otherwise returns the reports for all communities the user moderates
+
+##### Request
+```rust
+{
+ op: "ListCommentReports",
+ data: {
+ page: Option<i64>,
+ limit: Option<i64>,
+ community: Option<i32>,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "ListCommentReports",
+ data: {
+ comments: Vec<CommentReportView>
+ }
+}
+```
+##### HTTP
+
+`GET /comment/report/list`
+
### RSS / Atom feeds
#### All
use crate::{
check_community_ban,
+ collect_moderated_communities,
get_post,
get_user_from_jwt,
get_user_from_jwt_opt,
use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_db::{
comment::*,
+ comment_report::*,
comment_view::*,
moderator::*,
post::*,
Crud,
Likeable,
ListingType,
+ Reportable,
Saveable,
SortType,
};
ConnectionId,
LemmyError,
};
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
+use lemmy_websocket::{
+ messages::{SendComment, SendModRoomMessage, SendUserRoomMessage},
+ LemmyContext,
+ UserOperation,
+};
use std::str::FromStr;
#[async_trait::async_trait(?Send)]
Ok(GetCommentsResponse { comments })
}
}
+
+/// Creates a comment report and notifies the moderators of the community
+#[async_trait::async_trait(?Send)]
+impl Perform for CreateCommentReport {
+ type Response = CreateCommentReportResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<CreateCommentReportResponse, LemmyError> {
+ let data: &CreateCommentReport = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ // check size of report and check for whitespace
+ let reason = data.reason.trim();
+ if reason.is_empty() {
+ return Err(APIError::err("report_reason_required").into());
+ }
+ if reason.len() > 1000 {
+ return Err(APIError::err("report_too_long").into());
+ }
+
+ let user_id = user.id;
+ let comment_id = data.comment_id;
+ let comment = blocking(context.pool(), move |conn| {
+ CommentView::read(&conn, comment_id, None)
+ })
+ .await??;
+
+ check_community_ban(user_id, comment.community_id, context.pool()).await?;
+
+ let report_form = CommentReportForm {
+ creator_id: user_id,
+ comment_id,
+ original_comment_text: comment.content,
+ reason: data.reason.to_owned(),
+ };
+
+ let report = match blocking(context.pool(), move |conn| {
+ CommentReport::report(conn, &report_form)
+ })
+ .await?
+ {
+ Ok(report) => report,
+ Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
+ };
+
+ let res = CreateCommentReportResponse { success: true };
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::CreateCommentReport,
+ response: res.clone(),
+ recipient_id: user.id,
+ websocket_id,
+ });
+
+ context.chat_server().do_send(SendModRoomMessage {
+ op: UserOperation::CreateCommentReport,
+ response: report,
+ community_id: comment.community_id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+/// Resolves or unresolves a comment report and notifies the moderators of the community
+#[async_trait::async_trait(?Send)]
+impl Perform for ResolveCommentReport {
+ type Response = ResolveCommentReportResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<ResolveCommentReportResponse, LemmyError> {
+ let data: &ResolveCommentReport = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let report_id = data.report_id;
+ let report = blocking(context.pool(), move |conn| {
+ CommentReportView::read(&conn, report_id)
+ })
+ .await??;
+
+ let user_id = user.id;
+ is_mod_or_admin(context.pool(), user_id, report.community_id).await?;
+
+ let resolved = data.resolved;
+ let resolve_fun = move |conn: &'_ _| {
+ if resolved {
+ CommentReport::resolve(conn, report_id.clone(), user_id)
+ } else {
+ CommentReport::unresolve(conn, report_id.clone(), user_id)
+ }
+ };
+
+ if blocking(context.pool(), resolve_fun).await?.is_err() {
+ return Err(APIError::err("couldnt_resolve_report").into());
+ };
+
+ let report_id = data.report_id;
+ let res = ResolveCommentReportResponse {
+ report_id,
+ resolved,
+ };
+
+ context.chat_server().do_send(SendModRoomMessage {
+ op: UserOperation::ResolveCommentReport,
+ response: res.clone(),
+ community_id: report.community_id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+/// Lists comment reports for a community if an id is supplied
+/// or returns all comment reports for communities a user moderates
+#[async_trait::async_trait(?Send)]
+impl Perform for ListCommentReports {
+ type Response = ListCommentReportsResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<ListCommentReportsResponse, LemmyError> {
+ let data: &ListCommentReports = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let user_id = user.id;
+ let community_id = data.community;
+ let community_ids =
+ collect_moderated_communities(user_id, community_id, context.pool()).await?;
+
+ let page = data.page;
+ let limit = data.limit;
+ let comments = blocking(context.pool(), move |conn| {
+ CommentReportQueryBuilder::create(conn)
+ .community_ids(community_ids)
+ .page(page)
+ .limit(limit)
+ .list()
+ })
+ .await??;
+
+ let res = ListCommentReportsResponse { comments };
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::ListCommentReports,
+ response: res.clone(),
+ recipient_id: user.id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
LemmyError,
};
use lemmy_websocket::{
- messages::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage},
+ messages::{GetCommunityUsersOnline, JoinCommunityRoom, JoinModRoom, SendCommunityRoomMessage},
LemmyContext,
UserOperation,
};
Ok(CommunityJoinResponse { joined: true })
}
}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for ModJoin {
+ type Response = ModJoinResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<ModJoinResponse, LemmyError> {
+ let data: &ModJoin = &self;
+
+ if let Some(ws_id) = websocket_id {
+ context.chat_server().do_send(JoinModRoom {
+ community_id: data.community_id,
+ id: ws_id,
+ });
+ }
+
+ Ok(ModJoinResponse { joined: true })
+ }
+}
use crate::claims::Claims;
use actix_web::{web, web::Data};
use lemmy_db::{
- community::Community,
+ community::{Community, CommunityModerator},
community_view::CommunityUserBanView,
post::Post,
user::User_,
}
}
+/// Returns a list of communities that the user moderates
+/// or if a community_id is supplied validates the user is a moderator
+/// of that community and returns the community id in a vec
+///
+/// * `user_id` - the user id of the moderator
+/// * `community_id` - optional community id to check for moderator privileges
+/// * `pool` - the diesel db pool
+pub(crate) async fn collect_moderated_communities(
+ user_id: i32,
+ community_id: Option<i32>,
+ pool: &DbPool,
+) -> Result<Vec<i32>, LemmyError> {
+ if let Some(community_id) = community_id {
+ // if the user provides a community_id, just check for mod/admin privileges
+ is_mod_or_admin(pool, user_id, community_id).await?;
+ Ok(vec![community_id])
+ } else {
+ let ids = blocking(pool, move |conn: &'_ _| {
+ CommunityModerator::get_user_moderated_communities(conn, user_id)
+ })
+ .await??;
+ Ok(ids)
+ }
+}
+
pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
if let Some(Some(item)) = &item {
if Url::parse(item).is_err() {
UserOperation::CommunityJoin => {
do_websocket_operation::<CommunityJoin>(context, id, op, data).await
}
+ UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
UserOperation::SaveUserSettings => {
do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
}
+ UserOperation::GetReportCount => {
+ do_websocket_operation::<GetReportCount>(context, id, op, data).await
+ }
// Private Message ops
UserOperation::CreatePrivateMessage => {
do_websocket_operation::<CreatePostLike>(context, id, op, data).await
}
UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
+ UserOperation::CreatePostReport => {
+ do_websocket_operation::<CreatePostReport>(context, id, op, data).await
+ }
+ UserOperation::ListPostReports => {
+ do_websocket_operation::<ListPostReports>(context, id, op, data).await
+ }
+ UserOperation::ResolvePostReport => {
+ do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
+ }
// Comment ops
UserOperation::CreateComment => {
UserOperation::CreateCommentLike => {
do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
}
+ UserOperation::CreateCommentReport => {
+ do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
+ }
+ UserOperation::ListCommentReports => {
+ do_websocket_operation::<ListCommentReports>(context, id, op, data).await
+ }
+ UserOperation::ResolveCommentReport => {
+ do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
+ }
}
}
use crate::{
check_community_ban,
check_optional_url,
+ collect_moderated_communities,
get_user_from_jwt,
get_user_from_jwt_opt,
is_mod_or_admin,
moderator::*,
naive_now,
post::*,
+ post_report::*,
post_view::*,
site_view::*,
Crud,
Likeable,
ListingType,
+ Reportable,
Saveable,
SortType,
};
LemmyError,
};
use lemmy_websocket::{
- messages::{GetPostUsersOnline, JoinPostRoom, SendPost},
+ messages::{GetPostUsersOnline, JoinPostRoom, SendModRoomMessage, SendPost, SendUserRoomMessage},
LemmyContext,
UserOperation,
};
Ok(PostJoinResponse { joined: true })
}
}
+
+/// Creates a post report and notifies the moderators of the community
+#[async_trait::async_trait(?Send)]
+impl Perform for CreatePostReport {
+ type Response = CreatePostReportResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<CreatePostReportResponse, LemmyError> {
+ let data: &CreatePostReport = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ // check size of report and check for whitespace
+ let reason = data.reason.trim();
+ if reason.is_empty() {
+ return Err(APIError::err("report_reason_required").into());
+ }
+ if reason.len() > 1000 {
+ return Err(APIError::err("report_too_long").into());
+ }
+
+ let user_id = user.id;
+ let post_id = data.post_id;
+ let post = blocking(context.pool(), move |conn| {
+ PostView::read(&conn, post_id, None)
+ })
+ .await??;
+
+ check_community_ban(user_id, post.community_id, context.pool()).await?;
+
+ let report_form = PostReportForm {
+ creator_id: user_id,
+ post_id,
+ original_post_name: post.name,
+ original_post_url: post.url,
+ original_post_body: post.body,
+ reason: data.reason.to_owned(),
+ };
+
+ let report = match blocking(context.pool(), move |conn| {
+ PostReport::report(conn, &report_form)
+ })
+ .await?
+ {
+ Ok(report) => report,
+ Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
+ };
+
+ let res = CreatePostReportResponse { success: true };
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::CreatePostReport,
+ response: res.clone(),
+ recipient_id: user.id,
+ websocket_id,
+ });
+
+ context.chat_server().do_send(SendModRoomMessage {
+ op: UserOperation::CreatePostReport,
+ response: report,
+ community_id: post.community_id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+/// Resolves or unresolves a post report and notifies the moderators of the community
+#[async_trait::async_trait(?Send)]
+impl Perform for ResolvePostReport {
+ type Response = ResolvePostReportResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<ResolvePostReportResponse, LemmyError> {
+ let data: &ResolvePostReport = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let report_id = data.report_id;
+ let report = blocking(context.pool(), move |conn| {
+ PostReportView::read(&conn, report_id)
+ })
+ .await??;
+
+ let user_id = user.id;
+ is_mod_or_admin(context.pool(), user_id, report.community_id).await?;
+
+ let resolved = data.resolved;
+ let resolve_fun = move |conn: &'_ _| {
+ if resolved {
+ PostReport::resolve(conn, report_id.clone(), user_id)
+ } else {
+ PostReport::unresolve(conn, report_id.clone(), user_id)
+ }
+ };
+
+ let res = ResolvePostReportResponse {
+ report_id,
+ resolved: true,
+ };
+
+ if blocking(context.pool(), resolve_fun).await?.is_err() {
+ return Err(APIError::err("couldnt_resolve_report").into());
+ };
+
+ context.chat_server().do_send(SendModRoomMessage {
+ op: UserOperation::ResolvePostReport,
+ response: res.clone(),
+ community_id: report.community_id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+/// Lists post reports for a community if an id is supplied
+/// or returns all post reports for communities a user moderates
+#[async_trait::async_trait(?Send)]
+impl Perform for ListPostReports {
+ type Response = ListPostReportsResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<ListPostReportsResponse, LemmyError> {
+ let data: &ListPostReports = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let user_id = user.id;
+ let community_id = data.community;
+ let community_ids =
+ collect_moderated_communities(user_id, community_id, context.pool()).await?;
+
+ let page = data.page;
+ let limit = data.limit;
+ let posts = blocking(context.pool(), move |conn| {
+ PostReportQueryBuilder::create(conn)
+ .community_ids(community_ids)
+ .page(page)
+ .limit(limit)
+ .list()
+ })
+ .await??;
+
+ let res = ListPostReportsResponse { posts };
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::ListPostReports,
+ response: res.clone(),
+ recipient_id: user.id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
captcha_espeak_wav_base64,
check_optional_url,
claims::Claims,
+ collect_moderated_communities,
get_user_from_jwt,
get_user_from_jwt_opt,
is_admin,
use lemmy_apub::ApubObjectType;
use lemmy_db::{
comment::*,
+ comment_report::CommentReportView,
comment_view::*,
community::*,
community_view::*,
naive_now,
password_reset_request::*,
post::*,
+ post_report::PostReportView,
post_view::*,
private_message::*,
private_message_view::*,
Ok(UserJoinResponse { joined: true })
}
}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for GetReportCount {
+ type Response = GetReportCountResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<GetReportCountResponse, LemmyError> {
+ let data: &GetReportCount = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let user_id = user.id;
+ let community_id = data.community;
+ let community_ids =
+ collect_moderated_communities(user_id, community_id, context.pool()).await?;
+
+ let res = {
+ if community_ids.is_empty() {
+ GetReportCountResponse {
+ community: None,
+ comment_reports: 0,
+ post_reports: 0,
+ }
+ } else {
+ let ids = community_ids.clone();
+ let comment_reports = blocking(context.pool(), move |conn| {
+ CommentReportView::get_report_count(conn, &ids)
+ })
+ .await??;
+
+ let ids = community_ids.clone();
+ let post_reports = blocking(context.pool(), move |conn| {
+ PostReportView::get_report_count(conn, &ids)
+ })
+ .await??;
+
+ GetReportCountResponse {
+ community: data.community,
+ comment_reports,
+ post_reports,
+ }
+ }
+ };
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::GetReportCount,
+ response: res.clone(),
+ recipient_id: user.id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
--- /dev/null
+use diesel::{dsl::*, pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
+
+use crate::{
+ comment::Comment,
+ limit_and_offset,
+ naive_now,
+ schema::comment_report,
+ MaybeOptional,
+ Reportable,
+};
+
+table! {
+ comment_report_view (id) {
+ id -> Int4,
+ creator_id -> Int4,
+ comment_id -> Int4,
+ original_comment_text -> Text,
+ reason -> Text,
+ resolved -> Bool,
+ resolver_id -> Nullable<Int4>,
+ published -> Timestamp,
+ updated -> Nullable<Timestamp>,
+ post_id -> Int4,
+ current_comment_text -> Text,
+ community_id -> Int4,
+ creator_actor_id -> Text,
+ creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
+ creator_avatar -> Nullable<Text>,
+ creator_local -> Bool,
+ comment_creator_id -> Int4,
+ comment_creator_actor_id -> Text,
+ comment_creator_name -> Varchar,
+ comment_creator_preferred_username -> Nullable<Varchar>,
+ comment_creator_avatar -> Nullable<Text>,
+ comment_creator_local -> Bool,
+ resolver_actor_id -> Nullable<Text>,
+ resolver_name -> Nullable<Varchar>,
+ resolver_preferred_username -> Nullable<Varchar>,
+ resolver_avatar -> Nullable<Text>,
+ resolver_local -> Nullable<Bool>,
+ }
+}
+
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Serialize)]
+#[belongs_to(Comment)]
+#[table_name = "comment_report"]
+pub struct CommentReport {
+ pub id: i32,
+ pub creator_id: i32,
+ pub comment_id: i32,
+ pub original_comment_text: String,
+ pub reason: String,
+ pub resolved: bool,
+ pub resolver_id: Option<i32>,
+ pub published: chrono::NaiveDateTime,
+ pub updated: Option<chrono::NaiveDateTime>,
+}
+
+#[derive(Insertable, AsChangeset, Clone)]
+#[table_name = "comment_report"]
+pub struct CommentReportForm {
+ pub creator_id: i32,
+ pub comment_id: i32,
+ pub original_comment_text: String,
+ pub reason: String,
+}
+
+impl Reportable<CommentReportForm> for CommentReport {
+ /// creates a comment report and returns it
+ ///
+ /// * `conn` - the postgres connection
+ /// * `comment_report_form` - the filled CommentReportForm to insert
+ fn report(conn: &PgConnection, comment_report_form: &CommentReportForm) -> Result<Self, Error> {
+ use crate::schema::comment_report::dsl::*;
+ insert_into(comment_report)
+ .values(comment_report_form)
+ .get_result::<Self>(conn)
+ }
+
+ /// resolve a comment 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
+ fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+ use crate::schema::comment_report::dsl::*;
+ update(comment_report.find(report_id))
+ .set((
+ resolved.eq(true),
+ resolver_id.eq(by_resolver_id),
+ updated.eq(naive_now()),
+ ))
+ .execute(conn)
+ }
+
+ /// unresolve a comment 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
+ fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+ use crate::schema::comment_report::dsl::*;
+ update(comment_report.find(report_id))
+ .set((
+ resolved.eq(false),
+ resolver_id.eq(by_resolver_id),
+ updated.eq(naive_now()),
+ ))
+ .execute(conn)
+ }
+}
+
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone)]
+#[table_name = "comment_report_view"]
+pub struct CommentReportView {
+ pub id: i32,
+ pub creator_id: i32,
+ pub comment_id: i32,
+ pub original_comment_text: String,
+ pub reason: String,
+ pub resolved: bool,
+ pub resolver_id: Option<i32>,
+ pub published: chrono::NaiveDateTime,
+ pub updated: Option<chrono::NaiveDateTime>,
+ pub post_id: i32,
+ pub current_comment_text: String,
+ pub community_id: i32,
+ pub creator_actor_id: String,
+ pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
+ pub creator_avatar: Option<String>,
+ pub creator_local: bool,
+ pub comment_creator_id: i32,
+ pub comment_creator_actor_id: String,
+ pub comment_creator_name: String,
+ pub comment_creator_preferred_username: Option<String>,
+ pub comment_creator_avatar: Option<String>,
+ pub comment_creator_local: bool,
+ pub resolver_actor_id: Option<String>,
+ pub resolver_name: Option<String>,
+ pub resolver_preferred_username: Option<String>,
+ pub resolver_avatar: Option<String>,
+ pub resolver_local: Option<bool>,
+}
+
+pub struct CommentReportQueryBuilder<'a> {
+ conn: &'a PgConnection,
+ query: comment_report_view::BoxedQuery<'a, Pg>,
+ for_community_ids: Option<Vec<i32>>,
+ page: Option<i64>,
+ limit: Option<i64>,
+ resolved: Option<bool>,
+}
+
+impl CommentReportView {
+ /// returns the CommentReportView for the provided report_id
+ ///
+ /// * `report_id` - the report id to obtain
+ pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> {
+ use super::comment_report::comment_report_view::dsl::*;
+ comment_report_view.find(report_id).first::<Self>(conn)
+ }
+
+ /// returns the current unresolved comment report count for the supplied community ids
+ ///
+ /// * `community_ids` - a Vec<i32> of community_ids to get a count for
+ pub fn get_report_count(conn: &PgConnection, community_ids: &[i32]) -> Result<i64, Error> {
+ use super::comment_report::comment_report_view::dsl::*;
+ comment_report_view
+ .filter(resolved.eq(false).and(community_id.eq_any(community_ids)))
+ .select(count(id))
+ .first::<i64>(conn)
+ }
+}
+
+impl<'a> CommentReportQueryBuilder<'a> {
+ pub fn create(conn: &'a PgConnection) -> Self {
+ use super::comment_report::comment_report_view::dsl::*;
+
+ let query = comment_report_view.into_boxed();
+
+ CommentReportQueryBuilder {
+ conn,
+ query,
+ for_community_ids: None,
+ page: None,
+ limit: None,
+ resolved: Some(false),
+ }
+ }
+
+ pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
+ self.for_community_ids = community_ids.get_optional();
+ self
+ }
+
+ pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+ self.page = page.get_optional();
+ self
+ }
+
+ pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+ self.limit = limit.get_optional();
+ self
+ }
+
+ pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
+ self.resolved = resolved.get_optional();
+ self
+ }
+
+ pub fn list(self) -> Result<Vec<CommentReportView>, Error> {
+ use super::comment_report::comment_report_view::dsl::*;
+
+ let mut query = self.query;
+
+ if let Some(comm_ids) = self.for_community_ids {
+ query = query.filter(community_id.eq_any(comm_ids));
+ }
+
+ if let Some(resolved_flag) = self.resolved {
+ query = query.filter(resolved.eq(resolved_flag));
+ }
+
+ let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+ query
+ .order_by(published.asc())
+ .limit(limit)
+ .offset(offset)
+ .load::<CommentReportView>(self.conn)
+ }
+}
use crate::schema::community_moderator::dsl::*;
diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
}
+
+ pub fn get_user_moderated_communities(
+ conn: &PgConnection,
+ for_user_id: i32,
+ ) -> Result<Vec<i32>, Error> {
+ use crate::schema::community_moderator::dsl::*;
+ community_moderator
+ .filter(user_id.eq(for_user_id))
+ .select(community_id)
+ .load::<i32>(conn)
+ }
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
pub mod activity;
pub mod category;
pub mod comment;
+pub mod comment_report;
pub mod comment_view;
pub mod community;
pub mod community_view;
pub mod moderator_views;
pub mod password_reset_request;
pub mod post;
+pub mod post_report;
pub mod post_view;
pub mod private_message;
pub mod private_message_view;
Self: Sized;
}
+pub trait Reportable<T> {
+ fn report(conn: &PgConnection, form: &T) -> Result<Self, Error>
+ where
+ Self: Sized;
+ fn resolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
+ where
+ Self: Sized;
+ fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
+ where
+ Self: Sized;
+}
+
pub trait MaybeOptional<T> {
fn get_optional(self) -> Option<T>;
}
--- /dev/null
+use diesel::{dsl::*, pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
+
+use crate::{
+ limit_and_offset,
+ naive_now,
+ post::Post,
+ schema::post_report,
+ MaybeOptional,
+ Reportable,
+};
+
+table! {
+ post_report_view (id) {
+ id -> Int4,
+ creator_id -> Int4,
+ post_id -> Int4,
+ original_post_name -> Varchar,
+ original_post_url -> Nullable<Text>,
+ original_post_body -> Nullable<Text>,
+ reason -> Text,
+ resolved -> Bool,
+ resolver_id -> Nullable<Int4>,
+ published -> Timestamp,
+ updated -> Nullable<Timestamp>,
+ current_post_name -> Varchar,
+ current_post_url -> Nullable<Text>,
+ current_post_body -> Nullable<Text>,
+ community_id -> Int4,
+ creator_actor_id -> Text,
+ creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
+ creator_avatar -> Nullable<Text>,
+ creator_local -> Bool,
+ post_creator_id -> Int4,
+ post_creator_actor_id -> Text,
+ post_creator_name -> Varchar,
+ post_creator_preferred_username -> Nullable<Varchar>,
+ post_creator_avatar -> Nullable<Text>,
+ post_creator_local -> Bool,
+ resolver_actor_id -> Nullable<Text>,
+ resolver_name -> Nullable<Varchar>,
+ resolver_preferred_username -> Nullable<Varchar>,
+ resolver_avatar -> Nullable<Text>,
+ resolver_local -> Nullable<Bool>,
+ }
+}
+
+#[derive(Identifiable, Queryable, Associations, PartialEq, Serialize, Deserialize, Debug)]
+#[belongs_to(Post)]
+#[table_name = "post_report"]
+pub struct PostReport {
+ pub id: i32,
+ pub creator_id: i32,
+ pub post_id: i32,
+ pub original_post_name: String,
+ pub original_post_url: Option<String>,
+ pub original_post_body: Option<String>,
+ pub reason: String,
+ pub resolved: bool,
+ pub resolver_id: Option<i32>,
+ pub published: chrono::NaiveDateTime,
+ pub updated: Option<chrono::NaiveDateTime>,
+}
+
+#[derive(Insertable, AsChangeset, Clone)]
+#[table_name = "post_report"]
+pub struct PostReportForm {
+ pub creator_id: i32,
+ pub post_id: i32,
+ pub original_post_name: String,
+ pub original_post_url: Option<String>,
+ pub original_post_body: Option<String>,
+ pub reason: String,
+}
+
+impl Reportable<PostReportForm> for PostReport {
+ /// creates a post report and returns it
+ ///
+ /// * `conn` - the postgres connection
+ /// * `post_report_form` - the filled CommentReportForm to insert
+ fn report(conn: &PgConnection, post_report_form: &PostReportForm) -> Result<Self, Error> {
+ use crate::schema::post_report::dsl::*;
+ insert_into(post_report)
+ .values(post_report_form)
+ .get_result::<Self>(conn)
+ }
+
+ /// resolve a post 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
+ fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+ use crate::schema::post_report::dsl::*;
+ update(post_report.find(report_id))
+ .set((
+ resolved.eq(true),
+ resolver_id.eq(by_resolver_id),
+ updated.eq(naive_now()),
+ ))
+ .execute(conn)
+ }
+
+ /// resolve a post 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
+ fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
+ use crate::schema::post_report::dsl::*;
+ update(post_report.find(report_id))
+ .set((
+ resolved.eq(false),
+ resolver_id.eq(by_resolver_id),
+ updated.eq(naive_now()),
+ ))
+ .execute(conn)
+ }
+}
+
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone)]
+#[table_name = "post_report_view"]
+pub struct PostReportView {
+ pub id: i32,
+ pub creator_id: i32,
+ pub post_id: i32,
+ pub original_post_name: String,
+ pub original_post_url: Option<String>,
+ pub original_post_body: Option<String>,
+ pub reason: String,
+ pub resolved: bool,
+ pub resolver_id: Option<i32>,
+ pub published: chrono::NaiveDateTime,
+ pub updated: Option<chrono::NaiveDateTime>,
+ pub current_post_name: String,
+ pub current_post_url: Option<String>,
+ pub current_post_body: Option<String>,
+ pub community_id: i32,
+ pub creator_actor_id: String,
+ pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
+ pub creator_avatar: Option<String>,
+ pub creator_local: bool,
+ pub post_creator_id: i32,
+ pub post_creator_actor_id: String,
+ pub post_creator_name: String,
+ pub post_creator_preferred_username: Option<String>,
+ pub post_creator_avatar: Option<String>,
+ pub post_creator_local: bool,
+ pub resolver_actor_id: Option<String>,
+ pub resolver_name: Option<String>,
+ pub resolver_preferred_username: Option<String>,
+ pub resolver_avatar: Option<String>,
+ pub resolver_local: Option<bool>,
+}
+
+impl PostReportView {
+ /// returns the PostReportView for the provided report_id
+ ///
+ /// * `report_id` - the report id to obtain
+ pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> {
+ use super::post_report::post_report_view::dsl::*;
+ post_report_view.find(report_id).first::<Self>(conn)
+ }
+
+ /// returns the current unresolved post report count for the supplied community ids
+ ///
+ /// * `community_ids` - a Vec<i32> of community_ids to get a count for
+ pub fn get_report_count(conn: &PgConnection, community_ids: &[i32]) -> Result<i64, Error> {
+ use super::post_report::post_report_view::dsl::*;
+ post_report_view
+ .filter(resolved.eq(false).and(community_id.eq_any(community_ids)))
+ .select(count(id))
+ .first::<i64>(conn)
+ }
+}
+
+pub struct PostReportQueryBuilder<'a> {
+ conn: &'a PgConnection,
+ query: post_report_view::BoxedQuery<'a, Pg>,
+ for_community_ids: Option<Vec<i32>>,
+ page: Option<i64>,
+ limit: Option<i64>,
+ resolved: Option<bool>,
+}
+
+impl<'a> PostReportQueryBuilder<'a> {
+ pub fn create(conn: &'a PgConnection) -> Self {
+ use super::post_report::post_report_view::dsl::*;
+
+ let query = post_report_view.into_boxed();
+
+ PostReportQueryBuilder {
+ conn,
+ query,
+ for_community_ids: None,
+ page: None,
+ limit: None,
+ resolved: Some(false),
+ }
+ }
+
+ pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
+ self.for_community_ids = community_ids.get_optional();
+ self
+ }
+
+ pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+ self.page = page.get_optional();
+ self
+ }
+
+ pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+ self.limit = limit.get_optional();
+ self
+ }
+
+ pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
+ self.resolved = resolved.get_optional();
+ self
+ }
+
+ pub fn list(self) -> Result<Vec<PostReportView>, Error> {
+ use super::post_report::post_report_view::dsl::*;
+
+ let mut query = self.query;
+
+ if let Some(comm_ids) = self.for_community_ids {
+ query = query.filter(community_id.eq_any(comm_ids));
+ }
+
+ if let Some(resolved_flag) = self.resolved {
+ query = query.filter(resolved.eq(resolved_flag));
+ }
+
+ let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+ query
+ .order_by(published.asc())
+ .limit(limit)
+ .offset(offset)
+ .load::<PostReportView>(self.conn)
+ }
+}
}
}
+table! {
+ comment_report (id) {
+ id -> Int4,
+ creator_id -> Int4,
+ comment_id -> Int4,
+ original_comment_text -> Text,
+ reason -> Text,
+ resolved -> Bool,
+ resolver_id -> Nullable<Int4>,
+ published -> Timestamp,
+ updated -> Nullable<Timestamp>,
+ }
+}
+
table! {
comment_saved (id) {
id -> Int4,
}
}
+table! {
+ post_report (id) {
+ id -> Int4,
+ creator_id -> Int4,
+ post_id -> Int4,
+ original_post_name -> Varchar,
+ original_post_url -> Nullable<Text>,
+ original_post_body -> Nullable<Text>,
+ reason -> Text,
+ resolved -> Bool,
+ resolver_id -> Nullable<Int4>,
+ published -> Timestamp,
+ updated -> Nullable<Timestamp>,
+ }
+}
+
table! {
post_saved (id) {
id -> Int4,
joinable!(comment_like -> comment (comment_id));
joinable!(comment_like -> post (post_id));
joinable!(comment_like -> user_ (user_id));
+joinable!(comment_report -> comment (comment_id));
joinable!(comment_saved -> comment (comment_id));
joinable!(comment_saved -> user_ (user_id));
joinable!(community -> category (category_id));
joinable!(post_like -> user_ (user_id));
joinable!(post_read -> post (post_id));
joinable!(post_read -> user_ (user_id));
+joinable!(post_report -> post (post_id));
joinable!(post_saved -> post (post_id));
joinable!(post_saved -> user_ (user_id));
joinable!(site -> user_ (creator_id));
comment,
comment_aggregates_fast,
comment_like,
+ comment_report,
comment_saved,
community,
community_aggregates_fast,
post_aggregates_fast,
post_like,
post_read,
+ post_report,
post_saved,
private_message,
site,
-use lemmy_db::comment_view::CommentView;
+use lemmy_db::{comment_report::CommentReportView, comment_view::CommentView};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct GetCommentsResponse {
pub comments: Vec<CommentView>,
}
+
+#[derive(Serialize, Deserialize)]
+pub struct CreateCommentReport {
+ pub comment_id: i32,
+ pub reason: String,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct CreateCommentReportResponse {
+ pub success: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ResolveCommentReport {
+ pub report_id: i32,
+ pub resolved: bool,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ResolveCommentReportResponse {
+ pub report_id: i32,
+ pub resolved: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ListCommentReports {
+ pub page: Option<i64>,
+ pub limit: Option<i64>,
+ /// if no community is given, it returns reports for all communities moderated by the auth user
+ pub community: Option<i32>,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ListCommentReportsResponse {
+ pub comments: Vec<CommentReportView>,
+}
pub struct CommunityJoinResponse {
pub joined: bool,
}
+
+#[derive(Deserialize, Debug)]
+pub struct ModJoin {
+ pub community_id: i32,
+}
+
+#[derive(Serialize, Clone)]
+pub struct ModJoinResponse {
+ pub joined: bool,
+}
use lemmy_db::{
comment_view::CommentView,
community_view::{CommunityModeratorView, CommunityView},
+ post_report::PostReportView,
post_view::PostView,
};
use serde::{Deserialize, Serialize};
pub struct PostJoinResponse {
pub joined: bool,
}
+
+#[derive(Serialize, Deserialize)]
+pub struct CreatePostReport {
+ pub post_id: i32,
+ pub reason: String,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct CreatePostReportResponse {
+ pub success: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ResolvePostReport {
+ pub report_id: i32,
+ pub resolved: bool,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ResolvePostReportResponse {
+ pub report_id: i32,
+ pub resolved: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ListPostReports {
+ pub page: Option<i64>,
+ pub limit: Option<i64>,
+ pub community: Option<i32>,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ListPostReportsResponse {
+ pub posts: Vec<PostReportView>,
+}
pub struct UserJoinResponse {
pub joined: bool,
}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct GetReportCount {
+ pub community: Option<i32>,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct GetReportCountResponse {
+ pub community: Option<i32>,
+ pub comment_reports: i64,
+ pub post_reports: i64,
+}
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"]}
thiserror = "1.0"
-comrak = "0.8"
+comrak = { version = "0.8", default-features = false }
lazy_static = "1.3"
openssl = "0.10"
url = { version = "2.1", features = ["serde"] }
/// A map from community to set of connectionIDs
pub community_rooms: HashMap<CommunityId, HashSet<ConnectionId>>,
+ pub mod_rooms: HashMap<CommunityId, HashSet<ConnectionId>>,
+
/// A map from user id to its connection ID for joined users. Remember a user can have multiple
/// sessions (IE clients)
pub(super) user_rooms: HashMap<UserId, HashSet<ConnectionId>>,
sessions: HashMap::new(),
post_rooms: HashMap::new(),
community_rooms: HashMap::new(),
+ mod_rooms: HashMap::new(),
user_rooms: HashMap::new(),
rng: rand::thread_rng(),
pool,
Ok(())
}
+ pub fn join_mod_room(
+ &mut self,
+ community_id: CommunityId,
+ id: ConnectionId,
+ ) -> Result<(), LemmyError> {
+ // remove session from all rooms
+ for sessions in self.mod_rooms.values_mut() {
+ sessions.remove(&id);
+ }
+
+ // If the room doesn't exist yet
+ if self.mod_rooms.get_mut(&community_id).is_none() {
+ self.mod_rooms.insert(community_id, HashSet::new());
+ }
+
+ self
+ .mod_rooms
+ .get_mut(&community_id)
+ .context(location_info!())?
+ .insert(id);
+ Ok(())
+ }
+
pub fn join_post_room(&mut self, post_id: PostId, id: ConnectionId) -> Result<(), LemmyError> {
// remove session from all rooms
for sessions in self.post_rooms.values_mut() {
Ok(())
}
+ pub fn send_mod_room_message<Response>(
+ &self,
+ op: &UserOperation,
+ response: &Response,
+ community_id: CommunityId,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<(), LemmyError>
+ where
+ Response: Serialize,
+ {
+ let res_str = &serialize_websocket_message(op, response)?;
+ if let Some(sessions) = self.mod_rooms.get(&community_id) {
+ for id in sessions {
+ if let Some(my_id) = websocket_id {
+ if *id == my_id {
+ continue;
+ }
+ }
+ self.sendit(res_str, *id);
+ }
+ }
+ Ok(())
+ }
+
pub fn send_all_message<Response>(
&self,
op: &UserOperation,
}
}
+impl<Response> Handler<SendModRoomMessage<Response>> for ChatServer
+where
+ Response: Serialize,
+{
+ type Result = ();
+
+ fn handle(&mut self, msg: SendModRoomMessage<Response>, _: &mut Context<Self>) {
+ self
+ .send_mod_room_message(&msg.op, &msg.response, msg.community_id, msg.websocket_id)
+ .ok();
+ }
+}
+
impl Handler<SendPost> for ChatServer {
type Result = ();
}
}
+impl Handler<JoinModRoom> for ChatServer {
+ type Result = ();
+
+ fn handle(&mut self, msg: JoinModRoom, _: &mut Context<Self>) {
+ self.join_mod_room(msg.community_id, msg.id).ok();
+ }
+}
+
impl Handler<JoinPostRoom> for ChatServer {
type Result = ();
MarkCommentAsRead,
SaveComment,
CreateCommentLike,
+ CreateCommentReport,
+ ResolveCommentReport,
+ ListCommentReports,
GetPosts,
CreatePostLike,
EditPost,
LockPost,
StickyPost,
SavePost,
+ CreatePostReport,
+ ResolvePostReport,
+ ListPostReports,
+ GetReportCount,
EditCommunity,
DeleteCommunity,
RemoveCommunity,
SaveSiteConfig,
PostJoin,
CommunityJoin,
+ ModJoin,
}
pub websocket_id: Option<ConnectionId>,
}
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SendModRoomMessage<Response> {
+ pub op: UserOperation,
+ pub response: Response,
+ pub community_id: CommunityId,
+ pub websocket_id: Option<ConnectionId>,
+}
+
#[derive(Message)]
#[rtype(result = "()")]
pub struct SendPost {
pub id: ConnectionId,
}
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct JoinModRoom {
+ pub community_id: CommunityId,
+ pub id: ConnectionId,
+}
+
#[derive(Message)]
#[rtype(result = "()")]
pub struct JoinPostRoom {
--- /dev/null
+drop view comment_report_view;
+drop view post_report_view;
+drop table comment_report;
+drop table post_report;
--- /dev/null
+create table comment_report (
+ id serial primary key,
+ creator_id int references user_ on update cascade on delete cascade not null, -- user reporting comment
+ comment_id int references comment on update cascade on delete cascade not null, -- comment being reported
+ original_comment_text text not null,
+ reason text not null,
+ resolved bool not null default false,
+ resolver_id int references user_ on update cascade on delete cascade, -- user resolving report
+ published timestamp not null default now(),
+ updated timestamp null,
+ unique(comment_id, creator_id) -- users should only be able to report a comment once
+);
+
+create table post_report (
+ id serial primary key,
+ creator_id int references user_ on update cascade on delete cascade not null, -- user reporting post
+ post_id int references post on update cascade on delete cascade not null, -- post being reported
+ original_post_name varchar(100) not null,
+ original_post_url text,
+ original_post_body text,
+ reason text not null,
+ resolved bool not null default false,
+ resolver_id int references user_ on update cascade on delete cascade, -- user resolving report
+ published timestamp not null default now(),
+ updated timestamp null,
+ unique(post_id, creator_id) -- users should only be able to report a post once
+);
+
+create or replace view comment_report_view as
+select cr.*,
+c.post_id,
+c.content as current_comment_text,
+p.community_id,
+-- report creator details
+f.actor_id as creator_actor_id,
+f.name as creator_name,
+f.preferred_username as creator_preferred_username,
+f.avatar as creator_avatar,
+f.local as creator_local,
+-- comment creator details
+u.id as comment_creator_id,
+u.actor_id as comment_creator_actor_id,
+u.name as comment_creator_name,
+u.preferred_username as comment_creator_preferred_username,
+u.avatar as comment_creator_avatar,
+u.local as comment_creator_local,
+-- resolver details
+r.actor_id as resolver_actor_id,
+r.name as resolver_name,
+r.preferred_username as resolver_preferred_username,
+r.avatar as resolver_avatar,
+r.local as resolver_local
+from comment_report cr
+left join comment c on c.id = cr.comment_id
+left join post p on p.id = c.post_id
+left join user_ u on u.id = c.creator_id
+left join user_ f on f.id = cr.creator_id
+left join user_ r on r.id = cr.resolver_id;
+
+create or replace view post_report_view as
+select pr.*,
+p.name as current_post_name,
+p.url as current_post_url,
+p.body as current_post_body,
+p.community_id,
+-- report creator details
+f.actor_id as creator_actor_id,
+f.name as creator_name,
+f.preferred_username as creator_preferred_username,
+f.avatar as creator_avatar,
+f.local as creator_local,
+-- post creator details
+u.id as post_creator_id,
+u.actor_id as post_creator_actor_id,
+u.name as post_creator_name,
+u.preferred_username as post_creator_preferred_username,
+u.avatar as post_creator_avatar,
+u.local as post_creator_local,
+-- resolver details
+r.actor_id as resolver_actor_id,
+r.name as resolver_name,
+r.preferred_username as resolver_preferred_username,
+r.avatar as resolver_avatar,
+r.local as resolver_local
+from post_report pr
+left join post p on p.id = pr.post_id
+left join user_ u on u.id = p.creator_id
+left join user_ f on f.id = pr.creator_id
+left join user_ r on r.id = pr.resolver_id;
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
.route("/mod", web::post().to(route_post::<AddModToCommunity>))
- .route("/join", web::post().to(route_post::<CommunityJoin>)),
+ .route("/join", web::post().to(route_post::<CommunityJoin>))
+ .route("/mod/join", web::post().to(route_post::<ModJoin>)),
)
// Post
.service(
.route("/list", web::get().to(route_get::<GetPosts>))
.route("/like", web::post().to(route_post::<CreatePostLike>))
.route("/save", web::put().to(route_post::<SavePost>))
- .route("/join", web::post().to(route_post::<PostJoin>)),
+ .route("/join", web::post().to(route_post::<PostJoin>))
+ .route("/report", web::post().to(route_post::<CreatePostReport>))
+ .route(
+ "/report/resolve",
+ web::put().to(route_post::<ResolvePostReport>),
+ )
+ .route("/report/list", web::get().to(route_get::<ListPostReports>)),
)
// Comment
.service(
)
.route("/like", web::post().to(route_post::<CreateCommentLike>))
.route("/save", web::put().to(route_post::<SaveComment>))
- .route("/list", web::get().to(route_get::<GetComments>)),
+ .route("/list", web::get().to(route_get::<GetComments>))
+ .route("/report", web::post().to(route_post::<CreateCommentReport>))
+ .route(
+ "/report/resolve",
+ web::put().to(route_post::<ResolveCommentReport>),
+ )
+ .route(
+ "/report/list",
+ web::get().to(route_get::<ListCommentReports>),
+ ),
)
// Private Message
.service(
.route(
"/save_user_settings",
web::put().to(route_post::<SaveUserSettings>),
- ),
+ )
+ .route("/report_count", web::get().to(route_get::<GetReportCount>)),
)
// Admin Actions
.service(