Merge branch 'eiknat-feature/add-reporting-backend' into main
This commit is contained in:
commit
a30be1ca5d
22 changed files with 1531 additions and 10 deletions
|
@ -306,11 +306,12 @@ Connect to <code>ws://***host***/api/v1/ws</code> to get started.
|
||||||
|
|
||||||
If the ***`host`*** supports secure connections, you can use <code>wss://***host***/api/v1/ws</code>.
|
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.
|
- [UserJoin](#user-join). Receives replies, private messages, etc.
|
||||||
- [PostJoin](#post-join). Receives new comments on a post.
|
- [PostJoin](#post-join). Receives new comments on a post.
|
||||||
- [CommunityJoin](#community-join). Receives front page / community posts.
|
- [CommunityJoin](#community-join). Receives front page / community posts.
|
||||||
|
- [ModJoin](#mod-join). Receives community moderator updates like reports.
|
||||||
|
|
||||||
#### Testing with Websocat
|
#### Testing with Websocat
|
||||||
|
|
||||||
|
@ -916,6 +917,35 @@ Marks all user replies and mentions as read.
|
||||||
|
|
||||||
`POST /user/join`
|
`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
|
### Site
|
||||||
#### List Categories
|
#### List Categories
|
||||||
##### Request
|
##### Request
|
||||||
|
@ -1492,6 +1522,29 @@ The main / frontpage community is `community_id: 0`.
|
||||||
|
|
||||||
`POST /community/join`
|
`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
|
### Post
|
||||||
#### Create Post
|
#### Create Post
|
||||||
##### Request
|
##### Request
|
||||||
|
@ -1801,6 +1854,86 @@ Only admins and mods can sticky a post.
|
||||||
|
|
||||||
`POST /post/join`
|
`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
|
### Comment
|
||||||
#### Create Comment
|
#### Create Comment
|
||||||
##### Request
|
##### Request
|
||||||
|
@ -2032,6 +2165,86 @@ Only the recipient can do this.
|
||||||
|
|
||||||
`POST /comment/like`
|
`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
|
### RSS / Atom feeds
|
||||||
|
|
||||||
#### All
|
#### All
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
check_community_ban,
|
check_community_ban,
|
||||||
|
collect_moderated_communities,
|
||||||
get_post,
|
get_post,
|
||||||
get_user_from_jwt,
|
get_user_from_jwt,
|
||||||
get_user_from_jwt_opt,
|
get_user_from_jwt_opt,
|
||||||
|
@ -10,6 +11,7 @@ use actix_web::web::Data;
|
||||||
use lemmy_apub::{ApubLikeableType, ApubObjectType};
|
use lemmy_apub::{ApubLikeableType, ApubObjectType};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::*,
|
comment::*,
|
||||||
|
comment_report::*,
|
||||||
comment_view::*,
|
comment_view::*,
|
||||||
moderator::*,
|
moderator::*,
|
||||||
post::*,
|
post::*,
|
||||||
|
@ -18,6 +20,7 @@ use lemmy_db::{
|
||||||
Crud,
|
Crud,
|
||||||
Likeable,
|
Likeable,
|
||||||
ListingType,
|
ListingType,
|
||||||
|
Reportable,
|
||||||
Saveable,
|
Saveable,
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
|
@ -29,7 +32,11 @@ use lemmy_utils::{
|
||||||
ConnectionId,
|
ConnectionId,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
use lemmy_websocket::{
|
||||||
|
messages::{SendComment, SendModRoomMessage, SendUserRoomMessage},
|
||||||
|
LemmyContext,
|
||||||
|
UserOperation,
|
||||||
|
};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
|
@ -682,3 +689,165 @@ impl Perform for GetComments {
|
||||||
Ok(GetCommentsResponse { comments })
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ use lemmy_utils::{
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::{
|
use lemmy_websocket::{
|
||||||
messages::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage},
|
messages::{GetCommunityUsersOnline, JoinCommunityRoom, JoinModRoom, SendCommunityRoomMessage},
|
||||||
LemmyContext,
|
LemmyContext,
|
||||||
UserOperation,
|
UserOperation,
|
||||||
};
|
};
|
||||||
|
@ -883,3 +883,25 @@ impl Perform for CommunityJoin {
|
||||||
Ok(CommunityJoinResponse { joined: true })
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::claims::Claims;
|
use crate::claims::Claims;
|
||||||
use actix_web::{web, web::Data};
|
use actix_web::{web, web::Data};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
community::Community,
|
community::{Community, CommunityModerator},
|
||||||
community_view::CommunityUserBanView,
|
community_view::CommunityUserBanView,
|
||||||
post::Post,
|
post::Post,
|
||||||
user::User_,
|
user::User_,
|
||||||
|
@ -100,6 +100,31 @@ pub(crate) async fn check_community_ban(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
|
||||||
if let Some(Some(item)) = &item {
|
if let Some(Some(item)) = &item {
|
||||||
if Url::parse(item).is_err() {
|
if Url::parse(item).is_err() {
|
||||||
|
@ -178,9 +203,13 @@ pub async fn match_websocket_operation(
|
||||||
UserOperation::CommunityJoin => {
|
UserOperation::CommunityJoin => {
|
||||||
do_websocket_operation::<CommunityJoin>(context, id, op, data).await
|
do_websocket_operation::<CommunityJoin>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
|
||||||
UserOperation::SaveUserSettings => {
|
UserOperation::SaveUserSettings => {
|
||||||
do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
|
do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
UserOperation::GetReportCount => {
|
||||||
|
do_websocket_operation::<GetReportCount>(context, id, op, data).await
|
||||||
|
}
|
||||||
|
|
||||||
// Private Message ops
|
// Private Message ops
|
||||||
UserOperation::CreatePrivateMessage => {
|
UserOperation::CreatePrivateMessage => {
|
||||||
|
@ -266,6 +295,15 @@ pub async fn match_websocket_operation(
|
||||||
do_websocket_operation::<CreatePostLike>(context, id, op, data).await
|
do_websocket_operation::<CreatePostLike>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
UserOperation::SavePost => do_websocket_operation::<SavePost>(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
|
// Comment ops
|
||||||
UserOperation::CreateComment => {
|
UserOperation::CreateComment => {
|
||||||
|
@ -292,6 +330,15 @@ pub async fn match_websocket_operation(
|
||||||
UserOperation::CreateCommentLike => {
|
UserOperation::CreateCommentLike => {
|
||||||
do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
check_community_ban,
|
check_community_ban,
|
||||||
check_optional_url,
|
check_optional_url,
|
||||||
|
collect_moderated_communities,
|
||||||
get_user_from_jwt,
|
get_user_from_jwt,
|
||||||
get_user_from_jwt_opt,
|
get_user_from_jwt_opt,
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
|
@ -14,11 +15,13 @@ use lemmy_db::{
|
||||||
moderator::*,
|
moderator::*,
|
||||||
naive_now,
|
naive_now,
|
||||||
post::*,
|
post::*,
|
||||||
|
post_report::*,
|
||||||
post_view::*,
|
post_view::*,
|
||||||
site_view::*,
|
site_view::*,
|
||||||
Crud,
|
Crud,
|
||||||
Likeable,
|
Likeable,
|
||||||
ListingType,
|
ListingType,
|
||||||
|
Reportable,
|
||||||
Saveable,
|
Saveable,
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
|
@ -32,7 +35,7 @@ use lemmy_utils::{
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::{
|
use lemmy_websocket::{
|
||||||
messages::{GetPostUsersOnline, JoinPostRoom, SendPost},
|
messages::{GetPostUsersOnline, JoinPostRoom, SendModRoomMessage, SendPost, SendUserRoomMessage},
|
||||||
LemmyContext,
|
LemmyContext,
|
||||||
UserOperation,
|
UserOperation,
|
||||||
};
|
};
|
||||||
|
@ -741,3 +744,166 @@ impl Perform for PostJoin {
|
||||||
Ok(PostJoinResponse { joined: true })
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
captcha_espeak_wav_base64,
|
captcha_espeak_wav_base64,
|
||||||
check_optional_url,
|
check_optional_url,
|
||||||
claims::Claims,
|
claims::Claims,
|
||||||
|
collect_moderated_communities,
|
||||||
get_user_from_jwt,
|
get_user_from_jwt,
|
||||||
get_user_from_jwt_opt,
|
get_user_from_jwt_opt,
|
||||||
is_admin,
|
is_admin,
|
||||||
|
@ -15,6 +16,7 @@ use chrono::Duration;
|
||||||
use lemmy_apub::ApubObjectType;
|
use lemmy_apub::ApubObjectType;
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::*,
|
comment::*,
|
||||||
|
comment_report::CommentReportView,
|
||||||
comment_view::*,
|
comment_view::*,
|
||||||
community::*,
|
community::*,
|
||||||
community_view::*,
|
community_view::*,
|
||||||
|
@ -23,6 +25,7 @@ use lemmy_db::{
|
||||||
naive_now,
|
naive_now,
|
||||||
password_reset_request::*,
|
password_reset_request::*,
|
||||||
post::*,
|
post::*,
|
||||||
|
post_report::PostReportView,
|
||||||
post_view::*,
|
post_view::*,
|
||||||
private_message::*,
|
private_message::*,
|
||||||
private_message_view::*,
|
private_message_view::*,
|
||||||
|
@ -1294,3 +1297,59 @@ impl Perform for UserJoin {
|
||||||
Ok(UserJoinResponse { joined: true })
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
235
lemmy_db/src/comment_report.rs
Normal file
235
lemmy_db/src/comment_report.rs
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -224,6 +224,17 @@ impl CommunityModerator {
|
||||||
use crate::schema::community_moderator::dsl::*;
|
use crate::schema::community_moderator::dsl::*;
|
||||||
diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
|
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)]
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
|
|
|
@ -14,6 +14,7 @@ use std::{env, env::VarError};
|
||||||
pub mod activity;
|
pub mod activity;
|
||||||
pub mod category;
|
pub mod category;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
|
pub mod comment_report;
|
||||||
pub mod comment_view;
|
pub mod comment_view;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_view;
|
pub mod community_view;
|
||||||
|
@ -21,6 +22,7 @@ pub mod moderator;
|
||||||
pub mod moderator_views;
|
pub mod moderator_views;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
|
pub mod post_report;
|
||||||
pub mod post_view;
|
pub mod post_view;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
pub mod private_message_view;
|
pub mod private_message_view;
|
||||||
|
@ -110,6 +112,18 @@ pub trait Readable<T> {
|
||||||
Self: Sized;
|
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> {
|
pub trait MaybeOptional<T> {
|
||||||
fn get_optional(self) -> Option<T>;
|
fn get_optional(self) -> Option<T>;
|
||||||
}
|
}
|
||||||
|
|
245
lemmy_db/src/post_report.rs
Normal file
245
lemmy_db/src/post_report.rs
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,6 +81,20 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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! {
|
table! {
|
||||||
comment_saved (id) {
|
comment_saved (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -370,6 +384,22 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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! {
|
table! {
|
||||||
post_saved (id) {
|
post_saved (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -487,6 +517,7 @@ joinable!(comment -> user_ (creator_id));
|
||||||
joinable!(comment_like -> comment (comment_id));
|
joinable!(comment_like -> comment (comment_id));
|
||||||
joinable!(comment_like -> post (post_id));
|
joinable!(comment_like -> post (post_id));
|
||||||
joinable!(comment_like -> user_ (user_id));
|
joinable!(comment_like -> user_ (user_id));
|
||||||
|
joinable!(comment_report -> comment (comment_id));
|
||||||
joinable!(comment_saved -> comment (comment_id));
|
joinable!(comment_saved -> comment (comment_id));
|
||||||
joinable!(comment_saved -> user_ (user_id));
|
joinable!(comment_saved -> user_ (user_id));
|
||||||
joinable!(community -> category (category_id));
|
joinable!(community -> category (category_id));
|
||||||
|
@ -516,6 +547,7 @@ joinable!(post_like -> post (post_id));
|
||||||
joinable!(post_like -> user_ (user_id));
|
joinable!(post_like -> user_ (user_id));
|
||||||
joinable!(post_read -> post (post_id));
|
joinable!(post_read -> post (post_id));
|
||||||
joinable!(post_read -> user_ (user_id));
|
joinable!(post_read -> user_ (user_id));
|
||||||
|
joinable!(post_report -> post (post_id));
|
||||||
joinable!(post_saved -> post (post_id));
|
joinable!(post_saved -> post (post_id));
|
||||||
joinable!(post_saved -> user_ (user_id));
|
joinable!(post_saved -> user_ (user_id));
|
||||||
joinable!(site -> user_ (creator_id));
|
joinable!(site -> user_ (creator_id));
|
||||||
|
@ -529,6 +561,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
comment,
|
comment,
|
||||||
comment_aggregates_fast,
|
comment_aggregates_fast,
|
||||||
comment_like,
|
comment_like,
|
||||||
|
comment_report,
|
||||||
comment_saved,
|
comment_saved,
|
||||||
community,
|
community,
|
||||||
community_aggregates_fast,
|
community_aggregates_fast,
|
||||||
|
@ -549,6 +582,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
post_aggregates_fast,
|
post_aggregates_fast,
|
||||||
post_like,
|
post_like,
|
||||||
post_read,
|
post_read,
|
||||||
|
post_report,
|
||||||
post_saved,
|
post_saved,
|
||||||
private_message,
|
private_message,
|
||||||
site,
|
site,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use lemmy_db::comment_view::CommentView;
|
use lemmy_db::{comment_report::CommentReportView, comment_view::CommentView};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -76,3 +76,42 @@ pub struct GetComments {
|
||||||
pub struct GetCommentsResponse {
|
pub struct GetCommentsResponse {
|
||||||
pub comments: Vec<CommentView>,
|
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>,
|
||||||
|
}
|
||||||
|
|
|
@ -139,3 +139,13 @@ pub struct CommunityJoin {
|
||||||
pub struct CommunityJoinResponse {
|
pub struct CommunityJoinResponse {
|
||||||
pub joined: bool,
|
pub joined: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ModJoin {
|
||||||
|
pub community_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct ModJoinResponse {
|
||||||
|
pub joined: bool,
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment_view::CommentView,
|
comment_view::CommentView,
|
||||||
community_view::{CommunityModeratorView, CommunityView},
|
community_view::{CommunityModeratorView, CommunityView},
|
||||||
|
post_report::PostReportView,
|
||||||
post_view::PostView,
|
post_view::PostView,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -113,3 +114,41 @@ pub struct PostJoin {
|
||||||
pub struct PostJoinResponse {
|
pub struct PostJoinResponse {
|
||||||
pub joined: bool,
|
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>,
|
||||||
|
}
|
||||||
|
|
|
@ -237,3 +237,16 @@ pub struct UserJoin {
|
||||||
pub struct UserJoinResponse {
|
pub struct UserJoinResponse {
|
||||||
pub joined: bool,
|
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,
|
||||||
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ pub struct ChatServer {
|
||||||
/// A map from community to set of connectionIDs
|
/// A map from community to set of connectionIDs
|
||||||
pub community_rooms: HashMap<CommunityId, HashSet<ConnectionId>>,
|
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
|
/// A map from user id to its connection ID for joined users. Remember a user can have multiple
|
||||||
/// sessions (IE clients)
|
/// sessions (IE clients)
|
||||||
pub(super) user_rooms: HashMap<UserId, HashSet<ConnectionId>>,
|
pub(super) user_rooms: HashMap<UserId, HashSet<ConnectionId>>,
|
||||||
|
@ -90,6 +92,7 @@ impl ChatServer {
|
||||||
sessions: HashMap::new(),
|
sessions: HashMap::new(),
|
||||||
post_rooms: HashMap::new(),
|
post_rooms: HashMap::new(),
|
||||||
community_rooms: HashMap::new(),
|
community_rooms: HashMap::new(),
|
||||||
|
mod_rooms: HashMap::new(),
|
||||||
user_rooms: HashMap::new(),
|
user_rooms: HashMap::new(),
|
||||||
rng: rand::thread_rng(),
|
rng: rand::thread_rng(),
|
||||||
pool,
|
pool,
|
||||||
|
@ -130,6 +133,29 @@ impl ChatServer {
|
||||||
Ok(())
|
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> {
|
pub fn join_post_room(&mut self, post_id: PostId, id: ConnectionId) -> Result<(), LemmyError> {
|
||||||
// remove session from all rooms
|
// remove session from all rooms
|
||||||
for sessions in self.post_rooms.values_mut() {
|
for sessions in self.post_rooms.values_mut() {
|
||||||
|
@ -227,6 +253,30 @@ impl ChatServer {
|
||||||
Ok(())
|
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>(
|
pub fn send_all_message<Response>(
|
||||||
&self,
|
&self,
|
||||||
op: &UserOperation,
|
op: &UserOperation,
|
||||||
|
|
|
@ -120,6 +120,19 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Handler<SendPost> for ChatServer {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
|
|
||||||
|
@ -154,6 +167,14 @@ impl Handler<JoinCommunityRoom> for ChatServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Handler<JoinPostRoom> for ChatServer {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,9 @@ pub enum UserOperation {
|
||||||
MarkCommentAsRead,
|
MarkCommentAsRead,
|
||||||
SaveComment,
|
SaveComment,
|
||||||
CreateCommentLike,
|
CreateCommentLike,
|
||||||
|
CreateCommentReport,
|
||||||
|
ResolveCommentReport,
|
||||||
|
ListCommentReports,
|
||||||
GetPosts,
|
GetPosts,
|
||||||
CreatePostLike,
|
CreatePostLike,
|
||||||
EditPost,
|
EditPost,
|
||||||
|
@ -105,6 +108,10 @@ pub enum UserOperation {
|
||||||
LockPost,
|
LockPost,
|
||||||
StickyPost,
|
StickyPost,
|
||||||
SavePost,
|
SavePost,
|
||||||
|
CreatePostReport,
|
||||||
|
ResolvePostReport,
|
||||||
|
ListPostReports,
|
||||||
|
GetReportCount,
|
||||||
EditCommunity,
|
EditCommunity,
|
||||||
DeleteCommunity,
|
DeleteCommunity,
|
||||||
RemoveCommunity,
|
RemoveCommunity,
|
||||||
|
@ -141,4 +148,5 @@ pub enum UserOperation {
|
||||||
SaveSiteConfig,
|
SaveSiteConfig,
|
||||||
PostJoin,
|
PostJoin,
|
||||||
CommunityJoin,
|
CommunityJoin,
|
||||||
|
ModJoin,
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,15 @@ pub struct SendCommunityRoomMessage<Response> {
|
||||||
pub websocket_id: Option<ConnectionId>,
|
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)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct SendPost {
|
pub struct SendPost {
|
||||||
|
@ -93,6 +102,13 @@ pub struct JoinCommunityRoom {
|
||||||
pub id: ConnectionId,
|
pub id: ConnectionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct JoinModRoom {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub id: ConnectionId,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct JoinPostRoom {
|
pub struct JoinPostRoom {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
drop view comment_report_view;
|
||||||
|
drop view post_report_view;
|
||||||
|
drop table comment_report;
|
||||||
|
drop table post_report;
|
89
migrations/2020-10-13-212240_create_report_tables/up.sql
Normal file
89
migrations/2020-10-13-212240_create_report_tables/up.sql
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
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;
|
|
@ -57,7 +57,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
|
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
|
||||||
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
|
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
|
||||||
.route("/mod", web::post().to(route_post::<AddModToCommunity>))
|
.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
|
// Post
|
||||||
.service(
|
.service(
|
||||||
|
@ -79,7 +80,13 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route("/list", web::get().to(route_get::<GetPosts>))
|
.route("/list", web::get().to(route_get::<GetPosts>))
|
||||||
.route("/like", web::post().to(route_post::<CreatePostLike>))
|
.route("/like", web::post().to(route_post::<CreatePostLike>))
|
||||||
.route("/save", web::put().to(route_post::<SavePost>))
|
.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
|
// Comment
|
||||||
.service(
|
.service(
|
||||||
|
@ -95,7 +102,16 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
)
|
)
|
||||||
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
||||||
.route("/save", web::put().to(route_post::<SaveComment>))
|
.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
|
// Private Message
|
||||||
.service(
|
.service(
|
||||||
|
@ -163,7 +179,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route(
|
.route(
|
||||||
"/save_user_settings",
|
"/save_user_settings",
|
||||||
web::put().to(route_post::<SaveUserSettings>),
|
web::put().to(route_post::<SaveUserSettings>),
|
||||||
),
|
)
|
||||||
|
.route("/report_count", web::get().to(route_get::<GetReportCount>)),
|
||||||
)
|
)
|
||||||
// Admin Actions
|
// Admin Actions
|
||||||
.service(
|
.service(
|
||||||
|
|
Loading…
Reference in a new issue