From 7b1fb030b306306ed65999bff05a5d0924a37158 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 3 Apr 2019 16:01:20 -0700 Subject: [PATCH] Adding a sidebar, title, and forum categories - Adding a Sidebar component - Starting on forum categories. #17 - Adding a Sidebar and title to community. Fixes #16 --- .../up.sql | 1 + server/src/actions/category.rs | 73 +++++++++++++++ server/src/actions/community.rs | 7 -- server/src/actions/community_view.rs | 51 +++++++++++ server/src/actions/mod.rs | 2 + server/src/actions/post_view.rs | 6 +- server/src/actions/user.rs | 4 +- server/src/bin/main.rs | 2 +- server/src/websocket_server/server.rs | 89 ++++++++++++++----- ui/src/components/community.tsx | 14 ++- ui/src/components/create-community.tsx | 65 ++++++++++++-- ui/src/components/post.tsx | 19 ++-- ui/src/components/sidebar.tsx | 33 +++++++ ui/src/interfaces.ts | 24 ++++- ui/src/services/WebSocketService.ts | 4 + 15 files changed, 336 insertions(+), 58 deletions(-) create mode 100644 server/src/actions/category.rs create mode 100644 server/src/actions/community_view.rs create mode 100644 ui/src/components/sidebar.tsx diff --git a/server/migrations/2019-04-03-155205_create_community_view/up.sql b/server/migrations/2019-04-03-155205_create_community_view/up.sql index e731b7f..7497250 100644 --- a/server/migrations/2019-04-03-155205_create_community_view/up.sql +++ b/server/migrations/2019-04-03-155205_create_community_view/up.sql @@ -1,6 +1,7 @@ create view community_view as select *, (select name from user_ u where c.creator_id = u.id) as creator_name, +(select name from category ct where c.category_id = ct.id) as category_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; diff --git a/server/src/actions/category.rs b/server/src/actions/category.rs new file mode 100644 index 0000000..8491f1e --- /dev/null +++ b/server/src/actions/category.rs @@ -0,0 +1,73 @@ +extern crate diesel; +use schema::{category}; +use diesel::*; +use diesel::result::Error; +use serde::{Deserialize, Serialize}; +use {Crud}; + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="category"] +pub struct Category { + pub id: i32, + pub name: String +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="category"] +pub struct CategoryForm { + pub name: String, +} + +impl Crud for Category { + fn read(conn: &PgConnection, category_id: i32) -> Result { + use schema::category::dsl::*; + category.find(category_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, category_id: i32) -> Result { + use schema::category::dsl::*; + diesel::delete(category.find(category_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, new_category: &CategoryForm) -> Result { + use schema::category::dsl::*; + insert_into(category) + .values(new_category) + .get_result::(conn) + } + + fn update(conn: &PgConnection, category_id: i32, new_category: &CategoryForm) -> Result { + use schema::category::dsl::*; + diesel::update(category.find(category_id)) + .set(new_category) + .get_result::(conn) + } +} + +impl Category { + pub fn list_all(conn: &PgConnection) -> Result, Error> { + use schema::category::dsl::*; + category.load::(conn) + } +} + +#[cfg(test)] +mod tests { + use establish_connection; + use super::*; + // use Crud; + #[test] + fn test_crud() { + let conn = establish_connection(); + + let categories = Category::list_all(&conn).unwrap(); + let expected_first_category = Category { + id: 1, + name: "Discussion".into() + }; + + assert_eq!(expected_first_category, categories[0]); + } +} diff --git a/server/src/actions/community.rs b/server/src/actions/community.rs index a3fdbbd..1c6343d 100644 --- a/server/src/actions/community.rs +++ b/server/src/actions/community.rs @@ -125,13 +125,6 @@ impl Joinable for CommunityModerator { } } -impl Community { - pub fn list_all(conn: &PgConnection) -> Result, Error> { - use schema::community::dsl::*; - community.load::(conn) - } -} - #[cfg(test)] mod tests { use establish_connection; diff --git a/server/src/actions/community_view.rs b/server/src/actions/community_view.rs new file mode 100644 index 0000000..9da6215 --- /dev/null +++ b/server/src/actions/community_view.rs @@ -0,0 +1,51 @@ +extern crate diesel; +use diesel::*; +use diesel::result::Error; +use serde::{Deserialize, Serialize}; + +table! { + community_view (id) { + id -> Int4, + name -> Varchar, + title -> Varchar, + description -> Nullable, + category_id -> Int4, + creator_id -> Int4, + published -> Timestamp, + updated -> Nullable, + creator_name -> Varchar, + category_name -> Varchar, + number_of_subscribers -> BigInt, + number_of_posts -> BigInt, + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="community_view"] +pub struct CommunityView { + 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, + pub creator_name: String, + pub category_name: String, + pub number_of_subscribers: i64, + pub number_of_posts: i64 +} + +impl CommunityView { + pub fn read(conn: &PgConnection, from_community_id: i32) -> Result { + use actions::community_view::community_view::dsl::*; + community_view.find(from_community_id).first::(conn) + } + + pub fn list_all(conn: &PgConnection) -> Result, Error> { + use actions::community_view::community_view::dsl::*; + community_view.load::(conn) + } +} + diff --git a/server/src/actions/mod.rs b/server/src/actions/mod.rs index 8b4574d..c17fd81 100644 --- a/server/src/actions/mod.rs +++ b/server/src/actions/mod.rs @@ -4,3 +4,5 @@ pub mod post; pub mod comment; pub mod post_view; pub mod comment_view; +pub mod category; +pub mod community_view; diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index a1d71de..f53a9f0 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -100,7 +100,7 @@ impl PostView { } - pub fn get(conn: &PgConnection, from_post_id: i32, from_user_id: Option) -> Result { + pub fn read(conn: &PgConnection, from_post_id: i32, from_user_id: Option) -> Result { use actions::post_view::post_view::dsl::*; use diesel::prelude::*; @@ -235,8 +235,8 @@ mod tests { let read_post_listings_with_user = PostView::list(&conn, ListingType::Community, ListingSortType::New, Some(inserted_community.id), Some(inserted_user.id), 10).unwrap(); let read_post_listings_no_user = PostView::list(&conn, ListingType::Community, ListingSortType::New, Some(inserted_community.id), None, 10).unwrap(); - let read_post_listing_no_user = PostView::get(&conn, inserted_post.id, None).unwrap(); - let read_post_listing_with_user = PostView::get(&conn, inserted_post.id, Some(inserted_user.id)).unwrap(); + let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap(); + let read_post_listing_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap(); let like_removed = PostLike::remove(&conn, &post_like_form).unwrap(); let num_deleted = Post::delete(&conn, inserted_post.id).unwrap(); diff --git a/server/src/actions/user.rs b/server/src/actions/user.rs index 70a0213..d646adc 100644 --- a/server/src/actions/user.rs +++ b/server/src/actions/user.rs @@ -117,7 +117,7 @@ mod tests { let conn = establish_connection(); let new_user = UserForm { - name: "thom".into(), + name: "thommy".into(), fedi_name: "rrf".into(), preferred_username: None, password_encrypted: "nope".into(), @@ -129,7 +129,7 @@ mod tests { let expected_user = User_ { id: inserted_user.id, - name: "thom".into(), + name: "thommy".into(), fedi_name: "rrf".into(), preferred_username: None, password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(), diff --git a/server/src/bin/main.rs b/server/src/bin/main.rs index 08663f5..fa0f532 100644 --- a/server/src/bin/main.rs +++ b/server/src/bin/main.rs @@ -3,7 +3,7 @@ extern crate server; use std::time::{Instant, Duration}; use server::actix::*; use server::actix_web::server::HttpServer; -use server::actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse}; +use server::actix_web::{ws, App, Error, HttpRequest, HttpResponse}; use server::websocket_server::server::*; diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 4a2a746..e5e117e 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -17,10 +17,12 @@ use actions::post::*; use actions::comment::*; use actions::post_view::*; use actions::comment_view::*; +use actions::category::*; +use actions::community_view::*; #[derive(EnumString,ToString,Debug)] pub enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity + Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity } #[derive(Serialize, Deserialize)] @@ -103,7 +105,7 @@ pub struct CreateCommunity { #[derive(Serialize, Deserialize)] pub struct CreateCommunityResponse { op: String, - community: Community + community: CommunityView } #[derive(Serialize, Deserialize)] @@ -112,7 +114,16 @@ pub struct ListCommunities; #[derive(Serialize, Deserialize)] pub struct ListCommunitiesResponse { op: String, - communities: Vec + communities: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct ListCategories; + +#[derive(Serialize, Deserialize)] +pub struct ListCategoriesResponse { + op: String, + categories: Vec } #[derive(Serialize, Deserialize)] @@ -141,7 +152,8 @@ pub struct GetPost { pub struct GetPostResponse { op: String, post: PostView, - comments: Vec + comments: Vec, + community: CommunityView } #[derive(Serialize, Deserialize)] @@ -167,7 +179,7 @@ pub struct GetCommunity { #[derive(Serialize, Deserialize)] pub struct GetCommunityResponse { op: String, - community: Community + community: CommunityView } #[derive(Serialize, Deserialize)] @@ -333,7 +345,7 @@ impl Handler for ChatServer { } // send message to other users // for room in rooms { - // self.send_room_message(room, "Someone disconnected", 0); + // self.send_room_message(room, "Someone disconnected", 0); // } } } @@ -377,6 +389,10 @@ impl Handler for ChatServer { let list_communities: ListCommunities = ListCommunities; list_communities.perform(self, msg.id) }, + UserOperation::ListCategories => { + let list_categories: ListCategories = ListCategories; + list_categories.perform(self, msg.id) + }, UserOperation::CreatePost => { let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap(); create_post.perform(self, msg.id) @@ -576,10 +592,12 @@ impl Perform for CreateCommunity { } }; + let community_view = CommunityView::read(&conn, inserted_community.id).unwrap(); + serde_json::to_string( &CreateCommunityResponse { op: self.op_type().to_string(), - community: inserted_community + community: community_view } ) .unwrap() @@ -595,7 +613,7 @@ impl Perform for ListCommunities { let conn = establish_connection(); - let communities: Vec = Community::list_all(&conn).unwrap(); + let communities: Vec = CommunityView::list_all(&conn).unwrap(); // Return the jwt serde_json::to_string( @@ -608,6 +626,28 @@ impl Perform for ListCommunities { } } +impl Perform for ListCategories { + fn op_type(&self) -> UserOperation { + UserOperation::ListCategories + } + + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { + + let conn = establish_connection(); + + let categories: Vec = Category::list_all(&conn).unwrap(); + + // Return the jwt + serde_json::to_string( + &ListCategoriesResponse { + op: self.op_type().to_string(), + categories: categories + } + ) + .unwrap() + } +} + impl Perform for CreatePost { fn op_type(&self) -> UserOperation { UserOperation::CreatePost @@ -656,9 +696,9 @@ impl Perform for CreatePost { return self.error("Couldn't like post."); } }; - + // Refetch the view - let post_view = match PostView::get(&conn, inserted_post.id, Some(user_id)) { + let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) { Ok(post) => post, Err(_e) => { return self.error("Couldn't find Post"); @@ -700,7 +740,7 @@ impl Perform for GetPost { None => None }; - let post_view = match PostView::get(&conn, self.id, user_id) { + let post_view = match PostView::read(&conn, self.id, user_id) { Ok(post) => post, Err(_e) => { return self.error("Couldn't find Post"); @@ -720,12 +760,15 @@ impl Perform for GetPost { let comments = CommentView::list(&conn, self.id, user_id).unwrap(); + let community = CommunityView::read(&conn, post_view.community_id).unwrap(); + // Return the jwt serde_json::to_string( &GetPostResponse { op: self.op_type().to_string(), post: post_view, - comments: comments + comments: comments, + community: community } ) .unwrap() @@ -741,7 +784,7 @@ impl Perform for GetCommunity { let conn = establish_connection(); - let community = match Community::read(&conn, self.id) { + let community_view = match CommunityView::read(&conn, self.id) { Ok(community) => community, Err(_e) => { return self.error("Couldn't find Community"); @@ -752,7 +795,7 @@ impl Perform for GetCommunity { serde_json::to_string( &GetCommunityResponse { op: self.op_type().to_string(), - community: community + community: community_view } ) .unwrap() @@ -828,7 +871,7 @@ impl Perform for CreateComment { } ) .unwrap(); - + chat.send_room_message(self.post_id, &comment_sent_out, addr); comment_out @@ -890,7 +933,7 @@ impl Perform for EditComment { } ) .unwrap(); - + chat.send_room_message(self.post_id, &comment_sent_out, addr); comment_out @@ -958,9 +1001,9 @@ impl Perform for CreateCommentLike { ) .unwrap(); - chat.send_room_message(self.post_id, &like_sent_out, addr); + chat.send_room_message(self.post_id, &like_sent_out, addr); - like_out + like_out } } @@ -1049,7 +1092,7 @@ impl Perform for CreatePostLike { }; } - let post_view = match PostView::get(&conn, self.post_id, Some(user_id)) { + let post_view = match PostView::read(&conn, self.post_id, Some(user_id)) { Ok(post) => post, Err(_e) => { return self.error("Couldn't find Post"); @@ -1066,7 +1109,7 @@ impl Perform for CreatePostLike { ) .unwrap(); - like_out + like_out } } @@ -1104,7 +1147,7 @@ impl Perform for EditPost { } }; - let post_view = PostView::get(&conn, self.edit_id, Some(user_id)).unwrap(); + let post_view = PostView::read(&conn, self.edit_id, Some(user_id)).unwrap(); let mut post_sent = post_view.clone(); post_sent.my_vote = None; @@ -1124,7 +1167,7 @@ impl Perform for EditPost { } ) .unwrap(); - + chat.send_room_message(self.edit_id, &post_sent_out, addr); post_out @@ -1255,7 +1298,7 @@ impl Perform for EditPost { // ) // ) // }; - + // MessageResult( // Ok( // CreateCommunityResponse { diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 5bef29b..820db90 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -6,7 +6,8 @@ import { UserOperation, Community as CommunityI, CommunityResponse, Post, GetPos import { WebSocketService, UserService } from '../services'; import { MomentTime } from './moment-time'; import { PostListing } from './post-listing'; -import { msgOp } from '../utils'; +import { Sidebar } from './sidebar'; +import { msgOp, mdToHtml } from '../utils'; interface State { community: CommunityI; @@ -21,6 +22,13 @@ export class Community extends Component { community: { id: null, name: null, + title: null, + category_id: null, + category_name: null, + creator_id: null, + creator_name: null, + number_of_subscribers: null, + number_of_posts: null, published: null }, posts: [], @@ -70,10 +78,8 @@ export class Community extends Component { }
- Sidebar +
- - ) diff --git a/ui/src/components/create-community.tsx b/ui/src/components/create-community.tsx index 7e56fcd..c5149f0 100644 --- a/ui/src/components/create-community.tsx +++ b/ui/src/components/create-community.tsx @@ -1,7 +1,7 @@ import { Component, linkEvent } from 'inferno'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; -import { CommunityForm, UserOperation } from '../interfaces'; +import { CommunityForm, UserOperation, Category, ListCategoriesResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { msgOp } from '../utils'; @@ -9,6 +9,7 @@ import { Community } from '../interfaces'; interface State { communityForm: CommunityForm; + categories: Array; } export class CreateCommunity extends Component { @@ -17,14 +18,17 @@ export class CreateCommunity extends Component { private emptyState: State = { communityForm: { name: null, - } + title: null, + category_id: null + }, + categories: [] } 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( @@ -32,6 +36,8 @@ export class CreateCommunity extends Component { (err) => console.error(err), () => console.log("complete") ); + + WebSocketService.Instance.listCategories(); } componentWillUnmount() { @@ -61,6 +67,28 @@ export class CreateCommunity extends Component { +
+ +
+ +
+
+
+ +
+