From 9afadfb9c4c5db1796848ec4af9756fe03d51ee3 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Fri, 19 Apr 2019 21:06:25 -0700 Subject: Saving replies, the actual fixes will be in the merge to dev. --- .../2019-02-27-170003_create_community/up.sql | 2 +- .../2019-03-03-163336_create_post/down.sql | 2 + .../2019-03-03-163336_create_post/up.sql | 19 +- .../2019-03-05-233828_create_comment/down.sql | 1 + .../2019-03-05-233828_create_comment/up.sql | 11 +- .../2019-03-30-212058_create_post_view/up.sql | 6 +- .../2019-04-03-155205_create_community_view/up.sql | 7 +- .../2019-04-03-155309_create_comment_view/up.sql | 7 +- .../2019-04-11-144915_create_mod_views/up.sql | 4 +- server/src/actions/comment.rs | 110 ++++++-- server/src/actions/comment_view.rs | 47 ++-- server/src/actions/community.rs | 8 +- server/src/actions/community_view.rs | 6 +- server/src/actions/moderator.rs | 6 +- server/src/actions/post.rs | 163 +++++++++--- server/src/actions/post_view.rs | 49 ++-- server/src/lib.rs | 10 + server/src/schema.rs | 45 +++- server/src/websocket_server/server.rs | 293 +++++++++++++++++---- ui/src/components/comment-node.tsx | 229 +++++++++++----- ui/src/components/comment-nodes.tsx | 7 +- ui/src/components/communities.tsx | 4 +- ui/src/components/community.tsx | 6 +- ui/src/components/create-community.tsx | 2 +- ui/src/components/create-post.tsx | 2 +- ui/src/components/login.tsx | 4 +- ui/src/components/main.tsx | 12 +- ui/src/components/modlog.tsx | 40 ++- ui/src/components/navbar.tsx | 26 +- ui/src/components/post-listing.tsx | 35 ++- ui/src/components/post-listings.tsx | 2 +- ui/src/components/post.tsx | 50 +++- ui/src/components/setup.tsx | 2 +- ui/src/components/sidebar.tsx | 4 +- ui/src/components/site-form.tsx | 2 +- ui/src/components/user.tsx | 14 +- ui/src/index.html | 2 - ui/src/index.tsx | 3 +- ui/src/interfaces.ts | 72 +++-- ui/src/main.css | 87 ------ ui/src/services/WebSocketService.ts | 23 +- ui/src/utils.ts | 21 +- 42 files changed, 1015 insertions(+), 430 deletions(-) delete mode 100644 ui/src/main.css 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 2d6856b3..363f99f2 100644 --- a/server/migrations/2019-02-27-170003_create_community/up.sql +++ b/server/migrations/2019-02-27-170003_create_community/up.sql @@ -38,7 +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, + removed boolean default false not null, published timestamp not null default now(), updated timestamp ); diff --git a/server/migrations/2019-03-03-163336_create_post/down.sql b/server/migrations/2019-03-03-163336_create_post/down.sql index acc0b5d1..a671c2e7 100644 --- a/server/migrations/2019-03-03-163336_create_post/down.sql +++ b/server/migrations/2019-03-03-163336_create_post/down.sql @@ -1,2 +1,4 @@ +drop table post_read; +drop table post_saved; drop table post_like; drop table post; 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 c3b7c0b8..90737812 100644 --- a/server/migrations/2019-03-03-163336_create_post/up.sql +++ b/server/migrations/2019-03-03-163336_create_post/up.sql @@ -5,8 +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, + removed boolean default false not null, + locked boolean default false not null, published timestamp not null default now(), updated timestamp ); @@ -20,3 +20,18 @@ create table post_like ( unique(post_id, user_id) ); +create table post_saved ( + id serial primary key, + post_id int references post 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(post_id, user_id) +); + +create table post_read ( + id serial primary key, + post_id int references post 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(post_id, user_id) +); diff --git a/server/migrations/2019-03-05-233828_create_comment/down.sql b/server/migrations/2019-03-05-233828_create_comment/down.sql index 5b92a44c..80fe0b1f 100644 --- a/server/migrations/2019-03-05-233828_create_comment/down.sql +++ b/server/migrations/2019-03-05-233828_create_comment/down.sql @@ -1,2 +1,3 @@ +drop table comment_saved; drop table comment_like; drop table comment; 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 214d50a6..4b754ece 100644 --- a/server/migrations/2019-03-05-233828_create_comment/up.sql +++ b/server/migrations/2019-03-05-233828_create_comment/up.sql @@ -4,7 +4,8 @@ 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, + removed boolean default false not null, + read boolean default false not null, published timestamp not null default now(), updated timestamp ); @@ -18,3 +19,11 @@ create table comment_like ( published timestamp not null default now(), unique(comment_id, user_id) ); + +create table comment_saved ( + id serial primary key, + comment_id int references comment 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(comment_id, user_id) +); 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 ecf3280a..2f71b6fb 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 @@ -31,7 +31,8 @@ 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, -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 +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved 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 @@ -43,6 +44,7 @@ ap.*, null as user_id, null as my_vote, null as subscribed, -null as am_mod +null as read, +null as saved from all_post ap ; 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 1b73af51..7d38dbfa 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,19 +13,16 @@ with all_community as select ac.*, u.id as user_id, -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 +(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed 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 union all select ac.*, null as user_id, -null as subscribed, -null as am_mod +null as subscribed from all_community ac ; 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 a73b6182..a78e3ac3 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 @@ -4,7 +4,8 @@ with all_comment as select c.*, (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 u.banned from user_ u where c.creator_id = u.id) as banned, + (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, (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, @@ -18,7 +19,7 @@ select ac.*, u.id as user_id, 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 +(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved 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 @@ -29,6 +30,6 @@ select ac.*, null as user_id, null as my_vote, - null as am_mod + null as saved from all_comment ac ; 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 index 908028d0..70a33e46 100644 --- a/server/migrations/2019-04-11-144915_create_mod_views/up.sql +++ b/server/migrations/2019-04-11-144915_create_mod_views/up.sql @@ -43,8 +43,7 @@ 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; - +from mod_ban mb; create view mod_add_community_view as select ma.*, @@ -53,7 +52,6 @@ select ma.*, (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, diff --git a/server/src/actions/comment.rs b/server/src/actions/comment.rs index f6eee5f1..c3aa0107 100644 --- a/server/src/actions/comment.rs +++ b/server/src/actions/comment.rs @@ -1,9 +1,9 @@ extern crate diesel; -use schema::{comment, comment_like}; +use schema::{comment, comment_like, comment_saved}; use diesel::*; use diesel::result::Error; use serde::{Deserialize, Serialize}; -use {Crud, Likeable}; +use {Crud, Likeable, Saveable}; use actions::post::Post; // WITH RECURSIVE MyTree AS ( @@ -22,7 +22,8 @@ pub struct Comment { pub post_id: i32, pub parent_id: Option, pub content: String, - pub removed: Option, + pub removed: bool, + pub read: bool, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -38,27 +39,6 @@ pub struct CommentForm { pub updated: Option } -#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)] -#[belongs_to(Comment)] -#[table_name = "comment_like"] -pub struct CommentLike { - pub id: i32, - pub user_id: i32, - pub comment_id: i32, - pub post_id: i32, - pub score: i16, - pub published: chrono::NaiveDateTime, -} - -#[derive(Insertable, AsChangeset, Clone)] -#[table_name="comment_like"] -pub struct CommentLikeForm { - pub user_id: i32, - pub comment_id: i32, - pub post_id: i32, - pub score: i16 -} - impl Crud for Comment { fn read(conn: &PgConnection, comment_id: i32) -> Result { use schema::comment::dsl::*; @@ -87,6 +67,27 @@ impl Crud for Comment { } } +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)] +#[belongs_to(Comment)] +#[table_name = "comment_like"] +pub struct CommentLike { + pub id: i32, + pub user_id: i32, + pub comment_id: i32, + pub post_id: i32, + pub score: i16, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="comment_like"] +pub struct CommentLikeForm { + pub user_id: i32, + pub comment_id: i32, + pub post_id: i32, + pub score: i16 +} + impl Likeable for CommentLike { fn read(conn: &PgConnection, comment_id_from: i32) -> Result, Error> { use schema::comment_like::dsl::*; @@ -119,6 +120,39 @@ impl CommentLike { } } +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Comment)] +#[table_name = "comment_saved"] +pub struct CommentSaved { + pub id: i32, + pub comment_id: i32, + pub user_id: i32, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="comment_saved"] +pub struct CommentSavedForm { + pub comment_id: i32, + pub user_id: i32, +} + +impl Saveable for CommentSaved { + fn save(conn: &PgConnection, comment_saved_form: &CommentSavedForm) -> Result { + use schema::comment_saved::dsl::*; + insert_into(comment_saved) + .values(comment_saved_form) + .get_result::(conn) + } + fn unsave(conn: &PgConnection, comment_saved_form: &CommentSavedForm) -> Result { + use schema::comment_saved::dsl::*; + diesel::delete(comment_saved + .filter(comment_id.eq(comment_saved_form.comment_id)) + .filter(user_id.eq(comment_saved_form.user_id))) + .execute(conn) + } +} + #[cfg(test)] mod tests { use establish_connection; @@ -150,7 +184,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, - removed: None, + removed: false, updated: None }; @@ -162,8 +196,8 @@ mod tests { url: None, body: None, community_id: inserted_community.id, - removed: None, - locked: None, + removed: false, + locked: false, updated: None }; @@ -185,7 +219,8 @@ mod tests { content: "A test comment".into(), creator_id: inserted_user.id, post_id: inserted_post.id, - removed: Some(false), + removed: false, + read: false, parent_id: None, published: inserted_comment.published, updated: None @@ -202,6 +237,7 @@ mod tests { let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap(); + // Comment Like let comment_like_form = CommentLikeForm { comment_id: inserted_comment.id, post_id: inserted_post.id, @@ -220,9 +256,25 @@ mod tests { score: 1 }; + // Comment Saved + let comment_saved_form = CommentSavedForm { + comment_id: inserted_comment.id, + user_id: inserted_user.id, + }; + + let inserted_comment_saved = CommentSaved::save(&conn, &comment_saved_form).unwrap(); + + let expected_comment_saved = CommentSaved { + id: inserted_comment_saved.id, + comment_id: inserted_comment.id, + user_id: inserted_user.id, + published: inserted_comment_saved.published, + }; + let read_comment = Comment::read(&conn, inserted_comment.id).unwrap(); let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap(); let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap(); + let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap(); let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap(); Comment::delete(&conn, inserted_child_comment.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap(); @@ -233,8 +285,10 @@ mod tests { assert_eq!(expected_comment, inserted_comment); assert_eq!(expected_comment, updated_comment); assert_eq!(expected_comment_like, inserted_comment_like); + assert_eq!(expected_comment_saved, inserted_comment_saved); assert_eq!(expected_comment.id, inserted_child_comment.parent_id.unwrap()); assert_eq!(1, like_removed); + assert_eq!(1, saved_removed); assert_eq!(1, num_deleted); } diff --git a/server/src/actions/comment_view.rs b/server/src/actions/comment_view.rs index 0848ee1c..36043716 100644 --- a/server/src/actions/comment_view.rs +++ b/server/src/actions/comment_view.rs @@ -13,18 +13,20 @@ table! { post_id -> Int4, parent_id -> Nullable, content -> Text, - removed -> Nullable, + removed -> Bool, + read -> Bool, published -> Timestamp, updated -> Nullable, community_id -> Int4, - banned -> Nullable, + banned -> Bool, + banned_from_community -> Bool, creator_name -> Varchar, score -> BigInt, upvotes -> BigInt, downvotes -> BigInt, user_id -> Nullable, my_vote -> Nullable, - am_mod -> Nullable, + saved -> Nullable, } } @@ -36,18 +38,20 @@ pub struct CommentView { pub post_id: i32, pub parent_id: Option, pub content: String, - pub removed: Option, + pub removed: bool, + pub read: bool, pub published: chrono::NaiveDateTime, pub updated: Option, pub community_id: i32, - pub banned: Option, + pub banned: bool, + pub banned_from_community: bool, 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, + pub saved: Option, } impl CommentView { @@ -57,6 +61,7 @@ impl CommentView { for_post_id: Option, for_creator_id: Option, my_user_id: Option, + saved_only: bool, page: Option, limit: Option, ) -> Result, Error> { @@ -81,6 +86,10 @@ impl CommentView { if let Some(for_post_id) = for_post_id { query = query.filter(post_id.eq(for_post_id)); }; + + if saved_only { + query = query.filter(saved.eq(true)); + } query = match sort { // SortType::Hot => query.order_by(hot_rank.desc()), @@ -159,7 +168,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, - removed: None, + removed: false, updated: None }; @@ -171,8 +180,8 @@ mod tests { url: None, body: None, community_id: inserted_community.id, - removed: None, - locked: None, + removed: false, + locked: false, updated: None }; @@ -205,8 +214,10 @@ mod tests { post_id: inserted_post.id, community_id: inserted_community.id, parent_id: None, - removed: Some(false), - banned: None, + removed: false, + read: false, + banned: false, + banned_from_community: false, published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), @@ -215,7 +226,7 @@ mod tests { upvotes: 1, user_id: None, my_vote: None, - am_mod: None, + saved: None, }; let expected_comment_view_with_user = CommentView { @@ -225,8 +236,10 @@ mod tests { post_id: inserted_post.id, community_id: inserted_community.id, parent_id: None, - removed: Some(false), - banned: None, + removed: false, + read: false, + banned: false, + banned_from_community: false, published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), @@ -235,11 +248,11 @@ mod tests { upvotes: 1, user_id: Some(inserted_user.id), my_vote: Some(1), - am_mod: None, + saved: None, }; - let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, None, None).unwrap(); - let read_comment_views_with_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, Some(inserted_user.id), None, None).unwrap(); + let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, false, None, None).unwrap(); + let read_comment_views_with_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, Some(inserted_user.id), false, None, None).unwrap(); let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap(); let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap(); diff --git a/server/src/actions/community.rs b/server/src/actions/community.rs index ac331934..594518ba 100644 --- a/server/src/actions/community.rs +++ b/server/src/actions/community.rs @@ -14,7 +14,7 @@ pub struct Community { pub description: Option, pub category_id: i32, pub creator_id: i32, - pub removed: Option, + pub removed: bool, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -27,7 +27,7 @@ pub struct CommunityForm { pub description: Option, pub category_id: i32, pub creator_id: i32, - pub removed: Option, + pub removed: bool, pub updated: Option } @@ -236,7 +236,7 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, - removed: None, + removed: false, updated: None, }; @@ -249,7 +249,7 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, - removed: Some(false), + removed: false, published: inserted_community.published, updated: None }; diff --git a/server/src/actions/community_view.rs b/server/src/actions/community_view.rs index 4db97491..8966ee15 100644 --- a/server/src/actions/community_view.rs +++ b/server/src/actions/community_view.rs @@ -12,7 +12,7 @@ table! { description -> Nullable, category_id -> Int4, creator_id -> Int4, - removed -> Nullable, + removed -> Bool, published -> Timestamp, updated -> Nullable, creator_name -> Varchar, @@ -22,7 +22,6 @@ table! { number_of_comments -> BigInt, user_id -> Nullable, subscribed -> Nullable, - am_mod -> Nullable, } } @@ -83,7 +82,7 @@ pub struct CommunityView { pub description: Option, pub category_id: i32, pub creator_id: i32, - pub removed: Option, + pub removed: bool, pub published: chrono::NaiveDateTime, pub updated: Option, pub creator_name: String, @@ -93,7 +92,6 @@ pub struct CommunityView { pub number_of_comments: i64, pub user_id: Option, pub subscribed: Option, - pub am_mod: Option, } impl CommunityView { diff --git a/server/src/actions/moderator.rs b/server/src/actions/moderator.rs index a97b2120..e0d885ce 100644 --- a/server/src/actions/moderator.rs +++ b/server/src/actions/moderator.rs @@ -441,7 +441,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, - removed: None, + removed: false, updated: None }; @@ -453,8 +453,8 @@ mod tests { body: None, creator_id: inserted_user.id, community_id: inserted_community.id, - removed: None, - locked: None, + removed: false, + locked: false, updated: None }; diff --git a/server/src/actions/post.rs b/server/src/actions/post.rs index 468b3a9b..0fd0e5c5 100644 --- a/server/src/actions/post.rs +++ b/server/src/actions/post.rs @@ -1,9 +1,9 @@ extern crate diesel; -use schema::{post, post_like}; +use schema::{post, post_like, post_saved, post_read}; use diesel::*; use diesel::result::Error; use serde::{Deserialize, Serialize}; -use {Crud, Likeable}; +use {Crud, Likeable, Saveable, Readable}; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name="post"] @@ -14,8 +14,8 @@ pub struct Post { pub body: Option, pub creator_id: i32, pub community_id: i32, - pub removed: Option, - pub locked: Option, + pub removed: bool, + pub locked: bool, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -28,30 +28,11 @@ pub struct PostForm { pub body: Option, pub creator_id: i32, pub community_id: i32, - pub removed: Option, - pub locked: Option, + pub removed: bool, + pub locked: bool, pub updated: Option } -#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] -#[belongs_to(Post)] -#[table_name = "post_like"] -pub struct PostLike { - pub id: i32, - pub post_id: i32, - pub user_id: i32, - pub score: i16, - pub published: chrono::NaiveDateTime, -} - -#[derive(Insertable, AsChangeset, Clone)] -#[table_name="post_like"] -pub struct PostLikeForm { - pub post_id: i32, - pub user_id: i32, - pub score: i16 -} - impl Crud for Post { fn read(conn: &PgConnection, post_id: i32) -> Result { use schema::post::dsl::*; @@ -80,6 +61,25 @@ impl Crud for Post { } } +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Post)] +#[table_name = "post_like"] +pub struct PostLike { + pub id: i32, + pub post_id: i32, + pub user_id: i32, + pub score: i16, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="post_like"] +pub struct PostLikeForm { + pub post_id: i32, + pub user_id: i32, + pub score: i16 +} + impl Likeable for PostLike { fn read(conn: &PgConnection, post_id_from: i32) -> Result, Error> { use schema::post_like::dsl::*; @@ -102,6 +102,72 @@ impl Likeable for PostLike { } } +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Post)] +#[table_name = "post_saved"] +pub struct PostSaved { + pub id: i32, + pub post_id: i32, + pub user_id: i32, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="post_saved"] +pub struct PostSavedForm { + pub post_id: i32, + pub user_id: i32, +} + +impl Saveable for PostSaved { + fn save(conn: &PgConnection, post_saved_form: &PostSavedForm) -> Result { + use schema::post_saved::dsl::*; + insert_into(post_saved) + .values(post_saved_form) + .get_result::(conn) + } + fn unsave(conn: &PgConnection, post_saved_form: &PostSavedForm) -> Result { + use schema::post_saved::dsl::*; + diesel::delete(post_saved + .filter(post_id.eq(post_saved_form.post_id)) + .filter(user_id.eq(post_saved_form.user_id))) + .execute(conn) + } +} + +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Post)] +#[table_name = "post_read"] +pub struct PostRead { + pub id: i32, + pub post_id: i32, + pub user_id: i32, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="post_read"] +pub struct PostReadForm { + pub post_id: i32, + pub user_id: i32, +} + +impl Readable for PostRead { + fn mark_as_read(conn: &PgConnection, post_read_form: &PostReadForm) -> Result { + use schema::post_read::dsl::*; + insert_into(post_read) + .values(post_read_form) + .get_result::(conn) + } + fn mark_as_unread(conn: &PgConnection, post_read_form: &PostReadForm) -> Result { + use schema::post_read::dsl::*; + diesel::delete(post_read + .filter(post_id.eq(post_read_form.post_id)) + .filter(user_id.eq(post_read_form.user_id))) + .execute(conn) + } +} + #[cfg(test)] mod tests { use establish_connection; @@ -132,7 +198,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, - removed: None, + removed: false, updated: None }; @@ -144,8 +210,8 @@ mod tests { body: None, creator_id: inserted_user.id, community_id: inserted_community.id, - removed: None, - locked: None, + removed: false, + locked: false, updated: None }; @@ -159,11 +225,12 @@ mod tests { creator_id: inserted_user.id, community_id: inserted_community.id, published: inserted_post.published, - removed: Some(false), - locked: Some(false), + removed: false, + locked: false, updated: None }; + // Post Like let post_like_form = PostLikeForm { post_id: inserted_post.id, user_id: inserted_user.id, @@ -179,10 +246,42 @@ mod tests { published: inserted_post_like.published, score: 1 }; + + // Post Save + let post_saved_form = PostSavedForm { + post_id: inserted_post.id, + user_id: inserted_user.id, + }; + + let inserted_post_saved = PostSaved::save(&conn, &post_saved_form).unwrap(); + + let expected_post_saved = PostSaved { + id: inserted_post_saved.id, + post_id: inserted_post.id, + user_id: inserted_user.id, + published: inserted_post_saved.published, + }; + + // Post Read + let post_read_form = PostReadForm { + post_id: inserted_post.id, + user_id: inserted_user.id, + }; + + let inserted_post_read = PostRead::mark_as_read(&conn, &post_read_form).unwrap(); + + let expected_post_read = PostRead { + id: inserted_post_read.id, + post_id: inserted_post.id, + user_id: inserted_user.id, + published: inserted_post_read.published, + }; let read_post = Post::read(&conn, inserted_post.id).unwrap(); let updated_post = Post::update(&conn, inserted_post.id, &new_post).unwrap(); let like_removed = PostLike::remove(&conn, &post_like_form).unwrap(); + let saved_removed = PostSaved::unsave(&conn, &post_saved_form).unwrap(); + let read_removed = PostRead::mark_as_unread(&conn, &post_read_form).unwrap(); let num_deleted = Post::delete(&conn, inserted_post.id).unwrap(); Community::delete(&conn, inserted_community.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap(); @@ -191,7 +290,11 @@ mod tests { assert_eq!(expected_post, inserted_post); assert_eq!(expected_post, updated_post); assert_eq!(expected_post_like, inserted_post_like); + assert_eq!(expected_post_saved, inserted_post_saved); + assert_eq!(expected_post_read, inserted_post_read); assert_eq!(1, like_removed); + assert_eq!(1, saved_removed); + assert_eq!(1, read_removed); assert_eq!(1, num_deleted); } diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index 7ab490aa..78fcef63 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -19,8 +19,8 @@ table! { body -> Nullable, creator_id -> Int4, community_id -> Int4, - removed -> Nullable, - locked -> Nullable, + removed -> Bool, + locked -> Bool, published -> Timestamp, updated -> Nullable, creator_name -> Varchar, @@ -33,7 +33,8 @@ table! { user_id -> Nullable, my_vote -> Nullable, subscribed -> Nullable, - am_mod -> Nullable, + read -> Nullable, + saved -> Nullable, } } @@ -47,8 +48,8 @@ pub struct PostView { pub body: Option, pub creator_id: i32, pub community_id: i32, - pub removed: Option, - pub locked: Option, + pub removed: bool, + pub locked: bool, pub published: chrono::NaiveDateTime, pub updated: Option, pub creator_name: String, @@ -61,7 +62,8 @@ pub struct PostView { pub user_id: Option, pub my_vote: Option, pub subscribed: Option, - pub am_mod: Option, + pub read: Option, + pub saved: Option, } impl PostView { @@ -71,6 +73,8 @@ impl PostView { for_community_id: Option, for_creator_id: Option, my_user_id: Option, + saved_only: bool, + unread_only: bool, page: Option, limit: Option, ) -> Result, Error> { @@ -88,6 +92,15 @@ impl PostView { query = query.filter(creator_id.eq(for_creator_id)); }; + // TODO these are wrong, bc they'll only show saved for your logged in user, not theirs + if saved_only { + query = query.filter(saved.eq(true)); + }; + + if unread_only { + query = query.filter(read.eq(false)); + }; + match type_ { PostListingType::Subscribed => { query = query.filter(subscribed.eq(true)); @@ -187,7 +200,7 @@ mod tests { description: None, creator_id: inserted_user.id, category_id: 1, - removed: None, + removed: false, updated: None }; @@ -199,8 +212,8 @@ mod tests { body: None, creator_id: inserted_user.id, community_id: inserted_community.id, - removed: None, - locked: None, + removed: false, + locked: false, updated: None }; @@ -239,8 +252,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), + removed: false, + locked: false, community_name: community_name.to_owned(), number_of_comments: 0, score: 1, @@ -250,7 +263,8 @@ mod tests { published: inserted_post.published, updated: None, subscribed: None, - am_mod: None, + read: None, + saved: None, }; let expected_post_listing_with_user = PostView { @@ -260,8 +274,8 @@ mod tests { name: post_name.to_owned(), url: None, body: None, - removed: Some(false), - locked: Some(false), + removed: false, + locked: false, creator_id: inserted_user.id, creator_name: user_name.to_owned(), community_id: inserted_community.id, @@ -274,12 +288,13 @@ mod tests { published: inserted_post.published, updated: None, subscribed: None, - am_mod: None, + read: None, + saved: None, }; - let read_post_listings_with_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, Some(inserted_user.id), None, None).unwrap(); - let read_post_listings_no_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, None, None, None).unwrap(); + let read_post_listings_with_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, Some(inserted_user.id), false, false, None, None).unwrap(); + let read_post_listings_no_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, None, false, false, None, None).unwrap(); let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap(); let read_post_listing_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 3390dbdc..31c1af7c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -55,6 +55,16 @@ pub trait Bannable { fn unban(conn: &PgConnection, form: &T) -> Result where Self: Sized; } +pub trait Saveable { + fn save(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn unsave(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + +pub trait Readable { + fn mark_as_read(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn mark_as_unread(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + pub fn establish_connection() -> PgConnection { let db_url = Settings::get().db_url; PgConnection::establish(&db_url) diff --git a/server/src/schema.rs b/server/src/schema.rs index f431610a..65c2ae55 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -12,7 +12,8 @@ table! { post_id -> Int4, parent_id -> Nullable, content -> Text, - removed -> Nullable, + removed -> Bool, + read -> Bool, published -> Timestamp, updated -> Nullable, } @@ -29,6 +30,15 @@ table! { } } +table! { + comment_saved (id) { + id -> Int4, + comment_id -> Int4, + user_id -> Int4, + published -> Timestamp, + } +} + table! { community (id) { id -> Int4, @@ -37,7 +47,7 @@ table! { description -> Nullable, category_id -> Int4, creator_id -> Int4, - removed -> Nullable, + removed -> Bool, published -> Timestamp, updated -> Nullable, } @@ -168,8 +178,8 @@ table! { body -> Nullable, creator_id -> Int4, community_id -> Int4, - removed -> Nullable, - locked -> Nullable, + removed -> Bool, + locked -> Bool, published -> Timestamp, updated -> Nullable, } @@ -185,6 +195,24 @@ table! { } } +table! { + post_read (id) { + id -> Int4, + post_id -> Int4, + user_id -> Int4, + published -> Timestamp, + } +} + +table! { + post_saved (id) { + id -> Int4, + post_id -> Int4, + user_id -> Int4, + published -> Timestamp, + } +} + table! { site (id) { id -> Int4, @@ -225,6 +253,8 @@ joinable!(comment -> user_ (creator_id)); joinable!(comment_like -> comment (comment_id)); joinable!(comment_like -> post (post_id)); joinable!(comment_like -> user_ (user_id)); +joinable!(comment_saved -> comment (comment_id)); +joinable!(comment_saved -> user_ (user_id)); joinable!(community -> category (category_id)); joinable!(community -> user_ (creator_id)); joinable!(community_follower -> community (community_id)); @@ -247,6 +277,10 @@ joinable!(post -> community (community_id)); joinable!(post -> user_ (creator_id)); joinable!(post_like -> post (post_id)); joinable!(post_like -> user_ (user_id)); +joinable!(post_read -> post (post_id)); +joinable!(post_read -> user_ (user_id)); +joinable!(post_saved -> post (post_id)); +joinable!(post_saved -> user_ (user_id)); joinable!(site -> user_ (creator_id)); joinable!(user_ban -> user_ (user_id)); @@ -254,6 +288,7 @@ allow_tables_to_appear_in_same_query!( category, comment, comment_like, + comment_saved, community, community_follower, community_moderator, @@ -268,6 +303,8 @@ allow_tables_to_appear_in_same_query!( mod_remove_post, post, post_like, + post_read, + post_saved, site, user_, user_ban, diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index a4c5b620..d1f72109 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -11,7 +11,7 @@ use bcrypt::{verify}; use std::str::FromStr; use diesel::PgConnection; -use {Crud, Joinable, Likeable, Followable, Bannable, establish_connection, naive_now, naive_from_unix, SortType, has_slurs, remove_slurs}; +use {Crud, Joinable, Likeable, Followable, Bannable, Saveable, establish_connection, naive_now, naive_from_unix, SortType, has_slurs, remove_slurs}; use actions::community::*; use actions::user::*; use actions::post::*; @@ -26,7 +26,7 @@ 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, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser + Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser } #[derive(Serialize, Deserialize)] @@ -164,7 +164,8 @@ pub struct GetPostResponse { post: PostView, comments: Vec, community: CommunityView, - moderators: Vec + moderators: Vec, + admins: Vec, } #[derive(Serialize, Deserialize)] @@ -217,6 +218,13 @@ pub struct EditComment { auth: String } +#[derive(Serialize, Deserialize)] +pub struct SaveComment { + comment_id: i32, + save: bool, + auth: String +} + #[derive(Serialize, Deserialize)] pub struct CommentResponse { op: String, @@ -253,9 +261,16 @@ pub struct EditPost { name: String, url: Option, body: Option, - removed: Option, + removed: bool, + locked: bool, reason: Option, - locked: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct SavePost { + post_id: i32, + save: bool, auth: String } @@ -266,7 +281,7 @@ pub struct EditCommunity { title: String, description: Option, category_id: i32, - removed: Option, + removed: bool, reason: Option, expires: Option, auth: String @@ -297,7 +312,7 @@ pub struct GetUserDetails { page: Option, limit: Option, community_id: Option, - auth: Option + saved_only: bool, } #[derive(Serialize, Deserialize)] @@ -308,8 +323,6 @@ pub struct GetUserDetailsResponse { moderates: Vec, comments: Vec, posts: Vec, - saved_posts: Vec, - saved_comments: Vec, } #[derive(Serialize, Deserialize)] @@ -468,6 +481,8 @@ impl ChatServer { Some(community_id), None, None, + false, + false, None, Some(9999)) .unwrap(); @@ -491,7 +506,6 @@ impl Handler for ChatServer { type Result = usize; fn handle(&mut self, msg: Connect, _: &mut Context) -> Self::Result { - println!("Someone joined"); // notify all users in same room // self.send_room_message(&"Main".to_owned(), "Someone joined", 0); @@ -513,7 +527,6 @@ impl Handler for ChatServer { type Result = (); fn handle(&mut self, msg: Disconnect, _: &mut Context) { - println!("Someone disconnected"); // let mut rooms: Vec = Vec::new(); @@ -586,6 +599,10 @@ impl Handler for ChatServer { let edit_comment: EditComment = serde_json::from_str(data).unwrap(); edit_comment.perform(self, msg.id) }, + UserOperation::SaveComment => { + let save_post: SaveComment = serde_json::from_str(data).unwrap(); + save_post.perform(self, msg.id) + }, UserOperation::CreateCommentLike => { let create_comment_like: CreateCommentLike = serde_json::from_str(data).unwrap(); create_comment_like.perform(self, msg.id) @@ -602,6 +619,10 @@ impl Handler for ChatServer { let edit_post: EditPost = serde_json::from_str(data).unwrap(); edit_post.perform(self, msg.id) }, + UserOperation::SavePost => { + let save_post: SavePost = serde_json::from_str(data).unwrap(); + save_post.perform(self, msg.id) + }, UserOperation::EditCommunity => { let edit_community: EditCommunity = serde_json::from_str(data).unwrap(); edit_community.perform(self, msg.id) @@ -745,11 +766,11 @@ impl Perform for Register { } }; - // If its an admin, add them as a mod to main + // If its an admin, add them as a mod and follower to main if self.admin { let community_moderator_form = CommunityModeratorForm { community_id: 1, - user_id: inserted_user.id + user_id: inserted_user.id, }; let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { @@ -758,6 +779,18 @@ impl Perform for Register { return self.error("Community moderator already exists."); } }; + + let community_follower_form = CommunityFollowerForm { + community_id: 1, + user_id: inserted_user.id, + }; + + let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Community follower already exists."); + } + }; } @@ -797,6 +830,11 @@ impl Perform for CreateCommunity { let user_id = claims.id; + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } + // When you create a community, make sure the user becomes a moderator and a follower let community_form = CommunityForm { @@ -805,7 +843,7 @@ impl Perform for CreateCommunity { description: self.description.to_owned(), category_id: self.category_id, creator_id: user_id, - removed: None, + removed: false, updated: None, }; @@ -934,19 +972,24 @@ impl Perform for CreatePost { let user_id = claims.id; - // Check for a ban + // Check for a community ban if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() { return self.error("You have been banned from this community"); } + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } + 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, + removed: false, + locked: false, updated: None }; @@ -1031,12 +1074,14 @@ impl Perform for GetPost { chat.rooms.get_mut(&self.id).unwrap().insert(addr); - let comments = CommentView::list(&conn, &SortType::New, Some(self.id), None, user_id, None, Some(9999)).unwrap(); + let comments = CommentView::list(&conn, &SortType::New, Some(self.id), None, user_id, false, None, Some(9999)).unwrap(); let community = CommunityView::read(&conn, post_view.community_id, user_id).unwrap(); let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id).unwrap(); + let admins = UserView::admins(&conn).unwrap(); + // Return the jwt serde_json::to_string( &GetPostResponse { @@ -1044,7 +1089,8 @@ impl Perform for GetPost { post: post_view, comments: comments, community: community, - moderators: moderators + moderators: moderators, + admins: admins, } ) .unwrap() @@ -1117,11 +1163,16 @@ impl Perform for CreateComment { let user_id = claims.id; - // Check for a ban + // Check for a community 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"); } + + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } let content_slurs_removed = remove_slurs(&self.content.to_owned()); @@ -1202,24 +1253,38 @@ impl Perform for EditComment { let user_id = claims.id; - - // Verify its the creator or a mod + // Verify its the creator or a mod, or an admin let orig_comment = CommentView::read(&conn, self.edit_id, None).unwrap(); - let mut editors: Vec = CommunityModeratorView::for_community(&conn, orig_comment.community_id) + let mut editors: Vec = vec![self.creator_id]; + editors.append( + &mut CommunityModeratorView::for_community(&conn, orig_comment.community_id) .unwrap() .into_iter() .map(|m| m.user_id) - .collect(); - editors.push(self.creator_id); + .collect() + ); + editors.append( + &mut UserView::admins(&conn) + .unwrap() + .into_iter() + .map(|a| a.id) + .collect() + ); + if !editors.contains(&user_id) { return self.error("Not allowed to edit comment."); } - // Check for a ban + // Check for a community ban if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { return self.error("You have been banned from this community"); } + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } + let content_slurs_removed = remove_slurs(&self.content.to_owned()); let comment_form = CommentForm { @@ -1278,6 +1343,60 @@ impl Perform for EditComment { } } +impl Perform for SaveComment { + fn op_type(&self) -> UserOperation { + UserOperation::SaveComment + } + + 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 comment_saved_form = CommentSavedForm { + comment_id: self.comment_id, + user_id: user_id, + }; + + if self.save { + match CommentSaved::save(&conn, &comment_saved_form) { + Ok(comment) => comment, + Err(_e) => { + return self.error("Couldnt do comment save"); + } + }; + } else { + match CommentSaved::unsave(&conn, &comment_saved_form) { + Ok(comment) => comment, + Err(_e) => { + return self.error("Couldnt do comment save"); + } + }; + } + + let comment_view = CommentView::read(&conn, self.comment_id, Some(user_id)).unwrap(); + + let comment_out = serde_json::to_string( + &CommentResponse { + op: self.op_type().to_string(), + comment: comment_view + } + ) + .unwrap(); + + comment_out + } +} + + impl Perform for CreateCommentLike { fn op_type(&self) -> UserOperation { UserOperation::CreateCommentLike @@ -1296,12 +1415,17 @@ impl Perform for CreateCommentLike { let user_id = claims.id; - // Check for a ban + // Check for a community 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"); } + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } + let like_form = CommentLikeForm { comment_id: self.comment_id, post_id: self.post_id, @@ -1377,7 +1501,7 @@ impl Perform for GetPosts { let type_ = PostListingType::from_str(&self.type_).expect("listing type"); let sort = SortType::from_str(&self.sort).expect("listing sort"); - let posts = match PostView::list(&conn, type_, &sort, self.community_id, None, user_id, self.page, self.limit) { + let posts = match PostView::list(&conn, type_, &sort, self.community_id, None, user_id, false, false, self.page, self.limit) { Ok(posts) => posts, Err(_e) => { return self.error("Couldn't get posts"); @@ -1414,12 +1538,17 @@ impl Perform for CreatePostLike { let user_id = claims.id; - // Check for a ban + // Check for a community 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"); } + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } + let like_form = PostLikeForm { post_id: self.post_id, user_id: user_id, @@ -1494,11 +1623,16 @@ impl Perform for EditPost { return self.error("Not allowed to edit comment."); } - // Check for a ban + // Check for a community ban if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() { return self.error("You have been banned from this community"); } + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } + let post_form = PostForm { name: self.name.to_owned(), url: self.url.to_owned(), @@ -1518,21 +1652,21 @@ impl Perform for EditPost { }; // Mod tables - if let Some(removed) = self.removed.to_owned() { + if self.removed { let form = ModRemovePostForm { mod_user_id: user_id, post_id: self.edit_id, - removed: Some(removed), + removed: Some(self.removed), reason: self.reason.to_owned(), }; ModRemovePost::create(&conn, &form).unwrap(); } - if let Some(locked) = self.locked.to_owned() { + if self.locked { let form = ModLockPostForm { mod_user_id: user_id, post_id: self.edit_id, - locked: Some(locked), + locked: Some(self.locked), }; ModLockPost::create(&conn, &form).unwrap(); } @@ -1564,6 +1698,59 @@ impl Perform for EditPost { } } +impl Perform for SavePost { + fn op_type(&self) -> UserOperation { + UserOperation::SavePost + } + + 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 post_saved_form = PostSavedForm { + post_id: self.post_id, + user_id: user_id, + }; + + if self.save { + match PostSaved::save(&conn, &post_saved_form) { + Ok(post) => post, + Err(_e) => { + return self.error("Couldnt do post save"); + } + }; + } else { + match PostSaved::unsave(&conn, &post_saved_form) { + Ok(post) => post, + Err(_e) => { + return self.error("Couldnt do post save"); + } + }; + } + + let post_view = PostView::read(&conn, self.post_id, Some(user_id)).unwrap(); + + let post_out = serde_json::to_string( + &PostResponse { + op: self.op_type().to_string(), + post: post_view + } + ) + .unwrap(); + + post_out + } +} + impl Perform for EditCommunity { fn op_type(&self) -> UserOperation { UserOperation::EditCommunity @@ -1586,6 +1773,11 @@ impl Perform for EditCommunity { let user_id = claims.id; + // Check for a site ban + if UserView::read(&conn, user_id).unwrap().banned { + return self.error("You have been banned from the site"); + } + // 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(); @@ -1611,7 +1803,7 @@ impl Perform for EditCommunity { }; // Mod tables - if let Some(removed) = self.removed.to_owned() { + if self.removed { let expires = match self.expires { Some(time) => Some(naive_from_unix(time)), None => None @@ -1619,7 +1811,7 @@ impl Perform for EditCommunity { let form = ModRemoveCommunityForm { mod_user_id: user_id, community_id: self.edit_id, - removed: Some(removed), + removed: Some(self.removed), reason: self.reason.to_owned(), expires: expires }; @@ -1750,26 +1942,21 @@ impl Perform for GetUserDetails { let conn = establish_connection(); - let user_id: Option = match &self.auth { - Some(auth) => { - match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None - } - } - None => None - }; - - //TODO add save let sort = SortType::from_str(&self.sort).expect("listing sort"); let user_view = UserView::read(&conn, self.user_id).unwrap(); - let posts = PostView::list(&conn, PostListingType::All, &sort, self.community_id, Some(self.user_id), user_id, self.page, self.limit).unwrap(); - let comments = CommentView::list(&conn, &sort, None, Some(self.user_id), user_id, self.page, self.limit).unwrap(); + let posts = if self.saved_only { + PostView::list(&conn, PostListingType::All, &sort, self.community_id, None, Some(self.user_id), self.saved_only, false, self.page, self.limit).unwrap() + } else { + PostView::list(&conn, PostListingType::All, &sort, self.community_id, Some(self.user_id), None, self.saved_only, false, self.page, self.limit).unwrap() + }; + let comments = if self.saved_only { + CommentView::list(&conn, &sort, None, None, Some(self.user_id), self.saved_only, self.page, self.limit).unwrap() + } else { + CommentView::list(&conn, &sort, None, Some(self.user_id), None, self.saved_only, self.page, self.limit).unwrap() + }; + let follows = CommunityFollowerView::for_user(&conn, self.user_id).unwrap(); let moderates = CommunityModeratorView::for_user(&conn, self.user_id).unwrap(); @@ -1782,8 +1969,6 @@ impl Perform for GetUserDetails { moderates: moderates, comments: comments, posts: posts, - saved_posts: Vec::new(), - saved_comments: Vec::new(), } ) .unwrap() diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index dcfb18a9..c1fc059b 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -1,12 +1,14 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; -import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, BanFromCommunityForm, CommunityUser, AddModToCommunityForm } from '../interfaces'; +import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, SaveCommentForm, BanFromCommunityForm, BanUserForm, CommunityUser, UserView, AddModToCommunityForm, AddAdminForm } from '../interfaces'; import { WebSocketService, UserService } from '../services'; -import { mdToHtml, getUnixTime } from '../utils'; +import { mdToHtml, getUnixTime, canMod, isMod } from '../utils'; import { MomentTime } from './moment-time'; import { CommentForm } from './comment-form'; import { CommentNodes } from './comment-nodes'; +enum BanType {Community, Site}; + interface CommentNodeState { showReply: boolean; showEdit: boolean; @@ -15,6 +17,7 @@ interface CommentNodeState { showBanDialog: boolean; banReason: string; banExpires: string; + banType: BanType; } interface CommentNodeProps { @@ -23,6 +26,7 @@ interface CommentNodeProps { viewOnly?: boolean; locked?: boolean; moderators: Array; + admins: Array; } export class CommentNode extends Component { @@ -35,6 +39,7 @@ export class CommentNode extends Component { showBanDialog: false, banReason: null, banExpires: null, + banType: BanType.Community } constructor(props: any, context: any) { @@ -60,6 +65,12 @@ export class CommentNode extends Component {
  • {node.comment.creator_name}
  • + {this.isMod && +
  • mod
  • + } + {this.isAdmin && +
  • admin
  • + }
  • ( +{node.comment.upvotes} @@ -77,47 +88,70 @@ export class CommentNode extends Component {
      - {!this.props.viewOnly && - + {UserService.Instance.user && !this.props.viewOnly && + <>
    • reply
    • +
    • + {node.comment.saved ? 'unsave' : 'save'} +
    • {this.myComment && <> -
    • - edit -
    • -
    • - delete -
    • - +
    • + edit +
    • +
    • + delete +
    • + } - {this.canMod && - <> + {/* Admins and mods can remove comments */} + {this.canMod &&
    • {!this.props.node.comment.removed ? remove : restore }
    • - {!this.isMod && - <> + } + {/* Mods can ban from community, and appoint as mods to community */} + {this.canMod && + <> + {!this.isMod && +
    • + {!this.props.node.comment.banned_from_community ? + ban : +