`PUT /user/mention`
+#### Get Private Messages
+##### Request
+```rust
+{
+ op: "GetPrivateMessages",
+ data: {
+ unread_only: bool,
+ page: Option<i64>,
+ limit: Option<i64>,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "GetPrivateMessages",
+ data: {
+ messages: Vec<PrivateMessageView>,
+ }
+}
+```
+
+##### HTTP
+
+`GET /private_message/list`
+
+#### Create Private Message
+##### Request
+```rust
+{
+ op: "CreatePrivateMessage",
+ data: {
+ content: String,
+ recipient_id: i32,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "CreatePrivateMessage",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`POST /private_message`
+
+#### Edit Private Message
+##### Request
+```rust
+{
+ op: "EditPrivateMessage",
+ data: {
+ edit_id: i32,
+ content: String,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "EditPrivateMessage",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`PUT /private_message`
+
+#### Delete Private Message
+##### Request
+```rust
+{
+ op: "DeletePrivateMessage",
+ data: {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "DeletePrivateMessage",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`POST /private_message/delete`
+
+#### Mark Private Message as Read
+##### Request
+```rust
+{
+ op: "MarkPrivateMessageAsRead",
+ data: {
+ edit_id: i32,
+ read: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "MarkPrivateMessageAsRead",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`POST /private_message/mark_as_read`
+
#### Mark All As Read
Marks all user replies and mentions as read.
.filter(ap_id.eq(object_id))
.first::<Self>(conn)
}
+
+ pub fn update_content(
+ conn: &PgConnection,
+ private_message_id: i32,
+ new_content: &str,
+ ) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(private_message.find(private_message_id))
+ .set(content.eq(new_content))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_deleted(
+ conn: &PgConnection,
+ private_message_id: i32,
+ new_deleted: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(private_message.find(private_message_id))
+ .set(deleted.eq(new_deleted))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_read(
+ conn: &PgConnection,
+ private_message_id: i32,
+ new_read: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(private_message.find(private_message_id))
+ .set(read.eq(new_read))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn mark_all_as_read(conn: &PgConnection, for_recipient_id: i32) -> Result<Vec<Self>, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(
+ private_message
+ .filter(recipient_id.eq(for_recipient_id))
+ .filter(read.eq(false)),
+ )
+ .set(read.eq(true))
+ .get_results::<Self>(conn)
+ }
}
#[cfg(test)]
let read_private_message = PrivateMessage::read(&conn, inserted_private_message.id).unwrap();
let updated_private_message =
PrivateMessage::update(&conn, inserted_private_message.id, &private_message_form).unwrap();
+ let deleted_private_message =
+ PrivateMessage::update_deleted(&conn, inserted_private_message.id, true).unwrap();
+ let marked_read_private_message =
+ PrivateMessage::update_read(&conn, inserted_private_message.id, true).unwrap();
let num_deleted = PrivateMessage::delete(&conn, inserted_private_message.id).unwrap();
User_::delete(&conn, inserted_creator.id).unwrap();
User_::delete(&conn, inserted_recipient.id).unwrap();
assert_eq!(expected_private_message, read_private_message);
assert_eq!(expected_private_message, updated_private_message);
assert_eq!(expected_private_message, inserted_private_message);
+ assert!(deleted_private_message.deleted);
+ assert!(marked_read_private_message.read);
assert_eq!(1, num_deleted);
}
}
moderates: Vec<CommunityModeratorView>,
comments: Vec<CommentView>,
posts: Vec<PostView>,
- admins: Vec<UserView>,
+ admins: Vec<UserView>, // TODO why is this necessary, just use GetSite
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize)]
pub struct EditPrivateMessage {
edit_id: i32,
- content: Option<String>,
- deleted: Option<bool>,
- read: Option<bool>,
+ content: String,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct DeletePrivateMessage {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct MarkPrivateMessageAsRead {
+ edit_id: i32,
+ read: bool,
auth: String,
}
}
}
- // messages
- let messages = blocking(pool, move |conn| {
- PrivateMessageQueryBuilder::create(conn, user_id)
- .page(1)
- .limit(999)
- .unread_only(true)
- .list()
- })
- .await??;
-
- // TODO: this should probably be a bulk operation
- for message in &messages {
- let private_message_form = PrivateMessageForm {
- content: message.to_owned().content,
- creator_id: message.to_owned().creator_id,
- recipient_id: message.to_owned().recipient_id,
- deleted: None,
- read: Some(true),
- updated: None,
- ap_id: message.to_owned().ap_id,
- local: message.local,
- published: None,
- };
-
- let message_id = message.id;
- let update_pm =
- move |conn: &'_ _| PrivateMessage::update(conn, message_id, &private_message_form);
- if blocking(pool, update_pm).await?.is_err() {
- return Err(APIError::err("couldnt_update_private_message").into());
- }
+ // Mark all private_messages as read
+ let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
+ if blocking(pool, update_pm).await?.is_err() {
+ return Err(APIError::err("couldnt_update_private_message").into());
}
Ok(GetRepliesResponse { replies: vec![] })
let user_id = claims.id;
- let edit_id = data.edit_id;
- let orig_private_message =
- blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
-
// Check for a site ban
let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if user.banned {
return Err(APIError::err("site_ban").into());
}
- // Check to make sure they are the creator (or the recipient marking as read
- if !(data.read.is_some() && orig_private_message.recipient_id.eq(&user_id)
- || orig_private_message.creator_id.eq(&user_id))
- {
+ // Checking permissions
+ let edit_id = data.edit_id;
+ let orig_private_message =
+ blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
+ if user_id != orig_private_message.creator_id {
return Err(APIError::err("no_private_message_edit_allowed").into());
}
- let content_slurs_removed = match &data.content {
- Some(content) => remove_slurs(content),
- None => orig_private_message.content.clone(),
+ // Doing the update
+ let content_slurs_removed = remove_slurs(&data.content);
+ let edit_id = data.edit_id;
+ let updated_private_message = match blocking(pool, move |conn| {
+ PrivateMessage::update_content(conn, edit_id, &content_slurs_removed)
+ })
+ .await?
+ {
+ Ok(private_message) => private_message,
+ Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
};
- let private_message_form = {
- if data.read.is_some() {
- PrivateMessageForm {
- content: orig_private_message.content.to_owned(),
- creator_id: orig_private_message.creator_id,
- recipient_id: orig_private_message.recipient_id,
- read: data.read.to_owned(),
- updated: orig_private_message.updated,
- deleted: Some(orig_private_message.deleted),
- ap_id: orig_private_message.ap_id,
- local: orig_private_message.local,
- published: None,
- }
- } else {
- PrivateMessageForm {
- content: content_slurs_removed,
- creator_id: orig_private_message.creator_id,
- recipient_id: orig_private_message.recipient_id,
- deleted: data.deleted.to_owned(),
- read: Some(orig_private_message.read),
- updated: Some(naive_now()),
- ap_id: orig_private_message.ap_id,
- local: orig_private_message.local,
- published: None,
- }
- }
+ // Send the apub update
+ updated_private_message
+ .send_update(&user, &self.client, pool)
+ .await?;
+
+ let edit_id = data.edit_id;
+ let message = blocking(pool, move |conn| PrivateMessageView::read(conn, edit_id)).await??;
+ let recipient_id = message.recipient_id;
+
+ let res = PrivateMessageResponse { message };
+
+ if let Some(ws) = websocket_info {
+ ws.chatserver.do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res.clone(),
+ recipient_id,
+ my_id: ws.id,
+ });
+ }
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for Oper<DeletePrivateMessage> {
+ type Response = PrivateMessageResponse;
+
+ async fn perform(
+ &self,
+ pool: &DbPool,
+ websocket_info: Option<WebsocketInfo>,
+ ) -> Result<PrivateMessageResponse, LemmyError> {
+ let data: &DeletePrivateMessage = &self.data;
+
+ let claims = match Claims::decode(&data.auth) {
+ Ok(claims) => claims.claims,
+ Err(_e) => return Err(APIError::err("not_logged_in").into()),
};
+ let user_id = claims.id;
+
+ // Check for a site ban
+ let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ if user.banned {
+ return Err(APIError::err("site_ban").into());
+ }
+
+ // Checking permissions
let edit_id = data.edit_id;
+ let orig_private_message =
+ blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
+ if user_id != orig_private_message.creator_id {
+ return Err(APIError::err("no_private_message_edit_allowed").into());
+ }
+
+ // Doing the update
+ let edit_id = data.edit_id;
+ let deleted = data.deleted;
let updated_private_message = match blocking(pool, move |conn| {
- PrivateMessage::update(conn, edit_id, &private_message_form)
+ PrivateMessage::update_deleted(conn, edit_id, deleted)
})
.await?
{
Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
};
- if data.read.is_none() {
- if let Some(deleted) = data.deleted.to_owned() {
- if deleted {
- updated_private_message
- .send_delete(&user, &self.client, pool)
- .await?;
- } else {
- updated_private_message
- .send_undo_delete(&user, &self.client, pool)
- .await?;
- }
- } else {
- updated_private_message
- .send_update(&user, &self.client, pool)
- .await?;
- }
+ // Send the apub update
+ if data.deleted {
+ updated_private_message
+ .send_delete(&user, &self.client, pool)
+ .await?;
} else {
updated_private_message
- .send_update(&user, &self.client, pool)
+ .send_undo_delete(&user, &self.client, pool)
.await?;
}
let edit_id = data.edit_id;
let message = blocking(pool, move |conn| PrivateMessageView::read(conn, edit_id)).await??;
+ let recipient_id = message.recipient_id;
let res = PrivateMessageResponse { message };
if let Some(ws) = websocket_info {
ws.chatserver.do_send(SendUserRoomMessage {
- op: UserOperation::EditPrivateMessage,
+ op: UserOperation::DeletePrivateMessage,
+ response: res.clone(),
+ recipient_id,
+ my_id: ws.id,
+ });
+ }
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for Oper<MarkPrivateMessageAsRead> {
+ type Response = PrivateMessageResponse;
+
+ async fn perform(
+ &self,
+ pool: &DbPool,
+ websocket_info: Option<WebsocketInfo>,
+ ) -> Result<PrivateMessageResponse, LemmyError> {
+ let data: &MarkPrivateMessageAsRead = &self.data;
+
+ let claims = match Claims::decode(&data.auth) {
+ Ok(claims) => claims.claims,
+ Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ };
+
+ let user_id = claims.id;
+
+ // Check for a site ban
+ let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ if user.banned {
+ return Err(APIError::err("site_ban").into());
+ }
+
+ // Checking permissions
+ let edit_id = data.edit_id;
+ let orig_private_message =
+ blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
+ if user_id != orig_private_message.recipient_id {
+ return Err(APIError::err("couldnt_update_private_message").into());
+ }
+
+ // Doing the update
+ let edit_id = data.edit_id;
+ let read = data.read;
+ match blocking(pool, move |conn| {
+ PrivateMessage::update_read(conn, edit_id, read)
+ })
+ .await?
+ {
+ Ok(private_message) => private_message,
+ Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
+ };
+
+ // No need to send an apub update
+
+ let edit_id = data.edit_id;
+ let message = blocking(pool, move |conn| PrivateMessageView::read(conn, edit_id)).await??;
+ let recipient_id = message.recipient_id;
+
+ let res = PrivateMessageResponse { message };
+
+ if let Some(ws) = websocket_info {
+ ws.chatserver.do_send(SendUserRoomMessage {
+ op: UserOperation::MarkPrivateMessageAsRead,
response: res.clone(),
- recipient_id: orig_private_message.recipient_id,
+ recipient_id,
my_id: ws.id,
});
}
.wrap(rate_limit.message())
.route("/list", web::get().to(route_get::<GetPrivateMessages>))
.route("", web::post().to(route_post::<CreatePrivateMessage>))
- .route("", web::put().to(route_post::<EditPrivateMessage>)),
+ .route("", web::put().to(route_post::<EditPrivateMessage>))
+ .route(
+ "/delete",
+ web::post().to(route_post::<DeletePrivateMessage>),
+ )
+ .route(
+ "/mark_as_read",
+ web::post().to(route_post::<MarkPrivateMessageAsRead>),
+ ),
)
// User
.service(
PasswordChange,
CreatePrivateMessage,
EditPrivateMessage,
+ DeletePrivateMessage,
+ MarkPrivateMessageAsRead,
GetPrivateMessages,
UserJoin,
GetComments,
UserOperation::DeleteAccount => do_user_operation::<DeleteAccount>(args).await,
UserOperation::PasswordReset => do_user_operation::<PasswordReset>(args).await,
UserOperation::PasswordChange => do_user_operation::<PasswordChange>(args).await,
+ UserOperation::UserJoin => do_user_operation::<UserJoin>(args).await,
+ UserOperation::SaveUserSettings => do_user_operation::<SaveUserSettings>(args).await,
+
+ // Private Message ops
UserOperation::CreatePrivateMessage => {
do_user_operation::<CreatePrivateMessage>(args).await
}
UserOperation::EditPrivateMessage => do_user_operation::<EditPrivateMessage>(args).await,
+ UserOperation::DeletePrivateMessage => {
+ do_user_operation::<DeletePrivateMessage>(args).await
+ }
+ UserOperation::MarkPrivateMessageAsRead => {
+ do_user_operation::<MarkPrivateMessageAsRead>(args).await
+ }
UserOperation::GetPrivateMessages => do_user_operation::<GetPrivateMessages>(args).await,
- UserOperation::UserJoin => do_user_operation::<UserJoin>(args).await,
- UserOperation::SaveUserSettings => do_user_operation::<SaveUserSettings>(args).await,
// Site ops
UserOperation::GetModlog => do_user_operation::<GetModlog>(args).await,
FollowCommunityForm,
CommunityResponse,
GetFollowedCommunitiesResponse,
- GetPostForm,
GetPostResponse,
CommentForm,
CommentResponse,
CommunityForm,
- GetCommunityForm,
GetCommunityResponse,
CommentLikeForm,
CreatePostLikeForm,
PrivateMessageForm,
EditPrivateMessageForm,
+ DeletePrivateMessageForm,
PrivateMessageResponse,
PrivateMessagesResponse,
GetUserMentionsResponse,
);
// lemmy alpha deletes the private message
- let deletePrivateMessageForm: EditPrivateMessageForm = {
+ let deletePrivateMessageForm: DeletePrivateMessageForm = {
deleted: true,
edit_id: createRes.message.id,
auth: lemmyAlphaAuth,
};
let deleteRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
+ `${lemmyAlphaApiUrl}/private_message/delete`,
{
- method: 'PUT',
+ method: 'POST',
headers: {
'Content-Type': 'application/json',
},
expect(getPrivateMessagesDeletedRes.messages.length).toBe(0);
// lemmy alpha undeletes the private message
- let undeletePrivateMessageForm: EditPrivateMessageForm = {
+ let undeletePrivateMessageForm: DeletePrivateMessageForm = {
deleted: false,
edit_id: createRes.message.id,
auth: lemmyAlphaAuth,
};
let undeleteRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
+ `${lemmyAlphaApiUrl}/private_message/delete`,
{
- method: 'PUT',
+ method: 'POST',
headers: {
'Content-Type': 'application/json',
},
let found: PrivateMessageI = this.state.messages.find(
m => m.id === data.message.id
);
- found.content = data.message.content;
- found.updated = data.message.updated;
- found.deleted = data.message.deleted;
- // If youre in the unread view, just remove it from the list
- if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
- this.state.messages = this.state.messages.filter(
- r => r.id !== data.message.id
- );
- } else {
- let found = this.state.messages.find(c => c.id == data.message.id);
- found.read = data.message.read;
+ if (found) {
+ found.content = data.message.content;
+ found.updated = data.message.updated;
+ }
+ this.setState(this.state);
+ } else if (res.op == UserOperation.DeletePrivateMessage) {
+ let data = res.data as PrivateMessageResponse;
+ let found: PrivateMessageI = this.state.messages.find(
+ m => m.id === data.message.id
+ );
+ if (found) {
+ found.deleted = data.message.deleted;
+ found.updated = data.message.updated;
+ }
+ this.setState(this.state);
+ } else if (res.op == UserOperation.MarkPrivateMessageAsRead) {
+ let data = res.data as PrivateMessageResponse;
+ let found: PrivateMessageI = this.state.messages.find(
+ m => m.id === data.message.id
+ );
+
+ if (found) {
+ found.updated = data.message.updated;
+
+ // If youre in the unread view, just remove it from the list
+ if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
+ this.state.messages = this.state.messages.filter(
+ r => r.id !== data.message.id
+ );
+ } else {
+ let found = this.state.messages.find(c => c.id == data.message.id);
+ found.read = data.message.read;
+ }
}
this.sendUnreadCount();
- window.scrollTo(0, 0);
this.setState(this.state);
- setupTippy();
} else if (res.op == UserOperation.MarkAllAsRead) {
// Moved to be instant
} else if (res.op == UserOperation.EditComment) {
this.state.loading = false;
this.setState(this.state);
return;
- } else if (res.op == UserOperation.EditPrivateMessage) {
+ } else if (
+ res.op == UserOperation.EditPrivateMessage ||
+ res.op == UserOperation.DeletePrivateMessage ||
+ res.op == UserOperation.MarkPrivateMessageAsRead
+ ) {
let data = res.data as PrivateMessageResponse;
this.state.loading = false;
this.props.onEdit(data.message);
import { Link } from 'inferno-router';
import {
PrivateMessage as PrivateMessageI,
- EditPrivateMessageForm,
+ DeletePrivateMessageForm,
+ MarkPrivateMessageAsReadForm,
} from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { mdToHtml, pictrsAvatarThumbnail, showAvatars, toast } from '../utils';
}
handleDeleteClick(i: PrivateMessage) {
- let form: EditPrivateMessageForm = {
+ let form: DeletePrivateMessageForm = {
edit_id: i.props.privateMessage.id,
deleted: !i.props.privateMessage.deleted,
};
- WebSocketService.Instance.editPrivateMessage(form);
+ WebSocketService.Instance.deletePrivateMessage(form);
}
handleReplyCancel() {
}
handleMarkRead(i: PrivateMessage) {
- let form: EditPrivateMessageForm = {
+ let form: MarkPrivateMessageAsReadForm = {
edit_id: i.props.privateMessage.id,
read: !i.props.privateMessage.read,
};
- WebSocketService.Instance.editPrivateMessage(form);
+ WebSocketService.Instance.markPrivateMessageAsRead(form);
}
handleMessageCollapse(i: PrivateMessage) {
PasswordChange,
CreatePrivateMessage,
EditPrivateMessage,
+ DeletePrivateMessage,
+ MarkPrivateMessageAsRead,
GetPrivateMessages,
UserJoin,
GetComments,
export interface EditPrivateMessageForm {
edit_id: number;
- content?: string;
- deleted?: boolean;
- read?: boolean;
+ content: string;
+ auth?: string;
+}
+
+export interface DeletePrivateMessageForm {
+ edit_id: number;
+ deleted: boolean;
+ auth?: string;
+}
+
+export interface MarkPrivateMessageAsReadForm {
+ edit_id: number;
+ read: boolean;
auth?: string;
}
}
export type MessageType =
- | EditPrivateMessageForm
| LoginForm
| RegisterForm
| CommunityForm
| PasswordChangeForm
| PrivateMessageForm
| EditPrivateMessageForm
+ | DeletePrivateMessageForm
+ | MarkPrivateMessageAsReadForm
| GetPrivateMessagesForm
| SiteConfigForm;
PasswordChangeForm,
PrivateMessageForm,
EditPrivateMessageForm,
+ DeletePrivateMessageForm,
+ MarkPrivateMessageAsReadForm,
GetPrivateMessagesForm,
GetCommentsForm,
UserJoinForm,
this.ws.send(this.wsSendWrapper(UserOperation.EditPrivateMessage, form));
}
+ public deletePrivateMessage(form: DeletePrivateMessageForm) {
+ this.setAuth(form);
+ this.ws.send(this.wsSendWrapper(UserOperation.DeletePrivateMessage, form));
+ }
+
+ public markPrivateMessageAsRead(form: MarkPrivateMessageAsReadForm) {
+ this.setAuth(form);
+ this.ws.send(
+ this.wsSendWrapper(UserOperation.MarkPrivateMessageAsRead, form)
+ );
+ }
+
public getPrivateMessages(form: GetPrivateMessagesForm) {
this.setAuth(form);
this.ws.send(this.wsSendWrapper(UserOperation.GetPrivateMessages, form));