From 5351e379d5e89a50aaa82f01f414191981cd9ca6 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 15 Apr 2019 16:12:06 -0700 Subject: [PATCH] Commiting before I lose everything. I'll do this properly in a merge --- .../2019-02-26-002946_create_user/down.sql | 3 +- .../2019-02-26-002946_create_user/up.sql | 9 + .../down.sql | 1 + .../2019-02-27-170003_create_community/up.sql | 15 +- .../2019-03-03-163336_create_post/up.sql | 2 + .../2019-03-05-233828_create_comment/up.sql | 1 + .../2019-03-30-212058_create_post_view/up.sql | 33 +- .../down.sql | 1 + .../up.sql | 12 +- .../up.sql | 10 +- .../down.sql | 4 +- .../up.sql | 30 +- .../2019-04-08-015947_create_user_view/up.sql | 3 +- .../down.sql | 8 + .../2019-04-11-144915_create_mod_views/up.sql | 61 ++ server/src/actions/comment.rs | 10 + server/src/actions/comment_view.rs | 24 +- server/src/actions/community.rs | 65 +- server/src/actions/community_view.rs | 49 +- server/src/actions/mod.rs | 2 + server/src/actions/moderator.rs | 655 ++++++++++++++++++ server/src/actions/moderator_views.rs | 427 ++++++++++++ server/src/actions/post.rs | 13 +- server/src/actions/post_view.rs | 26 +- server/src/actions/user.rs | 8 + server/src/actions/user_view.rs | 4 + server/src/apub.rs | 2 + server/src/lib.rs | 9 + server/src/schema.rs | 136 ++++ server/src/websocket_server/server.rs | 430 +++++++++++- ui/src/components/comment-form.tsx | 13 +- ui/src/components/comment-node.tsx | 169 ++++- ui/src/components/comment-nodes.tsx | 11 +- ui/src/components/communities.tsx | 3 +- ui/src/components/community.tsx | 6 +- ui/src/components/modlog.tsx | 184 +++++ ui/src/components/moment-time.tsx | 6 +- ui/src/components/navbar.tsx | 3 + ui/src/components/post-form.tsx | 6 +- ui/src/components/post-listing.tsx | 122 +++- ui/src/components/post.tsx | 22 +- ui/src/components/sidebar.tsx | 159 +++-- ui/src/components/user.tsx | 19 +- ui/src/index.tsx | 2 + ui/src/interfaces.ts | 371 +++++++--- ui/src/main.css | 5 + ui/src/services/WebSocketService.ts | 16 +- ui/src/utils.ts | 8 + ui/src/version.ts | 2 +- 49 files changed, 2885 insertions(+), 295 deletions(-) create mode 100644 server/migrations/2019-04-11-144915_create_mod_views/down.sql create mode 100644 server/migrations/2019-04-11-144915_create_mod_views/up.sql create mode 100644 server/src/actions/moderator.rs create mode 100644 server/src/actions/moderator_views.rs create mode 100644 ui/src/components/modlog.tsx diff --git a/server/migrations/2019-02-26-002946_create_user/down.sql b/server/migrations/2019-02-26-002946_create_user/down.sql index 606be6e1a..67a280d62 100644 --- a/server/migrations/2019-02-26-002946_create_user/down.sql +++ b/server/migrations/2019-02-26-002946_create_user/down.sql @@ -1 +1,2 @@ -drop table user_ +drop table user_ban; +drop table user_; diff --git a/server/migrations/2019-02-26-002946_create_user/up.sql b/server/migrations/2019-02-26-002946_create_user/up.sql index 80e6e92a3..83112e9ba 100644 --- a/server/migrations/2019-02-26-002946_create_user/up.sql +++ b/server/migrations/2019-02-26-002946_create_user/up.sql @@ -6,9 +6,18 @@ create table user_ ( password_encrypted text not null, email text unique, icon bytea, + admin boolean default false, + banned boolean default false, published timestamp not null default now(), updated timestamp, unique(name, fedi_name) ); +create table user_ban ( + id serial primary key, + user_id int references user_ on update cascade on delete cascade not null, + published timestamp not null default now(), + unique (user_id) +); + insert into user_ (name, fedi_name, password_encrypted) values ('admin', 'TBD', 'TBD'); diff --git a/server/migrations/2019-02-27-170003_create_community/down.sql b/server/migrations/2019-02-27-170003_create_community/down.sql index f293dfad4..d5bd39940 100644 --- a/server/migrations/2019-02-27-170003_create_community/down.sql +++ b/server/migrations/2019-02-27-170003_create_community/down.sql @@ -1,3 +1,4 @@ +drop table community_user_ban;; drop table community_moderator; drop table community_follower; drop table community; diff --git a/server/migrations/2019-02-27-170003_create_community/up.sql b/server/migrations/2019-02-27-170003_create_community/up.sql index 46b4df52d..ad47adfe8 100644 --- a/server/migrations/2019-02-27-170003_create_community/up.sql +++ b/server/migrations/2019-02-27-170003_create_community/up.sql @@ -38,6 +38,7 @@ create table community ( description text, category_id int references category on update cascade on delete cascade not null, creator_id int references user_ on update cascade on delete cascade not null, + removed boolean default false, published timestamp not null default now(), updated timestamp ); @@ -46,14 +47,24 @@ create table community_moderator ( id serial primary key, community_id int references community on update cascade on delete cascade not null, user_id int references user_ on update cascade on delete cascade not null, - published timestamp not null default now() + published timestamp not null default now(), + unique (community_id, user_id) ); create table community_follower ( id serial primary key, community_id int references community on update cascade on delete cascade not null, user_id int references user_ on update cascade on delete cascade not null, - published timestamp not null default now() + published timestamp not null default now(), + unique (community_id, user_id) +); + +create table community_user_ban ( + id serial primary key, + community_id int references community on update cascade on delete cascade not null, + user_id int references user_ on update cascade on delete cascade not null, + published timestamp not null default now(), + unique (community_id, user_id) ); insert into community (name, title, category_id, creator_id) values ('main', 'The Default Community', 1, 1); diff --git a/server/migrations/2019-03-03-163336_create_post/up.sql b/server/migrations/2019-03-03-163336_create_post/up.sql index aaa6911eb..c3b7c0b8b 100644 --- a/server/migrations/2019-03-03-163336_create_post/up.sql +++ b/server/migrations/2019-03-03-163336_create_post/up.sql @@ -5,6 +5,8 @@ create table post ( body text, creator_id int references user_ on update cascade on delete cascade not null, community_id int references community on update cascade on delete cascade not null, + removed boolean default false, + locked boolean default false, published timestamp not null default now(), updated timestamp ); diff --git a/server/migrations/2019-03-05-233828_create_comment/up.sql b/server/migrations/2019-03-05-233828_create_comment/up.sql index aa20d3588..214d50a6a 100644 --- a/server/migrations/2019-03-05-233828_create_comment/up.sql +++ b/server/migrations/2019-03-05-233828_create_comment/up.sql @@ -4,6 +4,7 @@ create table comment ( post_id int references post on update cascade on delete cascade not null, parent_id int references comment on update cascade on delete cascade, content text not null, + removed boolean default false, published timestamp not null default now(), updated timestamp ); diff --git a/server/migrations/2019-03-30-212058_create_post_view/up.sql b/server/migrations/2019-03-30-212058_create_post_view/up.sql index 95789c734..ecf3280a4 100644 --- a/server/migrations/2019-03-30-212058_create_post_view/up.sql +++ b/server/migrations/2019-03-30-212058_create_post_view/up.sql @@ -30,7 +30,8 @@ select ap.*, u.id as user_id, coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +u.admin or (select cm.id::bool from community_moderator cm where u.id = cm.user_id and cm.community_id = ap.community_id) as am_mod from user_ u cross join all_post ap left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id @@ -41,33 +42,7 @@ select ap.*, null as user_id, null as my_vote, -null as subscribed +null as subscribed, +null as am_mod from all_post ap ; - -/* The old post view */ -/* create view post_view as */ -/* select */ -/* u.id as user_id, */ -/* pl.score as my_vote, */ -/* p.id as id, */ -/* p.name as name, */ -/* p.url, */ -/* p.body, */ -/* p.creator_id, */ -/* (select name from user_ where p.creator_id = user_.id) creator_name, */ -/* p.community_id, */ -/* (select name from community where p.community_id = community.id) as community_name, */ -/* (select count(*) from comment where comment.post_id = p.id) as number_of_comments, */ -/* coalesce(sum(pl.score) over (partition by p.id), 0) as score, */ -/* count (case when pl.score = 1 then 1 else null end) over (partition by p.id) as upvotes, */ -/* count (case when pl.score = -1 then 1 else null end) over (partition by p.id) as downvotes, */ -/* hot_rank(coalesce(sum(pl.score) over (partition by p.id) , 0), p.published) as hot_rank, */ -/* p.published, */ -/* p.updated */ -/* from user_ u */ -/* cross join post p */ -/* left join post_like pl on u.id = pl.user_id and p.id = pl.post_id; */ - - - diff --git a/server/migrations/2019-04-03-155205_create_community_view/down.sql b/server/migrations/2019-04-03-155205_create_community_view/down.sql index 6c7e87084..0c7a33c80 100644 --- a/server/migrations/2019-04-03-155205_create_community_view/down.sql +++ b/server/migrations/2019-04-03-155205_create_community_view/down.sql @@ -1,3 +1,4 @@ drop view community_view; drop view community_moderator_view; drop view community_follower_view; +drop view community_user_ban_view; diff --git a/server/migrations/2019-04-03-155205_create_community_view/up.sql b/server/migrations/2019-04-03-155205_create_community_view/up.sql index 7c6087428..510fd0f21 100644 --- a/server/migrations/2019-04-03-155205_create_community_view/up.sql +++ b/server/migrations/2019-04-03-155205_create_community_view/up.sql @@ -13,7 +13,8 @@ with all_community as select ac.*, u.id as user_id, -cf.id::boolean as subscribed +cf.id::boolean as subscribed, +u.admin or (select cm.id::bool from community_moderator cm where u.id = cm.user_id and cm.community_id = ac.id) as am_mod from user_ u cross join all_community ac left join community_follower cf on u.id = cf.user_id and ac.id = cf.community_id @@ -23,7 +24,8 @@ union all select ac.*, null as user_id, -null as subscribed +null as subscribed, +null as am_mod from all_community ac ; @@ -38,3 +40,9 @@ select *, (select name from user_ u where cf.user_id = u.id) as user_name, (select name from community c where cf.community_id = c.id) as community_name from community_follower cf; + +create view community_user_ban_view as +select *, +(select name from user_ u where cm.user_id = u.id) as user_name, +(select name from community c where cm.community_id = c.id) as community_name +from community_user_ban cm; diff --git a/server/migrations/2019-04-03-155309_create_comment_view/up.sql b/server/migrations/2019-04-03-155309_create_comment_view/up.sql index a4d2be9f2..a73b61825 100644 --- a/server/migrations/2019-04-03-155309_create_comment_view/up.sql +++ b/server/migrations/2019-04-03-155309_create_comment_view/up.sql @@ -3,7 +3,9 @@ with all_comment as ( select c.*, - (select name from user_ where c.creator_id = user_.id) creator_name, + (select community_id from post p where p.id = c.post_id), + (select cb.id::bool from community_user_ban cb where c.creator_id = cb.user_id) as banned, + (select name from user_ where c.creator_id = user_.id) as creator_name, coalesce(sum(cl.score), 0) as score, count (case when cl.score = 1 then 1 else null end) as upvotes, count (case when cl.score = -1 then 1 else null end) as downvotes @@ -15,7 +17,8 @@ with all_comment as select ac.*, u.id as user_id, -coalesce(cl.score, 0) as my_vote +coalesce(cl.score, 0) as my_vote, +u.admin or (select cm.id::bool from community_moderator cm, post p where u.id = cm.user_id and ac.post_id = p.id and p.community_id = cm.community_id) as am_mod from user_ u cross join all_comment ac left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id @@ -25,6 +28,7 @@ union all select ac.*, null as user_id, - null as my_vote + null as my_vote, + null as am_mod from all_comment ac ; diff --git a/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql b/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql index 15718917a..888a87feb 100644 --- a/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql +++ b/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql @@ -3,4 +3,6 @@ drop table mod_lock_post; drop table mod_remove_comment; drop table mod_remove_community; drop table mod_ban; -drop table mod_add_mod; +drop table mod_ban_from_community; +drop table mod_add; +drop table mod_add_community; diff --git a/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql b/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql index 41929e50f..3b320d810 100644 --- a/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql +++ b/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql @@ -1,4 +1,3 @@ - create table mod_remove_post ( id serial primary key, mod_user_id int references user_ on update cascade on delete cascade not null, @@ -12,6 +11,7 @@ create table mod_lock_post ( id serial primary key, mod_user_id int references user_ on update cascade on delete cascade not null, post_id int references post on update cascade on delete cascade not null, + locked boolean default true, when_ timestamp not null default now() ); @@ -30,25 +30,47 @@ create table mod_remove_community ( community_id int references community on update cascade on delete cascade not null, reason text, removed boolean default true, + expires timestamp, when_ timestamp not null default now() ); -- TODO make sure you can't ban other mods +create table mod_ban_from_community ( + id serial primary key, + mod_user_id int references user_ on update cascade on delete cascade not null, + other_user_id int references user_ on update cascade on delete cascade not null, + community_id int references community on update cascade on delete cascade not null, + reason text, + banned boolean default true, + expires timestamp, + when_ timestamp not null default now() +); + create table mod_ban ( id serial primary key, mod_user_id int references user_ on update cascade on delete cascade not null, other_user_id int references user_ on update cascade on delete cascade not null, reason text, - removed boolean default true, + banned boolean default true, expires timestamp, when_ timestamp not null default now() ); +create table mod_add_community ( + id serial primary key, + mod_user_id int references user_ on update cascade on delete cascade not null, + other_user_id int references user_ on update cascade on delete cascade not null, + community_id int references community on update cascade on delete cascade not null, + removed boolean default false, + when_ timestamp not null default now() +); + -- When removed is false that means kicked -create table mod_add_mod ( +create table mod_add ( id serial primary key, mod_user_id int references user_ on update cascade on delete cascade not null, other_user_id int references user_ on update cascade on delete cascade not null, removed boolean default false, when_ timestamp not null default now() -) +); + diff --git a/server/migrations/2019-04-08-015947_create_user_view/up.sql b/server/migrations/2019-04-08-015947_create_user_view/up.sql index 69d052de1..08eb56ca0 100644 --- a/server/migrations/2019-04-08-015947_create_user_view/up.sql +++ b/server/migrations/2019-04-08-015947_create_user_view/up.sql @@ -2,10 +2,11 @@ create view user_view as select id, name, fedi_name, +admin, +banned, 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; - diff --git a/server/migrations/2019-04-11-144915_create_mod_views/down.sql b/server/migrations/2019-04-11-144915_create_mod_views/down.sql new file mode 100644 index 000000000..95018f35a --- /dev/null +++ b/server/migrations/2019-04-11-144915_create_mod_views/down.sql @@ -0,0 +1,8 @@ +drop view mod_remove_post_view; +drop view mod_lock_post_view; +drop view mod_remove_comment_view; +drop view mod_remove_community_view; +drop view mod_ban_from_community_view; +drop view mod_ban_view; +drop view mod_add_community_view; +drop view mod_add_view; diff --git a/server/migrations/2019-04-11-144915_create_mod_views/up.sql b/server/migrations/2019-04-11-144915_create_mod_views/up.sql new file mode 100644 index 000000000..908028d03 --- /dev/null +++ b/server/migrations/2019-04-11-144915_create_mod_views/up.sql @@ -0,0 +1,61 @@ +create view mod_remove_post_view as +select mrp.*, +(select name from user_ u where mrp.mod_user_id = u.id) as mod_user_name, +(select name from post p where mrp.post_id = p.id) as post_name, +(select c.id from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_id, +(select c.name from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_name +from mod_remove_post mrp; + +create view mod_lock_post_view as +select mlp.*, +(select name from user_ u where mlp.mod_user_id = u.id) as mod_user_name, +(select name from post p where mlp.post_id = p.id) as post_name, +(select c.id from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_id, +(select c.name from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_name +from mod_lock_post mlp; + +create view mod_remove_comment_view as +select mrc.*, +(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name, +(select c.id from comment c where mrc.comment_id = c.id) as comment_user_id, +(select name from user_ u, comment c where mrc.comment_id = c.id and u.id = c.creator_id) as comment_user_name, +(select content from comment c where mrc.comment_id = c.id) as comment_content, +(select p.id from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_id, +(select p.name from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_name, +(select co.id from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_id, +(select co.name from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_name +from mod_remove_comment mrc; + +create view mod_remove_community_view as +select mrc.*, +(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name, +(select c.name from community c where mrc.community_id = c.id) as community_name +from mod_remove_community mrc; + +create view mod_ban_from_community_view as +select mb.*, +(select name from user_ u where mb.mod_user_id = u.id) as mod_user_name, +(select name from user_ u where mb.other_user_id = u.id) as other_user_name, +(select name from community c where mb.community_id = c.id) as community_name +from mod_ban_from_community mb; + +create view mod_ban_view as +select mb.*, +(select name from user_ u where mb.mod_user_id = u.id) as mod_user_name, +(select name from user_ u where mb.other_user_id = u.id) as other_user_name +from mod_ban_from_community mb; + + +create view mod_add_community_view as +select ma.*, +(select name from user_ u where ma.mod_user_id = u.id) as mod_user_name, +(select name from user_ u where ma.other_user_id = u.id) as other_user_name, +(select name from community c where ma.community_id = c.id) as community_name +from mod_add_community ma; + + +create view mod_add_view as +select ma.*, +(select name from user_ u where ma.mod_user_id = u.id) as mod_user_name, +(select name from user_ u where ma.other_user_id = u.id) as other_user_name +from mod_add ma; diff --git a/server/src/actions/comment.rs b/server/src/actions/comment.rs index ff5028503..2c5c570ee 100644 --- a/server/src/actions/comment.rs +++ b/server/src/actions/comment.rs @@ -22,6 +22,7 @@ pub struct Comment { pub post_id: i32, pub parent_id: Option, pub content: String, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -33,6 +34,7 @@ pub struct CommentForm { pub post_id: i32, pub parent_id: Option, pub content: String, + pub removed: Option, pub updated: Option } @@ -135,6 +137,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -146,6 +150,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, + removed: None, updated: None }; @@ -157,6 +162,8 @@ mod tests { url: None, body: None, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -166,6 +173,7 @@ mod tests { content: "A test comment".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + removed: None, parent_id: None, updated: None }; @@ -177,6 +185,7 @@ mod tests { content: "A test comment".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + removed: Some(false), parent_id: None, published: inserted_comment.published, updated: None @@ -187,6 +196,7 @@ mod tests { creator_id: inserted_user.id, post_id: inserted_post.id, parent_id: Some(inserted_comment.id), + removed: None, updated: None }; diff --git a/server/src/actions/comment_view.rs b/server/src/actions/comment_view.rs index 3b4e00bb8..e1cc41170 100644 --- a/server/src/actions/comment_view.rs +++ b/server/src/actions/comment_view.rs @@ -13,14 +13,18 @@ table! { post_id -> Int4, parent_id -> Nullable, content -> Text, + removed -> Nullable, published -> Timestamp, updated -> Nullable, + community_id -> Int4, + banned -> Nullable, creator_name -> Varchar, score -> BigInt, upvotes -> BigInt, downvotes -> BigInt, user_id -> Nullable, my_vote -> Nullable, + am_mod -> Nullable, } } @@ -32,14 +36,18 @@ pub struct CommentView { pub post_id: i32, pub parent_id: Option, pub content: String, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option, + pub community_id: i32, + pub banned: Option, pub creator_name: String, pub score: i64, pub upvotes: i64, pub downvotes: i64, pub user_id: Option, pub my_vote: Option, + pub am_mod: Option, } impl CommentView { @@ -130,6 +138,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -141,6 +151,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, + removed: None, updated: None }; @@ -152,6 +163,8 @@ mod tests { url: None, body: None, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -162,6 +175,7 @@ mod tests { creator_id: inserted_user.id, post_id: inserted_post.id, parent_id: None, + removed: None, updated: None }; @@ -181,7 +195,10 @@ mod tests { content: "A test comment 32".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + community_id: inserted_community.id, parent_id: None, + removed: Some(false), + banned: None, published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), @@ -189,7 +206,8 @@ mod tests { downvotes: 0, upvotes: 1, user_id: None, - my_vote: None + my_vote: None, + am_mod: None, }; let expected_comment_view_with_user = CommentView { @@ -197,7 +215,10 @@ mod tests { content: "A test comment 32".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + community_id: inserted_community.id, parent_id: None, + removed: Some(false), + banned: None, published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), @@ -206,6 +227,7 @@ mod tests { upvotes: 1, user_id: Some(inserted_user.id), my_vote: Some(1), + am_mod: None, }; let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, 999).unwrap(); diff --git a/server/src/actions/community.rs b/server/src/actions/community.rs index 1c6343d0b..d179b9a26 100644 --- a/server/src/actions/community.rs +++ b/server/src/actions/community.rs @@ -1,9 +1,9 @@ extern crate diesel; -use schema::{community, community_moderator, community_follower}; +use schema::{community, community_moderator, community_follower, community_user_ban}; use diesel::*; use diesel::result::Error; use serde::{Deserialize, Serialize}; -use {Crud, Followable, Joinable}; +use {Crud, Followable, Joinable, Bannable}; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name="community"] @@ -14,6 +14,7 @@ pub struct Community { pub description: Option, pub category_id: i32, pub creator_id: i32, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -26,6 +27,7 @@ pub struct CommunityForm { pub description: Option, pub category_id: i32, pub creator_id: i32, + pub removed: Option, pub updated: Option } @@ -46,6 +48,23 @@ pub struct CommunityModeratorForm { pub user_id: i32, } +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Community)] +#[table_name = "community_user_ban"] +pub struct CommunityUserBan { + pub id: i32, + pub community_id: i32, + pub user_id: i32, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="community_user_ban"] +pub struct CommunityUserBanForm { + pub community_id: i32, + pub user_id: i32, +} + #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] #[belongs_to(Community)] #[table_name = "community_follower"] @@ -125,6 +144,23 @@ impl Joinable for CommunityModerator { } } +impl Bannable for CommunityUserBan { + fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result { + use schema::community_user_ban::dsl::*; + insert_into(community_user_ban) + .values(community_user_ban_form) + .get_result::(conn) + } + + fn unban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result { + use schema::community_user_ban::dsl::*; + diesel::delete(community_user_ban + .filter(community_id.eq(community_user_ban_form.community_id)) + .filter(user_id.eq(community_user_ban_form.user_id))) + .execute(conn) + } +} + #[cfg(test)] mod tests { use establish_connection; @@ -136,11 +172,13 @@ mod tests { let conn = establish_connection(); let new_user = UserForm { - name: "bob".into(), + name: "bobbee".into(), fedi_name: "rrf".into(), preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -152,6 +190,7 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, + removed: None, updated: None, }; @@ -164,11 +203,11 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, + removed: Some(false), published: inserted_community.published, updated: None }; - let community_follower_form = CommunityFollowerForm { community_id: inserted_community.id, user_id: inserted_user.id @@ -176,6 +215,7 @@ mod tests { let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap(); + let expected_community_follower = CommunityFollower { id: inserted_community_follower.id, community_id: inserted_community.id, @@ -197,10 +237,25 @@ mod tests { published: inserted_community_user.published }; + let community_user_ban_form = CommunityUserBanForm { + community_id: inserted_community.id, + user_id: inserted_user.id + }; + + let inserted_community_user_ban = CommunityUserBan::ban(&conn, &community_user_ban_form).unwrap(); + + let expected_community_user_ban = CommunityUserBan { + id: inserted_community_user_ban.id, + community_id: inserted_community.id, + user_id: inserted_user.id, + published: inserted_community_user_ban.published + }; + let read_community = Community::read(&conn, inserted_community.id).unwrap(); let updated_community = Community::update(&conn, inserted_community.id, &new_community).unwrap(); let ignored_community = CommunityFollower::ignore(&conn, &community_follower_form).unwrap(); let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap(); + let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap(); let num_deleted = Community::delete(&conn, inserted_community.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap(); @@ -209,8 +264,10 @@ mod tests { assert_eq!(expected_community, updated_community); assert_eq!(expected_community_follower, inserted_community_follower); assert_eq!(expected_community_user, inserted_community_user); + assert_eq!(expected_community_user_ban, inserted_community_user_ban); assert_eq!(1, ignored_community); assert_eq!(1, left_community); + assert_eq!(1, unban); // assert_eq!(2, loaded_count); assert_eq!(1, num_deleted); diff --git a/server/src/actions/community_view.rs b/server/src/actions/community_view.rs index cb89b2264..14f38302f 100644 --- a/server/src/actions/community_view.rs +++ b/server/src/actions/community_view.rs @@ -12,6 +12,7 @@ table! { description -> Nullable, category_id -> Int4, creator_id -> Int4, + removed -> Nullable, published -> Timestamp, updated -> Nullable, creator_name -> Varchar, @@ -21,6 +22,7 @@ table! { number_of_comments -> BigInt, user_id -> Nullable, subscribed -> Nullable, + am_mod -> Nullable, } } @@ -46,6 +48,17 @@ table! { } } +table! { + community_user_ban_view (id) { + id -> Int4, + community_id -> Int4, + user_id -> Int4, + published -> Timestamp, + user_name -> Varchar, + community_name -> Varchar, + } +} + #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] #[table_name="community_view"] pub struct CommunityView { @@ -55,6 +68,7 @@ pub struct CommunityView { pub description: Option, pub category_id: i32, pub creator_id: i32, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option, pub creator_name: String, @@ -64,6 +78,7 @@ pub struct CommunityView { pub number_of_comments: i64, pub user_id: Option, pub subscribed: Option, + pub am_mod: Option, } impl CommunityView { @@ -107,7 +122,7 @@ impl CommunityView { query = query.limit(limit); }; - query.load::(conn) + query.filter(removed.eq(false)).load::(conn) } } @@ -157,3 +172,35 @@ impl CommunityFollowerView { community_follower_view.filter(user_id.eq(from_user_id)).load::(conn) } } + + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="community_user_ban_view"] +pub struct CommunityUserBanView { + pub id: i32, + pub community_id: i32, + pub user_id: i32, + pub published: chrono::NaiveDateTime, + pub user_name : String, + pub community_name: String, +} + +impl CommunityUserBanView { + pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result, Error> { + use actions::community_view::community_user_ban_view::dsl::*; + community_user_ban_view.filter(community_id.eq(from_community_id)).load::(conn) + } + + pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result, Error> { + use actions::community_view::community_user_ban_view::dsl::*; + community_user_ban_view.filter(user_id.eq(from_user_id)).load::(conn) + } + + pub fn get(conn: &PgConnection, from_user_id: i32, from_community_id: i32) -> Result { + use actions::community_view::community_user_ban_view::dsl::*; + community_user_ban_view + .filter(user_id.eq(from_user_id)) + .filter(community_id.eq(from_community_id)) + .first::(conn) + } +} diff --git a/server/src/actions/mod.rs b/server/src/actions/mod.rs index 819d5cdaf..ece1e885a 100644 --- a/server/src/actions/mod.rs +++ b/server/src/actions/mod.rs @@ -7,3 +7,5 @@ pub mod comment_view; pub mod category; pub mod community_view; pub mod user_view; +pub mod moderator; +pub mod moderator_views; diff --git a/server/src/actions/moderator.rs b/server/src/actions/moderator.rs new file mode 100644 index 000000000..089c7ce56 --- /dev/null +++ b/server/src/actions/moderator.rs @@ -0,0 +1,655 @@ +extern crate diesel; +use schema::{mod_remove_post, mod_lock_post, mod_remove_comment, mod_remove_community, mod_ban_from_community, mod_ban, mod_add_community, mod_add}; +use diesel::*; +use diesel::result::Error; +use serde::{Deserialize, Serialize}; +use {Crud}; + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_remove_post"] +pub struct ModRemovePost { + pub id: i32, + pub mod_user_id: i32, + pub post_id: i32, + pub reason: Option, + pub removed: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_remove_post"] +pub struct ModRemovePostForm { + pub mod_user_id: i32, + pub post_id: i32, + pub reason: Option, + pub removed: Option, +} + +impl Crud for ModRemovePost { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_post::dsl::*; + mod_remove_post.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_post::dsl::*; + diesel::delete(mod_remove_post.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModRemovePostForm) -> Result { + use schema::mod_remove_post::dsl::*; + insert_into(mod_remove_post) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModRemovePostForm) -> Result { + use schema::mod_remove_post::dsl::*; + diesel::update(mod_remove_post.find(from_id)) + .set(form) + .get_result::(conn) + } +} + + + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_lock_post"] +pub struct ModLockPost { + pub id: i32, + pub mod_user_id: i32, + pub post_id: i32, + pub locked: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_lock_post"] +pub struct ModLockPostForm { + pub mod_user_id: i32, + pub post_id: i32, + pub locked: Option, +} + +impl Crud for ModLockPost { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_lock_post::dsl::*; + mod_lock_post.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_lock_post::dsl::*; + diesel::delete(mod_lock_post.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModLockPostForm) -> Result { + use schema::mod_lock_post::dsl::*; + insert_into(mod_lock_post) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModLockPostForm) -> Result { + use schema::mod_lock_post::dsl::*; + diesel::update(mod_lock_post.find(from_id)) + .set(form) + .get_result::(conn) + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_remove_comment"] +pub struct ModRemoveComment { + pub id: i32, + pub mod_user_id: i32, + pub comment_id: i32, + pub reason: Option, + pub removed: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_remove_comment"] +pub struct ModRemoveCommentForm { + pub mod_user_id: i32, + pub comment_id: i32, + pub reason: Option, + pub removed: Option, +} + +impl Crud for ModRemoveComment { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_comment::dsl::*; + mod_remove_comment.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_comment::dsl::*; + diesel::delete(mod_remove_comment.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModRemoveCommentForm) -> Result { + use schema::mod_remove_comment::dsl::*; + insert_into(mod_remove_comment) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommentForm) -> Result { + use schema::mod_remove_comment::dsl::*; + diesel::update(mod_remove_comment.find(from_id)) + .set(form) + .get_result::(conn) + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_remove_community"] +pub struct ModRemoveCommunity { + pub id: i32, + pub mod_user_id: i32, + pub community_id: i32, + pub reason: Option, + pub removed: Option, + pub expires: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_remove_community"] +pub struct ModRemoveCommunityForm { + pub mod_user_id: i32, + pub community_id: i32, + pub reason: Option, + pub removed: Option, + pub expires: Option, +} + +impl Crud for ModRemoveCommunity { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_community::dsl::*; + mod_remove_community.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_community::dsl::*; + diesel::delete(mod_remove_community.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModRemoveCommunityForm) -> Result { + use schema::mod_remove_community::dsl::*; + insert_into(mod_remove_community) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommunityForm) -> Result { + use schema::mod_remove_community::dsl::*; + diesel::update(mod_remove_community.find(from_id)) + .set(form) + .get_result::(conn) + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_ban_from_community"] +pub struct ModBanFromCommunity { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub community_id: i32, + pub reason: Option, + pub banned: Option, + pub expires: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_ban_from_community"] +pub struct ModBanFromCommunityForm { + pub mod_user_id: i32, + pub other_user_id: i32, + pub community_id: i32, + pub reason: Option, + pub banned: Option, + pub expires: Option, +} + +impl Crud for ModBanFromCommunity { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_ban_from_community::dsl::*; + mod_ban_from_community.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_ban_from_community::dsl::*; + diesel::delete(mod_ban_from_community.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModBanFromCommunityForm) -> Result { + use schema::mod_ban_from_community::dsl::*; + insert_into(mod_ban_from_community) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModBanFromCommunityForm) -> Result { + use schema::mod_ban_from_community::dsl::*; + diesel::update(mod_ban_from_community.find(from_id)) + .set(form) + .get_result::(conn) + } +} + + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_ban"] +pub struct ModBan { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub reason: Option, + pub banned: Option, + pub expires: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_ban"] +pub struct ModBanForm { + pub mod_user_id: i32, + pub other_user_id: i32, + pub reason: Option, + pub banned: Option, + pub expires: Option, +} + +impl Crud for ModBan { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_ban::dsl::*; + mod_ban.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_ban::dsl::*; + diesel::delete(mod_ban.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModBanForm) -> Result { + use schema::mod_ban::dsl::*; + insert_into(mod_ban) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModBanForm) -> Result { + use schema::mod_ban::dsl::*; + diesel::update(mod_ban.find(from_id)) + .set(form) + .get_result::(conn) + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_add_community"] +pub struct ModAddCommunity { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub community_id: i32, + pub removed: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_add_community"] +pub struct ModAddCommunityForm { + pub mod_user_id: i32, + pub other_user_id: i32, + pub community_id: i32, + pub removed: Option, +} + +impl Crud for ModAddCommunity { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add_community::dsl::*; + mod_add_community.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add_community::dsl::*; + diesel::delete(mod_add_community.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModAddCommunityForm) -> Result { + use schema::mod_add_community::dsl::*; + insert_into(mod_add_community) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModAddCommunityForm) -> Result { + use schema::mod_add_community::dsl::*; + diesel::update(mod_add_community.find(from_id)) + .set(form) + .get_result::(conn) + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="mod_add"] +pub struct ModAdd { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub removed: Option, + pub when_: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="mod_add"] +pub struct ModAddForm { + pub mod_user_id: i32, + pub other_user_id: i32, + pub removed: Option, +} + +impl Crud for ModAdd { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add::dsl::*; + mod_add.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add::dsl::*; + diesel::delete(mod_add.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModAddForm) -> Result { + use schema::mod_add::dsl::*; + insert_into(mod_add) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModAddForm) -> Result { + use schema::mod_add::dsl::*; + diesel::update(mod_add.find(from_id)) + .set(form) + .get_result::(conn) + } +} + +#[cfg(test)] +mod tests { + use establish_connection; + use super::*; + use actions::user::*; + use actions::post::*; + use actions::community::*; + use actions::comment::*; + // use Crud; + #[test] + fn test_crud() { + let conn = establish_connection(); + + let new_mod = UserForm { + name: "the mod".into(), + fedi_name: "rrf".into(), + preferred_username: None, + password_encrypted: "nope".into(), + email: None, + admin: None, + banned: None, + updated: None + }; + + let inserted_mod = User_::create(&conn, &new_mod).unwrap(); + + let new_user = UserForm { + name: "jim2".into(), + fedi_name: "rrf".into(), + preferred_username: None, + password_encrypted: "nope".into(), + email: None, + admin: None, + banned: None, + updated: None + }; + + let inserted_user = User_::create(&conn, &new_user).unwrap(); + + let new_community = CommunityForm { + name: "mod_community".to_string(), + title: "nada".to_owned(), + description: None, + category_id: 1, + creator_id: inserted_user.id, + removed: None, + updated: None + }; + + let inserted_community = Community::create(&conn, &new_community).unwrap(); + + let new_post = PostForm { + name: "A test post thweep".into(), + url: None, + body: None, + creator_id: inserted_user.id, + community_id: inserted_community.id, + removed: None, + locked: None, + updated: None + }; + + let inserted_post = Post::create(&conn, &new_post).unwrap(); + + let comment_form = CommentForm { + content: "A test comment".into(), + creator_id: inserted_user.id, + post_id: inserted_post.id, + removed: None, + parent_id: None, + updated: None + }; + + let inserted_comment = Comment::create(&conn, &comment_form).unwrap(); + + // Now the actual tests + + // remove post + let mod_remove_post_form = ModRemovePostForm { + mod_user_id: inserted_mod.id, + post_id: inserted_post.id, + reason: None, + removed: None, + }; + let inserted_mod_remove_post = ModRemovePost::create(&conn, &mod_remove_post_form).unwrap(); + let read_moderator_remove_post = ModRemovePost::read(&conn, inserted_mod_remove_post.id).unwrap(); + let expected_moderator_remove_post = ModRemovePost { + id: inserted_mod_remove_post.id, + post_id: inserted_post.id, + mod_user_id: inserted_mod.id, + reason: None, + removed: Some(true), + when_: inserted_mod_remove_post.when_, + }; + + // lock post + + let mod_lock_post_form = ModLockPostForm { + mod_user_id: inserted_mod.id, + post_id: inserted_post.id, + locked: None, + }; + let inserted_mod_lock_post = ModLockPost::create(&conn, &mod_lock_post_form).unwrap(); + let read_moderator_lock_post = ModLockPost::read(&conn, inserted_mod_lock_post.id).unwrap(); + let expected_moderator_lock_post = ModLockPost { + id: inserted_mod_lock_post.id, + post_id: inserted_post.id, + mod_user_id: inserted_mod.id, + locked: Some(true), + when_: inserted_mod_lock_post.when_, + }; + + // comment + + let mod_remove_comment_form = ModRemoveCommentForm { + mod_user_id: inserted_mod.id, + comment_id: inserted_comment.id, + reason: None, + removed: None, + }; + let inserted_mod_remove_comment = ModRemoveComment::create(&conn, &mod_remove_comment_form).unwrap(); + let read_moderator_remove_comment = ModRemoveComment::read(&conn, inserted_mod_remove_comment.id).unwrap(); + let expected_moderator_remove_comment = ModRemoveComment { + id: inserted_mod_remove_comment.id, + comment_id: inserted_comment.id, + mod_user_id: inserted_mod.id, + reason: None, + removed: Some(true), + when_: inserted_mod_remove_comment.when_, + }; + + // community + + let mod_remove_community_form = ModRemoveCommunityForm { + mod_user_id: inserted_mod.id, + community_id: inserted_community.id, + reason: None, + removed: None, + expires: None, + }; + let inserted_mod_remove_community = ModRemoveCommunity::create(&conn, &mod_remove_community_form).unwrap(); + let read_moderator_remove_community = ModRemoveCommunity::read(&conn, inserted_mod_remove_community.id).unwrap(); + let expected_moderator_remove_community = ModRemoveCommunity { + id: inserted_mod_remove_community.id, + community_id: inserted_community.id, + mod_user_id: inserted_mod.id, + reason: None, + removed: Some(true), + expires: None, + when_: inserted_mod_remove_community.when_, + }; + + // ban from community + + let mod_ban_from_community_form = ModBanFromCommunityForm { + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + community_id: inserted_community.id, + reason: None, + banned: None, + expires: None, + }; + let inserted_mod_ban_from_community = ModBanFromCommunity::create(&conn, &mod_ban_from_community_form).unwrap(); + let read_moderator_ban_from_community = ModBanFromCommunity::read(&conn, inserted_mod_ban_from_community.id).unwrap(); + let expected_moderator_ban_from_community = ModBanFromCommunity { + id: inserted_mod_ban_from_community.id, + community_id: inserted_community.id, + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + reason: None, + banned: Some(true), + expires: None, + when_: inserted_mod_ban_from_community.when_, + }; + + // ban + + let mod_ban_form = ModBanForm { + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + reason: None, + banned: None, + expires: None, + }; + let inserted_mod_ban = ModBan::create(&conn, &mod_ban_form).unwrap(); + let read_moderator_ban = ModBan::read(&conn, inserted_mod_ban.id).unwrap(); + let expected_moderator_ban = ModBan { + id: inserted_mod_ban.id, + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + reason: None, + banned: Some(true), + expires: None, + when_: inserted_mod_ban.when_, + }; + + // mod add community + + let mod_add_community_form = ModAddCommunityForm { + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + community_id: inserted_community.id, + removed: None, + }; + let inserted_mod_add_community = ModAddCommunity::create(&conn, &mod_add_community_form).unwrap(); + let read_moderator_add_community = ModAddCommunity::read(&conn, inserted_mod_add_community.id).unwrap(); + let expected_moderator_add_community = ModAddCommunity { + id: inserted_mod_add_community.id, + community_id: inserted_community.id, + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + removed: Some(false), + when_: inserted_mod_add_community.when_, + }; + + // mod add + + let mod_add_form = ModAddForm { + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + removed: None, + }; + let inserted_mod_add = ModAdd::create(&conn, &mod_add_form).unwrap(); + let read_moderator_add = ModAdd::read(&conn, inserted_mod_add.id).unwrap(); + let expected_moderator_add = ModAdd { + id: inserted_mod_add.id, + mod_user_id: inserted_mod.id, + other_user_id: inserted_user.id, + removed: Some(false), + when_: inserted_mod_add.when_, + }; + + ModRemovePost::delete(&conn, inserted_mod_remove_post.id).unwrap(); + ModLockPost::delete(&conn, inserted_mod_lock_post.id).unwrap(); + ModRemoveComment::delete(&conn, inserted_mod_remove_comment.id).unwrap(); + ModRemoveCommunity::delete(&conn, inserted_mod_remove_community.id).unwrap(); + ModBanFromCommunity::delete(&conn, inserted_mod_ban_from_community.id).unwrap(); + ModBan::delete(&conn, inserted_mod_ban.id).unwrap(); + ModAddCommunity::delete(&conn, inserted_mod_add_community.id).unwrap(); + ModAdd::delete(&conn, inserted_mod_add.id).unwrap(); + + Comment::delete(&conn, inserted_comment.id).unwrap(); + Post::delete(&conn, inserted_post.id).unwrap(); + Community::delete(&conn, inserted_community.id).unwrap(); + User_::delete(&conn, inserted_user.id).unwrap(); + User_::delete(&conn, inserted_mod.id).unwrap(); + + assert_eq!(expected_moderator_remove_post, read_moderator_remove_post); + assert_eq!(expected_moderator_lock_post, read_moderator_lock_post); + assert_eq!(expected_moderator_remove_comment, read_moderator_remove_comment); + assert_eq!(expected_moderator_remove_community, read_moderator_remove_community); + assert_eq!(expected_moderator_ban_from_community, read_moderator_ban_from_community); + assert_eq!(expected_moderator_ban, read_moderator_ban); + assert_eq!(expected_moderator_add_community, read_moderator_add_community); + assert_eq!(expected_moderator_add, read_moderator_add); + } +} diff --git a/server/src/actions/moderator_views.rs b/server/src/actions/moderator_views.rs new file mode 100644 index 000000000..2e2435682 --- /dev/null +++ b/server/src/actions/moderator_views.rs @@ -0,0 +1,427 @@ +extern crate diesel; +use diesel::*; +use diesel::result::Error; +use serde::{Deserialize, Serialize}; + +table! { + mod_remove_post_view (id) { + id -> Int4, + mod_user_id -> Int4, + post_id -> Int4, + reason -> Nullable, + removed -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + post_name -> Varchar, + community_id -> Int4, + community_name -> Varchar, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_remove_post_view"] +pub struct ModRemovePostView { + pub id: i32, + pub mod_user_id: i32, + pub post_id: i32, + pub reason: Option, + pub removed: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub post_name: String, + pub community_id: i32, + pub community_name: String, +} + +impl ModRemovePostView { + pub fn list(conn: &PgConnection, + from_community_id: Option, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_remove_post_view::dsl::*; + let mut query = mod_remove_post_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_community_id) = from_community_id { + query = query.filter(community_id.eq(from_community_id)); + }; + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} + +table! { + mod_lock_post_view (id) { + id -> Int4, + mod_user_id -> Int4, + post_id -> Int4, + locked -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + post_name -> Varchar, + community_id -> Int4, + community_name -> Varchar, + } +} + + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_lock_post_view"] +pub struct ModLockPostView { + pub id: i32, + pub mod_user_id: i32, + pub post_id: i32, + pub locked: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub post_name: String, + pub community_id: i32, + pub community_name: String, +} + +impl ModLockPostView { + pub fn list(conn: &PgConnection, + from_community_id: Option, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_lock_post_view::dsl::*; + let mut query = mod_lock_post_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_community_id) = from_community_id { + query = query.filter(community_id.eq(from_community_id)); + }; + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} + +table! { + mod_remove_comment_view (id) { + id -> Int4, + mod_user_id -> Int4, + comment_id -> Int4, + reason -> Nullable, + removed -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + comment_user_id -> Int4, + comment_user_name -> Varchar, + comment_content -> Text, + post_id -> Int4, + post_name -> Varchar, + community_id -> Int4, + community_name -> Varchar, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_remove_comment_view"] +pub struct ModRemoveCommentView { + pub id: i32, + pub mod_user_id: i32, + pub comment_id: i32, + pub reason: Option, + pub removed: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub comment_user_id: i32, + pub comment_user_name: String, + pub comment_content: String, + pub post_id: i32, + pub post_name: String, + pub community_id: i32, + pub community_name: String, +} + +impl ModRemoveCommentView { + pub fn list(conn: &PgConnection, + from_community_id: Option, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_remove_comment_view::dsl::*; + let mut query = mod_remove_comment_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_community_id) = from_community_id { + query = query.filter(community_id.eq(from_community_id)); + }; + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} + +table! { + mod_remove_community_view (id) { + id -> Int4, + mod_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + removed -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + community_name -> Varchar, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_remove_community_view"] +pub struct ModRemoveCommunityView { + pub id: i32, + pub mod_user_id: i32, + pub community_id: i32, + pub reason: Option, + pub removed: Option, + pub expires: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub community_name: String, +} + +impl ModRemoveCommunityView { + pub fn list(conn: &PgConnection, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_remove_community_view::dsl::*; + let mut query = mod_remove_community_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} + + +table! { + mod_ban_from_community_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + other_user_name -> Varchar, + community_name -> Varchar, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_ban_from_community_view"] +pub struct ModBanFromCommunityView { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub community_id: i32, + pub reason: Option, + pub banned: Option, + pub expires: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub other_user_name: String, + pub community_name: String, +} + +impl ModBanFromCommunityView { + pub fn list(conn: &PgConnection, + from_community_id: Option, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_ban_from_community_view::dsl::*; + let mut query = mod_ban_from_community_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_community_id) = from_community_id { + query = query.filter(community_id.eq(from_community_id)); + }; + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} + +table! { + mod_ban_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + other_user_name -> Varchar, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_ban_view"] +pub struct ModBanView { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub reason: Option, + pub banned: Option, + pub expires: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub other_user_name: String, +} + +impl ModBanView { + pub fn list(conn: &PgConnection, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_ban_view::dsl::*; + let mut query = mod_ban_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} + +table! { + mod_add_community_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + removed -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + other_user_name -> Varchar, + community_name -> Varchar, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_add_community_view"] +pub struct ModAddCommunityView { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub community_id: i32, + pub removed: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub other_user_name: String, + pub community_name: String, +} + +impl ModAddCommunityView { + pub fn list(conn: &PgConnection, + from_community_id: Option, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_add_community_view::dsl::*; + let mut query = mod_add_community_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_community_id) = from_community_id { + query = query.filter(community_id.eq(from_community_id)); + }; + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} + +table! { + mod_add_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + removed -> Nullable, + when_ -> Timestamp, + mod_user_name -> Varchar, + other_user_name -> Varchar, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="mod_add_view"] +pub struct ModAddView { + pub id: i32, + pub mod_user_id: i32, + pub other_user_id: i32, + pub removed: Option, + pub when_: chrono::NaiveDateTime, + pub mod_user_name: String, + pub other_user_name: String, +} + +impl ModAddView { + pub fn list(conn: &PgConnection, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, Error> { + use actions::moderator_views::mod_add_view::dsl::*; + let mut query = mod_add_view.into_boxed(); + + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + + if let Some(from_mod_user_id) = from_mod_user_id { + query = query.filter(mod_user_id.eq(from_mod_user_id)); + }; + + query.limit(limit).offset(offset).order_by(when_.desc()).load::(conn) + } +} diff --git a/server/src/actions/post.rs b/server/src/actions/post.rs index b53aae463..b811bf326 100644 --- a/server/src/actions/post.rs +++ b/server/src/actions/post.rs @@ -14,6 +14,8 @@ pub struct Post { pub body: Option, pub creator_id: i32, pub community_id: i32, + pub removed: Option, + pub locked: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -26,6 +28,8 @@ pub struct PostForm { pub body: Option, pub creator_id: i32, pub community_id: i32, + pub removed: Option, + pub locked: Option, pub updated: Option } @@ -115,17 +119,20 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; let inserted_user = User_::create(&conn, &new_user).unwrap(); let new_community = CommunityForm { - name: "test community_2".to_string(), + name: "test community_3".to_string(), title: "nada".to_owned(), description: None, category_id: 1, creator_id: inserted_user.id, + removed: None, updated: None }; @@ -137,6 +144,8 @@ mod tests { body: None, creator_id: inserted_user.id, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -150,6 +159,8 @@ mod tests { creator_id: inserted_user.id, community_id: inserted_community.id, published: inserted_post.published, + removed: Some(false), + locked: Some(false), updated: None }; diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index 6ca85c341..9b4395d3f 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -19,6 +19,8 @@ table! { body -> Nullable, creator_id -> Int4, community_id -> Int4, + removed -> Nullable, + locked -> Nullable, published -> Timestamp, updated -> Nullable, creator_name -> Varchar, @@ -31,6 +33,7 @@ table! { user_id -> Nullable, my_vote -> Nullable, subscribed -> Nullable, + am_mod -> Nullable, } } @@ -44,6 +47,8 @@ pub struct PostView { pub body: Option, pub creator_id: i32, pub community_id: i32, + pub removed: Option, + pub locked: Option, pub published: chrono::NaiveDateTime, pub updated: Option, pub creator_name: String, @@ -56,6 +61,7 @@ pub struct PostView { pub user_id: Option, pub my_vote: Option, pub subscribed: Option, + pub am_mod: Option, } impl PostView { @@ -110,6 +116,8 @@ impl PostView { .order_by(score.desc()) }; + query = query.filter(removed.eq(false)); + query.load::(conn) } @@ -156,7 +164,9 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - updated: None + updated: None, + admin: None, + banned: None, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -167,6 +177,7 @@ mod tests { description: None, creator_id: inserted_user.id, category_id: 1, + removed: None, updated: None }; @@ -178,6 +189,8 @@ mod tests { body: None, creator_id: inserted_user.id, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -216,6 +229,8 @@ mod tests { creator_id: inserted_user.id, creator_name: user_name.to_owned(), community_id: inserted_community.id, + removed: Some(false), + locked: Some(false), community_name: community_name.to_owned(), number_of_comments: 0, score: 1, @@ -224,7 +239,8 @@ mod tests { hot_rank: 864, published: inserted_post.published, updated: None, - subscribed: None + subscribed: None, + am_mod: None, }; let expected_post_listing_with_user = PostView { @@ -234,6 +250,8 @@ mod tests { name: post_name.to_owned(), url: None, body: None, + removed: Some(false), + locked: Some(false), creator_id: inserted_user.id, creator_name: user_name.to_owned(), community_id: inserted_community.id, @@ -245,7 +263,8 @@ mod tests { hot_rank: 864, published: inserted_post.published, updated: None, - subscribed: None + subscribed: None, + am_mod: None, }; @@ -274,6 +293,5 @@ mod tests { assert_eq!(expected_post_like, inserted_post_like); assert_eq!(1, like_removed); assert_eq!(1, num_deleted); - } } diff --git a/server/src/actions/user.rs b/server/src/actions/user.rs index d646adcba..524fb66d4 100644 --- a/server/src/actions/user.rs +++ b/server/src/actions/user.rs @@ -17,6 +17,8 @@ pub struct User_ { pub password_encrypted: String, pub email: Option, pub icon: Option>, + pub admin: Option, + pub banned: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -28,6 +30,8 @@ pub struct UserForm { pub fedi_name: String, pub preferred_username: Option, pub password_encrypted: String, + pub admin: Option, + pub banned: Option, pub email: Option, pub updated: Option } @@ -122,6 +126,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -135,6 +141,8 @@ mod tests { password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(), email: None, icon: None, + admin: Some(false), + banned: Some(false), published: inserted_user.published, updated: None }; diff --git a/server/src/actions/user_view.rs b/server/src/actions/user_view.rs index 5873a5c86..4457e08a7 100644 --- a/server/src/actions/user_view.rs +++ b/server/src/actions/user_view.rs @@ -8,6 +8,8 @@ table! { id -> Int4, name -> Varchar, fedi_name -> Varchar, + admin -> Nullable, + banned -> Nullable, published -> Timestamp, number_of_posts -> BigInt, post_score -> BigInt, @@ -22,6 +24,8 @@ pub struct UserView { pub id: i32, pub name: String, pub fedi_name: String, + pub admin: Option, + pub banned: Option, pub published: chrono::NaiveDateTime, pub number_of_posts: i64, pub post_score: i64, diff --git a/server/src/apub.rs b/server/src/apub.rs index b24562614..9a535c0b0 100644 --- a/server/src/apub.rs +++ b/server/src/apub.rs @@ -44,6 +44,8 @@ mod tests { email: None, icon: None, published: naive_now(), + admin: None, + banned: None, updated: None }; diff --git a/server/src/lib.rs b/server/src/lib.rs index 814363b49..ab971edd9 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -50,6 +50,11 @@ pub trait Likeable { fn remove(conn: &PgConnection, form: &T) -> Result where Self: Sized; } +pub trait Bannable { + fn ban(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn unban(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + pub fn establish_connection() -> PgConnection { let db_url = Settings::get().db_url; PgConnection::establish(&db_url) @@ -88,6 +93,10 @@ pub fn naive_now() -> NaiveDateTime { chrono::prelude::Utc::now().naive_utc() } +pub fn naive_from_unix(time: i64) -> NaiveDateTime { + NaiveDateTime::from_timestamp(time, 0) +} + pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } diff --git a/server/src/schema.rs b/server/src/schema.rs index fe11a46bd..873f32e86 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -12,6 +12,7 @@ table! { post_id -> Int4, parent_id -> Nullable, content -> Text, + removed -> Nullable, published -> Timestamp, updated -> Nullable, } @@ -36,6 +37,7 @@ table! { description -> Nullable, category_id -> Int4, creator_id -> Int4, + removed -> Nullable, published -> Timestamp, updated -> Nullable, } @@ -59,6 +61,105 @@ table! { } } +table! { + community_user_ban (id) { + id -> Int4, + community_id -> Int4, + user_id -> Int4, + published -> Timestamp, + } +} + +table! { + mod_add (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + removed -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_add_community (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + removed -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_ban (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_ban_from_community (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_lock_post (id) { + id -> Int4, + mod_user_id -> Int4, + post_id -> Int4, + locked -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_remove_comment (id) { + id -> Int4, + mod_user_id -> Int4, + comment_id -> Int4, + reason -> Nullable, + removed -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_remove_community (id) { + id -> Int4, + mod_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + removed -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_remove_post (id) { + id -> Int4, + mod_user_id -> Int4, + post_id -> Int4, + reason -> Nullable, + removed -> Nullable, + when_ -> Timestamp, + } +} + table! { post (id) { id -> Int4, @@ -67,6 +168,8 @@ table! { body -> Nullable, creator_id -> Int4, community_id -> Int4, + removed -> Nullable, + locked -> Nullable, published -> Timestamp, updated -> Nullable, } @@ -91,11 +194,21 @@ table! { password_encrypted -> Text, email -> Nullable, icon -> Nullable, + admin -> Nullable, + banned -> Nullable, published -> Timestamp, updated -> Nullable, } } +table! { + user_ban (id) { + id -> Int4, + user_id -> Int4, + published -> Timestamp, + } +} + joinable!(comment -> post (post_id)); joinable!(comment -> user_ (creator_id)); joinable!(comment_like -> comment (comment_id)); @@ -107,10 +220,23 @@ joinable!(community_follower -> community (community_id)); joinable!(community_follower -> user_ (user_id)); joinable!(community_moderator -> community (community_id)); joinable!(community_moderator -> user_ (user_id)); +joinable!(community_user_ban -> community (community_id)); +joinable!(community_user_ban -> user_ (user_id)); +joinable!(mod_add_community -> community (community_id)); +joinable!(mod_ban_from_community -> community (community_id)); +joinable!(mod_lock_post -> post (post_id)); +joinable!(mod_lock_post -> user_ (mod_user_id)); +joinable!(mod_remove_comment -> comment (comment_id)); +joinable!(mod_remove_comment -> user_ (mod_user_id)); +joinable!(mod_remove_community -> community (community_id)); +joinable!(mod_remove_community -> user_ (mod_user_id)); +joinable!(mod_remove_post -> post (post_id)); +joinable!(mod_remove_post -> user_ (mod_user_id)); joinable!(post -> community (community_id)); joinable!(post -> user_ (creator_id)); joinable!(post_like -> post (post_id)); joinable!(post_like -> user_ (user_id)); +joinable!(user_ban -> user_ (user_id)); allow_tables_to_appear_in_same_query!( category, @@ -119,7 +245,17 @@ allow_tables_to_appear_in_same_query!( community, community_follower, community_moderator, + community_user_ban, + mod_add, + mod_add_community, + mod_ban, + mod_ban_from_community, + mod_lock_post, + mod_remove_comment, + mod_remove_community, + mod_remove_post, post, post_like, user_, + user_ban, ); diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 137761ab3..b3bdf78d7 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize}; use serde_json::{Value}; use bcrypt::{verify}; use std::str::FromStr; +use diesel::PgConnection; -use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType, has_slurs, remove_slurs}; +use {Crud, Joinable, Likeable, Followable, Bannable, establish_connection, naive_now, naive_from_unix, SortType, has_slurs, remove_slurs}; use actions::community::*; use actions::user::*; use actions::post::*; @@ -20,10 +21,12 @@ use actions::comment_view::*; use actions::category::*; use actions::community_view::*; use actions::user_view::*; +use actions::moderator_views::*; +use actions::moderator::*; #[derive(EnumString,ToString,Debug)] pub enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails + Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity, } #[derive(Serialize, Deserialize)] @@ -204,7 +207,10 @@ pub struct EditComment { content: String, parent_id: Option, edit_id: i32, + creator_id: i32, post_id: i32, + removed: Option, + reason: Option, auth: String } @@ -222,7 +228,6 @@ pub struct CreateCommentLike { auth: String } - #[derive(Serialize, Deserialize)] pub struct CreatePostLike { post_id: i32, @@ -240,10 +245,14 @@ pub struct CreatePostLikeResponse { #[derive(Serialize, Deserialize)] pub struct EditPost { edit_id: i32, + creator_id: i32, community_id: i32, name: String, url: Option, body: Option, + removed: Option, + reason: Option, + locked: Option, auth: String } @@ -254,6 +263,9 @@ pub struct EditCommunity { title: String, description: Option, category_id: i32, + removed: Option, + reason: Option, + expires: Option, auth: String } @@ -296,6 +308,59 @@ pub struct GetUserDetailsResponse { saved_comments: Vec, } +#[derive(Serialize, Deserialize)] +pub struct GetModlog { + mod_user_id: Option, + community_id: Option, + limit: Option, + page: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct GetModlogResponse { + op: String, + removed_posts: Vec, + locked_posts: Vec, + removed_comments: Vec, + removed_communities: Vec, + banned_from_community: Vec, + banned: Vec, + added_to_community: Vec, + added: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct BanFromCommunity { + community_id: i32, + user_id: i32, + ban: bool, + reason: Option, + expires: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct BanFromCommunityResponse { + op: String, + user: UserView, + banned: bool, +} + + +#[derive(Serialize, Deserialize)] +pub struct AddModToCommunity { + community_id: i32, + user_id: i32, + added: bool, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct AddModToCommunityResponse { + op: String, + moderators: Vec, +} + /// `ChatServer` manages chat rooms and responsible for coordinating chat /// session. implementation is super primitive pub struct ChatServer { @@ -330,6 +395,19 @@ impl ChatServer { } } } + + fn send_community_message(&self, conn: &PgConnection, community_id: i32, message: &str, skip_id: usize) { + let posts = PostView::list(conn, + PostListingType::Community, + &SortType::New, + Some(community_id), + None, + None, + 999).unwrap(); + for post in posts { + self.send_room_message(post.id, message, skip_id); + } + } } /// Make actor from `ChatServer` @@ -397,6 +475,9 @@ impl Handler for ChatServer { let op = &json["op"].as_str().unwrap(); let user_operation: UserOperation = UserOperation::from_str(&op).unwrap(); + + // TODO figure out how to do proper error handling here, instead of just returning + // error strings let res: String = match user_operation { UserOperation::Login => { let login: Login = serde_json::from_str(data).unwrap(); @@ -470,13 +551,18 @@ impl Handler for ChatServer { let get_user_details: GetUserDetails = serde_json::from_str(data).unwrap(); get_user_details.perform(self, msg.id) }, - // _ => { - // let e = ErrorMessage { - // op: "Unknown".to_string(), - // error: "Unknown User Operation".to_string() - // }; - // serde_json::to_string(&e).unwrap() - // } + UserOperation::GetModlog => { + let get_modlog: GetModlog = serde_json::from_str(data).unwrap(); + get_modlog.perform(self, msg.id) + }, + UserOperation::BanFromCommunity => { + let ban_from_community: BanFromCommunity = serde_json::from_str(data).unwrap(); + ban_from_community.perform(self, msg.id) + }, + UserOperation::AddModToCommunity => { + let mod_add_to_community: AddModToCommunity = serde_json::from_str(data).unwrap(); + mod_add_to_community.perform(self, msg.id) + }, }; MessageResult(res) @@ -554,7 +640,9 @@ impl Perform for Register { email: self.email.to_owned(), password_encrypted: self.password.to_owned(), preferred_username: None, - updated: None + updated: None, + admin: None, + banned: None, }; // Create the user @@ -609,7 +697,8 @@ impl Perform for CreateCommunity { description: self.description.to_owned(), category_id: self.category_id, creator_id: user_id, - updated: None + removed: None, + updated: None, }; let inserted_community = match Community::create(&conn, &community_form) { @@ -737,12 +826,19 @@ impl Perform for CreatePost { let user_id = claims.id; + // Check for a ban + if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() { + return self.error("You have been banned from this community"); + } + let post_form = PostForm { name: self.name.to_owned(), url: self.url.to_owned(), body: self.body.to_owned(), community_id: self.community_id, creator_id: user_id, + removed: None, + locked: None, updated: None }; @@ -913,6 +1009,12 @@ impl Perform for CreateComment { let user_id = claims.id; + // Check for a ban + let post = Post::read(&conn, self.post_id).unwrap(); + if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + return self.error("You have been banned from this community"); + } + let content_slurs_removed = remove_slurs(&self.content.to_owned()); let comment_form = CommentForm { @@ -920,6 +1022,7 @@ impl Perform for CreateComment { parent_id: self.parent_id.to_owned(), post_id: self.post_id, creator_id: user_id, + removed: None, updated: None }; @@ -991,10 +1094,22 @@ impl Perform for EditComment { let user_id = claims.id; - // Verify its the creator - let orig_comment = Comment::read(&conn, self.edit_id).unwrap(); - if user_id != orig_comment.creator_id { - return self.error("Incorrect creator."); + + // Verify its the creator or a mod + let orig_comment = CommentView::read(&conn, self.edit_id, None).unwrap(); + let mut editors: Vec = CommunityModeratorView::for_community(&conn, orig_comment.community_id) + .unwrap() + .into_iter() + .map(|m| m.user_id) + .collect(); + editors.push(self.creator_id); + if !editors.contains(&user_id) { + return self.error("Not allowed to edit comment."); + } + + // Check for a ban + if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { + return self.error("You have been banned from this community"); } let content_slurs_removed = remove_slurs(&self.content.to_owned()); @@ -1003,7 +1118,8 @@ impl Perform for EditComment { content: content_slurs_removed, parent_id: self.parent_id, post_id: self.post_id, - creator_id: user_id, + creator_id: self.creator_id, + removed: self.removed.to_owned(), updated: Some(naive_now()) }; @@ -1014,6 +1130,17 @@ impl Perform for EditComment { } }; + // Mod tables + if let Some(removed) = self.removed.to_owned() { + let form = ModRemoveCommentForm { + mod_user_id: user_id, + comment_id: self.edit_id, + removed: Some(removed), + reason: self.reason.to_owned(), + }; + ModRemoveComment::create(&conn, &form).unwrap(); + } + let comment_view = CommentView::read(&conn, self.edit_id, Some(user_id)).unwrap(); @@ -1061,6 +1188,12 @@ impl Perform for CreateCommentLike { let user_id = claims.id; + // Check for a ban + let post = Post::read(&conn, self.post_id).unwrap(); + if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + return self.error("You have been banned from this community"); + } + let like_form = CommentLikeForm { comment_id: self.comment_id, post_id: self.post_id, @@ -1173,6 +1306,12 @@ impl Perform for CreatePostLike { let user_id = claims.id; + // Check for a ban + let post = Post::read(&conn, self.post_id).unwrap(); + if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { + return self.error("You have been banned from this community"); + } + let like_form = PostLikeForm { post_id: self.post_id, user_id: user_id, @@ -1236,18 +1375,30 @@ impl Perform for EditPost { let user_id = claims.id; - // Verify its the creator - let orig_post = Post::read(&conn, self.edit_id).unwrap(); - if user_id != orig_post.creator_id { - return self.error("Incorrect creator."); + // Verify its the creator or a mod + let mut editors: Vec = CommunityModeratorView::for_community(&conn, self.community_id) + .unwrap() + .into_iter() + .map(|m| m.user_id) + .collect(); + editors.push(self.creator_id); + if !editors.contains(&user_id) { + return self.error("Not allowed to edit comment."); + } + + // Check for a ban + if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() { + return self.error("You have been banned from this community"); } let post_form = PostForm { name: self.name.to_owned(), url: self.url.to_owned(), body: self.body.to_owned(), - creator_id: user_id, + creator_id: self.creator_id.to_owned(), community_id: self.community_id, + removed: self.removed.to_owned(), + locked: self.locked.to_owned(), updated: Some(naive_now()) }; @@ -1258,6 +1409,26 @@ impl Perform for EditPost { } }; + // Mod tables + if let Some(removed) = self.removed.to_owned() { + let form = ModRemovePostForm { + mod_user_id: user_id, + post_id: self.edit_id, + removed: Some(removed), + reason: self.reason.to_owned(), + }; + ModRemovePost::create(&conn, &form).unwrap(); + } + + if let Some(locked) = self.locked.to_owned() { + let form = ModLockPostForm { + mod_user_id: user_id, + post_id: self.edit_id, + locked: Some(locked), + }; + ModLockPost::create(&conn, &form).unwrap(); + } + let post_view = PostView::read(&conn, self.edit_id, Some(user_id)).unwrap(); let mut post_sent = post_view.clone(); @@ -1307,7 +1478,6 @@ impl Perform for EditCommunity { let user_id = claims.id; - // Verify its a mod let moderator_view = CommunityModeratorView::for_community(&conn, self.edit_id).unwrap(); let mod_ids: Vec = moderator_view.into_iter().map(|m| m.user_id).collect(); @@ -1321,6 +1491,7 @@ impl Perform for EditCommunity { description: self.description.to_owned(), category_id: self.category_id.to_owned(), creator_id: user_id, + removed: self.removed.to_owned(), updated: Some(naive_now()) }; @@ -1331,11 +1502,23 @@ impl Perform for EditCommunity { } }; - let community_view = CommunityView::read(&conn, self.edit_id, Some(user_id)).unwrap(); + // Mod tables + if let Some(removed) = self.removed.to_owned() { + let expires = match self.expires { + Some(time) => Some(naive_from_unix(time)), + None => None + }; + let form = ModRemoveCommunityForm { + mod_user_id: user_id, + community_id: self.edit_id, + removed: Some(removed), + reason: self.reason.to_owned(), + expires: expires + }; + ModRemoveCommunity::create(&conn, &form).unwrap(); + } - // Do the subscriber stuff here - // let mut community_sent = post_view.clone(); - // community_sent.my_vote = None; + let community_view = CommunityView::read(&conn, self.edit_id, Some(user_id)).unwrap(); let community_out = serde_json::to_string( &CommunityResponse { @@ -1345,15 +1528,17 @@ impl Perform for EditCommunity { ) .unwrap(); - // let post_sent_out = serde_json::to_string( - // &PostResponse { - // op: self.op_type().to_string(), - // post: post_sent - // } - // ) - // .unwrap(); + let community_view_sent = CommunityView::read(&conn, self.edit_id, None).unwrap(); - chat.send_room_message(self.edit_id, &community_out, addr); + let community_sent = serde_json::to_string( + &CommunityResponse { + op: self.op_type().to_string(), + community: community_view_sent + } + ) + .unwrap(); + + chat.send_community_message(&conn, self.edit_id, &community_sent, addr); community_out } @@ -1492,3 +1677,178 @@ impl Perform for GetUserDetails { } } +impl Perform for GetModlog { + fn op_type(&self) -> UserOperation { + UserOperation::GetModlog + } + + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { + + let conn = establish_connection(); + + let removed_posts = ModRemovePostView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); + let locked_posts = ModLockPostView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); + let removed_comments = ModRemoveCommentView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); + let removed_communities = ModRemoveCommunityView::list(&conn, self.mod_user_id, self.limit, self.page).unwrap(); + let banned_from_community = ModBanFromCommunityView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); + let banned = ModBanView::list(&conn, self.mod_user_id, self.limit, self.page).unwrap(); + let added_to_community = ModAddCommunityView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); + let added = ModAddView::list(&conn, self.mod_user_id, self.limit, self.page).unwrap(); + + // Return the jwt + serde_json::to_string( + &GetModlogResponse { + op: self.op_type().to_string(), + removed_posts: removed_posts, + locked_posts: locked_posts, + removed_comments: removed_comments, + removed_communities: removed_communities, + banned_from_community: banned_from_community, + banned: banned, + added_to_community: added_to_community, + added: added, + } + ) + .unwrap() + } +} + +impl Perform for BanFromCommunity { + fn op_type(&self) -> UserOperation { + UserOperation::BanFromCommunity + } + + fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + + let conn = establish_connection(); + + let claims = match Claims::decode(&self.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return self.error("Not logged in."); + } + }; + + let user_id = claims.id; + + let community_user_ban_form = CommunityUserBanForm { + community_id: self.community_id, + user_id: self.user_id, + }; + + if self.ban { + match CommunityUserBan::ban(&conn, &community_user_ban_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Community user ban already exists"); + } + }; + } else { + match CommunityUserBan::unban(&conn, &community_user_ban_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Community user ban already exists"); + } + }; + } + + // Mod tables + let expires = match self.expires { + Some(time) => Some(naive_from_unix(time)), + None => None + }; + + let form = ModBanFromCommunityForm { + mod_user_id: user_id, + other_user_id: self.user_id, + community_id: self.community_id, + reason: self.reason.to_owned(), + banned: Some(self.ban), + expires: expires, + }; + ModBanFromCommunity::create(&conn, &form).unwrap(); + + let user_view = UserView::read(&conn, self.user_id).unwrap(); + + let res = serde_json::to_string( + &BanFromCommunityResponse { + op: self.op_type().to_string(), + user: user_view, + banned: self.ban + } + ) + .unwrap(); + + + chat.send_community_message(&conn, self.community_id, &res, addr); + + res + } +} + +impl Perform for AddModToCommunity { + fn op_type(&self) -> UserOperation { + UserOperation::AddModToCommunity + } + + fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + + let conn = establish_connection(); + + let claims = match Claims::decode(&self.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return self.error("Not logged in."); + } + }; + + let user_id = claims.id; + + let community_moderator_form = CommunityModeratorForm { + community_id: self.community_id, + user_id: self.user_id + }; + + if self.added { + match CommunityModerator::join(&conn, &community_moderator_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Community moderator already exists."); + } + }; + } else { + match CommunityModerator::leave(&conn, &community_moderator_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Community moderator already exists."); + } + }; + } + + // Mod tables + let form = ModAddCommunityForm { + mod_user_id: user_id, + other_user_id: self.user_id, + community_id: self.community_id, + removed: Some(!self.added), + }; + ModAddCommunity::create(&conn, &form).unwrap(); + + let moderators = CommunityModeratorView::for_community(&conn, self.community_id).unwrap(); + + let res = serde_json::to_string( + &AddModToCommunityResponse { + op: self.op_type().to_string(), + moderators: moderators, + } + ) + .unwrap(); + + + chat.send_community_message(&conn, self.community_id, &res, addr); + + res + + } +} + diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index a87dd3567..66f3094ee 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -1,6 +1,6 @@ import { Component, linkEvent } from 'inferno'; import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces'; -import { WebSocketService } from '../services'; +import { WebSocketService, UserService } from '../services'; import * as autosize from 'autosize'; interface CommentFormProps { @@ -8,6 +8,7 @@ interface CommentFormProps { node?: CommentNodeI; onReplyCancel?(): any; edit?: boolean; + disabled?: boolean; } interface CommentFormState { @@ -21,9 +22,10 @@ export class CommentForm extends Component { commentForm: { auth: null, content: null, - post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId + post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId, + creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null, }, - buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply" + buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply", } constructor(props: any, context: any) { @@ -36,6 +38,7 @@ export class CommentForm extends Component { this.state.commentForm.edit_id = this.props.node.comment.id; this.state.commentForm.parent_id = this.props.node.comment.parent_id; this.state.commentForm.content = this.props.node.comment.content; + this.state.commentForm.creator_id = this.props.node.comment.creator_id; } else { // A reply gets a new parent id this.state.commentForm.parent_id = this.props.node.comment.id; @@ -53,12 +56,12 @@ export class CommentForm extends Component {
-