mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-26 22:31:20 +00:00
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
|
## Features
|
||||||
- TBD
|
- TBD
|
||||||
-
|
|
||||||
the name
|
|
||||||
|
|
||||||
Lead singer from motorhead.
|
## Why's it called Lemmy?
|
||||||
The old school video game.
|
- Lead singer from [motorhead](https://invidio.us/watch?v=pWB5JZRGl0U).
|
||||||
The furry rodents.
|
- 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
|
Goals r/ censorship
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ create table user_ (
|
||||||
password_encrypted text not null,
|
password_encrypted text not null,
|
||||||
email text unique,
|
email text unique,
|
||||||
icon bytea,
|
icon bytea,
|
||||||
admin boolean default false,
|
admin boolean default false not null,
|
||||||
banned boolean default false,
|
banned boolean default false not null,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp,
|
updated timestamp,
|
||||||
unique(name, fedi_name)
|
unique(name, fedi_name)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
drop table site;
|
||||||
drop table community_user_ban;;
|
drop table community_user_ban;;
|
||||||
drop table community_moderator;
|
drop table community_moderator;
|
||||||
drop table community_follower;
|
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);
|
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_moderator_view;
|
||||||
drop view community_follower_view;
|
drop view community_follower_view;
|
||||||
drop view community_user_ban_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 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
|
(select name from community c where cm.community_id = c.id) as community_name
|
||||||
from community_user_ban cm;
|
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,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -138,8 +138,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
extern crate diesel;
|
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::*;
|
||||||
use diesel::result::Error;
|
use diesel::result::Error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -31,58 +31,6 @@ pub struct CommunityForm {
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
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 {
|
impl Crud<CommunityForm> for Community {
|
||||||
fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
|
fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
|
||||||
use schema::community::dsl::*;
|
use schema::community::dsl::*;
|
||||||
|
@ -111,20 +59,21 @@ impl Crud<CommunityForm> for Community {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Followable<CommunityFollowerForm> for CommunityFollower {
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
fn follow(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<Self, Error> {
|
#[belongs_to(Community)]
|
||||||
use schema::community_follower::dsl::*;
|
#[table_name = "community_moderator"]
|
||||||
insert_into(community_follower)
|
pub struct CommunityModerator {
|
||||||
.values(community_follower_form)
|
pub id: i32,
|
||||||
.get_result::<Self>(conn)
|
pub community_id: i32,
|
||||||
}
|
pub user_id: i32,
|
||||||
fn ignore(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<usize, Error> {
|
pub published: chrono::NaiveDateTime,
|
||||||
use schema::community_follower::dsl::*;
|
}
|
||||||
diesel::delete(community_follower
|
|
||||||
.filter(community_id.eq(&community_follower_form.community_id))
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
.filter(user_id.eq(&community_follower_form.user_id)))
|
#[table_name="community_moderator"]
|
||||||
.execute(conn)
|
pub struct CommunityModeratorForm {
|
||||||
}
|
pub community_id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Joinable<CommunityModeratorForm> for CommunityModerator {
|
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 {
|
impl Bannable<CommunityUserBanForm> for CommunityUserBan {
|
||||||
fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<Self, Error> {
|
fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<Self, Error> {
|
||||||
use schema::community_user_ban::dsl::*;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use establish_connection;
|
use establish_connection;
|
||||||
|
@ -177,8 +223,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
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)]
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
#[table_name="community_view"]
|
#[table_name="community_view"]
|
||||||
pub struct CommunityView {
|
pub struct CommunityView {
|
||||||
|
@ -204,3 +219,26 @@ impl CommunityUserBanView {
|
||||||
.first::<Self>(conn)
|
.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,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -428,8 +428,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -119,8 +119,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -165,8 +165,8 @@ mod tests {
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -17,8 +17,8 @@ pub struct User_ {
|
||||||
pub password_encrypted: String,
|
pub password_encrypted: String,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub icon: Option<Vec<u8>>,
|
pub icon: Option<Vec<u8>>,
|
||||||
pub admin: Option<bool>,
|
pub admin: bool,
|
||||||
pub banned: Option<bool>,
|
pub banned: bool,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ pub struct UserForm {
|
||||||
pub fedi_name: String,
|
pub fedi_name: String,
|
||||||
pub preferred_username: Option<String>,
|
pub preferred_username: Option<String>,
|
||||||
pub password_encrypted: String,
|
pub password_encrypted: String,
|
||||||
pub admin: Option<bool>,
|
pub admin: bool,
|
||||||
pub banned: Option<bool>,
|
pub banned: bool,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -46,22 +46,26 @@ impl Crud<UserForm> for User_ {
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
}
|
}
|
||||||
fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
|
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_)
|
insert_into(user_)
|
||||||
.values(edited_user)
|
.values(form)
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
}
|
}
|
||||||
fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
|
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 mut edited_user = form.clone();
|
||||||
let password_hash = hash(&form.password_encrypted, DEFAULT_COST)
|
let password_hash = hash(&form.password_encrypted, DEFAULT_COST)
|
||||||
.expect("Couldn't hash password");
|
.expect("Couldn't hash password");
|
||||||
edited_user.password_encrypted = password_hash;
|
edited_user.password_encrypted = password_hash;
|
||||||
diesel::update(user_.find(user_id))
|
|
||||||
.set(edited_user)
|
Self::create(&conn, &edited_user)
|
||||||
.get_result::<Self>(conn)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +130,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -138,11 +142,11 @@ mod tests {
|
||||||
name: "thommy".into(),
|
name: "thommy".into(),
|
||||||
fedi_name: "rrf".into(),
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
admin: Some(false),
|
admin: false,
|
||||||
banned: Some(false),
|
banned: false,
|
||||||
published: inserted_user.published,
|
published: inserted_user.published,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
@ -151,9 +155,9 @@ mod tests {
|
||||||
let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
|
let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
|
||||||
let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
||||||
|
|
||||||
assert_eq!(expected_user.id, read_user.id);
|
assert_eq!(expected_user, read_user);
|
||||||
assert_eq!(expected_user.id, inserted_user.id);
|
assert_eq!(expected_user, inserted_user);
|
||||||
assert_eq!(expected_user.id, updated_user.id);
|
assert_eq!(expected_user, updated_user);
|
||||||
assert_eq!(1, num_deleted);
|
assert_eq!(1, num_deleted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
fedi_name -> Varchar,
|
fedi_name -> Varchar,
|
||||||
admin -> Nullable<Bool>,
|
admin -> Bool,
|
||||||
banned -> Nullable<Bool>,
|
banned -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
number_of_posts -> BigInt,
|
number_of_posts -> BigInt,
|
||||||
post_score -> BigInt,
|
post_score -> BigInt,
|
||||||
|
@ -24,8 +24,8 @@ pub struct UserView {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub fedi_name: String,
|
pub fedi_name: String,
|
||||||
pub admin: Option<bool>,
|
pub admin: bool,
|
||||||
pub banned: Option<bool>,
|
pub banned: bool,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub number_of_posts: i64,
|
pub number_of_posts: i64,
|
||||||
pub post_score: i64,
|
pub post_score: i64,
|
||||||
|
@ -40,5 +40,17 @@ impl UserView {
|
||||||
user_view.find(from_user_id)
|
user_view.find(from_user_id)
|
||||||
.first::<Self>(conn)
|
.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,
|
email: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
published: naive_now(),
|
published: naive_now(),
|
||||||
admin: None,
|
admin: false,
|
||||||
banned: None,
|
banned: false,
|
||||||
updated: None
|
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! {
|
table! {
|
||||||
user_ (id) {
|
user_ (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -194,8 +205,8 @@ table! {
|
||||||
password_encrypted -> Text,
|
password_encrypted -> Text,
|
||||||
email -> Nullable<Text>,
|
email -> Nullable<Text>,
|
||||||
icon -> Nullable<Bytea>,
|
icon -> Nullable<Bytea>,
|
||||||
admin -> Nullable<Bool>,
|
admin -> Bool,
|
||||||
banned -> Nullable<Bool>,
|
banned -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
|
@ -236,6 +247,7 @@ joinable!(post -> community (community_id));
|
||||||
joinable!(post -> user_ (creator_id));
|
joinable!(post -> user_ (creator_id));
|
||||||
joinable!(post_like -> post (post_id));
|
joinable!(post_like -> post (post_id));
|
||||||
joinable!(post_like -> user_ (user_id));
|
joinable!(post_like -> user_ (user_id));
|
||||||
|
joinable!(site -> user_ (creator_id));
|
||||||
joinable!(user_ban -> user_ (user_id));
|
joinable!(user_ban -> user_ (user_id));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
|
@ -256,6 +268,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
mod_remove_post,
|
mod_remove_post,
|
||||||
post,
|
post,
|
||||||
post_like,
|
post_like,
|
||||||
|
site,
|
||||||
user_,
|
user_,
|
||||||
user_ban,
|
user_ban,
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,7 +26,7 @@ use actions::moderator::*;
|
||||||
|
|
||||||
#[derive(EnumString,ToString,Debug)]
|
#[derive(EnumString,ToString,Debug)]
|
||||||
pub enum UserOperation {
|
pub enum UserOperation {
|
||||||
Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, 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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -88,7 +88,8 @@ pub struct Register {
|
||||||
username: String,
|
username: String,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
password: String,
|
password: String,
|
||||||
password_verify: String
|
password_verify: String,
|
||||||
|
admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -361,6 +362,67 @@ pub struct AddModToCommunityResponse {
|
||||||
moderators: Vec<CommunityModeratorView>,
|
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
|
/// `ChatServer` manages chat rooms and responsible for coordinating chat
|
||||||
/// session. implementation is super primitive
|
/// session. implementation is super primitive
|
||||||
pub struct ChatServer {
|
pub struct ChatServer {
|
||||||
|
@ -563,6 +625,26 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
let mod_add_to_community: AddModToCommunity = serde_json::from_str(data).unwrap();
|
let mod_add_to_community: AddModToCommunity = serde_json::from_str(data).unwrap();
|
||||||
mod_add_to_community.perform(self, msg.id)
|
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)
|
MessageResult(res)
|
||||||
|
@ -633,6 +715,11 @@ impl Perform for Register {
|
||||||
return self.error("No slurs");
|
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
|
// Register the new user
|
||||||
let user_form = UserForm {
|
let user_form = UserForm {
|
||||||
name: self.username.to_owned(),
|
name: self.username.to_owned(),
|
||||||
|
@ -641,18 +728,34 @@ impl Perform for Register {
|
||||||
password_encrypted: self.password.to_owned(),
|
password_encrypted: self.password.to_owned(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
admin: None,
|
admin: self.admin,
|
||||||
banned: None,
|
banned: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the user
|
// Create the user
|
||||||
let inserted_user = match User_::create(&conn, &user_form) {
|
let inserted_user = match User_::register(&conn, &user_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
return self.error("User already exists.");
|
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
|
// Return the jwt
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&LoginResponse {
|
&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,
|
auth: null,
|
||||||
content: null,
|
content: null,
|
||||||
post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId,
|
post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId,
|
||||||
creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null,
|
creator_id: UserService.Instance.user ? UserService.Instance.user.id : null,
|
||||||
},
|
},
|
||||||
buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply",
|
buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply",
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommentSubmit(i: CommentForm, event: any) {
|
handleCommentSubmit(i: CommentForm, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
if (i.props.edit) {
|
if (i.props.edit) {
|
||||||
WebSocketService.Instance.editComment(i.state.commentForm);
|
WebSocketService.Instance.editComment(i.state.commentForm);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -154,13 +154,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get myComment(): boolean {
|
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 {
|
get canMod(): boolean {
|
||||||
|
|
||||||
// You can do moderator actions only on the mods added after you.
|
// 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 modIds = this.props.moderators.map(m => m.user_id);
|
||||||
let yourIndex = modIds.findIndex(id => id == UserService.Instance.user.id);
|
let yourIndex = modIds.findIndex(id => id == UserService.Instance.user.id);
|
||||||
if (yourIndex == -1) {
|
if (yourIndex == -1) {
|
||||||
|
@ -240,6 +240,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModRemoveSubmit(i: CommentNode) {
|
handleModRemoveSubmit(i: CommentNode) {
|
||||||
|
event.preventDefault();
|
||||||
let form: CommentFormI = {
|
let form: CommentFormI = {
|
||||||
content: i.props.node.comment.content,
|
content: i.props.node.comment.content,
|
||||||
edit_id: i.props.node.comment.id,
|
edit_id: i.props.node.comment.id,
|
||||||
|
@ -272,6 +273,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModBanSubmit(i: CommentNode) {
|
handleModBanSubmit(i: CommentNode) {
|
||||||
|
event.preventDefault();
|
||||||
let form: BanFromCommunityForm = {
|
let form: BanFromCommunityForm = {
|
||||||
user_id: i.props.node.comment.creator_id,
|
user_id: i.props.node.comment.creator_id,
|
||||||
community_id: i.props.node.comment.community_id,
|
community_id: i.props.node.comment.community_id,
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface CommentNodesState {
|
||||||
|
|
||||||
interface CommentNodesProps {
|
interface CommentNodesProps {
|
||||||
nodes: Array<CommentNodeI>;
|
nodes: Array<CommentNodeI>;
|
||||||
moderators: Array<CommunityUser>;
|
moderators?: Array<CommunityUser>;
|
||||||
noIndent?: boolean;
|
noIndent?: boolean;
|
||||||
viewOnly?: boolean;
|
viewOnly?: boolean;
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
|
|
|
@ -62,9 +62,9 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th class="text-right">Subscribers</th>
|
<th class="text-right d-none d-md-table-cell">Subscribers</th>
|
||||||
<th class="text-right">Posts</th>
|
<th class="text-right d-none d-md-table-cell">Posts</th>
|
||||||
<th class="text-right">Comments</th>
|
<th class="text-right d-none d-md-table-cell">Comments</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -74,13 +74,13 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
<td><Link to={`/community/${community.id}`}>{community.name}</Link></td>
|
<td><Link to={`/community/${community.id}`}>{community.name}</Link></td>
|
||||||
<td>{community.title}</td>
|
<td>{community.title}</td>
|
||||||
<td>{community.category_name}</td>
|
<td>{community.category_name}</td>
|
||||||
<td class="text-right">{community.number_of_subscribers}</td>
|
<td class="text-right d-none d-md-table-cell">{community.number_of_subscribers}</td>
|
||||||
<td class="text-right">{community.number_of_posts}</td>
|
<td class="text-right d-none d-md-table-cell">{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_comments}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{community.subscribed ?
|
{community.subscribed ?
|
||||||
<button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</button> :
|
<span class="pointer btn-link" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</span> :
|
||||||
<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.handleSubscribe)}>Subscribe</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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: {
|
registerForm: {
|
||||||
username: undefined,
|
username: undefined,
|
||||||
password: undefined,
|
password: undefined,
|
||||||
password_verify: undefined
|
password_verify: undefined,
|
||||||
|
admin: false,
|
||||||
},
|
},
|
||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
registerLoading: false
|
registerLoading: false
|
||||||
|
@ -147,6 +148,7 @@ export class Login extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRegisterSubmit(i: Login, event: any) {
|
handleRegisterSubmit(i: Login, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
i.state.registerLoading = true;
|
i.state.registerLoading = true;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -2,14 +2,15 @@ import { Component } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
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 { WebSocketService, UserService } from '../services';
|
||||||
import { PostListings } from './post-listings';
|
import { PostListings } from './post-listings';
|
||||||
import { msgOp, repoUrl } from '../utils';
|
import { msgOp, repoUrl, mdToHtml } from '../utils';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
subscribedCommunities: Array<CommunityUser>;
|
subscribedCommunities: Array<CommunityUser>;
|
||||||
trendingCommunities: Array<Community>;
|
trendingCommunities: Array<Community>;
|
||||||
|
site: GetSiteResponse;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +20,21 @@ export class Main extends Component<any, State> {
|
||||||
private emptyState: State = {
|
private emptyState: State = {
|
||||||
subscribedCommunities: [],
|
subscribedCommunities: [],
|
||||||
trendingCommunities: [],
|
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
|
loading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +51,9 @@ export class Main extends Component<any, State> {
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (UserService.Instance.loggedIn) {
|
WebSocketService.Instance.getSite();
|
||||||
|
|
||||||
|
if (UserService.Instance.user) {
|
||||||
WebSocketService.Instance.getFollowedCommunities();
|
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> :
|
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
<div>
|
<div>
|
||||||
{this.trendingCommunities()}
|
{this.trendingCommunities()}
|
||||||
{UserService.Instance.loggedIn ?
|
{UserService.Instance.user && this.state.subscribedCommunities.length > 0 &&
|
||||||
<div>
|
<div>
|
||||||
<h4>Subscribed forums</h4>
|
<h4>Subscribed forums</h4>
|
||||||
<ul class="list-inline">
|
<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>
|
<li class="list-inline-item"><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div> :
|
</div>
|
||||||
this.landing()
|
|
||||||
}
|
}
|
||||||
|
{this.landing()}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,7 +103,7 @@ export class Main extends Component<any, State> {
|
||||||
trendingCommunities() {
|
trendingCommunities() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4>Trending forums</h4>
|
<h4>Trending <Link class="text-white" to="/communities">forums</Link></h4>
|
||||||
<ul class="list-inline">
|
<ul class="list-inline">
|
||||||
{this.state.trendingCommunities.map(community =>
|
{this.state.trendingCommunities.map(community =>
|
||||||
<li class="list-inline-item"><Link to={`/community/${community.id}`}>{community.name}</Link></li>
|
<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() {
|
landing() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<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
|
<h4>Welcome to
|
||||||
<svg class="icon mx-2"><use xlinkHref="#icon-mouse"></use></svg>
|
<svg class="icon mx-2"><use xlinkHref="#icon-mouse"></use></svg>
|
||||||
<a href={repoUrl}>Lemmy<sup>Beta</sup></a>
|
<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.trendingCommunities = res.communities;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
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';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
interface ModlogState {
|
interface ModlogState {
|
||||||
removed_posts: Array<ModRemovePost>,
|
combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}>,
|
||||||
locked_posts: Array<ModLockPost>,
|
communityId?: number,
|
||||||
removed_comments: Array<ModRemoveComment>,
|
communityName?: string,
|
||||||
removed_communities: Array<ModRemoveCommunity>,
|
|
||||||
banned_from_community: Array<ModBanFromCommunity>,
|
|
||||||
banned: Array<ModBan>,
|
|
||||||
added_to_community: Array<ModAddCommunity>,
|
|
||||||
added: Array<ModAdd>,
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Modlog extends Component<any, ModlogState> {
|
export class Modlog extends Component<any, ModlogState> {
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: ModlogState = {
|
private emptyState: ModlogState = {
|
||||||
removed_posts: [],
|
combined: [],
|
||||||
locked_posts: [],
|
loading: true,
|
||||||
removed_comments: [],
|
|
||||||
removed_communities: [],
|
|
||||||
banned_from_community: [],
|
|
||||||
banned: [],
|
|
||||||
added_to_community: [],
|
|
||||||
added: [],
|
|
||||||
loading: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = this.emptyState;
|
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
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
@ -46,7 +36,7 @@ export class Modlog extends Component<any, ModlogState> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let modlogForm: GetModlogForm = {
|
let modlogForm: GetModlogForm = {
|
||||||
|
community_id: this.state.communityId
|
||||||
};
|
};
|
||||||
WebSocketService.Instance.getModlog(modlogForm);
|
WebSocketService.Instance.getModlog(modlogForm);
|
||||||
}
|
}
|
||||||
|
@ -55,30 +45,35 @@ export class Modlog extends Component<any, ModlogState> {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
combined() {
|
setCombined(res: GetModlogResponse) {
|
||||||
let combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}> = [];
|
let removed_posts = addTypeInfo(res.removed_posts, "removed_posts");
|
||||||
let removed_posts = addTypeInfo(this.state.removed_posts, "removed_posts");
|
let locked_posts = addTypeInfo(res.locked_posts, "locked_posts");
|
||||||
let locked_posts = addTypeInfo(this.state.locked_posts, "locked_posts");
|
let removed_comments = addTypeInfo(res.removed_comments, "removed_comments");
|
||||||
let removed_comments = addTypeInfo(this.state.removed_comments, "removed_comments");
|
let removed_communities = addTypeInfo(res.removed_communities, "removed_communities");
|
||||||
let removed_communities = addTypeInfo(this.state.removed_communities, "removed_communities");
|
let banned_from_community = addTypeInfo(res.banned_from_community, "banned_from_community");
|
||||||
let banned_from_community = addTypeInfo(this.state.banned_from_community, "banned_from_community");
|
let added_to_community = addTypeInfo(res.added_to_community, "added_to_community");
|
||||||
let added_to_community = addTypeInfo(this.state.added_to_community, "added_to_community");
|
|
||||||
|
|
||||||
combined.push(...removed_posts);
|
this.state.combined.push(...removed_posts);
|
||||||
combined.push(...locked_posts);
|
this.state.combined.push(...locked_posts);
|
||||||
combined.push(...removed_comments);
|
this.state.combined.push(...removed_comments);
|
||||||
combined.push(...removed_communities);
|
this.state.combined.push(...removed_communities);
|
||||||
combined.push(...banned_from_community);
|
this.state.combined.push(...banned_from_community);
|
||||||
combined.push(...added_to_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
|
// 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 (
|
return (
|
||||||
<tbody>
|
<tbody>
|
||||||
{combined.map(i =>
|
{this.state.combined.map(i =>
|
||||||
<tr>
|
<tr>
|
||||||
<td><MomentTime data={i.data} /></td>
|
<td><MomentTime data={i.data} /></td>
|
||||||
<td><Link to={`/user/${i.data.mod_user_id}`}>{i.data.mod_user_name}</Link></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 ?
|
{this.state.loading ?
|
||||||
<h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
<h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
<div>
|
<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">
|
<div class="table-responsive">
|
||||||
<table id="modlog_table" class="table table-sm table-hover">
|
<table id="modlog_table" class="table table-sm table-hover">
|
||||||
<thead class="pointer">
|
<thead class="pointer">
|
||||||
|
@ -171,14 +169,7 @@ export class Modlog extends Component<any, ModlogState> {
|
||||||
} else if (op == UserOperation.GetModlog) {
|
} else if (op == UserOperation.GetModlog) {
|
||||||
let res: GetModlogResponse = msg;
|
let res: GetModlogResponse = msg;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.state.removed_posts = res.removed_posts;
|
this.setCombined(res);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { repoUrl } from '../utils';
|
|
||||||
import { UserService } from '../services';
|
import { UserService } from '../services';
|
||||||
import { version } from '../version';
|
import { version } from '../version';
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ interface NavbarState {
|
||||||
export class Navbar extends Component<any, NavbarState> {
|
export class Navbar extends Component<any, NavbarState> {
|
||||||
|
|
||||||
emptyState: NavbarState = {
|
emptyState: NavbarState = {
|
||||||
isLoggedIn: UserService.Instance.loggedIn,
|
isLoggedIn: UserService.Instance.user !== undefined,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
expandUserDropdown: false
|
expandUserDropdown: false
|
||||||
}
|
}
|
||||||
|
@ -50,9 +49,6 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
</button>
|
</button>
|
||||||
<div className={`${!this.state.expanded && 'collapse'} navbar-collapse`}>
|
<div className={`${!this.state.expanded && 'collapse'} navbar-collapse`}>
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav mr-auto">
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href={repoUrl}>About</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<Link class="nav-link" to="/communities">Forums</Link>
|
<Link class="nav-link" to="/communities">Forums</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
name: null,
|
name: null,
|
||||||
auth: null,
|
auth: null,
|
||||||
community_id: 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: [],
|
communities: [],
|
||||||
loading: false
|
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} />
|
<textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={4} />
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<label class="col-sm-2 col-form-label">Forum</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="form-control" value={this.state.postForm.community_id} onInput={linkEvent(this, this.handlePostCommunityChange)}>
|
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<button type="submit" class="btn btn-secondary mr-2">
|
<button type="submit" class="btn btn-secondary mr-2">
|
||||||
|
|
|
@ -160,7 +160,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get myPost(): boolean {
|
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) {
|
handlePostLike(i: PostListing) {
|
||||||
|
@ -220,6 +220,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModRemoveSubmit(i: PostListing) {
|
handleModRemoveSubmit(i: PostListing) {
|
||||||
|
event.preventDefault();
|
||||||
let form: PostFormI = {
|
let form: PostFormI = {
|
||||||
name: i.props.post.name,
|
name: i.props.post.name,
|
||||||
community_id: i.props.post.community_id,
|
community_id: i.props.post.community_id,
|
||||||
|
|
|
@ -43,7 +43,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
|
||||||
sortType: SortType.Hot,
|
sortType: SortType.Hot,
|
||||||
type_: this.props.communityId
|
type_: this.props.communityId
|
||||||
? ListingType.Community
|
? ListingType.Community
|
||||||
: UserService.Instance.loggedIn
|
: UserService.Instance.user
|
||||||
? ListingType.Subscribed
|
? ListingType.Subscribed
|
||||||
: ListingType.All,
|
: ListingType.All,
|
||||||
loading: true
|
loading: true
|
||||||
|
@ -86,7 +86,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
|
||||||
{this.state.posts.length > 0
|
{this.state.posts.length > 0
|
||||||
? this.state.posts.map(post =>
|
? this.state.posts.map(post =>
|
||||||
<PostListing post={post} showCommunity={!this.props.communityId}/>)
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
|
||||||
<option value={SortType.TopAll}>All</option>
|
<option value={SortType.TopAll}>All</option>
|
||||||
</select>
|
</select>
|
||||||
{!this.props.communityId &&
|
{!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">
|
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
|
||||||
<option disabled>Type</option>
|
<option disabled>Type</option>
|
||||||
<option value={ListingType.All}>All</option>
|
<option value={ListingType.All}>All</option>
|
||||||
|
|
|
@ -79,14 +79,14 @@ export class Post extends Component<any, PostState> {
|
||||||
{this.state.loading ?
|
{this.state.loading ?
|
||||||
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-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 />
|
<PostListing post={this.state.post} showBody showCommunity editable />
|
||||||
<div className="mb-2" />
|
<div className="mb-2" />
|
||||||
<CommentForm postId={this.state.post.id} disabled={this.state.post.locked} />
|
<CommentForm postId={this.state.post.id} disabled={this.state.post.locked} />
|
||||||
{this.sortRadios()}
|
{this.sortRadios()}
|
||||||
{this.commentsTree()}
|
{this.commentsTree()}
|
||||||
</div>
|
</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()}
|
{this.state.comments.length > 0 && this.newComments()}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-12 col-lg-2">
|
<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>
|
</div>
|
||||||
</form>
|
</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"><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_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_posts} Posts</li>
|
||||||
<li className="list-inline-item badge badge-light">{community.number_of_comments} Comments</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>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
{community.subscribed
|
{community.subscribed
|
||||||
|
@ -103,15 +110,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
<div>
|
<div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(community.description)} />
|
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(community.description)} />
|
||||||
|
<hr />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -152,7 +153,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get amCreator(): boolean {
|
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 {
|
// private get amMod(): boolean {
|
||||||
|
@ -180,7 +181,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModRemoveSubmit(i: Sidebar) {
|
handleModRemoveSubmit(i: Sidebar) {
|
||||||
|
event.preventDefault();
|
||||||
let deleteForm: CommunityFormI = {
|
let deleteForm: CommunityFormI = {
|
||||||
name: i.props.community.name,
|
name: i.props.community.name,
|
||||||
title: i.props.community.title,
|
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 { HashRouter, Route, Switch } from 'inferno-router';
|
||||||
|
|
||||||
import { Navbar } from './components/navbar';
|
import { Navbar } from './components/navbar';
|
||||||
|
import { Footer } from './components/footer';
|
||||||
import { Home } from './components/home';
|
import { Home } from './components/home';
|
||||||
import { Login } from './components/login';
|
import { Login } from './components/login';
|
||||||
import { CreatePost } from './components/create-post';
|
import { CreatePost } from './components/create-post';
|
||||||
|
@ -11,6 +12,7 @@ import { Community } from './components/community';
|
||||||
import { Communities } from './components/communities';
|
import { Communities } from './components/communities';
|
||||||
import { User } from './components/user';
|
import { User } from './components/user';
|
||||||
import { Modlog } from './components/modlog';
|
import { Modlog } from './components/modlog';
|
||||||
|
import { Setup } from './components/setup';
|
||||||
import { Symbols } from './components/symbols';
|
import { Symbols } from './components/symbols';
|
||||||
|
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
@ -43,10 +45,13 @@ class Index extends Component<any, any> {
|
||||||
<Route path={`/community/:id`} component={Community} />
|
<Route path={`/community/:id`} component={Community} />
|
||||||
<Route path={`/user/:id/:heading`} component={User} />
|
<Route path={`/user/:id/:heading`} component={User} />
|
||||||
<Route path={`/user/:id`} component={User} />
|
<Route path={`/user/:id`} component={User} />
|
||||||
|
<Route path={`/modlog/community/:community_id`} component={Modlog} />
|
||||||
<Route path={`/modlog`} component={Modlog} />
|
<Route path={`/modlog`} component={Modlog} />
|
||||||
|
<Route path={`/setup`} component={Setup} />
|
||||||
</Switch>
|
</Switch>
|
||||||
<Symbols />
|
<Symbols />
|
||||||
</div>
|
</div>
|
||||||
|
<Footer />
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export enum UserOperation {
|
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 {
|
export enum CommentSortType {
|
||||||
|
@ -107,6 +107,19 @@ export interface Category {
|
||||||
name: string;
|
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 {
|
export interface FollowCommunityForm {
|
||||||
community_id: number;
|
community_id: number;
|
||||||
follow: boolean;
|
follow: boolean;
|
||||||
|
@ -294,6 +307,7 @@ export interface RegisterForm {
|
||||||
email?: string;
|
email?: string;
|
||||||
password: string;
|
password: string;
|
||||||
password_verify: string;
|
password_verify: string;
|
||||||
|
admin: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
|
@ -421,4 +435,49 @@ export interface CreatePostLikeResponse {
|
||||||
post: Post;
|
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);
|
this.sub.next(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get loggedIn(): boolean {
|
|
||||||
return this.user !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get auth(): string {
|
public get auth(): string {
|
||||||
return Cookies.get("jwt");
|
return Cookies.get("jwt");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { wsUri } from '../env';
|
import { wsUri } from '../env';
|
||||||
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, 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 { webSocket } from 'rxjs/webSocket';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
|
@ -8,16 +8,21 @@ import { UserService } from './';
|
||||||
export class WebSocketService {
|
export class WebSocketService {
|
||||||
private static _instance: WebSocketService;
|
private static _instance: WebSocketService;
|
||||||
public subject: Subject<any>;
|
public subject: Subject<any>;
|
||||||
|
public instanceName: string;
|
||||||
|
|
||||||
|
public site: Site;
|
||||||
|
public admins: Array<UserView>;
|
||||||
|
public banned: Array<UserView>;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.subject = webSocket(wsUri);
|
this.subject = webSocket(wsUri);
|
||||||
|
|
||||||
// Even tho this isn't used, its necessary to not keep reconnecting
|
// Necessary to not keep reconnecting
|
||||||
this.subject
|
this.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(60000), take(999))))
|
.pipe(retryWhen(errors => errors.pipe(delay(60000), take(999))))
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
console.log(`Connected to ${wsUri}`);
|
console.log(`Connected to ${wsUri}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get Instance(){
|
public static get Instance(){
|
||||||
|
@ -125,6 +130,19 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.GetModlog, form));
|
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) {
|
private wsSendWrapper(op: UserOperation, data: any) {
|
||||||
let send = { op: UserOperation[op], data: data };
|
let send = { op: UserOperation[op], data: data };
|
||||||
console.log(send);
|
console.log(send);
|
||||||
|
@ -138,7 +156,6 @@ export class WebSocketService {
|
||||||
throw "Not logged in";
|
throw "Not logged in";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onbeforeunload = (() => {
|
window.onbeforeunload = (() => {
|
||||||
|
|
Loading…
Reference in a new issue