]> Untitled Git - lemmy.git/commitdiff
Before big moderation merge
authorDessalines <tyhou13@gmx.com>
Tue, 16 Apr 2019 23:04:23 +0000 (16:04 -0700)
committerDessalines <tyhou13@gmx.com>
Tue, 16 Apr 2019 23:04:23 +0000 (16:04 -0700)
38 files changed:
README.md
server/migrations/2019-02-26-002946_create_user/up.sql
server/migrations/2019-02-27-170003_create_community/down.sql
server/migrations/2019-02-27-170003_create_community/up.sql
server/migrations/2019-04-03-155205_create_community_view/down.sql
server/migrations/2019-04-03-155205_create_community_view/up.sql
server/src/actions/comment.rs
server/src/actions/comment_view.rs
server/src/actions/community.rs
server/src/actions/community_view.rs
server/src/actions/moderator.rs
server/src/actions/post.rs
server/src/actions/post_view.rs
server/src/actions/user.rs
server/src/actions/user_view.rs
server/src/apub.rs
server/src/schema.rs
server/src/websocket_server/server.rs
ui/src/components/comment-form.tsx
ui/src/components/comment-node.tsx
ui/src/components/comment-nodes.tsx
ui/src/components/communities.tsx
ui/src/components/footer.tsx [new file with mode: 0644]
ui/src/components/login.tsx
ui/src/components/main.tsx
ui/src/components/modlog.tsx
ui/src/components/navbar.tsx
ui/src/components/post-form.tsx
ui/src/components/post-listing.tsx
ui/src/components/post-listings.tsx
ui/src/components/post.tsx
ui/src/components/setup.tsx [new file with mode: 0644]
ui/src/components/sidebar.tsx
ui/src/components/site-form.tsx [new file with mode: 0644]
ui/src/index.tsx
ui/src/interfaces.ts
ui/src/services/UserService.ts
ui/src/services/WebSocketService.ts

index 1c518a4e78ff4eaa15e974d93fb8ded295da3890..75baef5ae39762ead8fa4e204845a8fe0bb00246 100644 (file)
--- 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
 
index 83112e9ba0c0d11a8e65003694a021d2a3f9f402..ea2c9234f3718d741eed6fbe1f9c1be7617e0cc7 100644 (file)
@@ -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)
index d5bd3994083099be47fee0a660324e387aa7a6b1..219588d8f09f1475af4c91f46505ec5e4f5d1229 100644 (file)
@@ -1,3 +1,4 @@
+drop table site;
 drop table community_user_ban;;
 drop table community_moderator;
 drop table community_follower;
index ad47adfe857078bc5547c604f8b948eefa9301b5..2d6856b3e51363f59845b36f62e7fe1d07ab9094 100644 (file)
@@ -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
+);
index 0c7a33c80fd3d407bd3af24cd3684d041629c813..67d12f6fe1f491ca633a5b3f2eb61d3546058489 100644 (file)
@@ -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;
index 510fd0f2133b3847ecfd95f758abdcede637e92f..1b73af5123deb7072113f100468c3f26d0bd8afe 100644 (file)
@@ -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;
index 2c5c570eeeee38207ceb02207cd4ce7150e94ffb..f6eee5f128395bce578e43554114a34ea084c862 100644 (file)
@@ -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
     };
 
index e1cc41170716425f1319761c3ec24a71335da669..d6d9e40173d856b9cf94ccc2c42bc2c609927b2e 100644 (file)
@@ -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
     };
 
index d179b9a261bc69d5a4e169fcfef9d21545a33c25..ac33193403fcd27ac4d9321201fda81fb0d74d6b 100644 (file)
@@ -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,6 +31,34 @@ pub struct CommunityForm {
   pub updated: Option<chrono::NaiveDateTime>
 }
 
+impl Crud<CommunityForm> for Community {
+  fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
+    use schema::community::dsl::*;
+    community.find(community_id)
+      .first::<Self>(conn)
+  }
+
+  fn delete(conn: &PgConnection, community_id: i32) -> Result<usize, Error> {
+    use schema::community::dsl::*;
+    diesel::delete(community.find(community_id))
+      .execute(conn)
+  }
+
+  fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
+    use schema::community::dsl::*;
+      insert_into(community)
+        .values(new_community)
+        .get_result::<Self>(conn)
+  }
+
+  fn update(conn: &PgConnection, community_id: i32, new_community: &CommunityForm) -> Result<Self, Error> {
+    use schema::community::dsl::*;
+    diesel::update(community.find(community_id))
+      .set(new_community)
+      .get_result::<Self>(conn)
+  }
+}
+
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Community)]
 #[table_name = "community_moderator"]
@@ -48,6 +76,23 @@ pub struct CommunityModeratorForm {
   pub user_id: i32,
 }
 
+impl Joinable<CommunityModeratorForm> for CommunityModerator {
+  fn join(conn: &PgConnection, community_user_form: &CommunityModeratorForm) -> Result<Self, Error> {
+    use schema::community_moderator::dsl::*;
+    insert_into(community_moderator)
+      .values(community_user_form)
+      .get_result::<Self>(conn)
+  }
+
+  fn leave(conn: &PgConnection, community_user_form: &CommunityModeratorForm) -> Result<usize, Error> {
+    use schema::community_moderator::dsl::*;
+    diesel::delete(community_moderator
+      .filter(community_id.eq(community_user_form.community_id))
+      .filter(user_id.eq(community_user_form.user_id)))
+      .execute(conn)
+  }
+}
+
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Community)]
 #[table_name = "community_user_ban"]
@@ -65,6 +110,23 @@ pub struct CommunityUserBanForm {
   pub user_id: i32,
 }
 
+impl Bannable<CommunityUserBanForm> for CommunityUserBan {
+  fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<Self, Error> {
+    use schema::community_user_ban::dsl::*;
+    insert_into(community_user_ban)
+      .values(community_user_ban_form)
+      .get_result::<Self>(conn)
+  }
+
+  fn unban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<usize, Error> {
+    use schema::community_user_ban::dsl::*;
+    diesel::delete(community_user_ban
+      .filter(community_id.eq(community_user_ban_form.community_id))
+      .filter(user_id.eq(community_user_ban_form.user_id)))
+      .execute(conn)
+  }
+}
+
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Community)]
 #[table_name = "community_follower"]
@@ -82,35 +144,6 @@ pub struct CommunityFollowerForm {
   pub user_id: i32,
 }
 
-
-impl Crud<CommunityForm> for Community {
-  fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
-    use schema::community::dsl::*;
-    community.find(community_id)
-      .first::<Self>(conn)
-  }
-
-  fn delete(conn: &PgConnection, community_id: i32) -> Result<usize, Error> {
-    use schema::community::dsl::*;
-    diesel::delete(community.find(community_id))
-      .execute(conn)
-  }
-
-  fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
-    use schema::community::dsl::*;
-      insert_into(community)
-        .values(new_community)
-        .get_result::<Self>(conn)
-  }
-
-  fn update(conn: &PgConnection, community_id: i32, new_community: &CommunityForm) -> Result<Self, Error> {
-    use schema::community::dsl::*;
-    diesel::update(community.find(community_id))
-      .set(new_community)
-      .get_result::<Self>(conn)
-  }
-}
-
 impl Followable<CommunityFollowerForm> for CommunityFollower {
   fn follow(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<Self, Error> {
     use schema::community_follower::dsl::*;
@@ -127,37 +160,50 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
   }
 }
 
-impl Joinable<CommunityModeratorForm> for CommunityModerator {
-  fn join(conn: &PgConnection, community_user_form: &CommunityModeratorForm) -> Result<Self, Error> {
-    use schema::community_moderator::dsl::*;
-    insert_into(community_moderator)
-      .values(community_user_form)
-      .get_result::<Self>(conn)
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
+#[table_name="site"]
+pub struct Site {
+  pub id: i32,
+  pub name: String,
+  pub description: Option<String>,
+  pub creator_id: i32,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>
+}
+
+#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
+#[table_name="site"]
+pub struct SiteForm {
+  pub name: String,
+  pub description: Option<String>,
+  pub creator_id: i32,
+  pub updated: Option<chrono::NaiveDateTime>
+}
+
+impl Crud<SiteForm> for Site {
+  fn read(conn: &PgConnection, _site_id: i32) -> Result<Self, Error> {
+    use schema::site::dsl::*;
+    site.first::<Self>(conn)
   }
 
-  fn leave(conn: &PgConnection, community_user_form: &CommunityModeratorForm) -> Result<usize, Error> {
-    use schema::community_moderator::dsl::*;
-    diesel::delete(community_moderator
-      .filter(community_id.eq(community_user_form.community_id))
-      .filter(user_id.eq(community_user_form.user_id)))
+  fn delete(conn: &PgConnection, site_id: i32) -> Result<usize, Error> {
+    use schema::site::dsl::*;
+    diesel::delete(site.find(site_id))
       .execute(conn)
   }
-}
 
-impl Bannable<CommunityUserBanForm> for CommunityUserBan {
-  fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<Self, Error> {
-    use schema::community_user_ban::dsl::*;
-    insert_into(community_user_ban)
-      .values(community_user_ban_form)
-      .get_result::<Self>(conn)
+  fn create(conn: &PgConnection, new_site: &SiteForm) -> Result<Self, Error> {
+    use schema::site::dsl::*;
+      insert_into(site)
+        .values(new_site)
+        .get_result::<Self>(conn)
   }
 
-  fn unban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<usize, Error> {
-    use schema::community_user_ban::dsl::*;
-    diesel::delete(community_user_ban
-      .filter(community_id.eq(community_user_ban_form.community_id))
-      .filter(user_id.eq(community_user_ban_form.user_id)))
-      .execute(conn)
+  fn update(conn: &PgConnection, site_id: i32, new_site: &SiteForm) -> Result<Self, Error> {
+    use schema::site::dsl::*;
+    diesel::update(site.find(site_id))
+      .set(new_site)
+      .get_result::<Self>(conn)
   }
 }
 
@@ -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
     };
 
index 14f38302fc9ef71cce8a0a67cbe45068492becc4..078e7a6ea644caa85f41a341176419069d396f15 100644 (file)
@@ -59,6 +59,21 @@ table! {
   }
 }
 
+table! {
+    site_view (id) {
+      id -> Int4,
+      name -> Varchar,
+      description -> Nullable<Text>,
+      creator_id -> Int4,
+      published -> Timestamp,
+      updated -> Nullable<Timestamp>,
+      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::<Self>(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<String>,
+  pub creator_id: i32,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  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<Self, Error> {
+    use actions::community_view::site_view::dsl::*;
+    site_view.first::<Self>(conn)
+  }
+}
index 089c7ce560fb76e0e85f131978116bc55269f8d6..a97b21202d3d3ece1e7b91f7e79aacbb821ee8af 100644 (file)
@@ -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
     };
 
index b811bf326b0271dcfb02ad73e61c770b61d90352..468b3a9bd8cb68ec4f0cf5df569fac76e20b3657 100644 (file)
@@ -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
     };
 
index 9b4395d3fc4f92313fda89a0979650abebaf2f90..0ebcf40d3816c6d73fb2ebd2471e1618ba3c67a0 100644 (file)
@@ -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();
index 524fb66d44884a10750ff5c664504597562ed1fc..ea6f36e6f30b33accf1b1ca02fca7ad1bec3bae1 100644 (file)
@@ -17,8 +17,8 @@ pub struct User_ {
   pub password_encrypted: String,
   pub email: Option<String>,
   pub icon: Option<Vec<u8>>,
-  pub admin: Option<bool>,
-  pub banned: Option<bool>,
+  pub admin: bool,
+  pub banned: bool,
   pub published: chrono::NaiveDateTime,
   pub updated: Option<chrono::NaiveDateTime>
 }
@@ -30,8 +30,8 @@ pub struct UserForm {
     pub fedi_name: String,
     pub preferred_username: Option<String>,
     pub password_encrypted: String,
-    pub admin: Option<bool>,
-    pub banned: Option<bool>,
+    pub admin: bool,
+    pub banned: bool,
     pub email: Option<String>,
     pub updated: Option<chrono::NaiveDateTime>
 }
@@ -46,22 +46,26 @@ impl Crud<UserForm> for User_ {
       .execute(conn)
   }
   fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
-    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::<Self>(conn)
   }
   fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
+    diesel::update(user_.find(user_id))
+      .set(form)
+      .get_result::<Self>(conn)
+  }
+}
+
+impl User_ {
+  pub fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
     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::<Self>(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);
   }
 }
index 4457e08a7d777fdb1131fafc70074b34358b2b4b..a5187aee5a10e7fae128e8f5d51d69aa885e3375 100644 (file)
@@ -8,8 +8,8 @@ table! {
     id -> Int4,
     name -> Varchar,
     fedi_name -> Varchar,
-    admin -> Nullable<Bool>,
-    banned -> Nullable<Bool>,
+    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<bool>,
-  pub banned: Option<bool>,
+  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::<Self>(conn)
   }
+
+  pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    use actions::user_view::user_view::dsl::*;
+    user_view.filter(admin.eq(true))
+    .load::<Self>(conn)
+  }
+
+  pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    use actions::user_view::user_view::dsl::*;
+    user_view.filter(banned.eq(true))
+    .load::<Self>(conn)
+  }
 }
 
index 9a535c0b0a6d2b413a61761261c11f4d339edad9..a9a417e2053ad665a62cfb3e151c4a02de8827ee 100644 (file)
@@ -44,8 +44,8 @@ mod tests {
       email: None,
       icon: None,
       published: naive_now(),
-      admin: None,
-      banned: None,
+      admin: false,
+      banned: false,
       updated: None
     };
 
index 873f32e86bc2ff252babcae9d3b5fc76a1801b85..f431610a589553991e28820e0ebbfcbfcc03e6fd 100644 (file)
@@ -185,6 +185,17 @@ table! {
     }
 }
 
+table! {
+    site (id) {
+        id -> Int4,
+        name -> Varchar,
+        description -> Nullable<Text>,
+        creator_id -> Int4,
+        published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+    }
+}
+
 table! {
     user_ (id) {
         id -> Int4,
@@ -194,8 +205,8 @@ table! {
         password_encrypted -> Text,
         email -> Nullable<Text>,
         icon -> Nullable<Bytea>,
-        admin -> Nullable<Bool>,
-        banned -> Nullable<Bool>,
+        admin -> Bool,
+        banned -> Bool,
         published -> Timestamp,
         updated -> Nullable<Timestamp>,
     }
@@ -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,
 );
index b3bdf78d76e17525f5ca5fe00fcaacf93dd6685a..ef05f80193a3775df4995fedc94e66efef0bc63f 100644 (file)
@@ -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<String>,
   password: String,
-  password_verify: String
+  password_verify: String,
+  admin: bool,
 }
 
 #[derive(Serialize, Deserialize)]
@@ -361,6 +362,67 @@ pub struct AddModToCommunityResponse {
   moderators: Vec<CommunityModeratorView>,
 }
 
+#[derive(Serialize, Deserialize)]
+pub struct CreateSite {
+  name: String,
+  description: Option<String>,
+  auth: String
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct EditSite {
+  name: String,
+  description: Option<String>,
+  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<SiteView>,
+  admins: Vec<UserView>,
+  banned: Vec<UserView>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct AddAdmin {
+  user_id: i32,
+  added: bool,
+  auth: String
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct AddAdminResponse {
+  op: String,
+  admins: Vec<UserView>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct BanUser {
+  user_id: i32,
+  ban: bool,
+  reason: Option<String>,
+  expires: Option<i64>,
+  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<StandardMessage> 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
+
+  }
+}
index 66f3094eefcfe549f9b35caf2f9f76a736f96eb6..df079ba33e3a14887d304938374d41560470abc0 100644 (file)
@@ -23,7 +23,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
       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<CommentFormProps, CommentFormState> {
   }
 
   handleCommentSubmit(i: CommentForm, event: any) {
+    event.preventDefault();
     if (i.props.edit) {
       WebSocketService.Instance.editComment(i.state.commentForm);
     } else {
index eba36009c2a4afb7c9280a925007c473184758a5..1fba1d9237c0f3bb7e00155cb963c55a8660e12e 100644 (file)
@@ -154,13 +154,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
   }
 
   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<CommentNodeProps, CommentNodeState> {
   }
 
   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<CommentNodeProps, CommentNodeState> {
   }
 
   handleModBanSubmit(i: CommentNode) {
+    event.preventDefault();
     let form: BanFromCommunityForm = {
       user_id: i.props.node.comment.creator_id,
       community_id: i.props.node.comment.community_id,
index 19ba30c6704e439fb8c2f24ad6a07de54082d474..498c69b8fe75d304a25ce70f12006a35b10ab943 100644 (file)
@@ -7,7 +7,7 @@ interface CommentNodesState {
 
 interface CommentNodesProps {
   nodes: Array<CommentNodeI>;
-  moderators: Array<CommunityUser>;
+  moderators?: Array<CommunityUser>;
   noIndent?: boolean;
   viewOnly?: boolean;
   locked?: boolean;
index f8cce2c00aae0cae03c3b18c7fb5f3bdedc8c177..a78e43a7dc1886d84f476326feb2feec6283fe35 100644 (file)
@@ -62,9 +62,9 @@ export class Communities extends Component<any, CommunitiesState> {
                   <th>Name</th>
                   <th>Title</th>
                   <th>Category</th>
-                  <th class="text-right">Subscribers</th>
-                  <th class="text-right">Posts</th>
-                  <th class="text-right">Comments</th>
+                  <th class="text-right d-none d-md-table-cell">Subscribers</th>
+                  <th class="text-right d-none d-md-table-cell">Posts</th>
+                  <th class="text-right d-none d-md-table-cell">Comments</th>
                   <th></th>
                 </tr>
               </thead>
@@ -74,13 +74,13 @@ export class Communities extends Component<any, CommunitiesState> {
                     <td><Link to={`/community/${community.id}`}>{community.name}</Link></td>
                     <td>{community.title}</td>
                     <td>{community.category_name}</td>
-                    <td class="text-right">{community.number_of_subscribers}</td>
-                    <td class="text-right">{community.number_of_posts}</td>
-                    <td class="text-right">{community.number_of_comments}</td>
+                    <td class="text-right d-none d-md-table-cell">{community.number_of_subscribers}</td>
+                    <td class="text-right d-none d-md-table-cell">{community.number_of_posts}</td>
+                    <td class="text-right d-none d-md-table-cell">{community.number_of_comments}</td>
                     <td class="text-right">
                       {community.subscribed ? 
-                      <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</button> : 
-                      <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</button>
+                      <span class="pointer btn-link" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</span> : 
+                      <span class="pointer btn-link" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</span>
                       }
                     </td>
                   </tr>
diff --git a/ui/src/components/footer.tsx b/ui/src/components/footer.tsx
new file mode 100644 (file)
index 0000000..fe6d971
--- /dev/null
@@ -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<any, any> {
+
+
+  constructor(props: any, context: any) {
+    super(props, context);
+  }
+
+  render() {
+    return (
+      <nav title={version} class="container navbar navbar-expand-md navbar-light navbar-bg p-0 px-3 my-2">
+        <div className="navbar-collapse">
+          <ul class="navbar-nav ml-auto">
+            <li class="nav-item">
+              <Link class="nav-link" to="/modlog">Modlog</Link>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href={repoUrl}>Contribute</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href={repoUrl}>Code</a>
+            </li>
+            <li class="nav-item">
+              <a class="nav-link" href={repoUrl}>About</a>
+            </li>
+          </ul>
+        </div>
+      </nav>
+    );
+  }
+}
+
index 2d2339f55a14c79426022c387aa6ce124806868e..e871acef56aa5dadc438a500e0452018f14cbb63 100644 (file)
@@ -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<any, State> {
   }
 
   handleRegisterSubmit(i: Login, event: any) {
+    event.preventDefault();
     i.state.registerLoading = true;
     i.setState(i.state);
     event.preventDefault();
index 55066d0094c66bcdde9de1cb6e3fe31c752c25e0..2badb23ea85a7c90915be3822aeb408b1d92eb18 100644 (file)
@@ -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<CommunityUser>;
   trendingCommunities: Array<Community>;
+  site: GetSiteResponse;
   loading: boolean;
 }
 
@@ -19,6 +20,21 @@ export class Main extends Component<any, State> {
   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<any, State> {
         () => 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<any, State> {
             <h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> : 
             <div>
               {this.trendingCommunities()}
-              {UserService.Instance.loggedIn ?
+              {UserService.Instance.user && this.state.subscribedCommunities.length > 0 && 
               <div>
                 <h4>Subscribed forums</h4>
                 <ul class="list-inline"> 
@@ -71,9 +89,9 @@ export class Main extends Component<any, State> {
                     <li class="list-inline-item"><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
                   )}
                 </ul>
-              </div> :
-                this.landing()
+              </div>
               }
+              {this.landing()}
             </div>
             }
           </div>
@@ -85,7 +103,7 @@ export class Main extends Component<any, State> {
   trendingCommunities() {
     return (
       <div>
-        <h4>Trending forums</h4> 
+        <h4>Trending <Link class="text-white" to="/communities">forums</Link></h4> 
         <ul class="list-inline"> 
           {this.state.trendingCommunities.map(community =>
             <li class="list-inline-item"><Link to={`/community/${community.id}`}>{community.name}</Link></li>
@@ -98,6 +116,26 @@ export class Main extends Component<any, State> {
   landing() {
     return (
       <div>
+        <h4>{`${this.state.site.site.name}`}</h4>
+        <ul class="my-1 list-inline">
+          <li className="list-inline-item badge badge-light">{this.state.site.site.number_of_users} Users</li>
+          <li className="list-inline-item badge badge-light">{this.state.site.site.number_of_posts} Posts</li>
+          <li className="list-inline-item badge badge-light">{this.state.site.site.number_of_comments} Comments</li>
+          <li className="list-inline-item"><Link className="badge badge-light" to="/modlog">Modlog</Link></li>
+        </ul>
+        <ul class="list-inline small"> 
+          <li class="list-inline-item">admins: </li>
+          {this.state.site.admins.map(admin =>
+            <li class="list-inline-item"><Link class="text-info" to={`/user/${admin.id}`}>{admin.name}</Link></li>
+          )}
+        </ul>
+        {this.state.site.site.description && 
+          <div>
+            <hr />
+            <div className="md-div" dangerouslySetInnerHTML={mdToHtml(this.state.site.site.description)} />
+            <hr />
+          </div>
+        }
         <h4>Welcome to 
           <svg class="icon mx-2"><use xlinkHref="#icon-mouse"></use></svg>
           <a href={repoUrl}>Lemmy<sup>Beta</sup></a>
@@ -127,7 +165,18 @@ export class Main extends Component<any, State> {
       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);
+    } 
   }
 }
 
index 356cbc9256ca113a550b77861a72da8490cb9271..e4567885ef6c19841fa735e541be8af9eef1ab9e 100644 (file)
@@ -9,34 +9,24 @@ import { MomentTime } from './moment-time';
 import * as moment from 'moment';
 
 interface ModlogState {
-  removed_posts: Array<ModRemovePost>,
-  locked_posts: Array<ModLockPost>,
-  removed_comments: Array<ModRemoveComment>,
-  removed_communities: Array<ModRemoveCommunity>,
-  banned_from_community: Array<ModBanFromCommunity>,
-  banned: Array<ModBan>,
-  added_to_community: Array<ModAddCommunity>,
-  added: Array<ModAdd>,
+  combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}>,
+  communityId?: number,
+  communityName?: string,
   loading: boolean;
 }
 
 export class Modlog extends Component<any, ModlogState> {
   private subscription: Subscription;
   private emptyState: ModlogState = {
-    removed_posts: [],
-    locked_posts: [],
-    removed_comments: [],
-    removed_communities: [],
-    banned_from_community: [],
-    banned: [],
-    added_to_community: [],
-    added: [],
-    loading: true
+    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<any, ModlogState> {
     );
 
     let modlogForm: GetModlogForm = {
-
+      community_id: this.state.communityId
     };
     WebSocketService.Instance.getModlog(modlogForm);
   }
@@ -55,30 +45,35 @@ export class Modlog extends Component<any, ModlogState> {
     this.subscription.unsubscribe();
   }
 
-  combined() {
-    let combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}> = [];
-    let removed_posts = addTypeInfo(this.state.removed_posts, "removed_posts");
-    let locked_posts = addTypeInfo(this.state.locked_posts, "locked_posts");
-    let removed_comments = addTypeInfo(this.state.removed_comments, "removed_comments");
-    let removed_communities = addTypeInfo(this.state.removed_communities, "removed_communities");
-    let banned_from_community = addTypeInfo(this.state.banned_from_community, "banned_from_community");
-    let added_to_community = addTypeInfo(this.state.added_to_community, "added_to_community");
-
-    combined.push(...removed_posts);
-    combined.push(...locked_posts);
-    combined.push(...removed_comments);
-    combined.push(...removed_communities);
-    combined.push(...banned_from_community);
-    combined.push(...added_to_community);
+  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");
+
+    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 (
       <tbody>
-        {combined.map(i =>
+        {this.state.combined.map(i =>
           <tr>
             <td><MomentTime data={i.data} /></td>
             <td><Link to={`/user/${i.data.mod_user_id}`}>{i.data.mod_user_name}</Link></td>
@@ -143,7 +138,10 @@ export class Modlog extends Component<any, ModlogState> {
         {this.state.loading ? 
         <h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> : 
         <div>
-          <h4>Modlog</h4>
+          <h4>
+            {this.state.communityName && <Link className="text-white" to={`/community/${this.state.communityId}`}>/f/{this.state.communityName} </Link>}
+            <span>Modlog</span>
+          </h4>
           <div class="table-responsive">
             <table id="modlog_table" class="table table-sm table-hover">
               <thead class="pointer">
@@ -171,14 +169,7 @@ export class Modlog extends Component<any, ModlogState> {
     } 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);
     } 
   }
 }
index 64bf6e01f5786e664eeae643ec5837b5ee5a6bad..ae69382591b672f1cb9977e9bb4d15aa39ca8cdc 100644 (file)
@@ -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<any, NavbarState> {
 
   emptyState: NavbarState = {
-    isLoggedIn: UserService.Instance.loggedIn,
+    isLoggedIn: UserService.Instance.user !== undefined,
     expanded: false,
     expandUserDropdown: false
   }
@@ -50,9 +49,6 @@ export class Navbar extends Component<any, NavbarState> {
         </button>
         <div className={`${!this.state.expanded && 'collapse'} navbar-collapse`}>
           <ul class="navbar-nav mr-auto">
-            <li class="nav-item">
-              <a class="nav-link" href={repoUrl}>About</a>
-            </li>
             <li class="nav-item">
               <Link class="nav-link" to="/communities">Forums</Link>
             </li>
index b2042c1f4f39b68b92c97209af61609650c1d3d6..b8ea359fd194df089bf6867d0752fb7803b8bdd9 100644 (file)
@@ -27,7 +27,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
       name: null,
       auth: null,
       community_id: null,
-      creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null
+      creator_id: (UserService.Instance.user) ? UserService.Instance.user.id : null,
     },
     communities: [],
     loading: false
@@ -95,7 +95,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
               <textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={4} />
             </div>
           </div>
-          <div class="form-group row">
+          {/* Cant change a community from an edit */}
+          {!this.props.post &&
+            <div class="form-group row">
             <label class="col-sm-2 col-form-label">Forum</label>
             <div class="col-sm-10">
               <select class="form-control" value={this.state.postForm.community_id} onInput={linkEvent(this, this.handlePostCommunityChange)}>
@@ -104,7 +106,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
                 )}
               </select>
             </div>
-          </div>
+            </div>
+            }
           <div class="form-group row">
             <div class="col-sm-10">
               <button type="submit" class="btn btn-secondary mr-2">
index d2bfc7bd85a1c9d3c018c839e37ab9fe823272fa..46e2a9949ba8aac5b31c4b866838ac0e72f777c0 100644 (file)
@@ -160,7 +160,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   }
 
   private get myPost(): boolean {
-    return UserService.Instance.loggedIn && this.props.post.creator_id == UserService.Instance.user.id;
+    return UserService.Instance.user && this.props.post.creator_id == UserService.Instance.user.id;
   }
 
   handlePostLike(i: PostListing) {
@@ -220,6 +220,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   }
 
   handleModRemoveSubmit(i: PostListing) {
+    event.preventDefault();
     let form: PostFormI = {
       name: i.props.post.name,
       community_id: i.props.post.community_id,
index ce7833b2bb96ec87e93d19655d11c7ba278b4646..34814400833f0cf80159ca85b70220274f38ca02 100644 (file)
@@ -43,7 +43,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
     sortType: SortType.Hot,
     type_: this.props.communityId 
     ? ListingType.Community 
-    : UserService.Instance.loggedIn
+    : UserService.Instance.user
     ? ListingType.Subscribed 
     : ListingType.All,
     loading: true
@@ -86,7 +86,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
           {this.state.posts.length > 0 
             ? this.state.posts.map(post => 
               <PostListing post={post} showCommunity={!this.props.communityId}/>) 
-                : <div>No Listings. Subscribe to some <Link to="/communities">forums</Link>.</div>
+                : <div>No Listings. {!this.props.communityId && <span>Subscribe to some <Link to="/communities">forums</Link>.</span>}</div>
           }
         </div>
         }
@@ -109,7 +109,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
           <option value={SortType.TopAll}>All</option>
         </select>
         {!this.props.communityId && 
-          UserService.Instance.loggedIn &&
+          UserService.Instance.user &&
             <select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
               <option disabled>Type</option>
               <option value={ListingType.All}>All</option>
index c65f463d184a11412238b39b9acf6afc0cf20f43..d79a6c97cd8bcd74bc9d06bd6a0493c993bb6028 100644 (file)
@@ -79,14 +79,14 @@ export class Post extends Component<any, PostState> {
         {this.state.loading ? 
         <h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> : 
         <div class="row">
-            <div class="col-12 col-sm-8 col-lg-7 mb-3">
+            <div class="col-12 col-md-8 col-lg-7 mb-3">
               <PostListing post={this.state.post} showBody showCommunity editable />
               <div className="mb-2" />
               <CommentForm postId={this.state.post.id} disabled={this.state.post.locked} />
               {this.sortRadios()}
               {this.commentsTree()}
             </div>
-            <div class="col-12 col-sm-4 col-lg-3 mb-3">
+            <div class="col-12 col-md-4 col-lg-3 mb-3 d-none d-md-block">
               {this.state.comments.length > 0 && this.newComments()}
             </div>
             <div class="col-12 col-sm-12 col-lg-2">
diff --git a/ui/src/components/setup.tsx b/ui/src/components/setup.tsx
new file mode 100644 (file)
index 0000000..94480ee
--- /dev/null
@@ -0,0 +1,147 @@
+import { Component, linkEvent } from 'inferno';
+import { Subscription } from "rxjs";
+import { retryWhen, delay, take } from 'rxjs/operators';
+import { RegisterForm, LoginResponse, UserOperation } from '../interfaces';
+import { WebSocketService, UserService } from '../services';
+import { msgOp } from '../utils';
+import { SiteForm } from './site-form';
+
+interface State {
+  userForm: RegisterForm;
+  doneRegisteringUser: boolean;
+  userLoading: boolean;
+}
+
+export class Setup extends Component<any, State> {
+  private subscription: Subscription;
+
+  private emptyState: State = {
+    userForm: {
+      username: undefined,
+      password: undefined,
+      password_verify: undefined,
+      admin: true,
+    },
+    doneRegisteringUser: false,
+    userLoading: false,
+  }
+
+
+  constructor(props: any, context: any) {
+    super(props, context);
+
+    this.state = this.emptyState;
+
+    this.subscription = WebSocketService.Instance.subject
+    .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
+    .subscribe(
+      (msg) => this.parseMessage(msg),
+        (err) => console.error(err),
+        () => console.log("complete")
+    );
+  }
+
+  componentWillUnmount() {
+    this.subscription.unsubscribe();
+  }
+
+  render() {
+    return (
+      <div class="container">
+        <div class="row">
+          <div class="col-12 offset-lg-3 col-lg-6">
+            <h3>Lemmy Instance Setup</h3>
+            {!this.state.doneRegisteringUser ? this.registerUser() : <SiteForm />}
+          </div>
+        </div>
+      </div>
+    )
+  }
+
+  registerUser() {
+    return (
+      <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
+        <h4>Set up Site Administrator</h4>
+        <div class="form-group row">
+          <label class="col-sm-2 col-form-label">Username</label>
+          <div class="col-sm-10">
+            <input type="text" class="form-control" value={this.state.userForm.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} pattern="[a-zA-Z0-9_]+" />
+          </div>
+        </div>
+        <div class="form-group row">
+          <label class="col-sm-2 col-form-label">Email</label>
+          <div class="col-sm-10">
+            <input type="email" class="form-control" placeholder="Optional" value={this.state.userForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} />
+          </div>
+        </div>
+        <div class="form-group row">
+          <label class="col-sm-2 col-form-label">Password</label>
+          <div class="col-sm-10">
+            <input type="password" value={this.state.userForm.password} onInput={linkEvent(this, this.handleRegisterPasswordChange)} class="form-control" required />
+          </div>
+        </div>
+        <div class="form-group row">
+          <label class="col-sm-2 col-form-label">Verify Password</label>
+          <div class="col-sm-10">
+            <input type="password" value={this.state.userForm.password_verify} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} class="form-control" required />
+          </div>
+        </div>
+        <div class="form-group row">
+          <div class="col-sm-10">
+            <button type="submit" class="btn btn-secondary">{this.state.userLoading ? 
+            <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : 'Sign Up'}</button>
+
+          </div>
+        </div>
+      </form>
+    );
+  }
+
+
+  handleRegisterSubmit(i: Setup, event: any) {
+    event.preventDefault();
+    i.state.userLoading = true;
+    i.setState(i.state);
+    event.preventDefault();
+    WebSocketService.Instance.register(i.state.userForm);
+  }
+
+  handleRegisterUsernameChange(i: Setup, event: any) {
+    i.state.userForm.username = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleRegisterEmailChange(i: Setup, event: any) {
+    i.state.userForm.email = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleRegisterPasswordChange(i: Setup, event: any) {
+    i.state.userForm.password = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleRegisterPasswordVerifyChange(i: Setup, event: any) {
+    i.state.userForm.password_verify = event.target.value;
+    i.setState(i.state);
+  }
+
+  parseMessage(msg: any) {
+    let op: UserOperation = msgOp(msg);
+    if (msg.error) {
+      alert(msg.error);
+      this.state.userLoading = false;
+      this.setState(this.state);
+      return;
+    } else if (op == UserOperation.Register) {
+      this.state.userLoading = false;
+      this.state.doneRegisteringUser = true;
+      let res: LoginResponse = msg;
+      UserService.Instance.login(res);
+      console.log(res);
+      this.setState(this.state);
+    } else if (op == UserOperation.CreateSite) {
+      this.props.history.push('/');
+    }
+  }
+}
index 6640d84a05072201ec9839cc04f2fadd66caa43a..b0c0b7b9c79e5a774fb5705eaec7a5615babb02b 100644 (file)
@@ -87,11 +87,18 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
           </div>
         </form>
       }
-      <ul class="mt-1 list-inline">
+      <ul class="my-1 list-inline">
         <li className="list-inline-item"><Link className="badge badge-light" to="/communities">{community.category_name}</Link></li>
         <li className="list-inline-item badge badge-light">{community.number_of_subscribers} Subscribers</li>
         <li className="list-inline-item badge badge-light">{community.number_of_posts} Posts</li>
         <li className="list-inline-item badge badge-light">{community.number_of_comments} Comments</li>
+        <li className="list-inline-item"><Link className="badge badge-light" to={`/modlog/community/${this.props.community.id}`}>Modlog</Link></li>
+      </ul>
+      <ul class="list-inline small"> 
+        <li class="list-inline-item">mods: </li>
+        {this.props.moderators.map(mod =>
+          <li class="list-inline-item"><Link class="text-info" to={`/user/${mod.user_id}`}>{mod.user_name}</Link></li>
+        )}
       </ul>
       <div>
         {community.subscribed 
@@ -103,15 +110,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
         <div>
           <hr />
           <div className="md-div" dangerouslySetInnerHTML={mdToHtml(community.description)} />
+          <hr />
         </div>
       }
-      <hr />
-      <h4>Moderators</h4>
-      <ul class="list-inline"> 
-        {this.props.moderators.map(mod =>
-          <li class="list-inline-item"><Link to={`/user/${mod.user_id}`}>{mod.user_name}</Link></li>
-        )}
-      </ul>
     </div>
     );
   }
@@ -152,7 +153,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
   }
 
   private get amCreator(): boolean {
-    return UserService.Instance.loggedIn && this.props.community.creator_id == UserService.Instance.user.id;
+    return this.props.community.creator_id == UserService.Instance.user.id;
   }
 
   // private get amMod(): boolean {
@@ -180,7 +181,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
   }
 
   handleModRemoveSubmit(i: Sidebar) {
-
+    event.preventDefault();
     let deleteForm: CommunityFormI = {
       name: i.props.community.name,
       title: i.props.community.title,
diff --git a/ui/src/components/site-form.tsx b/ui/src/components/site-form.tsx
new file mode 100644 (file)
index 0000000..78a332f
--- /dev/null
@@ -0,0 +1,86 @@
+import { Component, linkEvent } from 'inferno';
+import { Site, SiteForm as SiteFormI } from '../interfaces';
+import { WebSocketService } from '../services';
+import * as autosize from 'autosize';
+
+interface SiteFormProps {
+  site?: Site; // If a site is given, that means this is an edit
+  onCancel?(): any;
+}
+
+interface SiteFormState {
+  siteForm: SiteFormI;
+  loading: boolean;
+}
+
+export class SiteForm extends Component<SiteFormProps, SiteFormState> {
+  private emptyState: SiteFormState ={
+    siteForm: {
+      name: null
+    },
+    loading: false
+  }
+
+  constructor(props: any, context: any) {
+    super(props, context);
+    this.state = this.emptyState;
+  }
+
+  componentDidMount() {
+    autosize(document.querySelectorAll('textarea'));
+  }
+
+  render() {
+    return (
+      <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
+        <h4>{`${this.props.site ? 'Edit' : 'Name'} your Site`}</h4>
+        <div class="form-group row">
+          <label class="col-12 col-form-label">Name</label>
+          <div class="col-12">
+            <input type="text" class="form-control" value={this.state.siteForm.name} onInput={linkEvent(this, this.handleSiteNameChange)} required minLength={3} />
+          </div>
+        </div>
+        <div class="form-group row">
+          <label class="col-12 col-form-label">Sidebar</label>
+          <div class="col-12">
+            <textarea value={this.state.siteForm.description} onInput={linkEvent(this, this.handleSiteDescriptionChange)} class="form-control" rows={3} />
+          </div>
+        </div>
+        <div class="form-group row">
+          <div class="col-12">
+            <button type="submit" class="btn btn-secondary mr-2">
+              {this.state.loading ? 
+              <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : 
+              this.props.site ? 'Save' : 'Create'}</button>
+              {this.props.site && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>}
+          </div>
+        </div>
+      </form>
+    );
+  }
+
+  handleCreateSiteSubmit(i: SiteForm, event: any) {
+    event.preventDefault();
+    i.state.loading = true;
+    if (i.props.site) {
+      WebSocketService.Instance.editSite(i.state.siteForm);
+    } else {
+      WebSocketService.Instance.createSite(i.state.siteForm);
+    }
+    i.setState(i.state);
+  }
+
+  handleSiteNameChange(i: SiteForm, event: any) {
+    i.state.siteForm.name = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleSiteDescriptionChange(i: SiteForm, event: any) {
+    i.state.siteForm.description = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleCancel(i: SiteForm) {
+    i.props.onCancel();
+  }
+}
index c2f2c92e096ec2e8f5f2f5f969fef885b908c057..cefcac0200faade23ced2d1afed25f2f8c99576c 100644 (file)
@@ -2,6 +2,7 @@ import { render, Component } from 'inferno';
 import { HashRouter, Route, Switch } from 'inferno-router';
 
 import { Navbar } from './components/navbar';
+import { Footer } from './components/footer';
 import { Home } from './components/home';
 import { Login } from './components/login';
 import { CreatePost } from './components/create-post';
@@ -11,6 +12,7 @@ import { Community } from './components/community';
 import { Communities } from './components/communities';
 import { User } from './components/user';
 import { Modlog } from './components/modlog';
+import { Setup } from './components/setup';
 import { Symbols } from './components/symbols';
 
 import './main.css';
@@ -43,10 +45,13 @@ class Index extends Component<any, any> {
             <Route path={`/community/:id`} component={Community} />
             <Route path={`/user/:id/:heading`} component={User} />
             <Route path={`/user/:id`} component={User} />
+            <Route path={`/modlog/community/:community_id`} component={Modlog} />
             <Route path={`/modlog`} component={Modlog} />
+            <Route path={`/setup`} component={Setup} />
           </Switch>
           <Symbols />
         </div>
+        <Footer />
       </HashRouter>
     );
   }
index d8a570d11f448177f4523b9330c72d0595a9b94e..869fb60bd7396b6c5ca1aee5a92a4d4d5814a264 100644 (file)
@@ -1,5 +1,5 @@
 export 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
 }
 
 export enum CommentSortType {
@@ -107,6 +107,19 @@ export interface Category {
   name: string;
 }
 
+export interface Site {
+  id: number;
+  name: string;
+  description?: string;
+  creator_id: number;
+  published: string;
+  updated?: string;
+  creator_name: string;
+  number_of_users: number;
+  number_of_posts: number;
+  number_of_comments: number;
+}
+
 export interface FollowCommunityForm {
   community_id: number;
   follow: boolean;
@@ -294,6 +307,7 @@ export interface RegisterForm {
   email?: string;
   password: string;
   password_verify: string;
+  admin: boolean;
 }
 
 export interface LoginResponse {
@@ -421,4 +435,49 @@ export interface CreatePostLikeResponse {
   post: Post;
 }
 
+export interface SiteForm {
+  name: string;
+  description?: string,
+  removed?: boolean;
+  reason?: string;
+  expires?: number;
+  auth?: string;
+}
+
+export interface GetSiteResponse {
+  op: string;
+  site: Site;
+  admins: Array<UserView>;
+  banned: Array<UserView>;
+}
+
 
+export interface SiteResponse {
+  op: string;
+  site: Site;
+}
+
+export interface BanUserForm {
+  user_id: number;
+  ban: boolean;
+  reason?: string,
+  expires?: number,
+  auth?: string;
+}
+
+export interface BanUserResponse {
+  op: string;
+  user: UserView,
+  banned: boolean,
+}
+
+export interface AddAdminForm {
+  user_id: number;
+  added: boolean;
+  auth?: string;
+}
+
+export interface AddAdminResponse {
+  op: string;
+  admins: Array<UserView>;
+}
index 4e53aa0810c54846a10016119361eb9ec3ef9353..e182134dec4802250071ca51f925f983a4b39e13 100644 (file)
@@ -31,10 +31,6 @@ export class UserService {
     this.sub.next(undefined);
   }
 
-  public get loggedIn(): boolean {
-    return this.user !== undefined;
-  }
-
   public get auth(): string {
     return Cookies.get("jwt");
   }
index 3596bb46e99f3ce2becd8bba872ffa4ff56cb432..80555fd98053aab46e7685660c4b6bbfeab402a2 100644 (file)
@@ -1,5 +1,5 @@
 import { wsUri } from '../env';
-import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm } from '../interfaces';
+import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, SiteForm, Site, UserView } from '../interfaces';
 import { webSocket } from 'rxjs/webSocket';
 import { Subject } from 'rxjs';
 import { retryWhen, delay, take } from 'rxjs/operators';
@@ -8,16 +8,21 @@ import { UserService } from './';
 export class WebSocketService {
   private static _instance: WebSocketService;
   public subject: Subject<any>;
+  public instanceName: string;
+
+  public site: Site;
+  public admins: Array<UserView>;
+  public banned: Array<UserView>;
 
   private constructor() {
     this.subject = webSocket(wsUri);
 
-    // Even tho this isn't used, its necessary to not keep reconnecting
+    // Necessary to not keep reconnecting
     this.subject
       .pipe(retryWhen(errors => errors.pipe(delay(60000), take(999))))
       .subscribe();
 
-    console.log(`Connected to ${wsUri}`);
+      console.log(`Connected to ${wsUri}`);
   }
 
   public static get Instance(){
@@ -125,6 +130,19 @@ export class WebSocketService {
     this.subject.next(this.wsSendWrapper(UserOperation.GetModlog, form));
   }
 
+  public createSite(siteForm: SiteForm) {
+    this.setAuth(siteForm);
+    this.subject.next(this.wsSendWrapper(UserOperation.CreateSite, siteForm));
+  }
+
+  public editSite(siteForm: SiteForm) {
+    this.setAuth(siteForm);
+    this.subject.next(this.wsSendWrapper(UserOperation.EditSite, siteForm));
+  }
+  public getSite() {
+    this.subject.next(this.wsSendWrapper(UserOperation.GetSite, {}));
+  }
+
   private wsSendWrapper(op: UserOperation, data: any) {
     let send = { op: UserOperation[op], data: data };
     console.log(send);
@@ -138,7 +156,6 @@ export class WebSocketService {
       throw "Not logged in";
     }
   }
-
 }
 
 window.onbeforeunload = (() => {