Squashed commit of the following:
commitf5b75f342b
Merge:bd1fc2b
69389f6
Author: Dessalines <happydooby@gmail.com> Date: Thu Jan 23 19:17:42 2020 -0500 Done merging http-api and private_message commitbd1fc2b80b
Author: Dessalines <happydooby@gmail.com> Date: Thu Jan 23 16:46:07 2020 -0500 Remove danger from private-message.tsx commit69389f61c9
Author: Dessalines <happydooby@gmail.com> Date: Thu Jan 23 11:21:21 2020 -0500 Fixing http curl POST docs. commit7fdcae4f07
Merge:dbe9ad0
752318f
Author: Dessalines <happydooby@gmail.com> Date: Thu Jan 23 11:01:06 2020 -0500 Merge remote-tracking branch 'nutomic/http-api' into dessalines-http-api commit752318fdf3
Author: Felix <me@nutomic.com> Date: Thu Jan 23 15:22:17 2020 +0100 api fixes commit9ccff18f23
Author: Dessalines <happydooby@gmail.com> Date: Wed Jan 22 22:29:11 2020 -0500 Adding a toaster to replace alerts. Fixes #457 commit5197407dd2
Merge:bacb9ac
58f673a
Author: Dessalines <happydooby@gmail.com> Date: Wed Jan 22 21:20:38 2020 -0500 Merge branch 'private_messaging' into dev commit58f673ab78
Author: Dessalines <happydooby@gmail.com> Date: Wed Jan 22 21:09:17 2020 -0500 Adding message to comment node actions. commitbacb9ac59e
Merge:10c6505
7d3adda
Author: Dessalines <happydooby@gmail.com> Date: Wed Jan 22 20:37:08 2020 -0500 Merge branch 'private_messaging' into dev commit10c6505968
Author: Dessalines <happydooby@gmail.com> Date: Wed Jan 22 20:35:20 2020 -0500 Adding correct hello_name to mail. commit7d3adda0cd
Author: Dessalines <happydooby@gmail.com> Date: Wed Jan 22 16:35:29 2020 -0500 Adding private messaging, and matrix user ids. - Fixes #244 commitdbe9ad0998
Author: Dessalines <happydooby@gmail.com> Date: Mon Jan 20 18:49:54 2020 -0500 Fixing last. commit20c9c54806
Author: Dessalines <happydooby@gmail.com> Date: Sun Jan 19 13:31:37 2020 -0500 Updating API docs. commitdc84ccaac9
Merge:6c61dd2
3edd75e
Author: Dessalines <happydooby@gmail.com> Date: Sun Jan 19 10:06:25 2020 -0500 Merge branch 'master' into dessalines-http-api commit6c61dd266b
Merge:c5eecd0
e518954
Author: Dessalines <happydooby@gmail.com> Date: Sun Jan 19 09:09:00 2020 -0500 Merge remote-tracking branch 'nutomic/websocket-generics' into dessalines-http-api commite518954bca
Author: Felix <me@nutomic.com> Date: Sun Jan 19 14:25:50 2020 +0100 Use generics to reduce code duplication in websocket commitc5eecd055e
Author: Dessalines <happydooby@gmail.com> Date: Sun Jan 19 00:38:45 2020 -0500 Strongly typing WebsocketJsonResponse. Forgot comment-form.tsx commit0c5eb47135
Author: Dessalines <happydooby@gmail.com> Date: Sat Jan 18 23:54:10 2020 -0500 First pass at fixing UI to work with new websocketresponses. commitbaf77bb6be
Author: Felix <me@nutomic.com> Date: Sat Jan 18 17:25:45 2020 +0100 simplify json serialization code commit047ec97e18
Author: Felix <me@nutomic.com> Date: Sat Jan 18 14:22:25 2020 +0100 rewrite api endpoint urls commit2fb4900b0c
Author: Felix <me@nutomic.com> Date: Thu Jan 16 17:04:37 2020 +0100 fix typo commitcba8081579
Author: Felix <me@nutomic.com> Date: Thu Jan 16 16:47:38 2020 +0100 fix formatting commitd7285d8c25
Author: Felix <me@nutomic.com> Date: Thu Jan 16 16:09:01 2020 +0100 small fix commit415040a1e9
Author: Felix <me@nutomic.com> Date: Thu Jan 16 15:39:08 2020 +0100 working! commit7a97c981a0
Author: Felix <me@nutomic.com> Date: Wed Jan 15 16:48:21 2020 +0100 try to simplify code with higher order functions commitc41082f98f
Author: Felix <me@nutomic.com> Date: Wed Jan 15 16:37:25 2020 +0100 Implement HTTP API using generics (fixes #380)
This commit is contained in:
parent
759453772d
commit
c3eaa2273a
63 changed files with 2967 additions and 1093 deletions
18
README.md
vendored
18
README.md
vendored
|
@ -157,15 +157,15 @@ If you'd like to add translations, take a look a look at the [English translatio
|
||||||
|
|
||||||
lang | done | missing
|
lang | done | missing
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
de | 93% | avatar,upload_avatar,show_avatars,docs,old_password,send_notifications_to_email,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,email_already_exists
|
de | 88% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,docs,message_sent,messages,old_password,matrix_user_id,private_message_disclaimer,send_notifications_to_email,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
eo | 80% | number_of_communities,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,donate_to_lemmy,donate,are_you_sure,yes,no,email_already_exists
|
eo | 76% | number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,donate_to_lemmy,donate,from,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
es | 89% | avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,email_already_exists
|
es | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
fr | 89% | avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,email_already_exists
|
fr | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
it | 89% | avatar,upload_avatar,show_avatars,archive_link,docs,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,email_already_exists
|
it | 84% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,docs,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
nl | 99% | donate_to_lemmy,donate,email_already_exists
|
nl | 93% | create_private_message,send_secure_message,send_message,message,message_sent,messages,matrix_user_id,private_message_disclaimer,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
ru | 77% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,donate_to_lemmy,donate,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no,email_already_exists
|
ru | 72% | cross_posts,cross_post,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
sv | 89% | avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,email_already_exists
|
sv | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
zh | 75% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,donate_to_lemmy,donate,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no,email_already_exists
|
zh | 70% | cross_posts,cross_post,users,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
|
||||||
|
|
||||||
<!-- translationsstop -->
|
<!-- translationsstop -->
|
||||||
|
|
||||||
|
|
2
docs/src/SUMMARY.md
vendored
2
docs/src/SUMMARY.md
vendored
|
@ -12,5 +12,5 @@
|
||||||
- [Contributing](contributing.md)
|
- [Contributing](contributing.md)
|
||||||
- [Docker Development](contributing_docker_development.md)
|
- [Docker Development](contributing_docker_development.md)
|
||||||
- [Local Development](contributing_local_development.md)
|
- [Local Development](contributing_local_development.md)
|
||||||
- [Websocket API](contributing_websocket_api.md)
|
- [Websocket/HTTP API](contributing_websocket_http_api.md)
|
||||||
- [ActivityPub API Outline](contributing_apub_api_outline.md)
|
- [ActivityPub API Outline](contributing_apub_api_outline.md)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
2
server/.rustfmt.toml
vendored
2
server/.rustfmt.toml
vendored
|
@ -1,2 +1,2 @@
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
edition="2018"
|
edition="2018"
|
34
server/migrations/2020-01-21-001001_create_private_message/down.sql
vendored
Normal file
34
server/migrations/2020-01-21-001001_create_private_message/down.sql
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
-- Drop the triggers
|
||||||
|
drop trigger refresh_private_message on private_message;
|
||||||
|
drop function refresh_private_message();
|
||||||
|
|
||||||
|
-- Drop the view and table
|
||||||
|
drop view private_message_view cascade;
|
||||||
|
drop table private_message;
|
||||||
|
|
||||||
|
-- Rebuild the old views
|
||||||
|
drop view user_view cascade;
|
||||||
|
create view user_view as
|
||||||
|
select
|
||||||
|
u.id,
|
||||||
|
u.name,
|
||||||
|
u.avatar,
|
||||||
|
u.email,
|
||||||
|
u.fedi_name,
|
||||||
|
u.admin,
|
||||||
|
u.banned,
|
||||||
|
u.show_avatars,
|
||||||
|
u.send_notifications_to_email,
|
||||||
|
u.published,
|
||||||
|
(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
|
||||||
|
(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
|
||||||
|
(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
|
||||||
|
(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
|
||||||
|
from user_ u;
|
||||||
|
|
||||||
|
create materialized view user_mview as select * from user_view;
|
||||||
|
|
||||||
|
create unique index idx_user_mview_id on user_mview (id);
|
||||||
|
|
||||||
|
-- Drop the columns
|
||||||
|
alter table user_ drop column matrix_user_id;
|
90
server/migrations/2020-01-21-001001_create_private_message/up.sql
vendored
Normal file
90
server/migrations/2020-01-21-001001_create_private_message/up.sql
vendored
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
-- Creating private message
|
||||||
|
create table private_message (
|
||||||
|
id serial primary key,
|
||||||
|
creator_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
recipient_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
content text not null,
|
||||||
|
deleted boolean default false not null,
|
||||||
|
read boolean default false not null,
|
||||||
|
published timestamp not null default now(),
|
||||||
|
updated timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create the view and materialized view which has the avatar and creator name
|
||||||
|
create view private_message_view as
|
||||||
|
select
|
||||||
|
pm.*,
|
||||||
|
u.name as creator_name,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
u2.name as recipient_name,
|
||||||
|
u2.avatar as recipient_avatar
|
||||||
|
from private_message pm
|
||||||
|
inner join user_ u on u.id = pm.creator_id
|
||||||
|
inner join user_ u2 on u2.id = pm.recipient_id;
|
||||||
|
|
||||||
|
create materialized view private_message_mview as select * from private_message_view;
|
||||||
|
|
||||||
|
create unique index idx_private_message_mview_id on private_message_mview (id);
|
||||||
|
|
||||||
|
-- Create the triggers
|
||||||
|
create or replace function refresh_private_message()
|
||||||
|
returns trigger language plpgsql
|
||||||
|
as $$
|
||||||
|
begin
|
||||||
|
refresh materialized view concurrently private_message_mview;
|
||||||
|
return null;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
create trigger refresh_private_message
|
||||||
|
after insert or update or delete or truncate
|
||||||
|
on private_message
|
||||||
|
for each statement
|
||||||
|
execute procedure refresh_private_message();
|
||||||
|
|
||||||
|
-- Update user to include matrix id
|
||||||
|
alter table user_ add column matrix_user_id text unique;
|
||||||
|
|
||||||
|
drop view user_view cascade;
|
||||||
|
create view user_view as
|
||||||
|
select
|
||||||
|
u.id,
|
||||||
|
u.name,
|
||||||
|
u.avatar,
|
||||||
|
u.email,
|
||||||
|
u.matrix_user_id,
|
||||||
|
u.fedi_name,
|
||||||
|
u.admin,
|
||||||
|
u.banned,
|
||||||
|
u.show_avatars,
|
||||||
|
u.send_notifications_to_email,
|
||||||
|
u.published,
|
||||||
|
(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
|
||||||
|
(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
|
||||||
|
(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
|
||||||
|
(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
|
||||||
|
from user_ u;
|
||||||
|
|
||||||
|
create materialized view user_mview as select * from user_view;
|
||||||
|
|
||||||
|
create unique index idx_user_mview_id on user_mview (id);
|
||||||
|
|
||||||
|
-- This is what a group pm table would look like
|
||||||
|
-- Not going to do it now because of the complications
|
||||||
|
--
|
||||||
|
-- create table private_message (
|
||||||
|
-- id serial primary key,
|
||||||
|
-- creator_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
-- content text not null,
|
||||||
|
-- deleted boolean default false not null,
|
||||||
|
-- published timestamp not null default now(),
|
||||||
|
-- updated timestamp
|
||||||
|
-- );
|
||||||
|
--
|
||||||
|
-- create table private_message_recipient (
|
||||||
|
-- id serial primary key,
|
||||||
|
-- private_message_id int references private_message on update cascade on delete cascade not null,
|
||||||
|
-- recipient_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
-- read boolean default false not null,
|
||||||
|
-- published timestamp not null default now(),
|
||||||
|
-- unique(private_message_id, recipient_id)
|
||||||
|
-- )
|
|
@ -7,7 +7,7 @@ use diesel::PgConnection;
|
||||||
pub struct CreateComment {
|
pub struct CreateComment {
|
||||||
content: String,
|
content: String,
|
||||||
parent_id: Option<i32>,
|
parent_id: Option<i32>,
|
||||||
edit_id: Option<i32>,
|
edit_id: Option<i32>, // TODO this isn't used
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
auth: String,
|
auth: String,
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ pub struct CreateComment {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct EditComment {
|
pub struct EditComment {
|
||||||
content: String,
|
content: String,
|
||||||
parent_id: Option<i32>,
|
parent_id: Option<i32>, // TODO why are the parent_id, creator_id, post_id, etc fields required? They aren't going to change
|
||||||
edit_id: i32,
|
edit_id: i32,
|
||||||
creator_id: i32,
|
creator_id: i32,
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
|
@ -35,7 +35,6 @@ pub struct SaveComment {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct CommentResponse {
|
pub struct CommentResponse {
|
||||||
op: String,
|
|
||||||
pub comment: CommentView,
|
pub comment: CommentView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +52,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -63,12 +62,12 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post = Post::read(&conn, data.post_id)?;
|
let post = Post::read(&conn, data.post_id)?;
|
||||||
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
||||||
return Err(APIError::err(&self.op, "community_ban").into());
|
return Err(APIError::err("community_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
||||||
|
@ -86,7 +85,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
|
||||||
|
|
||||||
let inserted_comment = match Comment::create(&conn, &comment_form) {
|
let inserted_comment = match Comment::create(&conn, &comment_form) {
|
||||||
Ok(comment) => comment,
|
Ok(comment) => comment,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Scan the comment for user mentions, add those rows
|
// Scan the comment for user mentions, add those rows
|
||||||
|
@ -193,13 +192,12 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
|
||||||
|
|
||||||
let _inserted_like = match CommentLike::like(&conn, &like_form) {
|
let _inserted_like = match CommentLike::like(&conn, &like_form) {
|
||||||
Ok(like) => like,
|
Ok(like) => like,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?;
|
let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(CommentResponse {
|
Ok(CommentResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
comment: comment_view,
|
comment: comment_view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -211,7 +209,7 @@ impl Perform<CommentResponse> for Oper<EditComment> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -231,17 +229,17 @@ impl Perform<CommentResponse> for Oper<EditComment> {
|
||||||
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
|
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
|
||||||
|
|
||||||
if !editors.contains(&user_id) {
|
if !editors.contains(&user_id) {
|
||||||
return Err(APIError::err(&self.op, "no_comment_edit_allowed").into());
|
return Err(APIError::err("no_comment_edit_allowed").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() {
|
if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() {
|
||||||
return Err(APIError::err(&self.op, "community_ban").into());
|
return Err(APIError::err("community_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +262,7 @@ impl Perform<CommentResponse> for Oper<EditComment> {
|
||||||
|
|
||||||
let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) {
|
let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) {
|
||||||
Ok(comment) => comment,
|
Ok(comment) => comment,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Scan the comment for user mentions, add those rows
|
// Scan the comment for user mentions, add those rows
|
||||||
|
@ -310,7 +308,6 @@ impl Perform<CommentResponse> for Oper<EditComment> {
|
||||||
let comment_view = CommentView::read(&conn, data.edit_id, Some(user_id))?;
|
let comment_view = CommentView::read(&conn, data.edit_id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(CommentResponse {
|
Ok(CommentResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
comment: comment_view,
|
comment: comment_view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -322,7 +319,7 @@ impl Perform<CommentResponse> for Oper<SaveComment> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -335,19 +332,18 @@ impl Perform<CommentResponse> for Oper<SaveComment> {
|
||||||
if data.save {
|
if data.save {
|
||||||
match CommentSaved::save(&conn, &comment_saved_form) {
|
match CommentSaved::save(&conn, &comment_saved_form) {
|
||||||
Ok(comment) => comment,
|
Ok(comment) => comment,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_save_comment").into()),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
match CommentSaved::unsave(&conn, &comment_saved_form) {
|
match CommentSaved::unsave(&conn, &comment_saved_form) {
|
||||||
Ok(comment) => comment,
|
Ok(comment) => comment,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_save_comment").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let comment_view = CommentView::read(&conn, data.comment_id, Some(user_id))?;
|
let comment_view = CommentView::read(&conn, data.comment_id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(CommentResponse {
|
Ok(CommentResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
comment: comment_view,
|
comment: comment_view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -359,7 +355,7 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -368,19 +364,19 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
|
||||||
if data.score == -1 {
|
if data.score == -1 {
|
||||||
let site = SiteView::read(&conn)?;
|
let site = SiteView::read(&conn)?;
|
||||||
if !site.enable_downvotes {
|
if !site.enable_downvotes {
|
||||||
return Err(APIError::err(&self.op, "downvotes_disabled").into());
|
return Err(APIError::err("downvotes_disabled").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post = Post::read(&conn, data.post_id)?;
|
let post = Post::read(&conn, data.post_id)?;
|
||||||
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
||||||
return Err(APIError::err(&self.op, "community_ban").into());
|
return Err(APIError::err("community_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let like_form = CommentLikeForm {
|
let like_form = CommentLikeForm {
|
||||||
|
@ -398,7 +394,7 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
|
||||||
if do_add {
|
if do_add {
|
||||||
let _inserted_like = match CommentLike::like(&conn, &like_form) {
|
let _inserted_like = match CommentLike::like(&conn, &like_form) {
|
||||||
Ok(like) => like,
|
Ok(like) => like,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,7 +402,6 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
|
||||||
let liked_comment = CommentView::read(&conn, data.comment_id, Some(user_id))?;
|
let liked_comment = CommentView::read(&conn, data.comment_id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(CommentResponse {
|
Ok(CommentResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
comment: liked_comment,
|
comment: liked_comment,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ pub struct GetCommunity {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetCommunityResponse {
|
pub struct GetCommunityResponse {
|
||||||
op: String,
|
|
||||||
community: CommunityView,
|
community: CommunityView,
|
||||||
moderators: Vec<CommunityModeratorView>,
|
moderators: Vec<CommunityModeratorView>,
|
||||||
admins: Vec<UserView>,
|
admins: Vec<UserView>,
|
||||||
|
@ -29,7 +28,6 @@ pub struct CreateCommunity {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct CommunityResponse {
|
pub struct CommunityResponse {
|
||||||
op: String,
|
|
||||||
pub community: CommunityView,
|
pub community: CommunityView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +41,6 @@ pub struct ListCommunities {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ListCommunitiesResponse {
|
pub struct ListCommunitiesResponse {
|
||||||
op: String,
|
|
||||||
communities: Vec<CommunityView>,
|
communities: Vec<CommunityView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +56,6 @@ pub struct BanFromCommunity {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct BanFromCommunityResponse {
|
pub struct BanFromCommunityResponse {
|
||||||
op: String,
|
|
||||||
user: UserView,
|
user: UserView,
|
||||||
banned: bool,
|
banned: bool,
|
||||||
}
|
}
|
||||||
|
@ -74,7 +70,6 @@ pub struct AddModToCommunity {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct AddModToCommunityResponse {
|
pub struct AddModToCommunityResponse {
|
||||||
op: String,
|
|
||||||
moderators: Vec<CommunityModeratorView>,
|
moderators: Vec<CommunityModeratorView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +102,6 @@ pub struct GetFollowedCommunities {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetFollowedCommunitiesResponse {
|
pub struct GetFollowedCommunitiesResponse {
|
||||||
op: String,
|
|
||||||
communities: Vec<CommunityFollowerView>,
|
communities: Vec<CommunityFollowerView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,19 +135,19 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
|
||||||
data.name.to_owned().unwrap_or_else(|| "main".to_string()),
|
data.name.to_owned().unwrap_or_else(|| "main".to_string()),
|
||||||
) {
|
) {
|
||||||
Ok(community) => community.id,
|
Ok(community) => community.id,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let community_view = match CommunityView::read(&conn, community_id, user_id) {
|
let community_view = match CommunityView::read(&conn, community_id, user_id) {
|
||||||
Ok(community) => community,
|
Ok(community) => community,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let moderators = match CommunityModeratorView::for_community(&conn, community_id) {
|
let moderators = match CommunityModeratorView::for_community(&conn, community_id) {
|
||||||
Ok(moderators) => moderators,
|
Ok(moderators) => moderators,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let site_creator_id = Site::read(&conn, 1)?.creator_id;
|
let site_creator_id = Site::read(&conn, 1)?.creator_id;
|
||||||
|
@ -164,7 +158,6 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(GetCommunityResponse {
|
Ok(GetCommunityResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
community: community_view,
|
community: community_view,
|
||||||
moderators,
|
moderators,
|
||||||
admins,
|
admins,
|
||||||
|
@ -178,21 +171,21 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if has_slurs(&data.name)
|
if has_slurs(&data.name)
|
||||||
|| has_slurs(&data.title)
|
|| has_slurs(&data.title)
|
||||||
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap()))
|
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap()))
|
||||||
{
|
{
|
||||||
return Err(APIError::err(&self.op, "no_slurs").into());
|
return Err(APIError::err("no_slurs").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// When you create a community, make sure the user becomes a moderator and a follower
|
// When you create a community, make sure the user becomes a moderator and a follower
|
||||||
|
@ -210,7 +203,7 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
|
||||||
|
|
||||||
let inserted_community = match Community::create(&conn, &community_form) {
|
let inserted_community = match Community::create(&conn, &community_form) {
|
||||||
Ok(community) => community,
|
Ok(community) => community,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "community_already_exists").into()),
|
Err(_e) => return Err(APIError::err("community_already_exists").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let community_moderator_form = CommunityModeratorForm {
|
let community_moderator_form = CommunityModeratorForm {
|
||||||
|
@ -221,9 +214,7 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
|
||||||
let _inserted_community_moderator =
|
let _inserted_community_moderator =
|
||||||
match CommunityModerator::join(&conn, &community_moderator_form) {
|
match CommunityModerator::join(&conn, &community_moderator_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => {
|
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
|
||||||
return Err(APIError::err(&self.op, "community_moderator_already_exists").into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let community_follower_form = CommunityFollowerForm {
|
let community_follower_form = CommunityFollowerForm {
|
||||||
|
@ -234,13 +225,12 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
|
||||||
let _inserted_community_follower =
|
let _inserted_community_follower =
|
||||||
match CommunityFollower::follow(&conn, &community_follower_form) {
|
match CommunityFollower::follow(&conn, &community_follower_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()),
|
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?;
|
let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(CommunityResponse {
|
Ok(CommunityResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
community: community_view,
|
community: community_view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -251,19 +241,19 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
|
||||||
let data: &EditCommunity = &self.data;
|
let data: &EditCommunity = &self.data;
|
||||||
|
|
||||||
if has_slurs(&data.name) || has_slurs(&data.title) {
|
if has_slurs(&data.name) || has_slurs(&data.title) {
|
||||||
return Err(APIError::err(&self.op, "no_slurs").into());
|
return Err(APIError::err("no_slurs").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify its a mod
|
// Verify its a mod
|
||||||
|
@ -276,7 +266,7 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
|
||||||
);
|
);
|
||||||
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
|
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
|
||||||
if !editors.contains(&user_id) {
|
if !editors.contains(&user_id) {
|
||||||
return Err(APIError::err(&self.op, "no_community_edit_allowed").into());
|
return Err(APIError::err("no_community_edit_allowed").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
let community_form = CommunityForm {
|
||||||
|
@ -293,7 +283,7 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
|
||||||
|
|
||||||
let _updated_community = match Community::update(&conn, data.edit_id, &community_form) {
|
let _updated_community = match Community::update(&conn, data.edit_id, &community_form) {
|
||||||
Ok(community) => community,
|
Ok(community) => community,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
|
@ -315,7 +305,6 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
|
||||||
let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
|
let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(CommunityResponse {
|
Ok(CommunityResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
community: community_view,
|
community: community_view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -354,10 +343,7 @@ impl Perform<ListCommunitiesResponse> for Oper<ListCommunities> {
|
||||||
.list()?;
|
.list()?;
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(ListCommunitiesResponse {
|
Ok(ListCommunitiesResponse { communities })
|
||||||
op: self.op.to_string(),
|
|
||||||
communities,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +353,7 @@ impl Perform<CommunityResponse> for Oper<FollowCommunity> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -380,19 +366,18 @@ impl Perform<CommunityResponse> for Oper<FollowCommunity> {
|
||||||
if data.follow {
|
if data.follow {
|
||||||
match CommunityFollower::follow(&conn, &community_follower_form) {
|
match CommunityFollower::follow(&conn, &community_follower_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()),
|
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
match CommunityFollower::ignore(&conn, &community_follower_form) {
|
match CommunityFollower::ignore(&conn, &community_follower_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()),
|
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
|
let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(CommunityResponse {
|
Ok(CommunityResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
community: community_view,
|
community: community_view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -404,7 +389,7 @@ impl Perform<GetFollowedCommunitiesResponse> for Oper<GetFollowedCommunities> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -412,14 +397,11 @@ impl Perform<GetFollowedCommunitiesResponse> for Oper<GetFollowedCommunities> {
|
||||||
let communities: Vec<CommunityFollowerView> =
|
let communities: Vec<CommunityFollowerView> =
|
||||||
match CommunityFollowerView::for_user(&conn, user_id) {
|
match CommunityFollowerView::for_user(&conn, user_id) {
|
||||||
Ok(communities) => communities,
|
Ok(communities) => communities,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "system_err_login").into()),
|
Err(_e) => return Err(APIError::err("system_err_login").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(GetFollowedCommunitiesResponse {
|
Ok(GetFollowedCommunitiesResponse { communities })
|
||||||
op: self.op.to_string(),
|
|
||||||
communities,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +411,7 @@ impl Perform<BanFromCommunityResponse> for Oper<BanFromCommunity> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -442,12 +424,12 @@ impl Perform<BanFromCommunityResponse> for Oper<BanFromCommunity> {
|
||||||
if data.ban {
|
if data.ban {
|
||||||
match CommunityUserBan::ban(&conn, &community_user_ban_form) {
|
match CommunityUserBan::ban(&conn, &community_user_ban_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned").into()),
|
Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
match CommunityUserBan::unban(&conn, &community_user_ban_form) {
|
match CommunityUserBan::unban(&conn, &community_user_ban_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned").into()),
|
Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,7 +452,6 @@ impl Perform<BanFromCommunityResponse> for Oper<BanFromCommunity> {
|
||||||
let user_view = UserView::read(&conn, data.user_id)?;
|
let user_view = UserView::read(&conn, data.user_id)?;
|
||||||
|
|
||||||
Ok(BanFromCommunityResponse {
|
Ok(BanFromCommunityResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
user: user_view,
|
user: user_view,
|
||||||
banned: data.ban,
|
banned: data.ban,
|
||||||
})
|
})
|
||||||
|
@ -483,7 +464,7 @@ impl Perform<AddModToCommunityResponse> for Oper<AddModToCommunity> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -496,16 +477,12 @@ impl Perform<AddModToCommunityResponse> for Oper<AddModToCommunity> {
|
||||||
if data.added {
|
if data.added {
|
||||||
match CommunityModerator::join(&conn, &community_moderator_form) {
|
match CommunityModerator::join(&conn, &community_moderator_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => {
|
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
|
||||||
return Err(APIError::err(&self.op, "community_moderator_already_exists").into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
match CommunityModerator::leave(&conn, &community_moderator_form) {
|
match CommunityModerator::leave(&conn, &community_moderator_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => {
|
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
|
||||||
return Err(APIError::err(&self.op, "community_moderator_already_exists").into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,10 +497,7 @@ impl Perform<AddModToCommunityResponse> for Oper<AddModToCommunity> {
|
||||||
|
|
||||||
let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?;
|
let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?;
|
||||||
|
|
||||||
Ok(AddModToCommunityResponse {
|
Ok(AddModToCommunityResponse { moderators })
|
||||||
op: self.op.to_string(),
|
|
||||||
moderators,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,7 +507,7 @@ impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -548,7 +522,7 @@ impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
|
||||||
|
|
||||||
// Make sure user is the creator, or an admin
|
// Make sure user is the creator, or an admin
|
||||||
if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
|
if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
|
||||||
return Err(APIError::err(&self.op, "not_an_admin").into());
|
return Err(APIError::err("not_an_admin").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
let community_form = CommunityForm {
|
||||||
|
@ -565,7 +539,7 @@ impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
|
||||||
|
|
||||||
let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
|
let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
|
||||||
Ok(community) => community,
|
Ok(community) => community,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// You also have to re-do the community_moderator table, reordering it.
|
// You also have to re-do the community_moderator table, reordering it.
|
||||||
|
@ -588,9 +562,7 @@ impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
|
||||||
let _inserted_community_moderator =
|
let _inserted_community_moderator =
|
||||||
match CommunityModerator::join(&conn, &community_moderator_form) {
|
match CommunityModerator::join(&conn, &community_moderator_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => {
|
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
|
||||||
return Err(APIError::err(&self.op, "community_moderator_already_exists").into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,17 +577,16 @@ impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
|
||||||
|
|
||||||
let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
|
let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
|
||||||
Ok(community) => community,
|
Ok(community) => community,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
|
let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
|
||||||
Ok(moderators) => moderators,
|
Ok(moderators) => moderators,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(GetCommunityResponse {
|
Ok(GetCommunityResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
community: community_view,
|
community: community_view,
|
||||||
moderators,
|
moderators,
|
||||||
admins,
|
admins,
|
||||||
|
|
|
@ -8,6 +8,8 @@ use crate::db::moderator_views::*;
|
||||||
use crate::db::password_reset_request::*;
|
use crate::db::password_reset_request::*;
|
||||||
use crate::db::post::*;
|
use crate::db::post::*;
|
||||||
use crate::db::post_view::*;
|
use crate::db::post_view::*;
|
||||||
|
use crate::db::private_message::*;
|
||||||
|
use crate::db::private_message_view::*;
|
||||||
use crate::db::site::*;
|
use crate::db::site::*;
|
||||||
use crate::db::site_view::*;
|
use crate::db::site_view::*;
|
||||||
use crate::db::user::*;
|
use crate::db::user::*;
|
||||||
|
@ -26,73 +28,27 @@ pub mod post;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
#[derive(EnumString, ToString, Debug)]
|
|
||||||
pub enum UserOperation {
|
|
||||||
Login,
|
|
||||||
Register,
|
|
||||||
CreateCommunity,
|
|
||||||
CreatePost,
|
|
||||||
ListCommunities,
|
|
||||||
ListCategories,
|
|
||||||
GetPost,
|
|
||||||
GetCommunity,
|
|
||||||
CreateComment,
|
|
||||||
EditComment,
|
|
||||||
SaveComment,
|
|
||||||
CreateCommentLike,
|
|
||||||
GetPosts,
|
|
||||||
CreatePostLike,
|
|
||||||
EditPost,
|
|
||||||
SavePost,
|
|
||||||
EditCommunity,
|
|
||||||
FollowCommunity,
|
|
||||||
GetFollowedCommunities,
|
|
||||||
GetUserDetails,
|
|
||||||
GetReplies,
|
|
||||||
GetUserMentions,
|
|
||||||
EditUserMention,
|
|
||||||
GetModlog,
|
|
||||||
BanFromCommunity,
|
|
||||||
AddModToCommunity,
|
|
||||||
CreateSite,
|
|
||||||
EditSite,
|
|
||||||
GetSite,
|
|
||||||
AddAdmin,
|
|
||||||
BanUser,
|
|
||||||
Search,
|
|
||||||
MarkAllAsRead,
|
|
||||||
SaveUserSettings,
|
|
||||||
TransferCommunity,
|
|
||||||
TransferSite,
|
|
||||||
DeleteAccount,
|
|
||||||
PasswordReset,
|
|
||||||
PasswordChange,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
#[fail(display = "{{\"op\":\"{}\", \"error\":\"{}\"}}", op, message)]
|
#[fail(display = "{{\"error\":\"{}\"}}", message)]
|
||||||
pub struct APIError {
|
pub struct APIError {
|
||||||
pub op: String,
|
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl APIError {
|
impl APIError {
|
||||||
pub fn err(op: &UserOperation, msg: &str) -> Self {
|
pub fn err(msg: &str) -> Self {
|
||||||
APIError {
|
APIError {
|
||||||
op: op.to_string(),
|
|
||||||
message: msg.to_string(),
|
message: msg.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Oper<T> {
|
pub struct Oper<T> {
|
||||||
op: UserOperation,
|
|
||||||
data: T,
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Oper<T> {
|
impl<T> Oper<T> {
|
||||||
pub fn new(op: UserOperation, data: T) -> Oper<T> {
|
pub fn new(data: T) -> Oper<T> {
|
||||||
Oper { op, data }
|
Oper { data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ pub struct CreatePost {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct PostResponse {
|
pub struct PostResponse {
|
||||||
op: String,
|
|
||||||
pub post: PostView,
|
pub post: PostView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +25,6 @@ pub struct GetPost {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetPostResponse {
|
pub struct GetPostResponse {
|
||||||
op: String,
|
|
||||||
post: PostView,
|
post: PostView,
|
||||||
comments: Vec<CommentView>,
|
comments: Vec<CommentView>,
|
||||||
community: CommunityView,
|
community: CommunityView,
|
||||||
|
@ -46,7 +44,6 @@ pub struct GetPosts {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetPostsResponse {
|
pub struct GetPostsResponse {
|
||||||
op: String,
|
|
||||||
posts: Vec<PostView>,
|
posts: Vec<PostView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +56,6 @@ pub struct CreatePostLike {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct CreatePostLikeResponse {
|
pub struct CreatePostLikeResponse {
|
||||||
op: String,
|
|
||||||
post: PostView,
|
post: PostView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,23 +89,23 @@ impl Perform<PostResponse> for Oper<CreatePost> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) {
|
if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) {
|
||||||
return Err(APIError::err(&self.op, "no_slurs").into());
|
return Err(APIError::err("no_slurs").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
|
if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
|
||||||
return Err(APIError::err(&self.op, "community_ban").into());
|
return Err(APIError::err("community_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
|
@ -128,7 +124,7 @@ impl Perform<PostResponse> for Oper<CreatePost> {
|
||||||
|
|
||||||
let inserted_post = match Post::create(&conn, &post_form) {
|
let inserted_post = match Post::create(&conn, &post_form) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// They like their own post by default
|
// They like their own post by default
|
||||||
|
@ -141,19 +137,16 @@ impl Perform<PostResponse> for Oper<CreatePost> {
|
||||||
// Only add the like if the score isnt 0
|
// Only add the like if the score isnt 0
|
||||||
let _inserted_like = match PostLike::like(&conn, &like_form) {
|
let _inserted_like = match PostLike::like(&conn, &like_form) {
|
||||||
Ok(like) => like,
|
Ok(like) => like,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Refetch the view
|
// Refetch the view
|
||||||
let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
|
let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(PostResponse {
|
Ok(PostResponse { post: post_view })
|
||||||
op: self.op.to_string(),
|
|
||||||
post: post_view,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +167,7 @@ impl Perform<GetPostResponse> for Oper<GetPost> {
|
||||||
|
|
||||||
let post_view = match PostView::read(&conn, data.id, user_id) {
|
let post_view = match PostView::read(&conn, data.id, user_id) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let comments = CommentQueryBuilder::create(&conn)
|
let comments = CommentQueryBuilder::create(&conn)
|
||||||
|
@ -195,7 +188,6 @@ impl Perform<GetPostResponse> for Oper<GetPost> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(GetPostResponse {
|
Ok(GetPostResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
post: post_view,
|
post: post_view,
|
||||||
comments,
|
comments,
|
||||||
community,
|
community,
|
||||||
|
@ -241,13 +233,10 @@ impl Perform<GetPostsResponse> for Oper<GetPosts> {
|
||||||
.list()
|
.list()
|
||||||
{
|
{
|
||||||
Ok(posts) => posts,
|
Ok(posts) => posts,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_get_posts").into()),
|
Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(GetPostsResponse {
|
Ok(GetPostsResponse { posts })
|
||||||
op: self.op.to_string(),
|
|
||||||
posts,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +246,7 @@ impl Perform<CreatePostLikeResponse> for Oper<CreatePostLike> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -266,19 +255,19 @@ impl Perform<CreatePostLikeResponse> for Oper<CreatePostLike> {
|
||||||
if data.score == -1 {
|
if data.score == -1 {
|
||||||
let site = SiteView::read(&conn)?;
|
let site = SiteView::read(&conn)?;
|
||||||
if !site.enable_downvotes {
|
if !site.enable_downvotes {
|
||||||
return Err(APIError::err(&self.op, "downvotes_disabled").into());
|
return Err(APIError::err("downvotes_disabled").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post = Post::read(&conn, data.post_id)?;
|
let post = Post::read(&conn, data.post_id)?;
|
||||||
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
||||||
return Err(APIError::err(&self.op, "community_ban").into());
|
return Err(APIError::err("community_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let like_form = PostLikeForm {
|
let like_form = PostLikeForm {
|
||||||
|
@ -295,20 +284,17 @@ impl Perform<CreatePostLikeResponse> for Oper<CreatePostLike> {
|
||||||
if do_add {
|
if do_add {
|
||||||
let _inserted_like = match PostLike::like(&conn, &like_form) {
|
let _inserted_like = match PostLike::like(&conn, &like_form) {
|
||||||
Ok(like) => like,
|
Ok(like) => like,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {
|
let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// just output the score
|
// just output the score
|
||||||
Ok(CreatePostLikeResponse {
|
Ok(CreatePostLikeResponse { post: post_view })
|
||||||
op: self.op.to_string(),
|
|
||||||
post: post_view,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,12 +302,12 @@ impl Perform<PostResponse> for Oper<EditPost> {
|
||||||
fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
|
fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
|
||||||
let data: &EditPost = &self.data;
|
let data: &EditPost = &self.data;
|
||||||
if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) {
|
if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) {
|
||||||
return Err(APIError::err(&self.op, "no_slurs").into());
|
return Err(APIError::err("no_slurs").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -336,17 +322,17 @@ impl Perform<PostResponse> for Oper<EditPost> {
|
||||||
);
|
);
|
||||||
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
|
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
|
||||||
if !editors.contains(&user_id) {
|
if !editors.contains(&user_id) {
|
||||||
return Err(APIError::err(&self.op, "no_post_edit_allowed").into());
|
return Err(APIError::err("no_post_edit_allowed").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
|
if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() {
|
||||||
return Err(APIError::err(&self.op, "community_ban").into());
|
return Err(APIError::err("community_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if UserView::read(&conn, user_id)?.banned {
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
return Err(APIError::err(&self.op, "site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
|
@ -365,7 +351,7 @@ impl Perform<PostResponse> for Oper<EditPost> {
|
||||||
|
|
||||||
let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
|
let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_post").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
|
@ -399,10 +385,7 @@ impl Perform<PostResponse> for Oper<EditPost> {
|
||||||
|
|
||||||
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
|
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(PostResponse {
|
Ok(PostResponse { post: post_view })
|
||||||
op: self.op.to_string(),
|
|
||||||
post: post_view,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,7 +395,7 @@ impl Perform<PostResponse> for Oper<SavePost> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -425,20 +408,17 @@ impl Perform<PostResponse> for Oper<SavePost> {
|
||||||
if data.save {
|
if data.save {
|
||||||
match PostSaved::save(&conn, &post_saved_form) {
|
match PostSaved::save(&conn, &post_saved_form) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
match PostSaved::unsave(&conn, &post_saved_form) {
|
match PostSaved::unsave(&conn, &post_saved_form) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_save_post").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let post_view = PostView::read(&conn, data.post_id, Some(user_id))?;
|
let post_view = PostView::read(&conn, data.post_id, Some(user_id))?;
|
||||||
|
|
||||||
Ok(PostResponse {
|
Ok(PostResponse { post: post_view })
|
||||||
op: self.op.to_string(),
|
|
||||||
post: post_view,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ pub struct ListCategories;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ListCategoriesResponse {
|
pub struct ListCategoriesResponse {
|
||||||
op: String,
|
|
||||||
categories: Vec<Category>,
|
categories: Vec<Category>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +23,6 @@ pub struct Search {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct SearchResponse {
|
pub struct SearchResponse {
|
||||||
op: String,
|
|
||||||
type_: String,
|
type_: String,
|
||||||
comments: Vec<CommentView>,
|
comments: Vec<CommentView>,
|
||||||
posts: Vec<PostView>,
|
posts: Vec<PostView>,
|
||||||
|
@ -42,7 +40,6 @@ pub struct GetModlog {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetModlogResponse {
|
pub struct GetModlogResponse {
|
||||||
op: String,
|
|
||||||
removed_posts: Vec<ModRemovePostView>,
|
removed_posts: Vec<ModRemovePostView>,
|
||||||
locked_posts: Vec<ModLockPostView>,
|
locked_posts: Vec<ModLockPostView>,
|
||||||
stickied_posts: Vec<ModStickyPostView>,
|
stickied_posts: Vec<ModStickyPostView>,
|
||||||
|
@ -79,13 +76,11 @@ pub struct GetSite;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct SiteResponse {
|
pub struct SiteResponse {
|
||||||
op: String,
|
|
||||||
site: SiteView,
|
site: SiteView,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetSiteResponse {
|
pub struct GetSiteResponse {
|
||||||
op: String,
|
|
||||||
site: Option<SiteView>,
|
site: Option<SiteView>,
|
||||||
admins: Vec<UserView>,
|
admins: Vec<UserView>,
|
||||||
banned: Vec<UserView>,
|
banned: Vec<UserView>,
|
||||||
|
@ -105,10 +100,7 @@ impl Perform<ListCategoriesResponse> for Oper<ListCategories> {
|
||||||
let categories: Vec<Category> = Category::list_all(&conn)?;
|
let categories: Vec<Category> = Category::list_all(&conn)?;
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(ListCategoriesResponse {
|
Ok(ListCategoriesResponse { categories })
|
||||||
op: self.op.to_string(),
|
|
||||||
categories,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +164,6 @@ impl Perform<GetModlogResponse> for Oper<GetModlog> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(GetModlogResponse {
|
Ok(GetModlogResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
removed_posts,
|
removed_posts,
|
||||||
locked_posts,
|
locked_posts,
|
||||||
stickied_posts,
|
stickied_posts,
|
||||||
|
@ -192,20 +183,20 @@ impl Perform<SiteResponse> for Oper<CreateSite> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if has_slurs(&data.name)
|
if has_slurs(&data.name)
|
||||||
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap()))
|
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap()))
|
||||||
{
|
{
|
||||||
return Err(APIError::err(&self.op, "no_slurs").into());
|
return Err(APIError::err("no_slurs").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
if !UserView::read(&conn, user_id)?.admin {
|
if !UserView::read(&conn, user_id)?.admin {
|
||||||
return Err(APIError::err(&self.op, "not_an_admin").into());
|
return Err(APIError::err("not_an_admin").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let site_form = SiteForm {
|
let site_form = SiteForm {
|
||||||
|
@ -220,15 +211,12 @@ impl Perform<SiteResponse> for Oper<CreateSite> {
|
||||||
|
|
||||||
match Site::create(&conn, &site_form) {
|
match Site::create(&conn, &site_form) {
|
||||||
Ok(site) => site,
|
Ok(site) => site,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "site_already_exists").into()),
|
Err(_e) => return Err(APIError::err("site_already_exists").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let site_view = SiteView::read(&conn)?;
|
let site_view = SiteView::read(&conn)?;
|
||||||
|
|
||||||
Ok(SiteResponse {
|
Ok(SiteResponse { site: site_view })
|
||||||
op: self.op.to_string(),
|
|
||||||
site: site_view,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,20 +226,20 @@ impl Perform<SiteResponse> for Oper<EditSite> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if has_slurs(&data.name)
|
if has_slurs(&data.name)
|
||||||
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap()))
|
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap()))
|
||||||
{
|
{
|
||||||
return Err(APIError::err(&self.op, "no_slurs").into());
|
return Err(APIError::err("no_slurs").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
if !UserView::read(&conn, user_id)?.admin {
|
if !UserView::read(&conn, user_id)?.admin {
|
||||||
return Err(APIError::err(&self.op, "not_an_admin").into());
|
return Err(APIError::err("not_an_admin").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let found_site = Site::read(&conn, 1)?;
|
let found_site = Site::read(&conn, 1)?;
|
||||||
|
@ -268,15 +256,12 @@ impl Perform<SiteResponse> for Oper<EditSite> {
|
||||||
|
|
||||||
match Site::update(&conn, 1, &site_form) {
|
match Site::update(&conn, 1, &site_form) {
|
||||||
Ok(site) => site,
|
Ok(site) => site,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let site_view = SiteView::read(&conn)?;
|
let site_view = SiteView::read(&conn)?;
|
||||||
|
|
||||||
Ok(SiteResponse {
|
Ok(SiteResponse { site: site_view })
|
||||||
op: self.op.to_string(),
|
|
||||||
site: site_view,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,7 +286,6 @@ impl Perform<GetSiteResponse> for Oper<GetSite> {
|
||||||
let banned = UserView::banned(&conn)?;
|
let banned = UserView::banned(&conn)?;
|
||||||
|
|
||||||
Ok(GetSiteResponse {
|
Ok(GetSiteResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
site: site_view,
|
site: site_view,
|
||||||
admins,
|
admins,
|
||||||
banned,
|
banned,
|
||||||
|
@ -419,7 +403,6 @@ impl Perform<SearchResponse> for Oper<Search> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(SearchResponse {
|
Ok(SearchResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
type_: data.type_.to_owned(),
|
type_: data.type_.to_owned(),
|
||||||
comments,
|
comments,
|
||||||
posts,
|
posts,
|
||||||
|
@ -435,7 +418,7 @@ impl Perform<GetSiteResponse> for Oper<TransferSite> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -444,7 +427,7 @@ impl Perform<GetSiteResponse> for Oper<TransferSite> {
|
||||||
|
|
||||||
// Make sure user is the creator
|
// Make sure user is the creator
|
||||||
if read_site.creator_id != user_id {
|
if read_site.creator_id != user_id {
|
||||||
return Err(APIError::err(&self.op, "not_an_admin").into());
|
return Err(APIError::err("not_an_admin").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let site_form = SiteForm {
|
let site_form = SiteForm {
|
||||||
|
@ -459,7 +442,7 @@ impl Perform<GetSiteResponse> for Oper<TransferSite> {
|
||||||
|
|
||||||
match Site::update(&conn, 1, &site_form) {
|
match Site::update(&conn, 1, &site_form) {
|
||||||
Ok(site) => site,
|
Ok(site) => site,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
|
@ -484,7 +467,6 @@ impl Perform<GetSiteResponse> for Oper<TransferSite> {
|
||||||
let banned = UserView::banned(&conn)?;
|
let banned = UserView::banned(&conn)?;
|
||||||
|
|
||||||
Ok(GetSiteResponse {
|
Ok(GetSiteResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
site: Some(site_view),
|
site: Some(site_view),
|
||||||
admins,
|
admins,
|
||||||
banned,
|
banned,
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub struct SaveUserSettings {
|
||||||
lang: String,
|
lang: String,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
|
matrix_user_id: Option<String>,
|
||||||
new_password: Option<String>,
|
new_password: Option<String>,
|
||||||
new_password_verify: Option<String>,
|
new_password_verify: Option<String>,
|
||||||
old_password: Option<String>,
|
old_password: Option<String>,
|
||||||
|
@ -40,7 +41,6 @@ pub struct SaveUserSettings {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoginResponse {
|
pub struct LoginResponse {
|
||||||
op: String,
|
|
||||||
jwt: String,
|
jwt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,6 @@ pub struct GetUserDetails {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetUserDetailsResponse {
|
pub struct GetUserDetailsResponse {
|
||||||
op: String,
|
|
||||||
user: UserView,
|
user: UserView,
|
||||||
follows: Vec<CommunityFollowerView>,
|
follows: Vec<CommunityFollowerView>,
|
||||||
moderates: Vec<CommunityModeratorView>,
|
moderates: Vec<CommunityModeratorView>,
|
||||||
|
@ -69,13 +68,11 @@ pub struct GetUserDetailsResponse {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetRepliesResponse {
|
pub struct GetRepliesResponse {
|
||||||
op: String,
|
|
||||||
replies: Vec<ReplyView>,
|
replies: Vec<ReplyView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetUserMentionsResponse {
|
pub struct GetUserMentionsResponse {
|
||||||
op: String,
|
|
||||||
mentions: Vec<UserMentionView>,
|
mentions: Vec<UserMentionView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +90,6 @@ pub struct AddAdmin {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct AddAdminResponse {
|
pub struct AddAdminResponse {
|
||||||
op: String,
|
|
||||||
admins: Vec<UserView>,
|
admins: Vec<UserView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +104,6 @@ pub struct BanUser {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct BanUserResponse {
|
pub struct BanUserResponse {
|
||||||
op: String,
|
|
||||||
user: UserView,
|
user: UserView,
|
||||||
banned: bool,
|
banned: bool,
|
||||||
}
|
}
|
||||||
|
@ -140,7 +135,6 @@ pub struct EditUserMention {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct UserMentionResponse {
|
pub struct UserMentionResponse {
|
||||||
op: String,
|
|
||||||
mention: UserMentionView,
|
mention: UserMentionView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,9 +150,7 @@ pub struct PasswordReset {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct PasswordResetResponse {
|
pub struct PasswordResetResponse {}
|
||||||
op: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct PasswordChange {
|
pub struct PasswordChange {
|
||||||
|
@ -167,6 +159,40 @@ pub struct PasswordChange {
|
||||||
password_verify: String,
|
password_verify: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreatePrivateMessage {
|
||||||
|
content: String,
|
||||||
|
recipient_id: i32,
|
||||||
|
auth: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct EditPrivateMessage {
|
||||||
|
edit_id: i32,
|
||||||
|
content: Option<String>,
|
||||||
|
deleted: Option<bool>,
|
||||||
|
read: Option<bool>,
|
||||||
|
auth: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GetPrivateMessages {
|
||||||
|
unread_only: bool,
|
||||||
|
page: Option<i64>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
auth: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct PrivateMessagesResponse {
|
||||||
|
messages: Vec<PrivateMessageView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct PrivateMessageResponse {
|
||||||
|
message: PrivateMessageView,
|
||||||
|
}
|
||||||
|
|
||||||
impl Perform<LoginResponse> for Oper<Login> {
|
impl Perform<LoginResponse> for Oper<Login> {
|
||||||
fn perform(&self, conn: &PgConnection) -> Result<LoginResponse, Error> {
|
fn perform(&self, conn: &PgConnection) -> Result<LoginResponse, Error> {
|
||||||
let data: &Login = &self.data;
|
let data: &Login = &self.data;
|
||||||
|
@ -174,20 +200,17 @@ impl Perform<LoginResponse> for Oper<Login> {
|
||||||
// Fetch that username / email
|
// Fetch that username / email
|
||||||
let user: User_ = match User_::find_by_email_or_username(&conn, &data.username_or_email) {
|
let user: User_ = match User_::find_by_email_or_username(&conn, &data.username_or_email) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_that_username_or_email").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify the password
|
// Verify the password
|
||||||
let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
|
let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
|
||||||
if !valid {
|
if !valid {
|
||||||
return Err(APIError::err(&self.op, "password_incorrect").into());
|
return Err(APIError::err("password_incorrect").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse { jwt: user.jwt() })
|
||||||
op: self.op.to_string(),
|
|
||||||
jwt: user.jwt(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,22 +221,22 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
// Make sure site has open registration
|
// Make sure site has open registration
|
||||||
if let Ok(site) = SiteView::read(&conn) {
|
if let Ok(site) = SiteView::read(&conn) {
|
||||||
if !site.open_registration {
|
if !site.open_registration {
|
||||||
return Err(APIError::err(&self.op, "registration_closed").into());
|
return Err(APIError::err("registration_closed").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure passwords match
|
// Make sure passwords match
|
||||||
if data.password != data.password_verify {
|
if data.password != data.password_verify {
|
||||||
return Err(APIError::err(&self.op, "passwords_dont_match").into());
|
return Err(APIError::err("passwords_dont_match").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_slurs(&data.username) {
|
if has_slurs(&data.username) {
|
||||||
return Err(APIError::err(&self.op, "no_slurs").into());
|
return Err(APIError::err("no_slurs").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure there are no admins
|
// Make sure there are no admins
|
||||||
if data.admin && !UserView::admins(&conn)?.is_empty() {
|
if data.admin && !UserView::admins(&conn)?.is_empty() {
|
||||||
return Err(APIError::err(&self.op, "admin_already_created").into());
|
return Err(APIError::err("admin_already_created").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the new user
|
// Register the new user
|
||||||
|
@ -221,6 +244,7 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
name: data.username.to_owned(),
|
name: data.username.to_owned(),
|
||||||
fedi_name: Settings::get().hostname.to_owned(),
|
fedi_name: Settings::get().hostname.to_owned(),
|
||||||
email: data.email.to_owned(),
|
email: data.email.to_owned(),
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
password_encrypted: data.password.to_owned(),
|
password_encrypted: data.password.to_owned(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
|
@ -248,7 +272,7 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
"user_already_exists"
|
"user_already_exists"
|
||||||
};
|
};
|
||||||
|
|
||||||
return Err(APIError::err(&self.op, err_type).into());
|
return Err(APIError::err(err_type).into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -280,7 +304,7 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
let _inserted_community_follower =
|
let _inserted_community_follower =
|
||||||
match CommunityFollower::follow(&conn, &community_follower_form) {
|
match CommunityFollower::follow(&conn, &community_follower_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()),
|
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If its an admin, add them as a mod and follower to main
|
// If its an admin, add them as a mod and follower to main
|
||||||
|
@ -293,15 +317,12 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
let _inserted_community_moderator =
|
let _inserted_community_moderator =
|
||||||
match CommunityModerator::join(&conn, &community_moderator_form) {
|
match CommunityModerator::join(&conn, &community_moderator_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => {
|
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
|
||||||
return Err(APIError::err(&self.op, "community_moderator_already_exists").into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
jwt: inserted_user.jwt(),
|
jwt: inserted_user.jwt(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -313,7 +334,7 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -331,7 +352,7 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
||||||
Some(new_password_verify) => {
|
Some(new_password_verify) => {
|
||||||
// Make sure passwords match
|
// Make sure passwords match
|
||||||
if new_password != new_password_verify {
|
if new_password != new_password_verify {
|
||||||
return Err(APIError::err(&self.op, "passwords_dont_match").into());
|
return Err(APIError::err("passwords_dont_match").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the old password
|
// Check the old password
|
||||||
|
@ -340,14 +361,14 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
||||||
let valid: bool =
|
let valid: bool =
|
||||||
verify(old_password, &read_user.password_encrypted).unwrap_or(false);
|
verify(old_password, &read_user.password_encrypted).unwrap_or(false);
|
||||||
if !valid {
|
if !valid {
|
||||||
return Err(APIError::err(&self.op, "password_incorrect").into());
|
return Err(APIError::err("password_incorrect").into());
|
||||||
}
|
}
|
||||||
User_::update_password(&conn, user_id, &new_password)?.password_encrypted
|
User_::update_password(&conn, user_id, &new_password)?.password_encrypted
|
||||||
}
|
}
|
||||||
None => return Err(APIError::err(&self.op, "password_incorrect").into()),
|
None => return Err(APIError::err("password_incorrect").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => return Err(APIError::err(&self.op, "passwords_dont_match").into()),
|
None => return Err(APIError::err("passwords_dont_match").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => read_user.password_encrypted,
|
None => read_user.password_encrypted,
|
||||||
|
@ -357,6 +378,7 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
||||||
name: read_user.name,
|
name: read_user.name,
|
||||||
fedi_name: read_user.fedi_name,
|
fedi_name: read_user.fedi_name,
|
||||||
email,
|
email,
|
||||||
|
matrix_user_id: data.matrix_user_id.to_owned(),
|
||||||
avatar: data.avatar.to_owned(),
|
avatar: data.avatar.to_owned(),
|
||||||
password_encrypted,
|
password_encrypted,
|
||||||
preferred_username: read_user.preferred_username,
|
preferred_username: read_user.preferred_username,
|
||||||
|
@ -383,13 +405,12 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
||||||
"user_already_exists"
|
"user_already_exists"
|
||||||
};
|
};
|
||||||
|
|
||||||
return Err(APIError::err(&self.op, err_type).into());
|
return Err(APIError::err(err_type).into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
jwt: updated_user.jwt(),
|
jwt: updated_user.jwt(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -430,9 +451,7 @@ impl Perform<GetUserDetailsResponse> for Oper<GetUserDetails> {
|
||||||
.unwrap_or_else(|| "admin".to_string()),
|
.unwrap_or_else(|| "admin".to_string()),
|
||||||
) {
|
) {
|
||||||
Ok(user) => user.id,
|
Ok(user) => user.id,
|
||||||
Err(_e) => {
|
Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
|
||||||
return Err(APIError::err(&self.op, "couldnt_find_that_username_or_email").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -475,7 +494,6 @@ impl Perform<GetUserDetailsResponse> for Oper<GetUserDetails> {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(GetUserDetailsResponse {
|
Ok(GetUserDetailsResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
user: user_view,
|
user: user_view,
|
||||||
follows,
|
follows,
|
||||||
moderates,
|
moderates,
|
||||||
|
@ -492,22 +510,24 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
if !UserView::read(&conn, user_id)?.admin {
|
if !UserView::read(&conn, user_id)?.admin {
|
||||||
return Err(APIError::err(&self.op, "not_an_admin").into());
|
return Err(APIError::err("not_an_admin").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let read_user = User_::read(&conn, data.user_id)?;
|
let read_user = User_::read(&conn, data.user_id)?;
|
||||||
|
|
||||||
|
// TODO make addadmin easier
|
||||||
let user_form = UserForm {
|
let user_form = UserForm {
|
||||||
name: read_user.name,
|
name: read_user.name,
|
||||||
fedi_name: read_user.fedi_name,
|
fedi_name: read_user.fedi_name,
|
||||||
email: read_user.email,
|
email: read_user.email,
|
||||||
|
matrix_user_id: read_user.matrix_user_id,
|
||||||
avatar: read_user.avatar,
|
avatar: read_user.avatar,
|
||||||
password_encrypted: read_user.password_encrypted,
|
password_encrypted: read_user.password_encrypted,
|
||||||
preferred_username: read_user.preferred_username,
|
preferred_username: read_user.preferred_username,
|
||||||
|
@ -525,7 +545,7 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
|
||||||
|
|
||||||
match User_::update(&conn, data.user_id, &user_form) {
|
match User_::update(&conn, data.user_id, &user_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
|
@ -543,10 +563,7 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
|
||||||
let creator_user = admins.remove(creator_index);
|
let creator_user = admins.remove(creator_index);
|
||||||
admins.insert(0, creator_user);
|
admins.insert(0, creator_user);
|
||||||
|
|
||||||
Ok(AddAdminResponse {
|
Ok(AddAdminResponse { admins })
|
||||||
op: self.op.to_string(),
|
|
||||||
admins,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,22 +573,24 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
if !UserView::read(&conn, user_id)?.admin {
|
if !UserView::read(&conn, user_id)?.admin {
|
||||||
return Err(APIError::err(&self.op, "not_an_admin").into());
|
return Err(APIError::err("not_an_admin").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let read_user = User_::read(&conn, data.user_id)?;
|
let read_user = User_::read(&conn, data.user_id)?;
|
||||||
|
|
||||||
|
// TODO make bans and addadmins easier
|
||||||
let user_form = UserForm {
|
let user_form = UserForm {
|
||||||
name: read_user.name,
|
name: read_user.name,
|
||||||
fedi_name: read_user.fedi_name,
|
fedi_name: read_user.fedi_name,
|
||||||
email: read_user.email,
|
email: read_user.email,
|
||||||
|
matrix_user_id: read_user.matrix_user_id,
|
||||||
avatar: read_user.avatar,
|
avatar: read_user.avatar,
|
||||||
password_encrypted: read_user.password_encrypted,
|
password_encrypted: read_user.password_encrypted,
|
||||||
preferred_username: read_user.preferred_username,
|
preferred_username: read_user.preferred_username,
|
||||||
|
@ -589,7 +608,7 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
|
||||||
|
|
||||||
match User_::update(&conn, data.user_id, &user_form) {
|
match User_::update(&conn, data.user_id, &user_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
|
@ -611,7 +630,6 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
|
||||||
let user_view = UserView::read(&conn, data.user_id)?;
|
let user_view = UserView::read(&conn, data.user_id)?;
|
||||||
|
|
||||||
Ok(BanUserResponse {
|
Ok(BanUserResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
user: user_view,
|
user: user_view,
|
||||||
banned: data.ban,
|
banned: data.ban,
|
||||||
})
|
})
|
||||||
|
@ -624,7 +642,7 @@ impl Perform<GetRepliesResponse> for Oper<GetReplies> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -638,10 +656,7 @@ impl Perform<GetRepliesResponse> for Oper<GetReplies> {
|
||||||
.limit(data.limit)
|
.limit(data.limit)
|
||||||
.list()?;
|
.list()?;
|
||||||
|
|
||||||
Ok(GetRepliesResponse {
|
Ok(GetRepliesResponse { replies })
|
||||||
op: self.op.to_string(),
|
|
||||||
replies,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,7 +666,7 @@ impl Perform<GetUserMentionsResponse> for Oper<GetUserMentions> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -665,10 +680,7 @@ impl Perform<GetUserMentionsResponse> for Oper<GetUserMentions> {
|
||||||
.limit(data.limit)
|
.limit(data.limit)
|
||||||
.list()?;
|
.list()?;
|
||||||
|
|
||||||
Ok(GetUserMentionsResponse {
|
Ok(GetUserMentionsResponse { mentions })
|
||||||
op: self.op.to_string(),
|
|
||||||
mentions,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,7 +690,7 @@ impl Perform<UserMentionResponse> for Oper<EditUserMention> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -694,13 +706,12 @@ impl Perform<UserMentionResponse> for Oper<EditUserMention> {
|
||||||
let _updated_user_mention =
|
let _updated_user_mention =
|
||||||
match UserMention::update(&conn, user_mention.id, &user_mention_form) {
|
match UserMention::update(&conn, user_mention.id, &user_mention_form) {
|
||||||
Ok(comment) => comment,
|
Ok(comment) => comment,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_mention_view = UserMentionView::read(&conn, user_mention.id, user_id)?;
|
let user_mention_view = UserMentionView::read(&conn, user_mention.id, user_id)?;
|
||||||
|
|
||||||
Ok(UserMentionResponse {
|
Ok(UserMentionResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
mention: user_mention_view,
|
mention: user_mention_view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -712,7 +723,7 @@ impl Perform<GetRepliesResponse> for Oper<MarkAllAsRead> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -737,7 +748,7 @@ impl Perform<GetRepliesResponse> for Oper<MarkAllAsRead> {
|
||||||
|
|
||||||
let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) {
|
let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) {
|
||||||
Ok(comment) => comment,
|
Ok(comment) => comment,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -758,14 +769,35 @@ impl Perform<GetRepliesResponse> for Oper<MarkAllAsRead> {
|
||||||
let _updated_mention =
|
let _updated_mention =
|
||||||
match UserMention::update(&conn, mention.user_mention_id, &mention_form) {
|
match UserMention::update(&conn, mention.user_mention_id, &mention_form) {
|
||||||
Ok(mention) => mention,
|
Ok(mention) => mention,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(GetRepliesResponse {
|
// messages
|
||||||
op: self.op.to_string(),
|
let messages = PrivateMessageQueryBuilder::create(&conn, user_id)
|
||||||
replies: vec![],
|
.page(1)
|
||||||
})
|
.limit(999)
|
||||||
|
.unread_only(true)
|
||||||
|
.list()?;
|
||||||
|
|
||||||
|
for message in &messages {
|
||||||
|
let private_message_form = PrivateMessageForm {
|
||||||
|
content: None,
|
||||||
|
creator_id: message.to_owned().creator_id,
|
||||||
|
recipient_id: message.to_owned().recipient_id,
|
||||||
|
deleted: None,
|
||||||
|
read: Some(true),
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _updated_message = match PrivateMessage::update(&conn, message.id, &private_message_form)
|
||||||
|
{
|
||||||
|
Ok(message) => message,
|
||||||
|
Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GetRepliesResponse { replies: vec![] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,7 +807,7 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
|
||||||
|
|
||||||
let claims = match Claims::decode(&data.auth) {
|
let claims = match Claims::decode(&data.auth) {
|
||||||
Ok(claims) => claims.claims,
|
Ok(claims) => claims.claims,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
@ -785,7 +817,7 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
|
||||||
// Verify the password
|
// Verify the password
|
||||||
let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
|
let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
|
||||||
if !valid {
|
if !valid {
|
||||||
return Err(APIError::err(&self.op, "password_incorrect").into());
|
return Err(APIError::err("password_incorrect").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
|
@ -808,7 +840,7 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
|
||||||
|
|
||||||
let _updated_comment = match Comment::update(&conn, comment.id, &comment_form) {
|
let _updated_comment = match Comment::update(&conn, comment.id, &comment_form) {
|
||||||
Ok(comment) => comment,
|
Ok(comment) => comment,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -836,12 +868,11 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
|
||||||
|
|
||||||
let _updated_post = match Post::update(&conn, post.id, &post_form) {
|
let _updated_post = match Post::update(&conn, post.id, &post_form) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_post").into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
jwt: data.auth.to_owned(),
|
jwt: data.auth.to_owned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -854,7 +885,7 @@ impl Perform<PasswordResetResponse> for Oper<PasswordReset> {
|
||||||
// Fetch that email
|
// Fetch that email
|
||||||
let user: User_ = match User_::find_by_email(&conn, &data.email) {
|
let user: User_ = match User_::find_by_email(&conn, &data.email) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_that_username_or_email").into()),
|
Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate a random token
|
// Generate a random token
|
||||||
|
@ -871,12 +902,10 @@ impl Perform<PasswordResetResponse> for Oper<PasswordReset> {
|
||||||
let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", user.name, hostname, &token);
|
let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", user.name, hostname, &token);
|
||||||
match send_email(subject, user_email, &user.name, html) {
|
match send_email(subject, user_email, &user.name, html) {
|
||||||
Ok(_o) => _o,
|
Ok(_o) => _o,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, &_e).into()),
|
Err(_e) => return Err(APIError::err(&_e).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(PasswordResetResponse {
|
Ok(PasswordResetResponse {})
|
||||||
op: self.op.to_string(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -889,19 +918,156 @@ impl Perform<LoginResponse> for Oper<PasswordChange> {
|
||||||
|
|
||||||
// Make sure passwords match
|
// Make sure passwords match
|
||||||
if data.password != data.password_verify {
|
if data.password != data.password_verify {
|
||||||
return Err(APIError::err(&self.op, "passwords_dont_match").into());
|
return Err(APIError::err("passwords_dont_match").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the user with the new password
|
// Update the user with the new password
|
||||||
let updated_user = match User_::update_password(&conn, user_id, &data.password) {
|
let updated_user = match User_::update_password(&conn, user_id, &data.password) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
op: self.op.to_string(),
|
|
||||||
jwt: updated_user.jwt(),
|
jwt: updated_user.jwt(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Perform<PrivateMessageResponse> for Oper<CreatePrivateMessage> {
|
||||||
|
fn perform(&self, conn: &PgConnection) -> Result<PrivateMessageResponse, Error> {
|
||||||
|
let data: &CreatePrivateMessage = &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;
|
||||||
|
|
||||||
|
let hostname = &format!("https://{}", Settings::get().hostname);
|
||||||
|
|
||||||
|
// Check for a site ban
|
||||||
|
if UserView::read(&conn, user_id)?.banned {
|
||||||
|
return Err(APIError::err("site_ban").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
||||||
|
|
||||||
|
let private_message_form = PrivateMessageForm {
|
||||||
|
content: Some(content_slurs_removed.to_owned()),
|
||||||
|
creator_id: user_id,
|
||||||
|
recipient_id: data.recipient_id,
|
||||||
|
deleted: None,
|
||||||
|
read: None,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_private_message = match PrivateMessage::create(&conn, &private_message_form) {
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => {
|
||||||
|
return Err(APIError::err("couldnt_create_private_message").into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send notifications to the recipient
|
||||||
|
let recipient_user = User_::read(&conn, data.recipient_id)?;
|
||||||
|
if recipient_user.send_notifications_to_email {
|
||||||
|
if let Some(email) = recipient_user.email {
|
||||||
|
let subject = &format!(
|
||||||
|
"{} - Private Message from {}",
|
||||||
|
Settings::get().hostname,
|
||||||
|
claims.username
|
||||||
|
);
|
||||||
|
let html = &format!(
|
||||||
|
"<h1>Private Message</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
||||||
|
claims.username, &content_slurs_removed, hostname
|
||||||
|
);
|
||||||
|
match send_email(subject, &email, &recipient_user.name, html) {
|
||||||
|
Ok(_o) => _o,
|
||||||
|
Err(e) => eprintln!("{}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = PrivateMessageView::read(&conn, inserted_private_message.id)?;
|
||||||
|
|
||||||
|
Ok(PrivateMessageResponse { message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Perform<PrivateMessageResponse> for Oper<EditPrivateMessage> {
|
||||||
|
fn perform(&self, conn: &PgConnection) -> Result<PrivateMessageResponse, Error> {
|
||||||
|
let data: &EditPrivateMessage = &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;
|
||||||
|
|
||||||
|
let orig_private_message = PrivateMessage::read(&conn, data.edit_id)?;
|
||||||
|
|
||||||
|
// Check for a site ban
|
||||||
|
if UserView::read(&conn, user_id)?.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))
|
||||||
|
{
|
||||||
|
return Err(APIError::err("no_private_message_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_slurs_removed = match &data.content {
|
||||||
|
Some(content) => Some(remove_slurs(content)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let private_message_form = 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: data.read.to_owned(),
|
||||||
|
updated: if data.read.is_some() {
|
||||||
|
orig_private_message.updated
|
||||||
|
} else {
|
||||||
|
Some(naive_now())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let _updated_private_message =
|
||||||
|
match PrivateMessage::update(&conn, data.edit_id, &private_message_form) {
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = PrivateMessageView::read(&conn, data.edit_id)?;
|
||||||
|
|
||||||
|
Ok(PrivateMessageResponse { message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Perform<PrivateMessagesResponse> for Oper<GetPrivateMessages> {
|
||||||
|
fn perform(&self, conn: &PgConnection) -> Result<PrivateMessagesResponse, Error> {
|
||||||
|
let data: &GetPrivateMessages = &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;
|
||||||
|
|
||||||
|
let messages = PrivateMessageQueryBuilder::create(&conn, user_id)
|
||||||
|
.page(data.page)
|
||||||
|
.limit(data.limit)
|
||||||
|
.unread_only(data.unread_only)
|
||||||
|
.list()?;
|
||||||
|
|
||||||
|
Ok(PrivateMessagesResponse { messages })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "here".into(),
|
password_encrypted: "here".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
published: naive_now(),
|
published: naive_now(),
|
||||||
admin: false,
|
admin: false,
|
||||||
|
|
|
@ -174,6 +174,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -398,6 +398,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -220,6 +220,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -15,6 +15,8 @@ pub mod moderator_views;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_view;
|
pub mod post_view;
|
||||||
|
pub mod private_message;
|
||||||
|
pub mod private_message_view;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
pub mod site_view;
|
pub mod site_view;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
|
@ -442,6 +442,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
@ -463,6 +464,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -92,6 +92,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -187,6 +187,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -339,6 +339,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
|
|
144
server/src/db/private_message.rs
Normal file
144
server/src/db/private_message.rs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
use super::*;
|
||||||
|
use crate::schema::private_message;
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name = "private_message"]
|
||||||
|
pub struct PrivateMessage {
|
||||||
|
pub id: i32,
|
||||||
|
pub creator_id: i32,
|
||||||
|
pub recipient_id: i32,
|
||||||
|
pub content: String,
|
||||||
|
pub deleted: bool,
|
||||||
|
pub read: bool,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
|
#[table_name = "private_message"]
|
||||||
|
pub struct PrivateMessageForm {
|
||||||
|
pub creator_id: i32,
|
||||||
|
pub recipient_id: i32,
|
||||||
|
pub content: Option<String>,
|
||||||
|
pub deleted: Option<bool>,
|
||||||
|
pub read: Option<bool>,
|
||||||
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<PrivateMessageForm> for PrivateMessage {
|
||||||
|
fn read(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> {
|
||||||
|
use crate::schema::private_message::dsl::*;
|
||||||
|
private_message.find(private_message_id).first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, private_message_id: i32) -> Result<usize, Error> {
|
||||||
|
use crate::schema::private_message::dsl::*;
|
||||||
|
diesel::delete(private_message.find(private_message_id)).execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, private_message_form: &PrivateMessageForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::private_message::dsl::*;
|
||||||
|
insert_into(private_message)
|
||||||
|
.values(private_message_form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
conn: &PgConnection,
|
||||||
|
private_message_id: i32,
|
||||||
|
private_message_form: &PrivateMessageForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::private_message::dsl::*;
|
||||||
|
diesel::update(private_message.find(private_message_id))
|
||||||
|
.set(private_message_form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::super::user::*;
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_crud() {
|
||||||
|
let conn = establish_unpooled_connection();
|
||||||
|
|
||||||
|
let creator_form = UserForm {
|
||||||
|
name: "creator_pm".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
|
avatar: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
|
updated: None,
|
||||||
|
show_nsfw: false,
|
||||||
|
theme: "darkly".into(),
|
||||||
|
default_sort_type: SortType::Hot as i16,
|
||||||
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_creator = User_::create(&conn, &creator_form).unwrap();
|
||||||
|
|
||||||
|
let recipient_form = UserForm {
|
||||||
|
name: "recipient_pm".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
|
avatar: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
|
updated: None,
|
||||||
|
show_nsfw: false,
|
||||||
|
theme: "darkly".into(),
|
||||||
|
default_sort_type: SortType::Hot as i16,
|
||||||
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
|
||||||
|
|
||||||
|
let private_message_form = PrivateMessageForm {
|
||||||
|
content: Some("A test private message".into()),
|
||||||
|
creator_id: inserted_creator.id,
|
||||||
|
recipient_id: inserted_recipient.id,
|
||||||
|
deleted: None,
|
||||||
|
read: None,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_private_message = PrivateMessage::create(&conn, &private_message_form).unwrap();
|
||||||
|
|
||||||
|
let expected_private_message = PrivateMessage {
|
||||||
|
id: inserted_private_message.id,
|
||||||
|
content: "A test private message".into(),
|
||||||
|
creator_id: inserted_creator.id,
|
||||||
|
recipient_id: inserted_recipient.id,
|
||||||
|
deleted: false,
|
||||||
|
read: false,
|
||||||
|
updated: None,
|
||||||
|
published: inserted_private_message.published,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 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_eq!(1, num_deleted);
|
||||||
|
}
|
||||||
|
}
|
140
server/src/db/private_message_view.rs
Normal file
140
server/src/db/private_message_view.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use super::*;
|
||||||
|
use diesel::pg::Pg;
|
||||||
|
|
||||||
|
// The faked schema since diesel doesn't do views
|
||||||
|
table! {
|
||||||
|
private_message_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
creator_id -> Int4,
|
||||||
|
recipient_id -> Int4,
|
||||||
|
content -> Text,
|
||||||
|
deleted -> Bool,
|
||||||
|
read -> Bool,
|
||||||
|
published -> Timestamp,
|
||||||
|
updated -> Nullable<Timestamp>,
|
||||||
|
creator_name -> Varchar,
|
||||||
|
creator_avatar -> Nullable<Text>,
|
||||||
|
recipient_name -> Varchar,
|
||||||
|
recipient_avatar -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
private_message_mview (id) {
|
||||||
|
id -> Int4,
|
||||||
|
creator_id -> Int4,
|
||||||
|
recipient_id -> Int4,
|
||||||
|
content -> Text,
|
||||||
|
deleted -> Bool,
|
||||||
|
read -> Bool,
|
||||||
|
published -> Timestamp,
|
||||||
|
updated -> Nullable<Timestamp>,
|
||||||
|
creator_name -> Varchar,
|
||||||
|
creator_avatar -> Nullable<Text>,
|
||||||
|
recipient_name -> Varchar,
|
||||||
|
recipient_avatar -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||||
|
)]
|
||||||
|
#[table_name = "private_message_view"]
|
||||||
|
pub struct PrivateMessageView {
|
||||||
|
pub id: i32,
|
||||||
|
pub creator_id: i32,
|
||||||
|
pub recipient_id: i32,
|
||||||
|
pub content: String,
|
||||||
|
pub deleted: bool,
|
||||||
|
pub read: bool,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
pub creator_name: String,
|
||||||
|
pub creator_avatar: Option<String>,
|
||||||
|
pub recipient_name: String,
|
||||||
|
pub recipient_avatar: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrivateMessageQueryBuilder<'a> {
|
||||||
|
conn: &'a PgConnection,
|
||||||
|
query: super::private_message_view::private_message_mview::BoxedQuery<'a, Pg>,
|
||||||
|
for_recipient_id: i32,
|
||||||
|
unread_only: bool,
|
||||||
|
page: Option<i64>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PrivateMessageQueryBuilder<'a> {
|
||||||
|
pub fn create(conn: &'a PgConnection, for_recipient_id: i32) -> Self {
|
||||||
|
use super::private_message_view::private_message_mview::dsl::*;
|
||||||
|
|
||||||
|
let query = private_message_mview.into_boxed();
|
||||||
|
|
||||||
|
PrivateMessageQueryBuilder {
|
||||||
|
conn,
|
||||||
|
query,
|
||||||
|
for_recipient_id,
|
||||||
|
unread_only: false,
|
||||||
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unread_only(mut self, unread_only: bool) -> Self {
|
||||||
|
self.unread_only = unread_only;
|
||||||
|
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 list(self) -> Result<Vec<PrivateMessageView>, Error> {
|
||||||
|
use super::private_message_view::private_message_mview::dsl::*;
|
||||||
|
|
||||||
|
let mut query = self.query;
|
||||||
|
|
||||||
|
// If its unread, I only want the ones to me
|
||||||
|
if self.unread_only {
|
||||||
|
query = query
|
||||||
|
.filter(read.eq(false))
|
||||||
|
.filter(recipient_id.eq(self.for_recipient_id));
|
||||||
|
}
|
||||||
|
// Otherwise, I want the ALL view to show both sent and received
|
||||||
|
else {
|
||||||
|
query = query.filter(
|
||||||
|
recipient_id
|
||||||
|
.eq(self.for_recipient_id)
|
||||||
|
.or(creator_id.eq(self.for_recipient_id)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let (limit, offset) = limit_and_offset(self.page, self.limit);
|
||||||
|
|
||||||
|
query
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.order_by(published.desc())
|
||||||
|
.load::<PrivateMessageView>(self.conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrivateMessageView {
|
||||||
|
pub fn read(conn: &PgConnection, from_private_message_id: i32) -> Result<Self, Error> {
|
||||||
|
use super::private_message_view::private_message_view::dsl::*;
|
||||||
|
|
||||||
|
let mut query = private_message_view.into_boxed();
|
||||||
|
|
||||||
|
query = query
|
||||||
|
.filter(id.eq(from_private_message_id))
|
||||||
|
.order_by(published.desc());
|
||||||
|
|
||||||
|
query.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ pub struct User_ {
|
||||||
pub lang: String,
|
pub lang: String,
|
||||||
pub show_avatars: bool,
|
pub show_avatars: bool,
|
||||||
pub send_notifications_to_email: bool,
|
pub send_notifications_to_email: bool,
|
||||||
|
pub matrix_user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable, AsChangeset, Clone)]
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
|
@ -47,6 +48,7 @@ pub struct UserForm {
|
||||||
pub lang: String,
|
pub lang: String,
|
||||||
pub show_avatars: bool,
|
pub show_avatars: bool,
|
||||||
pub send_notifications_to_email: bool,
|
pub send_notifications_to_email: bool,
|
||||||
|
pub matrix_user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Crud<UserForm> for User_ {
|
impl Crud<UserForm> for User_ {
|
||||||
|
@ -184,6 +186,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
@ -206,6 +209,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -68,6 +68,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
@ -89,6 +90,7 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
admin: false,
|
admin: false,
|
||||||
banned: false,
|
banned: false,
|
||||||
|
|
|
@ -8,6 +8,7 @@ table! {
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
avatar -> Nullable<Text>,
|
avatar -> Nullable<Text>,
|
||||||
email -> Nullable<Text>,
|
email -> Nullable<Text>,
|
||||||
|
matrix_user_id -> Nullable<Text>,
|
||||||
fedi_name -> Varchar,
|
fedi_name -> Varchar,
|
||||||
admin -> Bool,
|
admin -> Bool,
|
||||||
banned -> Bool,
|
banned -> Bool,
|
||||||
|
@ -27,6 +28,7 @@ table! {
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
avatar -> Nullable<Text>,
|
avatar -> Nullable<Text>,
|
||||||
email -> Nullable<Text>,
|
email -> Nullable<Text>,
|
||||||
|
matrix_user_id -> Nullable<Text>,
|
||||||
fedi_name -> Varchar,
|
fedi_name -> Varchar,
|
||||||
admin -> Bool,
|
admin -> Bool,
|
||||||
banned -> Bool,
|
banned -> Bool,
|
||||||
|
@ -49,6 +51,7 @@ pub struct UserView {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
|
pub matrix_user_id: Option<String>,
|
||||||
pub fedi_name: String,
|
pub fedi_name: String,
|
||||||
pub admin: bool,
|
pub admin: bool,
|
||||||
pub banned: bool,
|
pub banned: bool,
|
||||||
|
|
|
@ -105,7 +105,7 @@ pub fn send_email(
|
||||||
|
|
||||||
let mut mailer = SmtpClient::new_simple(&email_config.smtp_server)
|
let mut mailer = SmtpClient::new_simple(&email_config.smtp_server)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.hello_name(ClientId::Domain("localhost".to_string()))
|
.hello_name(ClientId::Domain(Settings::get().hostname.to_owned()))
|
||||||
.credentials(Credentials::new(
|
.credentials(Credentials::new(
|
||||||
email_config.smtp_login.to_owned(),
|
email_config.smtp_login.to_owned(),
|
||||||
email_config.smtp_password.to_owned(),
|
email_config.smtp_password.to_owned(),
|
||||||
|
@ -117,6 +117,8 @@ pub fn send_email(
|
||||||
|
|
||||||
let result = mailer.send(email.into());
|
let result = mailer.send(email.into());
|
||||||
|
|
||||||
|
mailer.close();
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(_) => Err("no_email_setup".to_string()),
|
Err(_) => Err("no_email_setup".to_string()),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use actix::prelude::*;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use diesel::r2d2::{ConnectionManager, Pool};
|
use diesel::r2d2::{ConnectionManager, Pool};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use lemmy_server::routes::{federation, feeds, index, nodeinfo, webfinger, websocket};
|
use lemmy_server::routes::{api, federation, feeds, index, nodeinfo, webfinger, websocket};
|
||||||
use lemmy_server::settings::Settings;
|
use lemmy_server::settings::Settings;
|
||||||
use lemmy_server::websocket::server::*;
|
use lemmy_server::websocket::server::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
@ -44,6 +44,7 @@ async fn main() -> io::Result<()> {
|
||||||
.data(pool.clone())
|
.data(pool.clone())
|
||||||
.data(server.clone())
|
.data(server.clone())
|
||||||
// The routes
|
// The routes
|
||||||
|
.configure(api::config)
|
||||||
.configure(federation::config)
|
.configure(federation::config)
|
||||||
.configure(feeds::config)
|
.configure(feeds::config)
|
||||||
.configure(index::config)
|
.configure(index::config)
|
||||||
|
|
103
server/src/routes/api.rs
Normal file
103
server/src/routes/api.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::api::comment::*;
|
||||||
|
use crate::api::community::*;
|
||||||
|
use crate::api::post::*;
|
||||||
|
use crate::api::site::*;
|
||||||
|
use crate::api::user::*;
|
||||||
|
use crate::api::{Oper, Perform};
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
|
use diesel::r2d2::{ConnectionManager, Pool};
|
||||||
|
use diesel::PgConnection;
|
||||||
|
use failure::Error;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
type DbParam = web::Data<Pool<ConnectionManager<PgConnection>>>;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg
|
||||||
|
// Site
|
||||||
|
.route("/api/v1/site", web::get().to(route_get::<GetSite, GetSiteResponse>))
|
||||||
|
.route("/api/v1/categories", web::get().to(route_get::<ListCategories, ListCategoriesResponse>))
|
||||||
|
.route("/api/v1/modlog", web::get().to(route_get::<GetModlog, GetModlogResponse>))
|
||||||
|
.route("/api/v1/search", web::get().to(route_get::<Search, SearchResponse>))
|
||||||
|
// Community
|
||||||
|
.route("/api/v1/community", web::post().to(route_post::<CreateCommunity, CommunityResponse>))
|
||||||
|
.route("/api/v1/community", web::get().to(route_get::<GetCommunity, GetCommunityResponse>))
|
||||||
|
.route("/api/v1/community", web::put().to(route_post::<EditCommunity, CommunityResponse>))
|
||||||
|
.route("/api/v1/community/list", web::get().to(route_get::<ListCommunities, ListCommunitiesResponse>))
|
||||||
|
.route("/api/v1/community/follow", web::post().to(route_post::<FollowCommunity, CommunityResponse>))
|
||||||
|
// Post
|
||||||
|
.route("/api/v1/post", web::post().to(route_post::<CreatePost, PostResponse>))
|
||||||
|
.route("/api/v1/post", web::put().to(route_post::<EditPost, PostResponse>))
|
||||||
|
.route("/api/v1/post", web::get().to(route_get::<GetPost, GetPostResponse>))
|
||||||
|
.route("/api/v1/post/list", web::get().to(route_get::<GetPosts, GetPostsResponse>))
|
||||||
|
.route("/api/v1/post/like", web::post().to(route_post::<CreatePostLike, CreatePostLikeResponse>))
|
||||||
|
.route("/api/v1/post/save", web::put().to(route_post::<SavePost, PostResponse>))
|
||||||
|
// Comment
|
||||||
|
.route("/api/v1/comment", web::post().to(route_post::<CreateComment, CommentResponse>))
|
||||||
|
.route("/api/v1/comment", web::put().to(route_post::<EditComment, CommentResponse>))
|
||||||
|
.route("/api/v1/comment/like", web::post().to(route_post::<CreateCommentLike, CommentResponse>))
|
||||||
|
.route("/api/v1/comment/save", web::put().to(route_post::<SaveComment, CommentResponse>))
|
||||||
|
// User
|
||||||
|
.route("/api/v1/user", web::get().to(route_get::<GetUserDetails, GetUserDetailsResponse>))
|
||||||
|
.route("/api/v1/user/mention", web::get().to(route_get::<GetUserMentions, GetUserMentionsResponse>))
|
||||||
|
.route("/api/v1/user/mention", web::put().to(route_post::<EditUserMention, UserMentionResponse>))
|
||||||
|
.route("/api/v1/user/replies", web::get().to(route_get::<GetReplies, GetRepliesResponse>))
|
||||||
|
.route("/api/v1/user/followed_communities", web::get().to(route_get::<GetFollowedCommunities, GetFollowedCommunitiesResponse>))
|
||||||
|
// Mod actions
|
||||||
|
.route("/api/v1/community/transfer", web::post().to(route_post::<TransferCommunity, GetCommunityResponse>))
|
||||||
|
.route("/api/v1/community/ban_user", web::post().to(route_post::<BanFromCommunity, BanFromCommunityResponse>))
|
||||||
|
.route("/api/v1/community/mod", web::post().to(route_post::<AddModToCommunity, AddModToCommunityResponse>))
|
||||||
|
// Admin actions
|
||||||
|
.route("/api/v1/site", web::post().to(route_post::<CreateSite, SiteResponse>))
|
||||||
|
.route("/api/v1/site", web::put().to(route_post::<EditSite, SiteResponse>))
|
||||||
|
.route("/api/v1/site/transfer", web::post().to(route_post::<TransferSite, GetSiteResponse>))
|
||||||
|
.route("/api/v1/admin/add", web::post().to(route_post::<AddAdmin, AddAdminResponse>))
|
||||||
|
.route("/api/v1/user/ban", web::post().to(route_post::<BanUser, BanUserResponse>))
|
||||||
|
// User account actions
|
||||||
|
.route("/api/v1/user/login", web::post().to(route_post::<Login, LoginResponse>))
|
||||||
|
.route("/api/v1/user/register", web::post().to(route_post::<Register, LoginResponse>))
|
||||||
|
.route("/api/v1/user/delete_account", web::post().to(route_post::<DeleteAccount, LoginResponse>))
|
||||||
|
.route("/api/v1/user/password_reset", web::post().to(route_post::<PasswordReset, PasswordResetResponse>))
|
||||||
|
.route("/api/v1/user/password_change", web::post().to(route_post::<PasswordChange, LoginResponse>))
|
||||||
|
.route("/api/v1/user/mark_all_as_read", web::post().to(route_post::<MarkAllAsRead, GetRepliesResponse>))
|
||||||
|
.route("/api/v1/user/save_user_settings", web::put().to(route_post::<SaveUserSettings, LoginResponse>));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform<Request, Response>(data: Request, db: DbParam) -> Result<HttpResponse, Error>
|
||||||
|
where
|
||||||
|
Response: Serialize,
|
||||||
|
Oper<Request>: Perform<Response>,
|
||||||
|
{
|
||||||
|
let conn = match db.get() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(format_err!("{}", e)),
|
||||||
|
};
|
||||||
|
let oper: Oper<Request> = Oper::new(data);
|
||||||
|
let response = oper.perform(&conn);
|
||||||
|
Ok(HttpResponse::Ok().json(response?))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn route_get<Data, Response>(
|
||||||
|
data: web::Query<Data>,
|
||||||
|
db: DbParam,
|
||||||
|
) -> Result<HttpResponse, Error>
|
||||||
|
where
|
||||||
|
Data: Serialize,
|
||||||
|
Response: Serialize,
|
||||||
|
Oper<Data>: Perform<Response>,
|
||||||
|
{
|
||||||
|
perform::<Data, Response>(data.0, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn route_post<Data, Response>(
|
||||||
|
data: web::Json<Data>,
|
||||||
|
db: DbParam,
|
||||||
|
) -> Result<HttpResponse, Error>
|
||||||
|
where
|
||||||
|
Data: Serialize,
|
||||||
|
Response: Serialize,
|
||||||
|
Oper<Data>: Perform<Response>,
|
||||||
|
{
|
||||||
|
perform::<Data, Response>(data.0, db)
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
.route("/login", web::get().to(index))
|
.route("/login", web::get().to(index))
|
||||||
.route("/create_post", web::get().to(index))
|
.route("/create_post", web::get().to(index))
|
||||||
.route("/create_community", web::get().to(index))
|
.route("/create_community", web::get().to(index))
|
||||||
|
.route("/create_private_message", web::get().to(index))
|
||||||
.route("/communities/page/{page}", web::get().to(index))
|
.route("/communities/page/{page}", web::get().to(index))
|
||||||
.route("/communities", web::get().to(index))
|
.route("/communities", web::get().to(index))
|
||||||
.route("/post/{id}/comment/{id2}", web::get().to(index))
|
.route("/post/{id}/comment/{id2}", web::get().to(index))
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod api;
|
||||||
pub mod federation;
|
pub mod federation;
|
||||||
pub mod feeds;
|
pub mod feeds;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
|
|
@ -238,6 +238,19 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
private_message (id) {
|
||||||
|
id -> Int4,
|
||||||
|
creator_id -> Int4,
|
||||||
|
recipient_id -> Int4,
|
||||||
|
content -> Text,
|
||||||
|
deleted -> Bool,
|
||||||
|
read -> Bool,
|
||||||
|
published -> Timestamp,
|
||||||
|
updated -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
site (id) {
|
site (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -272,6 +285,7 @@ table! {
|
||||||
lang -> Varchar,
|
lang -> Varchar,
|
||||||
show_avatars -> Bool,
|
show_avatars -> Bool,
|
||||||
send_notifications_to_email -> Bool,
|
send_notifications_to_email -> Bool,
|
||||||
|
matrix_user_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,6 +371,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
post_like,
|
post_like,
|
||||||
post_read,
|
post_read,
|
||||||
post_saved,
|
post_saved,
|
||||||
|
private_message,
|
||||||
site,
|
site,
|
||||||
user_,
|
user_,
|
||||||
user_ban,
|
user_ban,
|
||||||
|
|
|
@ -1 +1,47 @@
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
|
#[derive(EnumString, ToString, Debug)]
|
||||||
|
pub enum UserOperation {
|
||||||
|
Login,
|
||||||
|
Register,
|
||||||
|
CreateCommunity,
|
||||||
|
CreatePost,
|
||||||
|
ListCommunities,
|
||||||
|
ListCategories,
|
||||||
|
GetPost,
|
||||||
|
GetCommunity,
|
||||||
|
CreateComment,
|
||||||
|
EditComment,
|
||||||
|
SaveComment,
|
||||||
|
CreateCommentLike,
|
||||||
|
GetPosts,
|
||||||
|
CreatePostLike,
|
||||||
|
EditPost,
|
||||||
|
SavePost,
|
||||||
|
EditCommunity,
|
||||||
|
FollowCommunity,
|
||||||
|
GetFollowedCommunities,
|
||||||
|
GetUserDetails,
|
||||||
|
GetReplies,
|
||||||
|
GetUserMentions,
|
||||||
|
EditUserMention,
|
||||||
|
GetModlog,
|
||||||
|
BanFromCommunity,
|
||||||
|
AddModToCommunity,
|
||||||
|
CreateSite,
|
||||||
|
EditSite,
|
||||||
|
GetSite,
|
||||||
|
AddAdmin,
|
||||||
|
BanUser,
|
||||||
|
Search,
|
||||||
|
MarkAllAsRead,
|
||||||
|
SaveUserSettings,
|
||||||
|
TransferCommunity,
|
||||||
|
TransferSite,
|
||||||
|
DeleteAccount,
|
||||||
|
PasswordReset,
|
||||||
|
PasswordChange,
|
||||||
|
CreatePrivateMessage,
|
||||||
|
EditPrivateMessage,
|
||||||
|
GetPrivateMessages,
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! room through `ChatServer`.
|
//! room through `ChatServer`.
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use diesel::r2d2::{ConnectionManager, Pool};
|
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
|
@ -19,6 +19,7 @@ use crate::api::post::*;
|
||||||
use crate::api::site::*;
|
use crate::api::site::*;
|
||||||
use crate::api::user::*;
|
use crate::api::user::*;
|
||||||
use crate::api::*;
|
use crate::api::*;
|
||||||
|
use crate::websocket::UserOperation;
|
||||||
use crate::Settings;
|
use crate::Settings;
|
||||||
|
|
||||||
/// Chat server sends this messages to session
|
/// Chat server sends this messages to session
|
||||||
|
@ -201,7 +202,6 @@ impl ChatServer {
|
||||||
);
|
);
|
||||||
Err(
|
Err(
|
||||||
APIError {
|
APIError {
|
||||||
op: "Rate Limit".to_string(),
|
|
||||||
message: format!("Too many requests. {} per {} seconds", rate, per),
|
message: format!("Too many requests. {} per {} seconds", rate, per),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
@ -295,11 +295,42 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct WebsocketResponse<T> {
|
||||||
|
op: String,
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_json_string<T>(op: &UserOperation, data: T) -> Result<String, Error>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
let response = WebsocketResponse {
|
||||||
|
op: op.to_string(),
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
Ok(serde_json::to_string(&response)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_user_operation<'a, Data, Response>(
|
||||||
|
op: UserOperation,
|
||||||
|
data: &str,
|
||||||
|
conn: &PooledConnection<ConnectionManager<PgConnection>>,
|
||||||
|
) -> Result<String, Error>
|
||||||
|
where
|
||||||
|
for<'de> Data: Deserialize<'de> + 'a,
|
||||||
|
Response: Serialize,
|
||||||
|
Oper<Data>: Perform<Response>,
|
||||||
|
{
|
||||||
|
let parsed_data: Data = serde_json::from_str(data)?;
|
||||||
|
let res = Oper::new(parsed_data).perform(&conn)?;
|
||||||
|
to_json_string(&op, &res)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<String, Error> {
|
fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<String, Error> {
|
||||||
let json: Value = serde_json::from_str(&msg.msg)?;
|
let json: Value = serde_json::from_str(&msg.msg)?;
|
||||||
let data = &json["data"].to_string();
|
let data = &json["data"].to_string();
|
||||||
let op = &json["op"].as_str().ok_or(APIError {
|
let op = &json["op"].as_str().ok_or(APIError {
|
||||||
op: "Unknown op type".to_string(),
|
|
||||||
message: "Unknown op type".to_string(),
|
message: "Unknown op type".to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -307,245 +338,194 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
|
||||||
|
|
||||||
let user_operation: UserOperation = UserOperation::from_str(&op)?;
|
let user_operation: UserOperation = UserOperation::from_str(&op)?;
|
||||||
|
|
||||||
|
// TODO: none of the chat messages are going to work if stuff is submitted via http api,
|
||||||
|
// need to move that handling elsewhere
|
||||||
match user_operation {
|
match user_operation {
|
||||||
UserOperation::Login => {
|
UserOperation::Login => do_user_operation::<Login, LoginResponse>(user_operation, data, &conn),
|
||||||
let login: Login = serde_json::from_str(data)?;
|
|
||||||
let res = Oper::new(user_operation, login).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
|
||||||
UserOperation::Register => {
|
UserOperation::Register => {
|
||||||
let register: Register = serde_json::from_str(data)?;
|
chat.check_rate_limit_register(msg.id)?;
|
||||||
let res = Oper::new(user_operation, register).perform(&conn);
|
do_user_operation::<Register, LoginResponse>(user_operation, data, &conn)
|
||||||
if res.is_ok() {
|
|
||||||
chat.check_rate_limit_register(msg.id)?;
|
|
||||||
}
|
|
||||||
Ok(serde_json::to_string(&res?)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::GetUserDetails => {
|
UserOperation::GetUserDetails => {
|
||||||
let get_user_details: GetUserDetails = serde_json::from_str(data)?;
|
do_user_operation::<GetUserDetails, GetUserDetailsResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, get_user_details).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::SaveUserSettings => {
|
UserOperation::SaveUserSettings => {
|
||||||
let save_user_settings: SaveUserSettings = serde_json::from_str(data)?;
|
do_user_operation::<SaveUserSettings, LoginResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, save_user_settings).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::AddAdmin => {
|
UserOperation::AddAdmin => {
|
||||||
let add_admin: AddAdmin = serde_json::from_str(data)?;
|
do_user_operation::<AddAdmin, AddAdminResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, add_admin).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::BanUser => {
|
UserOperation::BanUser => {
|
||||||
let ban_user: BanUser = serde_json::from_str(data)?;
|
do_user_operation::<BanUser, BanUserResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, ban_user).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::GetReplies => {
|
UserOperation::GetReplies => {
|
||||||
let get_replies: GetReplies = serde_json::from_str(data)?;
|
do_user_operation::<GetReplies, GetRepliesResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, get_replies).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::GetUserMentions => {
|
UserOperation::GetUserMentions => {
|
||||||
let get_user_mentions: GetUserMentions = serde_json::from_str(data)?;
|
do_user_operation::<GetUserMentions, GetUserMentionsResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, get_user_mentions).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::EditUserMention => {
|
UserOperation::EditUserMention => {
|
||||||
let edit_user_mention: EditUserMention = serde_json::from_str(data)?;
|
do_user_operation::<EditUserMention, UserMentionResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, edit_user_mention).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::MarkAllAsRead => {
|
UserOperation::MarkAllAsRead => {
|
||||||
let mark_all_as_read: MarkAllAsRead = serde_json::from_str(data)?;
|
do_user_operation::<MarkAllAsRead, GetRepliesResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, mark_all_as_read).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::GetCommunity => {
|
UserOperation::GetCommunity => {
|
||||||
let get_community: GetCommunity = serde_json::from_str(data)?;
|
do_user_operation::<GetCommunity, GetCommunityResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, get_community).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::ListCommunities => {
|
UserOperation::ListCommunities => {
|
||||||
let list_communities: ListCommunities = serde_json::from_str(data)?;
|
do_user_operation::<ListCommunities, ListCommunitiesResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, list_communities).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::CreateCommunity => {
|
UserOperation::CreateCommunity => {
|
||||||
chat.check_rate_limit_register(msg.id)?;
|
chat.check_rate_limit_register(msg.id)?;
|
||||||
let create_community: CreateCommunity = serde_json::from_str(data)?;
|
do_user_operation::<CreateCommunity, CommunityResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, create_community).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::EditCommunity => {
|
UserOperation::EditCommunity => {
|
||||||
let edit_community: EditCommunity = serde_json::from_str(data)?;
|
let edit_community: EditCommunity = serde_json::from_str(data)?;
|
||||||
let res = Oper::new(user_operation, edit_community).perform(&conn)?;
|
let res = Oper::new(edit_community).perform(&conn)?;
|
||||||
let mut community_sent: CommunityResponse = res.clone();
|
let mut community_sent: CommunityResponse = res.clone();
|
||||||
community_sent.community.user_id = None;
|
community_sent.community.user_id = None;
|
||||||
community_sent.community.subscribed = None;
|
community_sent.community.subscribed = None;
|
||||||
let community_sent_str = serde_json::to_string(&community_sent)?;
|
let community_sent_str = to_json_string(&user_operation, &community_sent)?;
|
||||||
chat.send_community_message(community_sent.community.id, &community_sent_str, msg.id)?;
|
chat.send_community_message(community_sent.community.id, &community_sent_str, msg.id)?;
|
||||||
Ok(serde_json::to_string(&res)?)
|
to_json_string(&user_operation, &res)
|
||||||
}
|
}
|
||||||
UserOperation::FollowCommunity => {
|
UserOperation::FollowCommunity => {
|
||||||
let follow_community: FollowCommunity = serde_json::from_str(data)?;
|
do_user_operation::<FollowCommunity, CommunityResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, follow_community).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
|
||||||
UserOperation::GetFollowedCommunities => {
|
|
||||||
let followed_communities: GetFollowedCommunities = serde_json::from_str(data)?;
|
|
||||||
let res = Oper::new(user_operation, followed_communities).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
|
UserOperation::GetFollowedCommunities => do_user_operation::<
|
||||||
|
GetFollowedCommunities,
|
||||||
|
GetFollowedCommunitiesResponse,
|
||||||
|
>(user_operation, data, &conn),
|
||||||
UserOperation::BanFromCommunity => {
|
UserOperation::BanFromCommunity => {
|
||||||
let ban_from_community: BanFromCommunity = serde_json::from_str(data)?;
|
let ban_from_community: BanFromCommunity = serde_json::from_str(data)?;
|
||||||
let community_id = ban_from_community.community_id;
|
let community_id = ban_from_community.community_id;
|
||||||
let res = Oper::new(user_operation, ban_from_community).perform(&conn)?;
|
let res = Oper::new(ban_from_community).perform(&conn)?;
|
||||||
let res_str = serde_json::to_string(&res)?;
|
let res_str = to_json_string(&user_operation, &res)?;
|
||||||
chat.send_community_message(community_id, &res_str, msg.id)?;
|
chat.send_community_message(community_id, &res_str, msg.id)?;
|
||||||
Ok(res_str)
|
Ok(res_str)
|
||||||
}
|
}
|
||||||
UserOperation::AddModToCommunity => {
|
UserOperation::AddModToCommunity => {
|
||||||
let mod_add_to_community: AddModToCommunity = serde_json::from_str(data)?;
|
let mod_add_to_community: AddModToCommunity = serde_json::from_str(data)?;
|
||||||
let community_id = mod_add_to_community.community_id;
|
let community_id = mod_add_to_community.community_id;
|
||||||
let res = Oper::new(user_operation, mod_add_to_community).perform(&conn)?;
|
let res = Oper::new(mod_add_to_community).perform(&conn)?;
|
||||||
let res_str = serde_json::to_string(&res)?;
|
let res_str = to_json_string(&user_operation, &res)?;
|
||||||
chat.send_community_message(community_id, &res_str, msg.id)?;
|
chat.send_community_message(community_id, &res_str, msg.id)?;
|
||||||
Ok(res_str)
|
Ok(res_str)
|
||||||
}
|
}
|
||||||
UserOperation::ListCategories => {
|
UserOperation::ListCategories => {
|
||||||
let list_categories: ListCategories = ListCategories;
|
do_user_operation::<ListCategories, ListCategoriesResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, list_categories).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::CreatePost => {
|
UserOperation::CreatePost => {
|
||||||
chat.check_rate_limit_post(msg.id)?;
|
chat.check_rate_limit_post(msg.id)?;
|
||||||
let create_post: CreatePost = serde_json::from_str(data)?;
|
do_user_operation::<CreatePost, PostResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, create_post).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::GetPost => {
|
UserOperation::GetPost => {
|
||||||
let get_post: GetPost = serde_json::from_str(data)?;
|
let get_post: GetPost = serde_json::from_str(data)?;
|
||||||
chat.join_room(get_post.id, msg.id);
|
chat.join_room(get_post.id, msg.id);
|
||||||
let res = Oper::new(user_operation, get_post).perform(&conn)?;
|
let res = Oper::new(get_post).perform(&conn)?;
|
||||||
Ok(serde_json::to_string(&res)?)
|
to_json_string(&user_operation, &res)
|
||||||
}
|
}
|
||||||
UserOperation::GetPosts => {
|
UserOperation::GetPosts => {
|
||||||
let get_posts: GetPosts = serde_json::from_str(data)?;
|
do_user_operation::<GetPosts, GetPostsResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, get_posts).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::CreatePostLike => {
|
UserOperation::CreatePostLike => {
|
||||||
chat.check_rate_limit_message(msg.id)?;
|
chat.check_rate_limit_message(msg.id)?;
|
||||||
let create_post_like: CreatePostLike = serde_json::from_str(data)?;
|
do_user_operation::<CreatePostLike, CreatePostLikeResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, create_post_like).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::EditPost => {
|
UserOperation::EditPost => {
|
||||||
let edit_post: EditPost = serde_json::from_str(data)?;
|
let edit_post: EditPost = serde_json::from_str(data)?;
|
||||||
let res = Oper::new(user_operation, edit_post).perform(&conn)?;
|
let res = Oper::new(edit_post).perform(&conn)?;
|
||||||
let mut post_sent = res.clone();
|
let mut post_sent = res.clone();
|
||||||
post_sent.post.my_vote = None;
|
post_sent.post.my_vote = None;
|
||||||
let post_sent_str = serde_json::to_string(&post_sent)?;
|
let post_sent_str = to_json_string(&user_operation, &post_sent)?;
|
||||||
chat.send_room_message(post_sent.post.id, &post_sent_str, msg.id);
|
chat.send_room_message(post_sent.post.id, &post_sent_str, msg.id);
|
||||||
Ok(serde_json::to_string(&res)?)
|
to_json_string(&user_operation, &res)
|
||||||
}
|
}
|
||||||
UserOperation::SavePost => {
|
UserOperation::SavePost => {
|
||||||
let save_post: SavePost = serde_json::from_str(data)?;
|
do_user_operation::<SavePost, PostResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, save_post).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::CreateComment => {
|
UserOperation::CreateComment => {
|
||||||
chat.check_rate_limit_message(msg.id)?;
|
chat.check_rate_limit_message(msg.id)?;
|
||||||
let create_comment: CreateComment = serde_json::from_str(data)?;
|
let create_comment: CreateComment = serde_json::from_str(data)?;
|
||||||
let post_id = create_comment.post_id;
|
let post_id = create_comment.post_id;
|
||||||
let res = Oper::new(user_operation, create_comment).perform(&conn)?;
|
let res = Oper::new(create_comment).perform(&conn)?;
|
||||||
let mut comment_sent = res.clone();
|
let mut comment_sent = res.clone();
|
||||||
comment_sent.comment.my_vote = None;
|
comment_sent.comment.my_vote = None;
|
||||||
comment_sent.comment.user_id = None;
|
comment_sent.comment.user_id = None;
|
||||||
let comment_sent_str = serde_json::to_string(&comment_sent)?;
|
let comment_sent_str = to_json_string(&user_operation, &comment_sent)?;
|
||||||
chat.send_room_message(post_id, &comment_sent_str, msg.id);
|
chat.send_room_message(post_id, &comment_sent_str, msg.id);
|
||||||
Ok(serde_json::to_string(&res)?)
|
to_json_string(&user_operation, &res)
|
||||||
}
|
}
|
||||||
UserOperation::EditComment => {
|
UserOperation::EditComment => {
|
||||||
let edit_comment: EditComment = serde_json::from_str(data)?;
|
let edit_comment: EditComment = serde_json::from_str(data)?;
|
||||||
let post_id = edit_comment.post_id;
|
let post_id = edit_comment.post_id;
|
||||||
let res = Oper::new(user_operation, edit_comment).perform(&conn)?;
|
let res = Oper::new(edit_comment).perform(&conn)?;
|
||||||
let mut comment_sent = res.clone();
|
let mut comment_sent = res.clone();
|
||||||
comment_sent.comment.my_vote = None;
|
comment_sent.comment.my_vote = None;
|
||||||
comment_sent.comment.user_id = None;
|
comment_sent.comment.user_id = None;
|
||||||
let comment_sent_str = serde_json::to_string(&comment_sent)?;
|
let comment_sent_str = to_json_string(&user_operation, &comment_sent)?;
|
||||||
chat.send_room_message(post_id, &comment_sent_str, msg.id);
|
chat.send_room_message(post_id, &comment_sent_str, msg.id);
|
||||||
Ok(serde_json::to_string(&res)?)
|
to_json_string(&user_operation, &res)
|
||||||
}
|
}
|
||||||
UserOperation::SaveComment => {
|
UserOperation::SaveComment => {
|
||||||
let save_comment: SaveComment = serde_json::from_str(data)?;
|
do_user_operation::<SaveComment, CommentResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, save_comment).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::CreateCommentLike => {
|
UserOperation::CreateCommentLike => {
|
||||||
chat.check_rate_limit_message(msg.id)?;
|
chat.check_rate_limit_message(msg.id)?;
|
||||||
let create_comment_like: CreateCommentLike = serde_json::from_str(data)?;
|
let create_comment_like: CreateCommentLike = serde_json::from_str(data)?;
|
||||||
let post_id = create_comment_like.post_id;
|
let post_id = create_comment_like.post_id;
|
||||||
let res = Oper::new(user_operation, create_comment_like).perform(&conn)?;
|
let res = Oper::new(create_comment_like).perform(&conn)?;
|
||||||
let mut comment_sent = res.clone();
|
let mut comment_sent = res.clone();
|
||||||
comment_sent.comment.my_vote = None;
|
comment_sent.comment.my_vote = None;
|
||||||
comment_sent.comment.user_id = None;
|
comment_sent.comment.user_id = None;
|
||||||
let comment_sent_str = serde_json::to_string(&comment_sent)?;
|
let comment_sent_str = to_json_string(&user_operation, &comment_sent)?;
|
||||||
chat.send_room_message(post_id, &comment_sent_str, msg.id);
|
chat.send_room_message(post_id, &comment_sent_str, msg.id);
|
||||||
Ok(serde_json::to_string(&res)?)
|
to_json_string(&user_operation, &res)
|
||||||
}
|
}
|
||||||
UserOperation::GetModlog => {
|
UserOperation::GetModlog => {
|
||||||
let get_modlog: GetModlog = serde_json::from_str(data)?;
|
do_user_operation::<GetModlog, GetModlogResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, get_modlog).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::CreateSite => {
|
UserOperation::CreateSite => {
|
||||||
let create_site: CreateSite = serde_json::from_str(data)?;
|
do_user_operation::<CreateSite, SiteResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, create_site).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::EditSite => {
|
UserOperation::EditSite => {
|
||||||
let edit_site: EditSite = serde_json::from_str(data)?;
|
do_user_operation::<EditSite, SiteResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, edit_site).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::GetSite => {
|
UserOperation::GetSite => {
|
||||||
let online: usize = chat.sessions.len();
|
let online: usize = chat.sessions.len();
|
||||||
let get_site: GetSite = serde_json::from_str(data)?;
|
let get_site: GetSite = serde_json::from_str(data)?;
|
||||||
let mut res = Oper::new(user_operation, get_site).perform(&conn)?;
|
let mut res = Oper::new(get_site).perform(&conn)?;
|
||||||
res.online = online;
|
res.online = online;
|
||||||
Ok(serde_json::to_string(&res)?)
|
to_json_string(&user_operation, &res)
|
||||||
}
|
}
|
||||||
UserOperation::Search => {
|
UserOperation::Search => {
|
||||||
let search: Search = serde_json::from_str(data)?;
|
do_user_operation::<Search, SearchResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, search).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::TransferCommunity => {
|
UserOperation::TransferCommunity => {
|
||||||
let transfer_community: TransferCommunity = serde_json::from_str(data)?;
|
do_user_operation::<TransferCommunity, GetCommunityResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, transfer_community).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::TransferSite => {
|
UserOperation::TransferSite => {
|
||||||
let transfer_site: TransferSite = serde_json::from_str(data)?;
|
do_user_operation::<TransferSite, GetSiteResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, transfer_site).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::DeleteAccount => {
|
UserOperation::DeleteAccount => {
|
||||||
let delete_account: DeleteAccount = serde_json::from_str(data)?;
|
do_user_operation::<DeleteAccount, LoginResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, delete_account).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::PasswordReset => {
|
UserOperation::PasswordReset => {
|
||||||
let password_reset: PasswordReset = serde_json::from_str(data)?;
|
do_user_operation::<PasswordReset, PasswordResetResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, password_reset).perform(&conn)?;
|
|
||||||
Ok(serde_json::to_string(&res)?)
|
|
||||||
}
|
}
|
||||||
UserOperation::PasswordChange => {
|
UserOperation::PasswordChange => {
|
||||||
let password_change: PasswordChange = serde_json::from_str(data)?;
|
do_user_operation::<PasswordChange, LoginResponse>(user_operation, data, &conn)
|
||||||
let res = Oper::new(user_operation, password_change).perform(&conn)?;
|
}
|
||||||
Ok(serde_json::to_string(&res)?)
|
UserOperation::CreatePrivateMessage => {
|
||||||
|
chat.check_rate_limit_message(msg.id)?;
|
||||||
|
do_user_operation::<CreatePrivateMessage, PrivateMessageResponse>(user_operation, data, &conn)
|
||||||
|
}
|
||||||
|
UserOperation::EditPrivateMessage => {
|
||||||
|
do_user_operation::<EditPrivateMessage, PrivateMessageResponse>(user_operation, data, &conn)
|
||||||
|
}
|
||||||
|
UserOperation::GetPrivateMessages => {
|
||||||
|
do_user_operation::<GetPrivateMessages, PrivateMessagesResponse>(user_operation, data, &conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
78
ui/assets/css/toastify.css
vendored
Normal file
78
ui/assets/css/toastify.css
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*!
|
||||||
|
* Toastify js 1.6.2
|
||||||
|
* https://github.com/apvarun/toastify-js
|
||||||
|
* @license MIT licensed
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Varun A P
|
||||||
|
*/
|
||||||
|
|
||||||
|
.toastify {
|
||||||
|
padding: 12px 20px;
|
||||||
|
color: #ffffff;
|
||||||
|
display: inline-block;
|
||||||
|
box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(77, 96, 232, 0.3);
|
||||||
|
background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);
|
||||||
|
background: linear-gradient(135deg, #73a5ff, #5477f5);
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
max-width: calc(50% - 20px);
|
||||||
|
z-index: 2147483647;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify.on {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-close {
|
||||||
|
opacity: 0.4;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify-right {
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify-left {
|
||||||
|
left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify-top {
|
||||||
|
top: -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify-bottom {
|
||||||
|
bottom: -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify-rounded {
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify-avatar {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
margin: 0 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastify-center {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
max-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 360px) {
|
||||||
|
.toastify-right, .toastify-left {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
max-width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
1
ui/package.json
vendored
1
ui/package.json
vendored
|
@ -36,6 +36,7 @@
|
||||||
"prettier": "^1.18.2",
|
"prettier": "^1.18.2",
|
||||||
"rxjs": "^6.4.0",
|
"rxjs": "^6.4.0",
|
||||||
"terser": "^4.6.0",
|
"terser": "^4.6.0",
|
||||||
|
"toastify-js": "^1.6.2",
|
||||||
"tributejs": "^4.1.1",
|
"tributejs": "^4.1.1",
|
||||||
"twemoji": "^12.1.2",
|
"twemoji": "^12.1.2",
|
||||||
"ws": "^7.0.0"
|
"ws": "^7.0.0"
|
||||||
|
|
21
ui/src/components/comment-form.tsx
vendored
21
ui/src/components/comment-form.tsx
vendored
|
@ -10,12 +10,13 @@ import {
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
|
wsJsonToRes,
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
mentionDropdownFetchLimit,
|
mentionDropdownFetchLimit,
|
||||||
msgOp,
|
|
||||||
mdToHtml,
|
mdToHtml,
|
||||||
randomStr,
|
randomStr,
|
||||||
markdownHelpUrl,
|
markdownHelpUrl,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
|
@ -293,7 +294,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
i.state.imageLoading = false;
|
i.state.imageLoading = false;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
alert(error);
|
toast(error, 'danger');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,10 +312,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
|
|
||||||
this.userSub = WebSocketService.Instance.subject.subscribe(
|
this.userSub = WebSocketService.Instance.subject.subscribe(
|
||||||
msg => {
|
msg => {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (op == UserOperation.Search) {
|
if (res.op == UserOperation.Search) {
|
||||||
let res: SearchResponse = msg;
|
let data = res.data as SearchResponse;
|
||||||
let users = res.users.map(u => {
|
let users = data.users.map(u => {
|
||||||
return { key: u.name };
|
return { key: u.name };
|
||||||
});
|
});
|
||||||
cb(users);
|
cb(users);
|
||||||
|
@ -343,10 +344,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
|
|
||||||
this.communitySub = WebSocketService.Instance.subject.subscribe(
|
this.communitySub = WebSocketService.Instance.subject.subscribe(
|
||||||
msg => {
|
msg => {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (op == UserOperation.Search) {
|
if (res.op == UserOperation.Search) {
|
||||||
let res: SearchResponse = msg;
|
let data = res.data as SearchResponse;
|
||||||
let communities = res.communities.map(u => {
|
let communities = data.communities.map(u => {
|
||||||
return { key: u.name };
|
return { key: u.name };
|
||||||
});
|
});
|
||||||
cb(communities);
|
cb(communities);
|
||||||
|
|
10
ui/src/components/comment-node.tsx
vendored
10
ui/src/components/comment-node.tsx
vendored
|
@ -293,6 +293,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{!this.myComment && (
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<Link
|
||||||
|
class="text-muted"
|
||||||
|
to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
|
||||||
|
>
|
||||||
|
{i18n.t('message').toLowerCase()}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
<li className="list-inline-item">•</li>
|
<li className="list-inline-item">•</li>
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<span
|
<span
|
||||||
|
|
27
ui/src/components/communities.tsx
vendored
27
ui/src/components/communities.tsx
vendored
|
@ -10,9 +10,10 @@ import {
|
||||||
FollowCommunityForm,
|
FollowCommunityForm,
|
||||||
ListCommunitiesForm,
|
ListCommunitiesForm,
|
||||||
SortType,
|
SortType,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { wsJsonToRes, toast } from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -231,15 +232,15 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
WebSocketService.Instance.listCommunities(listCommunitiesForm);
|
WebSocketService.Instance.listCommunities(listCommunitiesForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.ListCommunities) {
|
} else if (res.op == UserOperation.ListCommunities) {
|
||||||
let res: ListCommunitiesResponse = msg;
|
let data = res.data as ListCommunitiesResponse;
|
||||||
this.state.communities = res.communities;
|
this.state.communities = data.communities;
|
||||||
this.state.communities.sort(
|
this.state.communities.sort(
|
||||||
(a, b) => b.number_of_subscribers - a.number_of_subscribers
|
(a, b) => b.number_of_subscribers - a.number_of_subscribers
|
||||||
);
|
);
|
||||||
|
@ -248,11 +249,11 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
let table = document.querySelector('#community_table');
|
let table = document.querySelector('#community_table');
|
||||||
Sortable.initTable(table);
|
Sortable.initTable(table);
|
||||||
} else if (op == UserOperation.FollowCommunity) {
|
} else if (res.op == UserOperation.FollowCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let data = res.data as CommunityResponse;
|
||||||
let found = this.state.communities.find(c => c.id == res.community.id);
|
let found = this.state.communities.find(c => c.id == data.community.id);
|
||||||
found.subscribed = res.community.subscribed;
|
found.subscribed = data.community.subscribed;
|
||||||
found.number_of_subscribers = res.community.number_of_subscribers;
|
found.number_of_subscribers = data.community.number_of_subscribers;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
50
ui/src/components/community-form.tsx
vendored
50
ui/src/components/community-form.tsx
vendored
|
@ -8,10 +8,11 @@ import {
|
||||||
ListCategoriesResponse,
|
ListCategoriesResponse,
|
||||||
CommunityResponse,
|
CommunityResponse,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { msgOp, capitalizeFirstLetter } from '../utils';
|
import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils';
|
||||||
import * as autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -67,14 +68,7 @@ export class CommunityForm extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
retryWhen(errors =>
|
|
||||||
errors.pipe(
|
|
||||||
delay(3000),
|
|
||||||
take(10)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
msg => this.parseMessage(msg),
|
msg => this.parseMessage(msg),
|
||||||
err => console.error(err),
|
err => console.error(err),
|
||||||
|
@ -246,34 +240,34 @@ export class CommunityForm extends Component<
|
||||||
i.props.onCancel();
|
i.props.onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.ListCategories) {
|
} else if (res.op == UserOperation.ListCategories) {
|
||||||
let res: ListCategoriesResponse = msg;
|
let data = res.data as ListCategoriesResponse;
|
||||||
this.state.categories = res.categories;
|
this.state.categories = data.categories;
|
||||||
if (!this.props.community) {
|
if (!this.props.community) {
|
||||||
this.state.communityForm.category_id = res.categories[0].id;
|
this.state.communityForm.category_id = data.categories[0].id;
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateCommunity) {
|
} else if (res.op == UserOperation.CreateCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.props.onCreate(res.community);
|
this.props.onCreate(data.community);
|
||||||
}
|
}
|
||||||
// TODO is ths necessary
|
// TODO is this necessary
|
||||||
else if (op == UserOperation.EditCommunity) {
|
else if (res.op == UserOperation.EditCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.props.onEdit(res.community);
|
this.props.onEdit(data.community);
|
||||||
} else if (op == UserOperation.GetSite) {
|
} else if (res.op == UserOperation.GetSite) {
|
||||||
let res: GetSiteResponse = msg;
|
let data = res.data as GetSiteResponse;
|
||||||
this.state.enable_nsfw = res.site.enable_nsfw;
|
this.state.enable_nsfw = data.site.enable_nsfw;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
56
ui/src/components/community.tsx
vendored
56
ui/src/components/community.tsx
vendored
|
@ -14,16 +14,18 @@ import {
|
||||||
ListingType,
|
ListingType,
|
||||||
GetPostsResponse,
|
GetPostsResponse,
|
||||||
CreatePostLikeResponse,
|
CreatePostLikeResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { PostListings } from './post-listings';
|
import { PostListings } from './post-listings';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
import { Sidebar } from './sidebar';
|
import { Sidebar } from './sidebar';
|
||||||
import {
|
import {
|
||||||
msgOp,
|
wsJsonToRes,
|
||||||
routeSortTypeToEnum,
|
routeSortTypeToEnum,
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
postRefetchSeconds,
|
postRefetchSeconds,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
@ -253,43 +255,43 @@ export class Community extends Component<any, State> {
|
||||||
WebSocketService.Instance.getPosts(getPostsForm);
|
WebSocketService.Instance.getPosts(getPostsForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.context.router.history.push('/');
|
this.context.router.history.push('/');
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.GetCommunity) {
|
} else if (res.op == UserOperation.GetCommunity) {
|
||||||
let res: GetCommunityResponse = msg;
|
let data = res.data as GetCommunityResponse;
|
||||||
this.state.community = res.community;
|
this.state.community = data.community;
|
||||||
this.state.moderators = res.moderators;
|
this.state.moderators = data.moderators;
|
||||||
this.state.admins = res.admins;
|
this.state.admins = data.admins;
|
||||||
document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`;
|
document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
this.keepFetchingPosts();
|
this.keepFetchingPosts();
|
||||||
} else if (op == UserOperation.EditCommunity) {
|
} else if (res.op == UserOperation.EditCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.community = res.community;
|
this.state.community = data.community;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.FollowCommunity) {
|
} else if (res.op == UserOperation.FollowCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.community.subscribed = res.community.subscribed;
|
this.state.community.subscribed = data.community.subscribed;
|
||||||
this.state.community.number_of_subscribers =
|
this.state.community.number_of_subscribers =
|
||||||
res.community.number_of_subscribers;
|
data.community.number_of_subscribers;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.GetPosts) {
|
} else if (res.op == UserOperation.GetPosts) {
|
||||||
let res: GetPostsResponse = msg;
|
let data = res.data as GetPostsResponse;
|
||||||
this.state.posts = res.posts;
|
this.state.posts = data.posts;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePostLike) {
|
} else if (res.op == UserOperation.CreatePostLike) {
|
||||||
let res: CreatePostLikeResponse = msg;
|
let data = res.data as CreatePostLikeResponse;
|
||||||
let found = this.state.posts.find(c => c.id == res.post.id);
|
let found = this.state.posts.find(c => c.id == data.post.id);
|
||||||
found.my_vote = res.post.my_vote;
|
found.my_vote = data.post.my_vote;
|
||||||
found.score = res.post.score;
|
found.score = data.post.score;
|
||||||
found.upvotes = res.post.upvotes;
|
found.upvotes = data.post.upvotes;
|
||||||
found.downvotes = res.post.downvotes;
|
found.downvotes = data.post.downvotes;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
53
ui/src/components/create-private-message.tsx
vendored
Normal file
53
ui/src/components/create-private-message.tsx
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { Component } from 'inferno';
|
||||||
|
import { PrivateMessageForm } from './private-message-form';
|
||||||
|
import { WebSocketService } from '../services';
|
||||||
|
import { PrivateMessageFormParams } from '../interfaces';
|
||||||
|
import { toast } from '../utils';
|
||||||
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
|
export class CreatePrivateMessage extends Component<any, any> {
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.title = `${i18n.t('create_private_message')} - ${
|
||||||
|
WebSocketService.Instance.site.name
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
|
||||||
|
<h5>{i18n.t('create_private_message')}</h5>
|
||||||
|
<PrivateMessageForm
|
||||||
|
onCreate={this.handlePrivateMessageCreate}
|
||||||
|
params={this.params}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get params(): PrivateMessageFormParams {
|
||||||
|
let urlParams = new URLSearchParams(this.props.location.search);
|
||||||
|
let params: PrivateMessageFormParams = {
|
||||||
|
recipient_id: Number(urlParams.get('recipient_id')),
|
||||||
|
};
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrivateMessageCreate() {
|
||||||
|
toast(i18n.t('message_sent'));
|
||||||
|
|
||||||
|
// Navigate to the front
|
||||||
|
this.props.history.push(`/`);
|
||||||
|
}
|
||||||
|
}
|
218
ui/src/components/inbox.tsx
vendored
218
ui/src/components/inbox.tsx
vendored
|
@ -12,10 +12,16 @@ import {
|
||||||
GetUserMentionsResponse,
|
GetUserMentionsResponse,
|
||||||
UserMentionResponse,
|
UserMentionResponse,
|
||||||
CommentResponse,
|
CommentResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
|
PrivateMessage as PrivateMessageI,
|
||||||
|
GetPrivateMessagesForm,
|
||||||
|
PrivateMessagesResponse,
|
||||||
|
PrivateMessageResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp, fetchLimit } from '../utils';
|
import { wsJsonToRes, fetchLimit, isCommentType, toast } from '../utils';
|
||||||
import { CommentNodes } from './comment-nodes';
|
import { CommentNodes } from './comment-nodes';
|
||||||
|
import { PrivateMessage } from './private-message';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -26,9 +32,10 @@ enum UnreadOrAll {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UnreadType {
|
enum UnreadType {
|
||||||
Both,
|
All,
|
||||||
Replies,
|
Replies,
|
||||||
Mentions,
|
Mentions,
|
||||||
|
Messages,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InboxState {
|
interface InboxState {
|
||||||
|
@ -36,6 +43,7 @@ interface InboxState {
|
||||||
unreadType: UnreadType;
|
unreadType: UnreadType;
|
||||||
replies: Array<Comment>;
|
replies: Array<Comment>;
|
||||||
mentions: Array<Comment>;
|
mentions: Array<Comment>;
|
||||||
|
messages: Array<PrivateMessageI>;
|
||||||
sort: SortType;
|
sort: SortType;
|
||||||
page: number;
|
page: number;
|
||||||
}
|
}
|
||||||
|
@ -44,9 +52,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: InboxState = {
|
private emptyState: InboxState = {
|
||||||
unreadOrAll: UnreadOrAll.Unread,
|
unreadOrAll: UnreadOrAll.Unread,
|
||||||
unreadType: UnreadType.Both,
|
unreadType: UnreadType.All,
|
||||||
replies: [],
|
replies: [],
|
||||||
mentions: [],
|
mentions: [],
|
||||||
|
messages: [],
|
||||||
sort: SortType.New,
|
sort: SortType.New,
|
||||||
page: 1,
|
page: 1,
|
||||||
};
|
};
|
||||||
|
@ -103,7 +112,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
</a>
|
</a>
|
||||||
</small>
|
</small>
|
||||||
</h5>
|
</h5>
|
||||||
{this.state.replies.length + this.state.mentions.length > 0 &&
|
{this.state.replies.length +
|
||||||
|
this.state.mentions.length +
|
||||||
|
this.state.messages.length >
|
||||||
|
0 &&
|
||||||
this.state.unreadOrAll == UnreadOrAll.Unread && (
|
this.state.unreadOrAll == UnreadOrAll.Unread && (
|
||||||
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
|
@ -114,9 +126,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
{this.selects()}
|
{this.selects()}
|
||||||
{this.state.unreadType == UnreadType.Both && this.both()}
|
{this.state.unreadType == UnreadType.All && this.all()}
|
||||||
{this.state.unreadType == UnreadType.Replies && this.replies()}
|
{this.state.unreadType == UnreadType.Replies && this.replies()}
|
||||||
{this.state.unreadType == UnreadType.Mentions && this.mentions()}
|
{this.state.unreadType == UnreadType.Mentions && this.mentions()}
|
||||||
|
{this.state.unreadType == UnreadType.Messages && this.messages()}
|
||||||
{this.paginator()}
|
{this.paginator()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -150,8 +163,8 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
<option disabled>
|
<option disabled>
|
||||||
<T i18nKey="type">#</T>
|
<T i18nKey="type">#</T>
|
||||||
</option>
|
</option>
|
||||||
<option value={UnreadType.Both}>
|
<option value={UnreadType.All}>
|
||||||
<T i18nKey="both">#</T>
|
<T i18nKey="all">#</T>
|
||||||
</option>
|
</option>
|
||||||
<option value={UnreadType.Replies}>
|
<option value={UnreadType.Replies}>
|
||||||
<T i18nKey="replies">#</T>
|
<T i18nKey="replies">#</T>
|
||||||
|
@ -159,6 +172,9 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
<option value={UnreadType.Mentions}>
|
<option value={UnreadType.Mentions}>
|
||||||
<T i18nKey="mentions">#</T>
|
<T i18nKey="mentions">#</T>
|
||||||
</option>
|
</option>
|
||||||
|
<option value={UnreadType.Messages}>
|
||||||
|
<T i18nKey="messages">#</T>
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<SortSelect
|
<SortSelect
|
||||||
sort={this.state.sort}
|
sort={this.state.sort}
|
||||||
|
@ -169,33 +185,25 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
both() {
|
all() {
|
||||||
let combined: Array<{
|
let combined: Array<Comment | PrivateMessageI> = [];
|
||||||
type_: string;
|
|
||||||
data: Comment;
|
|
||||||
}> = [];
|
|
||||||
let replies = this.state.replies.map(e => {
|
|
||||||
return { type_: 'replies', data: e };
|
|
||||||
});
|
|
||||||
let mentions = this.state.mentions.map(e => {
|
|
||||||
return { type_: 'mentions', data: e };
|
|
||||||
});
|
|
||||||
|
|
||||||
combined.push(...replies);
|
combined.push(...this.state.replies);
|
||||||
combined.push(...mentions);
|
combined.push(...this.state.mentions);
|
||||||
|
combined.push(...this.state.messages);
|
||||||
|
|
||||||
// Sort it
|
// Sort it
|
||||||
if (this.state.sort == SortType.New) {
|
combined.sort((a, b) => b.published.localeCompare(a.published));
|
||||||
combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
|
|
||||||
} else {
|
|
||||||
combined.sort((a, b) => b.data.score - a.data.score);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{combined.map(i => (
|
{combined.map(i =>
|
||||||
<CommentNodes nodes={[{ comment: i.data }]} noIndent markable />
|
isCommentType(i) ? (
|
||||||
))}
|
<CommentNodes nodes={[{ comment: i }]} noIndent markable />
|
||||||
|
) : (
|
||||||
|
<PrivateMessage privateMessage={i} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -220,6 +228,16 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messages() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.state.messages.map(message => (
|
||||||
|
<PrivateMessage privateMessage={message} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
paginator() {
|
paginator() {
|
||||||
return (
|
return (
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
|
@ -283,6 +301,13 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
};
|
};
|
||||||
WebSocketService.Instance.getUserMentions(userMentionsForm);
|
WebSocketService.Instance.getUserMentions(userMentionsForm);
|
||||||
|
|
||||||
|
let privateMessagesForm: GetPrivateMessagesForm = {
|
||||||
|
unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
|
||||||
|
page: this.state.page,
|
||||||
|
limit: fetchLimit,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSortChange(val: SortType) {
|
handleSortChange(val: SortType) {
|
||||||
|
@ -296,94 +321,122 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
WebSocketService.Instance.markAllAsRead();
|
WebSocketService.Instance.markAllAsRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.GetReplies) {
|
} else if (res.op == UserOperation.GetReplies) {
|
||||||
let res: GetRepliesResponse = msg;
|
let data = res.data as GetRepliesResponse;
|
||||||
this.state.replies = res.replies;
|
this.state.replies = data.replies;
|
||||||
this.sendUnreadCount();
|
this.sendUnreadCount();
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.GetUserMentions) {
|
} else if (res.op == UserOperation.GetUserMentions) {
|
||||||
let res: GetUserMentionsResponse = msg;
|
let data = res.data as GetUserMentionsResponse;
|
||||||
this.state.mentions = res.mentions;
|
this.state.mentions = data.mentions;
|
||||||
this.sendUnreadCount();
|
this.sendUnreadCount();
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.MarkAllAsRead) {
|
} else if (res.op == UserOperation.GetPrivateMessages) {
|
||||||
|
let data = res.data as PrivateMessagesResponse;
|
||||||
|
this.state.messages = data.messages;
|
||||||
|
this.sendUnreadCount();
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
this.setState(this.state);
|
||||||
|
} else if (res.op == UserOperation.EditPrivateMessage) {
|
||||||
|
let data = res.data as PrivateMessageResponse;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
this.sendUnreadCount();
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
this.setState(this.state);
|
||||||
|
} else if (res.op == UserOperation.MarkAllAsRead) {
|
||||||
this.state.replies = [];
|
this.state.replies = [];
|
||||||
this.state.mentions = [];
|
this.state.mentions = [];
|
||||||
|
this.state.messages = [];
|
||||||
|
this.sendUnreadCount();
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditComment) {
|
} else if (res.op == UserOperation.EditComment) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
|
|
||||||
let found = this.state.replies.find(c => c.id == res.comment.id);
|
let found = this.state.replies.find(c => c.id == data.comment.id);
|
||||||
found.content = res.comment.content;
|
found.content = data.comment.content;
|
||||||
found.updated = res.comment.updated;
|
found.updated = data.comment.updated;
|
||||||
found.removed = res.comment.removed;
|
found.removed = data.comment.removed;
|
||||||
found.deleted = res.comment.deleted;
|
found.deleted = data.comment.deleted;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = data.comment.upvotes;
|
||||||
found.downvotes = res.comment.downvotes;
|
found.downvotes = data.comment.downvotes;
|
||||||
found.score = res.comment.score;
|
found.score = data.comment.score;
|
||||||
|
|
||||||
// If youre in the unread view, just remove it from the list
|
// If youre in the unread view, just remove it from the list
|
||||||
if (this.state.unreadOrAll == UnreadOrAll.Unread && res.comment.read) {
|
if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
|
||||||
this.state.replies = this.state.replies.filter(
|
this.state.replies = this.state.replies.filter(
|
||||||
r => r.id !== res.comment.id
|
r => r.id !== data.comment.id
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let found = this.state.replies.find(c => c.id == res.comment.id);
|
let found = this.state.replies.find(c => c.id == data.comment.id);
|
||||||
found.read = res.comment.read;
|
found.read = data.comment.read;
|
||||||
}
|
}
|
||||||
this.sendUnreadCount();
|
this.sendUnreadCount();
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditUserMention) {
|
} else if (res.op == UserOperation.EditUserMention) {
|
||||||
let res: UserMentionResponse = msg;
|
let data = res.data as UserMentionResponse;
|
||||||
|
|
||||||
let found = this.state.mentions.find(c => c.id == res.mention.id);
|
let found = this.state.mentions.find(c => c.id == data.mention.id);
|
||||||
found.content = res.mention.content;
|
found.content = data.mention.content;
|
||||||
found.updated = res.mention.updated;
|
found.updated = data.mention.updated;
|
||||||
found.removed = res.mention.removed;
|
found.removed = data.mention.removed;
|
||||||
found.deleted = res.mention.deleted;
|
found.deleted = data.mention.deleted;
|
||||||
found.upvotes = res.mention.upvotes;
|
found.upvotes = data.mention.upvotes;
|
||||||
found.downvotes = res.mention.downvotes;
|
found.downvotes = data.mention.downvotes;
|
||||||
found.score = res.mention.score;
|
found.score = data.mention.score;
|
||||||
|
|
||||||
// If youre in the unread view, just remove it from the list
|
// If youre in the unread view, just remove it from the list
|
||||||
if (this.state.unreadOrAll == UnreadOrAll.Unread && res.mention.read) {
|
if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) {
|
||||||
this.state.mentions = this.state.mentions.filter(
|
this.state.mentions = this.state.mentions.filter(
|
||||||
r => r.id !== res.mention.id
|
r => r.id !== data.mention.id
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let found = this.state.mentions.find(c => c.id == res.mention.id);
|
let found = this.state.mentions.find(c => c.id == data.mention.id);
|
||||||
found.read = res.mention.read;
|
found.read = data.mention.read;
|
||||||
}
|
}
|
||||||
this.sendUnreadCount();
|
this.sendUnreadCount();
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateComment) {
|
} else if (res.op == UserOperation.CreateComment) {
|
||||||
// let res: CommentResponse = msg;
|
// let res: CommentResponse = msg;
|
||||||
alert(i18n.t('reply_sent'));
|
toast(i18n.t('reply_sent'));
|
||||||
// this.state.replies.unshift(res.comment); // TODO do this right
|
// this.state.replies.unshift(res.comment); // TODO do this right
|
||||||
// this.setState(this.state);
|
// this.setState(this.state);
|
||||||
} else if (op == UserOperation.SaveComment) {
|
} else if (res.op == UserOperation.SaveComment) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found = this.state.replies.find(c => c.id == res.comment.id);
|
let found = this.state.replies.find(c => c.id == data.comment.id);
|
||||||
found.saved = res.comment.saved;
|
found.saved = data.comment.saved;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateCommentLike) {
|
} else if (res.op == UserOperation.CreateCommentLike) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found: Comment = this.state.replies.find(
|
let found: Comment = this.state.replies.find(
|
||||||
c => c.id === res.comment.id
|
c => c.id === data.comment.id
|
||||||
);
|
);
|
||||||
found.score = res.comment.score;
|
found.score = data.comment.score;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = data.comment.upvotes;
|
||||||
found.downvotes = res.comment.downvotes;
|
found.downvotes = data.comment.downvotes;
|
||||||
if (res.comment.my_vote !== null) found.my_vote = res.comment.my_vote;
|
if (data.comment.my_vote !== null) found.my_vote = data.comment.my_vote;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,7 +444,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
sendUnreadCount() {
|
sendUnreadCount() {
|
||||||
let count =
|
let count =
|
||||||
this.state.replies.filter(r => !r.read).length +
|
this.state.replies.filter(r => !r.read).length +
|
||||||
this.state.mentions.filter(r => !r.read).length;
|
this.state.mentions.filter(r => !r.read).length +
|
||||||
|
this.state.messages.filter(
|
||||||
|
r => !r.read && r.creator_id !== UserService.Instance.user.id
|
||||||
|
).length;
|
||||||
UserService.Instance.sub.next({
|
UserService.Instance.sub.next({
|
||||||
user: UserService.Instance.user,
|
user: UserService.Instance.user,
|
||||||
unreadCount: count,
|
unreadCount: count,
|
||||||
|
|
43
ui/src/components/login.tsx
vendored
43
ui/src/components/login.tsx
vendored
|
@ -8,9 +8,10 @@ import {
|
||||||
UserOperation,
|
UserOperation,
|
||||||
PasswordResetForm,
|
PasswordResetForm,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp, validEmail } from '../utils';
|
import { wsJsonToRes, validEmail, toast } from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -48,14 +49,7 @@ export class Login extends Component<any, State> {
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
retryWhen(errors =>
|
|
||||||
errors.pipe(
|
|
||||||
delay(3000),
|
|
||||||
take(10)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
msg => this.parseMessage(msg),
|
msg => this.parseMessage(msg),
|
||||||
err => console.error(err),
|
err => console.error(err),
|
||||||
|
@ -299,31 +293,32 @@ export class Login extends Component<any, State> {
|
||||||
WebSocketService.Instance.passwordReset(resetForm);
|
WebSocketService.Instance.passwordReset(resetForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (op == UserOperation.Login) {
|
if (res.op == UserOperation.Login) {
|
||||||
|
let data = res.data as LoginResponse;
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
let res: LoginResponse = msg;
|
UserService.Instance.login(data);
|
||||||
UserService.Instance.login(res);
|
toast(i18n.t('logged_in'));
|
||||||
this.props.history.push('/');
|
this.props.history.push('/');
|
||||||
} else if (op == UserOperation.Register) {
|
} else if (res.op == UserOperation.Register) {
|
||||||
|
let data = res.data as LoginResponse;
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
let res: LoginResponse = msg;
|
UserService.Instance.login(data);
|
||||||
UserService.Instance.login(res);
|
|
||||||
this.props.history.push('/communities');
|
this.props.history.push('/communities');
|
||||||
} else if (op == UserOperation.PasswordReset) {
|
} else if (res.op == UserOperation.PasswordReset) {
|
||||||
alert(i18n.t('reset_password_mail_sent'));
|
toast(i18n.t('reset_password_mail_sent'));
|
||||||
} else if (op == UserOperation.GetSite) {
|
} else if (res.op == UserOperation.GetSite) {
|
||||||
let res: GetSiteResponse = msg;
|
let data = res.data as GetSiteResponse;
|
||||||
this.state.enable_nsfw = res.site.enable_nsfw;
|
this.state.enable_nsfw = data.site.enable_nsfw;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
document.title = `${i18n.t('login')} - ${
|
document.title = `${i18n.t('login')} - ${
|
||||||
WebSocketService.Instance.site.name
|
WebSocketService.Instance.site.name
|
||||||
|
|
65
ui/src/components/main.tsx
vendored
65
ui/src/components/main.tsx
vendored
|
@ -17,6 +17,7 @@ import {
|
||||||
CreatePostLikeResponse,
|
CreatePostLikeResponse,
|
||||||
Post,
|
Post,
|
||||||
GetPostsForm,
|
GetPostsForm,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { PostListings } from './post-listings';
|
import { PostListings } from './post-listings';
|
||||||
|
@ -24,7 +25,7 @@ import { SortSelect } from './sort-select';
|
||||||
import { ListingTypeSelect } from './listing-type-select';
|
import { ListingTypeSelect } from './listing-type-select';
|
||||||
import { SiteForm } from './site-form';
|
import { SiteForm } from './site-form';
|
||||||
import {
|
import {
|
||||||
msgOp,
|
wsJsonToRes,
|
||||||
repoUrl,
|
repoUrl,
|
||||||
mdToHtml,
|
mdToHtml,
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
|
@ -33,6 +34,7 @@ import {
|
||||||
postRefetchSeconds,
|
postRefetchSeconds,
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
showAvatars,
|
showAvatars,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -56,7 +58,6 @@ export class Main extends Component<any, MainState> {
|
||||||
subscribedCommunities: [],
|
subscribedCommunities: [],
|
||||||
trendingCommunities: [],
|
trendingCommunities: [],
|
||||||
site: {
|
site: {
|
||||||
op: null,
|
|
||||||
site: {
|
site: {
|
||||||
id: null,
|
id: null,
|
||||||
name: null,
|
name: null,
|
||||||
|
@ -562,50 +563,50 @@ export class Main extends Component<any, MainState> {
|
||||||
WebSocketService.Instance.getPosts(getPostsForm);
|
WebSocketService.Instance.getPosts(getPostsForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.GetFollowedCommunities) {
|
} else if (res.op == UserOperation.GetFollowedCommunities) {
|
||||||
let res: GetFollowedCommunitiesResponse = msg;
|
let data = res.data as GetFollowedCommunitiesResponse;
|
||||||
this.state.subscribedCommunities = res.communities;
|
this.state.subscribedCommunities = data.communities;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.ListCommunities) {
|
} else if (res.op == UserOperation.ListCommunities) {
|
||||||
let res: ListCommunitiesResponse = msg;
|
let data = res.data as ListCommunitiesResponse;
|
||||||
this.state.trendingCommunities = res.communities;
|
this.state.trendingCommunities = data.communities;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.GetSite) {
|
} else if (res.op == UserOperation.GetSite) {
|
||||||
let res: GetSiteResponse = msg;
|
let data = res.data as GetSiteResponse;
|
||||||
|
|
||||||
// This means it hasn't been set up yet
|
// This means it hasn't been set up yet
|
||||||
if (!res.site) {
|
if (!data.site) {
|
||||||
this.context.router.history.push('/setup');
|
this.context.router.history.push('/setup');
|
||||||
}
|
}
|
||||||
this.state.site.admins = res.admins;
|
this.state.site.admins = data.admins;
|
||||||
this.state.site.site = res.site;
|
this.state.site.site = data.site;
|
||||||
this.state.site.banned = res.banned;
|
this.state.site.banned = data.banned;
|
||||||
this.state.site.online = res.online;
|
this.state.site.online = data.online;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
document.title = `${WebSocketService.Instance.site.name}`;
|
document.title = `${WebSocketService.Instance.site.name}`;
|
||||||
} else if (op == UserOperation.EditSite) {
|
} else if (res.op == UserOperation.EditSite) {
|
||||||
let res: SiteResponse = msg;
|
let data = res.data as SiteResponse;
|
||||||
this.state.site.site = res.site;
|
this.state.site.site = data.site;
|
||||||
this.state.showEditSite = false;
|
this.state.showEditSite = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.GetPosts) {
|
} else if (res.op == UserOperation.GetPosts) {
|
||||||
let res: GetPostsResponse = msg;
|
let data = res.data as GetPostsResponse;
|
||||||
this.state.posts = res.posts;
|
this.state.posts = data.posts;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePostLike) {
|
} else if (res.op == UserOperation.CreatePostLike) {
|
||||||
let res: CreatePostLikeResponse = msg;
|
let data = res.data as CreatePostLikeResponse;
|
||||||
let found = this.state.posts.find(c => c.id == res.post.id);
|
let found = this.state.posts.find(c => c.id == data.post.id);
|
||||||
found.my_vote = res.post.my_vote;
|
found.my_vote = data.post.my_vote;
|
||||||
found.score = res.post.score;
|
found.score = data.post.score;
|
||||||
found.upvotes = res.post.upvotes;
|
found.upvotes = data.post.upvotes;
|
||||||
found.downvotes = res.post.downvotes;
|
found.downvotes = data.post.downvotes;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
ui/src/components/modlog.tsx
vendored
27
ui/src/components/modlog.tsx
vendored
|
@ -17,9 +17,9 @@ import {
|
||||||
ModAdd,
|
ModAdd,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { msgOp, addTypeInfo, fetchLimit } from '../utils';
|
import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
import * as moment from 'moment';
|
import moment from 'moment';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
interface ModlogState {
|
interface ModlogState {
|
||||||
|
@ -55,14 +55,7 @@ export class Modlog extends Component<any, ModlogState> {
|
||||||
? Number(this.props.match.params.community_id)
|
? Number(this.props.match.params.community_id)
|
||||||
: undefined;
|
: undefined;
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
retryWhen(errors =>
|
|
||||||
errors.pipe(
|
|
||||||
delay(3000),
|
|
||||||
take(10)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
msg => this.parseMessage(msg),
|
msg => this.parseMessage(msg),
|
||||||
err => console.error(err),
|
err => console.error(err),
|
||||||
|
@ -429,17 +422,17 @@ export class Modlog extends Component<any, ModlogState> {
|
||||||
WebSocketService.Instance.getModlog(modlogForm);
|
WebSocketService.Instance.getModlog(modlogForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.GetModlog) {
|
} else if (res.op == UserOperation.GetModlog) {
|
||||||
let res: GetModlogResponse = msg;
|
let data = res.data as GetModlogResponse;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setCombined(res);
|
this.setCombined(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
ui/src/components/moment-time.tsx
vendored
2
ui/src/components/moment-time.tsx
vendored
|
@ -1,5 +1,5 @@
|
||||||
import { Component } from 'inferno';
|
import { Component } from 'inferno';
|
||||||
import * as moment from 'moment';
|
import moment from 'moment';
|
||||||
import { getMomentLanguage } from '../utils';
|
import { getMomentLanguage } from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
|
|
77
ui/src/components/navbar.tsx
vendored
77
ui/src/components/navbar.tsx
vendored
|
@ -9,15 +9,21 @@ import {
|
||||||
GetRepliesResponse,
|
GetRepliesResponse,
|
||||||
GetUserMentionsForm,
|
GetUserMentionsForm,
|
||||||
GetUserMentionsResponse,
|
GetUserMentionsResponse,
|
||||||
|
GetPrivateMessagesForm,
|
||||||
|
PrivateMessagesResponse,
|
||||||
SortType,
|
SortType,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
Comment,
|
Comment,
|
||||||
|
PrivateMessage,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import {
|
import {
|
||||||
msgOp,
|
wsJsonToRes,
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
showAvatars,
|
showAvatars,
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
|
isCommentType,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { version } from '../version';
|
import { version } from '../version';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
@ -28,6 +34,7 @@ interface NavbarState {
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
replies: Array<Comment>;
|
replies: Array<Comment>;
|
||||||
mentions: Array<Comment>;
|
mentions: Array<Comment>;
|
||||||
|
messages: Array<PrivateMessage>;
|
||||||
fetchCount: number;
|
fetchCount: number;
|
||||||
unreadCount: number;
|
unreadCount: number;
|
||||||
siteName: string;
|
siteName: string;
|
||||||
|
@ -42,6 +49,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
fetchCount: 0,
|
fetchCount: 0,
|
||||||
replies: [],
|
replies: [],
|
||||||
mentions: [],
|
mentions: [],
|
||||||
|
messages: [],
|
||||||
expanded: false,
|
expanded: false,
|
||||||
siteName: undefined,
|
siteName: undefined,
|
||||||
};
|
};
|
||||||
|
@ -192,17 +200,17 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
if (msg.error == 'not_logged_in') {
|
if (res.error == 'not_logged_in') {
|
||||||
UserService.Instance.logout();
|
UserService.Instance.logout();
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.GetReplies) {
|
} else if (res.op == UserOperation.GetReplies) {
|
||||||
let res: GetRepliesResponse = msg;
|
let data = res.data as GetRepliesResponse;
|
||||||
let unreadReplies = res.replies.filter(r => !r.read);
|
let unreadReplies = data.replies.filter(r => !r.read);
|
||||||
if (
|
if (
|
||||||
unreadReplies.length > 0 &&
|
unreadReplies.length > 0 &&
|
||||||
this.state.fetchCount > 1 &&
|
this.state.fetchCount > 1 &&
|
||||||
|
@ -214,9 +222,9 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
this.state.replies = unreadReplies;
|
this.state.replies = unreadReplies;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
this.sendUnreadCount();
|
this.sendUnreadCount();
|
||||||
} else if (op == UserOperation.GetUserMentions) {
|
} else if (res.op == UserOperation.GetUserMentions) {
|
||||||
let res: GetUserMentionsResponse = msg;
|
let data = res.data as GetUserMentionsResponse;
|
||||||
let unreadMentions = res.mentions.filter(r => !r.read);
|
let unreadMentions = data.mentions.filter(r => !r.read);
|
||||||
if (
|
if (
|
||||||
unreadMentions.length > 0 &&
|
unreadMentions.length > 0 &&
|
||||||
this.state.fetchCount > 1 &&
|
this.state.fetchCount > 1 &&
|
||||||
|
@ -228,12 +236,26 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
this.state.mentions = unreadMentions;
|
this.state.mentions = unreadMentions;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
this.sendUnreadCount();
|
this.sendUnreadCount();
|
||||||
} else if (op == UserOperation.GetSite) {
|
} else if (res.op == UserOperation.GetPrivateMessages) {
|
||||||
let res: GetSiteResponse = msg;
|
let data = res.data as PrivateMessagesResponse;
|
||||||
|
let unreadMessages = data.messages.filter(r => !r.read);
|
||||||
|
if (
|
||||||
|
unreadMessages.length > 0 &&
|
||||||
|
this.state.fetchCount > 1 &&
|
||||||
|
JSON.stringify(this.state.messages) !== JSON.stringify(unreadMessages)
|
||||||
|
) {
|
||||||
|
this.notify(unreadMessages);
|
||||||
|
}
|
||||||
|
|
||||||
if (res.site) {
|
this.state.messages = unreadMessages;
|
||||||
this.state.siteName = res.site.name;
|
this.setState(this.state);
|
||||||
WebSocketService.Instance.site = res.site;
|
this.sendUnreadCount();
|
||||||
|
} else if (res.op == UserOperation.GetSite) {
|
||||||
|
let data = res.data as GetSiteResponse;
|
||||||
|
|
||||||
|
if (data.site) {
|
||||||
|
this.state.siteName = data.site.name;
|
||||||
|
WebSocketService.Instance.site = data.site;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,9 +281,17 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let privateMessagesForm: GetPrivateMessagesForm = {
|
||||||
|
unread_only: true,
|
||||||
|
page: 1,
|
||||||
|
limit: fetchLimit,
|
||||||
|
};
|
||||||
|
|
||||||
if (this.currentLocation !== '/inbox') {
|
if (this.currentLocation !== '/inbox') {
|
||||||
WebSocketService.Instance.getReplies(repliesForm);
|
WebSocketService.Instance.getReplies(repliesForm);
|
||||||
WebSocketService.Instance.getUserMentions(userMentionsForm);
|
WebSocketService.Instance.getUserMentions(userMentionsForm);
|
||||||
|
WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
|
||||||
this.state.fetchCount++;
|
this.state.fetchCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,7 +311,8 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
get unreadCount() {
|
get unreadCount() {
|
||||||
return (
|
return (
|
||||||
this.state.replies.filter(r => !r.read).length +
|
this.state.replies.filter(r => !r.read).length +
|
||||||
this.state.mentions.filter(r => !r.read).length
|
this.state.mentions.filter(r => !r.read).length +
|
||||||
|
this.state.messages.filter(r => !r.read).length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +320,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
if (UserService.Instance.user) {
|
if (UserService.Instance.user) {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
if (!Notification) {
|
if (!Notification) {
|
||||||
alert(i18n.t('notifications_error'));
|
toast(i18n.t('notifications_error'), 'danger');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,21 +330,25 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(replies: Array<Comment>) {
|
notify(replies: Array<Comment | PrivateMessage>) {
|
||||||
let recentReply = replies[0];
|
let recentReply = replies[0];
|
||||||
if (Notification.permission !== 'granted') Notification.requestPermission();
|
if (Notification.permission !== 'granted') Notification.requestPermission();
|
||||||
else {
|
else {
|
||||||
var notification = new Notification(
|
var notification = new Notification(
|
||||||
`${replies.length} ${i18n.t('unread_messages')}`,
|
`${replies.length} ${i18n.t('unread_messages')}`,
|
||||||
{
|
{
|
||||||
icon: `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
|
icon: recentReply.creator_avatar
|
||||||
|
? recentReply.creator_avatar
|
||||||
|
: `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
|
||||||
body: `${recentReply.creator_name}: ${recentReply.content}`,
|
body: `${recentReply.creator_name}: ${recentReply.content}`,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
notification.onclick = () => {
|
notification.onclick = () => {
|
||||||
this.context.router.history.push(
|
this.context.router.history.push(
|
||||||
`/post/${recentReply.post_id}/comment/${recentReply.id}`
|
isCommentType(recentReply)
|
||||||
|
? `/post/${recentReply.post_id}/comment/${recentReply.id}`
|
||||||
|
: `/inbox`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
24
ui/src/components/password_change.tsx
vendored
24
ui/src/components/password_change.tsx
vendored
|
@ -5,9 +5,10 @@ import {
|
||||||
UserOperation,
|
UserOperation,
|
||||||
LoginResponse,
|
LoginResponse,
|
||||||
PasswordChangeForm,
|
PasswordChangeForm,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp, capitalizeFirstLetter } from '../utils';
|
import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -34,14 +35,7 @@ export class PasswordChange extends Component<any, State> {
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
retryWhen(errors =>
|
|
||||||
errors.pipe(
|
|
||||||
delay(3000),
|
|
||||||
take(10)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
msg => this.parseMessage(msg),
|
msg => this.parseMessage(msg),
|
||||||
err => console.error(err),
|
err => console.error(err),
|
||||||
|
@ -140,19 +134,19 @@ export class PasswordChange extends Component<any, State> {
|
||||||
WebSocketService.Instance.passwordChange(i.state.passwordChangeForm);
|
WebSocketService.Instance.passwordChange(i.state.passwordChangeForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (op == UserOperation.PasswordChange) {
|
if (res.op == UserOperation.PasswordChange) {
|
||||||
|
let data = res.data as LoginResponse;
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
let res: LoginResponse = msg;
|
UserService.Instance.login(data);
|
||||||
UserService.Instance.login(res);
|
|
||||||
this.props.history.push('/');
|
this.props.history.push('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
54
ui/src/components/post-form.tsx
vendored
54
ui/src/components/post-form.tsx
vendored
|
@ -16,10 +16,11 @@ import {
|
||||||
SearchType,
|
SearchType,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import {
|
||||||
msgOp,
|
wsJsonToRes,
|
||||||
getPageTitle,
|
getPageTitle,
|
||||||
validURL,
|
validURL,
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
|
@ -28,6 +29,7 @@ import {
|
||||||
mdToHtml,
|
mdToHtml,
|
||||||
debounce,
|
debounce,
|
||||||
isImage,
|
isImage,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
@ -453,51 +455,51 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
i.state.imageLoading = false;
|
i.state.imageLoading = false;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
alert(error);
|
toast(error, 'danger');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.ListCommunities) {
|
} else if (res.op == UserOperation.ListCommunities) {
|
||||||
let res: ListCommunitiesResponse = msg;
|
let data = res.data as ListCommunitiesResponse;
|
||||||
this.state.communities = res.communities;
|
this.state.communities = data.communities;
|
||||||
if (this.props.post) {
|
if (this.props.post) {
|
||||||
this.state.postForm.community_id = this.props.post.community_id;
|
this.state.postForm.community_id = this.props.post.community_id;
|
||||||
} else if (this.props.params && this.props.params.community) {
|
} else if (this.props.params && this.props.params.community) {
|
||||||
let foundCommunityId = res.communities.find(
|
let foundCommunityId = data.communities.find(
|
||||||
r => r.name == this.props.params.community
|
r => r.name == this.props.params.community
|
||||||
).id;
|
).id;
|
||||||
this.state.postForm.community_id = foundCommunityId;
|
this.state.postForm.community_id = foundCommunityId;
|
||||||
} else {
|
} else {
|
||||||
this.state.postForm.community_id = res.communities[0].id;
|
this.state.postForm.community_id = data.communities[0].id;
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePost) {
|
} else if (res.op == UserOperation.CreatePost) {
|
||||||
|
let data = res.data as PostResponse;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
let res: PostResponse = msg;
|
this.props.onCreate(data.post.id);
|
||||||
this.props.onCreate(res.post.id);
|
} else if (res.op == UserOperation.EditPost) {
|
||||||
} else if (op == UserOperation.EditPost) {
|
let data = res.data as PostResponse;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
let res: PostResponse = msg;
|
this.props.onEdit(data.post);
|
||||||
this.props.onEdit(res.post);
|
} else if (res.op == UserOperation.Search) {
|
||||||
} else if (op == UserOperation.Search) {
|
let data = res.data as SearchResponse;
|
||||||
let res: SearchResponse = msg;
|
|
||||||
|
|
||||||
if (res.type_ == SearchType[SearchType.Posts]) {
|
if (data.type_ == SearchType[SearchType.Posts]) {
|
||||||
this.state.suggestedPosts = res.posts;
|
this.state.suggestedPosts = data.posts;
|
||||||
} else if (res.type_ == SearchType[SearchType.Url]) {
|
} else if (data.type_ == SearchType[SearchType.Url]) {
|
||||||
this.state.crossPosts = res.posts;
|
this.state.crossPosts = data.posts;
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.GetSite) {
|
} else if (res.op == UserOperation.GetSite) {
|
||||||
let res: GetSiteResponse = msg;
|
let data = res.data as GetSiteResponse;
|
||||||
this.state.enable_nsfw = res.site.enable_nsfw;
|
this.state.enable_nsfw = data.site.enable_nsfw;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
181
ui/src/components/post.tsx
vendored
181
ui/src/components/post.tsx
vendored
|
@ -26,9 +26,10 @@ import {
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
GetCommunityResponse,
|
GetCommunityResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp, hotRank } from '../utils';
|
import { wsJsonToRes, hotRank, toast } from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
import { PostListings } from './post-listings';
|
import { PostListings } from './post-listings';
|
||||||
import { Sidebar } from './sidebar';
|
import { Sidebar } from './sidebar';
|
||||||
|
@ -341,19 +342,19 @@ export class Post extends Component<any, PostState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.GetPost) {
|
} else if (res.op == UserOperation.GetPost) {
|
||||||
let res: GetPostResponse = msg;
|
let data = res.data as GetPostResponse;
|
||||||
this.state.post = res.post;
|
this.state.post = data.post;
|
||||||
this.state.comments = res.comments;
|
this.state.comments = data.comments;
|
||||||
this.state.community = res.community;
|
this.state.community = data.community;
|
||||||
this.state.moderators = res.moderators;
|
this.state.moderators = data.moderators;
|
||||||
this.state.admins = res.admins;
|
this.state.admins = data.admins;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
document.title = `${this.state.post.name} - ${WebSocketService.Instance.site.name}`;
|
document.title = `${this.state.post.name} - ${WebSocketService.Instance.site.name}`;
|
||||||
|
|
||||||
|
@ -370,111 +371,111 @@ export class Post extends Component<any, PostState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateComment) {
|
} else if (res.op == UserOperation.CreateComment) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
this.state.comments.unshift(res.comment);
|
this.state.comments.unshift(data.comment);
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditComment) {
|
} else if (res.op == UserOperation.EditComment) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found = this.state.comments.find(c => c.id == res.comment.id);
|
let found = this.state.comments.find(c => c.id == data.comment.id);
|
||||||
found.content = res.comment.content;
|
found.content = data.comment.content;
|
||||||
found.updated = res.comment.updated;
|
found.updated = data.comment.updated;
|
||||||
found.removed = res.comment.removed;
|
found.removed = data.comment.removed;
|
||||||
found.deleted = res.comment.deleted;
|
found.deleted = data.comment.deleted;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = data.comment.upvotes;
|
||||||
found.downvotes = res.comment.downvotes;
|
found.downvotes = data.comment.downvotes;
|
||||||
found.score = res.comment.score;
|
found.score = data.comment.score;
|
||||||
found.read = res.comment.read;
|
found.read = data.comment.read;
|
||||||
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.SaveComment) {
|
} else if (res.op == UserOperation.SaveComment) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found = this.state.comments.find(c => c.id == res.comment.id);
|
let found = this.state.comments.find(c => c.id == data.comment.id);
|
||||||
found.saved = res.comment.saved;
|
found.saved = data.comment.saved;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateCommentLike) {
|
} else if (res.op == UserOperation.CreateCommentLike) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found: Comment = this.state.comments.find(
|
let found: Comment = this.state.comments.find(
|
||||||
c => c.id === res.comment.id
|
c => c.id === data.comment.id
|
||||||
);
|
);
|
||||||
found.score = res.comment.score;
|
found.score = data.comment.score;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = data.comment.upvotes;
|
||||||
found.downvotes = res.comment.downvotes;
|
found.downvotes = data.comment.downvotes;
|
||||||
if (res.comment.my_vote !== null) {
|
if (data.comment.my_vote !== null) {
|
||||||
found.my_vote = res.comment.my_vote;
|
found.my_vote = data.comment.my_vote;
|
||||||
found.upvoteLoading = false;
|
found.upvoteLoading = false;
|
||||||
found.downvoteLoading = false;
|
found.downvoteLoading = false;
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePostLike) {
|
} else if (res.op == UserOperation.CreatePostLike) {
|
||||||
let res: CreatePostLikeResponse = msg;
|
let data = res.data as CreatePostLikeResponse;
|
||||||
this.state.post.my_vote = res.post.my_vote;
|
this.state.post.my_vote = data.post.my_vote;
|
||||||
this.state.post.score = res.post.score;
|
this.state.post.score = data.post.score;
|
||||||
this.state.post.upvotes = res.post.upvotes;
|
this.state.post.upvotes = data.post.upvotes;
|
||||||
this.state.post.downvotes = res.post.downvotes;
|
this.state.post.downvotes = data.post.downvotes;
|
||||||
this.state.post.upvoteLoading = false;
|
|
||||||
this.state.post.downvoteLoading = false;
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditPost) {
|
} else if (res.op == UserOperation.EditPost) {
|
||||||
let res: PostResponse = msg;
|
let data = res.data as PostResponse;
|
||||||
this.state.post = res.post;
|
this.state.post = data.post;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.SavePost) {
|
} else if (res.op == UserOperation.SavePost) {
|
||||||
let res: PostResponse = msg;
|
let data = res.data as PostResponse;
|
||||||
this.state.post = res.post;
|
this.state.post = data.post;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditCommunity) {
|
} else if (res.op == UserOperation.EditCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.community = res.community;
|
this.state.community = data.community;
|
||||||
this.state.post.community_id = res.community.id;
|
this.state.post.community_id = data.community.id;
|
||||||
this.state.post.community_name = res.community.name;
|
this.state.post.community_name = data.community.name;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.FollowCommunity) {
|
} else if (res.op == UserOperation.FollowCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let data = res.data as CommunityResponse;
|
||||||
this.state.community.subscribed = res.community.subscribed;
|
this.state.community.subscribed = data.community.subscribed;
|
||||||
this.state.community.number_of_subscribers =
|
this.state.community.number_of_subscribers =
|
||||||
res.community.number_of_subscribers;
|
data.community.number_of_subscribers;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.BanFromCommunity) {
|
} else if (res.op == UserOperation.BanFromCommunity) {
|
||||||
let res: BanFromCommunityResponse = msg;
|
let data = res.data as BanFromCommunityResponse;
|
||||||
this.state.comments
|
this.state.comments
|
||||||
.filter(c => c.creator_id == res.user.id)
|
.filter(c => c.creator_id == data.user.id)
|
||||||
.forEach(c => (c.banned_from_community = res.banned));
|
.forEach(c => (c.banned_from_community = data.banned));
|
||||||
if (this.state.post.creator_id == res.user.id) {
|
if (this.state.post.creator_id == data.user.id) {
|
||||||
this.state.post.banned_from_community = res.banned;
|
this.state.post.banned_from_community = data.banned;
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.AddModToCommunity) {
|
} else if (res.op == UserOperation.AddModToCommunity) {
|
||||||
let res: AddModToCommunityResponse = msg;
|
let data = res.data as AddModToCommunityResponse;
|
||||||
this.state.moderators = res.moderators;
|
this.state.moderators = data.moderators;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.BanUser) {
|
} else if (res.op == UserOperation.BanUser) {
|
||||||
let res: BanUserResponse = msg;
|
let data = res.data as BanUserResponse;
|
||||||
this.state.comments
|
this.state.comments
|
||||||
.filter(c => c.creator_id == res.user.id)
|
.filter(c => c.creator_id == data.user.id)
|
||||||
.forEach(c => (c.banned = res.banned));
|
.forEach(c => (c.banned = data.banned));
|
||||||
if (this.state.post.creator_id == res.user.id) {
|
if (this.state.post.creator_id == data.user.id) {
|
||||||
this.state.post.banned = res.banned;
|
this.state.post.banned = data.banned;
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.AddAdmin) {
|
} else if (res.op == UserOperation.AddAdmin) {
|
||||||
let res: AddAdminResponse = msg;
|
let data = res.data as AddAdminResponse;
|
||||||
this.state.admins = res.admins;
|
this.state.admins = data.admins;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.Search) {
|
} else if (res.op == UserOperation.Search) {
|
||||||
let res: SearchResponse = msg;
|
let data = res.data as SearchResponse;
|
||||||
this.state.crossPosts = res.posts.filter(p => p.id != this.state.post.id);
|
this.state.crossPosts = data.posts.filter(
|
||||||
|
p => p.id != this.state.post.id
|
||||||
|
);
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.TransferSite) {
|
} else if (res.op == UserOperation.TransferSite) {
|
||||||
let res: GetSiteResponse = msg;
|
let data = res.data as GetSiteResponse;
|
||||||
|
|
||||||
this.state.admins = res.admins;
|
this.state.admins = data.admins;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.TransferCommunity) {
|
} else if (res.op == UserOperation.TransferCommunity) {
|
||||||
let res: GetCommunityResponse = msg;
|
let data = res.data as GetCommunityResponse;
|
||||||
this.state.community = res.community;
|
this.state.community = data.community;
|
||||||
this.state.moderators = res.moderators;
|
this.state.moderators = data.moderators;
|
||||||
this.state.admins = res.admins;
|
this.state.admins = data.admins;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
293
ui/src/components/private-message-form.tsx
vendored
Normal file
293
ui/src/components/private-message-form.tsx
vendored
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
import { Component, linkEvent } from 'inferno';
|
||||||
|
import { Link } from 'inferno-router';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
PrivateMessageForm as PrivateMessageFormI,
|
||||||
|
EditPrivateMessageForm,
|
||||||
|
PrivateMessageFormParams,
|
||||||
|
PrivateMessage,
|
||||||
|
PrivateMessageResponse,
|
||||||
|
UserView,
|
||||||
|
UserOperation,
|
||||||
|
UserDetailsResponse,
|
||||||
|
GetUserDetailsForm,
|
||||||
|
SortType,
|
||||||
|
WebSocketJsonResponse,
|
||||||
|
} from '../interfaces';
|
||||||
|
import { WebSocketService } from '../services';
|
||||||
|
import {
|
||||||
|
capitalizeFirstLetter,
|
||||||
|
markdownHelpUrl,
|
||||||
|
mdToHtml,
|
||||||
|
showAvatars,
|
||||||
|
pictshareAvatarThumbnail,
|
||||||
|
wsJsonToRes,
|
||||||
|
toast,
|
||||||
|
} from '../utils';
|
||||||
|
import autosize from 'autosize';
|
||||||
|
import { i18n } from '../i18next';
|
||||||
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
interface PrivateMessageFormProps {
|
||||||
|
privateMessage?: PrivateMessage; // If a pm is given, that means this is an edit
|
||||||
|
params?: PrivateMessageFormParams;
|
||||||
|
onCancel?(): any;
|
||||||
|
onCreate?(message: PrivateMessage): any;
|
||||||
|
onEdit?(message: PrivateMessage): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PrivateMessageFormState {
|
||||||
|
privateMessageForm: PrivateMessageFormI;
|
||||||
|
recipient: UserView;
|
||||||
|
loading: boolean;
|
||||||
|
previewMode: boolean;
|
||||||
|
showDisclaimer: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrivateMessageForm extends Component<
|
||||||
|
PrivateMessageFormProps,
|
||||||
|
PrivateMessageFormState
|
||||||
|
> {
|
||||||
|
private subscription: Subscription;
|
||||||
|
private emptyState: PrivateMessageFormState = {
|
||||||
|
privateMessageForm: {
|
||||||
|
content: null,
|
||||||
|
recipient_id: null,
|
||||||
|
},
|
||||||
|
recipient: null,
|
||||||
|
loading: false,
|
||||||
|
previewMode: false,
|
||||||
|
showDisclaimer: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = this.emptyState;
|
||||||
|
|
||||||
|
if (this.props.privateMessage) {
|
||||||
|
this.state.privateMessageForm = {
|
||||||
|
content: this.props.privateMessage.content,
|
||||||
|
recipient_id: this.props.privateMessage.recipient_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.params) {
|
||||||
|
this.state.privateMessageForm.recipient_id = this.props.params.recipient_id;
|
||||||
|
let form: GetUserDetailsForm = {
|
||||||
|
user_id: this.state.privateMessageForm.recipient_id,
|
||||||
|
sort: SortType[SortType.New],
|
||||||
|
saved_only: false,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.getUserDetails(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscription = WebSocketService.Instance.subject
|
||||||
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
|
.subscribe(
|
||||||
|
msg => this.parseMessage(msg),
|
||||||
|
err => console.error(err),
|
||||||
|
() => console.log('complete')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
autosize(document.querySelectorAll('textarea'));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
|
||||||
|
{!this.props.privateMessage && (
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-2 col-form-label">
|
||||||
|
{capitalizeFirstLetter(i18n.t('to'))}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{this.state.recipient && (
|
||||||
|
<div class="col-sm-10 form-control-plaintext">
|
||||||
|
<Link
|
||||||
|
className="text-info"
|
||||||
|
to={`/u/${this.state.recipient.name}`}
|
||||||
|
>
|
||||||
|
{this.state.recipient.avatar && showAvatars() && (
|
||||||
|
<img
|
||||||
|
height="32"
|
||||||
|
width="32"
|
||||||
|
src={pictshareAvatarThumbnail(
|
||||||
|
this.state.recipient.avatar
|
||||||
|
)}
|
||||||
|
class="rounded-circle mr-1"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>{this.state.recipient.name}</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-2 col-form-label">{i18n.t('message')}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<textarea
|
||||||
|
value={this.state.privateMessageForm.content}
|
||||||
|
onInput={linkEvent(this, this.handleContentChange)}
|
||||||
|
className={`form-control ${this.state.previewMode && 'd-none'}`}
|
||||||
|
rows={4}
|
||||||
|
maxLength={10000}
|
||||||
|
/>
|
||||||
|
{this.state.previewMode && (
|
||||||
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(
|
||||||
|
this.state.privateMessageForm.content
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.privateMessageForm.content && (
|
||||||
|
<button
|
||||||
|
className={`mt-1 mr-2 btn btn-sm btn-secondary ${this.state
|
||||||
|
.previewMode && 'active'}`}
|
||||||
|
onClick={linkEvent(this, this.handlePreviewToggle)}
|
||||||
|
>
|
||||||
|
{i18n.t('preview')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<ul class="float-right list-inline mb-1 text-muted small font-weight-bold">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span
|
||||||
|
onClick={linkEvent(this, this.handleShowDisclaimer)}
|
||||||
|
class="pointer"
|
||||||
|
>
|
||||||
|
{i18n.t('disclaimer')}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href={markdownHelpUrl} target="_blank" class="text-muted">
|
||||||
|
{i18n.t('formatting_help')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.state.showDisclaimer && (
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<T i18nKey="private_message_disclaimer">
|
||||||
|
#
|
||||||
|
<a
|
||||||
|
class="alert-link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://about.riot.im/"
|
||||||
|
>
|
||||||
|
#
|
||||||
|
</a>
|
||||||
|
</T>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<button type="submit" class="btn btn-secondary mr-2">
|
||||||
|
{this.state.loading ? (
|
||||||
|
<svg class="icon icon-spinner spin">
|
||||||
|
<use xlinkHref="#icon-spinner"></use>
|
||||||
|
</svg>
|
||||||
|
) : this.props.privateMessage ? (
|
||||||
|
capitalizeFirstLetter(i18n.t('save'))
|
||||||
|
) : (
|
||||||
|
capitalizeFirstLetter(i18n.t('send_message'))
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{this.props.privateMessage && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
onClick={linkEvent(this, this.handleCancel)}
|
||||||
|
>
|
||||||
|
{i18n.t('cancel')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (i.props.privateMessage) {
|
||||||
|
let editForm: EditPrivateMessageForm = {
|
||||||
|
edit_id: i.props.privateMessage.id,
|
||||||
|
content: i.state.privateMessageForm.content,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.editPrivateMessage(editForm);
|
||||||
|
} else {
|
||||||
|
WebSocketService.Instance.createPrivateMessage(
|
||||||
|
i.state.privateMessageForm
|
||||||
|
);
|
||||||
|
}
|
||||||
|
i.state.loading = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRecipientChange(i: PrivateMessageForm, event: any) {
|
||||||
|
i.state.recipient = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleContentChange(i: PrivateMessageForm, event: any) {
|
||||||
|
i.state.privateMessageForm.content = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancel(i: PrivateMessageForm) {
|
||||||
|
i.props.onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePreviewToggle(i: PrivateMessageForm, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
i.state.previewMode = !i.state.previewMode;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleShowDisclaimer(i: PrivateMessageForm) {
|
||||||
|
i.state.showDisclaimer = !i.state.showDisclaimer;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
|
let res = wsJsonToRes(msg);
|
||||||
|
if (res.error) {
|
||||||
|
toast(i18n.t(msg.error), 'danger');
|
||||||
|
this.state.loading = false;
|
||||||
|
this.setState(this.state);
|
||||||
|
return;
|
||||||
|
} else if (res.op == UserOperation.EditPrivateMessage) {
|
||||||
|
let data = res.data as PrivateMessageResponse;
|
||||||
|
this.state.loading = false;
|
||||||
|
this.props.onEdit(data.message);
|
||||||
|
} else if (res.op == UserOperation.GetUserDetails) {
|
||||||
|
let data = res.data as UserDetailsResponse;
|
||||||
|
this.state.recipient = data.user;
|
||||||
|
this.state.privateMessageForm.recipient_id = data.user.id;
|
||||||
|
this.setState(this.state);
|
||||||
|
} else if (res.op == UserOperation.CreatePrivateMessage) {
|
||||||
|
let data = res.data as PrivateMessageResponse;
|
||||||
|
this.state.loading = false;
|
||||||
|
this.props.onCreate(data.message);
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
254
ui/src/components/private-message.tsx
vendored
Normal file
254
ui/src/components/private-message.tsx
vendored
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
import { Component, linkEvent } from 'inferno';
|
||||||
|
import { Link } from 'inferno-router';
|
||||||
|
import {
|
||||||
|
PrivateMessage as PrivateMessageI,
|
||||||
|
EditPrivateMessageForm,
|
||||||
|
} from '../interfaces';
|
||||||
|
import { WebSocketService, UserService } from '../services';
|
||||||
|
import {
|
||||||
|
mdToHtml,
|
||||||
|
pictshareAvatarThumbnail,
|
||||||
|
showAvatars,
|
||||||
|
toast,
|
||||||
|
} from '../utils';
|
||||||
|
import { MomentTime } from './moment-time';
|
||||||
|
import { PrivateMessageForm } from './private-message-form';
|
||||||
|
import { i18n } from '../i18next';
|
||||||
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
interface PrivateMessageState {
|
||||||
|
showReply: boolean;
|
||||||
|
showEdit: boolean;
|
||||||
|
collapsed: boolean;
|
||||||
|
viewSource: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PrivateMessageProps {
|
||||||
|
privateMessage: PrivateMessageI;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrivateMessage extends Component<
|
||||||
|
PrivateMessageProps,
|
||||||
|
PrivateMessageState
|
||||||
|
> {
|
||||||
|
private emptyState: PrivateMessageState = {
|
||||||
|
showReply: false,
|
||||||
|
showEdit: false,
|
||||||
|
collapsed: false,
|
||||||
|
viewSource: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = this.emptyState;
|
||||||
|
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
||||||
|
this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.handlePrivateMessageEdit = this.handlePrivateMessageEdit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mine(): boolean {
|
||||||
|
return UserService.Instance.user.id == this.props.privateMessage.creator_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let message = this.props.privateMessage;
|
||||||
|
return (
|
||||||
|
<div class="mb-2">
|
||||||
|
<div>
|
||||||
|
<ul class="list-inline mb-0 text-muted small">
|
||||||
|
<li className="list-inline-item">
|
||||||
|
{this.mine ? i18n.t('to') : i18n.t('from')}
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<Link
|
||||||
|
className="text-info"
|
||||||
|
to={
|
||||||
|
this.mine
|
||||||
|
? `/u/${message.recipient_name}`
|
||||||
|
: `/u/${message.creator_name}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(this.mine
|
||||||
|
? message.recipient_avatar
|
||||||
|
: message.creator_avatar) &&
|
||||||
|
showAvatars() && (
|
||||||
|
<img
|
||||||
|
height="32"
|
||||||
|
width="32"
|
||||||
|
src={pictshareAvatarThumbnail(
|
||||||
|
this.mine
|
||||||
|
? message.recipient_avatar
|
||||||
|
: message.creator_avatar
|
||||||
|
)}
|
||||||
|
class="rounded-circle mr-1"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{this.mine ? message.recipient_name : message.creator_name}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span>
|
||||||
|
<MomentTime data={message} />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<div
|
||||||
|
className="pointer text-monospace"
|
||||||
|
onClick={linkEvent(this, this.handleMessageCollapse)}
|
||||||
|
>
|
||||||
|
{this.state.collapsed ? '[+]' : '[-]'}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{this.state.showEdit && (
|
||||||
|
<PrivateMessageForm
|
||||||
|
privateMessage={message}
|
||||||
|
onEdit={this.handlePrivateMessageEdit}
|
||||||
|
onCancel={this.handleReplyCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!this.state.showEdit && !this.state.collapsed && (
|
||||||
|
<div>
|
||||||
|
{this.state.viewSource ? (
|
||||||
|
<pre>{this.messageUnlessRemoved}</pre>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
||||||
|
{!this.mine && (
|
||||||
|
<>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span
|
||||||
|
class="pointer"
|
||||||
|
onClick={linkEvent(this, this.handleMarkRead)}
|
||||||
|
>
|
||||||
|
{message.read
|
||||||
|
? i18n.t('mark_as_unread')
|
||||||
|
: i18n.t('mark_as_read')}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span
|
||||||
|
class="pointer"
|
||||||
|
onClick={linkEvent(this, this.handleReplyClick)}
|
||||||
|
>
|
||||||
|
<T i18nKey="reply">#</T>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{this.mine && (
|
||||||
|
<>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span
|
||||||
|
class="pointer"
|
||||||
|
onClick={linkEvent(this, this.handleEditClick)}
|
||||||
|
>
|
||||||
|
<T i18nKey="edit">#</T>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span
|
||||||
|
class="pointer"
|
||||||
|
onClick={linkEvent(this, this.handleDeleteClick)}
|
||||||
|
>
|
||||||
|
{!message.deleted
|
||||||
|
? i18n.t('delete')
|
||||||
|
: i18n.t('restore')}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<li className="list-inline-item">•</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span
|
||||||
|
className="pointer"
|
||||||
|
onClick={linkEvent(this, this.handleViewSource)}
|
||||||
|
>
|
||||||
|
<T i18nKey="view_source">#</T>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{this.state.showReply && (
|
||||||
|
<PrivateMessageForm
|
||||||
|
params={{
|
||||||
|
recipient_id: this.props.privateMessage.creator_id,
|
||||||
|
}}
|
||||||
|
onCreate={this.handlePrivateMessageCreate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* A collapsed clearfix */}
|
||||||
|
{this.state.collapsed && <div class="row col-12"></div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get messageUnlessRemoved(): string {
|
||||||
|
let message = this.props.privateMessage;
|
||||||
|
return message.deleted ? `*${i18n.t('deleted')}*` : message.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReplyClick(i: PrivateMessage) {
|
||||||
|
i.state.showReply = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEditClick(i: PrivateMessage) {
|
||||||
|
i.state.showEdit = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteClick(i: PrivateMessage) {
|
||||||
|
let form: EditPrivateMessageForm = {
|
||||||
|
edit_id: i.props.privateMessage.id,
|
||||||
|
deleted: !i.props.privateMessage.deleted,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.editPrivateMessage(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReplyCancel() {
|
||||||
|
this.state.showReply = false;
|
||||||
|
this.state.showEdit = false;
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMarkRead(i: PrivateMessage) {
|
||||||
|
let form: EditPrivateMessageForm = {
|
||||||
|
edit_id: i.props.privateMessage.id,
|
||||||
|
read: !i.props.privateMessage.read,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.editPrivateMessage(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessageCollapse(i: PrivateMessage) {
|
||||||
|
i.state.collapsed = !i.state.collapsed;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleViewSource(i: PrivateMessage) {
|
||||||
|
i.state.viewSource = !i.state.viewSource;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrivateMessageEdit() {
|
||||||
|
this.state.showEdit = false;
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrivateMessageCreate() {
|
||||||
|
this.state.showReply = false;
|
||||||
|
this.setState(this.state);
|
||||||
|
toast(i18n.t('message_sent'));
|
||||||
|
}
|
||||||
|
}
|
50
ui/src/components/search.tsx
vendored
50
ui/src/components/search.tsx
vendored
|
@ -14,15 +14,17 @@ import {
|
||||||
SearchType,
|
SearchType,
|
||||||
CreatePostLikeResponse,
|
CreatePostLikeResponse,
|
||||||
CommentResponse,
|
CommentResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import {
|
import {
|
||||||
msgOp,
|
wsJsonToRes,
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
routeSearchTypeToEnum,
|
routeSearchTypeToEnum,
|
||||||
routeSortTypeToEnum,
|
routeSortTypeToEnum,
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
showAvatars,
|
showAvatars,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
|
@ -47,7 +49,6 @@ export class Search extends Component<any, SearchState> {
|
||||||
sort: this.getSortTypeFromProps(this.props),
|
sort: this.getSortTypeFromProps(this.props),
|
||||||
page: this.getPageFromProps(this.props),
|
page: this.getPageFromProps(this.props),
|
||||||
searchResponse: {
|
searchResponse: {
|
||||||
op: null,
|
|
||||||
type_: null,
|
type_: null,
|
||||||
posts: [],
|
posts: [],
|
||||||
comments: [],
|
comments: [],
|
||||||
|
@ -400,7 +401,6 @@ export class Search extends Component<any, SearchState> {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{res &&
|
{res &&
|
||||||
res.op &&
|
|
||||||
res.posts.length == 0 &&
|
res.posts.length == 0 &&
|
||||||
res.comments.length == 0 &&
|
res.comments.length == 0 &&
|
||||||
res.communities.length == 0 &&
|
res.communities.length == 0 &&
|
||||||
|
@ -476,44 +476,44 @@ export class Search extends Component<any, SearchState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.Search) {
|
} else if (res.op == UserOperation.Search) {
|
||||||
let res: SearchResponse = msg;
|
let data = res.data as SearchResponse;
|
||||||
this.state.searchResponse = res;
|
this.state.searchResponse = data;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
document.title = `${i18n.t('search')} - ${this.state.q} - ${
|
document.title = `${i18n.t('search')} - ${this.state.q} - ${
|
||||||
WebSocketService.Instance.site.name
|
WebSocketService.Instance.site.name
|
||||||
}`;
|
}`;
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateCommentLike) {
|
} else if (res.op == UserOperation.CreateCommentLike) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found: Comment = this.state.searchResponse.comments.find(
|
let found: Comment = this.state.searchResponse.comments.find(
|
||||||
c => c.id === res.comment.id
|
c => c.id === data.comment.id
|
||||||
);
|
);
|
||||||
found.score = res.comment.score;
|
found.score = data.comment.score;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = data.comment.upvotes;
|
||||||
found.downvotes = res.comment.downvotes;
|
found.downvotes = data.comment.downvotes;
|
||||||
if (res.comment.my_vote !== null) {
|
if (data.comment.my_vote !== null) {
|
||||||
found.my_vote = res.comment.my_vote;
|
found.my_vote = data.comment.my_vote;
|
||||||
found.upvoteLoading = false;
|
found.upvoteLoading = false;
|
||||||
found.downvoteLoading = false;
|
found.downvoteLoading = false;
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePostLike) {
|
} else if (res.op == UserOperation.CreatePostLike) {
|
||||||
let res: CreatePostLikeResponse = msg;
|
let data = res.data as CreatePostLikeResponse;
|
||||||
let found = this.state.searchResponse.posts.find(
|
let found = this.state.searchResponse.posts.find(
|
||||||
c => c.id == res.post.id
|
c => c.id == data.post.id
|
||||||
);
|
);
|
||||||
found.my_vote = res.post.my_vote;
|
found.my_vote = data.post.my_vote;
|
||||||
found.score = res.post.score;
|
found.score = data.post.score;
|
||||||
found.upvotes = res.post.upvotes;
|
found.upvotes = data.post.upvotes;
|
||||||
found.downvotes = res.post.downvotes;
|
found.downvotes = data.post.downvotes;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
ui/src/components/setup.tsx
vendored
35
ui/src/components/setup.tsx
vendored
|
@ -1,9 +1,14 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { RegisterForm, LoginResponse, UserOperation } from '../interfaces';
|
import {
|
||||||
|
RegisterForm,
|
||||||
|
LoginResponse,
|
||||||
|
UserOperation,
|
||||||
|
WebSocketJsonResponse,
|
||||||
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { wsJsonToRes, toast } from '../utils';
|
||||||
import { SiteForm } from './site-form';
|
import { SiteForm } from './site-form';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -35,14 +40,7 @@ export class Setup extends Component<any, State> {
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
retryWhen(errors =>
|
|
||||||
errors.pipe(
|
|
||||||
delay(3000),
|
|
||||||
take(10)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
msg => this.parseMessage(msg),
|
msg => this.parseMessage(msg),
|
||||||
err => console.error(err),
|
err => console.error(err),
|
||||||
|
@ -188,21 +186,20 @@ export class Setup extends Component<any, State> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.state.userLoading = false;
|
this.state.userLoading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.Register) {
|
} else if (res.op == UserOperation.Register) {
|
||||||
|
let data = res.data as LoginResponse;
|
||||||
this.state.userLoading = false;
|
this.state.userLoading = false;
|
||||||
this.state.doneRegisteringUser = true;
|
this.state.doneRegisteringUser = true;
|
||||||
let res: LoginResponse = msg;
|
UserService.Instance.login(data);
|
||||||
UserService.Instance.login(res);
|
|
||||||
console.log(res);
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateSite) {
|
} else if (res.op == UserOperation.CreateSite) {
|
||||||
this.props.history.push('/');
|
this.props.history.push('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
169
ui/src/components/user.tsx
vendored
169
ui/src/components/user.tsx
vendored
|
@ -19,10 +19,11 @@ import {
|
||||||
AddAdminResponse,
|
AddAdminResponse,
|
||||||
DeleteAccountForm,
|
DeleteAccountForm,
|
||||||
CreatePostLikeResponse,
|
CreatePostLikeResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import {
|
||||||
msgOp,
|
wsJsonToRes,
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
routeSortTypeToEnum,
|
routeSortTypeToEnum,
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
|
@ -30,6 +31,7 @@ import {
|
||||||
setTheme,
|
setTheme,
|
||||||
languages,
|
languages,
|
||||||
showAvatars,
|
showAvatars,
|
||||||
|
toast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
|
@ -405,13 +407,30 @@ export class User extends Component<any, UserState> {
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{this.isCurrentUser && (
|
{this.isCurrentUser ? (
|
||||||
<button
|
<button
|
||||||
class="btn btn-block btn-secondary mt-3"
|
class="btn btn-block btn-secondary mt-3"
|
||||||
onClick={linkEvent(this, this.handleLogoutClick)}
|
onClick={linkEvent(this, this.handleLogoutClick)}
|
||||||
>
|
>
|
||||||
<T i18nKey="logout">#</T>
|
<T i18nKey="logout">#</T>
|
||||||
</button>
|
</button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
className={`btn btn-block btn-secondary mt-3 ${!this.state
|
||||||
|
.user.matrix_user_id && 'disabled'}`}
|
||||||
|
target="_blank"
|
||||||
|
href={`https://matrix.to/#/${this.state.user.matrix_user_id}`}
|
||||||
|
>
|
||||||
|
{i18n.t('send_secure_message')}
|
||||||
|
</a>
|
||||||
|
<Link
|
||||||
|
class="btn btn-block btn-secondary mt-3"
|
||||||
|
to={`/create_private_message?recipient_id=${this.state.user.id}`}
|
||||||
|
>
|
||||||
|
{i18n.t('send_message')}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -539,6 +558,26 @@ export class User extends Component<any, UserState> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-lg-5 col-form-label">
|
||||||
|
<a href="https://about.riot.im/" target="_blank">
|
||||||
|
{i18n.t('matrix_user_id')}
|
||||||
|
</a>
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="@user:example.com"
|
||||||
|
value={this.state.userSettingsForm.matrix_user_id}
|
||||||
|
onInput={linkEvent(
|
||||||
|
this,
|
||||||
|
this.handleUserSettingsMatrixUserIdChange
|
||||||
|
)}
|
||||||
|
minLength={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-lg-5 col-form-label">
|
<label class="col-lg-5 col-form-label">
|
||||||
<T i18nKey="new_password">#</T>
|
<T i18nKey="new_password">#</T>
|
||||||
|
@ -875,6 +914,17 @@ export class User extends Component<any, UserState> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserSettingsMatrixUserIdChange(i: User, event: any) {
|
||||||
|
i.state.userSettingsForm.matrix_user_id = event.target.value;
|
||||||
|
if (
|
||||||
|
i.state.userSettingsForm.matrix_user_id == '' &&
|
||||||
|
!i.state.user.matrix_user_id
|
||||||
|
) {
|
||||||
|
i.state.userSettingsForm.matrix_user_id = undefined;
|
||||||
|
}
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
handleUserSettingsNewPasswordChange(i: User, event: any) {
|
handleUserSettingsNewPasswordChange(i: User, event: any) {
|
||||||
i.state.userSettingsForm.new_password = event.target.value;
|
i.state.userSettingsForm.new_password = event.target.value;
|
||||||
if (i.state.userSettingsForm.new_password == '') {
|
if (i.state.userSettingsForm.new_password == '') {
|
||||||
|
@ -927,7 +977,7 @@ export class User extends Component<any, UserState> {
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
i.state.avatarLoading = false;
|
i.state.avatarLoading = false;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
alert(error);
|
toast(error, 'danger');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,27 +1013,27 @@ export class User extends Component<any, UserState> {
|
||||||
WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm);
|
WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (msg.error) {
|
if (res.error) {
|
||||||
alert(i18n.t(msg.error));
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.state.deleteAccountLoading = false;
|
this.state.deleteAccountLoading = false;
|
||||||
this.state.avatarLoading = false;
|
this.state.avatarLoading = false;
|
||||||
this.state.userSettingsLoading = false;
|
this.state.userSettingsLoading = false;
|
||||||
if (msg.error == 'couldnt_find_that_username_or_email') {
|
if (res.error == 'couldnt_find_that_username_or_email') {
|
||||||
this.context.router.history.push('/');
|
this.context.router.history.push('/');
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.GetUserDetails) {
|
} else if (res.op == UserOperation.GetUserDetails) {
|
||||||
let res: UserDetailsResponse = msg;
|
let data = res.data as UserDetailsResponse;
|
||||||
this.state.user = res.user;
|
this.state.user = data.user;
|
||||||
this.state.comments = res.comments;
|
this.state.comments = data.comments;
|
||||||
this.state.follows = res.follows;
|
this.state.follows = data.follows;
|
||||||
this.state.moderates = res.moderates;
|
this.state.moderates = data.moderates;
|
||||||
this.state.posts = res.posts;
|
this.state.posts = data.posts;
|
||||||
this.state.admins = res.admins;
|
this.state.admins = data.admins;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
if (this.isCurrentUser) {
|
if (this.isCurrentUser) {
|
||||||
this.state.userSettingsForm.show_nsfw =
|
this.state.userSettingsForm.show_nsfw =
|
||||||
|
@ -1001,71 +1051,72 @@ export class User extends Component<any, UserState> {
|
||||||
this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
|
this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
|
||||||
this.state.userSettingsForm.show_avatars =
|
this.state.userSettingsForm.show_avatars =
|
||||||
UserService.Instance.user.show_avatars;
|
UserService.Instance.user.show_avatars;
|
||||||
|
this.state.userSettingsForm.matrix_user_id = this.state.user.matrix_user_id;
|
||||||
}
|
}
|
||||||
document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
|
document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditComment) {
|
} else if (res.op == UserOperation.EditComment) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
|
|
||||||
let found = this.state.comments.find(c => c.id == res.comment.id);
|
let found = this.state.comments.find(c => c.id == data.comment.id);
|
||||||
found.content = res.comment.content;
|
found.content = data.comment.content;
|
||||||
found.updated = res.comment.updated;
|
found.updated = data.comment.updated;
|
||||||
found.removed = res.comment.removed;
|
found.removed = data.comment.removed;
|
||||||
found.deleted = res.comment.deleted;
|
found.deleted = data.comment.deleted;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = data.comment.upvotes;
|
||||||
found.downvotes = res.comment.downvotes;
|
found.downvotes = data.comment.downvotes;
|
||||||
found.score = res.comment.score;
|
found.score = data.comment.score;
|
||||||
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateComment) {
|
} else if (res.op == UserOperation.CreateComment) {
|
||||||
// let res: CommentResponse = msg;
|
// let res: CommentResponse = msg;
|
||||||
alert(i18n.t('reply_sent'));
|
toast(i18n.t('reply_sent'));
|
||||||
// this.state.comments.unshift(res.comment); // TODO do this right
|
// this.state.comments.unshift(res.comment); // TODO do this right
|
||||||
// this.setState(this.state);
|
// this.setState(this.state);
|
||||||
} else if (op == UserOperation.SaveComment) {
|
} else if (res.op == UserOperation.SaveComment) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found = this.state.comments.find(c => c.id == res.comment.id);
|
let found = this.state.comments.find(c => c.id == data.comment.id);
|
||||||
found.saved = res.comment.saved;
|
found.saved = data.comment.saved;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateCommentLike) {
|
} else if (res.op == UserOperation.CreateCommentLike) {
|
||||||
let res: CommentResponse = msg;
|
let data = res.data as CommentResponse;
|
||||||
let found: Comment = this.state.comments.find(
|
let found: Comment = this.state.comments.find(
|
||||||
c => c.id === res.comment.id
|
c => c.id === data.comment.id
|
||||||
);
|
);
|
||||||
found.score = res.comment.score;
|
found.score = data.comment.score;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = data.comment.upvotes;
|
||||||
found.downvotes = res.comment.downvotes;
|
found.downvotes = data.comment.downvotes;
|
||||||
if (res.comment.my_vote !== null) found.my_vote = res.comment.my_vote;
|
if (data.comment.my_vote !== null) found.my_vote = data.comment.my_vote;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePostLike) {
|
} else if (res.op == UserOperation.CreatePostLike) {
|
||||||
let res: CreatePostLikeResponse = msg;
|
let data = res.data as CreatePostLikeResponse;
|
||||||
let found = this.state.posts.find(c => c.id == res.post.id);
|
let found = this.state.posts.find(c => c.id == data.post.id);
|
||||||
found.my_vote = res.post.my_vote;
|
found.my_vote = data.post.my_vote;
|
||||||
found.score = res.post.score;
|
found.score = data.post.score;
|
||||||
found.upvotes = res.post.upvotes;
|
found.upvotes = data.post.upvotes;
|
||||||
found.downvotes = res.post.downvotes;
|
found.downvotes = data.post.downvotes;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.BanUser) {
|
} else if (res.op == UserOperation.BanUser) {
|
||||||
let res: BanUserResponse = msg;
|
let data = res.data as BanUserResponse;
|
||||||
this.state.comments
|
this.state.comments
|
||||||
.filter(c => c.creator_id == res.user.id)
|
.filter(c => c.creator_id == data.user.id)
|
||||||
.forEach(c => (c.banned = res.banned));
|
.forEach(c => (c.banned = data.banned));
|
||||||
this.state.posts
|
this.state.posts
|
||||||
.filter(c => c.creator_id == res.user.id)
|
.filter(c => c.creator_id == data.user.id)
|
||||||
.forEach(c => (c.banned = res.banned));
|
.forEach(c => (c.banned = data.banned));
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.AddAdmin) {
|
} else if (res.op == UserOperation.AddAdmin) {
|
||||||
let res: AddAdminResponse = msg;
|
let data = res.data as AddAdminResponse;
|
||||||
this.state.admins = res.admins;
|
this.state.admins = data.admins;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.SaveUserSettings) {
|
} else if (res.op == UserOperation.SaveUserSettings) {
|
||||||
|
let data = res.data as LoginResponse;
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
this.state.userSettingsLoading = false;
|
this.state.userSettingsLoading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
let res: LoginResponse = msg;
|
UserService.Instance.login(data);
|
||||||
UserService.Instance.login(res);
|
} else if (res.op == UserOperation.DeleteAccount) {
|
||||||
} else if (op == UserOperation.DeleteAccount) {
|
|
||||||
this.state.deleteAccountLoading = false;
|
this.state.deleteAccountLoading = false;
|
||||||
this.state.deleteAccountShowConfirm = false;
|
this.state.deleteAccountShowConfirm = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
|
|
1
ui/src/index.html
vendored
1
ui/src/index.html
vendored
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/tribute.css" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/tribute.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/toastify.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/darkly.min.css" id="darkly" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/darkly.min.css" id="darkly" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/main.css" />
|
<link rel="stylesheet" type="text/css" href="/static/assets/css/main.css" />
|
||||||
|
|
||||||
|
|
5
ui/src/index.tsx
vendored
5
ui/src/index.tsx
vendored
|
@ -7,6 +7,7 @@ import { Footer } from './components/footer';
|
||||||
import { Login } from './components/login';
|
import { Login } from './components/login';
|
||||||
import { CreatePost } from './components/create-post';
|
import { CreatePost } from './components/create-post';
|
||||||
import { CreateCommunity } from './components/create-community';
|
import { CreateCommunity } from './components/create-community';
|
||||||
|
import { CreatePrivateMessage } from './components/create-private-message';
|
||||||
import { PasswordChange } from './components/password_change';
|
import { PasswordChange } from './components/password_change';
|
||||||
import { Post } from './components/post';
|
import { Post } from './components/post';
|
||||||
import { Community } from './components/community';
|
import { Community } from './components/community';
|
||||||
|
@ -46,6 +47,10 @@ class Index extends Component<any, any> {
|
||||||
<Route path={`/login`} component={Login} />
|
<Route path={`/login`} component={Login} />
|
||||||
<Route path={`/create_post`} component={CreatePost} />
|
<Route path={`/create_post`} component={CreatePost} />
|
||||||
<Route path={`/create_community`} component={CreateCommunity} />
|
<Route path={`/create_community`} component={CreateCommunity} />
|
||||||
|
<Route
|
||||||
|
path={`/create_private_message`}
|
||||||
|
component={CreatePrivateMessage}
|
||||||
|
/>
|
||||||
<Route path={`/communities/page/:page`} component={Communities} />
|
<Route path={`/communities/page/:page`} component={Communities} />
|
||||||
<Route path={`/communities`} component={Communities} />
|
<Route path={`/communities`} component={Communities} />
|
||||||
<Route path={`/post/:id/comment/:comment_id`} component={Post} />
|
<Route path={`/post/:id/comment/:comment_id`} component={Post} />
|
||||||
|
|
115
ui/src/interfaces.ts
vendored
115
ui/src/interfaces.ts
vendored
|
@ -38,6 +38,9 @@ export enum UserOperation {
|
||||||
DeleteAccount,
|
DeleteAccount,
|
||||||
PasswordReset,
|
PasswordReset,
|
||||||
PasswordChange,
|
PasswordChange,
|
||||||
|
CreatePrivateMessage,
|
||||||
|
EditPrivateMessage,
|
||||||
|
GetPrivateMessages,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CommentSortType {
|
export enum CommentSortType {
|
||||||
|
@ -89,6 +92,7 @@ export interface UserView {
|
||||||
name: string;
|
name: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
matrix_user_id?: string;
|
||||||
fedi_name: string;
|
fedi_name: string;
|
||||||
published: string;
|
published: string;
|
||||||
number_of_posts: number;
|
number_of_posts: number;
|
||||||
|
@ -218,6 +222,21 @@ export interface Site {
|
||||||
enable_nsfw: boolean;
|
enable_nsfw: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PrivateMessage {
|
||||||
|
id: number;
|
||||||
|
creator_id: number;
|
||||||
|
recipient_id: number;
|
||||||
|
content: string;
|
||||||
|
deleted: boolean;
|
||||||
|
read: boolean;
|
||||||
|
published: string;
|
||||||
|
updated?: string;
|
||||||
|
creator_name: string;
|
||||||
|
creator_avatar?: string;
|
||||||
|
recipient_name: string;
|
||||||
|
recipient_avatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export enum BanType {
|
export enum BanType {
|
||||||
Community,
|
Community,
|
||||||
Site,
|
Site,
|
||||||
|
@ -230,7 +249,6 @@ export interface FollowCommunityForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetFollowedCommunitiesResponse {
|
export interface GetFollowedCommunitiesResponse {
|
||||||
op: string;
|
|
||||||
communities: Array<CommunityUser>;
|
communities: Array<CommunityUser>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +263,6 @@ export interface GetUserDetailsForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserDetailsResponse {
|
export interface UserDetailsResponse {
|
||||||
op: string;
|
|
||||||
user: UserView;
|
user: UserView;
|
||||||
follows: Array<CommunityUser>;
|
follows: Array<CommunityUser>;
|
||||||
moderates: Array<CommunityUser>;
|
moderates: Array<CommunityUser>;
|
||||||
|
@ -263,7 +280,6 @@ export interface GetRepliesForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetRepliesResponse {
|
export interface GetRepliesResponse {
|
||||||
op: string;
|
|
||||||
replies: Array<Comment>;
|
replies: Array<Comment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +292,6 @@ export interface GetUserMentionsForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetUserMentionsResponse {
|
export interface GetUserMentionsResponse {
|
||||||
op: string;
|
|
||||||
mentions: Array<Comment>;
|
mentions: Array<Comment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +302,6 @@ export interface EditUserMentionForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserMentionResponse {
|
export interface UserMentionResponse {
|
||||||
op: string;
|
|
||||||
mention: Comment;
|
mention: Comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,7 +315,6 @@ export interface BanFromCommunityForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BanFromCommunityResponse {
|
export interface BanFromCommunityResponse {
|
||||||
op: string;
|
|
||||||
user: UserView;
|
user: UserView;
|
||||||
banned: boolean;
|
banned: boolean;
|
||||||
}
|
}
|
||||||
|
@ -325,7 +338,6 @@ export interface TransferSiteForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddModToCommunityResponse {
|
export interface AddModToCommunityResponse {
|
||||||
op: string;
|
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +349,6 @@ export interface GetModlogForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetModlogResponse {
|
export interface GetModlogResponse {
|
||||||
op: string;
|
|
||||||
removed_posts: Array<ModRemovePost>;
|
removed_posts: Array<ModRemovePost>;
|
||||||
locked_posts: Array<ModLockPost>;
|
locked_posts: Array<ModLockPost>;
|
||||||
stickied_posts: Array<ModStickyPost>;
|
stickied_posts: Array<ModStickyPost>;
|
||||||
|
@ -478,7 +489,6 @@ export interface RegisterForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
op: string;
|
|
||||||
jwt: string;
|
jwt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,6 +500,7 @@ export interface UserSettingsForm {
|
||||||
lang: string;
|
lang: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
matrix_user_id?: string;
|
||||||
new_password?: string;
|
new_password?: string;
|
||||||
new_password_verify?: string;
|
new_password_verify?: string;
|
||||||
old_password?: string;
|
old_password?: string;
|
||||||
|
@ -513,14 +524,12 @@ export interface CommunityForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetCommunityResponse {
|
export interface GetCommunityResponse {
|
||||||
op: string;
|
|
||||||
community: Community;
|
community: Community;
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
admins: Array<UserView>;
|
admins: Array<UserView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommunityResponse {
|
export interface CommunityResponse {
|
||||||
op: string;
|
|
||||||
community: Community;
|
community: Community;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,12 +541,10 @@ export interface ListCommunitiesForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListCommunitiesResponse {
|
export interface ListCommunitiesResponse {
|
||||||
op: string;
|
|
||||||
communities: Array<Community>;
|
communities: Array<Community>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListCategoriesResponse {
|
export interface ListCategoriesResponse {
|
||||||
op: string;
|
|
||||||
categories: Array<Category>;
|
categories: Array<Category>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,7 +573,6 @@ export interface PostFormParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetPostResponse {
|
export interface GetPostResponse {
|
||||||
op: string;
|
|
||||||
post: Post;
|
post: Post;
|
||||||
comments: Array<Comment>;
|
comments: Array<Comment>;
|
||||||
community: Community;
|
community: Community;
|
||||||
|
@ -581,7 +587,6 @@ export interface SavePostForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostResponse {
|
export interface PostResponse {
|
||||||
op: string;
|
|
||||||
post: Post;
|
post: Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,7 +610,6 @@ export interface SaveCommentForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommentResponse {
|
export interface CommentResponse {
|
||||||
op: string;
|
|
||||||
comment: Comment;
|
comment: Comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,7 +635,6 @@ export interface GetPostsForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetPostsResponse {
|
export interface GetPostsResponse {
|
||||||
op: string;
|
|
||||||
posts: Array<Post>;
|
posts: Array<Post>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,7 +645,6 @@ export interface CreatePostLikeForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePostLikeResponse {
|
export interface CreatePostLikeResponse {
|
||||||
op: string;
|
|
||||||
post: Post;
|
post: Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,7 +658,6 @@ export interface SiteForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetSiteResponse {
|
export interface GetSiteResponse {
|
||||||
op: string;
|
|
||||||
site: Site;
|
site: Site;
|
||||||
admins: Array<UserView>;
|
admins: Array<UserView>;
|
||||||
banned: Array<UserView>;
|
banned: Array<UserView>;
|
||||||
|
@ -664,7 +665,6 @@ export interface GetSiteResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SiteResponse {
|
export interface SiteResponse {
|
||||||
op: string;
|
|
||||||
site: Site;
|
site: Site;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,7 +677,6 @@ export interface BanUserForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BanUserResponse {
|
export interface BanUserResponse {
|
||||||
op: string;
|
|
||||||
user: UserView;
|
user: UserView;
|
||||||
banned: boolean;
|
banned: boolean;
|
||||||
}
|
}
|
||||||
|
@ -689,7 +688,6 @@ export interface AddAdminForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddAdminResponse {
|
export interface AddAdminResponse {
|
||||||
op: string;
|
|
||||||
admins: Array<UserView>;
|
admins: Array<UserView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -704,7 +702,6 @@ export interface SearchForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchResponse {
|
export interface SearchResponse {
|
||||||
op: string;
|
|
||||||
type_: string;
|
type_: string;
|
||||||
posts?: Array<Post>;
|
posts?: Array<Post>;
|
||||||
comments?: Array<Comment>;
|
comments?: Array<Comment>;
|
||||||
|
@ -720,12 +717,78 @@ export interface PasswordResetForm {
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PasswordResetResponse {
|
// export interface PasswordResetResponse {
|
||||||
op: string;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
export interface PasswordChangeForm {
|
export interface PasswordChangeForm {
|
||||||
token: string;
|
token: string;
|
||||||
password: string;
|
password: string;
|
||||||
password_verify: string;
|
password_verify: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PrivateMessageForm {
|
||||||
|
content: string;
|
||||||
|
recipient_id: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivateMessageFormParams {
|
||||||
|
recipient_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditPrivateMessageForm {
|
||||||
|
edit_id: number;
|
||||||
|
content?: string;
|
||||||
|
deleted?: boolean;
|
||||||
|
read?: boolean;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetPrivateMessagesForm {
|
||||||
|
unread_only: boolean;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivateMessagesResponse {
|
||||||
|
messages: Array<PrivateMessage>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivateMessageResponse {
|
||||||
|
message: PrivateMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseType =
|
||||||
|
| SiteResponse
|
||||||
|
| GetFollowedCommunitiesResponse
|
||||||
|
| ListCommunitiesResponse
|
||||||
|
| GetPostsResponse
|
||||||
|
| CreatePostLikeResponse
|
||||||
|
| GetRepliesResponse
|
||||||
|
| GetUserMentionsResponse
|
||||||
|
| ListCategoriesResponse
|
||||||
|
| CommunityResponse
|
||||||
|
| CommentResponse
|
||||||
|
| UserMentionResponse
|
||||||
|
| LoginResponse
|
||||||
|
| GetModlogResponse
|
||||||
|
| SearchResponse
|
||||||
|
| BanFromCommunityResponse
|
||||||
|
| AddModToCommunityResponse
|
||||||
|
| BanUserResponse
|
||||||
|
| AddAdminResponse
|
||||||
|
| PrivateMessageResponse
|
||||||
|
| PrivateMessagesResponse;
|
||||||
|
|
||||||
|
export interface WebSocketResponse {
|
||||||
|
op: UserOperation;
|
||||||
|
data: ResponseType;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebSocketJsonResponse {
|
||||||
|
op: string;
|
||||||
|
data: ResponseType;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
29
ui/src/services/WebSocketService.ts
vendored
29
ui/src/services/WebSocketService.ts
vendored
|
@ -32,12 +32,16 @@ import {
|
||||||
DeleteAccountForm,
|
DeleteAccountForm,
|
||||||
PasswordResetForm,
|
PasswordResetForm,
|
||||||
PasswordChangeForm,
|
PasswordChangeForm,
|
||||||
|
PrivateMessageForm,
|
||||||
|
EditPrivateMessageForm,
|
||||||
|
GetPrivateMessagesForm,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { webSocket } from 'rxjs/webSocket';
|
import { webSocket } from 'rxjs/webSocket';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay } from 'rxjs/operators';
|
||||||
import { UserService } from './';
|
import { UserService } from './';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
import { toast } from '../utils';
|
||||||
|
|
||||||
export class WebSocketService {
|
export class WebSocketService {
|
||||||
private static _instance: WebSocketService;
|
private static _instance: WebSocketService;
|
||||||
|
@ -285,6 +289,27 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.PasswordChange, form));
|
this.subject.next(this.wsSendWrapper(UserOperation.PasswordChange, form));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createPrivateMessage(form: PrivateMessageForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.subject.next(
|
||||||
|
this.wsSendWrapper(UserOperation.CreatePrivateMessage, form)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public editPrivateMessage(form: EditPrivateMessageForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.subject.next(
|
||||||
|
this.wsSendWrapper(UserOperation.EditPrivateMessage, form)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrivateMessages(form: GetPrivateMessagesForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.subject.next(
|
||||||
|
this.wsSendWrapper(UserOperation.GetPrivateMessages, form)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private wsSendWrapper(op: UserOperation, data: any) {
|
private wsSendWrapper(op: UserOperation, data: any) {
|
||||||
let send = { op: UserOperation[op], data: data };
|
let send = { op: UserOperation[op], data: data };
|
||||||
console.log(send);
|
console.log(send);
|
||||||
|
@ -294,7 +319,7 @@ export class WebSocketService {
|
||||||
private setAuth(obj: any, throwErr: boolean = true) {
|
private setAuth(obj: any, throwErr: boolean = true) {
|
||||||
obj.auth = UserService.Instance.auth;
|
obj.auth = UserService.Instance.auth;
|
||||||
if (obj.auth == null && throwErr) {
|
if (obj.auth == null && throwErr) {
|
||||||
alert(i18n.t('not_logged_in'));
|
toast(i18n.t('not_logged_in'), 'danger');
|
||||||
throw 'Not logged in';
|
throw 'Not logged in';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
ui/src/translations/en.ts
vendored
14
ui/src/translations/en.ts
vendored
|
@ -23,6 +23,10 @@ export const en = {
|
||||||
list_of_communities: 'List of communities',
|
list_of_communities: 'List of communities',
|
||||||
number_of_communities: '{{count}} Communities',
|
number_of_communities: '{{count}} Communities',
|
||||||
community_reqs: 'lowercase, underscores, and no spaces.',
|
community_reqs: 'lowercase, underscores, and no spaces.',
|
||||||
|
create_private_message: 'Create Private Message',
|
||||||
|
send_secure_message: 'Send Secure Message',
|
||||||
|
send_message: 'Send Message',
|
||||||
|
message: 'Message',
|
||||||
edit: 'edit',
|
edit: 'edit',
|
||||||
reply: 'reply',
|
reply: 'reply',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
@ -109,6 +113,7 @@ export const en = {
|
||||||
replies: 'Replies',
|
replies: 'Replies',
|
||||||
mentions: 'Mentions',
|
mentions: 'Mentions',
|
||||||
reply_sent: 'Reply sent',
|
reply_sent: 'Reply sent',
|
||||||
|
message_sent: 'Message sent',
|
||||||
search: 'Search',
|
search: 'Search',
|
||||||
overview: 'Overview',
|
overview: 'Overview',
|
||||||
view: 'View',
|
view: 'View',
|
||||||
|
@ -119,6 +124,7 @@ export const en = {
|
||||||
notifications_error:
|
notifications_error:
|
||||||
'Desktop notifications not available in your browser. Try Firefox or Chrome.',
|
'Desktop notifications not available in your browser. Try Firefox or Chrome.',
|
||||||
unread_messages: 'Unread Messages',
|
unread_messages: 'Unread Messages',
|
||||||
|
messages: 'Messages',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
verify_password: 'Verify Password',
|
verify_password: 'Verify Password',
|
||||||
old_password: 'Old Password',
|
old_password: 'Old Password',
|
||||||
|
@ -128,6 +134,9 @@ export const en = {
|
||||||
new_password: 'New Password',
|
new_password: 'New Password',
|
||||||
no_email_setup: "This server hasn't correctly set up email.",
|
no_email_setup: "This server hasn't correctly set up email.",
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
|
matrix_user_id: 'Matrix User',
|
||||||
|
private_message_disclaimer:
|
||||||
|
'Warning: Private messages in Lemmy are not secure. Please create an account on <1>Riot.im</1> for secure messaging.',
|
||||||
send_notifications_to_email: 'Send notifications to Email',
|
send_notifications_to_email: 'Send notifications to Email',
|
||||||
optional: 'Optional',
|
optional: 'Optional',
|
||||||
expires: 'Expires',
|
expires: 'Expires',
|
||||||
|
@ -172,6 +181,7 @@ export const en = {
|
||||||
joined: 'Joined',
|
joined: 'Joined',
|
||||||
by: 'by',
|
by: 'by',
|
||||||
to: 'to',
|
to: 'to',
|
||||||
|
from: 'from',
|
||||||
transfer_community: 'transfer community',
|
transfer_community: 'transfer community',
|
||||||
transfer_site: 'transfer site',
|
transfer_site: 'transfer site',
|
||||||
are_you_sure: 'are you sure?',
|
are_you_sure: 'are you sure?',
|
||||||
|
@ -181,6 +191,7 @@ export const en = {
|
||||||
landing_0:
|
landing_0:
|
||||||
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
||||||
not_logged_in: 'Not logged in.',
|
not_logged_in: 'Not logged in.',
|
||||||
|
logged_in: 'Logged in.',
|
||||||
community_ban: 'You have been banned from this community.',
|
community_ban: 'You have been banned from this community.',
|
||||||
site_ban: 'You have been banned from the site',
|
site_ban: 'You have been banned from the site',
|
||||||
couldnt_create_comment: "Couldn't create comment.",
|
couldnt_create_comment: "Couldn't create comment.",
|
||||||
|
@ -215,5 +226,8 @@ export const en = {
|
||||||
email_already_exists: 'Email already exists.',
|
email_already_exists: 'Email already exists.',
|
||||||
couldnt_update_user: "Couldn't update user.",
|
couldnt_update_user: "Couldn't update user.",
|
||||||
system_err_login: 'System error. Try logging out and back in.',
|
system_err_login: 'System error. Try logging out and back in.',
|
||||||
|
couldnt_create_private_message: "Couldn't create private message.",
|
||||||
|
no_private_message_edit_allowed: 'Not allowed to edit private message.',
|
||||||
|
couldnt_update_private_message: "Couldn't update private message.",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
23
ui/src/utils.ts
vendored
23
ui/src/utils.ts
vendored
|
@ -11,10 +11,13 @@ import 'moment/locale/it';
|
||||||
import {
|
import {
|
||||||
UserOperation,
|
UserOperation,
|
||||||
Comment,
|
Comment,
|
||||||
|
PrivateMessage,
|
||||||
User,
|
User,
|
||||||
SortType,
|
SortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
SearchType,
|
SearchType,
|
||||||
|
WebSocketResponse,
|
||||||
|
WebSocketJsonResponse,
|
||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import { UserService } from './services/UserService';
|
import { UserService } from './services/UserService';
|
||||||
import markdown_it from 'markdown-it';
|
import markdown_it from 'markdown-it';
|
||||||
|
@ -22,6 +25,7 @@ import markdownitEmoji from 'markdown-it-emoji/light';
|
||||||
import markdown_it_container from 'markdown-it-container';
|
import markdown_it_container from 'markdown-it-container';
|
||||||
import * as twemoji from 'twemoji';
|
import * as twemoji from 'twemoji';
|
||||||
import * as emojiShortName from 'emoji-short-name';
|
import * as emojiShortName from 'emoji-short-name';
|
||||||
|
import Toastify from 'toastify-js';
|
||||||
|
|
||||||
export const repoUrl = 'https://github.com/dessalines/lemmy';
|
export const repoUrl = 'https://github.com/dessalines/lemmy';
|
||||||
export const markdownHelpUrl = 'https://commonmark.org/help/';
|
export const markdownHelpUrl = 'https://commonmark.org/help/';
|
||||||
|
@ -38,9 +42,12 @@ export function randomStr() {
|
||||||
.substr(2, 10);
|
.substr(2, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function msgOp(msg: any): UserOperation {
|
export function wsJsonToRes(msg: WebSocketJsonResponse): WebSocketResponse {
|
||||||
let opStr: string = msg.op;
|
let opStr: string = msg.op;
|
||||||
return UserOperation[opStr];
|
return {
|
||||||
|
op: UserOperation[opStr],
|
||||||
|
data: msg.data,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const md = new markdown_it({
|
export const md = new markdown_it({
|
||||||
|
@ -361,3 +368,15 @@ export function imageThumbnailer(url: string): string {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCommentType(item: Comment | PrivateMessage): item is Comment {
|
||||||
|
return (item as Comment).community_id !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toast(text: string, background: string = 'success') {
|
||||||
|
let backgroundColor = `var(--${background})`;
|
||||||
|
Toastify({
|
||||||
|
text: text,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
}).showToast();
|
||||||
|
}
|
||||||
|
|
5
ui/yarn.lock
vendored
5
ui/yarn.lock
vendored
|
@ -4622,6 +4622,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
||||||
regex-not "^1.0.2"
|
regex-not "^1.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
|
toastify-js@^1.6.2:
|
||||||
|
version "1.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/toastify-js/-/toastify-js-1.6.2.tgz#38af35625797d3d3f51fa09851f0bda449271423"
|
||||||
|
integrity sha512-ECQzgjTjxaElfwp/8e8qoIYx7U5rU2G54e5aiPMv+UtmGOYEitrtNp/Kr8uMgntnQNrDZEQJNGjBtoNnEgR5EA==
|
||||||
|
|
||||||
toidentifier@1.0.0:
|
toidentifier@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
|
|
Reference in a new issue