Before big moderation merge
This commit is contained in:
parent
5351e379d5
commit
2e73146e50
38 changed files with 1126 additions and 219 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
drop table site;
|
||||
drop table community_user_ban;;
|
||||
drop table community_moderator;
|
||||
drop table community_follower;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[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<CommunityForm> for Community {
|
||||
fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
|
||||
use schema::community::dsl::*;
|
||||
|
@ -111,20 +59,21 @@ impl Crud<CommunityForm> for Community {
|
|||
}
|
||||
}
|
||||
|
||||
impl Followable<CommunityFollowerForm> for CommunityFollower {
|
||||
fn follow(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<Self, Error> {
|
||||
use schema::community_follower::dsl::*;
|
||||
insert_into(community_follower)
|
||||
.values(community_follower_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn ignore(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<usize, Error> {
|
||||
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<CommunityModeratorForm> for CommunityModerator {
|
||||
|
@ -144,6 +93,23 @@ impl Joinable<CommunityModeratorForm> 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<CommunityUserBanForm> for CommunityUserBan {
|
||||
fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<Self, Error> {
|
||||
use schema::community_user_ban::dsl::*;
|
||||
|
@ -161,6 +127,86 @@ impl Bannable<CommunityUserBanForm> 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<CommunityFollowerForm> for CommunityFollower {
|
||||
fn follow(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<Self, Error> {
|
||||
use schema::community_follower::dsl::*;
|
||||
insert_into(community_follower)
|
||||
.values(community_follower_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn ignore(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<usize, Error> {
|
||||
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<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 delete(conn: &PgConnection, site_id: i32) -> Result<usize, Error> {
|
||||
use schema::site::dsl::*;
|
||||
diesel::delete(site.find(site_id))
|
||||
.execute(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 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)
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,8 +44,8 @@ mod tests {
|
|||
email: None,
|
||||
icon: None,
|
||||
published: naive_now(),
|
||||
admin: None,
|
||||
banned: None,
|
||||
admin: false,
|
||||
banned: false,
|
||||
updated: None
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -7,7 +7,7 @@ interface CommentNodesState {
|
|||
|
||||
interface CommentNodesProps {
|
||||
nodes: Array<CommentNodeI>;
|
||||
moderators: Array<CommunityUser>;
|
||||
moderators?: Array<CommunityUser>;
|
||||
noIndent?: boolean;
|
||||
viewOnly?: boolean;
|
||||
locked?: boolean;
|
||||
|
|
|
@ -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>
|
||||
|
|
36
ui/src/components/footer.tsx
Normal file
36
ui/src/components/footer.tsx
Normal file
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
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 (
|
||||
<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
147
ui/src/components/setup.tsx
Normal file
147
ui/src/components/setup.tsx
Normal file
|
@ -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('/');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
86
ui/src/components/site-form.tsx
Normal file
86
ui/src/components/site-form.tsx
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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 = (() => {
|
||||
|
|
Reference in a new issue