From 335cabc1aad31aec828223e592fbe8b37a58de30 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 17 Apr 2019 11:30:13 -0700 Subject: [PATCH] Adding post and user details paging. - Fixes #52 - Fixes #34 --- server/src/actions/comment_view.rs | 20 ++++++--- server/src/actions/community_view.rs | 22 ++++++---- server/src/actions/moderator_views.rs | 57 +++++++++++-------------- server/src/actions/post_view.rs | 19 ++++++--- server/src/lib.rs | 7 ++++ server/src/websocket_server/server.rs | 33 ++++++++------- ui/src/components/post-listings.tsx | 58 +++++++++++++++++--------- ui/src/components/user.tsx | 60 ++++++++++++++++++++------- ui/src/interfaces.ts | 10 +++-- ui/src/utils.ts | 2 + 10 files changed, 183 insertions(+), 105 deletions(-) diff --git a/server/src/actions/comment_view.rs b/server/src/actions/comment_view.rs index d6d9e40..0848ee1 100644 --- a/server/src/actions/comment_view.rs +++ b/server/src/actions/comment_view.rs @@ -3,7 +3,7 @@ use diesel::*; use diesel::result::Error; use diesel::dsl::*; use serde::{Deserialize, Serialize}; -use { SortType }; +use { SortType, limit_and_offset }; // The faked schema since diesel doesn't do views table! { @@ -57,10 +57,15 @@ impl CommentView { for_post_id: Option, for_creator_id: Option, my_user_id: Option, - limit: i64) -> Result, Error> { + page: Option, + limit: Option, + ) -> Result, Error> { use actions::comment_view::comment_view::dsl::*; - let mut query = comment_view.limit(limit).into_boxed(); + let (limit, offset) = limit_and_offset(page, limit); + + // TODO no limits here? + 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(my_user_id) = my_user_id { @@ -96,7 +101,10 @@ impl CommentView { _ => query.order_by(published.desc()) }; - query.load::(conn) + query + .limit(limit) + .offset(offset) + .load::(conn) } pub fn read(conn: &PgConnection, from_comment_id: i32, my_user_id: Option) -> Result { @@ -230,8 +238,8 @@ mod tests { am_mod: None, }; - let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, 999).unwrap(); - let read_comment_views_with_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, Some(inserted_user.id), 999).unwrap(); + let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, None, None).unwrap(); + let read_comment_views_with_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, Some(inserted_user.id), None, None).unwrap(); let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap(); let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap(); diff --git a/server/src/actions/community_view.rs b/server/src/actions/community_view.rs index 078e7a6..a754c2e 100644 --- a/server/src/actions/community_view.rs +++ b/server/src/actions/community_view.rs @@ -2,7 +2,7 @@ extern crate diesel; use diesel::*; use diesel::result::Error; use serde::{Deserialize, Serialize}; -use {SortType}; +use {SortType, limit_and_offset}; table! { community_view (id) { @@ -114,14 +114,18 @@ impl CommunityView { query.first::(conn) } - pub fn list(conn: &PgConnection, from_user_id: Option, sort: SortType, limit: Option) -> Result, Error> { + pub fn list(conn: &PgConnection, + from_user_id: Option, + sort: SortType, + page: Option, + limit: Option, + ) -> Result, Error> { use actions::community_view::community_view::dsl::*; let mut query = community_view.into_boxed(); - + let (limit, offset) = limit_and_offset(page, limit); // The view lets you pass a null user_id, if you're not logged in - match sort { SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()), SortType::TopAll => { @@ -133,11 +137,11 @@ impl CommunityView { _ => () }; - if let Some(limit) = limit { - query = query.limit(limit); - }; - - query.filter(removed.eq(false)).load::(conn) + query + .limit(limit) + .offset(offset) + .filter(removed.eq(false)) + .load::(conn) } } diff --git a/server/src/actions/moderator_views.rs b/server/src/actions/moderator_views.rs index 2e24356..8941827 100644 --- a/server/src/actions/moderator_views.rs +++ b/server/src/actions/moderator_views.rs @@ -2,6 +2,7 @@ extern crate diesel; use diesel::*; use diesel::result::Error; use serde::{Deserialize, Serialize}; +use {limit_and_offset}; table! { mod_remove_post_view (id) { @@ -37,14 +38,13 @@ impl ModRemovePostView { pub fn list(conn: &PgConnection, from_community_id: Option, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_remove_post_view::dsl::*; let mut query = mod_remove_post_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_community_id) = from_community_id { query = query.filter(community_id.eq(from_community_id)); @@ -91,14 +91,13 @@ impl ModLockPostView { pub fn list(conn: &PgConnection, from_community_id: Option, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_lock_post_view::dsl::*; let mut query = mod_lock_post_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_community_id) = from_community_id { query = query.filter(community_id.eq(from_community_id)); @@ -154,14 +153,13 @@ impl ModRemoveCommentView { pub fn list(conn: &PgConnection, from_community_id: Option, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_remove_comment_view::dsl::*; let mut query = mod_remove_comment_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_community_id) = from_community_id { query = query.filter(community_id.eq(from_community_id)); @@ -206,14 +204,13 @@ pub struct ModRemoveCommunityView { impl ModRemoveCommunityView { pub fn list(conn: &PgConnection, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_remove_community_view::dsl::*; let mut query = mod_remove_community_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_mod_user_id) = from_mod_user_id { query = query.filter(mod_user_id.eq(from_mod_user_id)); @@ -260,14 +257,13 @@ impl ModBanFromCommunityView { pub fn list(conn: &PgConnection, from_community_id: Option, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_ban_from_community_view::dsl::*; let mut query = mod_ban_from_community_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_community_id) = from_community_id { query = query.filter(community_id.eq(from_community_id)); @@ -312,14 +308,13 @@ pub struct ModBanView { impl ModBanView { pub fn list(conn: &PgConnection, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_ban_view::dsl::*; let mut query = mod_ban_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_mod_user_id) = from_mod_user_id { query = query.filter(mod_user_id.eq(from_mod_user_id)); @@ -361,14 +356,13 @@ impl ModAddCommunityView { pub fn list(conn: &PgConnection, from_community_id: Option, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_add_community_view::dsl::*; let mut query = mod_add_community_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_community_id) = from_community_id { query = query.filter(community_id.eq(from_community_id)); @@ -409,14 +403,13 @@ pub struct ModAddView { impl ModAddView { pub fn list(conn: &PgConnection, from_mod_user_id: Option, + page: Option, limit: Option, - page: Option) -> Result, Error> { + ) -> Result, Error> { use actions::moderator_views::mod_add_view::dsl::*; let mut query = mod_add_view.into_boxed(); - let page = page.unwrap_or(1); - let limit = limit.unwrap_or(10); - let offset = limit * (page - 1); + let (limit, offset) = limit_and_offset(page, limit); if let Some(from_mod_user_id) = from_mod_user_id { query = query.filter(mod_user_id.eq(from_mod_user_id)); diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index f95c017..7ab490a 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -3,7 +3,7 @@ use diesel::*; use diesel::result::Error; use diesel::dsl::*; use serde::{Deserialize, Serialize}; -use { SortType }; +use { SortType, limit_and_offset }; #[derive(EnumString,ToString,Debug, Serialize, Deserialize)] pub enum PostListingType { @@ -71,10 +71,14 @@ impl PostView { for_community_id: Option, for_creator_id: Option, my_user_id: Option, - limit: i64) -> Result, Error> { + page: Option, + limit: Option, + ) -> Result, Error> { use actions::post_view::post_view::dsl::*; - let mut query = post_view.limit(limit).into_boxed(); + let (limit, offset) = limit_and_offset(page, limit); + + let mut query = post_view.into_boxed(); if let Some(for_community_id) = for_community_id { query = query.filter(community_id.eq(for_community_id)); @@ -119,7 +123,10 @@ impl PostView { // TODO make sure community removed isn't fetched either - query = query.filter(removed.eq(false)); + query = query + .limit(limit) + .offset(offset) + .filter(removed.eq(false)); query.load::(conn) } @@ -271,8 +278,8 @@ mod tests { }; - let read_post_listings_with_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, Some(inserted_user.id), 10).unwrap(); - let read_post_listings_no_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, None, 10).unwrap(); + let read_post_listings_with_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, Some(inserted_user.id), None, None).unwrap(); + let read_post_listings_no_user = PostView::list(&conn, PostListingType::Community, &SortType::New, Some(inserted_community.id), None, None, None, None).unwrap(); let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap(); let read_post_listing_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap(); diff --git a/server/src/lib.rs b/server/src/lib.rs index ab971ed..3390dbd 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -109,6 +109,13 @@ pub fn has_slurs(test: &str) -> bool { SLUR_REGEX.is_match(test) } +pub fn limit_and_offset(page: Option, limit: Option) -> (i64, i64) { + let page = page.unwrap_or(1); + let limit = limit.unwrap_or(10); + let offset = limit * (page - 1); + (limit, offset) +} + #[cfg(test)] mod tests { use {Settings, is_email_regex, remove_slurs, has_slurs}; diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 6b91a76..4f0efb0 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -116,6 +116,7 @@ pub struct CommunityResponse { #[derive(Serialize, Deserialize)] pub struct ListCommunities { sort: String, + page: Option, limit: Option, auth: Option } @@ -170,7 +171,8 @@ pub struct GetPostResponse { pub struct GetPosts { type_: String, sort: String, - limit: i64, + page: Option, + limit: Option, community_id: Option, auth: Option } @@ -292,7 +294,8 @@ pub struct GetFollowedCommunitiesResponse { pub struct GetUserDetails { user_id: i32, sort: String, - limit: i64, + page: Option, + limit: Option, community_id: Option, auth: Option } @@ -313,8 +316,8 @@ pub struct GetUserDetailsResponse { pub struct GetModlog { mod_user_id: Option, community_id: Option, - limit: Option, page: Option, + limit: Option, } #[derive(Serialize, Deserialize)] @@ -465,7 +468,9 @@ impl ChatServer { Some(community_id), None, None, - 999).unwrap(); + None, + Some(9999)) + .unwrap(); for post in posts { self.send_room_message(post.id, message, skip_id); } @@ -871,7 +876,7 @@ impl Perform for ListCommunities { let sort = SortType::from_str(&self.sort).expect("listing sort"); - let communities: Vec = CommunityView::list(&conn, user_id, sort, self.limit).unwrap(); + let communities: Vec = CommunityView::list(&conn, user_id, sort, self.page, self.limit).unwrap(); // Return the jwt serde_json::to_string( @@ -1026,7 +1031,7 @@ impl Perform for GetPost { chat.rooms.get_mut(&self.id).unwrap().insert(addr); - let comments = CommentView::list(&conn, &SortType::New, Some(self.id), None, user_id, 999).unwrap(); + let comments = CommentView::list(&conn, &SortType::New, Some(self.id), None, user_id, None, Some(9999)).unwrap(); let community = CommunityView::read(&conn, post_view.community_id, user_id).unwrap(); @@ -1372,7 +1377,7 @@ impl Perform for GetPosts { let type_ = PostListingType::from_str(&self.type_).expect("listing type"); let sort = SortType::from_str(&self.sort).expect("listing sort"); - let posts = match PostView::list(&conn, type_, &sort, self.community_id, None, user_id, self.limit) { + let posts = match PostView::list(&conn, type_, &sort, self.community_id, None, user_id, self.page, self.limit) { Ok(posts) => posts, Err(_e) => { return self.error("Couldn't get posts"); @@ -1758,8 +1763,8 @@ impl Perform for GetUserDetails { let sort = SortType::from_str(&self.sort).expect("listing sort"); let user_view = UserView::read(&conn, self.user_id).unwrap(); - let posts = PostView::list(&conn, PostListingType::All, &sort, self.community_id, Some(self.user_id), user_id, self.limit).unwrap(); - let comments = CommentView::list(&conn, &sort, None, Some(self.user_id), user_id, self.limit).unwrap(); + let posts = PostView::list(&conn, PostListingType::All, &sort, self.community_id, Some(self.user_id), user_id, self.page, self.limit).unwrap(); + let comments = CommentView::list(&conn, &sort, None, Some(self.user_id), user_id, self.page, self.limit).unwrap(); let follows = CommunityFollowerView::for_user(&conn, self.user_id).unwrap(); let moderates = CommunityModeratorView::for_user(&conn, self.user_id).unwrap(); @@ -1789,11 +1794,11 @@ impl Perform for GetModlog { let conn = establish_connection(); - let removed_posts = ModRemovePostView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); - let locked_posts = ModLockPostView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); - let removed_comments = ModRemoveCommentView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); - let banned_from_community = ModBanFromCommunityView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); - let added_to_community = ModAddCommunityView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap(); + let removed_posts = ModRemovePostView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit).unwrap(); + let locked_posts = ModLockPostView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit).unwrap(); + let removed_comments = ModRemoveCommentView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit).unwrap(); + let banned_from_community = ModBanFromCommunityView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit).unwrap(); + let added_to_community = ModAddCommunityView::list(&conn, self.community_id, self.mod_user_id, self.page, self.limit).unwrap(); // These arrays are only for the full modlog, when a community isn't given let mut removed_communities = Vec::new(); diff --git a/ui/src/components/post-listings.tsx b/ui/src/components/post-listings.tsx index 3481440..5fbb6cb 100644 --- a/ui/src/components/post-listings.tsx +++ b/ui/src/components/post-listings.tsx @@ -5,8 +5,7 @@ import { retryWhen, delay, take } from 'rxjs/operators'; import { UserOperation, Community as CommunityI, Post, GetPostsForm, SortType, ListingType, GetPostsResponse, CreatePostLikeResponse, CommunityUser} from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { PostListing } from './post-listing'; -import { msgOp } from '../utils'; - +import { msgOp, fetchLimit } from '../utils'; interface PostListingsProps { communityId?: number; @@ -18,6 +17,7 @@ interface PostListingsState { posts: Array; sortType: SortType; type_: ListingType; + page: number; loading: boolean; } @@ -46,6 +46,7 @@ export class PostListings extends Component console.log('complete') ); - let getPostsForm: GetPostsForm = { - type_: ListingType[this.state.type_], - community_id: this.props.communityId, - limit: 10, - sort: SortType[SortType.Hot], - } - WebSocketService.Instance.getPosts(getPostsForm); + this.refetch(); } componentWillUnmount() { @@ -82,12 +77,13 @@ export class PostListings extends Component :
-
{this.selects()}
+ {this.selects()} {this.state.posts.length > 0 ? this.state.posts.map(post => ) :
No Listings. {!this.props.communityId && Subscribe to some forums.}
} + {this.paginator()}
} @@ -119,17 +115,44 @@ export class PostListings extends Component ) + } + paginator() { + return ( +
+ {this.state.page > 1 && + + } + +
+ ); + } + + nextPage(i: PostListings) { + i.state.page++; + i.setState(i.state); + i.refetch(); + } + + prevPage(i: PostListings) { + i.state.page--; + i.setState(i.state); + i.refetch(); } handleSortChange(i: PostListings, event: any) { i.state.sortType = Number(event.target.value); + i.state.page = 1; i.setState(i.state); + i.refetch(); + } + refetch() { let getPostsForm: GetPostsForm = { - community_id: i.state.community.id, - limit: 10, - sort: SortType[i.state.sortType], + community_id: this.state.community.id, + page: this.state.page, + limit: fetchLimit, + sort: SortType[this.state.sortType], type_: ListingType[ListingType.Community] } WebSocketService.Instance.getPosts(getPostsForm); @@ -137,14 +160,9 @@ export class PostListings extends Component; moderates: Array; comments: Array; @@ -22,6 +23,7 @@ interface UserState { saved?: Array; view: View; sort: SortType; + page: number; } export class User extends Component { @@ -38,12 +40,14 @@ export class User extends Component { number_of_comments: null, comment_score: null, }, + user_id: null, follows: [], moderates: [], comments: [], posts: [], view: View.Overview, - sort: SortType.New + sort: SortType.New, + page: 1, } constructor(props: any, context: any) { @@ -51,7 +55,7 @@ export class User extends Component { this.state = this.emptyState; - let userId = Number(this.props.match.params.id); + this.state.user_id = Number(this.props.match.params.id); this.subscription = WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) @@ -61,12 +65,7 @@ export class User extends Component { () => console.log('complete') ); - let form: GetUserDetailsForm = { - user_id: userId, - sort: SortType[this.state.sort], - limit: 999 - }; - WebSocketService.Instance.getUserDetails(form); + this.refetch(); } componentWillUnmount() { @@ -89,6 +88,7 @@ export class User extends Component { {this.state.view == View.Posts && this.posts() } + {this.paginator()}
{this.userInfo()} @@ -230,21 +230,51 @@ export class User extends Component { ) } - handleSortChange(i: User, event: any) { - i.state.sort = Number(event.target.value); - i.setState(i.state); + paginator() { + return ( +
+ {this.state.page > 1 && + + } + +
+ ); + } + nextPage(i: User) { + i.state.page++; + i.setState(i.state); + i.refetch(); + } + + prevPage(i: User) { + i.state.page--; + i.setState(i.state); + i.refetch(); + } + + refetch() { let form: GetUserDetailsForm = { - user_id: i.state.user.id, - sort: SortType[i.state.sort], - limit: 999 + user_id: this.state.user_id, + sort: SortType[this.state.sort], + page: this.state.page, + limit: fetchLimit, }; WebSocketService.Instance.getUserDetails(form); } + handleSortChange(i: User, event: any) { + i.state.sort = Number(event.target.value); + i.state.page = 1; + i.setState(i.state); + i.refetch(); + } + handleViewChange(i: User, event: any) { i.state.view = Number(event.target.value); + i.state.page = 1; i.setState(i.state); + i.refetch(); } parseMessage(msg: any) { diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 869fb60..6affc0e 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -134,7 +134,8 @@ export interface GetFollowedCommunitiesResponse { export interface GetUserDetailsForm { user_id: number; sort: string; // TODO figure this one out - limit: number; + page?: number; + limit?: number; community_id?: number; auth?: string; } @@ -179,8 +180,8 @@ export interface AddModToCommunityResponse { export interface GetModlogForm { mod_user_id?: number; community_id?: number; - limit?: number; page?: number; + limit?: number; } export interface GetModlogResponse { @@ -343,6 +344,7 @@ export interface CommunityResponse { export interface ListCommunitiesForm { sort: string; + page?: number; limit?: number; auth?: string; } @@ -356,6 +358,7 @@ export interface ListCategoriesResponse { op: string; categories: Array; } + export interface PostForm { name: string; url?: string; @@ -414,7 +417,8 @@ export interface CommentNode { export interface GetPostsForm { type_: string; sort: string; - limit: number; + page?: number; + limit?: number; community_id?: number; auth?: string; } diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 4bd0d90..c7f3bad 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -39,3 +39,5 @@ export function getUnixTime(text: string): number { export function addTypeInfo(arr: Array, name: string): Array<{type_: string, data: T}> { return arr.map(e => {return {type_: name, data: e}}); } + +export let fetchLimit: number = 20;