nsfw mostly done, except for settings page.

pull/283/head
Dessalines 2019-08-11 20:55:09 -07:00
parent d8361f2e00
commit b3d4a5c4ce
21 changed files with 423 additions and 99 deletions

View File

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

View File

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

View File

@ -22,7 +22,8 @@ pub struct CreateCommunity {
name: String, name: String,
title: String, title: String,
description: Option<String>, description: Option<String>,
category_id: i32 , category_id: i32,
nsfw: bool,
auth: String auth: String
} }
@ -86,6 +87,7 @@ pub struct EditCommunity {
category_id: i32, category_id: i32,
removed: Option<bool>, removed: Option<bool>,
deleted: Option<bool>, deleted: Option<bool>,
nsfw: bool,
reason: Option<String>, reason: Option<String>,
expires: Option<i64>, expires: Option<i64>,
auth: String auth: String
@ -194,6 +196,7 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
creator_id: user_id, creator_id: user_id,
removed: None, removed: None,
deleted: None, deleted: None,
nsfw: data.nsfw,
updated: None, updated: None,
}; };
@ -291,6 +294,7 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
creator_id: user_id, creator_id: user_id,
removed: data.removed.to_owned(), removed: data.removed.to_owned(),
deleted: data.deleted.to_owned(), deleted: data.deleted.to_owned(),
nsfw: data.nsfw,
updated: Some(naive_now()) updated: Some(naive_now())
}; };
@ -333,22 +337,38 @@ impl Perform<ListCommunitiesResponse> for Oper<ListCommunities> {
let data: &ListCommunities = &self.data; let data: &ListCommunities = &self.data;
let conn = establish_connection(); let conn = establish_connection();
let user_id: Option<i32> = match &data.auth { let user_claims: Option<Claims> = match &data.auth {
Some(auth) => { Some(auth) => {
match Claims::decode(&auth) { match Claims::decode(&auth) {
Ok(claims) => { Ok(claims) => {
let user_id = claims.claims.id; Some(claims.claims)
Some(user_id)
} }
Err(_e) => None Err(_e) => None
} }
} }
None => 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 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 // Return the jwt
Ok( Ok(

View File

@ -6,6 +6,7 @@ pub struct CreatePost {
name: String, name: String,
url: Option<String>, url: Option<String>,
body: Option<String>, body: Option<String>,
nsfw: bool,
community_id: i32, community_id: i32,
auth: String auth: String
} }
@ -73,6 +74,7 @@ pub struct EditPost {
body: Option<String>, body: Option<String>,
removed: Option<bool>, removed: Option<bool>,
deleted: Option<bool>, deleted: Option<bool>,
nsfw: bool,
locked: Option<bool>, locked: Option<bool>,
reason: Option<String>, reason: Option<String>,
auth: String auth: String
@ -123,6 +125,7 @@ impl Perform<PostResponse> for Oper<CreatePost> {
creator_id: user_id, creator_id: user_id,
removed: None, removed: None,
deleted: None, deleted: None,
nsfw: data.nsfw,
locked: None, locked: None,
updated: None updated: None
}; };
@ -219,40 +222,50 @@ impl Perform<GetPostsResponse> for Oper<GetPosts> {
let data: &GetPosts = &self.data; let data: &GetPosts = &self.data;
let conn = establish_connection(); let conn = establish_connection();
let user_id: Option<i32> = match &data.auth { let user_claims: Option<Claims> = match &data.auth {
Some(auth) => { Some(auth) => {
match Claims::decode(&auth) { match Claims::decode(&auth) {
Ok(claims) => { Ok(claims) => {
let user_id = claims.claims.id; Some(claims.claims)
Some(user_id)
} }
Err(_e) => None Err(_e) => None
} }
} }
None => 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 type_ = PostListingType::from_str(&data.type_)?;
let sort = SortType::from_str(&data.sort)?; let sort = SortType::from_str(&data.sort)?;
let posts = match PostView::list(&conn, let posts = match PostView::list(
type_, &conn,
&sort, type_,
data.community_id, &sort,
None, data.community_id,
None, None,
user_id, None,
false, user_id,
false, show_nsfw,
data.page, false,
data.limit) { false,
data.page,
data.limit) {
Ok(posts) => posts, Ok(posts) => posts,
Err(_e) => { Err(_e) => {
return Err(APIError::err(&self.op, "couldnt_get_posts"))? return Err(APIError::err(&self.op, "couldnt_get_posts"))?
} }
}; };
// Return the jwt
Ok( Ok(
GetPostsResponse { GetPostsResponse {
op: self.op.to_string(), op: self.op.to_string(),
@ -381,6 +394,7 @@ impl Perform<PostResponse> for Oper<EditPost> {
community_id: data.community_id, community_id: data.community_id,
removed: data.removed.to_owned(), removed: data.removed.to_owned(),
deleted: data.deleted.to_owned(), deleted: data.deleted.to_owned(),
nsfw: data.nsfw,
locked: data.locked.to_owned(), locked: data.locked.to_owned(),
updated: Some(naive_now()) updated: Some(naive_now())
}; };

View File

@ -277,6 +277,8 @@ impl Perform<SearchResponse> for Oper<Search> {
let mut communities = Vec::new(); let mut communities = Vec::new();
let mut users = Vec::new(); let mut users = Vec::new();
// TODO no clean / non-nsfw searching rn
match type_ { match type_ {
SearchType::Posts => { SearchType::Posts => {
posts = PostView::list( posts = PostView::list(
@ -287,6 +289,7 @@ impl Perform<SearchResponse> for Oper<Search> {
None, None,
Some(data.q.to_owned()), Some(data.q.to_owned()),
None, None,
true,
false, false,
false, false,
data.page, data.page,
@ -309,6 +312,7 @@ impl Perform<SearchResponse> for Oper<Search> {
&conn, &conn,
&sort, &sort,
None, None,
true,
Some(data.q.to_owned()), Some(data.q.to_owned()),
data.page, data.page,
data.limit)?; data.limit)?;
@ -330,6 +334,7 @@ impl Perform<SearchResponse> for Oper<Search> {
None, None,
Some(data.q.to_owned()), Some(data.q.to_owned()),
None, None,
true,
false, false,
false, false,
data.page, data.page,
@ -348,6 +353,7 @@ impl Perform<SearchResponse> for Oper<Search> {
&conn, &conn,
&sort, &sort,
None, None,
true,
Some(data.q.to_owned()), Some(data.q.to_owned()),
data.page, data.page,
data.limit)?; data.limit)?;

View File

@ -15,6 +15,7 @@ pub struct Register {
password: String, password: String,
password_verify: String, password_verify: String,
admin: bool, admin: bool,
show_nsfw: bool,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -151,6 +152,7 @@ impl Perform<LoginResponse> for Oper<Register> {
updated: None, updated: None,
admin: data.admin, admin: data.admin,
banned: false, banned: false,
show_nsfw: data.show_nsfw,
}; };
// Create the user // Create the user
@ -170,6 +172,7 @@ impl Perform<LoginResponse> for Oper<Register> {
title: "The Default Community".to_string(), title: "The Default Community".to_string(),
description: Some("The Default Community".to_string()), description: Some("The Default Community".to_string()),
category_id: 1, category_id: 1,
nsfw: false,
creator_id: inserted_user.id, creator_id: inserted_user.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -224,18 +227,27 @@ impl Perform<GetUserDetailsResponse> for Oper<GetUserDetails> {
let data: &GetUserDetails = &self.data; let data: &GetUserDetails = &self.data;
let conn = establish_connection(); let conn = establish_connection();
let user_id: Option<i32> = match &data.auth { let user_claims: Option<Claims> = match &data.auth {
Some(auth) => { Some(auth) => {
match Claims::decode(&auth) { match Claims::decode(&auth) {
Ok(claims) => { Ok(claims) => {
let user_id = claims.claims.id; Some(claims.claims)
Some(user_id)
} }
Err(_e) => None Err(_e) => None
} }
} }
None => 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 //TODO add save
let sort = SortType::from_str(&data.sort)?; let sort = SortType::from_str(&data.sort)?;
@ -249,50 +261,56 @@ impl Perform<GetUserDetailsResponse> for Oper<GetUserDetails> {
// If its saved only, you don't care what creator it was // If its saved only, you don't care what creator it was
let posts = if data.saved_only { let posts = if data.saved_only {
PostView::list(&conn, PostView::list(
PostListingType::All, &conn,
&sort, PostListingType::All,
data.community_id, &sort,
None, data.community_id,
None, None,
Some(user_details_id), None,
data.saved_only, Some(user_details_id),
false, show_nsfw,
data.page, data.saved_only,
data.limit)? false,
data.page,
data.limit)?
} else { } else {
PostView::list(&conn, PostView::list(
PostListingType::All, &conn,
&sort, PostListingType::All,
data.community_id, &sort,
Some(user_details_id), data.community_id,
None, Some(user_details_id),
user_id, None,
data.saved_only, user_id,
false, show_nsfw,
data.page, data.saved_only,
data.limit)? false,
data.page,
data.limit)?
}; };
let comments = if data.saved_only { let comments = if data.saved_only {
CommentView::list(&conn, CommentView::list(
&sort, &conn,
None, &sort,
None, None,
None, None,
Some(user_details_id), None,
data.saved_only, Some(user_details_id),
data.page, data.saved_only,
data.limit)? data.page,
data.limit)?
} else { } else {
CommentView::list(&conn, CommentView::list(
&sort, &conn,
None, &sort,
Some(user_details_id), None,
None, Some(user_details_id),
user_id, None,
data.saved_only, user_id,
data.page, data.saved_only,
data.limit)? data.page,
data.limit)?
}; };
let follows = CommunityFollowerView::for_user(&conn, user_details_id)?; let follows = CommunityFollowerView::for_user(&conn, user_details_id)?;
@ -343,6 +361,7 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
updated: Some(naive_now()), updated: Some(naive_now()),
admin: data.added, admin: data.added,
banned: read_user.banned, banned: read_user.banned,
show_nsfw: read_user.show_nsfw,
}; };
match User_::update(&conn, data.user_id, &user_form) { match User_::update(&conn, data.user_id, &user_form) {
@ -402,6 +421,7 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
updated: Some(naive_now()), updated: Some(naive_now()),
admin: read_user.admin, admin: read_user.admin,
banned: data.ban, banned: data.ban,
show_nsfw: read_user.show_nsfw,
}; };
match User_::update(&conn, data.user_id, &user_form) { match User_::update(&conn, data.user_id, &user_form) {

View File

@ -14,6 +14,7 @@ pub struct Community {
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub nsfw: bool,
} }
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] #[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
@ -27,6 +28,7 @@ pub struct CommunityForm {
pub removed: Option<bool>, pub removed: Option<bool>,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>, pub deleted: Option<bool>,
pub nsfw: bool,
} }
impl Crud<CommunityForm> for Community { impl Crud<CommunityForm> for Community {
@ -240,6 +242,7 @@ mod tests {
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1, category_id: 1,
nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
updated: None, updated: None,
@ -254,6 +257,7 @@ mod tests {
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1, category_id: 1,
nsfw: false,
removed: false, removed: false,
deleted: false, deleted: false,
published: inserted_community.published, published: inserted_community.published,

View File

@ -12,6 +12,7 @@ table! {
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
deleted -> Bool, deleted -> Bool,
nsfw -> Bool,
creator_name -> Varchar, creator_name -> Varchar,
category_name -> Varchar, category_name -> Varchar,
number_of_subscribers -> BigInt, number_of_subscribers -> BigInt,
@ -84,6 +85,7 @@ pub struct CommunityView {
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub nsfw: bool,
pub creator_name: String, pub creator_name: String,
pub category_name: String, pub category_name: String,
pub number_of_subscribers: i64, pub number_of_subscribers: i64,
@ -112,13 +114,15 @@ impl CommunityView {
query.first::<Self>(conn) query.first::<Self>(conn)
} }
pub fn list(conn: &PgConnection, pub fn list(
sort: &SortType, conn: &PgConnection,
from_user_id: Option<i32>, sort: &SortType,
search_term: Option<String>, from_user_id: Option<i32>,
page: Option<i64>, show_nsfw: bool,
limit: Option<i64>, search_term: Option<String>,
) -> Result<Vec<Self>, Error> { page: Option<i64>,
limit: Option<i64>,
) -> Result<Vec<Self>, Error> {
use super::community_view::community_view::dsl::*; use super::community_view::community_view::dsl::*;
let mut query = community_view.into_boxed(); let mut query = community_view.into_boxed();
@ -143,6 +147,10 @@ impl CommunityView {
_ => () _ => ()
}; };
if !show_nsfw {
query = query.filter(nsfw.eq(false));
};
query query
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)

View File

@ -15,6 +15,7 @@ pub struct Post {
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub nsfw: bool,
} }
#[derive(Insertable, AsChangeset, Clone)] #[derive(Insertable, AsChangeset, Clone)]
@ -29,6 +30,7 @@ pub struct PostForm {
pub locked: Option<bool>, pub locked: Option<bool>,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>, pub deleted: Option<bool>,
pub nsfw: bool,
} }
impl Crud<PostForm> for Post { impl Crud<PostForm> for Post {
@ -210,6 +212,7 @@ mod tests {
removed: None, removed: None,
deleted: None, deleted: None,
locked: None, locked: None,
nsfw: false,
updated: None updated: None
}; };
@ -225,6 +228,7 @@ mod tests {
published: inserted_post.published, published: inserted_post.published,
removed: false, removed: false,
locked: false, locked: false,
nsfw: false,
deleted: false, deleted: false,
updated: None updated: None
}; };

View File

@ -19,10 +19,12 @@ table! {
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
deleted -> Bool, deleted -> Bool,
nsfw -> Bool,
creator_name -> Varchar, creator_name -> Varchar,
community_name -> Varchar, community_name -> Varchar,
community_removed -> Bool, community_removed -> Bool,
community_deleted -> Bool, community_deleted -> Bool,
community_nsfw -> Bool,
number_of_comments -> BigInt, number_of_comments -> BigInt,
score -> BigInt, score -> BigInt,
upvotes -> BigInt, upvotes -> BigInt,
@ -51,10 +53,12 @@ pub struct PostView {
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub nsfw: bool,
pub creator_name: String, pub creator_name: String,
pub community_name: String, pub community_name: String,
pub community_removed: bool, pub community_removed: bool,
pub community_deleted: bool, pub community_deleted: bool,
pub community_nsfw: bool,
pub number_of_comments: i64, pub number_of_comments: i64,
pub score: i64, pub score: i64,
pub upvotes: i64, pub upvotes: i64,
@ -68,18 +72,20 @@ pub struct PostView {
} }
impl PostView { impl PostView {
pub fn list(conn: &PgConnection, pub fn list(
type_: PostListingType, conn: &PgConnection,
sort: &SortType, type_: PostListingType,
for_community_id: Option<i32>, sort: &SortType,
for_creator_id: Option<i32>, for_community_id: Option<i32>,
search_term: Option<String>, for_creator_id: Option<i32>,
my_user_id: Option<i32>, search_term: Option<String>,
saved_only: bool, my_user_id: Option<i32>,
unread_only: bool, show_nsfw: bool,
page: Option<i64>, saved_only: bool,
limit: Option<i64>, unread_only: bool,
) -> Result<Vec<Self>, Error> { page: Option<i64>,
limit: Option<i64>,
) -> Result<Vec<Self>, Error> {
use super::post_view::post_view::dsl::*; use super::post_view::post_view::dsl::*;
let (limit, offset) = limit_and_offset(page, limit); let (limit, offset) = limit_and_offset(page, limit);
@ -121,6 +127,12 @@ impl PostView {
query = query.filter(user_id.is_null()); query = query.filter(user_id.is_null());
} }
if !show_nsfw {
query = query
.filter(nsfw.eq(false))
.filter(community_nsfw.eq(false));
};
query = match sort { query = match sort {
SortType::Hot => query.order_by(hot_rank.desc()) SortType::Hot => query.order_by(hot_rank.desc())
.then_order_by(published.desc()), .then_order_by(published.desc()),
@ -266,6 +278,7 @@ mod tests {
community_name: community_name.to_owned(), community_name: community_name.to_owned(),
community_removed: false, community_removed: false,
community_deleted: false, community_deleted: false,
community_nsfw: false,
number_of_comments: 0, number_of_comments: 0,
score: 1, score: 1,
upvotes: 1, upvotes: 1,
@ -294,6 +307,7 @@ mod tests {
community_name: community_name.to_owned(), community_name: community_name.to_owned(),
community_removed: false, community_removed: false,
community_deleted: false, community_deleted: false,
community_nsfw: false,
number_of_comments: 0, number_of_comments: 0,
score: 1, score: 1,
upvotes: 1, upvotes: 1,

View File

@ -18,7 +18,8 @@ pub struct User_ {
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool,
} }
#[derive(Insertable, AsChangeset, Clone)] #[derive(Insertable, AsChangeset, Clone)]
@ -31,7 +32,8 @@ pub struct UserForm {
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub email: Option<String>, pub email: Option<String>,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool,
} }
impl Crud<UserForm> for User_ { impl Crud<UserForm> for User_ {
@ -77,6 +79,7 @@ pub struct Claims {
pub id: i32, pub id: i32,
pub username: String, pub username: String,
pub iss: String, pub iss: String,
pub show_nsfw: bool,
} }
impl Claims { impl Claims {
@ -96,6 +99,7 @@ impl User_ {
id: self.id, id: self.id,
username: self.name.to_owned(), username: self.name.to_owned(),
iss: self.fedi_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() encode(&Header::default(), &my_claims, Settings::get().jwt_secret.as_ref()).unwrap()
} }
@ -133,7 +137,8 @@ mod tests {
email: None, email: None,
admin: false, admin: false,
banned: false, banned: false,
updated: None updated: None,
show_nsfw: false,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
@ -149,7 +154,8 @@ mod tests {
admin: false, admin: false,
banned: false, banned: false,
published: inserted_user.published, published: inserted_user.published,
updated: None updated: None,
show_nsfw: false,
}; };
let read_user = User_::read(&conn, inserted_user.id).unwrap(); let read_user = User_::read(&conn, inserted_user.id).unwrap();

View File

@ -1,3 +1,4 @@
#![recursion_limit = "512"]
#[macro_use] pub extern crate strum_macros; #[macro_use] pub extern crate strum_macros;
#[macro_use] pub extern crate lazy_static; #[macro_use] pub extern crate lazy_static;
#[macro_use] pub extern crate failure; #[macro_use] pub extern crate failure;

View File

@ -52,6 +52,7 @@ table! {
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
deleted -> Bool, deleted -> Bool,
nsfw -> Bool,
} }
} }
@ -185,6 +186,7 @@ table! {
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
deleted -> Bool, deleted -> Bool,
nsfw -> Bool,
} }
} }
@ -240,6 +242,7 @@ table! {
banned -> Bool, banned -> Bool,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
show_nsfw -> Bool,
} }
} }

View File

@ -134,17 +134,19 @@ impl ChatServer {
use crate::db::*; use crate::db::*;
use crate::db::post_view::*; use crate::db::post_view::*;
let conn = establish_connection(); let conn = establish_connection();
let posts = PostView::list(&conn, let posts = PostView::list(
PostListingType::Community, &conn,
&SortType::New, PostListingType::Community,
Some(*community_id), &SortType::New,
None, Some(*community_id),
None, None,
None, None,
false, None,
false, false,
None, false,
Some(9999))?; false,
None,
Some(9999))?;
for post in posts { for post in posts {
self.send_room_message(&post.id, message, skip_id); self.send_room_message(&post.id, message, skip_id);
} }

View File

@ -30,7 +30,8 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
communityForm: { communityForm: {
name: null, name: null,
title: null, title: null,
category_id: null category_id: null,
nsfw: false,
}, },
categories: [], categories: [],
loading: false loading: false
@ -48,6 +49,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
category_id: this.props.community.category_id, category_id: this.props.community.category_id,
description: this.props.community.description, description: this.props.community.description,
edit_id: this.props.community.id, edit_id: this.props.community.id,
nsfw: this.props.community.nsfw,
auth: null auth: null
} }
} }
@ -103,6 +105,14 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
</select> </select>
</div> </div>
</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="form-group row">
<div class="col-12"> <div class="col-12">
<button type="submit" class="btn btn-secondary mr-2"> <button type="submit" class="btn btn-secondary mr-2">
@ -147,6 +157,11 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
i.setState(i.state); i.setState(i.state);
} }
handleCommunityNsfwChange(i: CommunityForm, event: any) {
i.state.communityForm.nsfw = event.target.checked;
i.setState(i.state);
}
handleCancel(i: CommunityForm) { handleCancel(i: CommunityForm) {
i.props.onCancel(); i.props.onCancel();
} }

View File

@ -37,6 +37,7 @@ export class Community extends Component<any, State> {
number_of_comments: null, number_of_comments: null,
published: null, published: null,
removed: null, removed: null,
nsfw: false,
deleted: null, deleted: null,
}, },
moderators: [], moderators: [],
@ -105,6 +106,9 @@ export class Community extends Component<any, State> {
{this.state.community.removed && {this.state.community.removed &&
<small className="ml-2 text-muted font-italic"><T i18nKey="removed">#</T></small> <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> </h5>
{this.selects()} {this.selects()}
<PostListings posts={this.state.posts} /> <PostListings posts={this.state.posts} />

View File

@ -28,6 +28,7 @@ export class Login extends Component<any, State> {
password: undefined, password: undefined,
password_verify: undefined, password_verify: undefined,
admin: false, admin: false,
show_nsfw: false,
}, },
loginLoading: false, loginLoading: false,
registerLoading: 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 /> <input type="password" value={this.state.registerForm.password_verify} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} class="form-control" required />
</div> </div>
</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="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary">{this.state.registerLoading ? <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> <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : i18n.t('sign_up')}</button>
</div> </div>
</div> </div>
</form> </form>
@ -181,6 +189,11 @@ export class Login extends Component<any, State> {
i.setState(i.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) { parseMessage(msg: any) {
let op: UserOperation = msgOp(msg); let op: UserOperation = msgOp(msg);
if (msg.error) { if (msg.error) {

View File

@ -31,6 +31,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
private emptyState: PostFormState = { private emptyState: PostFormState = {
postForm: { postForm: {
name: null, name: null,
nsfw: false,
auth: null, auth: null,
community_id: null, community_id: null,
creator_id: (UserService.Instance.user) ? UserService.Instance.user.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, edit_id: this.props.post.id,
creator_id: this.props.post.creator_id, creator_id: this.props.post.creator_id,
url: this.props.post.url, url: this.props.post.url,
nsfw: this.props.post.nsfw,
auth: null auth: null
} }
} }
@ -126,6 +128,14 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div> </div>
</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="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary mr-2"> <button type="submit" class="btn btn-secondary mr-2">
@ -196,6 +206,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
i.setState(i.state); i.setState(i.state);
} }
handlePostNsfwChange(i: PostForm, event: any) {
i.state.postForm.nsfw = event.target.checked;
i.setState(i.state);
}
handleCancel(i: PostForm) { handleCancel(i: PostForm) {
i.props.onCancel(); i.props.onCancel();
} }

View File

@ -93,6 +93,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.locked && {post.locked &&
<small className="ml-2 text-muted font-italic"><T i18nKey="locked">#</T></small> <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) && { post.url && isImage(post.url) &&
<> <>
{ !this.state.imageExpanded { !this.state.imageExpanded
@ -251,6 +254,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
edit_id: i.props.post.id, edit_id: i.props.post.id,
creator_id: i.props.post.creator_id, creator_id: i.props.post.creator_id,
deleted: !i.props.post.deleted, deleted: !i.props.post.deleted,
nsfw: i.props.post.nsfw,
auth: null auth: null
}; };
WebSocketService.Instance.editPost(deleteForm); WebSocketService.Instance.editPost(deleteForm);
@ -285,6 +289,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
creator_id: i.props.post.creator_id, creator_id: i.props.post.creator_id,
removed: !i.props.post.removed, removed: !i.props.post.removed,
reason: i.state.removeReason, reason: i.state.removeReason,
nsfw: i.props.post.nsfw,
auth: null, auth: null,
}; };
WebSocketService.Instance.editPost(form); WebSocketService.Instance.editPost(form);
@ -299,6 +304,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
community_id: i.props.post.community_id, community_id: i.props.post.community_id,
edit_id: i.props.post.id, edit_id: i.props.post.id,
creator_id: i.props.post.creator_id, creator_id: i.props.post.creator_id,
nsfw: i.props.post.nsfw,
locked: !i.props.post.locked, locked: !i.props.post.locked,
auth: null, auth: null,
}; };

View File

@ -22,6 +22,7 @@ export interface User {
id: number; id: number;
iss: string; iss: string;
username: string; username: string;
show_nsfw: boolean;
} }
export interface UserView { export interface UserView {
@ -53,6 +54,7 @@ export interface Community {
creator_id: number; creator_id: number;
removed: boolean; removed: boolean;
deleted: boolean; deleted: boolean;
nsfw: boolean;
published: string; published: string;
updated?: string; updated?: string;
creator_name: string; creator_name: string;
@ -74,11 +76,14 @@ export interface Post {
removed: boolean; removed: boolean;
deleted: boolean; deleted: boolean;
locked: boolean; locked: boolean;
nsfw: boolean;
published: string; published: string;
updated?: string; updated?: string;
creator_name: string; creator_name: string;
community_name: string; community_name: string;
community_removed: boolean; community_removed: boolean;
community_deleted: boolean;
community_nsfw: boolean;
number_of_comments: number; number_of_comments: number;
score: number; score: number;
upvotes: number; upvotes: number;
@ -334,6 +339,7 @@ export interface RegisterForm {
password: string; password: string;
password_verify: string; password_verify: string;
admin: boolean; admin: boolean;
show_nsfw: boolean;
} }
export interface LoginResponse { export interface LoginResponse {
@ -351,6 +357,7 @@ export interface CommunityForm {
edit_id?: number; edit_id?: number;
removed?: boolean; removed?: boolean;
deleted?: boolean; deleted?: boolean;
nsfw: boolean;
reason?: string; reason?: string;
expires?: number; expires?: number;
auth?: string; auth?: string;
@ -396,6 +403,7 @@ export interface PostForm {
creator_id: number; creator_id: number;
removed?: boolean; removed?: boolean;
deleted?: boolean; deleted?: boolean;
nsfw: boolean;
locked?: boolean; locked?: boolean;
reason?: string; reason?: string;
auth: string; auth: string;

View File

@ -112,6 +112,8 @@ export const en = {
setup_admin: 'Set Up Site Administrator', setup_admin: 'Set Up Site Administrator',
your_site: 'your site', your_site: 'your site',
modified: 'modified', modified: 'modified',
nsfw: 'NSFW',
show_nsfw: 'Show NSFW content',
sponsors: 'Sponsors', sponsors: 'Sponsors',
sponsors_of_lemmy: 'Sponsors of Lemmy', 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:', 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:',