From 81da0853aa460da0e277edfccfde9fcdf9334f31 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 3 Apr 2019 13:59:37 -0700 Subject: [PATCH] Adding post editing. - Adding post editing. Fixes #23 - Making SQL versions of comment and post fetching. Fixes #21 - Starting to add forum categories. #17 --- README.md | 1 + .../down.sql | 1 + .../2019-02-27-170003_create_community/up.sql | 40 ++- .../down.sql | 0 .../up.sql | 21 +- .../down.sql | 3 + .../up.sql | 16 + .../down.sql | 1 + .../up.sql | 30 ++ server/src/actions/comment.rs | 94 +----- server/src/actions/comment_view.rs | 195 ++++++++++++ server/src/actions/community.rs | 15 +- server/src/actions/mod.rs | 1 + server/src/actions/post.rs | 3 + server/src/actions/post_view.rs | 67 +--- server/src/apub.rs | 1 - server/src/schema.rs | 12 + server/src/websocket_server/server.rs | 286 +++++++++++------- ui/src/components/create-post.tsx | 129 +------- ui/src/components/post-form.tsx | 162 ++++++++++ ui/src/components/post-listing.tsx | 58 +++- ui/src/components/post.tsx | 15 +- ui/src/interfaces.ts | 16 +- ui/src/services/UserService.ts | 6 +- ui/src/services/WebSocketService.ts | 5 + 25 files changed, 764 insertions(+), 414 deletions(-) rename server/migrations/{2019-03-30-212058_post_view => 2019-03-30-212058_create_post_view}/down.sql (100%) rename server/migrations/{2019-03-30-212058_post_view => 2019-03-30-212058_create_post_view}/up.sql (91%) create mode 100644 server/migrations/2019-04-03-155205_create_community_view/down.sql create mode 100644 server/migrations/2019-04-03-155205_create_community_view/up.sql create mode 100644 server/migrations/2019-04-03-155309_create_comment_view/down.sql create mode 100644 server/migrations/2019-04-03-155309_create_comment_view/up.sql create mode 100644 server/src/actions/comment_view.rs create mode 100644 ui/src/components/post-form.tsx diff --git a/README.md b/README.md index 483a992..1192dc8 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ We have a twitter alternative (mastodon), a facebook alternative (friendica), so - [Rust JWT](https://github.com/Keats/jsonwebtoken) - [Hierarchical tree building javascript](https://stackoverflow.com/a/40732240/1655478) - [Hot sorting discussion](https://meta.stackexchange.com/questions/11602/what-formula-should-be-used-to-determine-hot-questions) [2](https://medium.com/hacking-and-gonzo/how-reddit-ranking-algorithms-work-ef111e33d0d9) +- [Classification types.](https://www.reddit.com/r/ModeratorDuck/wiki/subreddit_classification) ## TODOs - Endpoints 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 1a913b8..f293dfa 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_moderator; drop table community_follower; drop table community; +drop table category; 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 b4eeb29..f78486d 100644 --- a/server/migrations/2019-02-27-170003_create_community/up.sql +++ b/server/migrations/2019-02-27-170003_create_community/up.sql @@ -1,6 +1,44 @@ +create table category ( + id serial primary key, + name varchar(100) not null unique +); + +insert into category (name) values +('Discussion'), +('Humor/Memes'), +('Gaming'), +('Movies'), +('TV'), +('Music'), +('Literature'), +('Comics'), +('Photography'), +('Art'), +('Learning'), +('DIY'), +('Lifestyle'), +('News'), +('Politics'), +('Society'), +('Gender/Identity/Sexuality'), +('Race/Colonisation'), +('Religion'), +('Science/Technology'), +('Programming/Software'), +('Health/Sports/Fitness'), +('Porn'), +('Places'), +('Meta'), +('Other'); + + + create table community ( id serial primary key, name varchar(20) not null unique, + title varchar(100) not null, + 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, published timestamp not null default now(), updated timestamp @@ -20,4 +58,4 @@ create table community_follower ( published timestamp not null default now() ); -insert into community (name, creator_id) values ('main', 1); +insert into community (name, title, category_id, creator_id) values ('main', 'The default Community', 1, 1); diff --git a/server/migrations/2019-03-30-212058_post_view/down.sql b/server/migrations/2019-03-30-212058_create_post_view/down.sql similarity index 100% rename from server/migrations/2019-03-30-212058_post_view/down.sql rename to server/migrations/2019-03-30-212058_create_post_view/down.sql diff --git a/server/migrations/2019-03-30-212058_post_view/up.sql b/server/migrations/2019-03-30-212058_create_post_view/up.sql similarity index 91% rename from server/migrations/2019-03-30-212058_post_view/up.sql rename to server/migrations/2019-03-30-212058_create_post_view/up.sql index f225073..c184863 100644 --- a/server/migrations/2019-03-30-212058_post_view/up.sql +++ b/server/migrations/2019-03-30-212058_create_post_view/up.sql @@ -13,30 +13,23 @@ create view post_view as with all_post as ( select - p.id as id, - p.name as name, - p.url, - p.body, - p.creator_id, + p.*, (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), 0) as score, count (case when pl.score = 1 then 1 else null end) as upvotes, count (case when pl.score = -1 then 1 else null end) as downvotes, - hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank, - p.published, - p.updated + hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank from post p left join post_like pl on p.id = pl.post_id group by p.id ) select +ap.*, u.id as user_id, -coalesce(pl.score, 0) as my_vote, -ap.* +coalesce(pl.score, 0) as my_vote 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 @@ -44,9 +37,9 @@ left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id union all select - null as user_id, - null as my_vote, - ap.* +ap.*, +null as user_id, +null as my_vote from all_post ap ; 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 new file mode 100644 index 0000000..6c7e870 --- /dev/null +++ b/server/migrations/2019-04-03-155205_create_community_view/down.sql @@ -0,0 +1,3 @@ +drop view community_view; +drop view community_moderator_view; +drop view community_follower_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 new file mode 100644 index 0000000..e731b7f --- /dev/null +++ b/server/migrations/2019-04-03-155205_create_community_view/up.sql @@ -0,0 +1,16 @@ +create view community_view as +select *, +(select name from user_ u where c.creator_id = u.id) as creator_name, +(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, +(select count(*) from post p where p.community_id = c.id) as number_of_posts +from community c; + +create view community_moderator_view as +select *, +(select name from user_ u where cm.user_id = u.id) as user_name +from community_moderator cm; + +create view community_follower_view as +select *, +(select name from user_ u where cf.user_id = u.id) as user_name +from community_follower cf; diff --git a/server/migrations/2019-04-03-155309_create_comment_view/down.sql b/server/migrations/2019-04-03-155309_create_comment_view/down.sql new file mode 100644 index 0000000..2da934a --- /dev/null +++ b/server/migrations/2019-04-03-155309_create_comment_view/down.sql @@ -0,0 +1 @@ +drop view comment_view; 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 new file mode 100644 index 0000000..a4d2be9 --- /dev/null +++ b/server/migrations/2019-04-03-155309_create_comment_view/up.sql @@ -0,0 +1,30 @@ +create view comment_view as +with all_comment as +( + select + c.*, + (select name from user_ where c.creator_id = user_.id) 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 + from comment c + left join comment_like cl on c.id = cl.comment_id + group by c.id +) + +select +ac.*, +u.id as user_id, +coalesce(cl.score, 0) as my_vote +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 + +union all + +select + ac.*, + null as user_id, + null as my_vote +from all_comment ac +; diff --git a/server/src/actions/comment.rs b/server/src/actions/comment.rs index 3a3310f..ff50285 100644 --- a/server/src/actions/comment.rs +++ b/server/src/actions/comment.rs @@ -117,97 +117,6 @@ impl CommentLike { } } - - -impl Comment { - fn from_post(conn: &PgConnection, post_id_from: i32) -> Result, Error> { - use schema::comment::dsl::*; - comment - .filter(post_id.eq(post_id_from)) - .order_by(published.desc()) - .load::(conn) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct CommentView { - pub id: i32, - pub creator_id: i32, - pub content: String, - pub post_id: i32, - pub parent_id: Option, - pub published: chrono::NaiveDateTime, - pub updated: Option, - pub score: i32, - pub upvotes: i32, - pub downvotes: i32, - pub my_vote: Option -} - -impl CommentView { - pub fn from_comment(comment: &Comment, likes: &Vec, user_id: Option) -> Self { - let mut upvotes: i32 = 0; - let mut downvotes: i32 = 0; - let mut my_vote: Option = Some(0); - - for like in likes.iter() { - if like.score == 1 { - upvotes += 1; - } else if like.score == -1 { - downvotes += 1; - } - - if let Some(user) = user_id { - if like.user_id == user { - my_vote = Some(like.score); - } - } - - } - - let score: i32 = upvotes - downvotes; - - CommentView { - id: comment.id, - content: comment.content.to_owned(), - parent_id: comment.parent_id, - post_id: comment.post_id, - creator_id: comment.creator_id, - published: comment.published, - updated: comment.updated, - upvotes: upvotes, - score: score, - downvotes: downvotes, - my_vote: my_vote - } - } - - pub fn read(conn: &PgConnection, comment_id: i32, user_id: Option) -> Self { - let comment = Comment::read(&conn, comment_id).unwrap(); - let likes = CommentLike::read(&conn, comment_id).unwrap(); - Self::from_comment(&comment, &likes, user_id) - } - - pub fn from_post(conn: &PgConnection, post_id: i32, user_id: Option) -> Vec { - let comments = Comment::from_post(&conn, post_id).unwrap(); - let post_comment_likes = CommentLike::from_post(&conn, post_id).unwrap(); - - let mut views = Vec::new(); - for comment in comments.iter() { - let comment_likes: Vec = post_comment_likes - .iter() - .filter(|like| comment.id == like.comment_id) - .cloned() - .collect(); - let comment_view = CommentView::from_comment(&comment, &comment_likes, user_id); - views.push(comment_view); - }; - - views - } -} - - #[cfg(test)] mod tests { use establish_connection; @@ -233,6 +142,9 @@ mod tests { let new_community = CommunityForm { name: "test community".to_string(), + title: "nada".to_owned(), + description: None, + category_id: 1, creator_id: inserted_user.id, updated: None }; diff --git a/server/src/actions/comment_view.rs b/server/src/actions/comment_view.rs new file mode 100644 index 0000000..dcfcc25 --- /dev/null +++ b/server/src/actions/comment_view.rs @@ -0,0 +1,195 @@ +extern crate diesel; +use diesel::*; +use diesel::result::Error; +use serde::{Deserialize, Serialize}; + +// The faked schema since diesel doesn't do views +table! { + comment_view (id) { + id -> Int4, + creator_id -> Int4, + post_id -> Int4, + parent_id -> Nullable, + content -> Text, + published -> Timestamp, + updated -> Nullable, + creator_name -> Varchar, + score -> BigInt, + upvotes -> BigInt, + downvotes -> BigInt, + user_id -> Nullable, + my_vote -> Nullable, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="comment_view"] +pub struct CommentView { + pub id: i32, + pub creator_id: i32, + pub post_id: i32, + pub parent_id: Option, + pub content: String, + pub published: chrono::NaiveDateTime, + pub updated: Option, + pub creator_name: String, + pub score: i64, + pub upvotes: i64, + pub downvotes: i64, + pub user_id: Option, + pub my_vote: Option, +} + +impl CommentView { + + pub fn list(conn: &PgConnection, from_post_id: i32, from_user_id: Option) -> Result, Error> { + use actions::comment_view::comment_view::dsl::*; + use diesel::prelude::*; + + let mut query = comment_view.into_boxed(); + + // The view lets you pass a null user_id, if you're not logged in + if let Some(from_user_id) = from_user_id { + query = query.filter(user_id.eq(from_user_id)); + } else { + query = query.filter(user_id.is_null()); + } + + query = query.filter(post_id.eq(from_post_id)).order_by(published.desc()); + + query.load::(conn) + } + + pub fn read(conn: &PgConnection, from_comment_id: i32, from_user_id: Option) -> Result { + use actions::comment_view::comment_view::dsl::*; + use diesel::prelude::*; + + let mut query = comment_view.into_boxed(); + + // The view lets you pass a null user_id, if you're not logged in + if let Some(from_user_id) = from_user_id { + query = query.filter(user_id.eq(from_user_id)); + } else { + query = query.filter(user_id.is_null()); + } + + query = query.filter(id.eq(from_comment_id)).order_by(published.desc()); + + query.first::(conn) + } + +} + + +#[cfg(test)] +mod tests { + use establish_connection; + use super::*; + use actions::post::*; + use actions::community::*; + use actions::user::*; + use actions::comment::*; + use {Crud,Likeable}; + #[test] + fn test_crud() { + let conn = establish_connection(); + + let new_user = UserForm { + name: "timmy".into(), + fedi_name: "rrf".into(), + preferred_username: None, + password_encrypted: "nope".into(), + email: None, + updated: None + }; + + let inserted_user = User_::create(&conn, &new_user).unwrap(); + + let new_community = CommunityForm { + name: "test community 5".to_string(), + title: "nada".to_owned(), + description: None, + category_id: 1, + creator_id: inserted_user.id, + updated: None + }; + + let inserted_community = Community::create(&conn, &new_community).unwrap(); + + let new_post = PostForm { + name: "A test post 2".into(), + creator_id: inserted_user.id, + url: None, + body: None, + community_id: inserted_community.id, + updated: None + }; + + let inserted_post = Post::create(&conn, &new_post).unwrap(); + + let comment_form = CommentForm { + content: "A test comment 32".into(), + creator_id: inserted_user.id, + post_id: inserted_post.id, + parent_id: None, + updated: None + }; + + let inserted_comment = Comment::create(&conn, &comment_form).unwrap(); + + let comment_like_form = CommentLikeForm { + comment_id: inserted_comment.id, + post_id: inserted_post.id, + user_id: inserted_user.id, + score: 1 + }; + + let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap(); + + let expected_comment_view_no_user = CommentView { + id: inserted_comment.id, + content: "A test comment 32".into(), + creator_id: inserted_user.id, + post_id: inserted_post.id, + parent_id: None, + published: inserted_comment.published, + updated: None, + creator_name: inserted_user.name.to_owned(), + score: 1, + downvotes: 0, + upvotes: 1, + user_id: None, + my_vote: None + }; + + let expected_comment_view_with_user = CommentView { + id: inserted_comment.id, + content: "A test comment 32".into(), + creator_id: inserted_user.id, + post_id: inserted_post.id, + parent_id: None, + published: inserted_comment.published, + updated: None, + creator_name: inserted_user.name.to_owned(), + score: 1, + downvotes: 0, + upvotes: 1, + user_id: Some(inserted_user.id), + my_vote: Some(1), + }; + + let read_comment_views_no_user = CommentView::list(&conn, inserted_post.id, None).unwrap(); + let read_comment_views_with_user = CommentView::list(&conn, inserted_post.id, Some(inserted_user.id)).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(); + Community::delete(&conn, inserted_community.id).unwrap(); + User_::delete(&conn, inserted_user.id).unwrap(); + + assert_eq!(expected_comment_view_no_user, read_comment_views_no_user[0]); + assert_eq!(expected_comment_view_with_user, read_comment_views_with_user[0]); + assert_eq!(1, num_deleted); + assert_eq!(1, like_removed); + } +} + diff --git a/server/src/actions/community.rs b/server/src/actions/community.rs index 45eafdf..a3fdbbd 100644 --- a/server/src/actions/community.rs +++ b/server/src/actions/community.rs @@ -10,6 +10,9 @@ use {Crud, Followable, Joinable}; pub struct Community { pub id: i32, pub name: String, + pub title: String, + pub description: Option, + pub category_id: i32, pub creator_id: i32, pub published: chrono::NaiveDateTime, pub updated: Option @@ -19,6 +22,9 @@ pub struct Community { #[table_name="community"] pub struct CommunityForm { pub name: String, + pub title: String, + pub description: Option, + pub category_id: i32, pub creator_id: i32, pub updated: Option } @@ -149,8 +155,11 @@ mod tests { let new_community = CommunityForm { name: "TIL".into(), + creator_id: inserted_user.id, + title: "nada".to_owned(), + description: None, + category_id: 1, updated: None, - creator_id: inserted_user.id }; let inserted_community = Community::create(&conn, &new_community).unwrap(); @@ -159,6 +168,9 @@ mod tests { id: inserted_community.id, creator_id: inserted_user.id, name: "TIL".into(), + title: "nada".to_owned(), + description: None, + category_id: 1, published: inserted_community.published, updated: None }; @@ -196,7 +208,6 @@ mod tests { 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 loaded_count = Community::list_all(&conn).unwrap().len(); let num_deleted = Community::delete(&conn, inserted_community.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap(); diff --git a/server/src/actions/mod.rs b/server/src/actions/mod.rs index 21c3fc4..8b4574d 100644 --- a/server/src/actions/mod.rs +++ b/server/src/actions/mod.rs @@ -3,3 +3,4 @@ pub mod community; pub mod post; pub mod comment; pub mod post_view; +pub mod comment_view; diff --git a/server/src/actions/post.rs b/server/src/actions/post.rs index 2e333d0..b53aae4 100644 --- a/server/src/actions/post.rs +++ b/server/src/actions/post.rs @@ -122,6 +122,9 @@ mod tests { let new_community = CommunityForm { name: "test community_2".to_string(), + title: "nada".to_owned(), + description: None, + category_id: 1, creator_id: inserted_user.id, updated: None }; diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index e907143..a1d71de 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -16,47 +16,47 @@ pub enum ListingSortType { // The faked schema since diesel doesn't do views table! { post_view (id) { - user_id -> Nullable, - my_vote -> Nullable, id -> Int4, name -> Varchar, url -> Nullable, body -> Nullable, creator_id -> Int4, - creator_name -> Varchar, community_id -> Int4, + published -> Timestamp, + updated -> Nullable, + creator_name -> Varchar, community_name -> Varchar, number_of_comments -> BigInt, score -> BigInt, upvotes -> BigInt, downvotes -> BigInt, hot_rank -> Int4, - published -> Timestamp, - updated -> Nullable, + user_id -> Nullable, + my_vote -> Nullable, } } -#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName)] +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] #[table_name="post_view"] pub struct PostView { - pub user_id: Option, - pub my_vote: Option, pub id: i32, pub name: String, pub url: Option, pub body: Option, pub creator_id: i32, - pub creator_name: String, pub community_id: i32, + pub published: chrono::NaiveDateTime, + pub updated: Option, + pub creator_name: String, pub community_name: String, pub number_of_comments: i64, pub score: i64, pub upvotes: i64, pub downvotes: i64, pub hot_rank: i32, - pub published: chrono::NaiveDateTime, - pub updated: Option + pub user_id: Option, + pub my_vote: Option, } impl PostView { @@ -103,7 +103,6 @@ impl PostView { pub fn get(conn: &PgConnection, from_post_id: i32, from_user_id: Option) -> Result { use actions::post_view::post_view::dsl::*; - use diesel::dsl::*; use diesel::prelude::*; let mut query = post_view.into_boxed(); @@ -113,45 +112,8 @@ impl PostView { if let Some(from_user_id) = from_user_id { query = query.filter(user_id.eq(from_user_id)); } else { - // This fills in nulls for the user_id and user vote - query = query - .select(( - sql("null"), - sql("null"), - id, - name, - url, - body, - creator_id, - creator_name, - community_id, - community_name, - number_of_comments, - score, - upvotes, - downvotes, - hot_rank, - published, - updated - )) - .group_by(( - id, - name, - url, - body, - creator_id, - creator_name, - community_id, - community_name, - number_of_comments, - score, - upvotes, - downvotes, - hot_rank, - published, - updated - )); - }; + query = query.filter(user_id.is_null()); + } query.first::(conn) } @@ -187,7 +149,10 @@ mod tests { let new_community = CommunityForm { name: community_name.to_owned(), + title: "nada".to_owned(), + description: None, creator_id: inserted_user.id, + category_id: 1, updated: None }; diff --git a/server/src/apub.rs b/server/src/apub.rs index 9b2a37e..b245626 100644 --- a/server/src/apub.rs +++ b/server/src/apub.rs @@ -30,7 +30,6 @@ impl User_ { #[cfg(test)] mod tests { - use super::activitypub::{context, actor::Person}; use super::User_; use naive_now; diff --git a/server/src/schema.rs b/server/src/schema.rs index d152a81..fe11a46 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -1,3 +1,10 @@ +table! { + category (id) { + id -> Int4, + name -> Varchar, + } +} + table! { comment (id) { id -> Int4, @@ -25,6 +32,9 @@ table! { community (id) { id -> Int4, name -> Varchar, + title -> Varchar, + description -> Nullable, + category_id -> Int4, creator_id -> Int4, published -> Timestamp, updated -> Nullable, @@ -91,6 +101,7 @@ joinable!(comment -> user_ (creator_id)); joinable!(comment_like -> comment (comment_id)); joinable!(comment_like -> post (post_id)); joinable!(comment_like -> user_ (user_id)); +joinable!(community -> category (category_id)); joinable!(community -> user_ (creator_id)); joinable!(community_follower -> community (community_id)); joinable!(community_follower -> user_ (user_id)); @@ -102,6 +113,7 @@ joinable!(post_like -> post (post_id)); joinable!(post_like -> user_ (user_id)); allow_tables_to_appear_in_same_query!( + category, comment, comment_like, community, diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 474d043..4a2a746 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -16,10 +16,11 @@ use actions::user::*; use actions::post::*; use actions::comment::*; use actions::post_view::*; +use actions::comment_view::*; #[derive(EnumString,ToString,Debug)] pub enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike + Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity } #[derive(Serialize, Deserialize)] @@ -93,6 +94,9 @@ pub struct LoginResponse { #[derive(Serialize, Deserialize)] pub struct CreateCommunity { name: String, + title: String, + description: Option, + category_id: i32 , auth: String } @@ -121,9 +125,9 @@ pub struct CreatePost { } #[derive(Serialize, Deserialize)] -pub struct CreatePostResponse { +pub struct PostResponse { op: String, - post: Post + post: PostView } @@ -175,13 +179,6 @@ pub struct CreateComment { auth: String } -#[derive(Serialize, Deserialize)] -pub struct CreateCommentResponse { - op: String, - comment: CommentView -} - - #[derive(Serialize, Deserialize)] pub struct EditComment { content: String, @@ -192,7 +189,7 @@ pub struct EditComment { } #[derive(Serialize, Deserialize)] -pub struct EditCommentResponse { +pub struct CommentResponse { op: String, comment: CommentView } @@ -205,12 +202,6 @@ pub struct CreateCommentLike { auth: String } -#[derive(Serialize, Deserialize)] -pub struct CreateCommentLikeResponse { - op: String, - comment: CommentView -} - #[derive(Serialize, Deserialize)] pub struct CreatePostLike { @@ -225,6 +216,26 @@ pub struct CreatePostLikeResponse { post: PostView } + +#[derive(Serialize, Deserialize)] +pub struct EditPost { + edit_id: i32, + community_id: i32, + name: String, + url: Option, + body: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct EditCommunity { + edit_id: i32, + title: String, + description: Option, + category_id: i32, + auth: String +} + /// `ChatServer` manages chat rooms and responsible for coordinating chat /// session. implementation is super primitive pub struct ChatServer { @@ -260,15 +271,15 @@ impl ChatServer { } } - /// Send message only to self - fn send(&self, message: &str, id: &usize) { - // println!("{:?}", self.sessions); - if let Some(addr) = self.sessions.get(id) { - println!("msg: {}", message); - // println!("{:?}", addr.connected()); - let _ = addr.do_send(WSMessage(message.to_owned())); - } - } + // /// Send message only to self + // fn send(&self, message: &str, id: &usize) { + // // println!("{:?}", self.sessions); + // if let Some(addr) = self.sessions.get(id) { + // println!("msg: {}", message); + // // println!("{:?}", addr.connected()); + // let _ = addr.do_send(WSMessage(message.to_owned())); + // } + // } } /// Make actor from `ChatServer` @@ -309,12 +320,12 @@ impl Handler for ChatServer { fn handle(&mut self, msg: Disconnect, _: &mut Context) { println!("Someone disconnected"); - let mut rooms: Vec = Vec::new(); + // let mut rooms: Vec = Vec::new(); // remove address if self.sessions.remove(&msg.id).is_some() { // remove session from all rooms - for (id, sessions) in &mut self.rooms { + for (_id, sessions) in &mut self.rooms { if sessions.remove(&msg.id) { // rooms.push(*id); } @@ -398,6 +409,10 @@ impl Handler for ChatServer { let create_post_like: CreatePostLike = serde_json::from_str(&data.to_string()).unwrap(); create_post_like.perform(self, msg.id) }, + UserOperation::EditPost => { + let edit_post: EditPost = serde_json::from_str(&data.to_string()).unwrap(); + edit_post.perform(self, msg.id) + }, _ => { let e = ErrorMessage { op: "Unknown".to_string(), @@ -413,7 +428,7 @@ impl Handler for ChatServer { pub trait Perform { - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String; + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String; fn op_type(&self) -> UserOperation; fn error(&self, error_msg: &str) -> String { serde_json::to_string( @@ -430,14 +445,14 @@ impl Perform for Login { fn op_type(&self) -> UserOperation { UserOperation::Login } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { let conn = establish_connection(); // Fetch that username / email let user: User_ = match User_::find_by_email_or_username(&conn, &self.username_or_email) { Ok(user) => user, - Err(e) => return self.error("Couldn't find that username or email") + Err(_e) => return self.error("Couldn't find that username or email") }; // Verify the password @@ -462,7 +477,7 @@ impl Perform for Register { fn op_type(&self) -> UserOperation { UserOperation::Register } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { let conn = establish_connection(); @@ -484,7 +499,7 @@ impl Perform for Register { // Create the user let inserted_user = match User_::create(&conn, &user_form) { Ok(user) => user, - Err(e) => { + Err(_e) => { return self.error("User already exists."); } }; @@ -506,32 +521,33 @@ impl Perform for CreateCommunity { UserOperation::CreateCommunity } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + 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) => { + Err(_e) => { return self.error("Not logged in."); } }; let user_id = claims.id; - let username = claims.username; - let iss = claims.iss; // When you create a community, make sure the user becomes a moderator and a follower let community_form = CommunityForm { name: self.name.to_owned(), + title: self.title.to_owned(), + description: self.description.to_owned(), + category_id: self.category_id, creator_id: user_id, updated: None }; let inserted_community = match Community::create(&conn, &community_form) { Ok(community) => community, - Err(e) => { + Err(_e) => { return self.error("Community already exists."); } }; @@ -541,9 +557,9 @@ impl Perform for CreateCommunity { user_id: user_id }; - let inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { + let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, - Err(e) => { + Err(_e) => { return self.error("Community moderator already exists."); } }; @@ -553,9 +569,9 @@ impl Perform for CreateCommunity { user_id: user_id }; - let inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { + let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { Ok(user) => user, - Err(e) => { + Err(_e) => { return self.error("Community follower already exists."); } }; @@ -575,7 +591,7 @@ impl Perform for ListCommunities { UserOperation::ListCommunities } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { let conn = establish_connection(); @@ -597,20 +613,18 @@ impl Perform for CreatePost { UserOperation::CreatePost } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + 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) => { + Err(_e) => { return self.error("Not logged in."); } }; let user_id = claims.id; - let username = claims.username; - let iss = claims.iss; let post_form = PostForm { name: self.name.to_owned(), @@ -623,7 +637,7 @@ impl Perform for CreatePost { let inserted_post = match Post::create(&conn, &post_form) { Ok(post) => post, - Err(e) => { + Err(_e) => { return self.error("Couldn't create Post"); } }; @@ -636,17 +650,25 @@ impl Perform for CreatePost { }; // Only add the like if the score isnt 0 - let inserted_like = match PostLike::like(&conn, &like_form) { + let _inserted_like = match PostLike::like(&conn, &like_form) { Ok(like) => like, - Err(e) => { + Err(_e) => { return self.error("Couldn't like post."); } }; + + // Refetch the view + let post_view = match PostView::get(&conn, inserted_post.id, Some(user_id)) { + Ok(post) => post, + Err(_e) => { + return self.error("Couldn't find Post"); + } + }; serde_json::to_string( - &CreatePostResponse { + &PostResponse { op: self.op_type().to_string(), - post: inserted_post + post: post_view } ) .unwrap() @@ -670,11 +692,9 @@ impl Perform for GetPost { match Claims::decode(&auth) { Ok(claims) => { let user_id = claims.claims.id; - let username = claims.claims.username; - let iss = claims.claims.iss; Some(user_id) } - Err(e) => None + Err(_e) => None } } None => None @@ -682,13 +702,13 @@ impl Perform for GetPost { let post_view = match PostView::get(&conn, self.id, user_id) { Ok(post) => post, - Err(e) => { + Err(_e) => { return self.error("Couldn't find Post"); } }; // remove session from all rooms - for (n, sessions) in &mut chat.rooms { + for (_n, sessions) in &mut chat.rooms { sessions.remove(&addr); } @@ -698,10 +718,7 @@ impl Perform for GetPost { chat.rooms.get_mut(&self.id).unwrap().insert(addr); - let comments = CommentView::from_post(&conn, self.id, user_id); - - // println!("{:?}", chat.rooms.keys()); - // println!("{:?}", chat.rooms.get(&5i32).unwrap()); + let comments = CommentView::list(&conn, self.id, user_id).unwrap(); // Return the jwt serde_json::to_string( @@ -720,13 +737,13 @@ impl Perform for GetCommunity { UserOperation::GetCommunity } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { let conn = establish_connection(); let community = match Community::read(&conn, self.id) { Ok(community) => community, - Err(e) => { + Err(_e) => { return self.error("Couldn't find Community"); } }; @@ -753,15 +770,12 @@ impl Perform for CreateComment { let claims = match Claims::decode(&self.auth) { Ok(claims) => claims.claims, - Err(e) => { + Err(_e) => { return self.error("Not logged in."); } }; let user_id = claims.id; - let username = claims.username; - let iss = claims.iss; - let fedi_user_id = format!("{}/{}", iss, username); let comment_form = CommentForm { content: self.content.to_owned(), @@ -773,7 +787,7 @@ impl Perform for CreateComment { let inserted_comment = match Comment::create(&conn, &comment_form) { Ok(comment) => comment, - Err(e) => { + Err(_e) => { return self.error("Couldn't create Comment"); } }; @@ -786,22 +800,21 @@ impl Perform for CreateComment { score: 1 }; - let inserted_like = match CommentLike::like(&conn, &like_form) { + let _inserted_like = match CommentLike::like(&conn, &like_form) { Ok(like) => like, - Err(e) => { + Err(_e) => { return self.error("Couldn't like comment."); } }; - let likes: Vec = vec![inserted_like]; - - let comment_view = CommentView::from_comment(&inserted_comment, &likes, Some(user_id)); + let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id)).unwrap(); let mut comment_sent = comment_view.clone(); comment_sent.my_vote = None; + comment_sent.user_id = None; let comment_out = serde_json::to_string( - &CreateCommentResponse { + &CommentResponse { op: self.op_type().to_string(), comment: comment_view } @@ -809,7 +822,7 @@ impl Perform for CreateComment { .unwrap(); let comment_sent_out = serde_json::to_string( - &CreateCommentLikeResponse { + &CommentResponse { op: self.op_type().to_string(), comment: comment_sent } @@ -833,15 +846,12 @@ impl Perform for EditComment { let claims = match Claims::decode(&self.auth) { Ok(claims) => claims.claims, - Err(e) => { + Err(_e) => { return self.error("Not logged in."); } }; let user_id = claims.id; - let username = claims.username; - let iss = claims.iss; - let fedi_user_id = format!("{}/{}", iss, username); let comment_form = CommentForm { content: self.content.to_owned(), @@ -851,27 +861,22 @@ impl Perform for EditComment { updated: Some(naive_now()) }; - let updated_comment = match Comment::update(&conn, self.edit_id, &comment_form) { + let _updated_comment = match Comment::update(&conn, self.edit_id, &comment_form) { Ok(comment) => comment, - Err(e) => { + Err(_e) => { return self.error("Couldn't update Comment"); } }; - let likes = match CommentLike::read(&conn, self.edit_id) { - Ok(likes) => likes, - Err(e) => { - return self.error("Couldn't get likes"); - } - }; - let comment_view = CommentView::from_comment(&updated_comment, &likes, Some(user_id)); + let comment_view = CommentView::read(&conn, self.edit_id, Some(user_id)).unwrap(); let mut comment_sent = comment_view.clone(); comment_sent.my_vote = None; + comment_sent.user_id = None; let comment_out = serde_json::to_string( - &CreateCommentResponse { + &CommentResponse { op: self.op_type().to_string(), comment: comment_view } @@ -879,7 +884,7 @@ impl Perform for EditComment { .unwrap(); let comment_sent_out = serde_json::to_string( - &CreateCommentLikeResponse { + &CommentResponse { op: self.op_type().to_string(), comment: comment_sent } @@ -903,15 +908,12 @@ impl Perform for CreateCommentLike { let claims = match Claims::decode(&self.auth) { Ok(claims) => claims.claims, - Err(e) => { + Err(_e) => { return self.error("Not logged in."); } }; let user_id = claims.id; - let username = claims.username; - let iss = claims.iss; - let fedi_user_id = format!("{}/{}", iss, username); let like_form = CommentLikeForm { comment_id: self.comment_id, @@ -925,23 +927,23 @@ impl Perform for CreateCommentLike { // Only add the like if the score isnt 0 if &like_form.score != &0 { - let inserted_like = match CommentLike::like(&conn, &like_form) { + let _inserted_like = match CommentLike::like(&conn, &like_form) { Ok(like) => like, - Err(e) => { + Err(_e) => { return self.error("Couldn't like comment."); } }; } // Have to refetch the comment to get the current state - // thread::sleep(time::Duration::from_secs(1)); - let liked_comment = CommentView::read(&conn, self.comment_id, Some(user_id)); + let liked_comment = CommentView::read(&conn, self.comment_id, Some(user_id)).unwrap(); let mut liked_comment_sent = liked_comment.clone(); liked_comment_sent.my_vote = None; + liked_comment_sent.user_id = None; let like_out = serde_json::to_string( - &CreateCommentLikeResponse { + &CommentResponse { op: self.op_type().to_string(), comment: liked_comment } @@ -949,7 +951,7 @@ impl Perform for CreateCommentLike { .unwrap(); let like_sent_out = serde_json::to_string( - &CreateCommentLikeResponse { + &CommentResponse { op: self.op_type().to_string(), comment: liked_comment_sent } @@ -968,7 +970,7 @@ impl Perform for GetPosts { UserOperation::GetPosts } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { let conn = establish_connection(); @@ -981,7 +983,7 @@ impl Perform for GetPosts { let user_id = claims.claims.id; Some(user_id) } - Err(e) => None + Err(_e) => None } } None => None @@ -992,8 +994,8 @@ impl Perform for GetPosts { let posts = match PostView::list(&conn, type_, sort, self.community_id, user_id, self.limit) { Ok(posts) => posts, - Err(e) => { - eprintln!("{}", e); + Err(_e) => { + eprintln!("{}", _e); return self.error("Couldn't get posts"); } }; @@ -1015,13 +1017,13 @@ impl Perform for CreatePostLike { UserOperation::CreatePostLike } - fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + 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) => { + Err(_e) => { return self.error("Not logged in."); } }; @@ -1039,9 +1041,9 @@ impl Perform for CreatePostLike { // Only add the like if the score isnt 0 if &like_form.score != &0 { - let inserted_like = match PostLike::like(&conn, &like_form) { + let _inserted_like = match PostLike::like(&conn, &like_form) { Ok(like) => like, - Err(e) => { + Err(_e) => { return self.error("Couldn't like post."); } }; @@ -1049,7 +1051,7 @@ impl Perform for CreatePostLike { let post_view = match PostView::get(&conn, self.post_id, Some(user_id)) { Ok(post) => post, - Err(e) => { + Err(_e) => { return self.error("Couldn't find Post"); } }; @@ -1068,6 +1070,66 @@ impl Perform for CreatePostLike { } } +impl Perform for EditPost { + fn op_type(&self) -> UserOperation { + UserOperation::EditPost + } + + 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_form = PostForm { + name: self.name.to_owned(), + url: self.url.to_owned(), + body: self.body.to_owned(), + creator_id: user_id, + community_id: self.community_id, + updated: Some(naive_now()) + }; + + let _updated_post = match Post::update(&conn, self.edit_id, &post_form) { + Ok(post) => post, + Err(_e) => { + return self.error("Couldn't update Post"); + } + }; + + let post_view = PostView::get(&conn, self.edit_id, Some(user_id)).unwrap(); + + let mut post_sent = post_view.clone(); + post_sent.my_vote = None; + + let post_out = serde_json::to_string( + &PostResponse { + op: self.op_type().to_string(), + post: post_view + } + ) + .unwrap(); + + let post_sent_out = serde_json::to_string( + &PostResponse { + op: self.op_type().to_string(), + post: post_sent + } + ) + .unwrap(); + + chat.send_room_message(self.edit_id, &post_sent_out, addr); + + post_out + } +} // impl Handler for ChatServer { // type Result = MessageResult; @@ -1078,7 +1140,7 @@ impl Perform for CreatePostLike { // // Fetch that username / email // let user: User_ = match User_::find_by_email_or_username(&conn, &msg.username_or_email) { // Ok(user) => user, -// Err(e) => return MessageResult( +// Err(_e) => return MessageResult( // Err( // ErrorMessage { // op: UserOperation::Login.to_string(), @@ -1144,7 +1206,7 @@ impl Perform for CreatePostLike { // // Create the user // let inserted_user = match User_::create(&conn, &user_form) { // Ok(user) => user, -// Err(e) => return MessageResult( +// Err(_e) => return MessageResult( // Err( // ErrorMessage { // op: UserOperation::Register.to_string(), @@ -1184,7 +1246,7 @@ impl Perform for CreatePostLike { // let community = match Community::create(&conn, &community_form) { // Ok(community) => community, -// Err(e) => return MessageResult( +// Err(_e) => return MessageResult( // Err( // ErrorMessage { // op: UserOperation::CreateCommunity.to_string(), diff --git a/ui/src/components/create-post.tsx b/ui/src/components/create-post.tsx index 6715e9d..51e9921 100644 --- a/ui/src/components/create-post.tsx +++ b/ui/src/components/create-post.tsx @@ -1,47 +1,11 @@ import { Component, linkEvent } from 'inferno'; -import { Subscription } from "rxjs"; -import { retryWhen, delay, take } from 'rxjs/operators'; -import { PostForm, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces'; -import { WebSocketService, UserService } from '../services'; -import { msgOp } from '../utils'; -import { MomentTime } from './moment-time'; +import { PostForm } from './post-form'; -interface State { - postForm: PostForm; - communities: Array; -} - - -export class CreatePost extends Component { - - private subscription: Subscription; - private emptyState: State = { - postForm: { - name: null, - auth: null, - community_id: null - }, - communities: [] - } +export class CreatePost extends Component { constructor(props, context) { super(props, context); - - this.state = this.emptyState; - - this.subscription = WebSocketService.Instance.subject - .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) - .subscribe( - (msg) => this.parseMessage(msg), - (err) => console.error(err), - () => console.log('complete') - ); - - WebSocketService.Instance.listCommunities(); - } - - componentWillUnmount() { - this.subscription.unsubscribe(); + this.handlePostCreate = this.handlePostCreate.bind(this); } render() { @@ -49,92 +13,17 @@ export class CreatePost extends Component {
- {this.postForm()} +

Create a Post

+
) } - postForm() { - return ( -
-
-

Create a Post

-
- -
- -
-
-
- -
-