diff --git a/README.md b/README.md index 1c518a4e7..75baef5ae 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,11 @@ Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Infern ## Features - TBD -- -the name -Lead singer from motorhead. -The old school video game. -The furry rodents. +## Why's it called Lemmy? +- Lead singer from [motorhead](https://invidio.us/watch?v=pWB5JZRGl0U). +- The old school [video game](https://en.wikipedia.org/wiki/Lemmings_(video_game)). +- The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/). Goals r/ censorship 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 83112e9ba..ea2c9234f 100644 --- a/server/migrations/2019-02-26-002946_create_user/up.sql +++ b/server/migrations/2019-02-26-002946_create_user/up.sql @@ -6,8 +6,8 @@ create table user_ ( password_encrypted text not null, email text unique, icon bytea, - admin boolean default false, - banned boolean default false, + admin boolean default false not null, + banned boolean default false not null, published timestamp not null default now(), updated timestamp, unique(name, fedi_name) 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 d5bd39940..219588d8f 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 site; drop table community_user_ban;; drop table community_moderator; drop table community_follower; 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 ad47adfe8..2d6856b3e 100644 --- a/server/migrations/2019-02-27-170003_create_community/up.sql +++ b/server/migrations/2019-02-27-170003_create_community/up.sql @@ -68,3 +68,12 @@ create table community_user_ban ( ); insert into community (name, title, category_id, creator_id) values ('main', 'The Default Community', 1, 1); + +create table site ( + id serial primary key, + name varchar(20) not null unique, + description text, + creator_id int references user_ on update cascade on delete cascade not null, + published timestamp not null default now(), + updated timestamp +); 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 0c7a33c80..67d12f6fe 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 @@ -2,3 +2,4 @@ drop view community_view; drop view community_moderator_view; drop view community_follower_view; drop view community_user_ban_view; +drop view site_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 510fd0f21..1b73af512 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 @@ -46,3 +46,11 @@ 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; + +create view site_view as +select *, +(select name from user_ u where s.creator_id = u.id) as creator_name, +(select count(*) from user_) as number_of_users, +(select count(*) from post) as number_of_posts, +(select count(*) from comment) as number_of_comments +from site s; diff --git a/server/src/actions/comment.rs b/server/src/actions/comment.rs index 2c5c570ee..f6eee5f12 100644 --- a/server/src/actions/comment.rs +++ b/server/src/actions/comment.rs @@ -137,8 +137,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; diff --git a/server/src/actions/comment_view.rs b/server/src/actions/comment_view.rs index e1cc41170..d6d9e4017 100644 --- a/server/src/actions/comment_view.rs +++ b/server/src/actions/comment_view.rs @@ -138,8 +138,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; diff --git a/server/src/actions/community.rs b/server/src/actions/community.rs index d179b9a26..ac3319340 100644 --- a/server/src/actions/community.rs +++ b/server/src/actions/community.rs @@ -1,5 +1,5 @@ extern crate diesel; -use schema::{community, community_moderator, community_follower, community_user_ban}; +use schema::{community, community_moderator, community_follower, community_user_ban, site}; use diesel::*; use diesel::result::Error; use serde::{Deserialize, Serialize}; @@ -31,58 +31,6 @@ pub struct CommunityForm { pub updated: Option } -#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] -#[belongs_to(Community)] -#[table_name = "community_moderator"] -pub struct CommunityModerator { - pub id: i32, - pub community_id: i32, - pub user_id: i32, - pub published: chrono::NaiveDateTime, -} - -#[derive(Insertable, AsChangeset, Clone)] -#[table_name="community_moderator"] -pub struct CommunityModeratorForm { - pub community_id: i32, - pub user_id: i32, -} - -#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] -#[belongs_to(Community)] -#[table_name = "community_user_ban"] -pub struct CommunityUserBan { - pub id: i32, - pub community_id: i32, - pub user_id: i32, - pub published: chrono::NaiveDateTime, -} - -#[derive(Insertable, AsChangeset, Clone)] -#[table_name="community_user_ban"] -pub struct CommunityUserBanForm { - pub community_id: i32, - pub user_id: i32, -} - -#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] -#[belongs_to(Community)] -#[table_name = "community_follower"] -pub struct CommunityFollower { - pub id: i32, - pub community_id: i32, - pub user_id: i32, - pub published: chrono::NaiveDateTime, -} - -#[derive(Insertable, AsChangeset, Clone)] -#[table_name="community_follower"] -pub struct CommunityFollowerForm { - pub community_id: i32, - pub user_id: i32, -} - - impl Crud for Community { fn read(conn: &PgConnection, community_id: i32) -> Result { use schema::community::dsl::*; @@ -111,20 +59,21 @@ impl Crud for Community { } } -impl Followable for CommunityFollower { - fn follow(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result { - use schema::community_follower::dsl::*; - insert_into(community_follower) - .values(community_follower_form) - .get_result::(conn) - } - fn ignore(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result { - use schema::community_follower::dsl::*; - diesel::delete(community_follower - .filter(community_id.eq(&community_follower_form.community_id)) - .filter(user_id.eq(&community_follower_form.user_id))) - .execute(conn) - } +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Community)] +#[table_name = "community_moderator"] +pub struct CommunityModerator { + pub id: i32, + pub community_id: i32, + pub user_id: i32, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="community_moderator"] +pub struct CommunityModeratorForm { + pub community_id: i32, + pub user_id: i32, } impl Joinable for CommunityModerator { @@ -144,6 +93,23 @@ impl Joinable for CommunityModerator { } } +#[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, +} + impl Bannable for CommunityUserBan { fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result { use schema::community_user_ban::dsl::*; @@ -161,6 +127,86 @@ impl Bannable for CommunityUserBan { } } +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Community)] +#[table_name = "community_follower"] +pub struct CommunityFollower { + pub id: i32, + pub community_id: i32, + pub user_id: i32, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name="community_follower"] +pub struct CommunityFollowerForm { + pub community_id: i32, + pub user_id: i32, +} + +impl Followable for CommunityFollower { + fn follow(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result { + use schema::community_follower::dsl::*; + insert_into(community_follower) + .values(community_follower_form) + .get_result::(conn) + } + fn ignore(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result { + use schema::community_follower::dsl::*; + diesel::delete(community_follower + .filter(community_id.eq(&community_follower_form.community_id)) + .filter(user_id.eq(&community_follower_form.user_id))) + .execute(conn) + } +} + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] +#[table_name="site"] +pub struct Site { + pub id: i32, + pub name: String, + pub description: Option, + pub creator_id: i32, + pub published: chrono::NaiveDateTime, + pub updated: Option +} + +#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)] +#[table_name="site"] +pub struct SiteForm { + pub name: String, + pub description: Option, + pub creator_id: i32, + pub updated: Option +} + +impl Crud for Site { + fn read(conn: &PgConnection, _site_id: i32) -> Result { + use schema::site::dsl::*; + site.first::(conn) + } + + fn delete(conn: &PgConnection, site_id: i32) -> Result { + use schema::site::dsl::*; + diesel::delete(site.find(site_id)) + .execute(conn) + } + + fn create(conn: &PgConnection, new_site: &SiteForm) -> Result { + use schema::site::dsl::*; + insert_into(site) + .values(new_site) + .get_result::(conn) + } + + fn update(conn: &PgConnection, site_id: i32, new_site: &SiteForm) -> Result { + use schema::site::dsl::*; + diesel::update(site.find(site_id)) + .set(new_site) + .get_result::(conn) + } +} + #[cfg(test)] mod tests { use establish_connection; @@ -177,8 +223,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; diff --git a/server/src/actions/community_view.rs b/server/src/actions/community_view.rs index 14f38302f..078e7a6ea 100644 --- a/server/src/actions/community_view.rs +++ b/server/src/actions/community_view.rs @@ -59,6 +59,21 @@ table! { } } +table! { + site_view (id) { + id -> Int4, + name -> Varchar, + description -> Nullable, + creator_id -> Int4, + published -> Timestamp, + updated -> Nullable, + creator_name -> Varchar, + number_of_users -> BigInt, + number_of_posts -> BigInt, + number_of_comments -> BigInt, + } +} + #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] #[table_name="community_view"] pub struct CommunityView { @@ -204,3 +219,26 @@ impl CommunityUserBanView { .first::(conn) } } + + +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)] +#[table_name="site_view"] +pub struct SiteView { + pub id: i32, + pub name: String, + pub description: Option, + pub creator_id: i32, + pub published: chrono::NaiveDateTime, + pub updated: Option, + pub creator_name: String, + pub number_of_users: i64, + pub number_of_posts: i64, + pub number_of_comments: i64, +} + +impl SiteView { + pub fn read(conn: &PgConnection) -> Result { + use actions::community_view::site_view::dsl::*; + site_view.first::(conn) + } +} diff --git a/server/src/actions/moderator.rs b/server/src/actions/moderator.rs index 089c7ce56..a97b21202 100644 --- a/server/src/actions/moderator.rs +++ b/server/src/actions/moderator.rs @@ -415,8 +415,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; @@ -428,8 +428,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; diff --git a/server/src/actions/post.rs b/server/src/actions/post.rs index b811bf326..468b3a9bd 100644 --- a/server/src/actions/post.rs +++ b/server/src/actions/post.rs @@ -119,8 +119,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; diff --git a/server/src/actions/post_view.rs b/server/src/actions/post_view.rs index 9b4395d3f..0ebcf40d3 100644 --- a/server/src/actions/post_view.rs +++ b/server/src/actions/post_view.rs @@ -165,8 +165,8 @@ mod tests { password_encrypted: "nope".into(), email: None, updated: None, - admin: None, - banned: None, + admin: false, + banned: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); diff --git a/server/src/actions/user.rs b/server/src/actions/user.rs index 524fb66d4..ea6f36e6f 100644 --- a/server/src/actions/user.rs +++ b/server/src/actions/user.rs @@ -17,8 +17,8 @@ pub struct User_ { pub password_encrypted: String, pub email: Option, pub icon: Option>, - pub admin: Option, - pub banned: Option, + pub admin: bool, + pub banned: bool, pub published: chrono::NaiveDateTime, pub updated: Option } @@ -30,8 +30,8 @@ pub struct UserForm { pub fedi_name: String, pub preferred_username: Option, pub password_encrypted: String, - pub admin: Option, - pub banned: Option, + pub admin: bool, + pub banned: bool, pub email: Option, pub updated: Option } @@ -46,22 +46,26 @@ impl Crud for User_ { .execute(conn) } fn create(conn: &PgConnection, form: &UserForm) -> Result { - let mut edited_user = form.clone(); - let password_hash = hash(&form.password_encrypted, DEFAULT_COST) - .expect("Couldn't hash password"); - edited_user.password_encrypted = password_hash; insert_into(user_) - .values(edited_user) + .values(form) .get_result::(conn) } fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result { + diesel::update(user_.find(user_id)) + .set(form) + .get_result::(conn) + } +} + +impl User_ { + pub fn register(conn: &PgConnection, form: &UserForm) -> Result { let mut edited_user = form.clone(); let password_hash = hash(&form.password_encrypted, DEFAULT_COST) .expect("Couldn't hash password"); edited_user.password_encrypted = password_hash; - diesel::update(user_.find(user_id)) - .set(edited_user) - .get_result::(conn) + + Self::create(&conn, &edited_user) + } } @@ -126,8 +130,8 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; @@ -138,11 +142,11 @@ mod tests { name: "thommy".into(), fedi_name: "rrf".into(), preferred_username: None, - password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(), + password_encrypted: "nope".into(), email: None, icon: None, - admin: Some(false), - banned: Some(false), + admin: false, + banned: false, published: inserted_user.published, updated: None }; @@ -151,9 +155,9 @@ mod tests { let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap(); let num_deleted = User_::delete(&conn, inserted_user.id).unwrap(); - assert_eq!(expected_user.id, read_user.id); - assert_eq!(expected_user.id, inserted_user.id); - assert_eq!(expected_user.id, updated_user.id); + assert_eq!(expected_user, read_user); + assert_eq!(expected_user, inserted_user); + assert_eq!(expected_user, updated_user); assert_eq!(1, num_deleted); } } diff --git a/server/src/actions/user_view.rs b/server/src/actions/user_view.rs index 4457e08a7..a5187aee5 100644 --- a/server/src/actions/user_view.rs +++ b/server/src/actions/user_view.rs @@ -8,8 +8,8 @@ table! { id -> Int4, name -> Varchar, fedi_name -> Varchar, - admin -> Nullable, - banned -> Nullable, + admin -> Bool, + banned -> Bool, published -> Timestamp, number_of_posts -> BigInt, post_score -> BigInt, @@ -24,8 +24,8 @@ pub struct UserView { pub id: i32, pub name: String, pub fedi_name: String, - pub admin: Option, - pub banned: Option, + pub admin: bool, + pub banned: bool, pub published: chrono::NaiveDateTime, pub number_of_posts: i64, pub post_score: i64, @@ -40,5 +40,17 @@ impl UserView { user_view.find(from_user_id) .first::(conn) } + + pub fn admins(conn: &PgConnection) -> Result, Error> { + use actions::user_view::user_view::dsl::*; + user_view.filter(admin.eq(true)) + .load::(conn) + } + + pub fn banned(conn: &PgConnection) -> Result, Error> { + use actions::user_view::user_view::dsl::*; + user_view.filter(banned.eq(true)) + .load::(conn) + } } diff --git a/server/src/apub.rs b/server/src/apub.rs index 9a535c0b0..a9a417e20 100644 --- a/server/src/apub.rs +++ b/server/src/apub.rs @@ -44,8 +44,8 @@ mod tests { email: None, icon: None, published: naive_now(), - admin: None, - banned: None, + admin: false, + banned: false, updated: None }; diff --git a/server/src/schema.rs b/server/src/schema.rs index 873f32e86..f431610a5 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -185,6 +185,17 @@ table! { } } +table! { + site (id) { + id -> Int4, + name -> Varchar, + description -> Nullable, + creator_id -> Int4, + published -> Timestamp, + updated -> Nullable, + } +} + table! { user_ (id) { id -> Int4, @@ -194,8 +205,8 @@ table! { password_encrypted -> Text, email -> Nullable, icon -> Nullable, - admin -> Nullable, - banned -> Nullable, + admin -> Bool, + banned -> Bool, published -> Timestamp, updated -> Nullable, } @@ -236,6 +247,7 @@ joinable!(post -> community (community_id)); joinable!(post -> user_ (creator_id)); joinable!(post_like -> post (post_id)); joinable!(post_like -> user_ (user_id)); +joinable!(site -> user_ (creator_id)); joinable!(user_ban -> user_ (user_id)); allow_tables_to_appear_in_same_query!( @@ -256,6 +268,7 @@ allow_tables_to_appear_in_same_query!( mod_remove_post, post, post_like, + site, user_, user_ban, ); diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index b3bdf78d7..ef05f8019 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -26,7 +26,7 @@ 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, GetModlog, BanFromCommunity, AddModToCommunity, + Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser } #[derive(Serialize, Deserialize)] @@ -88,7 +88,8 @@ pub struct Register { username: String, email: Option, password: String, - password_verify: String + password_verify: String, + admin: bool, } #[derive(Serialize, Deserialize)] @@ -361,6 +362,67 @@ pub struct AddModToCommunityResponse { moderators: Vec, } +#[derive(Serialize, Deserialize)] +pub struct CreateSite { + name: String, + description: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct EditSite { + name: String, + description: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct GetSite { +} + +#[derive(Serialize, Deserialize)] +pub struct SiteResponse { + op: String, + site: SiteView, +} + +#[derive(Serialize, Deserialize)] +pub struct GetSiteResponse { + op: String, + site: Option, + admins: Vec, + banned: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct AddAdmin { + user_id: i32, + added: bool, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct AddAdminResponse { + op: String, + admins: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct BanUser { + user_id: i32, + ban: bool, + reason: Option, + expires: Option, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct BanUserResponse { + op: String, + user: UserView, + banned: bool, +} + /// `ChatServer` manages chat rooms and responsible for coordinating chat /// session. implementation is super primitive pub struct ChatServer { @@ -563,6 +625,26 @@ impl Handler for ChatServer { let mod_add_to_community: AddModToCommunity = serde_json::from_str(data).unwrap(); mod_add_to_community.perform(self, msg.id) }, + UserOperation::CreateSite => { + let create_site: CreateSite = serde_json::from_str(data).unwrap(); + create_site.perform(self, msg.id) + }, + UserOperation::EditSite => { + let edit_site: EditSite = serde_json::from_str(data).unwrap(); + edit_site.perform(self, msg.id) + }, + UserOperation::GetSite => { + let get_site: GetSite = serde_json::from_str(data).unwrap(); + get_site.perform(self, msg.id) + }, + UserOperation::AddAdmin => { + let add_admin: AddAdmin = serde_json::from_str(data).unwrap(); + add_admin.perform(self, msg.id) + }, + UserOperation::BanUser => { + let ban_user: BanUser = serde_json::from_str(data).unwrap(); + ban_user.perform(self, msg.id) + }, }; MessageResult(res) @@ -633,6 +715,11 @@ impl Perform for Register { return self.error("No slurs"); } + // Make sure there are no admins + if self.admin && UserView::admins(&conn).unwrap().len() > 0 { + return self.error("Sorry, there's already an admin."); + } + // Register the new user let user_form = UserForm { name: self.username.to_owned(), @@ -641,18 +728,34 @@ impl Perform for Register { password_encrypted: self.password.to_owned(), preferred_username: None, updated: None, - admin: None, - banned: None, + admin: self.admin, + banned: false, }; // Create the user - let inserted_user = match User_::create(&conn, &user_form) { + let inserted_user = match User_::register(&conn, &user_form) { Ok(user) => user, Err(_e) => { return self.error("User already exists."); } }; + // If its an admin, add them as a mod to main + if self.admin { + let community_moderator_form = CommunityModeratorForm { + community_id: 1, + user_id: inserted_user.id + }; + + let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Community moderator already exists."); + } + }; + } + + // Return the jwt serde_json::to_string( &LoginResponse { @@ -1852,3 +1955,284 @@ impl Perform for AddModToCommunity { } } +impl Perform for CreateSite { + fn op_type(&self) -> UserOperation { + UserOperation::CreateSite + } + + 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."); + } + }; + + if has_slurs(&self.name) || + (self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) { + return self.error("No slurs"); + } + + let user_id = claims.id; + + // Make sure user is an admin + if !UserView::read(&conn, user_id).unwrap().admin { + return self.error("Not an admin."); + } + + let site_form = SiteForm { + name: self.name.to_owned(), + description: self.description.to_owned(), + creator_id: user_id, + updated: None, + }; + + match Site::create(&conn, &site_form) { + Ok(site) => site, + Err(_e) => { + return self.error("Site exists already"); + } + }; + + let site_view = SiteView::read(&conn).unwrap(); + + serde_json::to_string( + &SiteResponse { + op: self.op_type().to_string(), + site: site_view, + } + ) + .unwrap() + } +} + +impl Perform for EditSite { + fn op_type(&self) -> UserOperation { + UserOperation::EditSite + } + + 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."); + } + }; + + if has_slurs(&self.name) || + (self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) { + return self.error("No slurs"); + } + + let user_id = claims.id; + + // Make sure user is an admin + if UserView::read(&conn, user_id).unwrap().admin == false { + return self.error("Not an admin."); + } + + let found_site = Site::read(&conn, 1).unwrap(); + + let site_form = SiteForm { + name: self.name.to_owned(), + description: self.description.to_owned(), + creator_id: found_site.creator_id, + updated: Some(naive_now()), + }; + + match Site::update(&conn, 1, &site_form) { + Ok(site) => site, + Err(_e) => { + return self.error("Couldn't update site."); + } + }; + + let site_view = SiteView::read(&conn).unwrap(); + + serde_json::to_string( + &SiteResponse { + op: self.op_type().to_string(), + site: site_view, + } + ) + .unwrap() + } +} + +impl Perform for GetSite { + fn op_type(&self) -> UserOperation { + UserOperation::GetSite + } + + fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String { + + let conn = establish_connection(); + + // It can return a null site in order to redirect + let site_view = match Site::read(&conn, 1) { + Ok(_site) => Some(SiteView::read(&conn).unwrap()), + Err(_e) => None + }; + + let admins = UserView::admins(&conn).unwrap(); + let banned = UserView::banned(&conn).unwrap(); + + serde_json::to_string( + &GetSiteResponse { + op: self.op_type().to_string(), + site: site_view, + admins: admins, + banned: banned, + } + ) + .unwrap() + } +} + +impl Perform for AddAdmin { + fn op_type(&self) -> UserOperation { + UserOperation::AddAdmin + } + + 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; + + // Make sure user is an admin + if UserView::read(&conn, user_id).unwrap().admin == false { + return self.error("Not an admin."); + } + + let read_user = User_::read(&conn, self.user_id).unwrap(); + + let user_form = UserForm { + name: read_user.name, + fedi_name: read_user.fedi_name, + email: read_user.email, + password_encrypted: read_user.password_encrypted, + preferred_username: read_user.preferred_username, + updated: Some(naive_now()), + admin: self.added, + banned: read_user.banned, + }; + + match User_::update(&conn, self.user_id, &user_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Couldn't update user"); + } + }; + + // Mod tables + let form = ModAddForm { + mod_user_id: user_id, + other_user_id: self.user_id, + removed: Some(!self.added), + }; + + ModAdd::create(&conn, &form).unwrap(); + + let admins = UserView::admins(&conn).unwrap(); + + let res = serde_json::to_string( + &AddAdminResponse { + op: self.op_type().to_string(), + admins: admins, + } + ) + .unwrap(); + + res + + } +} + +impl Perform for BanUser { + fn op_type(&self) -> UserOperation { + UserOperation::BanUser + } + + 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; + + // Make sure user is an admin + if UserView::read(&conn, user_id).unwrap().admin == false { + return self.error("Not an admin."); + } + + let read_user = User_::read(&conn, self.user_id).unwrap(); + + let user_form = UserForm { + name: read_user.name, + fedi_name: read_user.fedi_name, + email: read_user.email, + password_encrypted: read_user.password_encrypted, + preferred_username: read_user.preferred_username, + updated: Some(naive_now()), + admin: read_user.admin, + banned: self.ban, + }; + + match User_::update(&conn, self.user_id, &user_form) { + Ok(user) => user, + Err(_e) => { + return self.error("Couldn't update user"); + } + }; + + // Mod tables + let expires = match self.expires { + Some(time) => Some(naive_from_unix(time)), + None => None + }; + + let form = ModBanForm { + mod_user_id: user_id, + other_user_id: self.user_id, + reason: self.reason.to_owned(), + banned: Some(self.ban), + expires: expires, + }; + + ModBan::create(&conn, &form).unwrap(); + + let user_view = UserView::read(&conn, self.user_id).unwrap(); + + let res = serde_json::to_string( + &BanUserResponse { + op: self.op_type().to_string(), + user: user_view, + banned: self.ban + } + ) + .unwrap(); + + res + + } +} diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index 66f3094ee..df079ba33 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -23,7 +23,7 @@ export class CommentForm extends Component { auth: null, content: null, post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId, - creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null, + creator_id: UserService.Instance.user ? UserService.Instance.user.id : null, }, buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply", } @@ -71,6 +71,7 @@ export class CommentForm extends Component { } handleCommentSubmit(i: CommentForm, event: any) { + event.preventDefault(); if (i.props.edit) { WebSocketService.Instance.editComment(i.state.commentForm); } else { diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index eba36009c..1fba1d923 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -154,13 +154,13 @@ export class CommentNode extends Component { } get myComment(): boolean { - return UserService.Instance.loggedIn && this.props.node.comment.creator_id == UserService.Instance.user.id; + return UserService.Instance.user && this.props.node.comment.creator_id == UserService.Instance.user.id; } get canMod(): boolean { // You can do moderator actions only on the mods added after you. - if (UserService.Instance.loggedIn) { + if (UserService.Instance.user) { let modIds = this.props.moderators.map(m => m.user_id); let yourIndex = modIds.findIndex(id => id == UserService.Instance.user.id); if (yourIndex == -1) { @@ -240,6 +240,7 @@ export class CommentNode extends Component { } handleModRemoveSubmit(i: CommentNode) { + event.preventDefault(); let form: CommentFormI = { content: i.props.node.comment.content, edit_id: i.props.node.comment.id, @@ -272,6 +273,7 @@ export class CommentNode extends Component { } handleModBanSubmit(i: CommentNode) { + event.preventDefault(); let form: BanFromCommunityForm = { user_id: i.props.node.comment.creator_id, community_id: i.props.node.comment.community_id, diff --git a/ui/src/components/comment-nodes.tsx b/ui/src/components/comment-nodes.tsx index 19ba30c67..498c69b8f 100644 --- a/ui/src/components/comment-nodes.tsx +++ b/ui/src/components/comment-nodes.tsx @@ -7,7 +7,7 @@ interface CommentNodesState { interface CommentNodesProps { nodes: Array; - moderators: Array; + moderators?: Array; noIndent?: boolean; viewOnly?: boolean; locked?: boolean; diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx index f8cce2c00..a78e43a7d 100644 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@ -62,9 +62,9 @@ export class Communities extends Component { Name Title Category - Subscribers - Posts - Comments + Subscribers + Posts + Comments @@ -74,13 +74,13 @@ export class Communities extends Component { {community.name} {community.title} {community.category_name} - {community.number_of_subscribers} - {community.number_of_posts} - {community.number_of_comments} + {community.number_of_subscribers} + {community.number_of_posts} + {community.number_of_comments} {community.subscribed ? - : - + Unsubscribe : + Subscribe } diff --git a/ui/src/components/footer.tsx b/ui/src/components/footer.tsx new file mode 100644 index 000000000..fe6d971a4 --- /dev/null +++ b/ui/src/components/footer.tsx @@ -0,0 +1,36 @@ +import { Component } from 'inferno'; +import { Link } from 'inferno-router'; +import { repoUrl } from '../utils'; +import { version } from '../version'; + +export class Footer extends Component { + + + constructor(props: any, context: any) { + super(props, context); + } + + render() { + return ( + + ); + } +} + diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index 2d2339f55..e871acef5 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -20,7 +20,8 @@ let emptyState: State = { registerForm: { username: undefined, password: undefined, - password_verify: undefined + password_verify: undefined, + admin: false, }, loginLoading: false, registerLoading: false @@ -147,6 +148,7 @@ export class Login extends Component { } handleRegisterSubmit(i: Login, event: any) { + event.preventDefault(); i.state.registerLoading = true; i.setState(i.state); event.preventDefault(); diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 55066d009..2badb23ea 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -2,14 +2,15 @@ import { Component } from 'inferno'; import { Link } from 'inferno-router'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; -import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType } from '../interfaces'; +import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { PostListings } from './post-listings'; -import { msgOp, repoUrl } from '../utils'; +import { msgOp, repoUrl, mdToHtml } from '../utils'; interface State { subscribedCommunities: Array; trendingCommunities: Array; + site: GetSiteResponse; loading: boolean; } @@ -19,6 +20,21 @@ export class Main extends Component { private emptyState: State = { subscribedCommunities: [], trendingCommunities: [], + site: { + op: null, + site: { + id: null, + name: null, + creator_id: null, + creator_name: null, + published: null, + number_of_users: null, + number_of_posts: null, + number_of_comments: null, + }, + admins: [], + banned: [], + }, loading: true } @@ -35,7 +51,9 @@ export class Main extends Component { () => console.log('complete') ); - if (UserService.Instance.loggedIn) { + WebSocketService.Instance.getSite(); + + if (UserService.Instance.user) { WebSocketService.Instance.getFollowedCommunities(); } @@ -63,7 +81,7 @@ export class Main extends Component {

:
{this.trendingCommunities()} - {UserService.Instance.loggedIn ? + {UserService.Instance.user && this.state.subscribedCommunities.length > 0 &&

Subscribed forums

    @@ -71,9 +89,9 @@ export class Main extends Component {
  • {community.community_name}
  • )}
-
: - this.landing() +
} + {this.landing()} } @@ -85,7 +103,7 @@ export class Main extends Component { trendingCommunities() { return (
-

Trending forums

+

Trending forums

    {this.state.trendingCommunities.map(community =>
  • {community.name}
  • @@ -98,6 +116,26 @@ export class Main extends Component { landing() { return (
    +

    {`${this.state.site.site.name}`}

    +
      +
    • {this.state.site.site.number_of_users} Users
    • +
    • {this.state.site.site.number_of_posts} Posts
    • +
    • {this.state.site.site.number_of_comments} Comments
    • +
    • Modlog
    • +
    +
      +
    • admins:
    • + {this.state.site.admins.map(admin => +
    • {admin.name}
    • + )} +
    + {this.state.site.site.description && +
    +
    +
    +
    +
    + }

    Welcome to LemmyBeta @@ -127,7 +165,18 @@ export class Main extends Component { this.state.trendingCommunities = res.communities; this.state.loading = false; this.setState(this.state); - } + } else if (op == UserOperation.GetSite) { + let res: GetSiteResponse = msg; + + // This means it hasn't been set up yet + if (!res.site) { + this.context.router.history.push("/setup"); + } + this.state.site.admins = res.admins; + this.state.site.site = res.site; + this.state.site.banned = res.banned; + this.setState(this.state); + } } } diff --git a/ui/src/components/modlog.tsx b/ui/src/components/modlog.tsx index 356cbc925..e4567885e 100644 --- a/ui/src/components/modlog.tsx +++ b/ui/src/components/modlog.tsx @@ -9,34 +9,24 @@ import { MomentTime } from './moment-time'; import * as moment from 'moment'; interface ModlogState { - removed_posts: Array, - locked_posts: Array, - removed_comments: Array, - removed_communities: Array, - banned_from_community: Array, - banned: Array, - added_to_community: Array, - added: Array, + combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}>, + communityId?: number, + communityName?: string, loading: boolean; } export class Modlog extends Component { private subscription: Subscription; private emptyState: ModlogState = { - removed_posts: [], - locked_posts: [], - removed_comments: [], - removed_communities: [], - banned_from_community: [], - banned: [], - added_to_community: [], - added: [], - loading: true + combined: [], + loading: true, } constructor(props: any, context: any) { super(props, context); + this.state = this.emptyState; + this.state.communityId = this.props.match.params.community_id ? Number(this.props.match.params.community_id) : undefined; this.subscription = WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) .subscribe( @@ -46,7 +36,7 @@ export class Modlog extends Component { ); let modlogForm: GetModlogForm = { - + community_id: this.state.communityId }; WebSocketService.Instance.getModlog(modlogForm); } @@ -55,30 +45,35 @@ export class Modlog extends Component { this.subscription.unsubscribe(); } - combined() { - let combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}> = []; - let removed_posts = addTypeInfo(this.state.removed_posts, "removed_posts"); - let locked_posts = addTypeInfo(this.state.locked_posts, "locked_posts"); - let removed_comments = addTypeInfo(this.state.removed_comments, "removed_comments"); - let removed_communities = addTypeInfo(this.state.removed_communities, "removed_communities"); - let banned_from_community = addTypeInfo(this.state.banned_from_community, "banned_from_community"); - let added_to_community = addTypeInfo(this.state.added_to_community, "added_to_community"); + setCombined(res: GetModlogResponse) { + let removed_posts = addTypeInfo(res.removed_posts, "removed_posts"); + let locked_posts = addTypeInfo(res.locked_posts, "locked_posts"); + let removed_comments = addTypeInfo(res.removed_comments, "removed_comments"); + let removed_communities = addTypeInfo(res.removed_communities, "removed_communities"); + let banned_from_community = addTypeInfo(res.banned_from_community, "banned_from_community"); + let added_to_community = addTypeInfo(res.added_to_community, "added_to_community"); - combined.push(...removed_posts); - combined.push(...locked_posts); - combined.push(...removed_comments); - combined.push(...removed_communities); - combined.push(...banned_from_community); - combined.push(...added_to_community); + this.state.combined.push(...removed_posts); + this.state.combined.push(...locked_posts); + this.state.combined.push(...removed_comments); + this.state.combined.push(...removed_communities); + this.state.combined.push(...banned_from_community); + this.state.combined.push(...added_to_community); + + if (this.state.communityId && this.state.combined.length > 0) { + this.state.communityName = this.state.combined[0].data.community_name; + } // Sort them by time - combined.sort((a, b) => b.data.when_.localeCompare(a.data.when_)); + this.state.combined.sort((a, b) => b.data.when_.localeCompare(a.data.when_)); - console.log(combined); + this.setState(this.state); + } + combined() { return ( - {combined.map(i => + {this.state.combined.map(i => {i.data.mod_user_name} @@ -143,7 +138,10 @@ export class Modlog extends Component { {this.state.loading ?

    :
    -

    Modlog

    +

    + {this.state.communityName && /f/{this.state.communityName} } + Modlog +

    @@ -171,14 +169,7 @@ export class Modlog extends Component { } else if (op == UserOperation.GetModlog) { let res: GetModlogResponse = msg; this.state.loading = false; - this.state.removed_posts = res.removed_posts; - this.state.locked_posts = res.locked_posts; - this.state.removed_comments = res.removed_comments; - this.state.removed_communities = res.removed_communities; - this.state.banned_from_community = res.banned_from_community; - this.state.added_to_community = res.added_to_community; - - this.setState(this.state); + this.setCombined(res); } } } diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index 64bf6e01f..ae6938259 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -1,6 +1,5 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; -import { repoUrl } from '../utils'; import { UserService } from '../services'; import { version } from '../version'; @@ -13,7 +12,7 @@ interface NavbarState { export class Navbar extends Component { emptyState: NavbarState = { - isLoggedIn: UserService.Instance.loggedIn, + isLoggedIn: UserService.Instance.user !== undefined, expanded: false, expandUserDropdown: false } @@ -50,9 +49,6 @@ export class Navbar extends Component {