From 6a761165bb8c0bfe389b62ca39461880eff93b0f Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 13 Aug 2019 19:52:43 -0700 Subject: [PATCH] Squashed commit of the following: commit ecd6c5a2f47cbbb2fc4bf482fadd78380303a904 Author: Dessalines Date: Tue Aug 13 19:49:38 2019 -0700 Adding some docs commit 3babd09affb1920da3d0a0ceb7e24c8aeeb9cf1a Author: Dessalines Date: Tue Aug 13 19:28:46 2019 -0700 Adding save user settings commit 6e8da9cc9e522d0da668bfa31944c3348cc79620 Merge: 3246d5d c148eef Author: Dessalines Date: Tue Aug 13 17:26:25 2019 -0700 Merge branch 'dev' into nsfw commit b3d4a5c4ce441bcc664704aba44cedb51d887599 Author: Dessalines Date: Sun Aug 11 20:55:09 2019 -0700 nsfw mostly done, except for settings page. --- README.md | 1 + docker_db_backup.sh | 2 +- docs/api.md | 18 +- .../down.sql | 80 +++++++++ .../2019-08-11-000918_add_nsfw_columns/up.sql | 79 +++++++++ server/src/api/community.rs | 30 +++- server/src/api/mod.rs | 2 +- server/src/api/post.rs | 44 +++-- server/src/api/site.rs | 6 + server/src/api/user.rs | 156 +++++++++++++----- server/src/db/community.rs | 4 + server/src/db/community_view.rs | 22 ++- server/src/db/post.rs | 4 + server/src/db/post_view.rs | 38 +++-- server/src/db/user.rs | 14 +- server/src/lib.rs | 1 + server/src/schema.rs | 3 + server/src/websocket/server.rs | 29 ++-- ui/package.json | 2 +- ui/src/components/community-form.tsx | 17 +- ui/src/components/community.tsx | 4 + ui/src/components/login.tsx | 15 +- ui/src/components/post-form.tsx | 15 ++ ui/src/components/post-listing.tsx | 6 + ui/src/components/setup.tsx | 1 + ui/src/components/user.tsx | 66 +++++++- ui/src/interfaces.ts | 15 +- ui/src/services/WebSocketService.ts | 7 +- ui/src/translations/en.ts | 3 + ui/tslint.json | 2 +- ui/yarn.lock | 2 +- 31 files changed, 576 insertions(+), 112 deletions(-) create mode 100644 server/migrations/2019-08-11-000918_add_nsfw_columns/down.sql create mode 100644 server/migrations/2019-08-11-000918_add_nsfw_columns/up.sql diff --git a/README.md b/README.md index 295c436f6..9862885c4 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Front Page|Post - Can ban and unban users from communities and the site. - Clean, mobile-friendly interface. - i18n / internationalization support. +- NSFW post / community support. - High performance. - Server is written in rust. - Front end is `~80kB` gzipped. diff --git a/docker_db_backup.sh b/docker_db_backup.sh index 5b87b818b..e361da194 100755 --- a/docker_db_backup.sh +++ b/docker_db_backup.sh @@ -1 +1 @@ -docker exec -it lemmy_db_1 pg_dumpall -c -U rrr > dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql +docker exec -it lemmy_db_1 pg_dumpall -c -U rrr > dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql diff --git a/docs/api.md b/docs/api.md index 744de5f3b..90b931e61 100644 --- a/docs/api.md +++ b/docs/api.md @@ -28,7 +28,7 @@ A simple test command: ## API ### List -`Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead` +`Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead, SaveUserSettings` ### Sort Types These go wherever there is a `sort` field. @@ -109,7 +109,21 @@ Only the first user will be able to be the admin. posts: Vec, } ``` - +#### Save User Settings +##### Request +```rust +{ + show_nsfw: bool, + auth: String, +} +``` +##### Response +```rust +{ + op: String, + jwt: String +} +``` #### Get Replies / Inbox ##### Request ```rust diff --git a/server/migrations/2019-08-11-000918_add_nsfw_columns/down.sql b/server/migrations/2019-08-11-000918_add_nsfw_columns/down.sql new file mode 100644 index 000000000..2eefece4e --- /dev/null +++ b/server/migrations/2019-08-11-000918_add_nsfw_columns/down.sql @@ -0,0 +1,80 @@ +drop view community_view; +drop view post_view; +alter table community drop column nsfw; +alter table post drop column nsfw; +alter table user_ drop column show_nsfw; + +-- the views +create view community_view as +with all_community 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, + (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, + hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank + from community c +) + +select +ac.*, +u.id as user_id, +(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed +from user_ u +cross join all_community ac + +union all + +select +ac.*, +null as user_id, +null as subscribed +from all_community ac +; + + +-- Post view +create view post_view as +with all_post as +( + select + p.*, + (select name from user_ where p.creator_id = user_.id) as creator_name, + (select name from community where p.community_id = community.id) as community_name, + (select removed from community c where p.community_id = c.id) as community_removed, + (select deleted from community c where p.community_id = c.id) as community_deleted, + (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 + 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, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; + diff --git a/server/migrations/2019-08-11-000918_add_nsfw_columns/up.sql b/server/migrations/2019-08-11-000918_add_nsfw_columns/up.sql new file mode 100644 index 000000000..cc1e00740 --- /dev/null +++ b/server/migrations/2019-08-11-000918_add_nsfw_columns/up.sql @@ -0,0 +1,79 @@ +alter table community add column nsfw boolean default false not null; +alter table post add column nsfw boolean default false not null; +alter table user_ add column show_nsfw boolean default false not null; + +-- The views +drop view community_view; +create view community_view as +with all_community 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, + (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, + hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank + from community c +) + +select +ac.*, +u.id as user_id, +(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed +from user_ u +cross join all_community ac + +union all + +select +ac.*, +null as user_id, +null as subscribed +from all_community ac +; + +-- Post view +drop view post_view; +create view post_view as +with all_post as +( + select + p.*, + (select name from user_ where p.creator_id = user_.id) as creator_name, + (select name from community where p.community_id = community.id) as community_name, + (select removed from community c where p.community_id = c.id) as community_removed, + (select deleted from community c where p.community_id = c.id) as community_deleted, + (select nsfw from community c where p.community_id = c.id) as community_nsfw, + (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 + 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, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; diff --git a/server/src/api/community.rs b/server/src/api/community.rs index ca73de49c..740584888 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -22,7 +22,8 @@ pub struct CreateCommunity { name: String, title: String, description: Option, - category_id: i32 , + category_id: i32, + nsfw: bool, auth: String } @@ -86,6 +87,7 @@ pub struct EditCommunity { category_id: i32, removed: Option, deleted: Option, + nsfw: bool, reason: Option, expires: Option, auth: String @@ -194,6 +196,7 @@ impl Perform for Oper { creator_id: user_id, removed: None, deleted: None, + nsfw: data.nsfw, updated: None, }; @@ -291,6 +294,7 @@ impl Perform for Oper { creator_id: user_id, removed: data.removed.to_owned(), deleted: data.deleted.to_owned(), + nsfw: data.nsfw, updated: Some(naive_now()) }; @@ -333,22 +337,38 @@ impl Perform for Oper { let data: &ListCommunities = &self.data; let conn = establish_connection(); - let user_id: Option = match &data.auth { + let user_claims: Option = match &data.auth { Some(auth) => { match Claims::decode(&auth) { Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) + Some(claims.claims) } Err(_e) => None } } None => None }; + + let user_id = match &user_claims { + Some(claims) => Some(claims.id), + None => None + }; + + let show_nsfw = match &user_claims { + Some(claims) => claims.show_nsfw, + None => false + }; let sort = SortType::from_str(&data.sort)?; - let communities: Vec = CommunityView::list(&conn, &sort, user_id, None, data.page, data.limit)?; + let communities: Vec = CommunityView::list( + &conn, + &sort, + user_id, + show_nsfw, + None, + data.page, + data.limit)?; // Return the jwt Ok( diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index e10770b4d..3a4a08658 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -22,7 +22,7 @@ pub mod site; #[derive(EnumString,ToString,Debug)] pub enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead + Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead, SaveUserSettings } #[derive(Fail, Debug)] diff --git a/server/src/api/post.rs b/server/src/api/post.rs index df6ea852f..35363a171 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -6,6 +6,7 @@ pub struct CreatePost { name: String, url: Option, body: Option, + nsfw: bool, community_id: i32, auth: String } @@ -73,6 +74,7 @@ pub struct EditPost { body: Option, removed: Option, deleted: Option, + nsfw: bool, locked: Option, reason: Option, auth: String @@ -123,6 +125,7 @@ impl Perform for Oper { creator_id: user_id, removed: None, deleted: None, + nsfw: data.nsfw, locked: None, updated: None }; @@ -219,40 +222,50 @@ impl Perform for Oper { let data: &GetPosts = &self.data; let conn = establish_connection(); - let user_id: Option = match &data.auth { + let user_claims: Option = match &data.auth { Some(auth) => { match Claims::decode(&auth) { Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) + Some(claims.claims) } Err(_e) => None } } None => None }; + + let user_id = match &user_claims { + Some(claims) => Some(claims.id), + None => None + }; + + let show_nsfw = match &user_claims { + Some(claims) => claims.show_nsfw, + None => false + }; let type_ = PostListingType::from_str(&data.type_)?; let sort = SortType::from_str(&data.sort)?; - let posts = match PostView::list(&conn, - type_, - &sort, - data.community_id, - None, - None, - user_id, - false, - false, - data.page, - data.limit) { + let posts = match PostView::list( + &conn, + type_, + &sort, + data.community_id, + None, + None, + user_id, + show_nsfw, + false, + false, + data.page, + data.limit) { Ok(posts) => posts, Err(_e) => { return Err(APIError::err(&self.op, "couldnt_get_posts"))? } }; - // Return the jwt Ok( GetPostsResponse { op: self.op.to_string(), @@ -381,6 +394,7 @@ impl Perform for Oper { community_id: data.community_id, removed: data.removed.to_owned(), deleted: data.deleted.to_owned(), + nsfw: data.nsfw, locked: data.locked.to_owned(), updated: Some(naive_now()) }; diff --git a/server/src/api/site.rs b/server/src/api/site.rs index 09af742fa..8f094aace 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -277,6 +277,8 @@ impl Perform for Oper { let mut communities = Vec::new(); let mut users = Vec::new(); + // TODO no clean / non-nsfw searching rn + match type_ { SearchType::Posts => { posts = PostView::list( @@ -287,6 +289,7 @@ impl Perform for Oper { None, Some(data.q.to_owned()), None, + true, false, false, data.page, @@ -309,6 +312,7 @@ impl Perform for Oper { &conn, &sort, None, + true, Some(data.q.to_owned()), data.page, data.limit)?; @@ -330,6 +334,7 @@ impl Perform for Oper { None, Some(data.q.to_owned()), None, + true, false, false, data.page, @@ -348,6 +353,7 @@ impl Perform for Oper { &conn, &sort, None, + true, Some(data.q.to_owned()), data.page, data.limit)?; diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 5d5f1a6be..2a6c214a7 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -15,6 +15,13 @@ pub struct Register { password: String, password_verify: String, admin: bool, + show_nsfw: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct SaveUserSettings { + show_nsfw: bool, + auth: String, } #[derive(Serialize, Deserialize)] @@ -151,6 +158,7 @@ impl Perform for Oper { updated: None, admin: data.admin, banned: false, + show_nsfw: data.show_nsfw, }; // Create the user @@ -170,6 +178,7 @@ impl Perform for Oper { title: "The Default Community".to_string(), description: Some("The Default Community".to_string()), category_id: 1, + nsfw: false, creator_id: inserted_user.id, removed: None, deleted: None, @@ -218,24 +227,77 @@ impl Perform for Oper { } } +impl Perform for Oper { + fn perform(&self) -> Result { + let data: &SaveUserSettings = &self.data; + let conn = establish_connection(); + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => { + return Err(APIError::err(&self.op, "not_logged_in"))? + } + }; + + let user_id = claims.id; + + let read_user = User_::read(&conn, user_id)?; + + let user_form = UserForm { + name: read_user.name, + fedi_name: read_user.fedi_name, + email: read_user.email, + password_encrypted: read_user.password_encrypted, + preferred_username: read_user.preferred_username, + updated: Some(naive_now()), + admin: read_user.admin, + banned: read_user.banned, + show_nsfw: data.show_nsfw, + }; + + let updated_user = match User_::update(&conn, user_id, &user_form) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err(&self.op, "couldnt_update_user"))? + } + }; + + // Return the jwt + Ok( + LoginResponse { + op: self.op.to_string(), + jwt: updated_user.jwt() + } + ) + } +} impl Perform for Oper { fn perform(&self) -> Result { let data: &GetUserDetails = &self.data; let conn = establish_connection(); - let user_id: Option = match &data.auth { + let user_claims: Option = match &data.auth { Some(auth) => { match Claims::decode(&auth) { Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) + Some(claims.claims) } Err(_e) => None } } None => None }; + + let user_id = match &user_claims { + Some(claims) => Some(claims.id), + None => None + }; + + let show_nsfw = match &user_claims { + Some(claims) => claims.show_nsfw, + None => false + }; //TODO add save let sort = SortType::from_str(&data.sort)?; @@ -249,50 +311,56 @@ impl Perform for Oper { // If its saved only, you don't care what creator it was let posts = if data.saved_only { - PostView::list(&conn, - PostListingType::All, - &sort, - data.community_id, - None, - None, - Some(user_details_id), - data.saved_only, - false, - data.page, - data.limit)? + PostView::list( + &conn, + PostListingType::All, + &sort, + data.community_id, + None, + None, + Some(user_details_id), + show_nsfw, + data.saved_only, + false, + data.page, + data.limit)? } else { - PostView::list(&conn, - PostListingType::All, - &sort, - data.community_id, - Some(user_details_id), - None, - user_id, - data.saved_only, - false, - data.page, - data.limit)? + PostView::list( + &conn, + PostListingType::All, + &sort, + data.community_id, + Some(user_details_id), + None, + user_id, + show_nsfw, + data.saved_only, + false, + data.page, + data.limit)? }; let comments = if data.saved_only { - CommentView::list(&conn, - &sort, - None, - None, - None, - Some(user_details_id), - data.saved_only, - data.page, - data.limit)? + CommentView::list( + &conn, + &sort, + None, + None, + None, + Some(user_details_id), + data.saved_only, + data.page, + data.limit)? } else { - CommentView::list(&conn, - &sort, - None, - Some(user_details_id), - None, - user_id, - data.saved_only, - data.page, - data.limit)? + CommentView::list( + &conn, + &sort, + None, + Some(user_details_id), + None, + user_id, + data.saved_only, + data.page, + data.limit)? }; let follows = CommunityFollowerView::for_user(&conn, user_details_id)?; @@ -343,6 +411,7 @@ impl Perform for Oper { updated: Some(naive_now()), admin: data.added, banned: read_user.banned, + show_nsfw: read_user.show_nsfw, }; match User_::update(&conn, data.user_id, &user_form) { @@ -402,6 +471,7 @@ impl Perform for Oper { updated: Some(naive_now()), admin: read_user.admin, banned: data.ban, + show_nsfw: read_user.show_nsfw, }; match User_::update(&conn, data.user_id, &user_form) { diff --git a/server/src/db/community.rs b/server/src/db/community.rs index b32230b9c..aa28c9c73 100644 --- a/server/src/db/community.rs +++ b/server/src/db/community.rs @@ -14,6 +14,7 @@ pub struct Community { pub published: chrono::NaiveDateTime, pub updated: Option, pub deleted: bool, + pub nsfw: bool, } #[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] @@ -27,6 +28,7 @@ pub struct CommunityForm { pub removed: Option, pub updated: Option, pub deleted: Option, + pub nsfw: bool, } impl Crud for Community { @@ -240,6 +242,7 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, + nsfw: false, removed: None, deleted: None, updated: None, @@ -254,6 +257,7 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, + nsfw: false, removed: false, deleted: false, published: inserted_community.published, diff --git a/server/src/db/community_view.rs b/server/src/db/community_view.rs index 6249090d7..88ab10afe 100644 --- a/server/src/db/community_view.rs +++ b/server/src/db/community_view.rs @@ -12,6 +12,7 @@ table! { published -> Timestamp, updated -> Nullable, deleted -> Bool, + nsfw -> Bool, creator_name -> Varchar, category_name -> Varchar, number_of_subscribers -> BigInt, @@ -84,6 +85,7 @@ pub struct CommunityView { pub published: chrono::NaiveDateTime, pub updated: Option, pub deleted: bool, + pub nsfw: bool, pub creator_name: String, pub category_name: String, pub number_of_subscribers: i64, @@ -112,13 +114,15 @@ impl CommunityView { query.first::(conn) } - pub fn list(conn: &PgConnection, - sort: &SortType, - from_user_id: Option, - search_term: Option, - page: Option, - limit: Option, - ) -> Result, Error> { + pub fn list( + conn: &PgConnection, + sort: &SortType, + from_user_id: Option, + show_nsfw: bool, + search_term: Option, + page: Option, + limit: Option, + ) -> Result, Error> { use super::community_view::community_view::dsl::*; let mut query = community_view.into_boxed(); @@ -143,6 +147,10 @@ impl CommunityView { _ => () }; + if !show_nsfw { + query = query.filter(nsfw.eq(false)); + }; + query .limit(limit) .offset(offset) diff --git a/server/src/db/post.rs b/server/src/db/post.rs index d8fd27b0a..6a8bf26dd 100644 --- a/server/src/db/post.rs +++ b/server/src/db/post.rs @@ -15,6 +15,7 @@ pub struct Post { pub published: chrono::NaiveDateTime, pub updated: Option, pub deleted: bool, + pub nsfw: bool, } #[derive(Insertable, AsChangeset, Clone)] @@ -29,6 +30,7 @@ pub struct PostForm { pub locked: Option, pub updated: Option, pub deleted: Option, + pub nsfw: bool, } impl Crud for Post { @@ -210,6 +212,7 @@ mod tests { removed: None, deleted: None, locked: None, + nsfw: false, updated: None }; @@ -225,6 +228,7 @@ mod tests { published: inserted_post.published, removed: false, locked: false, + nsfw: false, deleted: false, updated: None }; diff --git a/server/src/db/post_view.rs b/server/src/db/post_view.rs index bec00b982..736862665 100644 --- a/server/src/db/post_view.rs +++ b/server/src/db/post_view.rs @@ -19,10 +19,12 @@ table! { published -> Timestamp, updated -> Nullable, deleted -> Bool, + nsfw -> Bool, creator_name -> Varchar, community_name -> Varchar, community_removed -> Bool, community_deleted -> Bool, + community_nsfw -> Bool, number_of_comments -> BigInt, score -> BigInt, upvotes -> BigInt, @@ -51,10 +53,12 @@ pub struct PostView { pub published: chrono::NaiveDateTime, pub updated: Option, pub deleted: bool, + pub nsfw: bool, pub creator_name: String, pub community_name: String, pub community_removed: bool, pub community_deleted: bool, + pub community_nsfw: bool, pub number_of_comments: i64, pub score: i64, pub upvotes: i64, @@ -68,18 +72,20 @@ pub struct PostView { } impl PostView { - pub fn list(conn: &PgConnection, - type_: PostListingType, - sort: &SortType, - for_community_id: Option, - for_creator_id: Option, - search_term: Option, - my_user_id: Option, - saved_only: bool, - unread_only: bool, - page: Option, - limit: Option, - ) -> Result, Error> { + pub fn list( + conn: &PgConnection, + type_: PostListingType, + sort: &SortType, + for_community_id: Option, + for_creator_id: Option, + search_term: Option, + my_user_id: Option, + show_nsfw: bool, + saved_only: bool, + unread_only: bool, + page: Option, + limit: Option, + ) -> Result, Error> { use super::post_view::post_view::dsl::*; let (limit, offset) = limit_and_offset(page, limit); @@ -121,6 +127,12 @@ impl PostView { query = query.filter(user_id.is_null()); } + if !show_nsfw { + query = query + .filter(nsfw.eq(false)) + .filter(community_nsfw.eq(false)); + }; + query = match sort { SortType::Hot => query.order_by(hot_rank.desc()) .then_order_by(published.desc()), @@ -266,6 +278,7 @@ mod tests { community_name: community_name.to_owned(), community_removed: false, community_deleted: false, + community_nsfw: false, number_of_comments: 0, score: 1, upvotes: 1, @@ -294,6 +307,7 @@ mod tests { community_name: community_name.to_owned(), community_removed: false, community_deleted: false, + community_nsfw: false, number_of_comments: 0, score: 1, upvotes: 1, diff --git a/server/src/db/user.rs b/server/src/db/user.rs index aed5e8909..b794524cb 100644 --- a/server/src/db/user.rs +++ b/server/src/db/user.rs @@ -18,7 +18,8 @@ pub struct User_ { pub admin: bool, pub banned: bool, pub published: chrono::NaiveDateTime, - pub updated: Option + pub updated: Option, + pub show_nsfw: bool, } #[derive(Insertable, AsChangeset, Clone)] @@ -31,7 +32,8 @@ pub struct UserForm { pub admin: bool, pub banned: bool, pub email: Option, - pub updated: Option + pub updated: Option, + pub show_nsfw: bool, } impl Crud for User_ { @@ -77,6 +79,7 @@ pub struct Claims { pub id: i32, pub username: String, pub iss: String, + pub show_nsfw: bool, } impl Claims { @@ -96,6 +99,7 @@ impl User_ { id: self.id, username: self.name.to_owned(), iss: self.fedi_name.to_owned(), + show_nsfw: self.show_nsfw, }; encode(&Header::default(), &my_claims, Settings::get().jwt_secret.as_ref()).unwrap() } @@ -133,7 +137,8 @@ mod tests { email: None, admin: false, banned: false, - updated: None + updated: None, + show_nsfw: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -149,7 +154,8 @@ mod tests { admin: false, banned: false, published: inserted_user.published, - updated: None + updated: None, + show_nsfw: false, }; let read_user = User_::read(&conn, inserted_user.id).unwrap(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 143be36ea..04076771b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,3 +1,4 @@ +#![recursion_limit = "512"] #[macro_use] pub extern crate strum_macros; #[macro_use] pub extern crate lazy_static; #[macro_use] pub extern crate failure; diff --git a/server/src/schema.rs b/server/src/schema.rs index 27bc3f941..635c8c467 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -52,6 +52,7 @@ table! { published -> Timestamp, updated -> Nullable, deleted -> Bool, + nsfw -> Bool, } } @@ -185,6 +186,7 @@ table! { published -> Timestamp, updated -> Nullable, deleted -> Bool, + nsfw -> Bool, } } @@ -240,6 +242,7 @@ table! { banned -> Bool, published -> Timestamp, updated -> Nullable, + show_nsfw -> Bool, } } diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 7088f3d5a..64f94f4cd 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -134,17 +134,19 @@ impl ChatServer { use crate::db::*; use crate::db::post_view::*; let conn = establish_connection(); - let posts = PostView::list(&conn, - PostListingType::Community, - &SortType::New, - Some(*community_id), - None, - None, - None, - false, - false, - None, - Some(9999))?; + let posts = PostView::list( + &conn, + PostListingType::Community, + &SortType::New, + Some(*community_id), + None, + None, + None, + false, + false, + false, + None, + Some(9999))?; for post in posts { self.send_room_message(&post.id, message, skip_id); } @@ -303,6 +305,11 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { + let save_user_settings: SaveUserSettings = serde_json::from_str(data)?; + let res = Oper::new(user_operation, save_user_settings).perform()?; + Ok(serde_json::to_string(&res)?) + }, UserOperation::AddAdmin => { let add_admin: AddAdmin = serde_json::from_str(data)?; let res = Oper::new(user_operation, add_admin).perform()?; diff --git a/ui/package.json b/ui/package.json index d86725f25..523700a23 100644 --- a/ui/package.json +++ b/ui/package.json @@ -41,6 +41,6 @@ "fuse-box": "^3.1.3", "ts-transform-classcat": "^0.0.2", "ts-transform-inferno": "^4.0.2", - "typescript": "^3.3.3333" + "typescript": "^3.5.3" } } diff --git a/ui/src/components/community-form.tsx b/ui/src/components/community-form.tsx index b039fb4d9..833d8a3f0 100644 --- a/ui/src/components/community-form.tsx +++ b/ui/src/components/community-form.tsx @@ -30,7 +30,8 @@ export class CommunityForm extends Component +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
-
@@ -181,6 +189,11 @@ export class Login extends Component { i.setState(i.state); } + handleRegisterShowNsfwChange(i: Login, event: any) { + i.state.registerForm.show_nsfw = event.target.checked; + i.setState(i.state); + } + parseMessage(msg: any) { let op: UserOperation = msgOp(msg); if (msg.error) { diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 79d37b426..704b1cdd9 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -31,6 +31,7 @@ export class PostForm extends Component { private emptyState: PostFormState = { postForm: { name: null, + nsfw: false, auth: null, community_id: null, creator_id: (UserService.Instance.user) ? UserService.Instance.user.id : null, @@ -54,6 +55,7 @@ export class PostForm extends Component { edit_id: this.props.post.id, creator_id: this.props.post.creator_id, url: this.props.post.url, + nsfw: this.props.post.nsfw, auth: null } } @@ -126,6 +128,14 @@ export class PostForm extends Component { } +
+
+
+ + +
+
+
{this.userInfo()} + {this.isCurrentUser && + this.userSettings() + } {this.moderates()} {this.follows()}
@@ -219,7 +233,7 @@ export class User extends Component { return (
{user.name}
-
{i18n.t('joined')}
+
{i18n.t('joined')}
@@ -235,6 +249,30 @@ export class User extends Component { ) } + userSettings() { + return ( +
+
#
+
+
+
+
+ + +
+
+
+
+
+ +
+
+ +
+ ) + } + moderates() { return (
@@ -329,6 +367,19 @@ export class User extends Component { i.refetch(); } + handleUserSettingsShowNsfwChange(i: User, event: any) { + i.state.userSettingsForm.show_nsfw = event.target.checked; + i.setState(i.state); + } + + handleUserSettingsSubmit(i: User, event: any) { + event.preventDefault(); + i.state.userSettingsLoading = true; + i.setState(i.state); + + WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm); + } + parseMessage(msg: any) { console.log(msg); let op: UserOperation = msgOp(msg); @@ -343,6 +394,9 @@ export class User extends Component { this.state.moderates = res.moderates; this.state.posts = res.posts; this.state.loading = false; + if (this.isCurrentUser) { + this.state.userSettingsForm.show_nsfw = UserService.Instance.user.show_nsfw; + } document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`; window.scrollTo(0,0); this.setState(this.state); @@ -378,6 +432,12 @@ export class User extends Component { if (res.comment.my_vote !== null) found.my_vote = res.comment.my_vote; this.setState(this.state); + } else if (op == UserOperation.SaveUserSettings) { + this.state = this.emptyState; + this.state.userSettingsLoading = false; + this.setState(this.state); + let res: LoginResponse = msg; + UserService.Instance.login(res); } } } diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 59f4ba1c9..ebd42340d 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -1,5 +1,5 @@ export enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead + Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead, SaveUserSettings } export enum CommentSortType { @@ -22,6 +22,7 @@ export interface User { id: number; iss: string; username: string; + show_nsfw: boolean; } export interface UserView { @@ -53,6 +54,7 @@ export interface Community { creator_id: number; removed: boolean; deleted: boolean; + nsfw: boolean; published: string; updated?: string; creator_name: string; @@ -74,11 +76,14 @@ export interface Post { removed: boolean; deleted: boolean; locked: boolean; + nsfw: boolean; published: string; updated?: string; creator_name: string; community_name: string; community_removed: boolean; + community_deleted: boolean; + community_nsfw: boolean; number_of_comments: number; score: number; upvotes: number; @@ -334,6 +339,7 @@ export interface RegisterForm { password: string; password_verify: string; admin: boolean; + show_nsfw: boolean; } export interface LoginResponse { @@ -341,7 +347,10 @@ export interface LoginResponse { jwt: string; } - +export interface UserSettingsForm { + show_nsfw: boolean; + auth: string; +} export interface CommunityForm { name: string; @@ -351,6 +360,7 @@ export interface CommunityForm { edit_id?: number; removed?: boolean; deleted?: boolean; + nsfw: boolean; reason?: string; expires?: number; auth?: string; @@ -396,6 +406,7 @@ export interface PostForm { creator_id: number; removed?: boolean; deleted?: boolean; + nsfw: boolean; locked?: boolean; reason?: string; auth: string; diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index c192c2b77..c34b6b3c1 100644 --- a/ui/src/services/WebSocketService.ts +++ b/ui/src/services/WebSocketService.ts @@ -1,5 +1,5 @@ import { wsUri } from '../env'; -import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm, SearchForm } from '../interfaces'; +import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm, SearchForm, UserSettingsForm } from '../interfaces'; import { webSocket } from 'rxjs/webSocket'; import { Subject } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; @@ -184,6 +184,11 @@ export class WebSocketService { this.subject.next(this.wsSendWrapper(UserOperation.MarkAllAsRead, form)); } + public saveUserSettings(userSettingsForm: UserSettingsForm) { + this.setAuth(userSettingsForm); + this.subject.next(this.wsSendWrapper(UserOperation.SaveUserSettings, userSettingsForm)); + } + private wsSendWrapper(op: UserOperation, data: any) { let send = { op: UserOperation[op], data: data }; console.log(send); diff --git a/ui/src/translations/en.ts b/ui/src/translations/en.ts index 7c2b184f1..1f79bef2f 100644 --- a/ui/src/translations/en.ts +++ b/ui/src/translations/en.ts @@ -29,6 +29,7 @@ export const en = { mod: 'mod', mods: 'mods', moderates: 'Moderates', + settings: 'Settings', remove_as_mod: 'remove as mod', appoint_as_mod: 'appoint as mod', modlog: 'Modlog', @@ -112,6 +113,8 @@ export const en = { setup_admin: 'Set Up Site Administrator', your_site: 'your site', modified: 'modified', + nsfw: 'NSFW', + show_nsfw: 'Show NSFW content', sponsors: 'Sponsors', sponsors_of_lemmy: 'Sponsors of Lemmy', sponsor_message: 'Lemmy is free, <1>open-source software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:', diff --git a/ui/tslint.json b/ui/tslint.json index d3e7a8a97..938502e47 100644 --- a/ui/tslint.json +++ b/ui/tslint.json @@ -2,7 +2,7 @@ "extends": "tslint:recommended", "rules": { "forin": false, - "indent": [ true, "tabs" ], + "indent": [ true, "spaces" ], "interface-name": false, "ban-types": true, "max-classes-per-file": true, diff --git a/ui/yarn.lock b/ui/yarn.lock index f47c16c45..f31f45ae5 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -2773,7 +2773,7 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== -typescript@^3.3.3333: +typescript@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
#