]> Untitled Git - lemmy.git/commitdiff
Adding post editing.
authorDessalines <tyhou13@gmx.com>
Wed, 3 Apr 2019 20:59:37 +0000 (13:59 -0700)
committerDessalines <tyhou13@gmx.com>
Wed, 3 Apr 2019 20:59:37 +0000 (13:59 -0700)
- Adding post editing. Fixes #23
- Making SQL versions of comment and post fetching. Fixes #21
- Starting to add forum categories. #17

25 files changed:
README.md
server/migrations/2019-02-27-170003_create_community/down.sql
server/migrations/2019-02-27-170003_create_community/up.sql
server/migrations/2019-03-30-212058_create_post_view/down.sql [moved from server/migrations/2019-03-30-212058_post_view/down.sql with 100% similarity]
server/migrations/2019-03-30-212058_create_post_view/up.sql [moved from server/migrations/2019-03-30-212058_post_view/up.sql with 91% similarity]
server/migrations/2019-04-03-155205_create_community_view/down.sql [new file with mode: 0644]
server/migrations/2019-04-03-155205_create_community_view/up.sql [new file with mode: 0644]
server/migrations/2019-04-03-155309_create_comment_view/down.sql [new file with mode: 0644]
server/migrations/2019-04-03-155309_create_comment_view/up.sql [new file with mode: 0644]
server/src/actions/comment.rs
server/src/actions/comment_view.rs [new file with mode: 0644]
server/src/actions/community.rs
server/src/actions/mod.rs
server/src/actions/post.rs
server/src/actions/post_view.rs
server/src/apub.rs
server/src/schema.rs
server/src/websocket_server/server.rs
ui/src/components/create-post.tsx
ui/src/components/post-form.tsx [new file with mode: 0644]
ui/src/components/post-listing.tsx
ui/src/components/post.tsx
ui/src/interfaces.ts
ui/src/services/UserService.ts
ui/src/services/WebSocketService.ts

index 483a9927b873d7e6e6dfbb7ef13488c0dbb7fde4..1192dc8f48c45609ad31bc2f47818ddce6a593ce 100644 (file)
--- 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
index 1a913b87be4b4c5a1edb757183b5607241aa244d..f293dfad420f7fc9edb8a915feabeeaaf2a37748 100644 (file)
@@ -1,3 +1,4 @@
 drop table community_moderator;
 drop table community_follower;
 drop table community;
+drop table category;
index b4eeb29b14f9372e55d53ee3a573b7e0b911dc70..f78486d583ead9464ad3f3808bcb3b96f98fe7aa 100644 (file)
@@ -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);
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 f22507354ef7c16d3ae422f5adcdb9411f23fe29..c1848631e35fe4d0980c6cd6e9ac04573448f885 100644 (file)
@@ -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 (file)
index 0000000..6c7e870
--- /dev/null
@@ -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 (file)
index 0000000..e731b7f
--- /dev/null
@@ -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 (file)
index 0000000..2da934a
--- /dev/null
@@ -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 (file)
index 0000000..a4d2be9
--- /dev/null
@@ -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
+;
index 3a3310fec4c93d56dcdb6ae129ce91df2b02d010..ff5028503c8151e05706ed3d00524847f96635cf 100644 (file)
@@ -117,97 +117,6 @@ impl CommentLike {
   }
 }
 
-
-
-impl Comment {
-  fn from_post(conn: &PgConnection, post_id_from: i32) -> Result<Vec<Self>, Error> {
-    use schema::comment::dsl::*;
-    comment
-      .filter(post_id.eq(post_id_from))
-      .order_by(published.desc())
-      .load::<Self>(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<i32>,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub score: i32,
-  pub upvotes: i32,
-  pub downvotes: i32,
-  pub my_vote: Option<i16>
-}
-
-impl CommentView {
-  pub fn from_comment(comment: &Comment, likes: &Vec<CommentLike>, user_id: Option<i32>) -> Self {
-    let mut upvotes: i32 = 0;
-    let mut downvotes: i32 = 0;
-    let mut my_vote: Option<i16> = 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<i32>) -> 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<i32>) -> Vec<Self> {
-    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<CommentLike> = 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 (file)
index 0000000..dcfcc25
--- /dev/null
@@ -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<Int4>,
+    content -> Text,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    creator_name -> Varchar,
+    score -> BigInt,
+    upvotes -> BigInt,
+    downvotes -> BigInt,
+    user_id -> Nullable<Int4>,
+    my_vote -> Nullable<Int4>,
+  }
+}
+
+#[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<i32>,
+  pub content: String,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub creator_name: String,
+  pub score: i64,
+  pub upvotes: i64,
+  pub downvotes: i64,
+  pub user_id: Option<i32>,
+  pub my_vote: Option<i32>,
+}
+
+impl CommentView {
+
+  pub fn list(conn: &PgConnection, from_post_id: i32, from_user_id: Option<i32>) -> Result<Vec<Self>, 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::<Self>(conn) 
+  }
+
+  pub fn read(conn: &PgConnection, from_comment_id: i32, from_user_id: Option<i32>) -> Result<Self, 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(id.eq(from_comment_id)).order_by(published.desc());
+
+    query.first::<Self>(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);
+  }
+}
+
index 45eafdf0eded9cf00f2830fe8d5108a342b4bb41..a3fdbbd405e8d16b9037002e2067d10e6e0877a6 100644 (file)
@@ -10,6 +10,9 @@ use {Crud, Followable, Joinable};
 pub struct Community {
   pub id: i32,
   pub name: String,
+  pub title: String,
+  pub description: Option<String>,
+  pub category_id: i32,
   pub creator_id: i32,
   pub published: chrono::NaiveDateTime,
   pub updated: Option<chrono::NaiveDateTime>
@@ -19,6 +22,9 @@ pub struct Community {
 #[table_name="community"]
 pub struct CommunityForm {
   pub name: String,
+  pub title: String,
+  pub description: Option<String>,
+  pub category_id: i32,
   pub creator_id: i32,
   pub updated: Option<chrono::NaiveDateTime>
 }
@@ -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();
 
index 21c3fc47d903c9b12c24996f8717c71cd247492c..8b4574d4abbb683f79ce4aa4203a242716dc6213 100644 (file)
@@ -3,3 +3,4 @@ pub mod community;
 pub mod post;
 pub mod comment;
 pub mod post_view;
+pub mod comment_view;
index 2e333d0fc3794dfa796f0ff1aa34f0101364d0fa..b53aae4635b98139fc35355edfc8930171eccebb 100644 (file)
@@ -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
     };
index e90714348b3e9d24ac5c3496d3c3395f499cc8ca..a1d71de15441201165eff5ad760d84b9f8d87b39 100644 (file)
@@ -16,47 +16,47 @@ pub enum ListingSortType {
 // The faked schema since diesel doesn't do views
 table! {
   post_view (id) {
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
     id -> Int4,
     name -> Varchar,
     url -> Nullable<Text>,
     body -> Nullable<Text>,
     creator_id -> Int4,
-    creator_name -> Varchar,
     community_id -> Int4,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    creator_name -> Varchar,
     community_name -> Varchar,
     number_of_comments -> BigInt,
     score -> BigInt,
     upvotes -> BigInt,
     downvotes -> BigInt,
     hot_rank -> Int4,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
+    user_id -> Nullable<Int4>,
+    my_vote -> Nullable<Int4>,
   }
 }
 
 
-#[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<i32>,
-  pub my_vote: Option<i32>,
   pub id: i32,
   pub name: String,
   pub url: Option<String>,
   pub body: Option<String>,
   pub creator_id: i32,
-  pub creator_name: String,
   pub community_id: i32,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  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<chrono::NaiveDateTime>
+  pub user_id: Option<i32>,
+  pub my_vote: Option<i32>,
 }
 
 impl PostView {
@@ -103,7 +103,6 @@ impl PostView {
   pub fn get(conn: &PgConnection, from_post_id: i32, from_user_id: Option<i32>) -> Result<Self, Error> {
 
     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::<Self>(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
     };
 
index 9b2a37e5cdab0d2a980af7b673d1e8be07cf34f2..b24562614c35caede8f26ed5ce10e8bfe22643cf 100644 (file)
@@ -30,7 +30,6 @@ impl User_ {
 
 #[cfg(test)]
 mod tests {
-  use super::activitypub::{context, actor::Person};
   use super::User_;
   use naive_now;
 
index d152a8191b83bf9075a9f8422f6b3acdc07b2c9d..fe11a46bd77f9ca0b223b2ad38bb7d3fd1efbe07 100644 (file)
@@ -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<Text>,
+        category_id -> Int4,
         creator_id -> Int4,
         published -> Timestamp,
         updated -> Nullable<Timestamp>,
@@ -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,
index 474d0433472ea9eaeca87d785e91678da26214a7..4a2a746cf603b3c8271954f8def8513a4b70eb2e 100644 (file)
@@ -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<String>,
+  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<String>,
+  body: Option<String>,
+  auth: String
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct EditCommunity {
+  edit_id: i32,
+  title: String,
+  description: Option<String>,
+  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<Disconnect> for ChatServer {
   fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
     println!("Someone disconnected");
 
-    let mut rooms: Vec<i32> = Vec::new();
+    // let mut rooms: Vec<i32> = 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<StandardMessage> 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<StandardMessage> 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<CommentLike> = 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<Login> for ChatServer {
 
 //   type Result = MessageResult<Login>;
@@ -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(), 
index 6715e9d84092756a4fad3184a6bd29b123c3918a..51e992111ccfc686ae1ed62ccf81bda0844fe79b 100644 (file)
@@ -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<Community>;
-}
-
-
-export class CreatePost extends Component<any, State> {
-
-  private subscription: Subscription;
-  private emptyState: State = {
-    postForm: {
-      name: null,
-      auth: null,
-      community_id: null
-    },
-    communities: []
-  }
+export class CreatePost extends Component<any, any> {
 
   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<any, State> {
       <div class="container">
         <div class="row">
           <div class="col-12 col-lg-6 mb-4">
-            {this.postForm()}
+            <h3>Create a Post</h3>
+            <PostForm onCreate={this.handlePostCreate}/>
           </div>
         </div>
       </div>
     )
   }
 
-  postForm() {
-    return (
-      <div>
-        <form onSubmit={linkEvent(this, this.handlePostSubmit)}>
-          <h3>Create a Post</h3>
-          <div class="form-group row">
-            <label class="col-sm-2 col-form-label">URL</label>
-            <div class="col-sm-10">
-              <input type="url" class="form-control" value={this.state.postForm.url} onInput={linkEvent(this, this.handlePostUrlChange)} />
-            </div>
-          </div>
-          <div class="form-group row">
-            <label class="col-sm-2 col-form-label">Title</label>
-            <div class="col-sm-10">
-              <textarea value={this.state.postForm.name} onInput={linkEvent(this, this.handlePostNameChange)} class="form-control" required rows={3} />
-            </div>
-          </div>
-          <div class="form-group row">
-            <label class="col-sm-2 col-form-label">Body</label>
-            <div class="col-sm-10">
-              <textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={6} />
-            </div>
-          </div>
-          <div class="form-group row">
-            <label class="col-sm-2 col-form-label">Forum</label>
-            <div class="col-sm-10">
-              <select class="form-control" value={this.state.postForm.community_id} onInput={linkEvent(this, this.handlePostCommunityChange)}>
-                {this.state.communities.map(community =>
-                  <option value={community.id}>{community.name}</option>
-                )}
-              </select>
-            </div>
-          </div>
-          <div class="form-group row">
-            <div class="col-sm-10">
-              <button type="submit" class="btn btn-secondary">Create Post</button>
-            </div>
-          </div>
-        </form>
-      </div>
-    );
-  }
-
-  handlePostSubmit(i: CreatePost, event) {
-    event.preventDefault();
-    WebSocketService.Instance.createPost(i.state.postForm);
-  }
-
-  handlePostUrlChange(i: CreatePost, event) {
-    i.state.postForm.url = event.target.value;
-  }
-
-  handlePostNameChange(i: CreatePost, event) {
-    i.state.postForm.name = event.target.value;
-  }
-
-  handlePostBodyChange(i: CreatePost, event) {
-    i.state.postForm.body = event.target.value;
-  }
-
-  handlePostCommunityChange(i: CreatePost, event) {
-    i.state.postForm.community_id = Number(event.target.value);
+  handlePostCreate(id: number) {
+    this.props.history.push(`/post/${id}`);
   }
+}
 
-  parseMessage(msg: any) {
-    console.log(msg);
-    let op: UserOperation = msgOp(msg);
-    if (msg.error) {
-      alert(msg.error);
-      return;
-    } else if (op == UserOperation.ListCommunities) {
-      let res: ListCommunitiesResponse = msg;
-      this.state.communities = res.communities;
-      this.state.postForm.community_id = res.communities[0].id; // TODO set it to the default community
-      this.setState(this.state);
-    } else if (op == UserOperation.CreatePost) {
-      let res: PostResponse = msg;
-      this.props.history.push(`/post/${res.post.id}`);
-    }
-  }
 
-}
diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx
new file mode 100644 (file)
index 0000000..b64ad66
--- /dev/null
@@ -0,0 +1,162 @@
+import { Component, linkEvent } from 'inferno';
+import { Subscription } from "rxjs";
+import { retryWhen, delay, take } from 'rxjs/operators';
+import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces';
+import { WebSocketService, UserService } from '../services';
+import { msgOp } from '../utils';
+import { MomentTime } from './moment-time';
+
+interface PostFormProps {
+  post?: Post; // If a post is given, that means this is an edit
+  onCancel?();
+  onCreate?(id: number);
+  onEdit?(post: Post);
+}
+
+interface PostFormState {
+  postForm: PostFormI;
+  communities: Array<Community>;
+}
+
+export class PostForm extends Component<PostFormProps, PostFormState> {
+
+  private subscription: Subscription;
+  private emptyState: PostFormState = {
+    postForm: {
+      name: null,
+      auth: null,
+      community_id: null
+    },
+    communities: []
+  }
+
+  constructor(props, context) {
+    super(props, context);
+
+    this.state = this.emptyState;
+
+    if (this.props.post) {
+      this.state.postForm = {
+        body: this.props.post.body,
+        name: this.props.post.name,
+        community_id: this.props.post.community_id,
+        edit_id: this.props.post.id,
+        url: this.props.post.url,
+        auth: null
+      }
+    }
+
+    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();
+  }
+
+  render() {
+    return (
+      <div>
+        <form onSubmit={linkEvent(this, this.handlePostSubmit)}>
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">URL</label>
+            <div class="col-sm-10">
+              <input type="url" class="form-control" value={this.state.postForm.url} onInput={linkEvent(this, this.handlePostUrlChange)} />
+            </div>
+          </div>
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">Title</label>
+            <div class="col-sm-10">
+              <textarea value={this.state.postForm.name} onInput={linkEvent(this, this.handlePostNameChange)} class="form-control" required rows={3} />
+            </div>
+          </div>
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">Body</label>
+            <div class="col-sm-10">
+              <textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={6} />
+            </div>
+          </div>
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">Forum</label>
+            <div class="col-sm-10">
+              <select class="form-control" value={this.state.postForm.community_id} onInput={linkEvent(this, this.handlePostCommunityChange)}>
+                {this.state.communities.map(community =>
+                  <option value={community.id}>{community.name}</option>
+                )}
+              </select>
+            </div>
+          </div>
+          <div class="form-group row">
+            <div class="col-sm-10">
+              <button type="submit" class="btn btn-secondary">{this.props.post ? 'Edit' : 'Create'} Post</button>
+            </div>
+          </div>
+        </form>
+      </div>
+    );
+  }
+
+  handlePostSubmit(i: PostForm, event) {
+    event.preventDefault();
+    console.log(i.state.postForm);
+    if (i.props.post) {
+      WebSocketService.Instance.editPost(i.state.postForm);
+    } else {
+      WebSocketService.Instance.createPost(i.state.postForm);
+    }
+  }
+
+  handlePostUrlChange(i: PostForm, event) {
+    i.state.postForm.url = event.target.value;
+    i.setState(i.state);
+  }
+
+  handlePostNameChange(i: PostForm, event) {
+    i.state.postForm.name = event.target.value;
+    i.setState(i.state);
+  }
+
+  handlePostBodyChange(i: PostForm, event) {
+    i.state.postForm.body = event.target.value;
+    i.setState(i.state);
+  }
+
+  handlePostCommunityChange(i: PostForm, event) {
+    i.state.postForm.community_id = Number(event.target.value);
+    i.setState(i.state);
+  }
+
+  parseMessage(msg: any) {
+    console.log(msg);
+    let op: UserOperation = msgOp(msg);
+    if (msg.error) {
+      alert(msg.error);
+      return;
+    } else if (op == UserOperation.ListCommunities) {
+      let res: ListCommunitiesResponse = msg;
+      this.state.communities = res.communities;
+      if (this.props.post) {
+        this.state.postForm.community_id = this.props.post.community_id;
+      } else {
+        this.state.postForm.community_id = res.communities[0].id;
+      }
+      this.setState(this.state);
+    } else if (op == UserOperation.CreatePost) {
+      let res: PostResponse = msg;
+      this.props.onCreate(res.post.id);
+    } else if (op == UserOperation.EditPost) {
+      let res: PostResponse = msg;
+      this.props.onEdit(res.post);
+    }
+  }
+
+}
+
+
index 861f0f150e25d390389edb6dfa5863d0606da1d3..c6c07d8b08427a14e16f1eb39dcc53495260527f 100644 (file)
@@ -3,15 +3,18 @@ import { Link } from 'inferno-router';
 import { Subscription } from "rxjs";
 import { retryWhen, delay, take } from 'rxjs/operators';
 import { WebSocketService, UserService } from '../services';
-import { Post, CreatePostLikeResponse, CreatePostLikeForm } from '../interfaces';
+import { Post, CreatePostLikeResponse, CreatePostLikeForm, PostForm as PostFormI } from '../interfaces';
 import { MomentTime } from './moment-time';
+import { PostForm } from './post-form';
 import { mdToHtml } from '../utils';
 
 interface PostListingState {
+  showEdit: boolean;
 }
 
 interface PostListingProps {
   post: Post;
+  editable?: boolean;
   showCommunity?: boolean;
   showBody?: boolean;
 }
@@ -19,6 +22,7 @@ interface PostListingProps {
 export class PostListing extends Component<PostListingProps, PostListingState> {
 
   private emptyState: PostListingState = {
+    showEdit: false
   }
 
   constructor(props, context) {
@@ -27,9 +31,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     this.state = this.emptyState;
     this.handlePostLike = this.handlePostLike.bind(this);
     this.handlePostDisLike = this.handlePostDisLike.bind(this);
+    this.handleEditPost = this.handleEditPost.bind(this);
   }
 
-  render() {
+ render() {
+   return (
+     <div>
+     {!this.state.showEdit 
+       ? this.listing()
+       : <PostForm post={this.props.post} onEdit={this.handleEditPost} />
+     }
+   </div>
+   )
+  }
+
+  listing() {
     let post = this.props.post;
     return (
       <div class="listing">
@@ -74,15 +90,25 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               <Link to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link>
             </li>
           </ul>
+            {this.myPost && 
+            <ul class="list-inline mb-1 text-muted small font-weight-bold"> 
+              <li className="list-inline-item">
+                <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
+              </li>
+              <li className="list-inline-item">
+                <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span>
+              </li>
+            </ul>
+          }
           {this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />}
         </div>
       </div>
     )
   }
 
-  // private get myPost(): boolean {
-  //   return this.props.node.comment.attributed_to == UserService.Instance.fediUserId;
-  // }
+  private get myPost(): boolean {
+    return this.props.editable && UserService.Instance.loggedIn && this.props.post.creator_id == UserService.Instance.user.id;
+  }
 
   handlePostLike(i: PostListing, event) {
 
@@ -100,5 +126,27 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     };
     WebSocketService.Instance.likePost(form);
   }
+
+  handleEditClick(i: PostListing, event) {
+    i.state.showEdit = true;
+    i.setState(i.state);
+  }
+
+  handleEditPost(post: Post) {
+    this.state.showEdit = false;
+    this.setState(this.state);
+  }
+
+  handleDeleteClick(i: PostListing, event) {
+    let deleteForm: PostFormI = {
+      body: '',
+      community_id: i.props.post.community_id,
+      name: "deleted",
+      url: '',
+      edit_id: i.props.post.id,
+      auth: null
+    };
+    WebSocketService.Instance.editPost(deleteForm);
+  }
 }
 
index 914eebb57da4969033e2779446bf4eb184e6f97d..68ba96346f5df43c1052709e8d6fdb047881a3bc 100644 (file)
@@ -1,7 +1,8 @@
 import { Component, linkEvent } from 'inferno';
+import { Link } from 'inferno-router';
 import { Subscription } from "rxjs";
 import { retryWhen, delay, take } from 'rxjs/operators';
-import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse, CommentSortType, CreatePostLikeResponse } from '../interfaces';
+import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse, CommentSortType, CreatePostLikeResponse } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
 import { msgOp, hotRank,mdToHtml } from '../utils';
 import { MomentTime } from './moment-time';
@@ -60,7 +61,7 @@ export class Post extends Component<any, State> {
         {this.state.post && 
           <div class="row">
             <div class="col-12 col-sm-8 col-lg-7 mb-3">
-              <PostListing post={this.state.post} showBody showCommunity />
+              <PostListing post={this.state.post} showBody showCommunity editable />
               <div className="mb-2" />
               <CommentForm postId={this.state.post.id} />
               {this.sortRadios()}
@@ -181,7 +182,7 @@ export class Post extends Component<any, State> {
       alert(msg.error);
       return;
     } else if (op == UserOperation.GetPost) {
-      let res: PostResponse = msg;
+      let res: GetPostResponse = msg;
       this.state.post = res.post;
       this.state.comments = res.comments;
       this.setState(this.state);
@@ -212,6 +213,10 @@ export class Post extends Component<any, State> {
       this.state.post.upvotes = res.post.upvotes;
       this.state.post.downvotes = res.post.downvotes;
       this.setState(this.state);
+    } else if (op == UserOperation.EditPost) {
+      let res: PostResponse = msg;
+      this.state.post = res.post;
+      this.setState(this.state);
     }
 
   }
@@ -281,7 +286,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
         <div className="details ml-4">
           <ul class="list-inline mb-0 text-muted small">
             <li className="list-inline-item">
-              <a href={node.comment.attributed_to}>{node.comment.attributed_to}</a>
+              <Link to={`/user/${node.comment.creator_id}`}>{node.comment.creator_name}</Link>
             </li>
             <li className="list-inline-item">
               <span>(
@@ -327,7 +332,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
   }
 
   private get myComment(): boolean {
-    return this.props.node.comment.attributed_to == UserService.Instance.fediUserId;
+    return UserService.Instance.loggedIn && this.props.node.comment.creator_id == UserService.Instance.user.id;
   }
 
   handleReplyClick(i: CommentNode, event) {
index 56d860bd4fc1db5745b1309d344a7c78e5064347..e21edf77eb5bc5a78247a21382a7a07013848437 100644 (file)
@@ -1,5 +1,5 @@
 export 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
 }
 
 export interface User {
@@ -56,19 +56,26 @@ export interface PostForm {
   body?: string;
   community_id: number;
   updated?: number;
+  edit_id?: number;
   auth: string;
 }
 
-export interface PostResponse {
+export interface GetPostResponse {
   op: string;
   post: Post;
   comments: Array<Comment>;
 }
 
+export interface PostResponse {
+  op: string;
+  post: Post;
+}
+
 export interface Comment {
   id: number;
   content: string;
   creator_id: number;
+  creator_name: string;
   post_id: number,
   parent_id?: number;
   published: string;
@@ -99,11 +106,6 @@ export interface CommentLikeForm {
   auth?: string;
 }
 
-export interface CreateCommentLikeResponse {
-  op: string;
-  comment: Comment;
-}
-
 export interface GetPostsForm {
   type_: string;
   sort: string;
index c090aebce253af720255802fb6d207d061bb5e5b..bf93f42a61a4391585d6c955e23fccd363907e9b 100644 (file)
@@ -5,7 +5,7 @@ import { Subject } from 'rxjs';
 
 export class UserService {
   private static _instance: UserService;
-  private user: User;
+  public user: User;
   public sub: Subject<User> = new Subject<User>();
 
   private constructor() {
@@ -45,10 +45,6 @@ export class UserService {
     console.log(this.user);
   }
 
-  public get fediUserId(): string {
-    return `${this.user.iss}/${this.user.username}`;
-  }
-
   public static get Instance(){
     return this._instance || (this._instance = new this());
   }
index 04376ff865c4da148769b79e82d3bf41f6ff7eca..c1fdb73bd32e04a3dc7237feed8f88aa7aeb07e3 100644 (file)
@@ -80,6 +80,11 @@ export class WebSocketService {
     this.subject.next(this.wsSendWrapper(UserOperation.CreatePostLike, form));
   }
 
+  public editPost(postForm: PostForm) {
+    this.setAuth(postForm);
+    this.subject.next(this.wsSendWrapper(UserOperation.EditPost, postForm));
+  }
+
   private wsSendWrapper(op: UserOperation, data: any) {
     let send = { op: UserOperation[op], data: data };
     console.log(send);