From 3d4872ce108398ba035d2e05882a289b94f6d63d Mon Sep 17 00:00:00 2001 From: Dessalines Date: Sat, 6 Apr 2019 17:58:28 -0700 Subject: [PATCH 01/13] adding migrations --- .../down.sql | 6 +++ .../up.sql | 54 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 server/migrations/2019-04-07-003142_create_moderation_logs/down.sql create mode 100644 server/migrations/2019-04-07-003142_create_moderation_logs/up.sql diff --git a/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql b/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql new file mode 100644 index 00000000..15718917 --- /dev/null +++ b/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql @@ -0,0 +1,6 @@ +drop table mod_remove_post; +drop table mod_lock_post; +drop table mod_remove_comment; +drop table mod_remove_community; +drop table mod_ban; +drop table mod_add_mod; diff --git a/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql b/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql new file mode 100644 index 00000000..41929e50 --- /dev/null +++ b/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql @@ -0,0 +1,54 @@ + +create table mod_remove_post ( + id serial primary key, + 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, + reason text, + removed boolean default true, + when_ timestamp not null default now() +); + +create table mod_lock_post ( + id serial primary key, + 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, + when_ timestamp not null default now() +); + +create table mod_remove_comment ( + id serial primary key, + mod_user_id int references user_ on update cascade on delete cascade not null, + comment_id int references comment on update cascade on delete cascade not null, + reason text, + removed boolean default true, + when_ timestamp not null default now() +); + +create table mod_remove_community ( + id serial primary key, + mod_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, + removed boolean default true, + when_ timestamp not null default now() +); + +-- TODO make sure you can't ban other mods +create table mod_ban ( + 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, + reason text, + removed boolean default true, + expires timestamp, + when_ timestamp not null default now() +); + +-- When removed is false that means kicked +create table mod_add_mod ( + 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, + removed boolean default false, + when_ timestamp not null default now() +) From 9407437cf85afce2d9d2137deb044c18bcca2f4e Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 8 Apr 2019 23:47:08 -0700 Subject: [PATCH 02/13] Temp removing fusebox git describe since it breaks docker. --- ui/.gitignore | 1 - ui/fuse.js | 4 ++-- ui/src/version.ts | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 ui/src/version.ts diff --git a/ui/.gitignore b/ui/.gitignore index 1e420dc4..cc0ab540 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -1,4 +1,3 @@ -src/version.ts dist .fusebox _site diff --git a/ui/fuse.js b/ui/fuse.js index c70edfb8..fe2c7664 100644 --- a/ui/fuse.js +++ b/ui/fuse.js @@ -46,12 +46,12 @@ Sparky.task('version', _ => setVersion()); Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/')); Sparky.task('env', _ => (isProduction = true)); Sparky.task('copy-assets', () => Sparky.src('assets/*.svg').dest('dist/')); -Sparky.task('dev', ['clean', 'config', 'copy-assets', 'version'], _ => { +Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => { fuse.dev(); app.hmr().watch(); return fuse.run(); }); -Sparky.task('prod', ['clean', 'env', 'config', 'copy-assets', 'version'], _ => { +Sparky.task('prod', ['clean', 'env', 'config', 'copy-assets'], _ => { // fuse.dev({ reload: true }); // remove after demo return fuse.run(); }); diff --git a/ui/src/version.ts b/ui/src/version.ts new file mode 100644 index 00000000..3f710072 --- /dev/null +++ b/ui/src/version.ts @@ -0,0 +1 @@ +export let version: string = "v0.0.2-0-gdae6651"; \ No newline at end of file From 0af9e08263e84c51b0267aeb03ffc7b441a15ab6 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 9 Apr 2019 07:46:44 -0700 Subject: [PATCH 03/13] Fixing endpoint. --- ui/src/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/env.ts b/ui/src/env.ts index 954d835f..03594bc6 100644 --- a/ui/src/env.ts +++ b/ui/src/env.ts @@ -1,2 +1,2 @@ export const endpoint = `${window.location.hostname}:8536`; -export let wsUri = (window.location.protocol=='https:') ? 'wss://' : 'ws://' + endpoint + '/service/ws'; +export let wsUri = `${(window.location.protocol=='https:') ? 'wss://' : 'ws://'}${endpoint}/service/ws`; From 8aa58e479eb742d192a17b7511c0bbce5fc7d534 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 9 Apr 2019 08:03:58 -0700 Subject: [PATCH 04/13] Fixing endpoint. --- ui/src/env.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/env.ts b/ui/src/env.ts index 03594bc6..aebbc57f 100644 --- a/ui/src/env.ts +++ b/ui/src/env.ts @@ -1,2 +1,4 @@ -export const endpoint = `${window.location.hostname}:8536`; +let host = `${window.location.hostname}`; +let port = `${window.location.port == "4444" ? '8536' : window.location.port}`; +let endpoint = `${host}:${port}`; export let wsUri = `${(window.location.protocol=='https:') ? 'wss://' : 'ws://'}${endpoint}/service/ws`; From 92670d1a5eea86e277d958467684b0632131d6c4 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 9 Apr 2019 11:35:16 -0700 Subject: [PATCH 05/13] Adding slur filter. - Fixes #45 --- server/Cargo.lock | 1 + server/Cargo.toml | 3 ++- server/src/lib.rs | 33 +++++++++++++++++++++----- server/src/websocket_server/server.rs | 34 ++++++++++++++++++++++++--- ui/src/components/community-form.tsx | 1 + ui/src/components/login.tsx | 2 +- ui/src/components/post-form.tsx | 2 ++ 7 files changed, 65 insertions(+), 11 deletions(-) diff --git a/server/Cargo.lock b/server/Cargo.lock index ce4e175a..36d2a901 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1359,6 +1359,7 @@ dependencies = [ "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "jsonwebtoken 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/server/Cargo.toml b/server/Cargo.toml index fd3d5773..93bd6acb 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -24,4 +24,5 @@ rand = "0.6.5" strum = "0.14.0" strum_macros = "0.14.0" jsonwebtoken = "*" -regex = "1" +regex = "*" +lazy_static = "*" diff --git a/server/src/lib.rs b/server/src/lib.rs index 9cdbd33e..814363b4 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -12,7 +12,7 @@ pub extern crate jsonwebtoken; pub extern crate bcrypt; pub extern crate regex; #[macro_use] pub extern crate strum_macros; - +#[macro_use] pub extern crate lazy_static; pub mod schema; pub mod apub; pub mod actions; @@ -89,21 +89,42 @@ pub fn naive_now() -> NaiveDateTime { } pub fn is_email_regex(test: &str) -> bool { - let re = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); - re.is_match(test) + EMAIL_REGEX.is_match(test) +} + +pub fn remove_slurs(test: &str) -> String { + SLUR_REGEX.replace_all(test, "*removed*").to_string() +} + +pub fn has_slurs(test: &str) -> bool { + SLUR_REGEX.is_match(test) } #[cfg(test)] mod tests { - use {Settings, is_email_regex}; + use {Settings, is_email_regex, remove_slurs, has_slurs}; #[test] fn test_api() { assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1"); } - #[test] - fn test_email() { + #[test] fn test_email() { assert!(is_email_regex("gush@gmail.com")); assert!(!is_email_regex("nada_neutho")); } + + #[test] fn test_slur_filter() { + let test = "coons test dindu ladyboy tranny. This is a bunch of other safe text.".to_string(); + let slur_free = "No slurs here"; + assert_eq!(remove_slurs(&test), "*removed* test *removed* *removed* *removed*. This is a bunch of other safe text.".to_string()); + assert!(has_slurs(&test)); + assert!(!has_slurs(slur_free)); + } } + + +lazy_static! { + static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); + static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bnig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?))").unwrap(); +} + diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 92542d0a..a946c9e0 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -10,7 +10,7 @@ use serde_json::{Value}; use bcrypt::{verify}; use std::str::FromStr; -use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType}; +use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType, has_slurs, remove_slurs}; use actions::community::*; use actions::user::*; use actions::post::*; @@ -541,6 +541,10 @@ impl Perform for Register { return self.error("Passwords do not match."); } + if has_slurs(&self.username) { + return self.error("No slurs"); + } + // Register the new user let user_form = UserForm { name: self.username.to_owned(), @@ -587,6 +591,12 @@ impl Perform for CreateCommunity { } }; + if has_slurs(&self.name) || + has_slurs(&self.title) || + (self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) { + return self.error("No slurs"); + } + let user_id = claims.id; // When you create a community, make sure the user becomes a moderator and a follower @@ -716,6 +726,11 @@ impl Perform for CreatePost { } }; + if has_slurs(&self.name) || + (self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) { + return self.error("No slurs"); + } + let user_id = claims.id; let post_form = PostForm { @@ -894,8 +909,10 @@ impl Perform for CreateComment { let user_id = claims.id; + let content_slurs_removed = remove_slurs(&self.content.to_owned()); + let comment_form = CommentForm { - content: self.content.to_owned(), + content: content_slurs_removed, parent_id: self.parent_id.to_owned(), post_id: self.post_id, creator_id: user_id, @@ -976,8 +993,10 @@ impl Perform for EditComment { return self.error("Incorrect creator."); } + let content_slurs_removed = remove_slurs(&self.content.to_owned()); + let comment_form = CommentForm { - content: self.content.to_owned(), + content: content_slurs_removed, parent_id: self.parent_id, post_id: self.post_id, creator_id: user_id, @@ -1197,6 +1216,11 @@ impl Perform for EditPost { fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + if has_slurs(&self.name) || + (self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) { + return self.error("No slurs"); + } + let conn = establish_connection(); let claims = match Claims::decode(&self.auth) { @@ -1264,6 +1288,10 @@ impl Perform for EditCommunity { fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + if has_slurs(&self.name) || has_slurs(&self.title) { + return self.error("No slurs"); + } + let conn = establish_connection(); let claims = match Claims::decode(&self.auth) { diff --git a/ui/src/components/community-form.tsx b/ui/src/components/community-form.tsx index b5b222c6..056c29dc 100644 --- a/ui/src/components/community-form.tsx +++ b/ui/src/components/community-form.tsx @@ -155,6 +155,7 @@ export class CommunityForm extends Component {
- +
diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 9845a1b1..03ace380 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -151,7 +151,9 @@ export class PostForm extends Component { parseMessage(msg: any) { let op: UserOperation = msgOp(msg); if (msg.error) { + alert(msg.error); this.state.loading = false; + this.setState(this.state); return; } else if (op == UserOperation.ListCommunities) { let res: ListCommunitiesResponse = msg; From 4bfb32c6658d3547ea7728fcf0927b187685658e Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 9 Apr 2019 14:21:19 -0700 Subject: [PATCH 06/13] Styling, sidebar message. --- server/src/websocket_server/server.rs | 36 +++++++++++++-------------- ui/src/components/communities.tsx | 2 +- ui/src/components/main.tsx | 30 ++++++++++++++++------ ui/src/components/navbar.tsx | 6 ++--- ui/src/components/post.tsx | 6 ++--- ui/src/components/sidebar.tsx | 2 +- ui/src/main.css | 9 +++++++ ui/src/services/UserService.ts | 4 +-- 8 files changed, 59 insertions(+), 36 deletions(-) diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index a946c9e0..42124d2d 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -391,25 +391,25 @@ impl Handler for ChatServer { let json: Value = serde_json::from_str(&msg.msg) .expect("Couldn't parse message"); - let data: &Value = &json["data"]; + let data = &json["data"].to_string(); let op = &json["op"].as_str().unwrap(); let user_operation: UserOperation = UserOperation::from_str(&op).unwrap(); let res: String = match user_operation { UserOperation::Login => { - let login: Login = serde_json::from_str(&data.to_string()).unwrap(); + let login: Login = serde_json::from_str(data).unwrap(); login.perform(self, msg.id) }, UserOperation::Register => { - let register: Register = serde_json::from_str(&data.to_string()).unwrap(); + let register: Register = serde_json::from_str(data).unwrap(); register.perform(self, msg.id) }, UserOperation::CreateCommunity => { - let create_community: CreateCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let create_community: CreateCommunity = serde_json::from_str(data).unwrap(); create_community.perform(self, msg.id) }, UserOperation::ListCommunities => { - let list_communities: ListCommunities = serde_json::from_str(&data.to_string()).unwrap(); + let list_communities: ListCommunities = serde_json::from_str(data).unwrap(); list_communities.perform(self, msg.id) }, UserOperation::ListCategories => { @@ -417,55 +417,55 @@ impl Handler for ChatServer { list_categories.perform(self, msg.id) }, UserOperation::CreatePost => { - let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap(); + let create_post: CreatePost = serde_json::from_str(data).unwrap(); create_post.perform(self, msg.id) }, UserOperation::GetPost => { - let get_post: GetPost = serde_json::from_str(&data.to_string()).unwrap(); + let get_post: GetPost = serde_json::from_str(data).unwrap(); get_post.perform(self, msg.id) }, UserOperation::GetCommunity => { - let get_community: GetCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let get_community: GetCommunity = serde_json::from_str(data).unwrap(); get_community.perform(self, msg.id) }, UserOperation::CreateComment => { - let create_comment: CreateComment = serde_json::from_str(&data.to_string()).unwrap(); + let create_comment: CreateComment = serde_json::from_str(data).unwrap(); create_comment.perform(self, msg.id) }, UserOperation::EditComment => { - let edit_comment: EditComment = serde_json::from_str(&data.to_string()).unwrap(); + let edit_comment: EditComment = serde_json::from_str(data).unwrap(); edit_comment.perform(self, msg.id) }, UserOperation::CreateCommentLike => { - let create_comment_like: CreateCommentLike = serde_json::from_str(&data.to_string()).unwrap(); + let create_comment_like: CreateCommentLike = serde_json::from_str(data).unwrap(); create_comment_like.perform(self, msg.id) }, UserOperation::GetPosts => { - let get_posts: GetPosts = serde_json::from_str(&data.to_string()).unwrap(); + let get_posts: GetPosts = serde_json::from_str(data).unwrap(); get_posts.perform(self, msg.id) }, UserOperation::CreatePostLike => { - let create_post_like: CreatePostLike = serde_json::from_str(&data.to_string()).unwrap(); + let create_post_like: CreatePostLike = serde_json::from_str(data).unwrap(); create_post_like.perform(self, msg.id) }, UserOperation::EditPost => { - let edit_post: EditPost = serde_json::from_str(&data.to_string()).unwrap(); + let edit_post: EditPost = serde_json::from_str(data).unwrap(); edit_post.perform(self, msg.id) }, UserOperation::EditCommunity => { - let edit_community: EditCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let edit_community: EditCommunity = serde_json::from_str(data).unwrap(); edit_community.perform(self, msg.id) }, UserOperation::FollowCommunity => { - let follow_community: FollowCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let follow_community: FollowCommunity = serde_json::from_str(data).unwrap(); follow_community.perform(self, msg.id) }, UserOperation::GetFollowedCommunities => { - let followed_communities: GetFollowedCommunities = serde_json::from_str(&data.to_string()).unwrap(); + let followed_communities: GetFollowedCommunities = serde_json::from_str(data).unwrap(); followed_communities.perform(self, msg.id) }, UserOperation::GetUserDetails => { - let get_user_details: GetUserDetails = serde_json::from_str(&data.to_string()).unwrap(); + let get_user_details: GetUserDetails = serde_json::from_str(data).unwrap(); get_user_details.perform(self, msg.id) }, // _ => { diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx index cf42238e..268aa115 100644 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@ -45,7 +45,7 @@ export class Communities extends Component { render() { return ( -
+
{this.state.loading ?

:
diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 477eec65..8faf858a 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -5,7 +5,7 @@ import { retryWhen, delay, take } from 'rxjs/operators'; import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { PostListings } from './post-listings'; -import { msgOp } from '../utils'; +import { msgOp, repoUrl } from '../utils'; interface State { subscribedCommunities: Array; @@ -46,17 +46,15 @@ export class Main extends Component { return (
-
+
-
-

A Landing message

- {UserService.Instance.loggedIn && +
+ {UserService.Instance.loggedIn ?
{this.state.loading ? -

: +

:
-

Subscribed forums

    {this.state.subscribedCommunities.map(community => @@ -65,7 +63,8 @@ export class Main extends Component {
} -
+
: + this.landing() }
@@ -73,6 +72,21 @@ export class Main extends Component { ) } + landing() { + return ( +
+

Welcome to + + LemmyBeta +

+

Lemmy is a link aggregator / reddit alternative, intended to work in the fediverse.

+

Its self-hostable, has live-updating comment threads, and is tiny (~80kB). Federation into the ActivityPub network is on the roadmap.

+

This is a very early beta version, and a lot of features are currently broken or missing.

+

Suggest new features or report bugs here.

+

Made with Rust, Actix, Inferno, Typescript.

+
+ ) + } parseMessage(msg: any) { console.log(msg); diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index ca0c5a2a..a9d95362 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -25,7 +25,7 @@ export class Navbar extends Component { // Subscribe to user changes UserService.Instance.sub.subscribe(user => { - let loggedIn: boolean = user !== null; + let loggedIn: boolean = user !== undefined; this.setState({isLoggedIn: loggedIn}); }); } @@ -40,7 +40,7 @@ export class Navbar extends Component { // TODO toggle css collapse navbar() { return ( -
: - Login + Login / Sign up }
diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index f36893f6..5ca3f770 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -101,17 +101,17 @@ export class Post extends Component { sortRadios() { return (
-
@@ -72,6 +82,19 @@ export class Main extends Component { ) } + trendingCommunities() { + return ( +
+

Trending forums

+
    + {this.state.trendingCommunities.map(community => +
  • {community.name}
  • + )} +
+
+ ) + } + landing() { return (
@@ -99,6 +122,11 @@ export class Main extends Component { this.state.subscribedCommunities = res.communities; this.state.loading = false; this.setState(this.state); + } else if (op == UserOperation.ListCommunities) { + let res: ListCommunitiesResponse = msg; + this.state.trendingCommunities = res.communities; + this.state.loading = false; + this.setState(this.state); } } } diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 03ace380..67a3f42e 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -1,7 +1,7 @@ import { Component, linkEvent } from 'inferno'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; -import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces'; +import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType } from '../interfaces'; import { WebSocketService } from '../services'; import { msgOp } from '../utils'; import * as autosize from 'autosize'; @@ -56,7 +56,11 @@ export class PostForm extends Component { () => console.log('complete') ); - WebSocketService.Instance.listCommunities(); + let listCommunitiesForm: ListCommunitiesForm = { + sort: SortType[SortType.TopAll] + } + + WebSocketService.Instance.listCommunities(listCommunitiesForm); } componentDidMount() { diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 66088647..b6139134 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -67,6 +67,12 @@ export interface CommunityResponse { community: Community; } +export interface ListCommunitiesForm { + sort: string; + limit?: number; + auth?: string; +} + export interface ListCommunitiesResponse { op: string; communities: Array; diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index b5efd6a7..99d65adf 100644 --- a/ui/src/services/WebSocketService.ts +++ b/ui/src/services/WebSocketService.ts @@ -1,5 +1,5 @@ import { wsUri } from '../env'; -import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm } from '../interfaces'; +import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm } from '../interfaces'; import { webSocket } from 'rxjs/webSocket'; import { Subject } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; @@ -47,9 +47,9 @@ export class WebSocketService { this.subject.next(this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm)); } - public listCommunities() { - let data = {auth: UserService.Instance.auth }; - this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, data)); + public listCommunities(form: ListCommunitiesForm) { + this.setAuth(form, false); + this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, form)); } public getFollowedCommunities() { From bdcc026d11d44bbb3b553cb99f6c4bc7a44daa6c Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 10 Apr 2019 11:10:57 -0700 Subject: [PATCH 11/13] Adding docker caching --- Dockerfile | 31 ++++++++++++++++++++++++++++--- ui/fuse.js | 4 ++-- ui/set_version.js | 2 ++ ui/src/version.ts | 2 +- 4 files changed, 33 insertions(+), 6 deletions(-) mode change 100644 => 100755 ui/set_version.js diff --git a/Dockerfile b/Dockerfile index 3e4468dd..e2803851 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,34 @@ RUN yarn RUN yarn build FROM rust:1.33 as rust -COPY server /app/server + +# create a new empty shell project +WORKDIR /app +RUN USER=root cargo new server WORKDIR /app/server + +# copy over your manifests +COPY server/Cargo.toml server/Cargo.lock ./ + +# this build step will cache your dependencies +RUN mkdir -p ./src/bin \ + && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs +RUN cargo build --release --bin lemmy +RUN ls ./target/release/.fingerprint/ +RUN rm -r ./target/release/.fingerprint/server-* + +# copy your source tree +# RUN rm -rf ./src/ +COPY server/src ./src/ +COPY server/migrations ./migrations/ + +# build for release +RUN cargo build --frozen --release --bin lemmy +RUN mv /app/server/target/release/lemmy /app/lemmy + +# The output image +# FROM debian:stable-slim +# RUN apt-get -y update && apt-get install -y postgresql-client +# COPY --from=rust /app/server/target/release/lemmy /app/lemmy COPY --from=node /app/ui/dist /app/dist -RUN cargo build --release -RUN mv /app/server/target/release/lemmy /app/ EXPOSE 8536 diff --git a/ui/fuse.js b/ui/fuse.js index fe2c7664..0fdf9a42 100644 --- a/ui/fuse.js +++ b/ui/fuse.js @@ -11,7 +11,7 @@ const transformInferno = require('ts-transform-inferno').default; const transformClasscat = require('ts-transform-classcat').default; let fuse, app; let isProduction = false; -var setVersion = require('./set_version.js').setVersion; +// var setVersion = require('./set_version.js').setVersion; Sparky.task('config', _ => { fuse = new FuseBox({ @@ -42,7 +42,7 @@ Sparky.task('config', _ => { }); app = fuse.bundle('app').instructions('>index.tsx'); }); -Sparky.task('version', _ => setVersion()); +// Sparky.task('version', _ => setVersion()); Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/')); Sparky.task('env', _ => (isProduction = true)); Sparky.task('copy-assets', () => Sparky.src('assets/*.svg').dest('dist/')); diff --git a/ui/set_version.js b/ui/set_version.js old mode 100644 new mode 100755 index bfd640c2..21893085 --- a/ui/set_version.js +++ b/ui/set_version.js @@ -7,3 +7,5 @@ exports.setVersion = function() { let line = `export let version: string = "${revision}";`; fs.writeFileSync("./src/version.ts", line); } + +this.setVersion() diff --git a/ui/src/version.ts b/ui/src/version.ts index 3f710072..c06ed5dd 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export let version: string = "v0.0.2-0-gdae6651"; \ No newline at end of file +export let version: string = "v0.0.2-9-g8e5a5d1"; \ No newline at end of file From e94885eb97b3240ed9cec7f97d0f405b2819e922 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 15 Apr 2019 16:12:06 -0700 Subject: [PATCH 12/13] Commiting before I lose everything. I'll do this properly in a merge --- .../2019-02-26-002946_create_user/down.sql | 3 +- .../2019-02-26-002946_create_user/up.sql | 9 + .../down.sql | 1 + .../2019-02-27-170003_create_community/up.sql | 15 +- .../2019-03-03-163336_create_post/up.sql | 2 + .../2019-03-05-233828_create_comment/up.sql | 1 + .../2019-03-30-212058_create_post_view/up.sql | 33 +- .../down.sql | 1 + .../up.sql | 12 +- .../up.sql | 10 +- .../down.sql | 4 +- .../up.sql | 30 +- .../2019-04-08-015947_create_user_view/up.sql | 3 +- .../down.sql | 8 + .../2019-04-11-144915_create_mod_views/up.sql | 61 ++ server/src/actions/comment.rs | 10 + server/src/actions/comment_view.rs | 24 +- server/src/actions/community.rs | 65 +- server/src/actions/community_view.rs | 49 +- server/src/actions/mod.rs | 2 + server/src/actions/moderator.rs | 655 ++++++++++++++++++ server/src/actions/moderator_views.rs | 427 ++++++++++++ server/src/actions/post.rs | 13 +- server/src/actions/post_view.rs | 26 +- server/src/actions/user.rs | 8 + server/src/actions/user_view.rs | 4 + server/src/apub.rs | 2 + server/src/lib.rs | 9 + server/src/schema.rs | 136 ++++ server/src/websocket_server/server.rs | 430 +++++++++++- ui/src/components/comment-form.tsx | 13 +- ui/src/components/comment-node.tsx | 169 ++++- ui/src/components/comment-nodes.tsx | 11 +- ui/src/components/communities.tsx | 3 +- ui/src/components/community.tsx | 6 +- ui/src/components/modlog.tsx | 184 +++++ ui/src/components/moment-time.tsx | 6 +- ui/src/components/navbar.tsx | 3 + ui/src/components/post-form.tsx | 6 +- ui/src/components/post-listing.tsx | 122 +++- ui/src/components/post.tsx | 22 +- ui/src/components/sidebar.tsx | 159 +++-- ui/src/components/user.tsx | 19 +- ui/src/index.tsx | 2 + ui/src/interfaces.ts | 371 +++++++--- ui/src/main.css | 5 + ui/src/services/WebSocketService.ts | 16 +- ui/src/utils.ts | 8 + ui/src/version.ts | 2 +- 49 files changed, 2885 insertions(+), 295 deletions(-) create mode 100644 server/migrations/2019-04-11-144915_create_mod_views/down.sql create mode 100644 server/migrations/2019-04-11-144915_create_mod_views/up.sql create mode 100644 server/src/actions/moderator.rs create mode 100644 server/src/actions/moderator_views.rs create mode 100644 ui/src/components/modlog.tsx diff --git a/server/migrations/2019-02-26-002946_create_user/down.sql b/server/migrations/2019-02-26-002946_create_user/down.sql index 606be6e1..67a280d6 100644 --- a/server/migrations/2019-02-26-002946_create_user/down.sql +++ b/server/migrations/2019-02-26-002946_create_user/down.sql @@ -1 +1,2 @@ -drop table user_ +drop table user_ban; +drop table user_; diff --git a/server/migrations/2019-02-26-002946_create_user/up.sql b/server/migrations/2019-02-26-002946_create_user/up.sql index 80e6e92a..83112e9b 100644 --- a/server/migrations/2019-02-26-002946_create_user/up.sql +++ b/server/migrations/2019-02-26-002946_create_user/up.sql @@ -6,9 +6,18 @@ create table user_ ( password_encrypted text not null, email text unique, icon bytea, + admin boolean default false, + banned boolean default false, published timestamp not null default now(), updated timestamp, 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'); diff --git a/server/migrations/2019-02-27-170003_create_community/down.sql b/server/migrations/2019-02-27-170003_create_community/down.sql index f293dfad..d5bd3994 100644 --- a/server/migrations/2019-02-27-170003_create_community/down.sql +++ b/server/migrations/2019-02-27-170003_create_community/down.sql @@ -1,3 +1,4 @@ +drop table community_user_ban;; drop table community_moderator; drop table community_follower; drop table community; diff --git a/server/migrations/2019-02-27-170003_create_community/up.sql b/server/migrations/2019-02-27-170003_create_community/up.sql index 46b4df52..ad47adfe 100644 --- a/server/migrations/2019-02-27-170003_create_community/up.sql +++ b/server/migrations/2019-02-27-170003_create_community/up.sql @@ -38,6 +38,7 @@ create table community ( description text, 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, + removed boolean default false, published timestamp not null default now(), updated timestamp ); @@ -46,14 +47,24 @@ create table community_moderator ( 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() + published timestamp not null default now(), + unique (community_id, user_id) ); create table community_follower ( 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() + 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); diff --git a/server/migrations/2019-03-03-163336_create_post/up.sql b/server/migrations/2019-03-03-163336_create_post/up.sql index aaa6911e..c3b7c0b8 100644 --- a/server/migrations/2019-03-03-163336_create_post/up.sql +++ b/server/migrations/2019-03-03-163336_create_post/up.sql @@ -5,6 +5,8 @@ create table post ( body text, 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, + removed boolean default false, + locked boolean default false, published timestamp not null default now(), updated timestamp ); diff --git a/server/migrations/2019-03-05-233828_create_comment/up.sql b/server/migrations/2019-03-05-233828_create_comment/up.sql index aa20d358..214d50a6 100644 --- a/server/migrations/2019-03-05-233828_create_comment/up.sql +++ b/server/migrations/2019-03-05-233828_create_comment/up.sql @@ -4,6 +4,7 @@ create table comment ( post_id int references post on update cascade on delete cascade not null, parent_id int references comment on update cascade on delete cascade, content text not null, + removed boolean default false, published timestamp not null default now(), updated timestamp ); diff --git a/server/migrations/2019-03-30-212058_create_post_view/up.sql b/server/migrations/2019-03-30-212058_create_post_view/up.sql index 95789c73..ecf3280a 100644 --- a/server/migrations/2019-03-30-212058_create_post_view/up.sql +++ b/server/migrations/2019-03-30-212058_create_post_view/up.sql @@ -30,7 +30,8 @@ select ap.*, u.id as user_id, coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed +(select 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 cross join all_post ap left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id @@ -41,33 +42,7 @@ select ap.*, null as user_id, null as my_vote, -null as subscribed +null as subscribed, +null as am_mod 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; */ - - - diff --git a/server/migrations/2019-04-03-155205_create_community_view/down.sql b/server/migrations/2019-04-03-155205_create_community_view/down.sql index 6c7e8708..0c7a33c8 100644 --- a/server/migrations/2019-04-03-155205_create_community_view/down.sql +++ b/server/migrations/2019-04-03-155205_create_community_view/down.sql @@ -1,3 +1,4 @@ drop view community_view; drop view community_moderator_view; drop view community_follower_view; +drop view community_user_ban_view; diff --git a/server/migrations/2019-04-03-155205_create_community_view/up.sql b/server/migrations/2019-04-03-155205_create_community_view/up.sql index 7c608742..510fd0f2 100644 --- a/server/migrations/2019-04-03-155205_create_community_view/up.sql +++ b/server/migrations/2019-04-03-155205_create_community_view/up.sql @@ -13,7 +13,8 @@ with all_community as select ac.*, 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 cross join all_community ac left join community_follower cf on u.id = cf.user_id and ac.id = cf.community_id @@ -23,7 +24,8 @@ union all select ac.*, null as user_id, -null as subscribed +null as subscribed, +null as am_mod 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 community c where cf.community_id = c.id) as community_name 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; diff --git a/server/migrations/2019-04-03-155309_create_comment_view/up.sql b/server/migrations/2019-04-03-155309_create_comment_view/up.sql index a4d2be9f..a73b6182 100644 --- a/server/migrations/2019-04-03-155309_create_comment_view/up.sql +++ b/server/migrations/2019-04-03-155309_create_comment_view/up.sql @@ -3,7 +3,9 @@ with all_comment as ( select 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, 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 @@ -15,7 +17,8 @@ with all_comment as select ac.*, 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 cross join all_comment ac left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id @@ -25,6 +28,7 @@ union all select ac.*, null as user_id, - null as my_vote + null as my_vote, + null as am_mod from all_comment ac ; diff --git a/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql b/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql index 15718917..888a87fe 100644 --- a/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql +++ b/server/migrations/2019-04-07-003142_create_moderation_logs/down.sql @@ -3,4 +3,6 @@ drop table mod_lock_post; drop table mod_remove_comment; drop table mod_remove_community; drop table mod_ban; -drop table mod_add_mod; +drop table mod_ban_from_community; +drop table mod_add; +drop table mod_add_community; diff --git a/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql b/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql index 41929e50..3b320d81 100644 --- a/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql +++ b/server/migrations/2019-04-07-003142_create_moderation_logs/up.sql @@ -1,4 +1,3 @@ - create table mod_remove_post ( id serial primary key, 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, 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, + locked boolean default true, 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, reason text, removed boolean default true, + expires timestamp, when_ timestamp not null default now() ); -- 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 ( 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, reason text, - removed boolean default true, + banned boolean default true, expires timestamp, 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 -create table mod_add_mod ( +create table mod_add ( 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, removed boolean default false, when_ timestamp not null default now() -) +); + diff --git a/server/migrations/2019-04-08-015947_create_user_view/up.sql b/server/migrations/2019-04-08-015947_create_user_view/up.sql index 69d052de..08eb56ca 100644 --- a/server/migrations/2019-04-08-015947_create_user_view/up.sql +++ b/server/migrations/2019-04-08-015947_create_user_view/up.sql @@ -2,10 +2,11 @@ create view user_view as select id, name, fedi_name, +admin, +banned, published, (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 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 from user_ u; - diff --git a/server/migrations/2019-04-11-144915_create_mod_views/down.sql b/server/migrations/2019-04-11-144915_create_mod_views/down.sql new file mode 100644 index 00000000..95018f35 --- /dev/null +++ b/server/migrations/2019-04-11-144915_create_mod_views/down.sql @@ -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; diff --git a/server/migrations/2019-04-11-144915_create_mod_views/up.sql b/server/migrations/2019-04-11-144915_create_mod_views/up.sql new file mode 100644 index 00000000..908028d0 --- /dev/null +++ b/server/migrations/2019-04-11-144915_create_mod_views/up.sql @@ -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; diff --git a/server/src/actions/comment.rs b/server/src/actions/comment.rs index ff502850..2c5c570e 100644 --- a/server/src/actions/comment.rs +++ b/server/src/actions/comment.rs @@ -22,6 +22,7 @@ pub struct Comment { pub post_id: i32, pub parent_id: Option, pub content: String, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -33,6 +34,7 @@ pub struct CommentForm { pub post_id: i32, pub parent_id: Option, pub content: String, + pub removed: Option, pub updated: Option } @@ -135,6 +137,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -146,6 +150,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, + removed: None, updated: None }; @@ -157,6 +162,8 @@ mod tests { url: None, body: None, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -166,6 +173,7 @@ mod tests { content: "A test comment".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + removed: None, parent_id: None, updated: None }; @@ -177,6 +185,7 @@ mod tests { content: "A test comment".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + removed: Some(false), parent_id: None, published: inserted_comment.published, updated: None @@ -187,6 +196,7 @@ mod tests { creator_id: inserted_user.id, post_id: inserted_post.id, parent_id: Some(inserted_comment.id), + removed: None, updated: None }; diff --git a/server/src/actions/comment_view.rs b/server/src/actions/comment_view.rs index 3b4e00bb..e1cc4117 100644 --- a/server/src/actions/comment_view.rs +++ b/server/src/actions/comment_view.rs @@ -13,14 +13,18 @@ table! { post_id -> Int4, parent_id -> Nullable, content -> Text, + removed -> Nullable, published -> Timestamp, updated -> Nullable, + community_id -> Int4, + banned -> Nullable, creator_name -> Varchar, score -> BigInt, upvotes -> BigInt, downvotes -> BigInt, user_id -> Nullable, my_vote -> Nullable, + am_mod -> Nullable, } } @@ -32,14 +36,18 @@ pub struct CommentView { pub post_id: i32, pub parent_id: Option, pub content: String, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option, + pub community_id: i32, + pub banned: Option, pub creator_name: String, pub score: i64, pub upvotes: i64, pub downvotes: i64, pub user_id: Option, pub my_vote: Option, + pub am_mod: Option, } impl CommentView { @@ -130,6 +138,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -141,6 +151,7 @@ mod tests { description: None, category_id: 1, creator_id: inserted_user.id, + removed: None, updated: None }; @@ -152,6 +163,8 @@ mod tests { url: None, body: None, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -162,6 +175,7 @@ mod tests { creator_id: inserted_user.id, post_id: inserted_post.id, parent_id: None, + removed: None, updated: None }; @@ -181,7 +195,10 @@ mod tests { content: "A test comment 32".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + community_id: inserted_community.id, parent_id: None, + removed: Some(false), + banned: None, published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), @@ -189,7 +206,8 @@ mod tests { downvotes: 0, upvotes: 1, user_id: None, - my_vote: None + my_vote: None, + am_mod: None, }; let expected_comment_view_with_user = CommentView { @@ -197,7 +215,10 @@ mod tests { content: "A test comment 32".into(), creator_id: inserted_user.id, post_id: inserted_post.id, + community_id: inserted_community.id, parent_id: None, + removed: Some(false), + banned: None, published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), @@ -206,6 +227,7 @@ mod tests { upvotes: 1, user_id: Some(inserted_user.id), 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(); diff --git a/server/src/actions/community.rs b/server/src/actions/community.rs index 1c6343d0..d179b9a2 100644 --- a/server/src/actions/community.rs +++ b/server/src/actions/community.rs @@ -1,9 +1,9 @@ extern crate diesel; -use schema::{community, community_moderator, community_follower}; +use schema::{community, community_moderator, community_follower, community_user_ban}; use diesel::*; use diesel::result::Error; use serde::{Deserialize, Serialize}; -use {Crud, Followable, Joinable}; +use {Crud, Followable, Joinable, Bannable}; #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[table_name="community"] @@ -14,6 +14,7 @@ pub struct Community { pub description: Option, pub category_id: i32, pub creator_id: i32, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -26,6 +27,7 @@ pub struct CommunityForm { pub description: Option, pub category_id: i32, pub creator_id: i32, + pub removed: Option, pub updated: Option } @@ -46,6 +48,23 @@ pub struct CommunityModeratorForm { 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)] #[belongs_to(Community)] #[table_name = "community_follower"] @@ -125,6 +144,23 @@ impl Joinable for CommunityModerator { } } +impl Bannable for CommunityUserBan { + fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result { + use schema::community_user_ban::dsl::*; + insert_into(community_user_ban) + .values(community_user_ban_form) + .get_result::(conn) + } + + fn unban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result { + 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)] mod tests { use establish_connection; @@ -136,11 +172,13 @@ mod tests { let conn = establish_connection(); let new_user = UserForm { - name: "bob".into(), + name: "bobbee".into(), fedi_name: "rrf".into(), preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -152,6 +190,7 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, + removed: None, updated: None, }; @@ -164,11 +203,11 @@ mod tests { title: "nada".to_owned(), description: None, category_id: 1, + removed: Some(false), published: inserted_community.published, updated: None }; - let community_follower_form = CommunityFollowerForm { community_id: inserted_community.id, user_id: inserted_user.id @@ -176,6 +215,7 @@ mod tests { let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap(); + let expected_community_follower = CommunityFollower { id: inserted_community_follower.id, community_id: inserted_community.id, @@ -197,10 +237,25 @@ mod tests { 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 updated_community = Community::update(&conn, inserted_community.id, &new_community).unwrap(); let ignored_community = CommunityFollower::ignore(&conn, &community_follower_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(); User_::delete(&conn, inserted_user.id).unwrap(); @@ -209,8 +264,10 @@ mod tests { assert_eq!(expected_community, updated_community); assert_eq!(expected_community_follower, inserted_community_follower); 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, left_community); + assert_eq!(1, unban); // assert_eq!(2, loaded_count); assert_eq!(1, num_deleted); diff --git a/server/src/actions/community_view.rs b/server/src/actions/community_view.rs index cb89b226..14f38302 100644 --- a/server/src/actions/community_view.rs +++ b/server/src/actions/community_view.rs @@ -12,6 +12,7 @@ table! { description -> Nullable, category_id -> Int4, creator_id -> Int4, + removed -> Nullable, published -> Timestamp, updated -> Nullable, creator_name -> Varchar, @@ -21,6 +22,7 @@ table! { number_of_comments -> BigInt, user_id -> Nullable, subscribed -> Nullable, + am_mod -> Nullable, } } @@ -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)] #[table_name="community_view"] pub struct CommunityView { @@ -55,6 +68,7 @@ pub struct CommunityView { pub description: Option, pub category_id: i32, pub creator_id: i32, + pub removed: Option, pub published: chrono::NaiveDateTime, pub updated: Option, pub creator_name: String, @@ -64,6 +78,7 @@ pub struct CommunityView { pub number_of_comments: i64, pub user_id: Option, pub subscribed: Option, + pub am_mod: Option, } impl CommunityView { @@ -107,7 +122,7 @@ impl CommunityView { query = query.limit(limit); }; - query.load::(conn) + query.filter(removed.eq(false)).load::(conn) } } @@ -157,3 +172,35 @@ impl CommunityFollowerView { community_follower_view.filter(user_id.eq(from_user_id)).load::(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, Error> { + use actions::community_view::community_user_ban_view::dsl::*; + community_user_ban_view.filter(community_id.eq(from_community_id)).load::(conn) + } + + pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result, Error> { + use actions::community_view::community_user_ban_view::dsl::*; + community_user_ban_view.filter(user_id.eq(from_user_id)).load::(conn) + } + + pub fn get(conn: &PgConnection, from_user_id: i32, from_community_id: i32) -> Result { + 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::(conn) + } +} diff --git a/server/src/actions/mod.rs b/server/src/actions/mod.rs index 819d5cda..ece1e885 100644 --- a/server/src/actions/mod.rs +++ b/server/src/actions/mod.rs @@ -7,3 +7,5 @@ pub mod comment_view; pub mod category; pub mod community_view; pub mod user_view; +pub mod moderator; +pub mod moderator_views; diff --git a/server/src/actions/moderator.rs b/server/src/actions/moderator.rs new file mode 100644 index 00000000..089c7ce5 --- /dev/null +++ b/server/src/actions/moderator.rs @@ -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, + pub removed: Option, + 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, + pub removed: Option, +} + +impl Crud for ModRemovePost { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_post::dsl::*; + mod_remove_post.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_post::dsl::*; + diesel::delete(mod_remove_post.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModRemovePostForm) -> Result { + use schema::mod_remove_post::dsl::*; + insert_into(mod_remove_post) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModRemovePostForm) -> Result { + use schema::mod_remove_post::dsl::*; + diesel::update(mod_remove_post.find(from_id)) + .set(form) + .get_result::(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, + 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, +} + +impl Crud for ModLockPost { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_lock_post::dsl::*; + mod_lock_post.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_lock_post::dsl::*; + diesel::delete(mod_lock_post.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModLockPostForm) -> Result { + use schema::mod_lock_post::dsl::*; + insert_into(mod_lock_post) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModLockPostForm) -> Result { + use schema::mod_lock_post::dsl::*; + diesel::update(mod_lock_post.find(from_id)) + .set(form) + .get_result::(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, + pub removed: Option, + 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, + pub removed: Option, +} + +impl Crud for ModRemoveComment { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_comment::dsl::*; + mod_remove_comment.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_comment::dsl::*; + diesel::delete(mod_remove_comment.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModRemoveCommentForm) -> Result { + use schema::mod_remove_comment::dsl::*; + insert_into(mod_remove_comment) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommentForm) -> Result { + use schema::mod_remove_comment::dsl::*; + diesel::update(mod_remove_comment.find(from_id)) + .set(form) + .get_result::(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, + pub removed: Option, + pub expires: Option, + 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, + pub removed: Option, + pub expires: Option, +} + +impl Crud for ModRemoveCommunity { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_community::dsl::*; + mod_remove_community.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_remove_community::dsl::*; + diesel::delete(mod_remove_community.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModRemoveCommunityForm) -> Result { + use schema::mod_remove_community::dsl::*; + insert_into(mod_remove_community) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommunityForm) -> Result { + use schema::mod_remove_community::dsl::*; + diesel::update(mod_remove_community.find(from_id)) + .set(form) + .get_result::(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, + pub banned: Option, + pub expires: Option, + 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, + pub banned: Option, + pub expires: Option, +} + +impl Crud for ModBanFromCommunity { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_ban_from_community::dsl::*; + mod_ban_from_community.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + 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 { + use schema::mod_ban_from_community::dsl::*; + insert_into(mod_ban_from_community) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModBanFromCommunityForm) -> Result { + use schema::mod_ban_from_community::dsl::*; + diesel::update(mod_ban_from_community.find(from_id)) + .set(form) + .get_result::(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, + pub banned: Option, + pub expires: Option, + 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, + pub banned: Option, + pub expires: Option, +} + +impl Crud for ModBan { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_ban::dsl::*; + mod_ban.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_ban::dsl::*; + diesel::delete(mod_ban.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModBanForm) -> Result { + use schema::mod_ban::dsl::*; + insert_into(mod_ban) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModBanForm) -> Result { + use schema::mod_ban::dsl::*; + diesel::update(mod_ban.find(from_id)) + .set(form) + .get_result::(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, + 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, +} + +impl Crud for ModAddCommunity { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add_community::dsl::*; + mod_add_community.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add_community::dsl::*; + diesel::delete(mod_add_community.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModAddCommunityForm) -> Result { + use schema::mod_add_community::dsl::*; + insert_into(mod_add_community) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModAddCommunityForm) -> Result { + use schema::mod_add_community::dsl::*; + diesel::update(mod_add_community.find(from_id)) + .set(form) + .get_result::(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, + 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, +} + +impl Crud for ModAdd { + fn read(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add::dsl::*; + mod_add.find(from_id) + .first::(conn) + } + + fn delete(conn: &PgConnection, from_id: i32) -> Result { + use schema::mod_add::dsl::*; + diesel::delete(mod_add.find(from_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, form: &ModAddForm) -> Result { + use schema::mod_add::dsl::*; + insert_into(mod_add) + .values(form) + .get_result::(conn) + } + + fn update(conn: &PgConnection, from_id: i32, form: &ModAddForm) -> Result { + use schema::mod_add::dsl::*; + diesel::update(mod_add.find(from_id)) + .set(form) + .get_result::(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); + } +} diff --git a/server/src/actions/moderator_views.rs b/server/src/actions/moderator_views.rs new file mode 100644 index 00000000..2e243568 --- /dev/null +++ b/server/src/actions/moderator_views.rs @@ -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, + removed -> Nullable, + 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, + pub removed: Option, + 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, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} + +table! { + mod_lock_post_view (id) { + id -> Int4, + mod_user_id -> Int4, + post_id -> Int4, + locked -> Nullable, + 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, + 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, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} + +table! { + mod_remove_comment_view (id) { + id -> Int4, + mod_user_id -> Int4, + comment_id -> Int4, + reason -> Nullable, + removed -> Nullable, + 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, + pub removed: Option, + 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, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} + +table! { + mod_remove_community_view (id) { + id -> Int4, + mod_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + removed -> Nullable, + expires -> Nullable, + 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, + pub removed: Option, + pub expires: Option, + 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, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} + + +table! { + mod_ban_from_community_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + 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, + pub banned: Option, + pub expires: Option, + 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, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} + +table! { + mod_ban_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + 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, + pub banned: Option, + pub expires: Option, + 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, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} + +table! { + mod_add_community_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + removed -> Nullable, + 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, + 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, + from_mod_user_id: Option, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} + +table! { + mod_add_view (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + removed -> Nullable, + 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, + 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, + limit: Option, + page: Option) -> Result, 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::(conn) + } +} diff --git a/server/src/actions/post.rs b/server/src/actions/post.rs index b53aae46..b811bf32 100644 --- a/server/src/actions/post.rs +++ b/server/src/actions/post.rs @@ -14,6 +14,8 @@ pub struct Post { pub body: Option, pub creator_id: i32, pub community_id: i32, + pub removed: Option, + pub locked: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -26,6 +28,8 @@ pub struct PostForm { pub body: Option, pub creator_id: i32, pub community_id: i32, + pub removed: Option, + pub locked: Option, pub updated: Option } @@ -115,17 +119,20 @@ mod tests { 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: "test community_2".to_string(), + name: "test community_3".to_string(), title: "nada".to_owned(), description: None, category_id: 1, creator_id: inserted_user.id, + removed: None, updated: None }; @@ -137,6 +144,8 @@ mod tests { body: None, creator_id: inserted_user.id, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -150,6 +159,8 @@ mod tests { creator_id: inserted_user.id, community_id: inserted_community.id, published: inserted_post.published, + removed: Some(false), + locked: Some(false), updated: None }; diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index 6ca85c34..9b4395d3 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -19,6 +19,8 @@ table! { body -> Nullable, creator_id -> Int4, community_id -> Int4, + removed -> Nullable, + locked -> Nullable, published -> Timestamp, updated -> Nullable, creator_name -> Varchar, @@ -31,6 +33,7 @@ table! { user_id -> Nullable, my_vote -> Nullable, subscribed -> Nullable, + am_mod -> Nullable, } } @@ -44,6 +47,8 @@ pub struct PostView { pub body: Option, pub creator_id: i32, pub community_id: i32, + pub removed: Option, + pub locked: Option, pub published: chrono::NaiveDateTime, pub updated: Option, pub creator_name: String, @@ -56,6 +61,7 @@ pub struct PostView { pub user_id: Option, pub my_vote: Option, pub subscribed: Option, + pub am_mod: Option, } impl PostView { @@ -110,6 +116,8 @@ impl PostView { .order_by(score.desc()) }; + query = query.filter(removed.eq(false)); + query.load::(conn) } @@ -156,7 +164,9 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - updated: None + updated: None, + admin: None, + banned: None, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -167,6 +177,7 @@ mod tests { description: None, creator_id: inserted_user.id, category_id: 1, + removed: None, updated: None }; @@ -178,6 +189,8 @@ mod tests { body: None, creator_id: inserted_user.id, community_id: inserted_community.id, + removed: None, + locked: None, updated: None }; @@ -216,6 +229,8 @@ mod tests { creator_id: inserted_user.id, creator_name: user_name.to_owned(), community_id: inserted_community.id, + removed: Some(false), + locked: Some(false), community_name: community_name.to_owned(), number_of_comments: 0, score: 1, @@ -224,7 +239,8 @@ mod tests { hot_rank: 864, published: inserted_post.published, updated: None, - subscribed: None + subscribed: None, + am_mod: None, }; let expected_post_listing_with_user = PostView { @@ -234,6 +250,8 @@ mod tests { name: post_name.to_owned(), url: None, body: None, + removed: Some(false), + locked: Some(false), creator_id: inserted_user.id, creator_name: user_name.to_owned(), community_id: inserted_community.id, @@ -245,7 +263,8 @@ mod tests { hot_rank: 864, published: inserted_post.published, 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!(1, like_removed); assert_eq!(1, num_deleted); - } } diff --git a/server/src/actions/user.rs b/server/src/actions/user.rs index d646adcb..524fb66d 100644 --- a/server/src/actions/user.rs +++ b/server/src/actions/user.rs @@ -17,6 +17,8 @@ pub struct User_ { pub password_encrypted: String, pub email: Option, pub icon: Option>, + pub admin: Option, + pub banned: Option, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -28,6 +30,8 @@ pub struct UserForm { pub fedi_name: String, pub preferred_username: Option, pub password_encrypted: String, + pub admin: Option, + pub banned: Option, pub email: Option, pub updated: Option } @@ -122,6 +126,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, + admin: None, + banned: None, updated: None }; @@ -135,6 +141,8 @@ mod tests { password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(), email: None, icon: None, + admin: Some(false), + banned: Some(false), published: inserted_user.published, updated: None }; diff --git a/server/src/actions/user_view.rs b/server/src/actions/user_view.rs index 5873a5c8..4457e08a 100644 --- a/server/src/actions/user_view.rs +++ b/server/src/actions/user_view.rs @@ -8,6 +8,8 @@ table! { id -> Int4, name -> Varchar, fedi_name -> Varchar, + admin -> Nullable, + banned -> Nullable, published -> Timestamp, number_of_posts -> BigInt, post_score -> BigInt, @@ -22,6 +24,8 @@ pub struct UserView { pub id: i32, pub name: String, pub fedi_name: String, + pub admin: Option, + pub banned: Option, pub published: chrono::NaiveDateTime, pub number_of_posts: i64, pub post_score: i64, diff --git a/server/src/apub.rs b/server/src/apub.rs index b2456261..9a535c0b 100644 --- a/server/src/apub.rs +++ b/server/src/apub.rs @@ -44,6 +44,8 @@ mod tests { email: None, icon: None, published: naive_now(), + admin: None, + banned: None, updated: None }; diff --git a/server/src/lib.rs b/server/src/lib.rs index 814363b4..ab971edd 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -50,6 +50,11 @@ pub trait Likeable { fn remove(conn: &PgConnection, form: &T) -> Result where Self: Sized; } +pub trait Bannable { + fn ban(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn unban(conn: &PgConnection, form: &T) -> Result where Self: Sized; +} + pub fn establish_connection() -> PgConnection { let db_url = Settings::get().db_url; PgConnection::establish(&db_url) @@ -88,6 +93,10 @@ pub fn naive_now() -> NaiveDateTime { 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 { EMAIL_REGEX.is_match(test) } diff --git a/server/src/schema.rs b/server/src/schema.rs index fe11a46b..873f32e8 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -12,6 +12,7 @@ table! { post_id -> Int4, parent_id -> Nullable, content -> Text, + removed -> Nullable, published -> Timestamp, updated -> Nullable, } @@ -36,6 +37,7 @@ table! { description -> Nullable, category_id -> Int4, creator_id -> Int4, + removed -> Nullable, published -> Timestamp, updated -> Nullable, } @@ -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, + when_ -> Timestamp, + } +} + +table! { + mod_add_community (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + removed -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_ban (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_ban_from_community (id) { + id -> Int4, + mod_user_id -> Int4, + other_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + banned -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_lock_post (id) { + id -> Int4, + mod_user_id -> Int4, + post_id -> Int4, + locked -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_remove_comment (id) { + id -> Int4, + mod_user_id -> Int4, + comment_id -> Int4, + reason -> Nullable, + removed -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_remove_community (id) { + id -> Int4, + mod_user_id -> Int4, + community_id -> Int4, + reason -> Nullable, + removed -> Nullable, + expires -> Nullable, + when_ -> Timestamp, + } +} + +table! { + mod_remove_post (id) { + id -> Int4, + mod_user_id -> Int4, + post_id -> Int4, + reason -> Nullable, + removed -> Nullable, + when_ -> Timestamp, + } +} + table! { post (id) { id -> Int4, @@ -67,6 +168,8 @@ table! { body -> Nullable, creator_id -> Int4, community_id -> Int4, + removed -> Nullable, + locked -> Nullable, published -> Timestamp, updated -> Nullable, } @@ -91,11 +194,21 @@ table! { password_encrypted -> Text, email -> Nullable, icon -> Nullable, + admin -> Nullable, + banned -> Nullable, published -> Timestamp, updated -> Nullable, } } +table! { + user_ban (id) { + id -> Int4, + user_id -> Int4, + published -> Timestamp, + } +} + joinable!(comment -> post (post_id)); joinable!(comment -> user_ (creator_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_moderator -> community (community_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 -> user_ (creator_id)); joinable!(post_like -> post (post_id)); joinable!(post_like -> user_ (user_id)); +joinable!(user_ban -> user_ (user_id)); allow_tables_to_appear_in_same_query!( category, @@ -119,7 +245,17 @@ allow_tables_to_appear_in_same_query!( community, community_follower, 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_like, user_, + user_ban, ); diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 137761ab..b3bdf78d 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize}; use serde_json::{Value}; use bcrypt::{verify}; 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::user::*; use actions::post::*; @@ -20,10 +21,12 @@ use actions::comment_view::*; use actions::category::*; use actions::community_view::*; use actions::user_view::*; +use actions::moderator_views::*; +use actions::moderator::*; #[derive(EnumString,ToString,Debug)] 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)] @@ -204,7 +207,10 @@ pub struct EditComment { content: String, parent_id: Option, edit_id: i32, + creator_id: i32, post_id: i32, + removed: Option, + reason: Option, auth: String } @@ -222,7 +228,6 @@ pub struct CreateCommentLike { auth: String } - #[derive(Serialize, Deserialize)] pub struct CreatePostLike { post_id: i32, @@ -240,10 +245,14 @@ pub struct CreatePostLikeResponse { #[derive(Serialize, Deserialize)] pub struct EditPost { edit_id: i32, + creator_id: i32, community_id: i32, name: String, url: Option, body: Option, + removed: Option, + reason: Option, + locked: Option, auth: String } @@ -254,6 +263,9 @@ pub struct EditCommunity { title: String, description: Option, category_id: i32, + removed: Option, + reason: Option, + expires: Option, auth: String } @@ -296,6 +308,59 @@ pub struct GetUserDetailsResponse { saved_comments: Vec, } +#[derive(Serialize, Deserialize)] +pub struct GetModlog { + mod_user_id: Option, + community_id: Option, + limit: Option, + page: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct GetModlogResponse { + op: String, + removed_posts: Vec, + locked_posts: Vec, + removed_comments: Vec, + removed_communities: Vec, + banned_from_community: Vec, + banned: Vec, + added_to_community: Vec, + added: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct BanFromCommunity { + community_id: i32, + user_id: i32, + ban: bool, + reason: Option, + expires: Option, + 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, +} + /// `ChatServer` manages chat rooms and responsible for coordinating chat /// session. implementation is super primitive 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` @@ -397,6 +475,9 @@ impl Handler for ChatServer { let op = &json["op"].as_str().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 { UserOperation::Login => { let login: Login = serde_json::from_str(data).unwrap(); @@ -470,13 +551,18 @@ impl Handler for ChatServer { let get_user_details: GetUserDetails = serde_json::from_str(data).unwrap(); get_user_details.perform(self, msg.id) }, - // _ => { - // let e = ErrorMessage { - // op: "Unknown".to_string(), - // error: "Unknown User Operation".to_string() - // }; - // serde_json::to_string(&e).unwrap() - // } + UserOperation::GetModlog => { + let get_modlog: GetModlog = serde_json::from_str(data).unwrap(); + get_modlog.perform(self, msg.id) + }, + UserOperation::BanFromCommunity => { + 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) @@ -554,7 +640,9 @@ impl Perform for Register { email: self.email.to_owned(), password_encrypted: self.password.to_owned(), preferred_username: None, - updated: None + updated: None, + admin: None, + banned: None, }; // Create the user @@ -609,7 +697,8 @@ impl Perform for CreateCommunity { description: self.description.to_owned(), category_id: self.category_id, creator_id: user_id, - updated: None + removed: None, + updated: None, }; let inserted_community = match Community::create(&conn, &community_form) { @@ -737,12 +826,19 @@ impl Perform for CreatePost { 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 { name: self.name.to_owned(), url: self.url.to_owned(), body: self.body.to_owned(), community_id: self.community_id, creator_id: user_id, + removed: None, + locked: None, updated: None }; @@ -913,6 +1009,12 @@ impl Perform for CreateComment { 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 comment_form = CommentForm { @@ -920,6 +1022,7 @@ impl Perform for CreateComment { parent_id: self.parent_id.to_owned(), post_id: self.post_id, creator_id: user_id, + removed: None, updated: None }; @@ -991,10 +1094,22 @@ impl Perform for EditComment { let user_id = claims.id; - // Verify its the creator - let orig_comment = Comment::read(&conn, self.edit_id).unwrap(); - if user_id != orig_comment.creator_id { - return self.error("Incorrect creator."); + + // Verify its the creator or a mod + let orig_comment = CommentView::read(&conn, self.edit_id, None).unwrap(); + let mut editors: Vec = 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()); @@ -1003,7 +1118,8 @@ impl Perform for EditComment { content: content_slurs_removed, parent_id: self.parent_id, post_id: self.post_id, - creator_id: user_id, + creator_id: self.creator_id, + removed: self.removed.to_owned(), 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(); @@ -1061,6 +1188,12 @@ impl Perform for CreateCommentLike { 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 { comment_id: self.comment_id, post_id: self.post_id, @@ -1173,6 +1306,12 @@ impl Perform for CreatePostLike { 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 { post_id: self.post_id, user_id: user_id, @@ -1236,18 +1375,30 @@ impl Perform for EditPost { let user_id = claims.id; - // Verify its the creator - let orig_post = Post::read(&conn, self.edit_id).unwrap(); - if user_id != orig_post.creator_id { - return self.error("Incorrect creator."); + // Verify its the creator or a mod + let mut editors: Vec = CommunityModeratorView::for_community(&conn, self.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, self.community_id).is_ok() { + return self.error("You have been banned from this community"); } let post_form = PostForm { name: self.name.to_owned(), url: self.url.to_owned(), body: self.body.to_owned(), - creator_id: user_id, + creator_id: self.creator_id.to_owned(), community_id: self.community_id, + removed: self.removed.to_owned(), + locked: self.locked.to_owned(), 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 mut post_sent = post_view.clone(); @@ -1307,7 +1478,6 @@ impl Perform for EditCommunity { let user_id = claims.id; - // Verify its a mod let moderator_view = CommunityModeratorView::for_community(&conn, self.edit_id).unwrap(); let mod_ids: Vec = moderator_view.into_iter().map(|m| m.user_id).collect(); @@ -1321,6 +1491,7 @@ impl Perform for EditCommunity { description: self.description.to_owned(), category_id: self.category_id.to_owned(), creator_id: user_id, + removed: self.removed.to_owned(), 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 mut community_sent = post_view.clone(); - // community_sent.my_vote = None; + let community_view = CommunityView::read(&conn, self.edit_id, Some(user_id)).unwrap(); let community_out = serde_json::to_string( &CommunityResponse { @@ -1345,15 +1528,17 @@ impl Perform for EditCommunity { ) .unwrap(); - // let post_sent_out = serde_json::to_string( - // &PostResponse { - // op: self.op_type().to_string(), - // post: post_sent - // } - // ) - // .unwrap(); + let community_view_sent = CommunityView::read(&conn, self.edit_id, None).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 } @@ -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 + + } +} + diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index a87dd356..66f3094e 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -1,6 +1,6 @@ import { Component, linkEvent } from 'inferno'; import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces'; -import { WebSocketService } from '../services'; +import { WebSocketService, UserService } from '../services'; import * as autosize from 'autosize'; interface CommentFormProps { @@ -8,6 +8,7 @@ interface CommentFormProps { node?: CommentNodeI; onReplyCancel?(): any; edit?: boolean; + disabled?: boolean; } interface CommentFormState { @@ -21,9 +22,10 @@ export class CommentForm extends Component { commentForm: { auth: 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) { @@ -36,6 +38,7 @@ export class CommentForm extends Component { this.state.commentForm.edit_id = this.props.node.comment.id; this.state.commentForm.parent_id = this.props.node.comment.parent_id; this.state.commentForm.content = this.props.node.comment.content; + this.state.commentForm.creator_id = this.props.node.comment.creator_id; } else { // A reply gets a new parent id this.state.commentForm.parent_id = this.props.node.comment.id; @@ -53,12 +56,12 @@ export class CommentForm extends Component {
-