Commiting before I lose everything. I'll do this properly in a merge

This commit is contained in:
Dessalines 2019-04-15 16:12:06 -07:00
parent 8590a612f6
commit e94885eb97
49 changed files with 2885 additions and 295 deletions

View file

@ -1 +1,2 @@
drop table user_ drop table user_ban;
drop table user_;

View file

@ -6,9 +6,18 @@ create table user_ (
password_encrypted text not null, password_encrypted text not null,
email text unique, email text unique,
icon bytea, icon bytea,
admin boolean default false,
banned boolean default false,
published timestamp not null default now(), published timestamp not null default now(),
updated timestamp, updated timestamp,
unique(name, fedi_name) unique(name, fedi_name)
); );
create table user_ban (
id serial primary key,
user_id int references user_ on update cascade on delete cascade not null,
published timestamp not null default now(),
unique (user_id)
);
insert into user_ (name, fedi_name, password_encrypted) values ('admin', 'TBD', 'TBD'); insert into user_ (name, fedi_name, password_encrypted) values ('admin', 'TBD', 'TBD');

View file

@ -1,3 +1,4 @@
drop table community_user_ban;;
drop table community_moderator; drop table community_moderator;
drop table community_follower; drop table community_follower;
drop table community; drop table community;

View file

@ -38,6 +38,7 @@ create table community (
description text, description text,
category_id int references category on update cascade on delete cascade not null, category_id int references category on update cascade on delete cascade not null,
creator_id int references user_ on update cascade on delete cascade not null, creator_id int references user_ on update cascade on delete cascade not null,
removed boolean default false,
published timestamp not null default now(), published timestamp not null default now(),
updated timestamp updated timestamp
); );
@ -46,14 +47,24 @@ create table community_moderator (
id serial primary key, id serial primary key,
community_id int references community on update cascade on delete cascade not null, community_id int references community on update cascade on delete cascade not null,
user_id int references user_ on update cascade on delete cascade not null, user_id int references user_ on update cascade on delete cascade not null,
published timestamp not null default now() published timestamp not null default now(),
unique (community_id, user_id)
); );
create table community_follower ( create table community_follower (
id serial primary key, id serial primary key,
community_id int references community on update cascade on delete cascade not null, community_id int references community on update cascade on delete cascade not null,
user_id int references user_ on update cascade on delete cascade not null, user_id int references user_ on update cascade on delete cascade not null,
published timestamp not null default now() published timestamp not null default now(),
unique (community_id, user_id)
);
create table community_user_ban (
id serial primary key,
community_id int references community on update cascade on delete cascade not null,
user_id int references user_ on update cascade on delete cascade not null,
published timestamp not null default now(),
unique (community_id, user_id)
); );
insert into community (name, title, category_id, creator_id) values ('main', 'The Default Community', 1, 1); insert into community (name, title, category_id, creator_id) values ('main', 'The Default Community', 1, 1);

View file

@ -5,6 +5,8 @@ create table post (
body text, body text,
creator_id int references user_ on update cascade on delete cascade not null, creator_id int references user_ on update cascade on delete cascade not null,
community_id int references community on update cascade on delete cascade not null, community_id int references community on update cascade on delete cascade not null,
removed boolean default false,
locked boolean default false,
published timestamp not null default now(), published timestamp not null default now(),
updated timestamp updated timestamp
); );

View file

@ -4,6 +4,7 @@ create table comment (
post_id int references post on update cascade on delete cascade not null, post_id int references post on update cascade on delete cascade not null,
parent_id int references comment on update cascade on delete cascade, parent_id int references comment on update cascade on delete cascade,
content text not null, content text not null,
removed boolean default false,
published timestamp not null default now(), published timestamp not null default now(),
updated timestamp updated timestamp
); );

View file

@ -30,7 +30,8 @@ select
ap.*, ap.*,
u.id as user_id, u.id as user_id,
coalesce(pl.score, 0) as my_vote, 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 cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
u.admin or (select cm.id::bool from community_moderator cm where u.id = cm.user_id and cm.community_id = ap.community_id) as am_mod
from user_ u from user_ u
cross join all_post ap cross join all_post ap
left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
@ -41,33 +42,7 @@ select
ap.*, ap.*,
null as user_id, null as user_id,
null as my_vote, null as my_vote,
null as subscribed null as subscribed,
null as am_mod
from all_post ap from all_post ap
; ;
/* The old post view */
/* create view post_view as */
/* select */
/* u.id as user_id, */
/* pl.score as my_vote, */
/* p.id as id, */
/* p.name as name, */
/* p.url, */
/* p.body, */
/* p.creator_id, */
/* (select name from user_ where p.creator_id = user_.id) creator_name, */
/* p.community_id, */
/* (select name from community where p.community_id = community.id) as community_name, */
/* (select count(*) from comment where comment.post_id = p.id) as number_of_comments, */
/* coalesce(sum(pl.score) over (partition by p.id), 0) as score, */
/* count (case when pl.score = 1 then 1 else null end) over (partition by p.id) as upvotes, */
/* count (case when pl.score = -1 then 1 else null end) over (partition by p.id) as downvotes, */
/* hot_rank(coalesce(sum(pl.score) over (partition by p.id) , 0), p.published) as hot_rank, */
/* p.published, */
/* p.updated */
/* from user_ u */
/* cross join post p */
/* left join post_like pl on u.id = pl.user_id and p.id = pl.post_id; */

View file

@ -1,3 +1,4 @@
drop view community_view; drop view community_view;
drop view community_moderator_view; drop view community_moderator_view;
drop view community_follower_view; drop view community_follower_view;
drop view community_user_ban_view;

View file

@ -13,7 +13,8 @@ with all_community as
select select
ac.*, ac.*,
u.id as user_id, u.id as user_id,
cf.id::boolean as subscribed cf.id::boolean as subscribed,
u.admin or (select cm.id::bool from community_moderator cm where u.id = cm.user_id and cm.community_id = ac.id) as am_mod
from user_ u from user_ u
cross join all_community ac cross join all_community ac
left join community_follower cf on u.id = cf.user_id and ac.id = cf.community_id left join community_follower cf on u.id = cf.user_id and ac.id = cf.community_id
@ -23,7 +24,8 @@ union all
select select
ac.*, ac.*,
null as user_id, null as user_id,
null as subscribed null as subscribed,
null as am_mod
from all_community ac from all_community ac
; ;
@ -38,3 +40,9 @@ select *,
(select name from user_ u where cf.user_id = u.id) as user_name, (select name from user_ u where cf.user_id = u.id) as user_name,
(select name from community c where cf.community_id = c.id) as community_name (select name from community c where cf.community_id = c.id) as community_name
from community_follower cf; from community_follower cf;
create view community_user_ban_view as
select *,
(select name from user_ u where cm.user_id = u.id) as user_name,
(select name from community c where cm.community_id = c.id) as community_name
from community_user_ban cm;

View file

@ -3,7 +3,9 @@ with all_comment as
( (
select select
c.*, c.*,
(select name from user_ where c.creator_id = user_.id) creator_name, (select community_id from post p where p.id = c.post_id),
(select cb.id::bool from community_user_ban cb where c.creator_id = cb.user_id) as banned,
(select name from user_ where c.creator_id = user_.id) as creator_name,
coalesce(sum(cl.score), 0) as score, coalesce(sum(cl.score), 0) as score,
count (case when cl.score = 1 then 1 else null end) as upvotes, count (case when cl.score = 1 then 1 else null end) as upvotes,
count (case when cl.score = -1 then 1 else null end) as downvotes count (case when cl.score = -1 then 1 else null end) as downvotes
@ -15,7 +17,8 @@ with all_comment as
select select
ac.*, ac.*,
u.id as user_id, u.id as user_id,
coalesce(cl.score, 0) as my_vote coalesce(cl.score, 0) as my_vote,
u.admin or (select cm.id::bool from community_moderator cm, post p where u.id = cm.user_id and ac.post_id = p.id and p.community_id = cm.community_id) as am_mod
from user_ u from user_ u
cross join all_comment ac cross join all_comment ac
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
@ -25,6 +28,7 @@ union all
select select
ac.*, ac.*,
null as user_id, null as user_id,
null as my_vote null as my_vote,
null as am_mod
from all_comment ac from all_comment ac
; ;

View file

@ -3,4 +3,6 @@ drop table mod_lock_post;
drop table mod_remove_comment; drop table mod_remove_comment;
drop table mod_remove_community; drop table mod_remove_community;
drop table mod_ban; drop table mod_ban;
drop table mod_add_mod; drop table mod_ban_from_community;
drop table mod_add;
drop table mod_add_community;

View file

@ -1,4 +1,3 @@
create table mod_remove_post ( create table mod_remove_post (
id serial primary key, id serial primary key,
mod_user_id int references user_ on update cascade on delete cascade not null, mod_user_id int references user_ on update cascade on delete cascade not null,
@ -12,6 +11,7 @@ create table mod_lock_post (
id serial primary key, id serial primary key,
mod_user_id int references user_ on update cascade on delete cascade not null, mod_user_id int references user_ on update cascade on delete cascade not null,
post_id int references post on update cascade on delete cascade not null, post_id int references post on update cascade on delete cascade not null,
locked boolean default true,
when_ timestamp not null default now() when_ timestamp not null default now()
); );
@ -30,25 +30,47 @@ create table mod_remove_community (
community_id int references community on update cascade on delete cascade not null, community_id int references community on update cascade on delete cascade not null,
reason text, reason text,
removed boolean default true, removed boolean default true,
expires timestamp,
when_ timestamp not null default now() when_ timestamp not null default now()
); );
-- TODO make sure you can't ban other mods -- TODO make sure you can't ban other mods
create table mod_ban_from_community (
id serial primary key,
mod_user_id int references user_ on update cascade on delete cascade not null,
other_user_id int references user_ on update cascade on delete cascade not null,
community_id int references community on update cascade on delete cascade not null,
reason text,
banned boolean default true,
expires timestamp,
when_ timestamp not null default now()
);
create table mod_ban ( create table mod_ban (
id serial primary key, id serial primary key,
mod_user_id int references user_ on update cascade on delete cascade not null, mod_user_id int references user_ on update cascade on delete cascade not null,
other_user_id int references user_ on update cascade on delete cascade not null, other_user_id int references user_ on update cascade on delete cascade not null,
reason text, reason text,
removed boolean default true, banned boolean default true,
expires timestamp, expires timestamp,
when_ timestamp not null default now() when_ timestamp not null default now()
); );
create table mod_add_community (
id serial primary key,
mod_user_id int references user_ on update cascade on delete cascade not null,
other_user_id int references user_ on update cascade on delete cascade not null,
community_id int references community on update cascade on delete cascade not null,
removed boolean default false,
when_ timestamp not null default now()
);
-- When removed is false that means kicked -- When removed is false that means kicked
create table mod_add_mod ( create table mod_add (
id serial primary key, id serial primary key,
mod_user_id int references user_ on update cascade on delete cascade not null, mod_user_id int references user_ on update cascade on delete cascade not null,
other_user_id int references user_ on update cascade on delete cascade not null, other_user_id int references user_ on update cascade on delete cascade not null,
removed boolean default false, removed boolean default false,
when_ timestamp not null default now() when_ timestamp not null default now()
) );

View file

@ -2,10 +2,11 @@ create view user_view as
select id, select id,
name, name,
fedi_name, fedi_name,
admin,
banned,
published, published,
(select count(*) from post p where p.creator_id = u.id) as number_of_posts, (select count(*) from post p where p.creator_id = u.id) as number_of_posts,
(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, (select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, (select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score (select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
from user_ u; from user_ u;

View file

@ -0,0 +1,8 @@
drop view mod_remove_post_view;
drop view mod_lock_post_view;
drop view mod_remove_comment_view;
drop view mod_remove_community_view;
drop view mod_ban_from_community_view;
drop view mod_ban_view;
drop view mod_add_community_view;
drop view mod_add_view;

View file

@ -0,0 +1,61 @@
create view mod_remove_post_view as
select mrp.*,
(select name from user_ u where mrp.mod_user_id = u.id) as mod_user_name,
(select name from post p where mrp.post_id = p.id) as post_name,
(select c.id from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_id,
(select c.name from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_name
from mod_remove_post mrp;
create view mod_lock_post_view as
select mlp.*,
(select name from user_ u where mlp.mod_user_id = u.id) as mod_user_name,
(select name from post p where mlp.post_id = p.id) as post_name,
(select c.id from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_id,
(select c.name from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_name
from mod_lock_post mlp;
create view mod_remove_comment_view as
select mrc.*,
(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name,
(select c.id from comment c where mrc.comment_id = c.id) as comment_user_id,
(select name from user_ u, comment c where mrc.comment_id = c.id and u.id = c.creator_id) as comment_user_name,
(select content from comment c where mrc.comment_id = c.id) as comment_content,
(select p.id from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_id,
(select p.name from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_name,
(select co.id from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_id,
(select co.name from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_name
from mod_remove_comment mrc;
create view mod_remove_community_view as
select mrc.*,
(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name,
(select c.name from community c where mrc.community_id = c.id) as community_name
from mod_remove_community mrc;
create view mod_ban_from_community_view as
select mb.*,
(select name from user_ u where mb.mod_user_id = u.id) as mod_user_name,
(select name from user_ u where mb.other_user_id = u.id) as other_user_name,
(select name from community c where mb.community_id = c.id) as community_name
from mod_ban_from_community mb;
create view mod_ban_view as
select mb.*,
(select name from user_ u where mb.mod_user_id = u.id) as mod_user_name,
(select name from user_ u where mb.other_user_id = u.id) as other_user_name
from mod_ban_from_community mb;
create view mod_add_community_view as
select ma.*,
(select name from user_ u where ma.mod_user_id = u.id) as mod_user_name,
(select name from user_ u where ma.other_user_id = u.id) as other_user_name,
(select name from community c where ma.community_id = c.id) as community_name
from mod_add_community ma;
create view mod_add_view as
select ma.*,
(select name from user_ u where ma.mod_user_id = u.id) as mod_user_name,
(select name from user_ u where ma.other_user_id = u.id) as other_user_name
from mod_add ma;

View file

@ -22,6 +22,7 @@ pub struct Comment {
pub post_id: i32, pub post_id: i32,
pub parent_id: Option<i32>, pub parent_id: Option<i32>,
pub content: String, pub content: String,
pub removed: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -33,6 +34,7 @@ pub struct CommentForm {
pub post_id: i32, pub post_id: i32,
pub parent_id: Option<i32>, pub parent_id: Option<i32>,
pub content: String, pub content: String,
pub removed: Option<bool>,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -135,6 +137,8 @@ mod tests {
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(), password_encrypted: "nope".into(),
email: None, email: None,
admin: None,
banned: None,
updated: None updated: None
}; };
@ -146,6 +150,7 @@ mod tests {
description: None, description: None,
category_id: 1, category_id: 1,
creator_id: inserted_user.id, creator_id: inserted_user.id,
removed: None,
updated: None updated: None
}; };
@ -157,6 +162,8 @@ mod tests {
url: None, url: None,
body: None, body: None,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None,
locked: None,
updated: None updated: None
}; };
@ -166,6 +173,7 @@ mod tests {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_user.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None,
parent_id: None, parent_id: None,
updated: None updated: None
}; };
@ -177,6 +185,7 @@ mod tests {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_user.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: Some(false),
parent_id: None, parent_id: None,
published: inserted_comment.published, published: inserted_comment.published,
updated: None updated: None
@ -187,6 +196,7 @@ mod tests {
creator_id: inserted_user.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
parent_id: Some(inserted_comment.id), parent_id: Some(inserted_comment.id),
removed: None,
updated: None updated: None
}; };

View file

@ -13,14 +13,18 @@ table! {
post_id -> Int4, post_id -> Int4,
parent_id -> Nullable<Int4>, parent_id -> Nullable<Int4>,
content -> Text, content -> Text,
removed -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
community_id -> Int4,
banned -> Nullable<Bool>,
creator_name -> Varchar, creator_name -> Varchar,
score -> BigInt, score -> BigInt,
upvotes -> BigInt, upvotes -> BigInt,
downvotes -> BigInt, downvotes -> BigInt,
user_id -> Nullable<Int4>, user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>, my_vote -> Nullable<Int4>,
am_mod -> Nullable<Bool>,
} }
} }
@ -32,14 +36,18 @@ pub struct CommentView {
pub post_id: i32, pub post_id: i32,
pub parent_id: Option<i32>, pub parent_id: Option<i32>,
pub content: String, pub content: String,
pub removed: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub community_id: i32,
pub banned: Option<bool>,
pub creator_name: String, pub creator_name: String,
pub score: i64, pub score: i64,
pub upvotes: i64, pub upvotes: i64,
pub downvotes: i64, pub downvotes: i64,
pub user_id: Option<i32>, pub user_id: Option<i32>,
pub my_vote: Option<i32>, pub my_vote: Option<i32>,
pub am_mod: Option<bool>,
} }
impl CommentView { impl CommentView {
@ -130,6 +138,8 @@ mod tests {
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(), password_encrypted: "nope".into(),
email: None, email: None,
admin: None,
banned: None,
updated: None updated: None
}; };
@ -141,6 +151,7 @@ mod tests {
description: None, description: None,
category_id: 1, category_id: 1,
creator_id: inserted_user.id, creator_id: inserted_user.id,
removed: None,
updated: None updated: None
}; };
@ -152,6 +163,8 @@ mod tests {
url: None, url: None,
body: None, body: None,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None,
locked: None,
updated: None updated: None
}; };
@ -162,6 +175,7 @@ mod tests {
creator_id: inserted_user.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
parent_id: None, parent_id: None,
removed: None,
updated: None updated: None
}; };
@ -181,7 +195,10 @@ mod tests {
content: "A test comment 32".into(), content: "A test comment 32".into(),
creator_id: inserted_user.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
community_id: inserted_community.id,
parent_id: None, parent_id: None,
removed: Some(false),
banned: None,
published: inserted_comment.published, published: inserted_comment.published,
updated: None, updated: None,
creator_name: inserted_user.name.to_owned(), creator_name: inserted_user.name.to_owned(),
@ -189,7 +206,8 @@ mod tests {
downvotes: 0, downvotes: 0,
upvotes: 1, upvotes: 1,
user_id: None, user_id: None,
my_vote: None my_vote: None,
am_mod: None,
}; };
let expected_comment_view_with_user = CommentView { let expected_comment_view_with_user = CommentView {
@ -197,7 +215,10 @@ mod tests {
content: "A test comment 32".into(), content: "A test comment 32".into(),
creator_id: inserted_user.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
community_id: inserted_community.id,
parent_id: None, parent_id: None,
removed: Some(false),
banned: None,
published: inserted_comment.published, published: inserted_comment.published,
updated: None, updated: None,
creator_name: inserted_user.name.to_owned(), creator_name: inserted_user.name.to_owned(),
@ -206,6 +227,7 @@ mod tests {
upvotes: 1, upvotes: 1,
user_id: Some(inserted_user.id), user_id: Some(inserted_user.id),
my_vote: Some(1), my_vote: Some(1),
am_mod: None,
}; };
let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, 999).unwrap(); let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, 999).unwrap();

View file

@ -1,9 +1,9 @@
extern crate diesel; extern crate diesel;
use schema::{community, community_moderator, community_follower}; use schema::{community, community_moderator, community_follower, community_user_ban};
use diesel::*; use diesel::*;
use diesel::result::Error; use diesel::result::Error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use {Crud, Followable, Joinable}; use {Crud, Followable, Joinable, Bannable};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="community"] #[table_name="community"]
@ -14,6 +14,7 @@ pub struct Community {
pub description: Option<String>, pub description: Option<String>,
pub category_id: i32, pub category_id: i32,
pub creator_id: i32, pub creator_id: i32,
pub removed: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -26,6 +27,7 @@ pub struct CommunityForm {
pub description: Option<String>, pub description: Option<String>,
pub category_id: i32, pub category_id: i32,
pub creator_id: i32, pub creator_id: i32,
pub removed: Option<bool>,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -46,6 +48,23 @@ pub struct CommunityModeratorForm {
pub user_id: i32, pub user_id: i32,
} }
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
#[belongs_to(Community)]
#[table_name = "community_user_ban"]
pub struct CommunityUserBan {
pub id: i32,
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone)]
#[table_name="community_user_ban"]
pub struct CommunityUserBanForm {
pub community_id: i32,
pub user_id: i32,
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
#[belongs_to(Community)] #[belongs_to(Community)]
#[table_name = "community_follower"] #[table_name = "community_follower"]
@ -125,6 +144,23 @@ impl Joinable<CommunityModeratorForm> for CommunityModerator {
} }
} }
impl Bannable<CommunityUserBanForm> for CommunityUserBan {
fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<Self, Error> {
use schema::community_user_ban::dsl::*;
insert_into(community_user_ban)
.values(community_user_ban_form)
.get_result::<Self>(conn)
}
fn unban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<usize, Error> {
use schema::community_user_ban::dsl::*;
diesel::delete(community_user_ban
.filter(community_id.eq(community_user_ban_form.community_id))
.filter(user_id.eq(community_user_ban_form.user_id)))
.execute(conn)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use establish_connection; use establish_connection;
@ -136,11 +172,13 @@ mod tests {
let conn = establish_connection(); let conn = establish_connection();
let new_user = UserForm { let new_user = UserForm {
name: "bob".into(), name: "bobbee".into(),
fedi_name: "rrf".into(), fedi_name: "rrf".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(), password_encrypted: "nope".into(),
email: None, email: None,
admin: None,
banned: None,
updated: None updated: None
}; };
@ -152,6 +190,7 @@ mod tests {
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1, category_id: 1,
removed: None,
updated: None, updated: None,
}; };
@ -164,11 +203,11 @@ mod tests {
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1, category_id: 1,
removed: Some(false),
published: inserted_community.published, published: inserted_community.published,
updated: None updated: None
}; };
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
user_id: inserted_user.id user_id: inserted_user.id
@ -176,6 +215,7 @@ mod tests {
let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap(); let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap();
let expected_community_follower = CommunityFollower { let expected_community_follower = CommunityFollower {
id: inserted_community_follower.id, id: inserted_community_follower.id,
community_id: inserted_community.id, community_id: inserted_community.id,
@ -197,10 +237,25 @@ mod tests {
published: inserted_community_user.published published: inserted_community_user.published
}; };
let community_user_ban_form = CommunityUserBanForm {
community_id: inserted_community.id,
user_id: inserted_user.id
};
let inserted_community_user_ban = CommunityUserBan::ban(&conn, &community_user_ban_form).unwrap();
let expected_community_user_ban = CommunityUserBan {
id: inserted_community_user_ban.id,
community_id: inserted_community.id,
user_id: inserted_user.id,
published: inserted_community_user_ban.published
};
let read_community = Community::read(&conn, inserted_community.id).unwrap(); let read_community = Community::read(&conn, inserted_community.id).unwrap();
let updated_community = Community::update(&conn, inserted_community.id, &new_community).unwrap(); let updated_community = Community::update(&conn, inserted_community.id, &new_community).unwrap();
let ignored_community = CommunityFollower::ignore(&conn, &community_follower_form).unwrap(); let ignored_community = CommunityFollower::ignore(&conn, &community_follower_form).unwrap();
let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap(); let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap();
let num_deleted = Community::delete(&conn, inserted_community.id).unwrap(); let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
User_::delete(&conn, inserted_user.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap();
@ -209,8 +264,10 @@ mod tests {
assert_eq!(expected_community, updated_community); assert_eq!(expected_community, updated_community);
assert_eq!(expected_community_follower, inserted_community_follower); assert_eq!(expected_community_follower, inserted_community_follower);
assert_eq!(expected_community_user, inserted_community_user); assert_eq!(expected_community_user, inserted_community_user);
assert_eq!(expected_community_user_ban, inserted_community_user_ban);
assert_eq!(1, ignored_community); assert_eq!(1, ignored_community);
assert_eq!(1, left_community); assert_eq!(1, left_community);
assert_eq!(1, unban);
// assert_eq!(2, loaded_count); // assert_eq!(2, loaded_count);
assert_eq!(1, num_deleted); assert_eq!(1, num_deleted);

View file

@ -12,6 +12,7 @@ table! {
description -> Nullable<Text>, description -> Nullable<Text>,
category_id -> Int4, category_id -> Int4,
creator_id -> Int4, creator_id -> Int4,
removed -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
creator_name -> Varchar, creator_name -> Varchar,
@ -21,6 +22,7 @@ table! {
number_of_comments -> BigInt, number_of_comments -> BigInt,
user_id -> Nullable<Int4>, user_id -> Nullable<Int4>,
subscribed -> Nullable<Bool>, subscribed -> Nullable<Bool>,
am_mod -> Nullable<Bool>,
} }
} }
@ -46,6 +48,17 @@ table! {
} }
} }
table! {
community_user_ban_view (id) {
id -> Int4,
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
user_name -> Varchar,
community_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="community_view"] #[table_name="community_view"]
pub struct CommunityView { pub struct CommunityView {
@ -55,6 +68,7 @@ pub struct CommunityView {
pub description: Option<String>, pub description: Option<String>,
pub category_id: i32, pub category_id: i32,
pub creator_id: i32, pub creator_id: i32,
pub removed: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub creator_name: String, pub creator_name: String,
@ -64,6 +78,7 @@ pub struct CommunityView {
pub number_of_comments: i64, pub number_of_comments: i64,
pub user_id: Option<i32>, pub user_id: Option<i32>,
pub subscribed: Option<bool>, pub subscribed: Option<bool>,
pub am_mod: Option<bool>,
} }
impl CommunityView { impl CommunityView {
@ -107,7 +122,7 @@ impl CommunityView {
query = query.limit(limit); query = query.limit(limit);
}; };
query.load::<Self>(conn) query.filter(removed.eq(false)).load::<Self>(conn)
} }
} }
@ -157,3 +172,35 @@ impl CommunityFollowerView {
community_follower_view.filter(user_id.eq(from_user_id)).load::<Self>(conn) community_follower_view.filter(user_id.eq(from_user_id)).load::<Self>(conn)
} }
} }
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="community_user_ban_view"]
pub struct CommunityUserBanView {
pub id: i32,
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
pub user_name : String,
pub community_name: String,
}
impl CommunityUserBanView {
pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result<Vec<Self>, Error> {
use actions::community_view::community_user_ban_view::dsl::*;
community_user_ban_view.filter(community_id.eq(from_community_id)).load::<Self>(conn)
}
pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result<Vec<Self>, Error> {
use actions::community_view::community_user_ban_view::dsl::*;
community_user_ban_view.filter(user_id.eq(from_user_id)).load::<Self>(conn)
}
pub fn get(conn: &PgConnection, from_user_id: i32, from_community_id: i32) -> Result<Self, Error> {
use actions::community_view::community_user_ban_view::dsl::*;
community_user_ban_view
.filter(user_id.eq(from_user_id))
.filter(community_id.eq(from_community_id))
.first::<Self>(conn)
}
}

View file

@ -7,3 +7,5 @@ pub mod comment_view;
pub mod category; pub mod category;
pub mod community_view; pub mod community_view;
pub mod user_view; pub mod user_view;
pub mod moderator;
pub mod moderator_views;

View file

@ -0,0 +1,655 @@
extern crate diesel;
use schema::{mod_remove_post, mod_lock_post, mod_remove_comment, mod_remove_community, mod_ban_from_community, mod_ban, mod_add_community, mod_add};
use diesel::*;
use diesel::result::Error;
use serde::{Deserialize, Serialize};
use {Crud};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_remove_post"]
pub struct ModRemovePost {
pub id: i32,
pub mod_user_id: i32,
pub post_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_remove_post"]
pub struct ModRemovePostForm {
pub mod_user_id: i32,
pub post_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
}
impl Crud<ModRemovePostForm> for ModRemovePost {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_remove_post::dsl::*;
mod_remove_post.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_remove_post::dsl::*;
diesel::delete(mod_remove_post.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModRemovePostForm) -> Result<Self, Error> {
use schema::mod_remove_post::dsl::*;
insert_into(mod_remove_post)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModRemovePostForm) -> Result<Self, Error> {
use schema::mod_remove_post::dsl::*;
diesel::update(mod_remove_post.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_lock_post"]
pub struct ModLockPost {
pub id: i32,
pub mod_user_id: i32,
pub post_id: i32,
pub locked: Option<bool>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_lock_post"]
pub struct ModLockPostForm {
pub mod_user_id: i32,
pub post_id: i32,
pub locked: Option<bool>,
}
impl Crud<ModLockPostForm> for ModLockPost {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_lock_post::dsl::*;
mod_lock_post.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_lock_post::dsl::*;
diesel::delete(mod_lock_post.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModLockPostForm) -> Result<Self, Error> {
use schema::mod_lock_post::dsl::*;
insert_into(mod_lock_post)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModLockPostForm) -> Result<Self, Error> {
use schema::mod_lock_post::dsl::*;
diesel::update(mod_lock_post.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_remove_comment"]
pub struct ModRemoveComment {
pub id: i32,
pub mod_user_id: i32,
pub comment_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_remove_comment"]
pub struct ModRemoveCommentForm {
pub mod_user_id: i32,
pub comment_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
}
impl Crud<ModRemoveCommentForm> for ModRemoveComment {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_remove_comment::dsl::*;
mod_remove_comment.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_remove_comment::dsl::*;
diesel::delete(mod_remove_comment.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModRemoveCommentForm) -> Result<Self, Error> {
use schema::mod_remove_comment::dsl::*;
insert_into(mod_remove_comment)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommentForm) -> Result<Self, Error> {
use schema::mod_remove_comment::dsl::*;
diesel::update(mod_remove_comment.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_remove_community"]
pub struct ModRemoveCommunity {
pub id: i32,
pub mod_user_id: i32,
pub community_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_remove_community"]
pub struct ModRemoveCommunityForm {
pub mod_user_id: i32,
pub community_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
}
impl Crud<ModRemoveCommunityForm> for ModRemoveCommunity {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_remove_community::dsl::*;
mod_remove_community.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_remove_community::dsl::*;
diesel::delete(mod_remove_community.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModRemoveCommunityForm) -> Result<Self, Error> {
use schema::mod_remove_community::dsl::*;
insert_into(mod_remove_community)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommunityForm) -> Result<Self, Error> {
use schema::mod_remove_community::dsl::*;
diesel::update(mod_remove_community.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_ban_from_community"]
pub struct ModBanFromCommunity {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub community_id: i32,
pub reason: Option<String>,
pub banned: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_ban_from_community"]
pub struct ModBanFromCommunityForm {
pub mod_user_id: i32,
pub other_user_id: i32,
pub community_id: i32,
pub reason: Option<String>,
pub banned: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
}
impl Crud<ModBanFromCommunityForm> for ModBanFromCommunity {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_ban_from_community::dsl::*;
mod_ban_from_community.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_ban_from_community::dsl::*;
diesel::delete(mod_ban_from_community.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModBanFromCommunityForm) -> Result<Self, Error> {
use schema::mod_ban_from_community::dsl::*;
insert_into(mod_ban_from_community)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModBanFromCommunityForm) -> Result<Self, Error> {
use schema::mod_ban_from_community::dsl::*;
diesel::update(mod_ban_from_community.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_ban"]
pub struct ModBan {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub reason: Option<String>,
pub banned: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_ban"]
pub struct ModBanForm {
pub mod_user_id: i32,
pub other_user_id: i32,
pub reason: Option<String>,
pub banned: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
}
impl Crud<ModBanForm> for ModBan {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_ban::dsl::*;
mod_ban.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_ban::dsl::*;
diesel::delete(mod_ban.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModBanForm) -> Result<Self, Error> {
use schema::mod_ban::dsl::*;
insert_into(mod_ban)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModBanForm) -> Result<Self, Error> {
use schema::mod_ban::dsl::*;
diesel::update(mod_ban.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_add_community"]
pub struct ModAddCommunity {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub community_id: i32,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_add_community"]
pub struct ModAddCommunityForm {
pub mod_user_id: i32,
pub other_user_id: i32,
pub community_id: i32,
pub removed: Option<bool>,
}
impl Crud<ModAddCommunityForm> for ModAddCommunity {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_add_community::dsl::*;
mod_add_community.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_add_community::dsl::*;
diesel::delete(mod_add_community.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModAddCommunityForm) -> Result<Self, Error> {
use schema::mod_add_community::dsl::*;
insert_into(mod_add_community)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModAddCommunityForm) -> Result<Self, Error> {
use schema::mod_add_community::dsl::*;
diesel::update(mod_add_community.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name="mod_add"]
pub struct ModAdd {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name="mod_add"]
pub struct ModAddForm {
pub mod_user_id: i32,
pub other_user_id: i32,
pub removed: Option<bool>,
}
impl Crud<ModAddForm> for ModAdd {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use schema::mod_add::dsl::*;
mod_add.find(from_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
use schema::mod_add::dsl::*;
diesel::delete(mod_add.find(from_id))
.execute(conn)
}
fn create(conn: &PgConnection, form: &ModAddForm) -> Result<Self, Error> {
use schema::mod_add::dsl::*;
insert_into(mod_add)
.values(form)
.get_result::<Self>(conn)
}
fn update(conn: &PgConnection, from_id: i32, form: &ModAddForm) -> Result<Self, Error> {
use schema::mod_add::dsl::*;
diesel::update(mod_add.find(from_id))
.set(form)
.get_result::<Self>(conn)
}
}
#[cfg(test)]
mod tests {
use establish_connection;
use super::*;
use actions::user::*;
use actions::post::*;
use actions::community::*;
use actions::comment::*;
// use Crud;
#[test]
fn test_crud() {
let conn = establish_connection();
let new_mod = UserForm {
name: "the mod".into(),
fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
admin: None,
banned: None,
updated: None
};
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
let new_user = UserForm {
name: "jim2".into(),
fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
admin: None,
banned: None,
updated: None
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
let new_community = CommunityForm {
name: "mod_community".to_string(),
title: "nada".to_owned(),
description: None,
category_id: 1,
creator_id: inserted_user.id,
removed: None,
updated: None
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
let new_post = PostForm {
name: "A test post thweep".into(),
url: None,
body: None,
creator_id: inserted_user.id,
community_id: inserted_community.id,
removed: None,
locked: None,
updated: None
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
let comment_form = CommentForm {
content: "A test comment".into(),
creator_id: inserted_user.id,
post_id: inserted_post.id,
removed: None,
parent_id: None,
updated: None
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
// Now the actual tests
// remove post
let mod_remove_post_form = ModRemovePostForm {
mod_user_id: inserted_mod.id,
post_id: inserted_post.id,
reason: None,
removed: None,
};
let inserted_mod_remove_post = ModRemovePost::create(&conn, &mod_remove_post_form).unwrap();
let read_moderator_remove_post = ModRemovePost::read(&conn, inserted_mod_remove_post.id).unwrap();
let expected_moderator_remove_post = ModRemovePost {
id: inserted_mod_remove_post.id,
post_id: inserted_post.id,
mod_user_id: inserted_mod.id,
reason: None,
removed: Some(true),
when_: inserted_mod_remove_post.when_,
};
// lock post
let mod_lock_post_form = ModLockPostForm {
mod_user_id: inserted_mod.id,
post_id: inserted_post.id,
locked: None,
};
let inserted_mod_lock_post = ModLockPost::create(&conn, &mod_lock_post_form).unwrap();
let read_moderator_lock_post = ModLockPost::read(&conn, inserted_mod_lock_post.id).unwrap();
let expected_moderator_lock_post = ModLockPost {
id: inserted_mod_lock_post.id,
post_id: inserted_post.id,
mod_user_id: inserted_mod.id,
locked: Some(true),
when_: inserted_mod_lock_post.when_,
};
// comment
let mod_remove_comment_form = ModRemoveCommentForm {
mod_user_id: inserted_mod.id,
comment_id: inserted_comment.id,
reason: None,
removed: None,
};
let inserted_mod_remove_comment = ModRemoveComment::create(&conn, &mod_remove_comment_form).unwrap();
let read_moderator_remove_comment = ModRemoveComment::read(&conn, inserted_mod_remove_comment.id).unwrap();
let expected_moderator_remove_comment = ModRemoveComment {
id: inserted_mod_remove_comment.id,
comment_id: inserted_comment.id,
mod_user_id: inserted_mod.id,
reason: None,
removed: Some(true),
when_: inserted_mod_remove_comment.when_,
};
// community
let mod_remove_community_form = ModRemoveCommunityForm {
mod_user_id: inserted_mod.id,
community_id: inserted_community.id,
reason: None,
removed: None,
expires: None,
};
let inserted_mod_remove_community = ModRemoveCommunity::create(&conn, &mod_remove_community_form).unwrap();
let read_moderator_remove_community = ModRemoveCommunity::read(&conn, inserted_mod_remove_community.id).unwrap();
let expected_moderator_remove_community = ModRemoveCommunity {
id: inserted_mod_remove_community.id,
community_id: inserted_community.id,
mod_user_id: inserted_mod.id,
reason: None,
removed: Some(true),
expires: None,
when_: inserted_mod_remove_community.when_,
};
// ban from community
let mod_ban_from_community_form = ModBanFromCommunityForm {
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
community_id: inserted_community.id,
reason: None,
banned: None,
expires: None,
};
let inserted_mod_ban_from_community = ModBanFromCommunity::create(&conn, &mod_ban_from_community_form).unwrap();
let read_moderator_ban_from_community = ModBanFromCommunity::read(&conn, inserted_mod_ban_from_community.id).unwrap();
let expected_moderator_ban_from_community = ModBanFromCommunity {
id: inserted_mod_ban_from_community.id,
community_id: inserted_community.id,
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
reason: None,
banned: Some(true),
expires: None,
when_: inserted_mod_ban_from_community.when_,
};
// ban
let mod_ban_form = ModBanForm {
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
reason: None,
banned: None,
expires: None,
};
let inserted_mod_ban = ModBan::create(&conn, &mod_ban_form).unwrap();
let read_moderator_ban = ModBan::read(&conn, inserted_mod_ban.id).unwrap();
let expected_moderator_ban = ModBan {
id: inserted_mod_ban.id,
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
reason: None,
banned: Some(true),
expires: None,
when_: inserted_mod_ban.when_,
};
// mod add community
let mod_add_community_form = ModAddCommunityForm {
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
community_id: inserted_community.id,
removed: None,
};
let inserted_mod_add_community = ModAddCommunity::create(&conn, &mod_add_community_form).unwrap();
let read_moderator_add_community = ModAddCommunity::read(&conn, inserted_mod_add_community.id).unwrap();
let expected_moderator_add_community = ModAddCommunity {
id: inserted_mod_add_community.id,
community_id: inserted_community.id,
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
removed: Some(false),
when_: inserted_mod_add_community.when_,
};
// mod add
let mod_add_form = ModAddForm {
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
removed: None,
};
let inserted_mod_add = ModAdd::create(&conn, &mod_add_form).unwrap();
let read_moderator_add = ModAdd::read(&conn, inserted_mod_add.id).unwrap();
let expected_moderator_add = ModAdd {
id: inserted_mod_add.id,
mod_user_id: inserted_mod.id,
other_user_id: inserted_user.id,
removed: Some(false),
when_: inserted_mod_add.when_,
};
ModRemovePost::delete(&conn, inserted_mod_remove_post.id).unwrap();
ModLockPost::delete(&conn, inserted_mod_lock_post.id).unwrap();
ModRemoveComment::delete(&conn, inserted_mod_remove_comment.id).unwrap();
ModRemoveCommunity::delete(&conn, inserted_mod_remove_community.id).unwrap();
ModBanFromCommunity::delete(&conn, inserted_mod_ban_from_community.id).unwrap();
ModBan::delete(&conn, inserted_mod_ban.id).unwrap();
ModAddCommunity::delete(&conn, inserted_mod_add_community.id).unwrap();
ModAdd::delete(&conn, inserted_mod_add.id).unwrap();
Comment::delete(&conn, inserted_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap();
User_::delete(&conn, inserted_user.id).unwrap();
User_::delete(&conn, inserted_mod.id).unwrap();
assert_eq!(expected_moderator_remove_post, read_moderator_remove_post);
assert_eq!(expected_moderator_lock_post, read_moderator_lock_post);
assert_eq!(expected_moderator_remove_comment, read_moderator_remove_comment);
assert_eq!(expected_moderator_remove_community, read_moderator_remove_community);
assert_eq!(expected_moderator_ban_from_community, read_moderator_ban_from_community);
assert_eq!(expected_moderator_ban, read_moderator_ban);
assert_eq!(expected_moderator_add_community, read_moderator_add_community);
assert_eq!(expected_moderator_add, read_moderator_add);
}
}

View file

@ -0,0 +1,427 @@
extern crate diesel;
use diesel::*;
use diesel::result::Error;
use serde::{Deserialize, Serialize};
table! {
mod_remove_post_view (id) {
id -> Int4,
mod_user_id -> Int4,
post_id -> Int4,
reason -> Nullable<Text>,
removed -> Nullable<Bool>,
when_ -> Timestamp,
mod_user_name -> Varchar,
post_name -> Varchar,
community_id -> Int4,
community_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_remove_post_view"]
pub struct ModRemovePostView {
pub id: i32,
pub mod_user_id: i32,
pub post_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub post_name: String,
pub community_id: i32,
pub community_name: String,
}
impl ModRemovePostView {
pub fn list(conn: &PgConnection,
from_community_id: Option<i32>,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_remove_post_view::dsl::*;
let mut query = mod_remove_post_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_community_id) = from_community_id {
query = query.filter(community_id.eq(from_community_id));
};
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}
table! {
mod_lock_post_view (id) {
id -> Int4,
mod_user_id -> Int4,
post_id -> Int4,
locked -> Nullable<Bool>,
when_ -> Timestamp,
mod_user_name -> Varchar,
post_name -> Varchar,
community_id -> Int4,
community_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_lock_post_view"]
pub struct ModLockPostView {
pub id: i32,
pub mod_user_id: i32,
pub post_id: i32,
pub locked: Option<bool>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub post_name: String,
pub community_id: i32,
pub community_name: String,
}
impl ModLockPostView {
pub fn list(conn: &PgConnection,
from_community_id: Option<i32>,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_lock_post_view::dsl::*;
let mut query = mod_lock_post_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_community_id) = from_community_id {
query = query.filter(community_id.eq(from_community_id));
};
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}
table! {
mod_remove_comment_view (id) {
id -> Int4,
mod_user_id -> Int4,
comment_id -> Int4,
reason -> Nullable<Text>,
removed -> Nullable<Bool>,
when_ -> Timestamp,
mod_user_name -> Varchar,
comment_user_id -> Int4,
comment_user_name -> Varchar,
comment_content -> Text,
post_id -> Int4,
post_name -> Varchar,
community_id -> Int4,
community_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_remove_comment_view"]
pub struct ModRemoveCommentView {
pub id: i32,
pub mod_user_id: i32,
pub comment_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub comment_user_id: i32,
pub comment_user_name: String,
pub comment_content: String,
pub post_id: i32,
pub post_name: String,
pub community_id: i32,
pub community_name: String,
}
impl ModRemoveCommentView {
pub fn list(conn: &PgConnection,
from_community_id: Option<i32>,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_remove_comment_view::dsl::*;
let mut query = mod_remove_comment_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_community_id) = from_community_id {
query = query.filter(community_id.eq(from_community_id));
};
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}
table! {
mod_remove_community_view (id) {
id -> Int4,
mod_user_id -> Int4,
community_id -> Int4,
reason -> Nullable<Text>,
removed -> Nullable<Bool>,
expires -> Nullable<Timestamp>,
when_ -> Timestamp,
mod_user_name -> Varchar,
community_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_remove_community_view"]
pub struct ModRemoveCommunityView {
pub id: i32,
pub mod_user_id: i32,
pub community_id: i32,
pub reason: Option<String>,
pub removed: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub community_name: String,
}
impl ModRemoveCommunityView {
pub fn list(conn: &PgConnection,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_remove_community_view::dsl::*;
let mut query = mod_remove_community_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}
table! {
mod_ban_from_community_view (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
community_id -> Int4,
reason -> Nullable<Text>,
banned -> Nullable<Bool>,
expires -> Nullable<Timestamp>,
when_ -> Timestamp,
mod_user_name -> Varchar,
other_user_name -> Varchar,
community_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_ban_from_community_view"]
pub struct ModBanFromCommunityView {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub community_id: i32,
pub reason: Option<String>,
pub banned: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub other_user_name: String,
pub community_name: String,
}
impl ModBanFromCommunityView {
pub fn list(conn: &PgConnection,
from_community_id: Option<i32>,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_ban_from_community_view::dsl::*;
let mut query = mod_ban_from_community_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_community_id) = from_community_id {
query = query.filter(community_id.eq(from_community_id));
};
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}
table! {
mod_ban_view (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
reason -> Nullable<Text>,
banned -> Nullable<Bool>,
expires -> Nullable<Timestamp>,
when_ -> Timestamp,
mod_user_name -> Varchar,
other_user_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_ban_view"]
pub struct ModBanView {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub reason: Option<String>,
pub banned: Option<bool>,
pub expires: Option<chrono::NaiveDateTime>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub other_user_name: String,
}
impl ModBanView {
pub fn list(conn: &PgConnection,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_ban_view::dsl::*;
let mut query = mod_ban_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}
table! {
mod_add_community_view (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
community_id -> Int4,
removed -> Nullable<Bool>,
when_ -> Timestamp,
mod_user_name -> Varchar,
other_user_name -> Varchar,
community_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_add_community_view"]
pub struct ModAddCommunityView {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub community_id: i32,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub other_user_name: String,
pub community_name: String,
}
impl ModAddCommunityView {
pub fn list(conn: &PgConnection,
from_community_id: Option<i32>,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_add_community_view::dsl::*;
let mut query = mod_add_community_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_community_id) = from_community_id {
query = query.filter(community_id.eq(from_community_id));
};
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}
table! {
mod_add_view (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
removed -> Nullable<Bool>,
when_ -> Timestamp,
mod_user_name -> Varchar,
other_user_name -> Varchar,
}
}
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
#[table_name="mod_add_view"]
pub struct ModAddView {
pub id: i32,
pub mod_user_id: i32,
pub other_user_id: i32,
pub removed: Option<bool>,
pub when_: chrono::NaiveDateTime,
pub mod_user_name: String,
pub other_user_name: String,
}
impl ModAddView {
pub fn list(conn: &PgConnection,
from_mod_user_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::moderator_views::mod_add_view::dsl::*;
let mut query = mod_add_view.into_boxed();
let page = page.unwrap_or(1);
let limit = limit.unwrap_or(10);
let offset = limit * (page - 1);
if let Some(from_mod_user_id) = from_mod_user_id {
query = query.filter(mod_user_id.eq(from_mod_user_id));
};
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
}
}

View file

@ -14,6 +14,8 @@ pub struct Post {
pub body: Option<String>, pub body: Option<String>,
pub creator_id: i32, pub creator_id: i32,
pub community_id: i32, pub community_id: i32,
pub removed: Option<bool>,
pub locked: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -26,6 +28,8 @@ pub struct PostForm {
pub body: Option<String>, pub body: Option<String>,
pub creator_id: i32, pub creator_id: i32,
pub community_id: i32, pub community_id: i32,
pub removed: Option<bool>,
pub locked: Option<bool>,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -115,17 +119,20 @@ mod tests {
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(), password_encrypted: "nope".into(),
email: None, email: None,
admin: None,
banned: None,
updated: None updated: None
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "test community_2".to_string(), name: "test community_3".to_string(),
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1, category_id: 1,
creator_id: inserted_user.id, creator_id: inserted_user.id,
removed: None,
updated: None updated: None
}; };
@ -137,6 +144,8 @@ mod tests {
body: None, body: None,
creator_id: inserted_user.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None,
locked: None,
updated: None updated: None
}; };
@ -150,6 +159,8 @@ mod tests {
creator_id: inserted_user.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
published: inserted_post.published, published: inserted_post.published,
removed: Some(false),
locked: Some(false),
updated: None updated: None
}; };

View file

@ -19,6 +19,8 @@ table! {
body -> Nullable<Text>, body -> Nullable<Text>,
creator_id -> Int4, creator_id -> Int4,
community_id -> Int4, community_id -> Int4,
removed -> Nullable<Bool>,
locked -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
creator_name -> Varchar, creator_name -> Varchar,
@ -31,6 +33,7 @@ table! {
user_id -> Nullable<Int4>, user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>, my_vote -> Nullable<Int4>,
subscribed -> Nullable<Bool>, subscribed -> Nullable<Bool>,
am_mod -> Nullable<Bool>,
} }
} }
@ -44,6 +47,8 @@ pub struct PostView {
pub body: Option<String>, pub body: Option<String>,
pub creator_id: i32, pub creator_id: i32,
pub community_id: i32, pub community_id: i32,
pub removed: Option<bool>,
pub locked: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub creator_name: String, pub creator_name: String,
@ -56,6 +61,7 @@ pub struct PostView {
pub user_id: Option<i32>, pub user_id: Option<i32>,
pub my_vote: Option<i32>, pub my_vote: Option<i32>,
pub subscribed: Option<bool>, pub subscribed: Option<bool>,
pub am_mod: Option<bool>,
} }
impl PostView { impl PostView {
@ -110,6 +116,8 @@ impl PostView {
.order_by(score.desc()) .order_by(score.desc())
}; };
query = query.filter(removed.eq(false));
query.load::<Self>(conn) query.load::<Self>(conn)
} }
@ -156,7 +164,9 @@ mod tests {
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(), password_encrypted: "nope".into(),
email: None, email: None,
updated: None updated: None,
admin: None,
banned: None,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
@ -167,6 +177,7 @@ mod tests {
description: None, description: None,
creator_id: inserted_user.id, creator_id: inserted_user.id,
category_id: 1, category_id: 1,
removed: None,
updated: None updated: None
}; };
@ -178,6 +189,8 @@ mod tests {
body: None, body: None,
creator_id: inserted_user.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None,
locked: None,
updated: None updated: None
}; };
@ -216,6 +229,8 @@ mod tests {
creator_id: inserted_user.id, creator_id: inserted_user.id,
creator_name: user_name.to_owned(), creator_name: user_name.to_owned(),
community_id: inserted_community.id, community_id: inserted_community.id,
removed: Some(false),
locked: Some(false),
community_name: community_name.to_owned(), community_name: community_name.to_owned(),
number_of_comments: 0, number_of_comments: 0,
score: 1, score: 1,
@ -224,7 +239,8 @@ mod tests {
hot_rank: 864, hot_rank: 864,
published: inserted_post.published, published: inserted_post.published,
updated: None, updated: None,
subscribed: None subscribed: None,
am_mod: None,
}; };
let expected_post_listing_with_user = PostView { let expected_post_listing_with_user = PostView {
@ -234,6 +250,8 @@ mod tests {
name: post_name.to_owned(), name: post_name.to_owned(),
url: None, url: None,
body: None, body: None,
removed: Some(false),
locked: Some(false),
creator_id: inserted_user.id, creator_id: inserted_user.id,
creator_name: user_name.to_owned(), creator_name: user_name.to_owned(),
community_id: inserted_community.id, community_id: inserted_community.id,
@ -245,7 +263,8 @@ mod tests {
hot_rank: 864, hot_rank: 864,
published: inserted_post.published, published: inserted_post.published,
updated: None, updated: None,
subscribed: None subscribed: None,
am_mod: None,
}; };
@ -274,6 +293,5 @@ mod tests {
assert_eq!(expected_post_like, inserted_post_like); assert_eq!(expected_post_like, inserted_post_like);
assert_eq!(1, like_removed); assert_eq!(1, like_removed);
assert_eq!(1, num_deleted); assert_eq!(1, num_deleted);
} }
} }

View file

@ -17,6 +17,8 @@ pub struct User_ {
pub password_encrypted: String, pub password_encrypted: String,
pub email: Option<String>, pub email: Option<String>,
pub icon: Option<Vec<u8>>, pub icon: Option<Vec<u8>>,
pub admin: Option<bool>,
pub banned: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -28,6 +30,8 @@ pub struct UserForm {
pub fedi_name: String, pub fedi_name: String,
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub password_encrypted: String, pub password_encrypted: String,
pub admin: Option<bool>,
pub banned: Option<bool>,
pub email: Option<String>, pub email: Option<String>,
pub updated: Option<chrono::NaiveDateTime> pub updated: Option<chrono::NaiveDateTime>
} }
@ -122,6 +126,8 @@ mod tests {
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(), password_encrypted: "nope".into(),
email: None, email: None,
admin: None,
banned: None,
updated: None updated: None
}; };
@ -135,6 +141,8 @@ mod tests {
password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(), password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
email: None, email: None,
icon: None, icon: None,
admin: Some(false),
banned: Some(false),
published: inserted_user.published, published: inserted_user.published,
updated: None updated: None
}; };

View file

@ -8,6 +8,8 @@ table! {
id -> Int4, id -> Int4,
name -> Varchar, name -> Varchar,
fedi_name -> Varchar, fedi_name -> Varchar,
admin -> Nullable<Bool>,
banned -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
number_of_posts -> BigInt, number_of_posts -> BigInt,
post_score -> BigInt, post_score -> BigInt,
@ -22,6 +24,8 @@ pub struct UserView {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub fedi_name: String, pub fedi_name: String,
pub admin: Option<bool>,
pub banned: Option<bool>,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub number_of_posts: i64, pub number_of_posts: i64,
pub post_score: i64, pub post_score: i64,

View file

@ -44,6 +44,8 @@ mod tests {
email: None, email: None,
icon: None, icon: None,
published: naive_now(), published: naive_now(),
admin: None,
banned: None,
updated: None updated: None
}; };

View file

@ -50,6 +50,11 @@ pub trait Likeable<T> {
fn remove(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized; fn remove(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
} }
pub trait Bannable<T> {
fn ban(conn: &PgConnection, form: &T) -> Result<Self, Error> where Self: Sized;
fn unban(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
}
pub fn establish_connection() -> PgConnection { pub fn establish_connection() -> PgConnection {
let db_url = Settings::get().db_url; let db_url = Settings::get().db_url;
PgConnection::establish(&db_url) PgConnection::establish(&db_url)
@ -88,6 +93,10 @@ pub fn naive_now() -> NaiveDateTime {
chrono::prelude::Utc::now().naive_utc() chrono::prelude::Utc::now().naive_utc()
} }
pub fn naive_from_unix(time: i64) -> NaiveDateTime {
NaiveDateTime::from_timestamp(time, 0)
}
pub fn is_email_regex(test: &str) -> bool { pub fn is_email_regex(test: &str) -> bool {
EMAIL_REGEX.is_match(test) EMAIL_REGEX.is_match(test)
} }

View file

@ -12,6 +12,7 @@ table! {
post_id -> Int4, post_id -> Int4,
parent_id -> Nullable<Int4>, parent_id -> Nullable<Int4>,
content -> Text, content -> Text,
removed -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
} }
@ -36,6 +37,7 @@ table! {
description -> Nullable<Text>, description -> Nullable<Text>,
category_id -> Int4, category_id -> Int4,
creator_id -> Int4, creator_id -> Int4,
removed -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
} }
@ -59,6 +61,105 @@ table! {
} }
} }
table! {
community_user_ban (id) {
id -> Int4,
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
}
}
table! {
mod_add (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
removed -> Nullable<Bool>,
when_ -> Timestamp,
}
}
table! {
mod_add_community (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
community_id -> Int4,
removed -> Nullable<Bool>,
when_ -> Timestamp,
}
}
table! {
mod_ban (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
reason -> Nullable<Text>,
banned -> Nullable<Bool>,
expires -> Nullable<Timestamp>,
when_ -> Timestamp,
}
}
table! {
mod_ban_from_community (id) {
id -> Int4,
mod_user_id -> Int4,
other_user_id -> Int4,
community_id -> Int4,
reason -> Nullable<Text>,
banned -> Nullable<Bool>,
expires -> Nullable<Timestamp>,
when_ -> Timestamp,
}
}
table! {
mod_lock_post (id) {
id -> Int4,
mod_user_id -> Int4,
post_id -> Int4,
locked -> Nullable<Bool>,
when_ -> Timestamp,
}
}
table! {
mod_remove_comment (id) {
id -> Int4,
mod_user_id -> Int4,
comment_id -> Int4,
reason -> Nullable<Text>,
removed -> Nullable<Bool>,
when_ -> Timestamp,
}
}
table! {
mod_remove_community (id) {
id -> Int4,
mod_user_id -> Int4,
community_id -> Int4,
reason -> Nullable<Text>,
removed -> Nullable<Bool>,
expires -> Nullable<Timestamp>,
when_ -> Timestamp,
}
}
table! {
mod_remove_post (id) {
id -> Int4,
mod_user_id -> Int4,
post_id -> Int4,
reason -> Nullable<Text>,
removed -> Nullable<Bool>,
when_ -> Timestamp,
}
}
table! { table! {
post (id) { post (id) {
id -> Int4, id -> Int4,
@ -67,6 +168,8 @@ table! {
body -> Nullable<Text>, body -> Nullable<Text>,
creator_id -> Int4, creator_id -> Int4,
community_id -> Int4, community_id -> Int4,
removed -> Nullable<Bool>,
locked -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
} }
@ -91,11 +194,21 @@ table! {
password_encrypted -> Text, password_encrypted -> Text,
email -> Nullable<Text>, email -> Nullable<Text>,
icon -> Nullable<Bytea>, icon -> Nullable<Bytea>,
admin -> Nullable<Bool>,
banned -> Nullable<Bool>,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
} }
} }
table! {
user_ban (id) {
id -> Int4,
user_id -> Int4,
published -> Timestamp,
}
}
joinable!(comment -> post (post_id)); joinable!(comment -> post (post_id));
joinable!(comment -> user_ (creator_id)); joinable!(comment -> user_ (creator_id));
joinable!(comment_like -> comment (comment_id)); joinable!(comment_like -> comment (comment_id));
@ -107,10 +220,23 @@ joinable!(community_follower -> community (community_id));
joinable!(community_follower -> user_ (user_id)); joinable!(community_follower -> user_ (user_id));
joinable!(community_moderator -> community (community_id)); joinable!(community_moderator -> community (community_id));
joinable!(community_moderator -> user_ (user_id)); joinable!(community_moderator -> user_ (user_id));
joinable!(community_user_ban -> community (community_id));
joinable!(community_user_ban -> user_ (user_id));
joinable!(mod_add_community -> community (community_id));
joinable!(mod_ban_from_community -> community (community_id));
joinable!(mod_lock_post -> post (post_id));
joinable!(mod_lock_post -> user_ (mod_user_id));
joinable!(mod_remove_comment -> comment (comment_id));
joinable!(mod_remove_comment -> user_ (mod_user_id));
joinable!(mod_remove_community -> community (community_id));
joinable!(mod_remove_community -> user_ (mod_user_id));
joinable!(mod_remove_post -> post (post_id));
joinable!(mod_remove_post -> user_ (mod_user_id));
joinable!(post -> community (community_id)); joinable!(post -> community (community_id));
joinable!(post -> user_ (creator_id)); joinable!(post -> user_ (creator_id));
joinable!(post_like -> post (post_id)); joinable!(post_like -> post (post_id));
joinable!(post_like -> user_ (user_id)); joinable!(post_like -> user_ (user_id));
joinable!(user_ban -> user_ (user_id));
allow_tables_to_appear_in_same_query!( allow_tables_to_appear_in_same_query!(
category, category,
@ -119,7 +245,17 @@ allow_tables_to_appear_in_same_query!(
community, community,
community_follower, community_follower,
community_moderator, community_moderator,
community_user_ban,
mod_add,
mod_add_community,
mod_ban,
mod_ban_from_community,
mod_lock_post,
mod_remove_comment,
mod_remove_community,
mod_remove_post,
post, post,
post_like, post_like,
user_, user_,
user_ban,
); );

View file

@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize};
use serde_json::{Value}; use serde_json::{Value};
use bcrypt::{verify}; use bcrypt::{verify};
use std::str::FromStr; use std::str::FromStr;
use diesel::PgConnection;
use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType, has_slurs, remove_slurs}; use {Crud, Joinable, Likeable, Followable, Bannable, establish_connection, naive_now, naive_from_unix, SortType, has_slurs, remove_slurs};
use actions::community::*; use actions::community::*;
use actions::user::*; use actions::user::*;
use actions::post::*; use actions::post::*;
@ -20,10 +21,12 @@ use actions::comment_view::*;
use actions::category::*; use actions::category::*;
use actions::community_view::*; use actions::community_view::*;
use actions::user_view::*; use actions::user_view::*;
use actions::moderator_views::*;
use actions::moderator::*;
#[derive(EnumString,ToString,Debug)] #[derive(EnumString,ToString,Debug)]
pub enum UserOperation { pub enum UserOperation {
Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -204,7 +207,10 @@ pub struct EditComment {
content: String, content: String,
parent_id: Option<i32>, parent_id: Option<i32>,
edit_id: i32, edit_id: i32,
creator_id: i32,
post_id: i32, post_id: i32,
removed: Option<bool>,
reason: Option<String>,
auth: String auth: String
} }
@ -222,7 +228,6 @@ pub struct CreateCommentLike {
auth: String auth: String
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct CreatePostLike { pub struct CreatePostLike {
post_id: i32, post_id: i32,
@ -240,10 +245,14 @@ pub struct CreatePostLikeResponse {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct EditPost { pub struct EditPost {
edit_id: i32, edit_id: i32,
creator_id: i32,
community_id: i32, community_id: i32,
name: String, name: String,
url: Option<String>, url: Option<String>,
body: Option<String>, body: Option<String>,
removed: Option<bool>,
reason: Option<String>,
locked: Option<bool>,
auth: String auth: String
} }
@ -254,6 +263,9 @@ pub struct EditCommunity {
title: String, title: String,
description: Option<String>, description: Option<String>,
category_id: i32, category_id: i32,
removed: Option<bool>,
reason: Option<String>,
expires: Option<i64>,
auth: String auth: String
} }
@ -296,6 +308,59 @@ pub struct GetUserDetailsResponse {
saved_comments: Vec<CommentView>, saved_comments: Vec<CommentView>,
} }
#[derive(Serialize, Deserialize)]
pub struct GetModlog {
mod_user_id: Option<i32>,
community_id: Option<i32>,
limit: Option<i64>,
page: Option<i64>,
}
#[derive(Serialize, Deserialize)]
pub struct GetModlogResponse {
op: String,
removed_posts: Vec<ModRemovePostView>,
locked_posts: Vec<ModLockPostView>,
removed_comments: Vec<ModRemoveCommentView>,
removed_communities: Vec<ModRemoveCommunityView>,
banned_from_community: Vec<ModBanFromCommunityView>,
banned: Vec<ModBanView>,
added_to_community: Vec<ModAddCommunityView>,
added: Vec<ModAddView>,
}
#[derive(Serialize, Deserialize)]
pub struct BanFromCommunity {
community_id: i32,
user_id: i32,
ban: bool,
reason: Option<String>,
expires: Option<i64>,
auth: String
}
#[derive(Serialize, Deserialize)]
pub struct BanFromCommunityResponse {
op: String,
user: UserView,
banned: bool,
}
#[derive(Serialize, Deserialize)]
pub struct AddModToCommunity {
community_id: i32,
user_id: i32,
added: bool,
auth: String
}
#[derive(Serialize, Deserialize)]
pub struct AddModToCommunityResponse {
op: String,
moderators: Vec<CommunityModeratorView>,
}
/// `ChatServer` manages chat rooms and responsible for coordinating chat /// `ChatServer` manages chat rooms and responsible for coordinating chat
/// session. implementation is super primitive /// session. implementation is super primitive
pub struct ChatServer { pub struct ChatServer {
@ -330,6 +395,19 @@ impl ChatServer {
} }
} }
} }
fn send_community_message(&self, conn: &PgConnection, community_id: i32, message: &str, skip_id: usize) {
let posts = PostView::list(conn,
PostListingType::Community,
&SortType::New,
Some(community_id),
None,
None,
999).unwrap();
for post in posts {
self.send_room_message(post.id, message, skip_id);
}
}
} }
/// Make actor from `ChatServer` /// Make actor from `ChatServer`
@ -397,6 +475,9 @@ impl Handler<StandardMessage> for ChatServer {
let op = &json["op"].as_str().unwrap(); let op = &json["op"].as_str().unwrap();
let user_operation: UserOperation = UserOperation::from_str(&op).unwrap(); let user_operation: UserOperation = UserOperation::from_str(&op).unwrap();
// TODO figure out how to do proper error handling here, instead of just returning
// error strings
let res: String = match user_operation { let res: String = match user_operation {
UserOperation::Login => { UserOperation::Login => {
let login: Login = serde_json::from_str(data).unwrap(); let login: Login = serde_json::from_str(data).unwrap();
@ -470,13 +551,18 @@ impl Handler<StandardMessage> for ChatServer {
let get_user_details: GetUserDetails = serde_json::from_str(data).unwrap(); let get_user_details: GetUserDetails = serde_json::from_str(data).unwrap();
get_user_details.perform(self, msg.id) get_user_details.perform(self, msg.id)
}, },
// _ => { UserOperation::GetModlog => {
// let e = ErrorMessage { let get_modlog: GetModlog = serde_json::from_str(data).unwrap();
// op: "Unknown".to_string(), get_modlog.perform(self, msg.id)
// error: "Unknown User Operation".to_string() },
// }; UserOperation::BanFromCommunity => {
// serde_json::to_string(&e).unwrap() let ban_from_community: BanFromCommunity = serde_json::from_str(data).unwrap();
// } ban_from_community.perform(self, msg.id)
},
UserOperation::AddModToCommunity => {
let mod_add_to_community: AddModToCommunity = serde_json::from_str(data).unwrap();
mod_add_to_community.perform(self, msg.id)
},
}; };
MessageResult(res) MessageResult(res)
@ -554,7 +640,9 @@ impl Perform for Register {
email: self.email.to_owned(), email: self.email.to_owned(),
password_encrypted: self.password.to_owned(), password_encrypted: self.password.to_owned(),
preferred_username: None, preferred_username: None,
updated: None updated: None,
admin: None,
banned: None,
}; };
// Create the user // Create the user
@ -609,7 +697,8 @@ impl Perform for CreateCommunity {
description: self.description.to_owned(), description: self.description.to_owned(),
category_id: self.category_id, category_id: self.category_id,
creator_id: user_id, creator_id: user_id,
updated: None removed: None,
updated: None,
}; };
let inserted_community = match Community::create(&conn, &community_form) { let inserted_community = match Community::create(&conn, &community_form) {
@ -737,12 +826,19 @@ impl Perform for CreatePost {
let user_id = claims.id; let user_id = claims.id;
// Check for a ban
if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() {
return self.error("You have been banned from this community");
}
let post_form = PostForm { let post_form = PostForm {
name: self.name.to_owned(), name: self.name.to_owned(),
url: self.url.to_owned(), url: self.url.to_owned(),
body: self.body.to_owned(), body: self.body.to_owned(),
community_id: self.community_id, community_id: self.community_id,
creator_id: user_id, creator_id: user_id,
removed: None,
locked: None,
updated: None updated: None
}; };
@ -913,6 +1009,12 @@ impl Perform for CreateComment {
let user_id = claims.id; let user_id = claims.id;
// Check for a ban
let post = Post::read(&conn, self.post_id).unwrap();
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
return self.error("You have been banned from this community");
}
let content_slurs_removed = remove_slurs(&self.content.to_owned()); let content_slurs_removed = remove_slurs(&self.content.to_owned());
let comment_form = CommentForm { let comment_form = CommentForm {
@ -920,6 +1022,7 @@ impl Perform for CreateComment {
parent_id: self.parent_id.to_owned(), parent_id: self.parent_id.to_owned(),
post_id: self.post_id, post_id: self.post_id,
creator_id: user_id, creator_id: user_id,
removed: None,
updated: None updated: None
}; };
@ -991,10 +1094,22 @@ impl Perform for EditComment {
let user_id = claims.id; let user_id = claims.id;
// Verify its the creator
let orig_comment = Comment::read(&conn, self.edit_id).unwrap(); // Verify its the creator or a mod
if user_id != orig_comment.creator_id { let orig_comment = CommentView::read(&conn, self.edit_id, None).unwrap();
return self.error("Incorrect creator."); let mut editors: Vec<i32> = CommunityModeratorView::for_community(&conn, orig_comment.community_id)
.unwrap()
.into_iter()
.map(|m| m.user_id)
.collect();
editors.push(self.creator_id);
if !editors.contains(&user_id) {
return self.error("Not allowed to edit comment.");
}
// Check for a ban
if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() {
return self.error("You have been banned from this community");
} }
let content_slurs_removed = remove_slurs(&self.content.to_owned()); let content_slurs_removed = remove_slurs(&self.content.to_owned());
@ -1003,7 +1118,8 @@ impl Perform for EditComment {
content: content_slurs_removed, content: content_slurs_removed,
parent_id: self.parent_id, parent_id: self.parent_id,
post_id: self.post_id, post_id: self.post_id,
creator_id: user_id, creator_id: self.creator_id,
removed: self.removed.to_owned(),
updated: Some(naive_now()) updated: Some(naive_now())
}; };
@ -1014,6 +1130,17 @@ impl Perform for EditComment {
} }
}; };
// Mod tables
if let Some(removed) = self.removed.to_owned() {
let form = ModRemoveCommentForm {
mod_user_id: user_id,
comment_id: self.edit_id,
removed: Some(removed),
reason: self.reason.to_owned(),
};
ModRemoveComment::create(&conn, &form).unwrap();
}
let comment_view = CommentView::read(&conn, self.edit_id, Some(user_id)).unwrap(); let comment_view = CommentView::read(&conn, self.edit_id, Some(user_id)).unwrap();
@ -1061,6 +1188,12 @@ impl Perform for CreateCommentLike {
let user_id = claims.id; let user_id = claims.id;
// Check for a ban
let post = Post::read(&conn, self.post_id).unwrap();
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
return self.error("You have been banned from this community");
}
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: self.comment_id, comment_id: self.comment_id,
post_id: self.post_id, post_id: self.post_id,
@ -1173,6 +1306,12 @@ impl Perform for CreatePostLike {
let user_id = claims.id; let user_id = claims.id;
// Check for a ban
let post = Post::read(&conn, self.post_id).unwrap();
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
return self.error("You have been banned from this community");
}
let like_form = PostLikeForm { let like_form = PostLikeForm {
post_id: self.post_id, post_id: self.post_id,
user_id: user_id, user_id: user_id,
@ -1236,18 +1375,30 @@ impl Perform for EditPost {
let user_id = claims.id; let user_id = claims.id;
// Verify its the creator // Verify its the creator or a mod
let orig_post = Post::read(&conn, self.edit_id).unwrap(); let mut editors: Vec<i32> = CommunityModeratorView::for_community(&conn, self.community_id)
if user_id != orig_post.creator_id { .unwrap()
return self.error("Incorrect creator."); .into_iter()
.map(|m| m.user_id)
.collect();
editors.push(self.creator_id);
if !editors.contains(&user_id) {
return self.error("Not allowed to edit comment.");
}
// Check for a ban
if CommunityUserBanView::get(&conn, user_id, self.community_id).is_ok() {
return self.error("You have been banned from this community");
} }
let post_form = PostForm { let post_form = PostForm {
name: self.name.to_owned(), name: self.name.to_owned(),
url: self.url.to_owned(), url: self.url.to_owned(),
body: self.body.to_owned(), body: self.body.to_owned(),
creator_id: user_id, creator_id: self.creator_id.to_owned(),
community_id: self.community_id, community_id: self.community_id,
removed: self.removed.to_owned(),
locked: self.locked.to_owned(),
updated: Some(naive_now()) updated: Some(naive_now())
}; };
@ -1258,6 +1409,26 @@ impl Perform for EditPost {
} }
}; };
// Mod tables
if let Some(removed) = self.removed.to_owned() {
let form = ModRemovePostForm {
mod_user_id: user_id,
post_id: self.edit_id,
removed: Some(removed),
reason: self.reason.to_owned(),
};
ModRemovePost::create(&conn, &form).unwrap();
}
if let Some(locked) = self.locked.to_owned() {
let form = ModLockPostForm {
mod_user_id: user_id,
post_id: self.edit_id,
locked: Some(locked),
};
ModLockPost::create(&conn, &form).unwrap();
}
let post_view = PostView::read(&conn, self.edit_id, Some(user_id)).unwrap(); let post_view = PostView::read(&conn, self.edit_id, Some(user_id)).unwrap();
let mut post_sent = post_view.clone(); let mut post_sent = post_view.clone();
@ -1307,7 +1478,6 @@ impl Perform for EditCommunity {
let user_id = claims.id; let user_id = claims.id;
// Verify its a mod // Verify its a mod
let moderator_view = CommunityModeratorView::for_community(&conn, self.edit_id).unwrap(); let moderator_view = CommunityModeratorView::for_community(&conn, self.edit_id).unwrap();
let mod_ids: Vec<i32> = moderator_view.into_iter().map(|m| m.user_id).collect(); let mod_ids: Vec<i32> = moderator_view.into_iter().map(|m| m.user_id).collect();
@ -1321,6 +1491,7 @@ impl Perform for EditCommunity {
description: self.description.to_owned(), description: self.description.to_owned(),
category_id: self.category_id.to_owned(), category_id: self.category_id.to_owned(),
creator_id: user_id, creator_id: user_id,
removed: self.removed.to_owned(),
updated: Some(naive_now()) updated: Some(naive_now())
}; };
@ -1331,11 +1502,23 @@ impl Perform for EditCommunity {
} }
}; };
let community_view = CommunityView::read(&conn, self.edit_id, Some(user_id)).unwrap(); // Mod tables
if let Some(removed) = self.removed.to_owned() {
let expires = match self.expires {
Some(time) => Some(naive_from_unix(time)),
None => None
};
let form = ModRemoveCommunityForm {
mod_user_id: user_id,
community_id: self.edit_id,
removed: Some(removed),
reason: self.reason.to_owned(),
expires: expires
};
ModRemoveCommunity::create(&conn, &form).unwrap();
}
// Do the subscriber stuff here let community_view = CommunityView::read(&conn, self.edit_id, Some(user_id)).unwrap();
// let mut community_sent = post_view.clone();
// community_sent.my_vote = None;
let community_out = serde_json::to_string( let community_out = serde_json::to_string(
&CommunityResponse { &CommunityResponse {
@ -1345,15 +1528,17 @@ impl Perform for EditCommunity {
) )
.unwrap(); .unwrap();
// let post_sent_out = serde_json::to_string( let community_view_sent = CommunityView::read(&conn, self.edit_id, None).unwrap();
// &PostResponse {
// op: self.op_type().to_string(),
// post: post_sent
// }
// )
// .unwrap();
chat.send_room_message(self.edit_id, &community_out, addr); let community_sent = serde_json::to_string(
&CommunityResponse {
op: self.op_type().to_string(),
community: community_view_sent
}
)
.unwrap();
chat.send_community_message(&conn, self.edit_id, &community_sent, addr);
community_out community_out
} }
@ -1492,3 +1677,178 @@ impl Perform for GetUserDetails {
} }
} }
impl Perform for GetModlog {
fn op_type(&self) -> UserOperation {
UserOperation::GetModlog
}
fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String {
let conn = establish_connection();
let removed_posts = ModRemovePostView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap();
let locked_posts = ModLockPostView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap();
let removed_comments = ModRemoveCommentView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap();
let removed_communities = ModRemoveCommunityView::list(&conn, self.mod_user_id, self.limit, self.page).unwrap();
let banned_from_community = ModBanFromCommunityView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap();
let banned = ModBanView::list(&conn, self.mod_user_id, self.limit, self.page).unwrap();
let added_to_community = ModAddCommunityView::list(&conn, self.community_id, self.mod_user_id, self.limit, self.page).unwrap();
let added = ModAddView::list(&conn, self.mod_user_id, self.limit, self.page).unwrap();
// Return the jwt
serde_json::to_string(
&GetModlogResponse {
op: self.op_type().to_string(),
removed_posts: removed_posts,
locked_posts: locked_posts,
removed_comments: removed_comments,
removed_communities: removed_communities,
banned_from_community: banned_from_community,
banned: banned,
added_to_community: added_to_community,
added: added,
}
)
.unwrap()
}
}
impl Perform for BanFromCommunity {
fn op_type(&self) -> UserOperation {
UserOperation::BanFromCommunity
}
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
let conn = establish_connection();
let claims = match Claims::decode(&self.auth) {
Ok(claims) => claims.claims,
Err(_e) => {
return self.error("Not logged in.");
}
};
let user_id = claims.id;
let community_user_ban_form = CommunityUserBanForm {
community_id: self.community_id,
user_id: self.user_id,
};
if self.ban {
match CommunityUserBan::ban(&conn, &community_user_ban_form) {
Ok(user) => user,
Err(_e) => {
return self.error("Community user ban already exists");
}
};
} else {
match CommunityUserBan::unban(&conn, &community_user_ban_form) {
Ok(user) => user,
Err(_e) => {
return self.error("Community user ban already exists");
}
};
}
// Mod tables
let expires = match self.expires {
Some(time) => Some(naive_from_unix(time)),
None => None
};
let form = ModBanFromCommunityForm {
mod_user_id: user_id,
other_user_id: self.user_id,
community_id: self.community_id,
reason: self.reason.to_owned(),
banned: Some(self.ban),
expires: expires,
};
ModBanFromCommunity::create(&conn, &form).unwrap();
let user_view = UserView::read(&conn, self.user_id).unwrap();
let res = serde_json::to_string(
&BanFromCommunityResponse {
op: self.op_type().to_string(),
user: user_view,
banned: self.ban
}
)
.unwrap();
chat.send_community_message(&conn, self.community_id, &res, addr);
res
}
}
impl Perform for AddModToCommunity {
fn op_type(&self) -> UserOperation {
UserOperation::AddModToCommunity
}
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
let conn = establish_connection();
let claims = match Claims::decode(&self.auth) {
Ok(claims) => claims.claims,
Err(_e) => {
return self.error("Not logged in.");
}
};
let user_id = claims.id;
let community_moderator_form = CommunityModeratorForm {
community_id: self.community_id,
user_id: self.user_id
};
if self.added {
match CommunityModerator::join(&conn, &community_moderator_form) {
Ok(user) => user,
Err(_e) => {
return self.error("Community moderator already exists.");
}
};
} else {
match CommunityModerator::leave(&conn, &community_moderator_form) {
Ok(user) => user,
Err(_e) => {
return self.error("Community moderator already exists.");
}
};
}
// Mod tables
let form = ModAddCommunityForm {
mod_user_id: user_id,
other_user_id: self.user_id,
community_id: self.community_id,
removed: Some(!self.added),
};
ModAddCommunity::create(&conn, &form).unwrap();
let moderators = CommunityModeratorView::for_community(&conn, self.community_id).unwrap();
let res = serde_json::to_string(
&AddModToCommunityResponse {
op: self.op_type().to_string(),
moderators: moderators,
}
)
.unwrap();
chat.send_community_message(&conn, self.community_id, &res, addr);
res
}
}

View file

@ -1,6 +1,6 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces'; import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService, UserService } from '../services';
import * as autosize from 'autosize'; import * as autosize from 'autosize';
interface CommentFormProps { interface CommentFormProps {
@ -8,6 +8,7 @@ interface CommentFormProps {
node?: CommentNodeI; node?: CommentNodeI;
onReplyCancel?(): any; onReplyCancel?(): any;
edit?: boolean; edit?: boolean;
disabled?: boolean;
} }
interface CommentFormState { interface CommentFormState {
@ -21,9 +22,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
commentForm: { commentForm: {
auth: null, auth: null,
content: null, content: null,
post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId,
creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null,
}, },
buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply" buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply",
} }
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -36,6 +38,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
this.state.commentForm.edit_id = this.props.node.comment.id; this.state.commentForm.edit_id = this.props.node.comment.id;
this.state.commentForm.parent_id = this.props.node.comment.parent_id; this.state.commentForm.parent_id = this.props.node.comment.parent_id;
this.state.commentForm.content = this.props.node.comment.content; this.state.commentForm.content = this.props.node.comment.content;
this.state.commentForm.creator_id = this.props.node.comment.creator_id;
} else { } else {
// A reply gets a new parent id // A reply gets a new parent id
this.state.commentForm.parent_id = this.props.node.comment.id; this.state.commentForm.parent_id = this.props.node.comment.id;
@ -53,12 +56,12 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
<form onSubmit={linkEvent(this, this.handleCommentSubmit)}> <form onSubmit={linkEvent(this, this.handleCommentSubmit)}>
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-12"> <div class="col-sm-12">
<textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required /> <textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required disabled={this.props.disabled}/>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<button type="submit" class="btn btn-sm btn-secondary mr-2">{this.state.buttonTitle}</button> <button type="submit" class="btn btn-sm btn-secondary mr-2" disabled={this.props.disabled}>{this.state.buttonTitle}</button>
{this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>} {this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>}
</div> </div>
</div> </div>

View file

@ -1,8 +1,8 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI } from '../interfaces'; import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, BanFromCommunityForm, CommunityUser, AddModToCommunityForm } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { mdToHtml } from '../utils'; import { mdToHtml, getUnixTime } from '../utils';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
import { CommentForm } from './comment-form'; import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
@ -10,19 +10,31 @@ import { CommentNodes } from './comment-nodes';
interface CommentNodeState { interface CommentNodeState {
showReply: boolean; showReply: boolean;
showEdit: boolean; showEdit: boolean;
showRemoveDialog: boolean;
removeReason: string;
showBanDialog: boolean;
banReason: string;
banExpires: string;
} }
interface CommentNodeProps { interface CommentNodeProps {
node: CommentNodeI; node: CommentNodeI;
noIndent?: boolean; noIndent?: boolean;
viewOnly?: boolean; viewOnly?: boolean;
locked?: boolean;
moderators: Array<CommunityUser>;
} }
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
private emptyState: CommentNodeState = { private emptyState: CommentNodeState = {
showReply: false, showReply: false,
showEdit: false showEdit: false,
showRemoveDialog: false,
removeReason: null,
showBanDialog: false,
banReason: null,
banExpires: null,
} }
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -60,10 +72,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<span><MomentTime data={node.comment} /></span> <span><MomentTime data={node.comment} /></span>
</li> </li>
</ul> </ul>
{this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} />} {this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} />}
{!this.state.showEdit && {!this.state.showEdit &&
<div> <div>
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.content)} /> <div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.removed ? '*removed*' : node.comment.content)} />
<ul class="list-inline mb-1 text-muted small font-weight-bold"> <ul class="list-inline mb-1 text-muted small font-weight-bold">
{!this.props.viewOnly && {!this.props.viewOnly &&
<span class="mr-2"> <span class="mr-2">
@ -71,14 +83,39 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}>reply</span> <span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}>reply</span>
</li> </li>
{this.myComment && {this.myComment &&
<>
<li className="list-inline-item"> <li className="list-inline-item">
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
</li> </li>
}
{this.myComment &&
<li className="list-inline-item"> <li className="list-inline-item">
<span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span>
</li> </li>
</>
}
{this.canMod &&
<>
<li className="list-inline-item">
{!this.props.node.comment.removed ?
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
}
</li>
{!this.isMod &&
<>
<li className="list-inline-item">
{!this.props.node.comment.banned ?
<span class="pointer" onClick={linkEvent(this, this.handleModBanShow)}>ban</span> :
<span class="pointer" onClick={linkEvent(this, this.handleModBanSubmit)}>unban</span>
}
</li>
</>
}
{!this.props.node.comment.banned &&
<li className="list-inline-item">
<span class="pointer" onClick={linkEvent(this, this.handleAddModToCommunity)}>{`${this.isMod ? 'remove' : 'appoint'} as mod`}</span>
</li>
}
</>
} }
</span> </span>
} }
@ -89,16 +126,61 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</div> </div>
} }
</div> </div>
{this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} />} {this.state.showRemoveDialog &&
{this.props.node.children && <CommentNodes nodes={this.props.node.children} />} <form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
<input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} />
<button type="submit" class="btn btn-secondary">Remove Comment</button>
</form>
}
{this.state.showBanDialog &&
<form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
<div class="form-group row">
<label class="col-form-label">Reason</label>
<input type="text" class="form-control mr-2" placeholder="Optional" value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} />
</div>
<div class="form-group row">
<label class="col-form-label">Expires</label>
<input type="date" class="form-control mr-2" placeholder="Expires" value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} />
</div>
<div class="form-group row">
<button type="submit" class="btn btn-secondary">Ban {this.props.node.comment.creator_name}</button>
</div>
</form>
}
{this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} />}
{this.props.node.children && <CommentNodes nodes={this.props.node.children} locked={this.props.locked} moderators={this.props.moderators}/>}
</div> </div>
) )
} }
private get myComment(): boolean { get myComment(): boolean {
return UserService.Instance.loggedIn && this.props.node.comment.creator_id == UserService.Instance.user.id; return UserService.Instance.loggedIn && this.props.node.comment.creator_id == UserService.Instance.user.id;
} }
get canMod(): boolean {
// You can do moderator actions only on the mods added after you.
if (UserService.Instance.loggedIn) {
let modIds = this.props.moderators.map(m => m.user_id);
let yourIndex = modIds.findIndex(id => id == UserService.Instance.user.id);
if (yourIndex == -1) {
return false;
} else {
console.log(modIds);
modIds = modIds.slice(0, yourIndex+1); // +1 cause you cant mod yourself
console.log(modIds);
return !modIds.includes(this.props.node.comment.creator_id);
}
} else {
return false;
}
}
get isMod(): boolean {
return this.props.moderators.map(m => m.user_id).includes(this.props.node.comment.creator_id);
}
handleReplyClick(i: CommentNode) { handleReplyClick(i: CommentNode) {
i.state.showReply = true; i.state.showReply = true;
i.setState(i.state); i.setState(i.state);
@ -113,6 +195,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
let deleteForm: CommentFormI = { let deleteForm: CommentFormI = {
content: "*deleted*", content: "*deleted*",
edit_id: i.props.node.comment.id, edit_id: i.props.node.comment.id,
creator_id: i.props.node.comment.creator_id,
post_id: i.props.node.comment.post_id, post_id: i.props.node.comment.post_id,
parent_id: i.props.node.comment.parent_id, parent_id: i.props.node.comment.parent_id,
auth: null auth: null
@ -145,4 +228,70 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}; };
WebSocketService.Instance.likeComment(form); WebSocketService.Instance.likeComment(form);
} }
handleModRemoveShow(i: CommentNode) {
i.state.showRemoveDialog = true;
i.setState(i.state);
}
handleModRemoveReasonChange(i: CommentNode, event: any) {
i.state.removeReason = event.target.value;
i.setState(i.state);
}
handleModRemoveSubmit(i: CommentNode) {
let form: CommentFormI = {
content: i.props.node.comment.content,
edit_id: i.props.node.comment.id,
creator_id: i.props.node.comment.creator_id,
post_id: i.props.node.comment.post_id,
parent_id: i.props.node.comment.parent_id,
removed: !i.props.node.comment.removed,
reason: i.state.removeReason,
auth: null
};
WebSocketService.Instance.editComment(form);
i.state.showRemoveDialog = false;
i.setState(i.state);
}
handleModBanShow(i: CommentNode) {
i.state.showBanDialog = true;
i.setState(i.state);
}
handleModBanReasonChange(i: CommentNode, event: any) {
i.state.banReason = event.target.value;
i.setState(i.state);
}
handleModBanExpiresChange(i: CommentNode, event: any) {
i.state.banExpires = event.target.value;
i.setState(i.state);
}
handleModBanSubmit(i: CommentNode) {
let form: BanFromCommunityForm = {
user_id: i.props.node.comment.creator_id,
community_id: i.props.node.comment.community_id,
ban: !i.props.node.comment.banned,
reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires),
};
WebSocketService.Instance.banFromCommunity(form);
i.state.showBanDialog = false;
i.setState(i.state);
}
handleAddModToCommunity(i: CommentNode) {
let form: AddModToCommunityForm = {
user_id: i.props.node.comment.creator_id,
community_id: i.props.node.comment.community_id,
added: !i.isMod,
};
WebSocketService.Instance.addModToCommunity(form);
i.setState(i.state);
}
} }

View file

@ -1,5 +1,5 @@
import { Component } from 'inferno'; import { Component } from 'inferno';
import { CommentNode as CommentNodeI } from '../interfaces'; import { CommentNode as CommentNodeI, CommunityUser } from '../interfaces';
import { CommentNode } from './comment-node'; import { CommentNode } from './comment-node';
interface CommentNodesState { interface CommentNodesState {
@ -7,8 +7,10 @@ interface CommentNodesState {
interface CommentNodesProps { interface CommentNodesProps {
nodes: Array<CommentNodeI>; nodes: Array<CommentNodeI>;
moderators: Array<CommunityUser>;
noIndent?: boolean; noIndent?: boolean;
viewOnly?: boolean; viewOnly?: boolean;
locked?: boolean;
} }
export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> { export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> {
@ -21,10 +23,15 @@ export class CommentNodes extends Component<CommentNodesProps, CommentNodesState
return ( return (
<div className="comments"> <div className="comments">
{this.props.nodes.map(node => {this.props.nodes.map(node =>
<CommentNode node={node} noIndent={this.props.noIndent} viewOnly={this.props.viewOnly}/> <CommentNode node={node}
noIndent={this.props.noIndent}
viewOnly={this.props.viewOnly}
locked={this.props.locked}
moderators={this.props.moderators}/>
)} )}
</div> </div>
) )
} }
} }

View file

@ -102,7 +102,6 @@ export class Communities extends Component<any, CommunitiesState> {
WebSocketService.Instance.followCommunity(form); WebSocketService.Instance.followCommunity(form);
} }
handleSubscribe(communityId: number) { handleSubscribe(communityId: number) {
let form: FollowCommunityForm = { let form: FollowCommunityForm = {
community_id: communityId, community_id: communityId,
@ -129,6 +128,6 @@ export class Communities extends Component<any, CommunitiesState> {
found.subscribed = res.community.subscribed; found.subscribed = res.community.subscribed;
found.number_of_subscribers = res.community.number_of_subscribers; found.number_of_subscribers = res.community.number_of_subscribers;
this.setState(this.state); this.setState(this.state);
} }
} }
} }

View file

@ -63,7 +63,11 @@ export class Community extends Component<any, State> {
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> : <h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div class="row"> <div class="row">
<div class="col-12 col-md-9"> <div class="col-12 col-md-9">
<h4>{this.state.community.title}</h4> <h4>{this.state.community.title}
{this.state.community.removed &&
<small className="ml-2 text-muted font-italic">removed</small>
}
</h4>
<PostListings communityId={this.state.communityId} /> <PostListings communityId={this.state.communityId} />
</div> </div>
<div class="col-12 col-md-3"> <div class="col-12 col-md-3">

View file

@ -0,0 +1,184 @@
import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, GetModlogForm, GetModlogResponse, ModRemovePost, ModLockPost, ModRemoveComment, ModRemoveCommunity, ModBanFromCommunity, ModBan, ModAddCommunity, ModAdd } from '../interfaces';
import { WebSocketService } from '../services';
import { msgOp, addTypeInfo } from '../utils';
import { MomentTime } from './moment-time';
import * as moment from 'moment';
interface ModlogState {
removed_posts: Array<ModRemovePost>,
locked_posts: Array<ModLockPost>,
removed_comments: Array<ModRemoveComment>,
removed_communities: Array<ModRemoveCommunity>,
banned_from_community: Array<ModBanFromCommunity>,
banned: Array<ModBan>,
added_to_community: Array<ModAddCommunity>,
added: Array<ModAdd>,
loading: boolean;
}
export class Modlog extends Component<any, ModlogState> {
private subscription: Subscription;
private emptyState: ModlogState = {
removed_posts: [],
locked_posts: [],
removed_comments: [],
removed_communities: [],
banned_from_community: [],
banned: [],
added_to_community: [],
added: [],
loading: true
}
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
(err) => console.error(err),
() => console.log('complete')
);
let modlogForm: GetModlogForm = {
};
WebSocketService.Instance.getModlog(modlogForm);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
combined() {
let combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}> = [];
let removed_posts = addTypeInfo(this.state.removed_posts, "removed_posts");
let locked_posts = addTypeInfo(this.state.locked_posts, "locked_posts");
let removed_comments = addTypeInfo(this.state.removed_comments, "removed_comments");
let removed_communities = addTypeInfo(this.state.removed_communities, "removed_communities");
let banned_from_community = addTypeInfo(this.state.banned_from_community, "banned_from_community");
let added_to_community = addTypeInfo(this.state.added_to_community, "added_to_community");
combined.push(...removed_posts);
combined.push(...locked_posts);
combined.push(...removed_comments);
combined.push(...removed_communities);
combined.push(...banned_from_community);
combined.push(...added_to_community);
// Sort them by time
combined.sort((a, b) => b.data.when_.localeCompare(a.data.when_));
console.log(combined);
return (
<tbody>
{combined.map(i =>
<tr>
<td><MomentTime data={i.data} /></td>
<td><Link to={`/user/${i.data.mod_user_id}`}>{i.data.mod_user_name}</Link></td>
<td>
{i.type_ == 'removed_posts' &&
<>
{(i.data as ModRemovePost).removed? 'Removed' : 'Restored'}
<span> Post <Link to={`/post/${(i.data as ModRemovePost).post_id}`}>{(i.data as ModRemovePost).post_name}</Link></span>
<div>{(i.data as ModRemovePost).reason && ` reason: ${(i.data as ModRemovePost).reason}`}</div>
</>
}
{i.type_ == 'locked_posts' &&
<>
{(i.data as ModLockPost).locked? 'Locked' : 'Unlocked'}
<span> Post <Link to={`/post/${(i.data as ModLockPost).post_id}`}>{(i.data as ModLockPost).post_name}</Link></span>
</>
}
{i.type_ == 'removed_comments' &&
<>
{(i.data as ModRemoveComment).removed? 'Removed' : 'Restored'}
<span> Comment <Link to={`/post/${(i.data as ModRemoveComment).post_id}/comment/${(i.data as ModRemoveComment).comment_id}`}>{(i.data as ModRemoveComment).comment_content}</Link></span>
<div>{(i.data as ModRemoveComment).reason && ` reason: ${(i.data as ModRemoveComment).reason}`}</div>
</>
}
{i.type_ == 'removed_communities' &&
<>
{(i.data as ModRemoveCommunity).removed ? 'Removed' : 'Restored'}
<span> Community <Link to={`/community/${i.data.community_id}`}>{i.data.community_name}</Link></span>
<div>{(i.data as ModRemoveCommunity).reason && ` reason: ${(i.data as ModRemoveCommunity).reason}`}</div>
<div>{(i.data as ModRemoveCommunity).expires && ` expires: ${moment.utc((i.data as ModRemoveCommunity).expires).fromNow()}`}</div>
</>
}
{i.type_ == 'banned_from_community' &&
<>
<span>{(i.data as ModBanFromCommunity).banned ? 'Banned ' : 'Unbanned '} </span>
<span><Link to={`/user/${(i.data as ModBanFromCommunity).other_user_id}`}>{(i.data as ModBanFromCommunity).other_user_name}</Link></span>
<div>{(i.data as ModBanFromCommunity).reason && ` reason: ${(i.data as ModBanFromCommunity).reason}`}</div>
<div>{(i.data as ModBanFromCommunity).expires && ` expires: ${moment.utc((i.data as ModBanFromCommunity).expires).fromNow()}`}</div>
</>
}
{i.type_ == 'added_to_community' &&
<>
<span>{(i.data as ModAddCommunity).removed ? 'Removed ' : 'Appointed '} </span>
<span><Link to={`/user/${(i.data as ModAddCommunity).other_user_id}`}>{(i.data as ModAddCommunity).other_user_name}</Link></span>
<span> as a mod to the community </span>
<span><Link to={`/community/${i.data.community_id}`}>{i.data.community_name}</Link></span>
</>
}
</td>
</tr>
)
}
</tbody>
);
}
render() {
return (
<div class="container">
{this.state.loading ?
<h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div>
<h4>Modlog</h4>
<div class="table-responsive">
<table id="modlog_table" class="table table-sm table-hover">
<thead class="pointer">
<tr>
<th>Time</th>
<th>Mod</th>
<th>Action</th>
</tr>
</thead>
{this.combined()}
</table>
</div>
</div>
}
</div>
);
}
parseMessage(msg: any) {
console.log(msg);
let op: UserOperation = msgOp(msg);
if (msg.error) {
alert(msg.error);
return;
} else if (op == UserOperation.GetModlog) {
let res: GetModlogResponse = msg;
this.state.loading = false;
this.state.removed_posts = res.removed_posts;
this.state.locked_posts = res.locked_posts;
this.state.removed_comments = res.removed_comments;
this.state.removed_communities = res.removed_communities;
this.state.banned_from_community = res.banned_from_community;
this.state.added_to_community = res.added_to_community;
this.setState(this.state);
}
}
}

View file

@ -3,7 +3,8 @@ import * as moment from 'moment';
interface MomentTimeProps { interface MomentTimeProps {
data: { data: {
published: string; published?: string;
when_?: string;
updated?: string; updated?: string;
} }
} }
@ -20,8 +21,9 @@ export class MomentTime extends Component<MomentTimeProps, any> {
<span title={this.props.data.updated} className="font-italics">modified {moment.utc(this.props.data.updated).fromNow()}</span> <span title={this.props.data.updated} className="font-italics">modified {moment.utc(this.props.data.updated).fromNow()}</span>
) )
} else { } else {
let str = this.props.data.published || this.props.data.when_;
return ( return (
<span title={this.props.data.published}>{moment.utc(this.props.data.published).fromNow()}</span> <span title={str}>{moment.utc(str).fromNow()}</span>
) )
} }
} }

View file

@ -56,6 +56,9 @@ export class Navbar extends Component<any, NavbarState> {
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/communities">Forums</Link> <Link class="nav-link" to="/communities">Forums</Link>
</li> </li>
<li class="nav-item">
<Link class="nav-link" to="/modlog">Modlog</Link>
</li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/create_post">Create Post</Link> <Link class="nav-link" to="/create_post">Create Post</Link>
</li> </li>

View file

@ -2,7 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType } from '../interfaces'; import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService, UserService } from '../services';
import { msgOp } from '../utils'; import { msgOp } from '../utils';
import * as autosize from 'autosize'; import * as autosize from 'autosize';
@ -26,7 +26,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
postForm: { postForm: {
name: null, name: null,
auth: null, auth: null,
community_id: null community_id: null,
creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null
}, },
communities: [], communities: [],
loading: false loading: false
@ -43,6 +44,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
name: this.props.post.name, name: this.props.post.name,
community_id: this.props.post.community_id, community_id: this.props.post.community_id,
edit_id: this.props.post.id, edit_id: this.props.post.id,
creator_id: this.props.post.creator_id,
url: this.props.post.url, url: this.props.post.url,
auth: null auth: null
} }

View file

@ -8,6 +8,8 @@ import { mdToHtml } from '../utils';
interface PostListingState { interface PostListingState {
showEdit: boolean; showEdit: boolean;
showRemoveDialog: boolean;
removeReason: string;
iframeExpanded: boolean; iframeExpanded: boolean;
} }
@ -23,6 +25,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
private emptyState: PostListingState = { private emptyState: PostListingState = {
showEdit: false, showEdit: false,
showRemoveDialog: false,
removeReason: null,
iframeExpanded: false iframeExpanded: false
} }
@ -59,20 +63,34 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<div className="ml-4"> <div className="ml-4">
{post.url {post.url
? <div className="mb-0"> ? <div className="mb-0">
<h4 className="d-inline"><a className="text-white" href={post.url}>{post.name}</a></h4> <h4 className="d-inline"><a className="text-white" href={post.url}>{post.name}</a>
<small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small> {post.removed &&
{ !this.state.iframeExpanded <small className="ml-2 text-muted font-italic">removed</small>
? <span class="pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleIframeExpandClick)}>+</span>
:
<span>
<span class="pointer ml-2 text-muted small" onClick={linkEvent(this, this.handleIframeExpandClick)}>-</span>
<div class="embed-responsive embed-responsive-1by1">
<iframe scrolling="yes" class="embed-responsive-item" src={post.url}></iframe>
</div>
</span>
} }
</div> {post.locked &&
: <h4 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link></h4> <small className="ml-2 text-muted font-italic">locked</small>
}
</h4>
<small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small>
{ !this.state.iframeExpanded
? <span class="pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleIframeExpandClick)}>+</span>
:
<span>
<span class="pointer ml-2 text-muted small" onClick={linkEvent(this, this.handleIframeExpandClick)}>-</span>
<div class="embed-responsive embed-responsive-1by1">
<iframe scrolling="yes" class="embed-responsive-item" src={post.url}></iframe>
</div>
</span>
}
</div>
: <h4 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link>
{post.removed &&
<small className="ml-2 text-muted font-italic">removed</small>
}
{post.locked &&
<small className="ml-2 text-muted font-italic">locked</small>
}
</h4>
} }
</div> </div>
<div className="details ml-4 mb-1"> <div className="details ml-4 mb-1">
@ -102,16 +120,39 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<Link className="text-muted" to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link> <Link className="text-muted" to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link>
</li> </li>
</ul> </ul>
{this.myPost && {this.props.editable &&
<ul class="list-inline mb-1 text-muted small font-weight-bold"> <ul class="list-inline mb-1 text-muted small font-weight-bold">
<li className="list-inline-item"> {this.myPost &&
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> <span>
</li> <li className="list-inline-item">
<li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
<span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> </li>
</li> <li className="list-inline-item mr-2">
<span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span>
</li>
</span>
}
{this.props.post.am_mod &&
<span>
<li className="list-inline-item">
{!this.props.post.removed ?
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
}
</li>
<li className="list-inline-item">
<span class="pointer" onClick={linkEvent(this, this.handleModLock)}>{this.props.post.locked ? 'unlock' : 'lock'}</span>
</li>
</span>
}
</ul> </ul>
} }
{this.state.showRemoveDialog &&
<form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
<input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} />
<button type="submit" class="btn btn-secondary">Remove Post</button>
</form>
}
{this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />} {this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />}
</div> </div>
</div> </div>
@ -119,7 +160,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
private get myPost(): boolean { private get myPost(): boolean {
return this.props.editable && UserService.Instance.loggedIn && this.props.post.creator_id == UserService.Instance.user.id; return UserService.Instance.loggedIn && this.props.post.creator_id == UserService.Instance.user.id;
} }
handlePostLike(i: PostListing) { handlePostLike(i: PostListing) {
@ -162,11 +203,50 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
name: "deleted", name: "deleted",
url: '', url: '',
edit_id: i.props.post.id, edit_id: i.props.post.id,
creator_id: i.props.post.creator_id,
auth: null auth: null
}; };
WebSocketService.Instance.editPost(deleteForm); WebSocketService.Instance.editPost(deleteForm);
} }
handleModRemoveShow(i: PostListing) {
i.state.showRemoveDialog = true;
i.setState(i.state);
}
handleModRemoveReasonChange(i: PostListing, event: any) {
i.state.removeReason = event.target.value;
i.setState(i.state);
}
handleModRemoveSubmit(i: PostListing) {
let form: PostFormI = {
name: i.props.post.name,
community_id: i.props.post.community_id,
edit_id: i.props.post.id,
creator_id: i.props.post.creator_id,
removed: !i.props.post.removed,
reason: i.state.removeReason,
auth: null,
};
WebSocketService.Instance.editPost(form);
i.state.showRemoveDialog = false;
i.setState(i.state);
}
handleModLock(i: PostListing) {
let form: PostFormI = {
name: i.props.post.name,
community_id: i.props.post.community_id,
edit_id: i.props.post.id,
creator_id: i.props.post.creator_id,
locked: !i.props.post.locked,
auth: null,
};
WebSocketService.Instance.editPost(form);
}
handleIframeExpandClick(i: PostListing) { handleIframeExpandClick(i: PostListing) {
i.state.iframeExpanded = !i.state.iframeExpanded; i.state.iframeExpanded = !i.state.iframeExpanded;
i.setState(i.state); i.setState(i.state);

View file

@ -1,7 +1,7 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentResponse, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse, CommentNode as CommentNodeI } from '../interfaces'; import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentResponse, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse, CommentNode as CommentNodeI, BanFromCommunityResponse, AddModToCommunityResponse } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { msgOp, hotRank } from '../utils'; import { msgOp, hotRank } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
@ -82,7 +82,7 @@ export class Post extends Component<any, PostState> {
<div class="col-12 col-sm-8 col-lg-7 mb-3"> <div class="col-12 col-sm-8 col-lg-7 mb-3">
<PostListing post={this.state.post} showBody showCommunity editable /> <PostListing post={this.state.post} showBody showCommunity editable />
<div className="mb-2" /> <div className="mb-2" />
<CommentForm postId={this.state.post.id} /> <CommentForm postId={this.state.post.id} disabled={this.state.post.locked} />
{this.sortRadios()} {this.sortRadios()}
{this.commentsTree()} {this.commentsTree()}
</div> </div>
@ -125,7 +125,7 @@ export class Post extends Component<any, PostState> {
<div class="sticky-top"> <div class="sticky-top">
<h4>New Comments</h4> <h4>New Comments</h4>
{this.state.comments.map(comment => {this.state.comments.map(comment =>
<CommentNodes nodes={[{comment: comment}]} noIndent /> <CommentNodes nodes={[{comment: comment}]} noIndent locked={this.state.post.locked} moderators={this.state.moderators} />
)} )}
</div> </div>
) )
@ -188,7 +188,7 @@ export class Post extends Component<any, PostState> {
let nodes = this.buildCommentsTree(); let nodes = this.buildCommentsTree();
return ( return (
<div className=""> <div className="">
<CommentNodes nodes={nodes} /> <CommentNodes nodes={nodes} locked={this.state.post.locked} moderators={this.state.moderators} />
</div> </div>
); );
} }
@ -216,6 +216,11 @@ export class Post extends Component<any, PostState> {
let found = this.state.comments.find(c => c.id == res.comment.id); let found = this.state.comments.find(c => c.id == res.comment.id);
found.content = res.comment.content; found.content = res.comment.content;
found.updated = res.comment.updated; found.updated = res.comment.updated;
found.removed = res.comment.removed;
found.upvotes = res.comment.upvotes;
found.downvotes = res.comment.downvotes;
found.score = res.comment.score;
this.setState(this.state); this.setState(this.state);
} }
else if (op == UserOperation.CreateCommentLike) { else if (op == UserOperation.CreateCommentLike) {
@ -249,6 +254,15 @@ export class Post extends Component<any, PostState> {
this.state.community.subscribed = res.community.subscribed; this.state.community.subscribed = res.community.subscribed;
this.state.community.number_of_subscribers = res.community.number_of_subscribers; this.state.community.number_of_subscribers = res.community.number_of_subscribers;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.BanFromCommunity) {
let res: BanFromCommunityResponse = msg;
this.state.comments.filter(c => c.creator_id == res.user.id)
.forEach(c => c.banned = res.banned);
this.setState(this.state);
} else if (op == UserOperation.AddModToCommunity) {
let res: AddModToCommunityResponse = msg;
this.state.moderators = res.moderators;
this.setState(this.state);
} }
} }

View file

@ -1,8 +1,8 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Community, CommunityUser, FollowCommunityForm } from '../interfaces'; import { Community, CommunityUser, FollowCommunityForm, CommunityForm as CommunityFormI } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { mdToHtml } from '../utils'; import { mdToHtml, getUnixTime } from '../utils';
import { CommunityForm } from './community-form'; import { CommunityForm } from './community-form';
interface SidebarProps { interface SidebarProps {
@ -12,12 +12,18 @@ interface SidebarProps {
interface SidebarState { interface SidebarState {
showEdit: boolean; showEdit: boolean;
showRemoveDialog: boolean;
removeReason: string;
removeExpires: string;
} }
export class Sidebar extends Component<SidebarProps, SidebarState> { export class Sidebar extends Component<SidebarProps, SidebarState> {
private emptyState: SidebarState = { private emptyState: SidebarState = {
showEdit: false showEdit: false,
showRemoveDialog: false,
removeReason: null,
removeExpires: null
} }
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -42,44 +48,71 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
let community = this.props.community; let community = this.props.community;
return ( return (
<div> <div>
<h4 className="mb-0">{community.title}</h4> <h4 className="mb-0">{community.title}
<Link className="text-muted" to={`/community/${community.id}`}>/f/{community.name}</Link> {community.removed &&
{this.amMod && <small className="ml-2 text-muted font-italic">removed</small>
<ul class="list-inline mb-1 text-muted small font-weight-bold"> }
<li className="list-inline-item"> </h4>
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> <Link className="text-muted" to={`/community/${community.id}`}>/f/{community.name}</Link>
</li> {community.am_mod &&
{this.amCreator && <ul class="list-inline mb-1 text-muted small font-weight-bold">
<li className="list-inline-item"> <li className="list-inline-item">
{/* <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> */} <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
</li> </li>
} {this.amCreator &&
</ul> <li className="list-inline-item">
} {/* <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> */}
<ul class="mt-1 list-inline"> </li>
<li className="list-inline-item"><Link className="badge badge-light" to="/communities">{community.category_name}</Link></li> }
<li className="list-inline-item badge badge-light">{community.number_of_subscribers} Subscribers</li> <li className="list-inline-item">
<li className="list-inline-item badge badge-light">{community.number_of_posts} Posts</li> {!this.props.community.removed ?
<li className="list-inline-item badge badge-light">{community.number_of_comments} Comments</li> <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
</ul> <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
<div> }
{community.subscribed </li>
? <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</button> </ul>
: <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</button> }
} {this.state.showRemoveDialog &&
</div> <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
{community.description && <div class="form-group row">
<div> <label class="col-form-label">Reason</label>
<hr /> <input type="text" class="form-control mr-2" placeholder="Optional" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} />
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(community.description)} /> </div>
</div> <div class="form-group row">
<label class="col-form-label">Expires</label>
<input type="date" class="form-control mr-2" placeholder="Expires" value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} />
</div>
<div class="form-group row">
<button type="submit" class="btn btn-secondary">Remove Community</button>
</div>
</form>
}
<ul class="mt-1 list-inline">
<li className="list-inline-item"><Link className="badge badge-light" to="/communities">{community.category_name}</Link></li>
<li className="list-inline-item badge badge-light">{community.number_of_subscribers} Subscribers</li>
<li className="list-inline-item badge badge-light">{community.number_of_posts} Posts</li>
<li className="list-inline-item badge badge-light">{community.number_of_comments} Comments</li>
</ul>
<div>
{community.subscribed
? <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</button>
: <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</button>
} }
<hr />
<h4>Moderators</h4>
{this.props.moderators.map(mod =>
<Link to={`/user/${mod.user_id}`}>{mod.user_name}</Link>
)}
</div> </div>
{community.description &&
<div>
<hr />
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(community.description)} />
</div>
}
<hr />
<h4>Moderators</h4>
<ul class="list-inline">
{this.props.moderators.map(mod =>
<li class="list-inline-item"><Link to={`/user/${mod.user_id}`}>{mod.user_name}</Link></li>
)}
</ul>
</div>
); );
} }
@ -122,10 +155,48 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
return UserService.Instance.loggedIn && this.props.community.creator_id == UserService.Instance.user.id; return UserService.Instance.loggedIn && this.props.community.creator_id == UserService.Instance.user.id;
} }
private get amMod(): boolean { // private get amMod(): boolean {
console.log(this.props.moderators); // return UserService.Instance.loggedIn &&
console.log(this.props); // this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id);
return UserService.Instance.loggedIn && // }
this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id);
handleDeleteClick() {
} }
handleModRemoveShow(i: Sidebar) {
i.state.showRemoveDialog = true;
i.setState(i.state);
}
handleModRemoveReasonChange(i: Sidebar, event: any) {
i.state.removeReason = event.target.value;
i.setState(i.state);
}
handleModRemoveExpiresChange(i: Sidebar, event: any) {
console.log(event.target.value);
i.state.removeExpires = event.target.value;
i.setState(i.state);
}
handleModRemoveSubmit(i: Sidebar) {
let deleteForm: CommunityFormI = {
name: i.props.community.name,
title: i.props.community.title,
category_id: i.props.community.category_id,
edit_id: i.props.community.id,
removed: !i.props.community.removed,
reason: i.state.removeReason,
expires: getUnixTime(i.state.removeExpires),
auth: null,
};
WebSocketService.Instance.editCommunity(deleteForm);
i.state.showRemoveDialog = false;
i.setState(i.state);
}
} }

View file

@ -125,24 +125,27 @@ export class User extends Component<any, UserState> {
} }
overview() { overview() {
let combined: Array<any> = []; let combined: Array<{type_: string, data: Comment | Post}> = [];
combined.push(...this.state.comments); let comments = this.state.comments.map(e => {return {type_: "comments", data: e}});
combined.push(...this.state.posts); let posts = this.state.posts.map(e => {return {type_: "posts", data: e}});
combined.push(...comments);
combined.push(...posts);
// Sort it // Sort it
if (this.state.sort == SortType.New) { if (this.state.sort == SortType.New) {
combined.sort((a, b) => b.published.localeCompare(a.published)); combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
} else { } else {
combined.sort((a, b) => b.score - a.score); combined.sort((a, b) => b.data.score - a.data.score);
} }
return ( return (
<div> <div>
{combined.map(i => {combined.map(i =>
<div> <div>
{i.community_id {i.type_ == "posts"
? <PostListing post={i} showCommunity viewOnly /> ? <PostListing post={i.data as Post} showCommunity viewOnly />
: <CommentNodes nodes={[{comment: i}]} noIndent viewOnly /> : <CommentNodes nodes={[{comment: i.data as Comment}]} noIndent viewOnly />
} }
</div> </div>
) )

View file

@ -10,6 +10,7 @@ import { Post } from './components/post';
import { Community } from './components/community'; import { Community } from './components/community';
import { Communities } from './components/communities'; import { Communities } from './components/communities';
import { User } from './components/user'; import { User } from './components/user';
import { Modlog } from './components/modlog';
import { Symbols } from './components/symbols'; import { Symbols } from './components/symbols';
import './main.css'; import './main.css';
@ -42,6 +43,7 @@ class Index extends Component<any, any> {
<Route path={`/community/:id`} component={Community} /> <Route path={`/community/:id`} component={Community} />
<Route path={`/user/:id/:heading`} component={User} /> <Route path={`/user/:id/:heading`} component={User} />
<Route path={`/user/:id`} component={User} /> <Route path={`/user/:id`} component={User} />
<Route path={`/modlog`} component={Modlog} />
</Switch> </Switch>
<Symbols /> <Symbols />
</div> </div>

View file

@ -1,5 +1,17 @@
export enum UserOperation { export enum UserOperation {
Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity
}
export enum CommentSortType {
Hot, Top, New
}
export enum ListingType {
All, Subscribed, Community
}
export enum SortType {
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
} }
export interface User { export interface User {
@ -31,6 +43,8 @@ export interface CommunityUser {
export interface Community { export interface Community {
user_id?: number; user_id?: number;
subscribed?: boolean; subscribed?: boolean;
am_mod?: boolean;
removed?: boolean;
id: number; id: number;
name: string; name: string;
title: string; title: string;
@ -46,12 +60,258 @@ export interface Community {
updated?: string; updated?: string;
} }
export interface Post {
user_id?: number;
my_vote?: number;
am_mod?: boolean;
removed?: boolean;
locked?: boolean;
id: number;
name: string;
url?: string;
body?: string;
creator_id: number;
creator_name: string;
community_id: number;
community_name: string;
number_of_comments: number;
score: number;
upvotes: number;
downvotes: number;
hot_rank: number;
published: string;
updated?: string;
}
export interface Comment {
id: number;
content: string;
creator_id: number;
creator_name: string;
post_id: number,
community_id: number,
parent_id?: number;
published: string;
updated?: string;
score: number;
upvotes: number;
downvotes: number;
my_vote?: number;
am_mod?: boolean;
removed?: boolean;
banned?: boolean;
}
export interface Category {
id: number;
name: string;
}
export interface FollowCommunityForm {
community_id: number;
follow: boolean;
auth?: string;
}
export interface GetFollowedCommunitiesResponse {
op: string;
communities: Array<CommunityUser>;
}
export interface GetUserDetailsForm {
user_id: number;
sort: string; // TODO figure this one out
limit: number;
community_id?: number;
auth?: string;
}
export interface UserDetailsResponse {
op: string;
user: UserView;
follows: Array<CommunityUser>;
moderates: Array<CommunityUser>;
comments: Array<Comment>;
posts: Array<Post>;
saved?: Array<Post>;
}
export interface BanFromCommunityForm {
community_id: number;
user_id: number;
ban: boolean;
reason?: string,
expires?: number,
auth?: string;
}
export interface BanFromCommunityResponse {
op: string;
user: UserView,
banned: boolean,
}
export interface AddModToCommunityForm {
community_id: number;
user_id: number;
added: boolean;
auth?: string;
}
export interface AddModToCommunityResponse {
op: string;
moderators: Array<CommunityUser>;
}
export interface GetModlogForm {
mod_user_id?: number;
community_id?: number;
limit?: number;
page?: number;
}
export interface GetModlogResponse {
op: string;
removed_posts: Array<ModRemovePost>,
locked_posts: Array<ModLockPost>,
removed_comments: Array<ModRemoveComment>,
removed_communities: Array<ModRemoveCommunity>,
banned_from_community: Array<ModBanFromCommunity>,
banned: Array<ModBan>,
added_to_community: Array<ModAddCommunity>,
added: Array<ModAdd>,
}
export interface ModRemovePost {
id: number;
mod_user_id: number;
post_id: number;
reason?: string;
removed?: boolean;
when_: string
mod_user_name: string;
post_name: string;
community_id: number;
community_name: string;
}
export interface ModLockPost {
id: number,
mod_user_id: number,
post_id: number,
locked?: boolean,
when_: string,
mod_user_name: string,
post_name: string,
community_id: number,
community_name: string,
}
export interface ModRemoveComment {
id: number,
mod_user_id: number,
comment_id: number,
reason?: string,
removed?: boolean,
when_: string,
mod_user_name: string,
comment_user_id: number,
comment_user_name: string,
comment_content: string,
post_id: number,
post_name: string,
community_id: number,
community_name: string,
}
export interface ModRemoveCommunity {
id: number,
mod_user_id: number,
community_id: number,
reason?: string,
removed?: boolean,
expires?: number,
when_: string,
mod_user_name: string,
community_name: string,
}
export interface ModBanFromCommunity {
id: number,
mod_user_id: number,
other_user_id: number,
community_id: number,
reason?: string,
banned?: boolean,
expires?: number,
when_: string,
mod_user_name: string,
other_user_name: string,
community_name: string,
}
export interface ModBan {
id: number,
mod_user_id: number,
other_user_id: number,
reason?: string,
banned?: boolean,
expires?: number,
when_: string,
mod_user_name: string,
other_user_name: string,
}
export interface ModAddCommunity {
id: number,
mod_user_id: number,
other_user_id: number,
community_id: number,
removed?: boolean,
when_: string,
mod_user_name: string,
other_user_name: string,
community_name: string,
}
export interface ModAdd {
id: number,
mod_user_id: number,
other_user_id: number,
removed?: boolean,
when_: string,
mod_user_name: string,
other_user_name: string,
}
export interface LoginForm {
username_or_email: string;
password: string;
}
export interface RegisterForm {
username: string;
email?: string;
password: string;
password_verify: string;
}
export interface LoginResponse {
op: string;
jwt: string;
}
export interface CommunityForm { export interface CommunityForm {
name: string; name: string;
title: string; title: string;
description?: string, description?: string,
category_id: number, category_id: number,
edit_id?: number; edit_id?: number;
removed?: boolean;
reason?: string;
expires?: number;
auth?: string; auth?: string;
} }
@ -82,27 +342,6 @@ export interface ListCategoriesResponse {
op: string; op: string;
categories: Array<Category>; categories: Array<Category>;
} }
export interface Post {
user_id?: number;
my_vote?: number;
id: number;
name: string;
url?: string;
body?: string;
creator_id: number;
creator_name: string;
community_id: number;
community_name: string;
number_of_comments: number;
score: number;
upvotes: number;
downvotes: number;
hot_rank: number;
published: string;
updated?: string;
}
export interface PostForm { export interface PostForm {
name: string; name: string;
url?: string; url?: string;
@ -110,6 +349,10 @@ export interface PostForm {
community_id: number; community_id: number;
updated?: number; updated?: number;
edit_id?: number; edit_id?: number;
creator_id: number;
removed?: boolean;
reason?: string;
locked?: boolean;
auth: string; auth: string;
} }
@ -126,26 +369,14 @@ export interface PostResponse {
post: Post; post: Post;
} }
export interface Comment {
id: number;
content: string;
creator_id: number;
creator_name: string;
post_id: number,
parent_id?: number;
published: string;
updated?: string;
score: number;
upvotes: number;
downvotes: number;
my_vote?: number;
}
export interface CommentForm { export interface CommentForm {
content: string; content: string;
post_id: number; post_id: number;
parent_id?: number; parent_id?: number;
edit_id?: number; edit_id?: number;
creator_id: number;
removed?: boolean;
reason?: string;
auth: string; auth: string;
} }
@ -190,70 +421,4 @@ export interface CreatePostLikeResponse {
post: Post; post: Post;
} }
export interface Category {
id: number;
name: string;
}
export interface FollowCommunityForm {
community_id: number;
follow: boolean;
auth?: string;
}
export interface GetFollowedCommunitiesResponse {
op: string;
communities: Array<CommunityUser>;
}
export interface GetUserDetailsForm {
user_id: number;
sort: string; // TODO figure this one out
limit: number;
community_id?: number;
auth?: string;
}
export interface UserDetailsResponse {
op: string;
user: UserView;
follows: Array<CommunityUser>;
moderates: Array<CommunityUser>;
comments: Array<Comment>;
posts: Array<Post>;
saved?: Array<Post>;
}
export interface LoginForm {
username_or_email: string;
password: string;
}
export interface RegisterForm {
username: string;
email?: string;
password: string;
password_verify: string;
}
export interface LoginResponse {
op: string;
jwt: string;
}
export enum CommentSortType {
Hot, Top, New
}
export enum ListingType {
All, Subscribed, Community
}
export enum SortType {
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
}

View file

@ -24,6 +24,11 @@ body {
color: #fff; color: #fff;
} }
.form-control:disabled {
background-color: var(--secondary);
opacity: .5;
}
.custom-select { .custom-select {
color: #fff; color: #fff;
background-color: var(--secondary); background-color: var(--secondary);

View file

@ -1,5 +1,5 @@
import { wsUri } from '../env'; import { wsUri } from '../env';
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm } from '../interfaces'; import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket'; import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
@ -106,11 +106,25 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.EditPost, postForm)); this.subject.next(this.wsSendWrapper(UserOperation.EditPost, postForm));
} }
public banFromCommunity(form: BanFromCommunityForm) {
this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.BanFromCommunity, form));
}
public addModToCommunity(form: AddModToCommunityForm) {
this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.AddModToCommunity, form));
}
public getUserDetails(form: GetUserDetailsForm) { public getUserDetails(form: GetUserDetailsForm) {
this.setAuth(form, false); this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetUserDetails, form)); this.subject.next(this.wsSendWrapper(UserOperation.GetUserDetails, form));
} }
public getModlog(form: GetModlogForm) {
this.subject.next(this.wsSendWrapper(UserOperation.GetModlog, form));
}
private wsSendWrapper(op: UserOperation, data: any) { private wsSendWrapper(op: UserOperation, data: any) {
let send = { op: UserOperation[op], data: data }; let send = { op: UserOperation[op], data: data };
console.log(send); console.log(send);

View file

@ -31,3 +31,11 @@ export function hotRank(comment: Comment): number {
export function mdToHtml(text: string) { export function mdToHtml(text: string) {
return {__html: md.render(text)}; return {__html: md.render(text)};
} }
export function getUnixTime(text: string): number {
return text ? new Date(text).getTime()/1000 : undefined;
}
export function addTypeInfo<T>(arr: Array<T>, name: string): Array<{type_: string, data: T}> {
return arr.map(e => {return {type_: name, data: e}});
}

View file

@ -1 +1 @@
export let version: string = "v0.0.2-9-g8e5a5d1"; export let version: string = "v0.0.2-13-g1bf0dfd";