Squashed commit of the following:

commit ecd6c5a2f4
Author: Dessalines <happydooby@gmail.com>
Date:   Tue Aug 13 19:49:38 2019 -0700

    Adding some docs

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

    Adding save user settings

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

    Merge branch 'dev' into nsfw

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

    nsfw mostly done, except for settings page.
pull/722/head
Dessalines 5 years ago
parent ad4dbbcd77
commit d7ab85ab70

@ -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.

@ -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

@ -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

@ -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
;

@ -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
;

@ -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(

@ -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)]

@ -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())
};

@ -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)?;

@ -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) {

@ -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,

@ -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)

@ -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
};

@ -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,

@ -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();

@ -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;

@ -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,
}
}

@ -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()?;

@ -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"
}
}

@ -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();
}

@ -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} />

@ -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) {

@ -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();
}

@ -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,
};

@ -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,

@ -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);
}
}
}

@ -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;

@ -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);

@ -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:',

@ -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,

@ -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==

Loading…
Cancel
Save