]> Untitled Git - lemmy.git/commitdiff
Squashed commit of the following:
authorDessalines <tyhou13@gmx.com>
Wed, 14 Aug 2019 02:52:43 +0000 (19:52 -0700)
committerDessalines <tyhou13@gmx.com>
Wed, 14 Aug 2019 02:52:43 +0000 (19:52 -0700)
commit ecd6c5a2f47cbbb2fc4bf482fadd78380303a904
Author: Dessalines <happydooby@gmail.com>
Date:   Tue Aug 13 19:49:38 2019 -0700

    Adding some docs

commit 3babd09affb1920da3d0a0ceb7e24c8aeeb9cf1a
Author: Dessalines <happydooby@gmail.com>
Date:   Tue Aug 13 19:28:46 2019 -0700

    Adding save user settings

commit 6e8da9cc9e522d0da668bfa31944c3348cc79620
Merge: 3246d5d c148eef
Author: Dessalines <happydooby@gmail.com>
Date:   Tue Aug 13 17:26:25 2019 -0700

    Merge branch 'dev' into nsfw

commit b3d4a5c4ce441bcc664704aba44cedb51d887599
Author: Dessalines <happydooby@gmail.com>
Date:   Sun Aug 11 20:55:09 2019 -0700

    nsfw mostly done, except for settings page.

31 files changed:
README.md
docker_db_backup.sh
docs/api.md
server/migrations/2019-08-11-000918_add_nsfw_columns/down.sql [new file with mode: 0644]
server/migrations/2019-08-11-000918_add_nsfw_columns/up.sql [new file with mode: 0644]
server/src/api/community.rs
server/src/api/mod.rs
server/src/api/post.rs
server/src/api/site.rs
server/src/api/user.rs
server/src/db/community.rs
server/src/db/community_view.rs
server/src/db/post.rs
server/src/db/post_view.rs
server/src/db/user.rs
server/src/lib.rs
server/src/schema.rs
server/src/websocket/server.rs
ui/package.json
ui/src/components/community-form.tsx
ui/src/components/community.tsx
ui/src/components/login.tsx
ui/src/components/post-form.tsx
ui/src/components/post-listing.tsx
ui/src/components/setup.tsx
ui/src/components/user.tsx
ui/src/interfaces.ts
ui/src/services/WebSocketService.ts
ui/src/translations/en.ts
ui/tslint.json
ui/yarn.lock

index 295c436f6b2edf3115ab4f4fb89795bba399cefe..9862885c4e9409b6d9b510bb59c1b4bc37079507 100644 (file)
--- 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.
index 5b87b818b2f933a7fd4cdcdd7240d7bcb839b17e..e361da1941b17d473b4f3b63543baf81866bc1ee 100755 (executable)
@@ -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
index 744de5f3b9a49ca3766341ad71ed47a2425e63ad..90b931e6190e15292b5d5ecb358463b973aa8a84 100644 (file)
@@ -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<PostView>,
 }
 ```
-
+#### 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 (file)
index 0000000..2eefece
--- /dev/null
@@ -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 (file)
index 0000000..cc1e007
--- /dev/null
@@ -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
+;
index ca73de49c9d458939289f4ef16fecc9b7d6e1e0c..7405848882108537f3ad79e4e7c3c43bb6abf462 100644 (file)
@@ -22,7 +22,8 @@ pub struct CreateCommunity {
   name: String,
   title: String,
   description: Option<String>,
-  category_id: i32 ,
+  category_id: i32,
+  nsfw: bool,
   auth: String
 }
 
@@ -86,6 +87,7 @@ pub struct EditCommunity {
   category_id: i32,
   removed: Option<bool>,
   deleted: Option<bool>,
+  nsfw: bool,
   reason: Option<String>,
   expires: Option<i64>,
   auth: String
@@ -194,6 +196,7 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
       creator_id: user_id,
       removed: None,
       deleted: None,
+      nsfw: data.nsfw,
       updated: None,
     };
 
@@ -291,6 +294,7 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
       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<ListCommunitiesResponse> for Oper<ListCommunities> {
     let data: &ListCommunities = &self.data;
     let conn = establish_connection();
 
-    let user_id: Option<i32> = match &data.auth {
+    let user_claims: Option<Claims> = 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> = CommunityView::list(&conn, &sort, user_id, None, data.page, data.limit)?;
+    let communities: Vec<CommunityView> = CommunityView::list(
+      &conn, 
+      &sort, 
+      user_id, 
+      show_nsfw, 
+      None, 
+      data.page, 
+      data.limit)?;
 
     // Return the jwt
     Ok(
index e10770b4df8bf4015f4827925f3703c0659ab716..3a4a08658a0dc31e32d95aa4ee2d86b827fd06b4 100644 (file)
@@ -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)]
index df6ea852f8b31a510585a7831ca1636cd4fa2276..35363a171679b9cd9c01141e5b85ff309245d7c4 100644 (file)
@@ -6,6 +6,7 @@ pub struct CreatePost {
   name: String,
   url: Option<String>,
   body: Option<String>,
+  nsfw: bool,
   community_id: i32,
   auth: String
 }
@@ -73,6 +74,7 @@ pub struct EditPost {
   body: Option<String>,
   removed: Option<bool>,
   deleted: Option<bool>,
+  nsfw: bool,
   locked: Option<bool>,
   reason: Option<String>,
   auth: String
@@ -123,6 +125,7 @@ impl Perform<PostResponse> for Oper<CreatePost> {
       creator_id: user_id,
       removed: None,
       deleted: None,
+      nsfw: data.nsfw,
       locked: None,
       updated: None
     };
@@ -219,40 +222,50 @@ impl Perform<GetPostsResponse> for Oper<GetPosts> {
     let data: &GetPosts = &self.data;
     let conn = establish_connection();
 
-    let user_id: Option<i32> = match &data.auth {
+    let user_claims: Option<Claims> = 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<PostResponse> for Oper<EditPost> {
       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())
     };
index 09af742fae5327a371210cb699621ed984924f94..8f094aace850fe1425132f744f6f23c0d5e828b3 100644 (file)
@@ -277,6 +277,8 @@ impl Perform<SearchResponse> for Oper<Search> {
     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<SearchResponse> for Oper<Search> {
           None,
           Some(data.q.to_owned()),
           None, 
+          true,
           false, 
           false, 
           data.page, 
@@ -309,6 +312,7 @@ impl Perform<SearchResponse> for Oper<Search> {
           &conn, 
           &sort, 
           None, 
+          true,
           Some(data.q.to_owned()),
           data.page, 
           data.limit)?;
@@ -330,6 +334,7 @@ impl Perform<SearchResponse> for Oper<Search> {
           None,
           Some(data.q.to_owned()),
           None, 
+          true,
           false, 
           false, 
           data.page, 
@@ -348,6 +353,7 @@ impl Perform<SearchResponse> for Oper<Search> {
           &conn, 
           &sort, 
           None, 
+          true,
           Some(data.q.to_owned()),
           data.page, 
           data.limit)?;
index 5d5f1a6be22d872ac4a1ae273e1c8626a4e758be..2a6c214a788111c5307edb8109def94eeddc4a52 100644 (file)
@@ -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<LoginResponse> for Oper<Register> {
       updated: None,
       admin: data.admin,
       banned: false,
+      show_nsfw: data.show_nsfw,
     };
 
     // Create the user
@@ -170,6 +178,7 @@ impl Perform<LoginResponse> for Oper<Register> {
           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<LoginResponse> for Oper<Register> {
   }
 }
 
+impl Perform<LoginResponse> for Oper<SaveUserSettings> {
+  fn perform(&self) -> Result<LoginResponse, Error> {
+    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<GetUserDetailsResponse> for Oper<GetUserDetails> {
   fn perform(&self) -> Result<GetUserDetailsResponse, Error> {
     let data: &GetUserDetails = &self.data;
     let conn = establish_connection();
 
-    let user_id: Option<i32> = match &data.auth {
+    let user_claims: Option<Claims> = 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<GetUserDetailsResponse> for Oper<GetUserDetails> {
 
     // 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<AddAdminResponse> for Oper<AddAdmin> {
       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<BanUserResponse> for Oper<BanUser> {
       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) {
index b32230b9c9410240f74476fed4dc91598cb0be1e..aa28c9c735c9f2b686d5eafb1494ac8f16c35af4 100644 (file)
@@ -14,6 +14,7 @@ pub struct Community {
   pub published: chrono::NaiveDateTime,
   pub updated: Option<chrono::NaiveDateTime>,
   pub deleted: bool,
+  pub nsfw: bool,
 }
 
 #[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
@@ -27,6 +28,7 @@ pub struct CommunityForm {
   pub removed: Option<bool>,
   pub updated: Option<chrono::NaiveDateTime>,
   pub deleted: Option<bool>,
+  pub nsfw: bool,
 }
 
 impl Crud<CommunityForm> 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,
index 6249090d77978dfa68da64c8b7ad62c7386c21ca..88ab10afe6ce0bc1493c7202d32b876dfa62ed91 100644 (file)
@@ -12,6 +12,7 @@ table! {
     published -> Timestamp,
     updated -> Nullable<Timestamp>,
     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<chrono::NaiveDateTime>,
   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::<Self>(conn)
   }
 
-  pub fn list(conn: &PgConnection, 
-              sort: &SortType, 
-              from_user_id: Option<i32>, 
-              search_term: Option<String>,
-              page: Option<i64>,
-              limit: Option<i64>,
-              ) -> Result<Vec<Self>, Error> {
+  pub fn list(
+    conn: &PgConnection, 
+    sort: &SortType, 
+    from_user_id: Option<i32>, 
+    show_nsfw: bool,
+    search_term: Option<String>,
+    page: Option<i64>,
+    limit: Option<i64>,
+    ) -> Result<Vec<Self>, 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)
index d8fd27b0a92b43d4fbd456812e2699583d2502e3..6a8bf26dde411738fa24d3ed9f371a2ccbd9a1f8 100644 (file)
@@ -15,6 +15,7 @@ pub struct Post {
   pub published: chrono::NaiveDateTime,
   pub updated: Option<chrono::NaiveDateTime>,
   pub deleted: bool,
+  pub nsfw: bool,
 }
 
 #[derive(Insertable, AsChangeset, Clone)]
@@ -29,6 +30,7 @@ pub struct PostForm {
   pub locked: Option<bool>,
   pub updated: Option<chrono::NaiveDateTime>,
   pub deleted: Option<bool>,
+  pub nsfw: bool,
 }
 
 impl Crud<PostForm> 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
     };
index bec00b982bce0b3b8b640b789d59d46d7da40af8..736862665b30ad3e5b397cbafd3ed1a07b3ff20a 100644 (file)
@@ -19,10 +19,12 @@ table! {
     published -> Timestamp,
     updated -> Nullable<Timestamp>,
     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<chrono::NaiveDateTime>,
   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<i32>, 
-              for_creator_id: Option<i32>, 
-              search_term: Option<String>,
-              my_user_id: Option<i32>, 
-              saved_only: bool,
-              unread_only: bool,
-              page: Option<i64>,
-              limit: Option<i64>,
-              ) -> Result<Vec<Self>, Error> {
+  pub fn list(
+    conn: &PgConnection, 
+    type_: PostListingType, 
+    sort: &SortType, 
+    for_community_id: Option<i32>, 
+    for_creator_id: Option<i32>, 
+    search_term: Option<String>,
+    my_user_id: Option<i32>, 
+    show_nsfw: bool,
+    saved_only: bool,
+    unread_only: bool,
+    page: Option<i64>,
+    limit: Option<i64>,
+    ) -> Result<Vec<Self>, 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,
index aed5e89092732cb3c9575fc6e32f4f2c5f7fa96e..b794524cb0ca49334e8c44f44d5077db7d9810cb 100644 (file)
@@ -18,7 +18,8 @@ pub struct User_ {
   pub admin: bool,
   pub banned: bool,
   pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub show_nsfw: bool,
 }
 
 #[derive(Insertable, AsChangeset, Clone)]
@@ -31,7 +32,8 @@ pub struct UserForm {
     pub admin: bool,
     pub banned: bool,
     pub email: Option<String>,
-    pub updated: Option<chrono::NaiveDateTime>
+    pub updated: Option<chrono::NaiveDateTime>,
+    pub show_nsfw: bool,
 }
 
 impl Crud<UserForm> 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();
index 143be36ea52b37267336dbb3733414a87ae1bf73..04076771b5e69725c7b5eb0cae845dcbeb7efd31 100644 (file)
@@ -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;
index 27bc3f941c901f8b44c431597a052441cd264559..635c8c467bbb5f1451a38203320c47f0a632c9bb 100644 (file)
@@ -52,6 +52,7 @@ table! {
         published -> Timestamp,
         updated -> Nullable<Timestamp>,
         deleted -> Bool,
+        nsfw -> Bool,
     }
 }
 
@@ -185,6 +186,7 @@ table! {
         published -> Timestamp,
         updated -> Nullable<Timestamp>,
         deleted -> Bool,
+        nsfw -> Bool,
     }
 }
 
@@ -240,6 +242,7 @@ table! {
         banned -> Bool,
         published -> Timestamp,
         updated -> Nullable<Timestamp>,
+        show_nsfw -> Bool,
     }
 }
 
index 7088f3d5ac99d333456e1a8d78d57161cfa2ae00..64f94f4cd4fc5242e167577be4bba060aa518b24 100644 (file)
@@ -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<Str
       let res = Oper::new(user_operation, get_user_details).perform()?;
       Ok(serde_json::to_string(&res)?)
     },
+    UserOperation::SaveUserSettings => {
+      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()?;
index d86725f254d45b35a846d9a5bf197820828cdc26..523700a23b8113c26db66aef83fe5f7dcdce2275 100644 (file)
@@ -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"
   }
 }
index b039fb4d9ee5d4d6fadeee6cd471a71ef6b39d1b..833d8a3f0acf3aaeefcbe3352142abb5b6cd8570 100644 (file)
@@ -30,7 +30,8 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
     communityForm: {
       name: null,
       title: null,
-      category_id: null
+      category_id: null,
+      nsfw: false,
     },
     categories: [],
     loading: false
@@ -48,6 +49,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
         category_id: this.props.community.category_id,
         description: this.props.community.description,
         edit_id: this.props.community.id,
+        nsfw: this.props.community.nsfw,
         auth: null
       }
     }
@@ -103,6 +105,14 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
             </select>
           </div>
         </div>
+        <div class="form-group row">
+          <div class="col-12">
+            <div class="form-check">
+              <input class="form-check-input" type="checkbox" checked={this.state.communityForm.nsfw} onChange={linkEvent(this, this.handleCommunityNsfwChange)}/>
+              <label class="form-check-label"><T i18nKey="nsfw">#</T></label>
+            </div>
+          </div>
+        </div>
         <div class="form-group row">
           <div class="col-12">
             <button type="submit" class="btn btn-secondary mr-2">
@@ -147,6 +157,11 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
     i.setState(i.state);
   }
 
+  handleCommunityNsfwChange(i: CommunityForm, event: any) {
+    i.state.communityForm.nsfw = event.target.checked;
+    i.setState(i.state);
+  }
+
   handleCancel(i: CommunityForm) {
     i.props.onCancel();
   }
index 480b909ea3b04ae519e6c1f79ccc54ab351f787d..b9f9c8b2f3c5db8c03e3f8dfb96911a94d919e6d 100644 (file)
@@ -37,6 +37,7 @@ export class Community extends Component<any, State> {
       number_of_comments: null,
       published: null,
       removed: null,
+      nsfw: false,
       deleted: null,
     },
     moderators: [],
@@ -105,6 +106,9 @@ export class Community extends Component<any, State> {
             {this.state.community.removed &&
               <small className="ml-2 text-muted font-italic"><T i18nKey="removed">#</T></small>
             }
+            {this.state.community.nsfw &&
+              <small className="ml-2 text-muted font-italic"><T i18nKey="nsfw">#</T></small>
+            }
           </h5>
           {this.selects()}
           <PostListings posts={this.state.posts} />
index e7af89ca981133cd93be790fa3b3935eb90428df..66962accf113520215e0c5487b8e3514124da029 100644 (file)
@@ -28,6 +28,7 @@ export class Login extends Component<any, State> {
       password: undefined,
       password_verify: undefined,
       admin: false,
+      show_nsfw: false,
     },
     loginLoading: false,
     registerLoading: false,
@@ -125,11 +126,18 @@ export class Login extends Component<any, State> {
             <input type="password" value={this.state.registerForm.password_verify} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} class="form-control" required />
           </div>
         </div>
+        <div class="form-group row">
+          <div class="col-sm-10">
+            <div class="form-check">
+              <input class="form-check-input" type="checkbox" checked={this.state.registerForm.show_nsfw} onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}/>
+              <label class="form-check-label"><T i18nKey="show_nsfw">#</T></label>
+            </div>
+          </div>
+        </div>
         <div class="form-group row">
           <div class="col-sm-10">
             <button type="submit" class="btn btn-secondary">{this.state.registerLoading ? 
             <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : i18n.t('sign_up')}</button>
-
           </div>
         </div>
       </form>
@@ -181,6 +189,11 @@ export class Login extends Component<any, State> {
     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) {
index 79d37b4266f8addda1a28b9e1d6e7e517c1d6294..704b1cdd99e234779d196fdad9cb5e08806e856e 100644 (file)
@@ -31,6 +31,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
   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<PostFormProps, PostFormState> {
         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<PostFormProps, PostFormState> {
             </div>
             </div>
             }
+          <div class="form-group row">
+            <div class="col-sm-10">
+              <div class="form-check">
+                <input class="form-check-input" type="checkbox" checked={this.state.postForm.nsfw} onChange={linkEvent(this, this.handlePostNsfwChange)}/>
+                <label class="form-check-label"><T i18nKey="nsfw">#</T></label>
+              </div>
+            </div>
+          </div>
           <div class="form-group row">
             <div class="col-sm-10">
               <button type="submit" class="btn btn-secondary mr-2">
@@ -196,6 +206,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
     i.setState(i.state);
   }
 
+  handlePostNsfwChange(i: PostForm, event: any) {
+    i.state.postForm.nsfw = event.target.checked;
+    i.setState(i.state);
+  }
+
   handleCancel(i: PostForm) {
     i.props.onCancel();
   }
index ff70783cb9237027a9a9731642d408821654bc66..ca03b9801366c89026b27b6fa1fb869613bc523f 100644 (file)
@@ -93,6 +93,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             {post.locked &&
               <small className="ml-2 text-muted font-italic"><T i18nKey="locked">#</T></small>
             }
+            {post.nsfw &&
+              <small className="ml-2 text-muted font-italic"><T i18nKey="nsfw">#</T></small>
+            }
             { post.url && isImage(post.url) && 
               <>
                 { !this.state.imageExpanded
@@ -251,6 +254,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
       edit_id: i.props.post.id,
       creator_id: i.props.post.creator_id,
       deleted: !i.props.post.deleted,
+      nsfw: i.props.post.nsfw,
       auth: null
     };
     WebSocketService.Instance.editPost(deleteForm);
@@ -285,6 +289,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
       creator_id: i.props.post.creator_id,
       removed: !i.props.post.removed,
       reason: i.state.removeReason,
+      nsfw: i.props.post.nsfw,
       auth: null,
     };
     WebSocketService.Instance.editPost(form);
@@ -299,6 +304,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
       community_id: i.props.post.community_id,
       edit_id: i.props.post.id,
       creator_id: i.props.post.creator_id,
+      nsfw: i.props.post.nsfw,
       locked: !i.props.post.locked,
       auth: null,
     };
index f11dc14e0d07b64a238b8d527b86258bc454ef19..24a5f2d68961709a73fd49587e6307693c5c8954 100644 (file)
@@ -23,6 +23,7 @@ export class Setup extends Component<any, State> {
       password: undefined,
       password_verify: undefined,
       admin: true,
+      show_nsfw: true,
     },
     doneRegisteringUser: false,
     userLoading: false,
index c6a70560f497b74eeccbbe2f0795b12866fbf51d..39a13e16218033d34af9b78275cca7a29734c804 100644 (file)
@@ -2,8 +2,8 @@ import { Component, linkEvent } from 'inferno';
 import { Link } from 'inferno-router';
 import { Subscription } from "rxjs";
 import { retryWhen, delay, take } from 'rxjs/operators';
-import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse } from '../interfaces';
-import { WebSocketService } from '../services';
+import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse, UserSettingsForm, LoginResponse } from '../interfaces';
+import { WebSocketService, UserService } from '../services';
 import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter } from '../utils';
 import { PostListing } from './post-listing';
 import { CommentNodes } from './comment-nodes';
@@ -28,6 +28,8 @@ interface UserState {
   sort: SortType;
   page: number;
   loading: boolean;
+  userSettingsForm: UserSettingsForm;
+  userSettingsLoading: boolean;
 }
 
 export class User extends Component<any, UserState> {
@@ -54,6 +56,11 @@ export class User extends Component<any, UserState> {
     view: this.getViewFromProps(this.props),
     sort: this.getSortTypeFromProps(this.props),
     page: this.getPageFromProps(this.props),
+    userSettingsForm: {
+      show_nsfw: null,
+      auth: null,
+    },
+    userSettingsLoading: null,
   }
 
   constructor(props: any, context: any) {
@@ -75,6 +82,10 @@ export class User extends Component<any, UserState> {
     this.refetch();
   }
 
+  get isCurrentUser() {
+    return UserService.Instance.user && UserService.Instance.user.id == this.state.user.id;
+  }
+
   getViewFromProps(props: any): View {
     return (props.match.params.view) ? 
       View[capitalizeFirstLetter(props.match.params.view)] : 
@@ -131,6 +142,9 @@ export class User extends Component<any, UserState> {
           </div>
           <div class="col-12 col-md-3">
             {this.userInfo()}
+            {this.isCurrentUser &&
+              this.userSettings()
+            }
             {this.moderates()}
             {this.follows()}
           </div>
@@ -219,7 +233,7 @@ export class User extends Component<any, UserState> {
     return (
       <div>
         <h5>{user.name}</h5>
-        <div>{i18n.t('joined')}<MomentTime data={user} /></div>
+        <div>{i18n.t('joined')} <MomentTime data={user} /></div>
         <table class="table table-bordered table-sm mt-2">
           <tr>
             <td><T i18nKey="number_of_points" interpolation={{count: user.post_score}}>#</T></td>
@@ -235,6 +249,30 @@ export class User extends Component<any, UserState> {
     )
   }
 
+  userSettings() {  
+    return (
+      <div>
+        <h5><T i18nKey="settings">#</T></h5>
+        <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
+          <div class="form-group row">
+            <div class="col-12">
+              <div class="form-check">
+                <input class="form-check-input" type="checkbox" checked={this.state.userSettingsForm.show_nsfw} onChange={linkEvent(this, this.handleUserSettingsShowNsfwChange)}/>
+                <label class="form-check-label"><T i18nKey="show_nsfw">#</T></label>
+              </div>
+            </div>
+          </div>
+          <div class="form-group row">
+            <div class="col-12">
+              <button type="submit" class="btn btn-secondary">{this.state.userSettingsLoading ? 
+              <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : capitalizeFirstLetter(i18n.t('save'))}</button>
+            </div>
+          </div>
+        </form>
+      </div>
+    )
+  }
+
   moderates() {
     return (
       <div>
@@ -329,6 +367,19 @@ export class User extends Component<any, UserState> {
     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<any, UserState> {
       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<any, UserState> {
       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);
     }
   }
 }
index 59f4ba1c91fef07ef2a9c9494d90c5b05037892e..ebd42340d193034aaf89aad017e9b4b6066e3da3 100644 (file)
@@ -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;
index c192c2b77add21af3462149398dad568674f874d..c34b6b3c15e0bde6bf28d0bd9e05c23cae95fe24 100644 (file)
@@ -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);
index 7c2b184f18b7624a657eaa297fe372f61cfe021d..1f79bef2f23309438147a05a41e56120b701b046 100644 (file)
@@ -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</1> 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:',
index d3e7a8a97242ade6a3f24cb1b53df2426dcb72a0..938502e473c519ba0903f21928b0fc46be64589b 100644 (file)
@@ -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,
index f47c16c45912d5edf90adf73f97c51835d656f7d..f31f45ae5a80d124b8ddaca2f7cf5206748e5c8b 100644 (file)
@@ -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==