Merge branch 'moderation'
- Fixes #63 - Fixes #62 - Fixes #61 - Fixes #60 - Fixes #59 - Fixes #57 - Fixes #56 - Fixes #50 - Fixes #49 - Some of #48 - Fixes #45 - Some of #35 - Fixes #29 - Fixes #14
This commit is contained in:
commit
6839cc9d6d
66 changed files with 4189 additions and 465 deletions
37
Dockerfile
37
Dockerfile
|
@ -1,12 +1,39 @@
|
||||||
FROM node:10-jessie as node
|
FROM node:10-jessie as node
|
||||||
#If encounter Invalid cross-device error -run on host 'echo N | sudo tee /sys/module/overlay/parameters/metacopy'
|
#If encounter Invalid cross-device error -run on host 'echo N | sudo tee /sys/module/overlay/parameters/metacopy'
|
||||||
COPY ui /app/ui
|
COPY ui /app/ui
|
||||||
RUN cd /app/ui && yarn && yarn build
|
WORKDIR /app/ui
|
||||||
|
RUN yarn
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
FROM rust:1.33 as rust
|
FROM rust:1.33 as rust
|
||||||
COPY server /app/server
|
|
||||||
|
# create a new empty shell project
|
||||||
|
WORKDIR /app
|
||||||
|
RUN USER=root cargo new server
|
||||||
|
WORKDIR /app/server
|
||||||
|
|
||||||
|
# copy over your manifests
|
||||||
|
COPY server/Cargo.toml server/Cargo.lock ./
|
||||||
|
|
||||||
|
# this build step will cache your dependencies
|
||||||
|
RUN mkdir -p ./src/bin \
|
||||||
|
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
||||||
|
RUN cargo build --release --bin lemmy
|
||||||
|
RUN ls ./target/release/.fingerprint/
|
||||||
|
RUN rm -r ./target/release/.fingerprint/server-*
|
||||||
|
|
||||||
|
# copy your source tree
|
||||||
|
# RUN rm -rf ./src/
|
||||||
|
COPY server/src ./src/
|
||||||
|
COPY server/migrations ./migrations/
|
||||||
|
|
||||||
|
# build for release
|
||||||
|
RUN cargo build --frozen --release --bin lemmy
|
||||||
|
RUN mv /app/server/target/release/lemmy /app/lemmy
|
||||||
|
|
||||||
|
# The output image
|
||||||
|
# FROM debian:stable-slim
|
||||||
|
# RUN apt-get -y update && apt-get install -y postgresql-client
|
||||||
|
# COPY --from=rust /app/server/target/release/lemmy /app/lemmy
|
||||||
COPY --from=node /app/ui/dist /app/dist
|
COPY --from=node /app/ui/dist /app/dist
|
||||||
RUN cd /app/server && cargo build --release
|
|
||||||
RUN mv /app/server/target/release/lemmy /app/
|
|
||||||
WORKDIR /app/
|
|
||||||
EXPOSE 8536
|
EXPOSE 8536
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ services:
|
||||||
POSTGRES_DB: rrr
|
POSTGRES_DB: rrr
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U rrr"]
|
test: ["CMD-SHELL", "pg_isready -U rrr"]
|
||||||
interval: 30s
|
interval: 5s
|
||||||
timeout: 30s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 20
|
||||||
lemmy:
|
lemmy:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
|
@ -22,6 +22,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
LEMMY_FRONT_END_DIR: /app/dist
|
LEMMY_FRONT_END_DIR: /app/dist
|
||||||
DATABASE_URL: postgres://rrr:rrr@db:5432/rrr
|
DATABASE_URL: postgres://rrr:rrr@db:5432/rrr
|
||||||
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
1
server/Cargo.lock
generated
1
server/Cargo.lock
generated
|
@ -1359,6 +1359,7 @@ dependencies = [
|
||||||
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonwebtoken 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"jsonwebtoken 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -24,4 +24,5 @@ rand = "0.6.5"
|
||||||
strum = "0.14.0"
|
strum = "0.14.0"
|
||||||
strum_macros = "0.14.0"
|
strum_macros = "0.14.0"
|
||||||
jsonwebtoken = "*"
|
jsonwebtoken = "*"
|
||||||
regex = "1"
|
regex = "*"
|
||||||
|
lazy_static = "*"
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
drop table user_
|
drop table user_ban;
|
||||||
|
drop table user_;
|
||||||
|
|
|
@ -6,9 +6,18 @@ 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 not null,
|
||||||
|
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)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table user_ban (
|
||||||
|
id serial primary key,
|
||||||
|
user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
published timestamp not null default now(),
|
||||||
|
unique (user_id)
|
||||||
|
);
|
||||||
|
|
||||||
insert into user_ (name, fedi_name, password_encrypted) values ('admin', 'TBD', 'TBD');
|
insert into user_ (name, fedi_name, password_encrypted) values ('admin', 'TBD', 'TBD');
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
drop table site;
|
||||||
|
drop table community_user_ban;;
|
||||||
drop table community_moderator;
|
drop table community_moderator;
|
||||||
drop table community_follower;
|
drop table community_follower;
|
||||||
drop table community;
|
drop table community;
|
||||||
|
|
|
@ -38,6 +38,7 @@ create table community (
|
||||||
description text,
|
description text,
|
||||||
category_id int references category on update cascade on delete cascade not null,
|
category_id int references category on update cascade on delete cascade not null,
|
||||||
creator_id int references user_ on update cascade on delete cascade not null,
|
creator_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
removed boolean default false,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp
|
updated timestamp
|
||||||
);
|
);
|
||||||
|
@ -46,14 +47,33 @@ create table community_moderator (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
community_id int references community on update cascade on delete cascade not null,
|
community_id int references community on update cascade on delete cascade not null,
|
||||||
user_id int references user_ on update cascade on delete cascade not null,
|
user_id int references user_ on update cascade on delete cascade not null,
|
||||||
published timestamp not null default now()
|
published timestamp not null default now(),
|
||||||
|
unique (community_id, user_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
create table community_follower (
|
create table community_follower (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
community_id int references community on update cascade on delete cascade not null,
|
community_id int references community on update cascade on delete cascade not null,
|
||||||
user_id int references user_ on update cascade on delete cascade not null,
|
user_id int references user_ on update cascade on delete cascade not null,
|
||||||
published timestamp not null default now()
|
published timestamp not null default now(),
|
||||||
|
unique (community_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table community_user_ban (
|
||||||
|
id serial primary key,
|
||||||
|
community_id int references community on update cascade on delete cascade not null,
|
||||||
|
user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
published timestamp not null default now(),
|
||||||
|
unique (community_id, user_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
insert into community (name, title, category_id, creator_id) values ('main', 'The Default Community', 1, 1);
|
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
|
||||||
|
);
|
||||||
|
|
|
@ -5,6 +5,8 @@ create table post (
|
||||||
body text,
|
body text,
|
||||||
creator_id int references user_ on update cascade on delete cascade not null,
|
creator_id int references user_ on update cascade on delete cascade not null,
|
||||||
community_id int references community on update cascade on delete cascade not null,
|
community_id int references community on update cascade on delete cascade not null,
|
||||||
|
removed boolean default false,
|
||||||
|
locked boolean default false,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp
|
updated timestamp
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@ create table comment (
|
||||||
post_id int references post on update cascade on delete cascade not null,
|
post_id int references post on update cascade on delete cascade not null,
|
||||||
parent_id int references comment on update cascade on delete cascade,
|
parent_id int references comment on update cascade on delete cascade,
|
||||||
content text not null,
|
content text not null,
|
||||||
|
removed boolean default false,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp
|
updated timestamp
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,7 +30,8 @@ select
|
||||||
ap.*,
|
ap.*,
|
||||||
u.id as user_id,
|
u.id as user_id,
|
||||||
coalesce(pl.score, 0) as my_vote,
|
coalesce(pl.score, 0) as my_vote,
|
||||||
(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed
|
(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
|
||||||
|
u.admin or (select cm.id::bool from community_moderator cm where u.id = cm.user_id and cm.community_id = ap.community_id) as am_mod
|
||||||
from user_ u
|
from user_ u
|
||||||
cross join all_post ap
|
cross join all_post ap
|
||||||
left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
|
left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
|
||||||
|
@ -41,33 +42,7 @@ select
|
||||||
ap.*,
|
ap.*,
|
||||||
null as user_id,
|
null as user_id,
|
||||||
null as my_vote,
|
null as my_vote,
|
||||||
null as subscribed
|
null as subscribed,
|
||||||
|
null as am_mod
|
||||||
from all_post ap
|
from all_post ap
|
||||||
;
|
;
|
||||||
|
|
||||||
/* The old post view */
|
|
||||||
/* create view post_view as */
|
|
||||||
/* select */
|
|
||||||
/* u.id as user_id, */
|
|
||||||
/* pl.score as my_vote, */
|
|
||||||
/* p.id as id, */
|
|
||||||
/* p.name as name, */
|
|
||||||
/* p.url, */
|
|
||||||
/* p.body, */
|
|
||||||
/* p.creator_id, */
|
|
||||||
/* (select name from user_ where p.creator_id = user_.id) creator_name, */
|
|
||||||
/* p.community_id, */
|
|
||||||
/* (select name from community where p.community_id = community.id) as community_name, */
|
|
||||||
/* (select count(*) from comment where comment.post_id = p.id) as number_of_comments, */
|
|
||||||
/* coalesce(sum(pl.score) over (partition by p.id), 0) as score, */
|
|
||||||
/* count (case when pl.score = 1 then 1 else null end) over (partition by p.id) as upvotes, */
|
|
||||||
/* count (case when pl.score = -1 then 1 else null end) over (partition by p.id) as downvotes, */
|
|
||||||
/* hot_rank(coalesce(sum(pl.score) over (partition by p.id) , 0), p.published) as hot_rank, */
|
|
||||||
/* p.published, */
|
|
||||||
/* p.updated */
|
|
||||||
/* from user_ u */
|
|
||||||
/* cross join post p */
|
|
||||||
/* left join post_like pl on u.id = pl.user_id and p.id = pl.post_id; */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
drop view community_view;
|
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 site_view;
|
||||||
|
|
|
@ -13,7 +13,8 @@ with all_community as
|
||||||
select
|
select
|
||||||
ac.*,
|
ac.*,
|
||||||
u.id as user_id,
|
u.id as user_id,
|
||||||
cf.id::boolean as subscribed
|
cf.id::boolean as subscribed,
|
||||||
|
u.admin or (select cm.id::bool from community_moderator cm where u.id = cm.user_id and cm.community_id = ac.id) as am_mod
|
||||||
from user_ u
|
from user_ u
|
||||||
cross join all_community ac
|
cross join all_community ac
|
||||||
left join community_follower cf on u.id = cf.user_id and ac.id = cf.community_id
|
left join community_follower cf on u.id = cf.user_id and ac.id = cf.community_id
|
||||||
|
@ -23,7 +24,8 @@ union all
|
||||||
select
|
select
|
||||||
ac.*,
|
ac.*,
|
||||||
null as user_id,
|
null as user_id,
|
||||||
null as subscribed
|
null as subscribed,
|
||||||
|
null as am_mod
|
||||||
from all_community ac
|
from all_community ac
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -38,3 +40,17 @@ select *,
|
||||||
(select name from user_ u where cf.user_id = u.id) as user_name,
|
(select name from user_ u where cf.user_id = u.id) as user_name,
|
||||||
(select name from community c where cf.community_id = c.id) as community_name
|
(select name from community c where cf.community_id = c.id) as community_name
|
||||||
from community_follower cf;
|
from community_follower cf;
|
||||||
|
|
||||||
|
create view community_user_ban_view as
|
||||||
|
select *,
|
||||||
|
(select name from user_ u where cm.user_id = u.id) as user_name,
|
||||||
|
(select name from community c where cm.community_id = c.id) as community_name
|
||||||
|
from community_user_ban cm;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
|
@ -3,7 +3,9 @@ with all_comment as
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
c.*,
|
c.*,
|
||||||
(select name from user_ where c.creator_id = user_.id) creator_name,
|
(select community_id from post p where p.id = c.post_id),
|
||||||
|
(select cb.id::bool from community_user_ban cb where c.creator_id = cb.user_id) as banned,
|
||||||
|
(select name from user_ where c.creator_id = user_.id) as creator_name,
|
||||||
coalesce(sum(cl.score), 0) as score,
|
coalesce(sum(cl.score), 0) as score,
|
||||||
count (case when cl.score = 1 then 1 else null end) as upvotes,
|
count (case when cl.score = 1 then 1 else null end) as upvotes,
|
||||||
count (case when cl.score = -1 then 1 else null end) as downvotes
|
count (case when cl.score = -1 then 1 else null end) as downvotes
|
||||||
|
@ -15,7 +17,8 @@ with all_comment as
|
||||||
select
|
select
|
||||||
ac.*,
|
ac.*,
|
||||||
u.id as user_id,
|
u.id as user_id,
|
||||||
coalesce(cl.score, 0) as my_vote
|
coalesce(cl.score, 0) as my_vote,
|
||||||
|
u.admin or (select cm.id::bool from community_moderator cm, post p where u.id = cm.user_id and ac.post_id = p.id and p.community_id = cm.community_id) as am_mod
|
||||||
from user_ u
|
from user_ u
|
||||||
cross join all_comment ac
|
cross join all_comment ac
|
||||||
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
|
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
|
||||||
|
@ -25,6 +28,7 @@ union all
|
||||||
select
|
select
|
||||||
ac.*,
|
ac.*,
|
||||||
null as user_id,
|
null as user_id,
|
||||||
null as my_vote
|
null as my_vote,
|
||||||
|
null as am_mod
|
||||||
from all_comment ac
|
from all_comment ac
|
||||||
;
|
;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
drop table mod_remove_post;
|
||||||
|
drop table mod_lock_post;
|
||||||
|
drop table mod_remove_comment;
|
||||||
|
drop table mod_remove_community;
|
||||||
|
drop table mod_ban;
|
||||||
|
drop table mod_ban_from_community;
|
||||||
|
drop table mod_add;
|
||||||
|
drop table mod_add_community;
|
|
@ -0,0 +1,76 @@
|
||||||
|
create table mod_remove_post (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
post_id int references post on update cascade on delete cascade not null,
|
||||||
|
reason text,
|
||||||
|
removed boolean default true,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table mod_lock_post (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
post_id int references post on update cascade on delete cascade not null,
|
||||||
|
locked boolean default true,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table mod_remove_comment (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
comment_id int references comment on update cascade on delete cascade not null,
|
||||||
|
reason text,
|
||||||
|
removed boolean default true,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table mod_remove_community (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
community_id int references community on update cascade on delete cascade not null,
|
||||||
|
reason text,
|
||||||
|
removed boolean default true,
|
||||||
|
expires timestamp,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- TODO make sure you can't ban other mods
|
||||||
|
create table mod_ban_from_community (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
other_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
community_id int references community on update cascade on delete cascade not null,
|
||||||
|
reason text,
|
||||||
|
banned boolean default true,
|
||||||
|
expires timestamp,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table mod_ban (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
other_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
reason text,
|
||||||
|
banned boolean default true,
|
||||||
|
expires timestamp,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table mod_add_community (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
other_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
community_id int references community on update cascade on delete cascade not null,
|
||||||
|
removed boolean default false,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- When removed is false that means kicked
|
||||||
|
create table mod_add (
|
||||||
|
id serial primary key,
|
||||||
|
mod_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
other_user_id int references user_ on update cascade on delete cascade not null,
|
||||||
|
removed boolean default false,
|
||||||
|
when_ timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
|
@ -2,10 +2,11 @@ create view user_view as
|
||||||
select id,
|
select id,
|
||||||
name,
|
name,
|
||||||
fedi_name,
|
fedi_name,
|
||||||
|
admin,
|
||||||
|
banned,
|
||||||
published,
|
published,
|
||||||
(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
|
(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
|
||||||
(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
|
(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
|
||||||
(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
|
(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
|
||||||
(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
|
(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
|
||||||
from user_ u;
|
from user_ u;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
drop view mod_remove_post_view;
|
||||||
|
drop view mod_lock_post_view;
|
||||||
|
drop view mod_remove_comment_view;
|
||||||
|
drop view mod_remove_community_view;
|
||||||
|
drop view mod_ban_from_community_view;
|
||||||
|
drop view mod_ban_view;
|
||||||
|
drop view mod_add_community_view;
|
||||||
|
drop view mod_add_view;
|
61
server/migrations/2019-04-11-144915_create_mod_views/up.sql
Normal file
61
server/migrations/2019-04-11-144915_create_mod_views/up.sql
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
create view mod_remove_post_view as
|
||||||
|
select mrp.*,
|
||||||
|
(select name from user_ u where mrp.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select name from post p where mrp.post_id = p.id) as post_name,
|
||||||
|
(select c.id from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_id,
|
||||||
|
(select c.name from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_name
|
||||||
|
from mod_remove_post mrp;
|
||||||
|
|
||||||
|
create view mod_lock_post_view as
|
||||||
|
select mlp.*,
|
||||||
|
(select name from user_ u where mlp.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select name from post p where mlp.post_id = p.id) as post_name,
|
||||||
|
(select c.id from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_id,
|
||||||
|
(select c.name from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_name
|
||||||
|
from mod_lock_post mlp;
|
||||||
|
|
||||||
|
create view mod_remove_comment_view as
|
||||||
|
select mrc.*,
|
||||||
|
(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select c.id from comment c where mrc.comment_id = c.id) as comment_user_id,
|
||||||
|
(select name from user_ u, comment c where mrc.comment_id = c.id and u.id = c.creator_id) as comment_user_name,
|
||||||
|
(select content from comment c where mrc.comment_id = c.id) as comment_content,
|
||||||
|
(select p.id from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_id,
|
||||||
|
(select p.name from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_name,
|
||||||
|
(select co.id from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_id,
|
||||||
|
(select co.name from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_name
|
||||||
|
from mod_remove_comment mrc;
|
||||||
|
|
||||||
|
create view mod_remove_community_view as
|
||||||
|
select mrc.*,
|
||||||
|
(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select c.name from community c where mrc.community_id = c.id) as community_name
|
||||||
|
from mod_remove_community mrc;
|
||||||
|
|
||||||
|
create view mod_ban_from_community_view as
|
||||||
|
select mb.*,
|
||||||
|
(select name from user_ u where mb.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select name from user_ u where mb.other_user_id = u.id) as other_user_name,
|
||||||
|
(select name from community c where mb.community_id = c.id) as community_name
|
||||||
|
from mod_ban_from_community mb;
|
||||||
|
|
||||||
|
create view mod_ban_view as
|
||||||
|
select mb.*,
|
||||||
|
(select name from user_ u where mb.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select name from user_ u where mb.other_user_id = u.id) as other_user_name
|
||||||
|
from mod_ban_from_community mb;
|
||||||
|
|
||||||
|
|
||||||
|
create view mod_add_community_view as
|
||||||
|
select ma.*,
|
||||||
|
(select name from user_ u where ma.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select name from user_ u where ma.other_user_id = u.id) as other_user_name,
|
||||||
|
(select name from community c where ma.community_id = c.id) as community_name
|
||||||
|
from mod_add_community ma;
|
||||||
|
|
||||||
|
|
||||||
|
create view mod_add_view as
|
||||||
|
select ma.*,
|
||||||
|
(select name from user_ u where ma.mod_user_id = u.id) as mod_user_name,
|
||||||
|
(select name from user_ u where ma.other_user_id = u.id) as other_user_name
|
||||||
|
from mod_add ma;
|
|
@ -22,6 +22,7 @@ pub struct Comment {
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
pub removed: Option<bool>,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -33,6 +34,7 @@ pub struct CommentForm {
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
pub removed: Option<bool>,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +137,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -146,6 +150,7 @@ mod tests {
|
||||||
description: None,
|
description: None,
|
||||||
category_id: 1,
|
category_id: 1,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
|
removed: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -157,6 +162,8 @@ mod tests {
|
||||||
url: None,
|
url: None,
|
||||||
body: None,
|
body: None,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
|
removed: None,
|
||||||
|
locked: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -166,6 +173,7 @@ mod tests {
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
|
removed: None,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
@ -177,6 +185,7 @@ mod tests {
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
|
removed: Some(false),
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
published: inserted_comment.published,
|
published: inserted_comment.published,
|
||||||
updated: None
|
updated: None
|
||||||
|
@ -187,6 +196,7 @@ mod tests {
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
parent_id: Some(inserted_comment.id),
|
||||||
|
removed: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,18 @@ table! {
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
|
community_id -> Int4,
|
||||||
|
banned -> Nullable<Bool>,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
score -> BigInt,
|
score -> BigInt,
|
||||||
upvotes -> BigInt,
|
upvotes -> BigInt,
|
||||||
downvotes -> BigInt,
|
downvotes -> BigInt,
|
||||||
user_id -> Nullable<Int4>,
|
user_id -> Nullable<Int4>,
|
||||||
my_vote -> Nullable<Int4>,
|
my_vote -> Nullable<Int4>,
|
||||||
|
am_mod -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,14 +36,18 @@ pub struct CommentView {
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
pub removed: Option<bool>,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub banned: Option<bool>,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
pub score: i64,
|
pub score: i64,
|
||||||
pub upvotes: i64,
|
pub upvotes: i64,
|
||||||
pub downvotes: i64,
|
pub downvotes: i64,
|
||||||
pub user_id: Option<i32>,
|
pub user_id: Option<i32>,
|
||||||
pub my_vote: Option<i32>,
|
pub my_vote: Option<i32>,
|
||||||
|
pub am_mod: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommentView {
|
impl CommentView {
|
||||||
|
@ -130,6 +138,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,6 +151,7 @@ mod tests {
|
||||||
description: None,
|
description: None,
|
||||||
category_id: 1,
|
category_id: 1,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
|
removed: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,6 +163,8 @@ mod tests {
|
||||||
url: None,
|
url: None,
|
||||||
body: None,
|
body: None,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
|
removed: None,
|
||||||
|
locked: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,6 +175,7 @@ mod tests {
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
|
removed: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -181,7 +195,10 @@ mod tests {
|
||||||
content: "A test comment 32".into(),
|
content: "A test comment 32".into(),
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
|
removed: Some(false),
|
||||||
|
banned: None,
|
||||||
published: inserted_comment.published,
|
published: inserted_comment.published,
|
||||||
updated: None,
|
updated: None,
|
||||||
creator_name: inserted_user.name.to_owned(),
|
creator_name: inserted_user.name.to_owned(),
|
||||||
|
@ -189,7 +206,8 @@ mod tests {
|
||||||
downvotes: 0,
|
downvotes: 0,
|
||||||
upvotes: 1,
|
upvotes: 1,
|
||||||
user_id: None,
|
user_id: None,
|
||||||
my_vote: None
|
my_vote: None,
|
||||||
|
am_mod: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_comment_view_with_user = CommentView {
|
let expected_comment_view_with_user = CommentView {
|
||||||
|
@ -197,7 +215,10 @@ mod tests {
|
||||||
content: "A test comment 32".into(),
|
content: "A test comment 32".into(),
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
|
removed: Some(false),
|
||||||
|
banned: None,
|
||||||
published: inserted_comment.published,
|
published: inserted_comment.published,
|
||||||
updated: None,
|
updated: None,
|
||||||
creator_name: inserted_user.name.to_owned(),
|
creator_name: inserted_user.name.to_owned(),
|
||||||
|
@ -206,6 +227,7 @@ mod tests {
|
||||||
upvotes: 1,
|
upvotes: 1,
|
||||||
user_id: Some(inserted_user.id),
|
user_id: Some(inserted_user.id),
|
||||||
my_vote: Some(1),
|
my_vote: Some(1),
|
||||||
|
am_mod: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, 999).unwrap();
|
let read_comment_views_no_user = CommentView::list(&conn, &SortType::New, Some(inserted_post.id), None, None, 999).unwrap();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
use schema::{community, community_moderator, community_follower};
|
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};
|
||||||
use {Crud, Followable, Joinable};
|
use {Crud, Followable, Joinable, Bannable};
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
#[table_name="community"]
|
#[table_name="community"]
|
||||||
|
@ -14,6 +14,7 @@ pub struct Community {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub category_id: i32,
|
pub category_id: i32,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -26,44 +27,10 @@ pub struct CommunityForm {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub category_id: i32,
|
pub category_id: i32,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
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_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::*;
|
||||||
|
@ -92,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))
|
|
||||||
.filter(user_id.eq(&community_follower_form.user_id)))
|
|
||||||
.execute(conn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
|
#[table_name="community_moderator"]
|
||||||
|
pub struct CommunityModeratorForm {
|
||||||
|
pub community_id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Joinable<CommunityModeratorForm> for CommunityModerator {
|
impl Joinable<CommunityModeratorForm> for CommunityModerator {
|
||||||
|
@ -125,6 +93,120 @@ impl Joinable<CommunityModeratorForm> for CommunityModerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
|
#[belongs_to(Community)]
|
||||||
|
#[table_name = "community_user_ban"]
|
||||||
|
pub struct CommunityUserBan {
|
||||||
|
pub id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
|
#[table_name="community_user_ban"]
|
||||||
|
pub struct CommunityUserBanForm {
|
||||||
|
pub community_id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bannable<CommunityUserBanForm> for CommunityUserBan {
|
||||||
|
fn ban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<Self, Error> {
|
||||||
|
use schema::community_user_ban::dsl::*;
|
||||||
|
insert_into(community_user_ban)
|
||||||
|
.values(community_user_ban_form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unban(conn: &PgConnection, community_user_ban_form: &CommunityUserBanForm) -> Result<usize, Error> {
|
||||||
|
use schema::community_user_ban::dsl::*;
|
||||||
|
diesel::delete(community_user_ban
|
||||||
|
.filter(community_id.eq(community_user_ban_form.community_id))
|
||||||
|
.filter(user_id.eq(community_user_ban_form.user_id)))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
|
#[belongs_to(Community)]
|
||||||
|
#[table_name = "community_follower"]
|
||||||
|
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;
|
||||||
|
@ -136,11 +218,13 @@ mod tests {
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
let new_user = UserForm {
|
let new_user = UserForm {
|
||||||
name: "bob".into(),
|
name: "bobbee".into(),
|
||||||
fedi_name: "rrf".into(),
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,6 +236,7 @@ mod tests {
|
||||||
title: "nada".to_owned(),
|
title: "nada".to_owned(),
|
||||||
description: None,
|
description: None,
|
||||||
category_id: 1,
|
category_id: 1,
|
||||||
|
removed: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -164,11 +249,11 @@ mod tests {
|
||||||
title: "nada".to_owned(),
|
title: "nada".to_owned(),
|
||||||
description: None,
|
description: None,
|
||||||
category_id: 1,
|
category_id: 1,
|
||||||
|
removed: Some(false),
|
||||||
published: inserted_community.published,
|
published: inserted_community.published,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let community_follower_form = CommunityFollowerForm {
|
let community_follower_form = CommunityFollowerForm {
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
user_id: inserted_user.id
|
user_id: inserted_user.id
|
||||||
|
@ -176,6 +261,7 @@ mod tests {
|
||||||
|
|
||||||
let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap();
|
let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap();
|
||||||
|
|
||||||
|
|
||||||
let expected_community_follower = CommunityFollower {
|
let expected_community_follower = CommunityFollower {
|
||||||
id: inserted_community_follower.id,
|
id: inserted_community_follower.id,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
|
@ -197,10 +283,25 @@ mod tests {
|
||||||
published: inserted_community_user.published
|
published: inserted_community_user.published
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let community_user_ban_form = CommunityUserBanForm {
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
user_id: inserted_user.id
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_community_user_ban = CommunityUserBan::ban(&conn, &community_user_ban_form).unwrap();
|
||||||
|
|
||||||
|
let expected_community_user_ban = CommunityUserBan {
|
||||||
|
id: inserted_community_user_ban.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
user_id: inserted_user.id,
|
||||||
|
published: inserted_community_user_ban.published
|
||||||
|
};
|
||||||
|
|
||||||
let read_community = Community::read(&conn, inserted_community.id).unwrap();
|
let read_community = Community::read(&conn, inserted_community.id).unwrap();
|
||||||
let updated_community = Community::update(&conn, inserted_community.id, &new_community).unwrap();
|
let updated_community = Community::update(&conn, inserted_community.id, &new_community).unwrap();
|
||||||
let ignored_community = CommunityFollower::ignore(&conn, &community_follower_form).unwrap();
|
let ignored_community = CommunityFollower::ignore(&conn, &community_follower_form).unwrap();
|
||||||
let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
|
let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
|
||||||
|
let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap();
|
||||||
let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
|
let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
|
||||||
User_::delete(&conn, inserted_user.id).unwrap();
|
User_::delete(&conn, inserted_user.id).unwrap();
|
||||||
|
|
||||||
|
@ -209,8 +310,10 @@ mod tests {
|
||||||
assert_eq!(expected_community, updated_community);
|
assert_eq!(expected_community, updated_community);
|
||||||
assert_eq!(expected_community_follower, inserted_community_follower);
|
assert_eq!(expected_community_follower, inserted_community_follower);
|
||||||
assert_eq!(expected_community_user, inserted_community_user);
|
assert_eq!(expected_community_user, inserted_community_user);
|
||||||
|
assert_eq!(expected_community_user_ban, inserted_community_user_ban);
|
||||||
assert_eq!(1, ignored_community);
|
assert_eq!(1, ignored_community);
|
||||||
assert_eq!(1, left_community);
|
assert_eq!(1, left_community);
|
||||||
|
assert_eq!(1, unban);
|
||||||
// assert_eq!(2, loaded_count);
|
// assert_eq!(2, loaded_count);
|
||||||
assert_eq!(1, num_deleted);
|
assert_eq!(1, num_deleted);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ extern crate diesel;
|
||||||
use diesel::*;
|
use diesel::*;
|
||||||
use diesel::result::Error;
|
use diesel::result::Error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use {SortType};
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
community_view (id) {
|
community_view (id) {
|
||||||
|
@ -11,6 +12,7 @@ table! {
|
||||||
description -> Nullable<Text>,
|
description -> Nullable<Text>,
|
||||||
category_id -> Int4,
|
category_id -> Int4,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
|
@ -20,6 +22,7 @@ table! {
|
||||||
number_of_comments -> BigInt,
|
number_of_comments -> BigInt,
|
||||||
user_id -> Nullable<Int4>,
|
user_id -> Nullable<Int4>,
|
||||||
subscribed -> Nullable<Bool>,
|
subscribed -> Nullable<Bool>,
|
||||||
|
am_mod -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +48,32 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
community_user_ban_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
user_id -> Int4,
|
||||||
|
published -> Timestamp,
|
||||||
|
user_name -> Varchar,
|
||||||
|
community_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -54,6 +83,7 @@ pub struct CommunityView {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub category_id: i32,
|
pub category_id: i32,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
|
@ -63,6 +93,7 @@ pub struct CommunityView {
|
||||||
pub number_of_comments: i64,
|
pub number_of_comments: i64,
|
||||||
pub user_id: Option<i32>,
|
pub user_id: Option<i32>,
|
||||||
pub subscribed: Option<bool>,
|
pub subscribed: Option<bool>,
|
||||||
|
pub am_mod: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommunityView {
|
impl CommunityView {
|
||||||
|
@ -83,20 +114,30 @@ impl CommunityView {
|
||||||
query.first::<Self>(conn)
|
query.first::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_all(conn: &PgConnection, from_user_id: Option<i32>) -> Result<Vec<Self>, Error> {
|
pub fn list(conn: &PgConnection, from_user_id: Option<i32>, sort: SortType, limit: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
use actions::community_view::community_view::dsl::*;
|
use actions::community_view::community_view::dsl::*;
|
||||||
let mut query = community_view.into_boxed();
|
let mut query = community_view.into_boxed();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// The view lets you pass a null user_id, if you're not logged in
|
// The view lets you pass a null user_id, if you're not logged in
|
||||||
if let Some(from_user_id) = from_user_id {
|
|
||||||
query = query.filter(user_id.eq(from_user_id))
|
match sort {
|
||||||
.order_by((subscribed.desc(), number_of_subscribers.desc()));
|
SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()),
|
||||||
} else {
|
SortType::TopAll => {
|
||||||
query = query.filter(user_id.is_null())
|
match from_user_id {
|
||||||
.order_by(number_of_subscribers.desc());
|
Some(from_user_id) => query = query.filter(user_id.eq(from_user_id)).order_by((subscribed.desc(), number_of_subscribers.desc())),
|
||||||
|
None => query = query.order_by(number_of_subscribers.desc()).filter(user_id.is_null())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
};
|
};
|
||||||
|
|
||||||
query.load::<Self>(conn)
|
if let Some(limit) = limit {
|
||||||
|
query = query.limit(limit);
|
||||||
|
};
|
||||||
|
|
||||||
|
query.filter(removed.eq(false)).load::<Self>(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,3 +187,58 @@ impl CommunityFollowerView {
|
||||||
community_follower_view.filter(user_id.eq(from_user_id)).load::<Self>(conn)
|
community_follower_view.filter(user_id.eq(from_user_id)).load::<Self>(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="community_user_ban_view"]
|
||||||
|
pub struct CommunityUserBanView {
|
||||||
|
pub id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
pub user_name : String,
|
||||||
|
pub community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommunityUserBanView {
|
||||||
|
pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::community_view::community_user_ban_view::dsl::*;
|
||||||
|
community_user_ban_view.filter(community_id.eq(from_community_id)).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::community_view::community_user_ban_view::dsl::*;
|
||||||
|
community_user_ban_view.filter(user_id.eq(from_user_id)).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(conn: &PgConnection, from_user_id: i32, from_community_id: i32) -> Result<Self, Error> {
|
||||||
|
use actions::community_view::community_user_ban_view::dsl::*;
|
||||||
|
community_user_ban_view
|
||||||
|
.filter(user_id.eq(from_user_id))
|
||||||
|
.filter(community_id.eq(from_community_id))
|
||||||
|
.first::<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,3 +7,5 @@ pub mod comment_view;
|
||||||
pub mod category;
|
pub mod category;
|
||||||
pub mod community_view;
|
pub mod community_view;
|
||||||
pub mod user_view;
|
pub mod user_view;
|
||||||
|
pub mod moderator;
|
||||||
|
pub mod moderator_views;
|
||||||
|
|
655
server/src/actions/moderator.rs
Normal file
655
server/src/actions/moderator.rs
Normal file
|
@ -0,0 +1,655 @@
|
||||||
|
extern crate diesel;
|
||||||
|
use schema::{mod_remove_post, mod_lock_post, mod_remove_comment, mod_remove_community, mod_ban_from_community, mod_ban, mod_add_community, mod_add};
|
||||||
|
use diesel::*;
|
||||||
|
use diesel::result::Error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use {Crud};
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_remove_post"]
|
||||||
|
pub struct ModRemovePost {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub post_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_remove_post"]
|
||||||
|
pub struct ModRemovePostForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub post_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModRemovePostForm> for ModRemovePost {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_post::dsl::*;
|
||||||
|
mod_remove_post.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_remove_post::dsl::*;
|
||||||
|
diesel::delete(mod_remove_post.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModRemovePostForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_post::dsl::*;
|
||||||
|
insert_into(mod_remove_post)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModRemovePostForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_post::dsl::*;
|
||||||
|
diesel::update(mod_remove_post.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_lock_post"]
|
||||||
|
pub struct ModLockPost {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub post_id: i32,
|
||||||
|
pub locked: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_lock_post"]
|
||||||
|
pub struct ModLockPostForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub post_id: i32,
|
||||||
|
pub locked: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModLockPostForm> for ModLockPost {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_lock_post::dsl::*;
|
||||||
|
mod_lock_post.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_lock_post::dsl::*;
|
||||||
|
diesel::delete(mod_lock_post.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModLockPostForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_lock_post::dsl::*;
|
||||||
|
insert_into(mod_lock_post)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModLockPostForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_lock_post::dsl::*;
|
||||||
|
diesel::update(mod_lock_post.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_remove_comment"]
|
||||||
|
pub struct ModRemoveComment {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub comment_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_remove_comment"]
|
||||||
|
pub struct ModRemoveCommentForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub comment_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModRemoveCommentForm> for ModRemoveComment {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_comment::dsl::*;
|
||||||
|
mod_remove_comment.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_remove_comment::dsl::*;
|
||||||
|
diesel::delete(mod_remove_comment.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModRemoveCommentForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_comment::dsl::*;
|
||||||
|
insert_into(mod_remove_comment)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommentForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_comment::dsl::*;
|
||||||
|
diesel::update(mod_remove_comment.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_remove_community"]
|
||||||
|
pub struct ModRemoveCommunity {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_remove_community"]
|
||||||
|
pub struct ModRemoveCommunityForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModRemoveCommunityForm> for ModRemoveCommunity {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_community::dsl::*;
|
||||||
|
mod_remove_community.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_remove_community::dsl::*;
|
||||||
|
diesel::delete(mod_remove_community.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModRemoveCommunityForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_community::dsl::*;
|
||||||
|
insert_into(mod_remove_community)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModRemoveCommunityForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_remove_community::dsl::*;
|
||||||
|
diesel::update(mod_remove_community.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_ban_from_community"]
|
||||||
|
pub struct ModBanFromCommunity {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub banned: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_ban_from_community"]
|
||||||
|
pub struct ModBanFromCommunityForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub banned: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModBanFromCommunityForm> for ModBanFromCommunity {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_ban_from_community::dsl::*;
|
||||||
|
mod_ban_from_community.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_ban_from_community::dsl::*;
|
||||||
|
diesel::delete(mod_ban_from_community.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModBanFromCommunityForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_ban_from_community::dsl::*;
|
||||||
|
insert_into(mod_ban_from_community)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModBanFromCommunityForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_ban_from_community::dsl::*;
|
||||||
|
diesel::update(mod_ban_from_community.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_ban"]
|
||||||
|
pub struct ModBan {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub banned: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_ban"]
|
||||||
|
pub struct ModBanForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub banned: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModBanForm> for ModBan {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_ban::dsl::*;
|
||||||
|
mod_ban.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_ban::dsl::*;
|
||||||
|
diesel::delete(mod_ban.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModBanForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_ban::dsl::*;
|
||||||
|
insert_into(mod_ban)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModBanForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_ban::dsl::*;
|
||||||
|
diesel::update(mod_ban.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_add_community"]
|
||||||
|
pub struct ModAddCommunity {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_add_community"]
|
||||||
|
pub struct ModAddCommunityForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModAddCommunityForm> for ModAddCommunity {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_add_community::dsl::*;
|
||||||
|
mod_add_community.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_add_community::dsl::*;
|
||||||
|
diesel::delete(mod_add_community.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModAddCommunityForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_add_community::dsl::*;
|
||||||
|
insert_into(mod_add_community)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModAddCommunityForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_add_community::dsl::*;
|
||||||
|
diesel::update(mod_add_community.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_add"]
|
||||||
|
pub struct ModAdd {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||||
|
#[table_name="mod_add"]
|
||||||
|
pub struct ModAddForm {
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Crud<ModAddForm> for ModAdd {
|
||||||
|
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||||
|
use schema::mod_add::dsl::*;
|
||||||
|
mod_add.find(from_id)
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
|
||||||
|
use schema::mod_add::dsl::*;
|
||||||
|
diesel::delete(mod_add.find(from_id))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(conn: &PgConnection, form: &ModAddForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_add::dsl::*;
|
||||||
|
insert_into(mod_add)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, from_id: i32, form: &ModAddForm) -> Result<Self, Error> {
|
||||||
|
use schema::mod_add::dsl::*;
|
||||||
|
diesel::update(mod_add.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use establish_connection;
|
||||||
|
use super::*;
|
||||||
|
use actions::user::*;
|
||||||
|
use actions::post::*;
|
||||||
|
use actions::community::*;
|
||||||
|
use actions::comment::*;
|
||||||
|
// use Crud;
|
||||||
|
#[test]
|
||||||
|
fn test_crud() {
|
||||||
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
let new_mod = UserForm {
|
||||||
|
name: "the mod".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
|
||||||
|
|
||||||
|
let new_user = UserForm {
|
||||||
|
name: "jim2".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
||||||
|
let new_community = CommunityForm {
|
||||||
|
name: "mod_community".to_string(),
|
||||||
|
title: "nada".to_owned(),
|
||||||
|
description: None,
|
||||||
|
category_id: 1,
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
removed: None,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||||
|
|
||||||
|
let new_post = PostForm {
|
||||||
|
name: "A test post thweep".into(),
|
||||||
|
url: None,
|
||||||
|
body: None,
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
removed: None,
|
||||||
|
locked: None,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
||||||
|
|
||||||
|
let comment_form = CommentForm {
|
||||||
|
content: "A test comment".into(),
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
removed: None,
|
||||||
|
parent_id: None,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||||
|
|
||||||
|
// Now the actual tests
|
||||||
|
|
||||||
|
// remove post
|
||||||
|
let mod_remove_post_form = ModRemovePostForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
reason: None,
|
||||||
|
removed: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_remove_post = ModRemovePost::create(&conn, &mod_remove_post_form).unwrap();
|
||||||
|
let read_moderator_remove_post = ModRemovePost::read(&conn, inserted_mod_remove_post.id).unwrap();
|
||||||
|
let expected_moderator_remove_post = ModRemovePost {
|
||||||
|
id: inserted_mod_remove_post.id,
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
reason: None,
|
||||||
|
removed: Some(true),
|
||||||
|
when_: inserted_mod_remove_post.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
// lock post
|
||||||
|
|
||||||
|
let mod_lock_post_form = ModLockPostForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
locked: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_lock_post = ModLockPost::create(&conn, &mod_lock_post_form).unwrap();
|
||||||
|
let read_moderator_lock_post = ModLockPost::read(&conn, inserted_mod_lock_post.id).unwrap();
|
||||||
|
let expected_moderator_lock_post = ModLockPost {
|
||||||
|
id: inserted_mod_lock_post.id,
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
locked: Some(true),
|
||||||
|
when_: inserted_mod_lock_post.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
// comment
|
||||||
|
|
||||||
|
let mod_remove_comment_form = ModRemoveCommentForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
comment_id: inserted_comment.id,
|
||||||
|
reason: None,
|
||||||
|
removed: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_remove_comment = ModRemoveComment::create(&conn, &mod_remove_comment_form).unwrap();
|
||||||
|
let read_moderator_remove_comment = ModRemoveComment::read(&conn, inserted_mod_remove_comment.id).unwrap();
|
||||||
|
let expected_moderator_remove_comment = ModRemoveComment {
|
||||||
|
id: inserted_mod_remove_comment.id,
|
||||||
|
comment_id: inserted_comment.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
reason: None,
|
||||||
|
removed: Some(true),
|
||||||
|
when_: inserted_mod_remove_comment.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
// community
|
||||||
|
|
||||||
|
let mod_remove_community_form = ModRemoveCommunityForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
reason: None,
|
||||||
|
removed: None,
|
||||||
|
expires: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_remove_community = ModRemoveCommunity::create(&conn, &mod_remove_community_form).unwrap();
|
||||||
|
let read_moderator_remove_community = ModRemoveCommunity::read(&conn, inserted_mod_remove_community.id).unwrap();
|
||||||
|
let expected_moderator_remove_community = ModRemoveCommunity {
|
||||||
|
id: inserted_mod_remove_community.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
reason: None,
|
||||||
|
removed: Some(true),
|
||||||
|
expires: None,
|
||||||
|
when_: inserted_mod_remove_community.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ban from community
|
||||||
|
|
||||||
|
let mod_ban_from_community_form = ModBanFromCommunityForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
reason: None,
|
||||||
|
banned: None,
|
||||||
|
expires: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_ban_from_community = ModBanFromCommunity::create(&conn, &mod_ban_from_community_form).unwrap();
|
||||||
|
let read_moderator_ban_from_community = ModBanFromCommunity::read(&conn, inserted_mod_ban_from_community.id).unwrap();
|
||||||
|
let expected_moderator_ban_from_community = ModBanFromCommunity {
|
||||||
|
id: inserted_mod_ban_from_community.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
reason: None,
|
||||||
|
banned: Some(true),
|
||||||
|
expires: None,
|
||||||
|
when_: inserted_mod_ban_from_community.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ban
|
||||||
|
|
||||||
|
let mod_ban_form = ModBanForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
reason: None,
|
||||||
|
banned: None,
|
||||||
|
expires: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_ban = ModBan::create(&conn, &mod_ban_form).unwrap();
|
||||||
|
let read_moderator_ban = ModBan::read(&conn, inserted_mod_ban.id).unwrap();
|
||||||
|
let expected_moderator_ban = ModBan {
|
||||||
|
id: inserted_mod_ban.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
reason: None,
|
||||||
|
banned: Some(true),
|
||||||
|
expires: None,
|
||||||
|
when_: inserted_mod_ban.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
// mod add community
|
||||||
|
|
||||||
|
let mod_add_community_form = ModAddCommunityForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
removed: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_add_community = ModAddCommunity::create(&conn, &mod_add_community_form).unwrap();
|
||||||
|
let read_moderator_add_community = ModAddCommunity::read(&conn, inserted_mod_add_community.id).unwrap();
|
||||||
|
let expected_moderator_add_community = ModAddCommunity {
|
||||||
|
id: inserted_mod_add_community.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
removed: Some(false),
|
||||||
|
when_: inserted_mod_add_community.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
// mod add
|
||||||
|
|
||||||
|
let mod_add_form = ModAddForm {
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
removed: None,
|
||||||
|
};
|
||||||
|
let inserted_mod_add = ModAdd::create(&conn, &mod_add_form).unwrap();
|
||||||
|
let read_moderator_add = ModAdd::read(&conn, inserted_mod_add.id).unwrap();
|
||||||
|
let expected_moderator_add = ModAdd {
|
||||||
|
id: inserted_mod_add.id,
|
||||||
|
mod_user_id: inserted_mod.id,
|
||||||
|
other_user_id: inserted_user.id,
|
||||||
|
removed: Some(false),
|
||||||
|
when_: inserted_mod_add.when_,
|
||||||
|
};
|
||||||
|
|
||||||
|
ModRemovePost::delete(&conn, inserted_mod_remove_post.id).unwrap();
|
||||||
|
ModLockPost::delete(&conn, inserted_mod_lock_post.id).unwrap();
|
||||||
|
ModRemoveComment::delete(&conn, inserted_mod_remove_comment.id).unwrap();
|
||||||
|
ModRemoveCommunity::delete(&conn, inserted_mod_remove_community.id).unwrap();
|
||||||
|
ModBanFromCommunity::delete(&conn, inserted_mod_ban_from_community.id).unwrap();
|
||||||
|
ModBan::delete(&conn, inserted_mod_ban.id).unwrap();
|
||||||
|
ModAddCommunity::delete(&conn, inserted_mod_add_community.id).unwrap();
|
||||||
|
ModAdd::delete(&conn, inserted_mod_add.id).unwrap();
|
||||||
|
|
||||||
|
Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||||
|
Post::delete(&conn, inserted_post.id).unwrap();
|
||||||
|
Community::delete(&conn, inserted_community.id).unwrap();
|
||||||
|
User_::delete(&conn, inserted_user.id).unwrap();
|
||||||
|
User_::delete(&conn, inserted_mod.id).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(expected_moderator_remove_post, read_moderator_remove_post);
|
||||||
|
assert_eq!(expected_moderator_lock_post, read_moderator_lock_post);
|
||||||
|
assert_eq!(expected_moderator_remove_comment, read_moderator_remove_comment);
|
||||||
|
assert_eq!(expected_moderator_remove_community, read_moderator_remove_community);
|
||||||
|
assert_eq!(expected_moderator_ban_from_community, read_moderator_ban_from_community);
|
||||||
|
assert_eq!(expected_moderator_ban, read_moderator_ban);
|
||||||
|
assert_eq!(expected_moderator_add_community, read_moderator_add_community);
|
||||||
|
assert_eq!(expected_moderator_add, read_moderator_add);
|
||||||
|
}
|
||||||
|
}
|
427
server/src/actions/moderator_views.rs
Normal file
427
server/src/actions/moderator_views.rs
Normal file
|
@ -0,0 +1,427 @@
|
||||||
|
extern crate diesel;
|
||||||
|
use diesel::*;
|
||||||
|
use diesel::result::Error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_remove_post_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
post_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
post_name -> Varchar,
|
||||||
|
community_id -> Int4,
|
||||||
|
community_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_remove_post_view"]
|
||||||
|
pub struct ModRemovePostView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub post_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub post_name: String,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModRemovePostView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_community_id: Option<i32>,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_remove_post_view::dsl::*;
|
||||||
|
let mut query = mod_remove_post_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_community_id) = from_community_id {
|
||||||
|
query = query.filter(community_id.eq(from_community_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_lock_post_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
post_id -> Int4,
|
||||||
|
locked -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
post_name -> Varchar,
|
||||||
|
community_id -> Int4,
|
||||||
|
community_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_lock_post_view"]
|
||||||
|
pub struct ModLockPostView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub post_id: i32,
|
||||||
|
pub locked: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub post_name: String,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModLockPostView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_community_id: Option<i32>,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_lock_post_view::dsl::*;
|
||||||
|
let mut query = mod_lock_post_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_community_id) = from_community_id {
|
||||||
|
query = query.filter(community_id.eq(from_community_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_remove_comment_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
comment_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
comment_user_id -> Int4,
|
||||||
|
comment_user_name -> Varchar,
|
||||||
|
comment_content -> Text,
|
||||||
|
post_id -> Int4,
|
||||||
|
post_name -> Varchar,
|
||||||
|
community_id -> Int4,
|
||||||
|
community_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_remove_comment_view"]
|
||||||
|
pub struct ModRemoveCommentView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub comment_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub comment_user_id: i32,
|
||||||
|
pub comment_user_name: String,
|
||||||
|
pub comment_content: String,
|
||||||
|
pub post_id: i32,
|
||||||
|
pub post_name: String,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModRemoveCommentView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_community_id: Option<i32>,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_remove_comment_view::dsl::*;
|
||||||
|
let mut query = mod_remove_comment_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_community_id) = from_community_id {
|
||||||
|
query = query.filter(community_id.eq(from_community_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_remove_community_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
expires -> Nullable<Timestamp>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
community_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_remove_community_view"]
|
||||||
|
pub struct ModRemoveCommunityView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModRemoveCommunityView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_remove_community_view::dsl::*;
|
||||||
|
let mut query = mod_remove_community_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_ban_from_community_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
banned -> Nullable<Bool>,
|
||||||
|
expires -> Nullable<Timestamp>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
other_user_name -> Varchar,
|
||||||
|
community_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_ban_from_community_view"]
|
||||||
|
pub struct ModBanFromCommunityView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub banned: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub other_user_name: String,
|
||||||
|
pub community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModBanFromCommunityView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_community_id: Option<i32>,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_ban_from_community_view::dsl::*;
|
||||||
|
let mut query = mod_ban_from_community_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_community_id) = from_community_id {
|
||||||
|
query = query.filter(community_id.eq(from_community_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_ban_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
banned -> Nullable<Bool>,
|
||||||
|
expires -> Nullable<Timestamp>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
other_user_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_ban_view"]
|
||||||
|
pub struct ModBanView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub banned: Option<bool>,
|
||||||
|
pub expires: Option<chrono::NaiveDateTime>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub other_user_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModBanView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_ban_view::dsl::*;
|
||||||
|
let mut query = mod_ban_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_add_community_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
other_user_name -> Varchar,
|
||||||
|
community_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_add_community_view"]
|
||||||
|
pub struct ModAddCommunityView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub other_user_name: String,
|
||||||
|
pub community_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModAddCommunityView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_community_id: Option<i32>,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_add_community_view::dsl::*;
|
||||||
|
let mut query = mod_add_community_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_community_id) = from_community_id {
|
||||||
|
query = query.filter(community_id.eq(from_community_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_add_view (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
mod_user_name -> Varchar,
|
||||||
|
other_user_name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
|
||||||
|
#[table_name="mod_add_view"]
|
||||||
|
pub struct ModAddView {
|
||||||
|
pub id: i32,
|
||||||
|
pub mod_user_id: i32,
|
||||||
|
pub other_user_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub when_: chrono::NaiveDateTime,
|
||||||
|
pub mod_user_name: String,
|
||||||
|
pub other_user_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModAddView {
|
||||||
|
pub fn list(conn: &PgConnection,
|
||||||
|
from_mod_user_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
page: Option<i64>) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::moderator_views::mod_add_view::dsl::*;
|
||||||
|
let mut query = mod_add_view.into_boxed();
|
||||||
|
|
||||||
|
let page = page.unwrap_or(1);
|
||||||
|
let limit = limit.unwrap_or(10);
|
||||||
|
let offset = limit * (page - 1);
|
||||||
|
|
||||||
|
if let Some(from_mod_user_id) = from_mod_user_id {
|
||||||
|
query = query.filter(mod_user_id.eq(from_mod_user_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.limit(limit).offset(offset).order_by(when_.desc()).load::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ pub struct Post {
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub locked: Option<bool>,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -26,6 +28,8 @@ pub struct PostForm {
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub locked: Option<bool>,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,17 +119,20 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
||||||
let new_community = CommunityForm {
|
let new_community = CommunityForm {
|
||||||
name: "test community_2".to_string(),
|
name: "test community_3".to_string(),
|
||||||
title: "nada".to_owned(),
|
title: "nada".to_owned(),
|
||||||
description: None,
|
description: None,
|
||||||
category_id: 1,
|
category_id: 1,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
|
removed: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,6 +144,8 @@ mod tests {
|
||||||
body: None,
|
body: None,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
|
removed: None,
|
||||||
|
locked: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -150,6 +159,8 @@ mod tests {
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
published: inserted_post.published,
|
published: inserted_post.published,
|
||||||
|
removed: Some(false),
|
||||||
|
locked: Some(false),
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ table! {
|
||||||
body -> Nullable<Text>,
|
body -> Nullable<Text>,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
community_id -> Int4,
|
community_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
locked -> Nullable<Bool>,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
|
@ -31,6 +33,7 @@ table! {
|
||||||
user_id -> Nullable<Int4>,
|
user_id -> Nullable<Int4>,
|
||||||
my_vote -> Nullable<Int4>,
|
my_vote -> Nullable<Int4>,
|
||||||
subscribed -> Nullable<Bool>,
|
subscribed -> Nullable<Bool>,
|
||||||
|
am_mod -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +47,8 @@ pub struct PostView {
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
|
pub removed: Option<bool>,
|
||||||
|
pub locked: Option<bool>,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
|
@ -56,6 +61,7 @@ pub struct PostView {
|
||||||
pub user_id: Option<i32>,
|
pub user_id: Option<i32>,
|
||||||
pub my_vote: Option<i32>,
|
pub my_vote: Option<i32>,
|
||||||
pub subscribed: Option<bool>,
|
pub subscribed: Option<bool>,
|
||||||
|
pub am_mod: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PostView {
|
impl PostView {
|
||||||
|
@ -110,6 +116,8 @@ impl PostView {
|
||||||
.order_by(score.desc())
|
.order_by(score.desc())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
query = query.filter(removed.eq(false));
|
||||||
|
|
||||||
query.load::<Self>(conn)
|
query.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +164,9 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
updated: None
|
updated: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
@ -167,6 +177,7 @@ mod tests {
|
||||||
description: None,
|
description: None,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
category_id: 1,
|
category_id: 1,
|
||||||
|
removed: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -178,6 +189,8 @@ mod tests {
|
||||||
body: None,
|
body: None,
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
|
removed: None,
|
||||||
|
locked: None,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -216,6 +229,8 @@ mod tests {
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
creator_name: user_name.to_owned(),
|
creator_name: user_name.to_owned(),
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
|
removed: Some(false),
|
||||||
|
locked: Some(false),
|
||||||
community_name: community_name.to_owned(),
|
community_name: community_name.to_owned(),
|
||||||
number_of_comments: 0,
|
number_of_comments: 0,
|
||||||
score: 1,
|
score: 1,
|
||||||
|
@ -224,7 +239,8 @@ mod tests {
|
||||||
hot_rank: 864,
|
hot_rank: 864,
|
||||||
published: inserted_post.published,
|
published: inserted_post.published,
|
||||||
updated: None,
|
updated: None,
|
||||||
subscribed: None
|
subscribed: None,
|
||||||
|
am_mod: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_post_listing_with_user = PostView {
|
let expected_post_listing_with_user = PostView {
|
||||||
|
@ -234,6 +250,8 @@ mod tests {
|
||||||
name: post_name.to_owned(),
|
name: post_name.to_owned(),
|
||||||
url: None,
|
url: None,
|
||||||
body: None,
|
body: None,
|
||||||
|
removed: Some(false),
|
||||||
|
locked: Some(false),
|
||||||
creator_id: inserted_user.id,
|
creator_id: inserted_user.id,
|
||||||
creator_name: user_name.to_owned(),
|
creator_name: user_name.to_owned(),
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
|
@ -245,7 +263,8 @@ mod tests {
|
||||||
hot_rank: 864,
|
hot_rank: 864,
|
||||||
published: inserted_post.published,
|
published: inserted_post.published,
|
||||||
updated: None,
|
updated: None,
|
||||||
subscribed: None
|
subscribed: None,
|
||||||
|
am_mod: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -274,6 +293,5 @@ mod tests {
|
||||||
assert_eq!(expected_post_like, inserted_post_like);
|
assert_eq!(expected_post_like, inserted_post_like);
|
||||||
assert_eq!(1, like_removed);
|
assert_eq!(1, like_removed);
|
||||||
assert_eq!(1, num_deleted);
|
assert_eq!(1, num_deleted);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +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: bool,
|
||||||
|
pub banned: bool,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -28,6 +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: bool,
|
||||||
|
pub banned: bool,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -42,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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +130,8 @@ mod tests {
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,9 +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: false,
|
||||||
|
banned: false,
|
||||||
published: inserted_user.published,
|
published: inserted_user.published,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
@ -143,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,6 +8,8 @@ table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
fedi_name -> Varchar,
|
fedi_name -> Varchar,
|
||||||
|
admin -> Bool,
|
||||||
|
banned -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
number_of_posts -> BigInt,
|
number_of_posts -> BigInt,
|
||||||
post_score -> BigInt,
|
post_score -> BigInt,
|
||||||
|
@ -22,6 +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: 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,
|
||||||
|
@ -36,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,6 +44,8 @@ mod tests {
|
||||||
email: None,
|
email: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
published: naive_now(),
|
published: naive_now(),
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub extern crate jsonwebtoken;
|
||||||
pub extern crate bcrypt;
|
pub extern crate bcrypt;
|
||||||
pub extern crate regex;
|
pub extern crate regex;
|
||||||
#[macro_use] pub extern crate strum_macros;
|
#[macro_use] pub extern crate strum_macros;
|
||||||
|
#[macro_use] pub extern crate lazy_static;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod apub;
|
pub mod apub;
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
|
@ -50,6 +50,11 @@ pub trait Likeable<T> {
|
||||||
fn remove(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
|
fn remove(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Bannable<T> {
|
||||||
|
fn ban(conn: &PgConnection, form: &T) -> Result<Self, Error> where Self: Sized;
|
||||||
|
fn unban(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn establish_connection() -> PgConnection {
|
pub fn establish_connection() -> PgConnection {
|
||||||
let db_url = Settings::get().db_url;
|
let db_url = Settings::get().db_url;
|
||||||
PgConnection::establish(&db_url)
|
PgConnection::establish(&db_url)
|
||||||
|
@ -88,22 +93,47 @@ pub fn naive_now() -> NaiveDateTime {
|
||||||
chrono::prelude::Utc::now().naive_utc()
|
chrono::prelude::Utc::now().naive_utc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn naive_from_unix(time: i64) -> NaiveDateTime {
|
||||||
|
NaiveDateTime::from_timestamp(time, 0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_email_regex(test: &str) -> bool {
|
pub fn is_email_regex(test: &str) -> bool {
|
||||||
let re = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
|
EMAIL_REGEX.is_match(test)
|
||||||
re.is_match(test)
|
}
|
||||||
|
|
||||||
|
pub fn remove_slurs(test: &str) -> String {
|
||||||
|
SLUR_REGEX.replace_all(test, "*removed*").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_slurs(test: &str) -> bool {
|
||||||
|
SLUR_REGEX.is_match(test)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {Settings, is_email_regex};
|
use {Settings, is_email_regex, remove_slurs, has_slurs};
|
||||||
#[test]
|
#[test]
|
||||||
fn test_api() {
|
fn test_api() {
|
||||||
assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1");
|
assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test] fn test_email() {
|
||||||
fn test_email() {
|
|
||||||
assert!(is_email_regex("gush@gmail.com"));
|
assert!(is_email_regex("gush@gmail.com"));
|
||||||
assert!(!is_email_regex("nada_neutho"));
|
assert!(!is_email_regex("nada_neutho"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test] fn test_slur_filter() {
|
||||||
|
let test = "coons test dindu ladyboy tranny. This is a bunch of other safe text.".to_string();
|
||||||
|
let slur_free = "No slurs here";
|
||||||
|
assert_eq!(remove_slurs(&test), "*removed* test *removed* *removed* *removed*. This is a bunch of other safe text.".to_string());
|
||||||
|
assert!(has_slurs(&test));
|
||||||
|
assert!(!has_slurs(slur_free));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
|
||||||
|
static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bnig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?))").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ table! {
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
|
@ -36,6 +37,7 @@ table! {
|
||||||
description -> Nullable<Text>,
|
description -> Nullable<Text>,
|
||||||
category_id -> Int4,
|
category_id -> Int4,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
|
@ -59,6 +61,105 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
community_user_ban (id) {
|
||||||
|
id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
user_id -> Int4,
|
||||||
|
published -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_add (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_add_community (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_ban (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
banned -> Nullable<Bool>,
|
||||||
|
expires -> Nullable<Timestamp>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_ban_from_community (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
other_user_id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
banned -> Nullable<Bool>,
|
||||||
|
expires -> Nullable<Timestamp>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_lock_post (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
post_id -> Int4,
|
||||||
|
locked -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_remove_comment (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
comment_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_remove_community (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
community_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
expires -> Nullable<Timestamp>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mod_remove_post (id) {
|
||||||
|
id -> Int4,
|
||||||
|
mod_user_id -> Int4,
|
||||||
|
post_id -> Int4,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
when_ -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
post (id) {
|
post (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -67,6 +168,8 @@ table! {
|
||||||
body -> Nullable<Text>,
|
body -> Nullable<Text>,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
community_id -> Int4,
|
community_id -> Int4,
|
||||||
|
removed -> Nullable<Bool>,
|
||||||
|
locked -> Nullable<Bool>,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
|
@ -82,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,
|
||||||
|
@ -91,11 +205,21 @@ table! {
|
||||||
password_encrypted -> Text,
|
password_encrypted -> Text,
|
||||||
email -> Nullable<Text>,
|
email -> Nullable<Text>,
|
||||||
icon -> Nullable<Bytea>,
|
icon -> Nullable<Bytea>,
|
||||||
|
admin -> Bool,
|
||||||
|
banned -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
user_ban (id) {
|
||||||
|
id -> Int4,
|
||||||
|
user_id -> Int4,
|
||||||
|
published -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
joinable!(comment -> post (post_id));
|
joinable!(comment -> post (post_id));
|
||||||
joinable!(comment -> user_ (creator_id));
|
joinable!(comment -> user_ (creator_id));
|
||||||
joinable!(comment_like -> comment (comment_id));
|
joinable!(comment_like -> comment (comment_id));
|
||||||
|
@ -107,10 +231,24 @@ joinable!(community_follower -> community (community_id));
|
||||||
joinable!(community_follower -> user_ (user_id));
|
joinable!(community_follower -> user_ (user_id));
|
||||||
joinable!(community_moderator -> community (community_id));
|
joinable!(community_moderator -> community (community_id));
|
||||||
joinable!(community_moderator -> user_ (user_id));
|
joinable!(community_moderator -> user_ (user_id));
|
||||||
|
joinable!(community_user_ban -> community (community_id));
|
||||||
|
joinable!(community_user_ban -> user_ (user_id));
|
||||||
|
joinable!(mod_add_community -> community (community_id));
|
||||||
|
joinable!(mod_ban_from_community -> community (community_id));
|
||||||
|
joinable!(mod_lock_post -> post (post_id));
|
||||||
|
joinable!(mod_lock_post -> user_ (mod_user_id));
|
||||||
|
joinable!(mod_remove_comment -> comment (comment_id));
|
||||||
|
joinable!(mod_remove_comment -> user_ (mod_user_id));
|
||||||
|
joinable!(mod_remove_community -> community (community_id));
|
||||||
|
joinable!(mod_remove_community -> user_ (mod_user_id));
|
||||||
|
joinable!(mod_remove_post -> post (post_id));
|
||||||
|
joinable!(mod_remove_post -> user_ (mod_user_id));
|
||||||
joinable!(post -> community (community_id));
|
joinable!(post -> 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));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
category,
|
category,
|
||||||
|
@ -119,7 +257,18 @@ allow_tables_to_appear_in_same_query!(
|
||||||
community,
|
community,
|
||||||
community_follower,
|
community_follower,
|
||||||
community_moderator,
|
community_moderator,
|
||||||
|
community_user_ban,
|
||||||
|
mod_add,
|
||||||
|
mod_add_community,
|
||||||
|
mod_ban,
|
||||||
|
mod_ban_from_community,
|
||||||
|
mod_lock_post,
|
||||||
|
mod_remove_comment,
|
||||||
|
mod_remove_community,
|
||||||
|
mod_remove_post,
|
||||||
post,
|
post,
|
||||||
post_like,
|
post_like,
|
||||||
|
site,
|
||||||
user_,
|
user_,
|
||||||
|
user_ban,
|
||||||
);
|
);
|
||||||
|
|
File diff suppressed because it is too large
Load diff
1
ui/.gitignore
vendored
1
ui/.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
src/version.ts
|
|
||||||
dist
|
dist
|
||||||
.fusebox
|
.fusebox
|
||||||
_site
|
_site
|
||||||
|
|
|
@ -11,7 +11,7 @@ const transformInferno = require('ts-transform-inferno').default;
|
||||||
const transformClasscat = require('ts-transform-classcat').default;
|
const transformClasscat = require('ts-transform-classcat').default;
|
||||||
let fuse, app;
|
let fuse, app;
|
||||||
let isProduction = false;
|
let isProduction = false;
|
||||||
var setVersion = require('./set_version.js').setVersion;
|
// var setVersion = require('./set_version.js').setVersion;
|
||||||
|
|
||||||
Sparky.task('config', _ => {
|
Sparky.task('config', _ => {
|
||||||
fuse = new FuseBox({
|
fuse = new FuseBox({
|
||||||
|
@ -42,16 +42,16 @@ Sparky.task('config', _ => {
|
||||||
});
|
});
|
||||||
app = fuse.bundle('app').instructions('>index.tsx');
|
app = fuse.bundle('app').instructions('>index.tsx');
|
||||||
});
|
});
|
||||||
Sparky.task('version', _ => setVersion());
|
// Sparky.task('version', _ => setVersion());
|
||||||
Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
|
Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
|
||||||
Sparky.task('env', _ => (isProduction = true));
|
Sparky.task('env', _ => (isProduction = true));
|
||||||
Sparky.task('copy-assets', () => Sparky.src('assets/*.svg').dest('dist/'));
|
Sparky.task('copy-assets', () => Sparky.src('assets/*.svg').dest('dist/'));
|
||||||
Sparky.task('dev', ['clean', 'config', 'copy-assets', 'version'], _ => {
|
Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => {
|
||||||
fuse.dev();
|
fuse.dev();
|
||||||
app.hmr().watch();
|
app.hmr().watch();
|
||||||
return fuse.run();
|
return fuse.run();
|
||||||
});
|
});
|
||||||
Sparky.task('prod', ['clean', 'env', 'config', 'copy-assets', 'version'], _ => {
|
Sparky.task('prod', ['clean', 'env', 'config', 'copy-assets'], _ => {
|
||||||
// fuse.dev({ reload: true }); // remove after demo
|
// fuse.dev({ reload: true }); // remove after demo
|
||||||
return fuse.run();
|
return fuse.run();
|
||||||
});
|
});
|
||||||
|
|
2
ui/set_version.js
Normal file → Executable file
2
ui/set_version.js
Normal file → Executable file
|
@ -7,3 +7,5 @@ exports.setVersion = function() {
|
||||||
let line = `export let version: string = "${revision}";`;
|
let line = `export let version: string = "${revision}";`;
|
||||||
fs.writeFileSync("./src/version.ts", line);
|
fs.writeFileSync("./src/version.ts", line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setVersion()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces';
|
import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import * as autosize from 'autosize';
|
import * as autosize from 'autosize';
|
||||||
|
|
||||||
interface CommentFormProps {
|
interface CommentFormProps {
|
||||||
|
@ -8,6 +8,7 @@ interface CommentFormProps {
|
||||||
node?: CommentNodeI;
|
node?: CommentNodeI;
|
||||||
onReplyCancel?(): any;
|
onReplyCancel?(): any;
|
||||||
edit?: boolean;
|
edit?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommentFormState {
|
interface CommentFormState {
|
||||||
|
@ -21,9 +22,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
commentForm: {
|
commentForm: {
|
||||||
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.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",
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -36,6 +38,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
this.state.commentForm.edit_id = this.props.node.comment.id;
|
this.state.commentForm.edit_id = this.props.node.comment.id;
|
||||||
this.state.commentForm.parent_id = this.props.node.comment.parent_id;
|
this.state.commentForm.parent_id = this.props.node.comment.parent_id;
|
||||||
this.state.commentForm.content = this.props.node.comment.content;
|
this.state.commentForm.content = this.props.node.comment.content;
|
||||||
|
this.state.commentForm.creator_id = this.props.node.comment.creator_id;
|
||||||
} else {
|
} else {
|
||||||
// A reply gets a new parent id
|
// A reply gets a new parent id
|
||||||
this.state.commentForm.parent_id = this.props.node.comment.id;
|
this.state.commentForm.parent_id = this.props.node.comment.id;
|
||||||
|
@ -53,12 +56,12 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
<form onSubmit={linkEvent(this, this.handleCommentSubmit)}>
|
<form onSubmit={linkEvent(this, this.handleCommentSubmit)}>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required />
|
<textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required disabled={this.props.disabled}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="submit" class="btn btn-sm btn-secondary mr-2">{this.state.buttonTitle}</button>
|
<button type="submit" class="btn btn-sm btn-secondary mr-2" disabled={this.props.disabled}>{this.state.buttonTitle}</button>
|
||||||
{this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>}
|
{this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,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 {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI } from '../interfaces';
|
import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, BanFromCommunityForm, CommunityUser, AddModToCommunityForm } from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { mdToHtml } from '../utils';
|
import { mdToHtml, getUnixTime } from '../utils';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
import { CommentForm } from './comment-form';
|
import { CommentForm } from './comment-form';
|
||||||
import { CommentNodes } from './comment-nodes';
|
import { CommentNodes } from './comment-nodes';
|
||||||
|
@ -10,19 +10,31 @@ import { CommentNodes } from './comment-nodes';
|
||||||
interface CommentNodeState {
|
interface CommentNodeState {
|
||||||
showReply: boolean;
|
showReply: boolean;
|
||||||
showEdit: boolean;
|
showEdit: boolean;
|
||||||
|
showRemoveDialog: boolean;
|
||||||
|
removeReason: string;
|
||||||
|
showBanDialog: boolean;
|
||||||
|
banReason: string;
|
||||||
|
banExpires: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommentNodeProps {
|
interface CommentNodeProps {
|
||||||
node: CommentNodeI;
|
node: CommentNodeI;
|
||||||
noIndent?: boolean;
|
noIndent?: boolean;
|
||||||
viewOnly?: boolean;
|
viewOnly?: boolean;
|
||||||
|
locked?: boolean;
|
||||||
|
moderators: Array<CommunityUser>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
|
|
||||||
private emptyState: CommentNodeState = {
|
private emptyState: CommentNodeState = {
|
||||||
showReply: false,
|
showReply: false,
|
||||||
showEdit: false
|
showEdit: false,
|
||||||
|
showRemoveDialog: false,
|
||||||
|
removeReason: null,
|
||||||
|
showBanDialog: false,
|
||||||
|
banReason: null,
|
||||||
|
banExpires: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -60,10 +72,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<span><MomentTime data={node.comment} /></span>
|
<span><MomentTime data={node.comment} /></span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} />}
|
{this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} />}
|
||||||
{!this.state.showEdit &&
|
{!this.state.showEdit &&
|
||||||
<div>
|
<div>
|
||||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.content)} />
|
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.removed ? '*removed*' : node.comment.content)} />
|
||||||
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
||||||
{!this.props.viewOnly &&
|
{!this.props.viewOnly &&
|
||||||
<span class="mr-2">
|
<span class="mr-2">
|
||||||
|
@ -71,14 +83,39 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}>reply</span>
|
<span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}>reply</span>
|
||||||
</li>
|
</li>
|
||||||
{this.myComment &&
|
{this.myComment &&
|
||||||
|
<>
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
||||||
</li>
|
</li>
|
||||||
}
|
|
||||||
{this.myComment &&
|
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span>
|
<span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span>
|
||||||
</li>
|
</li>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{this.canMod &&
|
||||||
|
<>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
{!this.props.node.comment.removed ?
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
{!this.isMod &&
|
||||||
|
<>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
{!this.props.node.comment.banned ?
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModBanShow)}>ban</span> :
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModBanSubmit)}>unban</span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{!this.props.node.comment.banned &&
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleAddModToCommunity)}>{`${this.isMod ? 'remove' : 'appoint'} as mod`}</span>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
@ -89,14 +126,59 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} />}
|
{this.state.showRemoveDialog &&
|
||||||
{this.props.node.children && <CommentNodes nodes={this.props.node.children} />}
|
<form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
|
||||||
|
<input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} />
|
||||||
|
<button type="submit" class="btn btn-secondary">Remove Comment</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
{this.state.showBanDialog &&
|
||||||
|
<form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-form-label">Reason</label>
|
||||||
|
<input type="text" class="form-control mr-2" placeholder="Optional" value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} />
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-form-label">Expires</label>
|
||||||
|
<input type="date" class="form-control mr-2" placeholder="Expires" value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} />
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<button type="submit" class="btn btn-secondary">Ban {this.props.node.comment.creator_name}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
{this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} />}
|
||||||
|
{this.props.node.children && <CommentNodes nodes={this.props.node.children} locked={this.props.locked} moderators={this.props.moderators}/>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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 {
|
||||||
|
|
||||||
|
// You can do moderator actions only on the mods added after you.
|
||||||
|
if (UserService.Instance.user) {
|
||||||
|
let modIds = this.props.moderators.map(m => m.user_id);
|
||||||
|
let yourIndex = modIds.findIndex(id => id == UserService.Instance.user.id);
|
||||||
|
if (yourIndex == -1) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
console.log(modIds);
|
||||||
|
modIds = modIds.slice(0, yourIndex+1); // +1 cause you cant mod yourself
|
||||||
|
console.log(modIds);
|
||||||
|
return !modIds.includes(this.props.node.comment.creator_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get isMod(): boolean {
|
||||||
|
return this.props.moderators.map(m => m.user_id).includes(this.props.node.comment.creator_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReplyClick(i: CommentNode) {
|
handleReplyClick(i: CommentNode) {
|
||||||
|
@ -113,6 +195,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
let deleteForm: CommentFormI = {
|
let deleteForm: CommentFormI = {
|
||||||
content: "*deleted*",
|
content: "*deleted*",
|
||||||
edit_id: i.props.node.comment.id,
|
edit_id: i.props.node.comment.id,
|
||||||
|
creator_id: i.props.node.comment.creator_id,
|
||||||
post_id: i.props.node.comment.post_id,
|
post_id: i.props.node.comment.post_id,
|
||||||
parent_id: i.props.node.comment.parent_id,
|
parent_id: i.props.node.comment.parent_id,
|
||||||
auth: null
|
auth: null
|
||||||
|
@ -145,4 +228,72 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
};
|
};
|
||||||
WebSocketService.Instance.likeComment(form);
|
WebSocketService.Instance.likeComment(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleModRemoveShow(i: CommentNode) {
|
||||||
|
i.state.showRemoveDialog = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModRemoveReasonChange(i: CommentNode, event: any) {
|
||||||
|
i.state.removeReason = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModRemoveSubmit(i: CommentNode) {
|
||||||
|
event.preventDefault();
|
||||||
|
let form: CommentFormI = {
|
||||||
|
content: i.props.node.comment.content,
|
||||||
|
edit_id: i.props.node.comment.id,
|
||||||
|
creator_id: i.props.node.comment.creator_id,
|
||||||
|
post_id: i.props.node.comment.post_id,
|
||||||
|
parent_id: i.props.node.comment.parent_id,
|
||||||
|
removed: !i.props.node.comment.removed,
|
||||||
|
reason: i.state.removeReason,
|
||||||
|
auth: null
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.editComment(form);
|
||||||
|
|
||||||
|
i.state.showRemoveDialog = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModBanShow(i: CommentNode) {
|
||||||
|
i.state.showBanDialog = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModBanReasonChange(i: CommentNode, event: any) {
|
||||||
|
i.state.banReason = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModBanExpiresChange(i: CommentNode, event: any) {
|
||||||
|
i.state.banExpires = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModBanSubmit(i: CommentNode) {
|
||||||
|
event.preventDefault();
|
||||||
|
let form: BanFromCommunityForm = {
|
||||||
|
user_id: i.props.node.comment.creator_id,
|
||||||
|
community_id: i.props.node.comment.community_id,
|
||||||
|
ban: !i.props.node.comment.banned,
|
||||||
|
reason: i.state.banReason,
|
||||||
|
expires: getUnixTime(i.state.banExpires),
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.banFromCommunity(form);
|
||||||
|
|
||||||
|
i.state.showBanDialog = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddModToCommunity(i: CommentNode) {
|
||||||
|
let form: AddModToCommunityForm = {
|
||||||
|
user_id: i.props.node.comment.creator_id,
|
||||||
|
community_id: i.props.node.comment.community_id,
|
||||||
|
added: !i.isMod,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.addModToCommunity(form);
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component } from 'inferno';
|
import { Component } from 'inferno';
|
||||||
import { CommentNode as CommentNodeI } from '../interfaces';
|
import { CommentNode as CommentNodeI, CommunityUser } from '../interfaces';
|
||||||
import { CommentNode } from './comment-node';
|
import { CommentNode } from './comment-node';
|
||||||
|
|
||||||
interface CommentNodesState {
|
interface CommentNodesState {
|
||||||
|
@ -7,8 +7,10 @@ interface CommentNodesState {
|
||||||
|
|
||||||
interface CommentNodesProps {
|
interface CommentNodesProps {
|
||||||
nodes: Array<CommentNodeI>;
|
nodes: Array<CommentNodeI>;
|
||||||
|
moderators?: Array<CommunityUser>;
|
||||||
noIndent?: boolean;
|
noIndent?: boolean;
|
||||||
viewOnly?: boolean;
|
viewOnly?: boolean;
|
||||||
|
locked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> {
|
export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> {
|
||||||
|
@ -21,10 +23,15 @@ export class CommentNodes extends Component<CommentNodesProps, CommentNodesState
|
||||||
return (
|
return (
|
||||||
<div className="comments">
|
<div className="comments">
|
||||||
{this.props.nodes.map(node =>
|
{this.props.nodes.map(node =>
|
||||||
<CommentNode node={node} noIndent={this.props.noIndent} viewOnly={this.props.viewOnly}/>
|
<CommentNode node={node}
|
||||||
|
noIndent={this.props.noIndent}
|
||||||
|
viewOnly={this.props.viewOnly}
|
||||||
|
locked={this.props.locked}
|
||||||
|
moderators={this.props.moderators}/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Component, linkEvent } 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, Community, ListCommunitiesResponse, CommunityResponse, FollowCommunityForm } from '../interfaces';
|
import { UserOperation, Community, ListCommunitiesResponse, CommunityResponse, FollowCommunityForm, ListCommunitiesForm, SortType } from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp } from '../utils';
|
||||||
|
|
||||||
|
@ -30,7 +30,12 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
(err) => console.error(err),
|
(err) => console.error(err),
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
WebSocketService.Instance.listCommunities();
|
|
||||||
|
let listCommunitiesForm: ListCommunitiesForm = {
|
||||||
|
sort: SortType[SortType.TopAll]
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketService.Instance.listCommunities(listCommunitiesForm);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +50,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div class="container-fluid">
|
<div class="container">
|
||||||
{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>
|
||||||
|
@ -57,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>
|
||||||
|
@ -69,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>
|
||||||
|
@ -97,7 +102,6 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
WebSocketService.Instance.followCommunity(form);
|
WebSocketService.Instance.followCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
handleSubscribe(communityId: number) {
|
handleSubscribe(communityId: number) {
|
||||||
let form: FollowCommunityForm = {
|
let form: FollowCommunityForm = {
|
||||||
community_id: communityId,
|
community_id: communityId,
|
||||||
|
|
|
@ -155,6 +155,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
alert(msg.error);
|
alert(msg.error);
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.ListCategories){
|
} else if (op == UserOperation.ListCategories){
|
||||||
let res: ListCategoriesResponse = msg;
|
let res: ListCategoriesResponse = msg;
|
||||||
|
|
|
@ -63,7 +63,11 @@ export class Community 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 class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-9">
|
<div class="col-12 col-md-9">
|
||||||
<h4>{this.state.community.title}</h4>
|
<h4>{this.state.community.title}
|
||||||
|
{this.state.community.removed &&
|
||||||
|
<small className="ml-2 text-muted font-italic">removed</small>
|
||||||
|
}
|
||||||
|
</h4>
|
||||||
<PostListings communityId={this.state.communityId} />
|
<PostListings communityId={this.state.communityId} />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
|
|
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
|
||||||
|
@ -97,13 +98,13 @@ export class Login extends Component<any, State> {
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-2 col-form-label">Username</label>
|
<label class="col-sm-2 col-form-label">Username</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" value={this.state.registerForm.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} />
|
<input type="text" class="form-control" value={this.state.registerForm.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} pattern="[a-zA-Z0-9_]+" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-2 col-form-label">Email</label>
|
<label class="col-sm-2 col-form-label">Email</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="email" class="form-control" value={this.state.registerForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} />
|
<input type="email" class="form-control" placeholder="Optional" value={this.state.registerForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -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,13 +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 } 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 } from '../utils';
|
import { msgOp, repoUrl, mdToHtml } from '../utils';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
subscribedCommunities: Array<CommunityUser>;
|
subscribedCommunities: Array<CommunityUser>;
|
||||||
|
trendingCommunities: Array<Community>;
|
||||||
|
site: GetSiteResponse;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +19,22 @@ export class Main extends Component<any, State> {
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: State = {
|
private emptyState: State = {
|
||||||
subscribedCommunities: [],
|
subscribedCommunities: [],
|
||||||
|
trendingCommunities: [],
|
||||||
|
site: {
|
||||||
|
op: null,
|
||||||
|
site: {
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
creator_id: null,
|
||||||
|
creator_name: null,
|
||||||
|
published: null,
|
||||||
|
number_of_users: null,
|
||||||
|
number_of_posts: null,
|
||||||
|
number_of_comments: null,
|
||||||
|
},
|
||||||
|
admins: [],
|
||||||
|
banned: [],
|
||||||
|
},
|
||||||
loading: true
|
loading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +51,18 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let listCommunitiesForm: ListCommunitiesForm = {
|
||||||
|
sort: SortType[SortType.New],
|
||||||
|
limit: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketService.Instance.listCommunities(listCommunitiesForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -46,25 +73,25 @@ export class Main extends Component<any, State> {
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-9">
|
<div class="col-12 col-md-8">
|
||||||
<PostListings />
|
<PostListings />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<h4>A Landing message</h4>
|
|
||||||
{UserService.Instance.loggedIn &&
|
|
||||||
<div>
|
|
||||||
{this.state.loading ?
|
{this.state.loading ?
|
||||||
<h4 class="mt-3"><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>
|
||||||
|
{this.trendingCommunities()}
|
||||||
|
{UserService.Instance.user && this.state.subscribedCommunities.length > 0 &&
|
||||||
<div>
|
<div>
|
||||||
<hr />
|
|
||||||
<h4>Subscribed forums</h4>
|
<h4>Subscribed forums</h4>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-inline">
|
||||||
{this.state.subscribedCommunities.map(community =>
|
{this.state.subscribedCommunities.map(community =>
|
||||||
<li><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()}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,6 +100,54 @@ export class Main extends Component<any, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trendingCommunities() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4>Trending <Link class="text-white" to="/communities">forums</Link></h4>
|
||||||
|
<ul class="list-inline">
|
||||||
|
{this.state.trendingCommunities.map(community =>
|
||||||
|
<li class="list-inline-item"><Link to={`/community/${community.id}`}>{community.name}</Link></li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
landing() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4>{`${this.state.site.site.name}`}</h4>
|
||||||
|
<ul class="my-1 list-inline">
|
||||||
|
<li className="list-inline-item badge badge-light">{this.state.site.site.number_of_users} Users</li>
|
||||||
|
<li className="list-inline-item badge badge-light">{this.state.site.site.number_of_posts} Posts</li>
|
||||||
|
<li className="list-inline-item badge badge-light">{this.state.site.site.number_of_comments} Comments</li>
|
||||||
|
<li className="list-inline-item"><Link className="badge badge-light" to="/modlog">Modlog</Link></li>
|
||||||
|
</ul>
|
||||||
|
<ul class="list-inline small">
|
||||||
|
<li class="list-inline-item">admins: </li>
|
||||||
|
{this.state.site.admins.map(admin =>
|
||||||
|
<li class="list-inline-item"><Link class="text-info" to={`/user/${admin.id}`}>{admin.name}</Link></li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
{this.state.site.site.description &&
|
||||||
|
<div>
|
||||||
|
<hr />
|
||||||
|
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(this.state.site.site.description)} />
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<h4>Welcome to
|
||||||
|
<svg class="icon mx-2"><use xlinkHref="#icon-mouse"></use></svg>
|
||||||
|
<a href={repoUrl}>Lemmy<sup>Beta</sup></a>
|
||||||
|
</h4>
|
||||||
|
<p>Lemmy is a <a href="https://en.wikipedia.org/wiki/Link_aggregation">link aggregator</a> / reddit alternative, intended to work in the <a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a>.</p>
|
||||||
|
<p>Its self-hostable, has live-updating comment threads, and is tiny (<code>~80kB</code>). Federation into the ActivityPub network is on the roadmap.</p>
|
||||||
|
<p>This is a <b>very early beta version</b>, and a lot of features are currently broken or missing.</p>
|
||||||
|
<p>Suggest new features or report bugs <a href={repoUrl}>here.</a></p>
|
||||||
|
<p>Made with <a href="https://www.rust-lang.org">Rust</a>, <a href="https://actix.rs/">Actix</a>, <a href="https://www.infernojs.org">Inferno</a>, <a href="https://www.typescriptlang.org/">Typescript</a>.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
|
@ -85,6 +160,22 @@ export class Main extends Component<any, State> {
|
||||||
this.state.subscribedCommunities = res.communities;
|
this.state.subscribedCommunities = res.communities;
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
|
} else if (op == UserOperation.ListCommunities) {
|
||||||
|
let res: ListCommunitiesResponse = msg;
|
||||||
|
this.state.trendingCommunities = res.communities;
|
||||||
|
this.state.loading = false;
|
||||||
|
this.setState(this.state);
|
||||||
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
175
ui/src/components/modlog.tsx
Normal file
175
ui/src/components/modlog.tsx
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
import { Component } from 'inferno';
|
||||||
|
import { Link } from 'inferno-router';
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
|
import { UserOperation, GetModlogForm, GetModlogResponse, ModRemovePost, ModLockPost, ModRemoveComment, ModRemoveCommunity, ModBanFromCommunity, ModBan, ModAddCommunity, ModAdd } from '../interfaces';
|
||||||
|
import { WebSocketService } from '../services';
|
||||||
|
import { msgOp, addTypeInfo } from '../utils';
|
||||||
|
import { MomentTime } from './moment-time';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
interface ModlogState {
|
||||||
|
combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}>,
|
||||||
|
communityId?: number,
|
||||||
|
communityName?: string,
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Modlog extends Component<any, ModlogState> {
|
||||||
|
private subscription: Subscription;
|
||||||
|
private emptyState: ModlogState = {
|
||||||
|
combined: [],
|
||||||
|
loading: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = this.emptyState;
|
||||||
|
this.state.communityId = this.props.match.params.community_id ? Number(this.props.match.params.community_id) : undefined;
|
||||||
|
this.subscription = WebSocketService.Instance.subject
|
||||||
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
|
.subscribe(
|
||||||
|
(msg) => this.parseMessage(msg),
|
||||||
|
(err) => console.error(err),
|
||||||
|
() => console.log('complete')
|
||||||
|
);
|
||||||
|
|
||||||
|
let modlogForm: GetModlogForm = {
|
||||||
|
community_id: this.state.communityId
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.getModlog(modlogForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
setCombined(res: GetModlogResponse) {
|
||||||
|
let removed_posts = addTypeInfo(res.removed_posts, "removed_posts");
|
||||||
|
let locked_posts = addTypeInfo(res.locked_posts, "locked_posts");
|
||||||
|
let removed_comments = addTypeInfo(res.removed_comments, "removed_comments");
|
||||||
|
let removed_communities = addTypeInfo(res.removed_communities, "removed_communities");
|
||||||
|
let banned_from_community = addTypeInfo(res.banned_from_community, "banned_from_community");
|
||||||
|
let added_to_community = addTypeInfo(res.added_to_community, "added_to_community");
|
||||||
|
|
||||||
|
this.state.combined.push(...removed_posts);
|
||||||
|
this.state.combined.push(...locked_posts);
|
||||||
|
this.state.combined.push(...removed_comments);
|
||||||
|
this.state.combined.push(...removed_communities);
|
||||||
|
this.state.combined.push(...banned_from_community);
|
||||||
|
this.state.combined.push(...added_to_community);
|
||||||
|
|
||||||
|
if (this.state.communityId && this.state.combined.length > 0) {
|
||||||
|
this.state.communityName = this.state.combined[0].data.community_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort them by time
|
||||||
|
this.state.combined.sort((a, b) => b.data.when_.localeCompare(a.data.when_));
|
||||||
|
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
combined() {
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
{this.state.combined.map(i =>
|
||||||
|
<tr>
|
||||||
|
<td><MomentTime data={i.data} /></td>
|
||||||
|
<td><Link to={`/user/${i.data.mod_user_id}`}>{i.data.mod_user_name}</Link></td>
|
||||||
|
<td>
|
||||||
|
{i.type_ == 'removed_posts' &&
|
||||||
|
<>
|
||||||
|
{(i.data as ModRemovePost).removed? 'Removed' : 'Restored'}
|
||||||
|
<span> Post <Link to={`/post/${(i.data as ModRemovePost).post_id}`}>{(i.data as ModRemovePost).post_name}</Link></span>
|
||||||
|
<div>{(i.data as ModRemovePost).reason && ` reason: ${(i.data as ModRemovePost).reason}`}</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{i.type_ == 'locked_posts' &&
|
||||||
|
<>
|
||||||
|
{(i.data as ModLockPost).locked? 'Locked' : 'Unlocked'}
|
||||||
|
<span> Post <Link to={`/post/${(i.data as ModLockPost).post_id}`}>{(i.data as ModLockPost).post_name}</Link></span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{i.type_ == 'removed_comments' &&
|
||||||
|
<>
|
||||||
|
{(i.data as ModRemoveComment).removed? 'Removed' : 'Restored'}
|
||||||
|
<span> Comment <Link to={`/post/${(i.data as ModRemoveComment).post_id}/comment/${(i.data as ModRemoveComment).comment_id}`}>{(i.data as ModRemoveComment).comment_content}</Link></span>
|
||||||
|
<div>{(i.data as ModRemoveComment).reason && ` reason: ${(i.data as ModRemoveComment).reason}`}</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{i.type_ == 'removed_communities' &&
|
||||||
|
<>
|
||||||
|
{(i.data as ModRemoveCommunity).removed ? 'Removed' : 'Restored'}
|
||||||
|
<span> Community <Link to={`/community/${i.data.community_id}`}>{i.data.community_name}</Link></span>
|
||||||
|
<div>{(i.data as ModRemoveCommunity).reason && ` reason: ${(i.data as ModRemoveCommunity).reason}`}</div>
|
||||||
|
<div>{(i.data as ModRemoveCommunity).expires && ` expires: ${moment.utc((i.data as ModRemoveCommunity).expires).fromNow()}`}</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{i.type_ == 'banned_from_community' &&
|
||||||
|
<>
|
||||||
|
<span>{(i.data as ModBanFromCommunity).banned ? 'Banned ' : 'Unbanned '} </span>
|
||||||
|
<span><Link to={`/user/${(i.data as ModBanFromCommunity).other_user_id}`}>{(i.data as ModBanFromCommunity).other_user_name}</Link></span>
|
||||||
|
<div>{(i.data as ModBanFromCommunity).reason && ` reason: ${(i.data as ModBanFromCommunity).reason}`}</div>
|
||||||
|
<div>{(i.data as ModBanFromCommunity).expires && ` expires: ${moment.utc((i.data as ModBanFromCommunity).expires).fromNow()}`}</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{i.type_ == 'added_to_community' &&
|
||||||
|
<>
|
||||||
|
<span>{(i.data as ModAddCommunity).removed ? 'Removed ' : 'Appointed '} </span>
|
||||||
|
<span><Link to={`/user/${(i.data as ModAddCommunity).other_user_id}`}>{(i.data as ModAddCommunity).other_user_name}</Link></span>
|
||||||
|
<span> as a mod to the community </span>
|
||||||
|
<span><Link to={`/community/${i.data.community_id}`}>{i.data.community_name}</Link></span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="container">
|
||||||
|
{this.state.loading ?
|
||||||
|
<h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
{this.state.communityName && <Link className="text-white" to={`/community/${this.state.communityId}`}>/f/{this.state.communityName} </Link>}
|
||||||
|
<span>Modlog</span>
|
||||||
|
</h4>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="modlog_table" class="table table-sm table-hover">
|
||||||
|
<thead class="pointer">
|
||||||
|
<tr>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Mod</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{this.combined()}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMessage(msg: any) {
|
||||||
|
console.log(msg);
|
||||||
|
let op: UserOperation = msgOp(msg);
|
||||||
|
if (msg.error) {
|
||||||
|
alert(msg.error);
|
||||||
|
return;
|
||||||
|
} else if (op == UserOperation.GetModlog) {
|
||||||
|
let res: GetModlogResponse = msg;
|
||||||
|
this.state.loading = false;
|
||||||
|
this.setCombined(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,8 @@ import * as moment from 'moment';
|
||||||
|
|
||||||
interface MomentTimeProps {
|
interface MomentTimeProps {
|
||||||
data: {
|
data: {
|
||||||
published: string;
|
published?: string;
|
||||||
|
when_?: string;
|
||||||
updated?: string;
|
updated?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +21,9 @@ export class MomentTime extends Component<MomentTimeProps, any> {
|
||||||
<span title={this.props.data.updated} className="font-italics">modified {moment.utc(this.props.data.updated).fromNow()}</span>
|
<span title={this.props.data.updated} className="font-italics">modified {moment.utc(this.props.data.updated).fromNow()}</span>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
let str = this.props.data.published || this.props.data.when_;
|
||||||
return (
|
return (
|
||||||
<span title={this.props.data.published}>{moment.utc(this.props.data.published).fromNow()}</span>
|
<span title={str}>{moment.utc(str).fromNow()}</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -25,7 +24,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
|
|
||||||
// Subscribe to user changes
|
// Subscribe to user changes
|
||||||
UserService.Instance.sub.subscribe(user => {
|
UserService.Instance.sub.subscribe(user => {
|
||||||
let loggedIn: boolean = user !== null;
|
let loggedIn: boolean = user !== undefined;
|
||||||
this.setState({isLoggedIn: loggedIn});
|
this.setState({isLoggedIn: loggedIn});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -40,7 +39,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
// TODO toggle css collapse
|
// TODO toggle css collapse
|
||||||
navbar() {
|
navbar() {
|
||||||
return (
|
return (
|
||||||
<nav class="navbar navbar-expand-sm navbar-light bg-light p-0 px-3 shadow">
|
<nav class="container navbar navbar-expand-md navbar-light navbar-bg p-0 px-3">
|
||||||
<a title={version} class="navbar-brand" href="#">
|
<a title={version} class="navbar-brand" href="#">
|
||||||
<svg class="icon mr-2"><use xlinkHref="#icon-mouse"></use></svg>
|
<svg class="icon mr-2"><use xlinkHref="#icon-mouse"></use></svg>
|
||||||
Lemmy
|
Lemmy
|
||||||
|
@ -51,10 +50,10 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
<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">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href={repoUrl}>About</a>
|
<Link class="nav-link" to="/communities">Forums</Link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<Link class="nav-link" to="/communities">Forums</Link>
|
<Link class="nav-link" to="/modlog">Modlog</Link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<Link class="nav-link" to="/create_post">Create Post</Link>
|
<Link class="nav-link" to="/create_post">Create Post</Link>
|
||||||
|
@ -74,7 +73,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
<a role="button" class="dropdown-item pointer" onClick={ linkEvent(this, this.handleLogoutClick) }>Logout</a>
|
<a role="button" class="dropdown-item pointer" onClick={ linkEvent(this, this.handleLogoutClick) }>Logout</a>
|
||||||
</div>
|
</div>
|
||||||
</li> :
|
</li> :
|
||||||
<Link class="nav-link" to="/login">Login</Link>
|
<Link class="nav-link" to="/login">Login / Sign up</Link>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces';
|
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType } from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp } from '../utils';
|
||||||
import * as autosize from 'autosize';
|
import * as autosize from 'autosize';
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
postForm: {
|
postForm: {
|
||||||
name: null,
|
name: null,
|
||||||
auth: null,
|
auth: null,
|
||||||
community_id: null
|
community_id: null,
|
||||||
|
creator_id: (UserService.Instance.user) ? UserService.Instance.user.id : null,
|
||||||
},
|
},
|
||||||
communities: [],
|
communities: [],
|
||||||
loading: false
|
loading: false
|
||||||
|
@ -43,6 +44,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
name: this.props.post.name,
|
name: this.props.post.name,
|
||||||
community_id: this.props.post.community_id,
|
community_id: this.props.post.community_id,
|
||||||
edit_id: this.props.post.id,
|
edit_id: this.props.post.id,
|
||||||
|
creator_id: this.props.post.creator_id,
|
||||||
url: this.props.post.url,
|
url: this.props.post.url,
|
||||||
auth: null
|
auth: null
|
||||||
}
|
}
|
||||||
|
@ -56,7 +58,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
|
|
||||||
WebSocketService.Instance.listCommunities();
|
let listCommunitiesForm: ListCommunitiesForm = {
|
||||||
|
sort: SortType[SortType.TopAll]
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketService.Instance.listCommunities(listCommunitiesForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -89,6 +95,8 @@ 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>
|
||||||
|
{/* Cant change a community from an edit */}
|
||||||
|
{!this.props.post &&
|
||||||
<div class="form-group row">
|
<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">
|
||||||
|
@ -99,6 +107,7 @@ 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">
|
||||||
|
@ -151,7 +160,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let op: UserOperation = msgOp(msg);
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
|
alert(msg.error);
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.ListCommunities) {
|
} else if (op == UserOperation.ListCommunities) {
|
||||||
let res: ListCommunitiesResponse = msg;
|
let res: ListCommunitiesResponse = msg;
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { mdToHtml } from '../utils';
|
||||||
|
|
||||||
interface PostListingState {
|
interface PostListingState {
|
||||||
showEdit: boolean;
|
showEdit: boolean;
|
||||||
|
showRemoveDialog: boolean;
|
||||||
|
removeReason: string;
|
||||||
iframeExpanded: boolean;
|
iframeExpanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +25,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
|
|
||||||
private emptyState: PostListingState = {
|
private emptyState: PostListingState = {
|
||||||
showEdit: false,
|
showEdit: false,
|
||||||
|
showRemoveDialog: false,
|
||||||
|
removeReason: null,
|
||||||
iframeExpanded: false
|
iframeExpanded: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +63,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
{post.url
|
{post.url
|
||||||
? <div className="mb-0">
|
? <div className="mb-0">
|
||||||
<h4 className="d-inline"><a className="text-white" href={post.url}>{post.name}</a></h4>
|
<h4 className="d-inline"><a className="text-white" href={post.url}>{post.name}</a>
|
||||||
|
{post.removed &&
|
||||||
|
<small className="ml-2 text-muted font-italic">removed</small>
|
||||||
|
}
|
||||||
|
{post.locked &&
|
||||||
|
<small className="ml-2 text-muted font-italic">locked</small>
|
||||||
|
}
|
||||||
|
</h4>
|
||||||
<small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small>
|
<small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small>
|
||||||
{ !this.state.iframeExpanded
|
{ !this.state.iframeExpanded
|
||||||
? <span class="pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleIframeExpandClick)}>+</span>
|
? <span class="pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleIframeExpandClick)}>+</span>
|
||||||
|
@ -72,7 +83,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
: <h4 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link></h4>
|
: <h4 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link>
|
||||||
|
{post.removed &&
|
||||||
|
<small className="ml-2 text-muted font-italic">removed</small>
|
||||||
|
}
|
||||||
|
{post.locked &&
|
||||||
|
<small className="ml-2 text-muted font-italic">locked</small>
|
||||||
|
}
|
||||||
|
</h4>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="details ml-4 mb-1">
|
<div className="details ml-4 mb-1">
|
||||||
|
@ -102,16 +120,39 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
<Link className="text-muted" to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link>
|
<Link className="text-muted" to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{this.myPost &&
|
{this.props.editable &&
|
||||||
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
||||||
|
{this.myPost &&
|
||||||
|
<span>
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item mr-2">
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span>
|
<span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span>
|
||||||
</li>
|
</li>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
{this.props.post.am_mod &&
|
||||||
|
<span>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
{!this.props.post.removed ?
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModLock)}>{this.props.post.locked ? 'unlock' : 'lock'}</span>
|
||||||
|
</li>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
{this.state.showRemoveDialog &&
|
||||||
|
<form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
|
||||||
|
<input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} />
|
||||||
|
<button type="submit" class="btn btn-secondary">Remove Post</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
{this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />}
|
{this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,7 +160,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get myPost(): boolean {
|
private get myPost(): boolean {
|
||||||
return this.props.editable && 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) {
|
||||||
|
@ -162,11 +203,51 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
name: "deleted",
|
name: "deleted",
|
||||||
url: '',
|
url: '',
|
||||||
edit_id: i.props.post.id,
|
edit_id: i.props.post.id,
|
||||||
|
creator_id: i.props.post.creator_id,
|
||||||
auth: null
|
auth: null
|
||||||
};
|
};
|
||||||
WebSocketService.Instance.editPost(deleteForm);
|
WebSocketService.Instance.editPost(deleteForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleModRemoveShow(i: PostListing) {
|
||||||
|
i.state.showRemoveDialog = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModRemoveReasonChange(i: PostListing, event: any) {
|
||||||
|
i.state.removeReason = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModRemoveSubmit(i: PostListing) {
|
||||||
|
event.preventDefault();
|
||||||
|
let form: PostFormI = {
|
||||||
|
name: i.props.post.name,
|
||||||
|
community_id: i.props.post.community_id,
|
||||||
|
edit_id: i.props.post.id,
|
||||||
|
creator_id: i.props.post.creator_id,
|
||||||
|
removed: !i.props.post.removed,
|
||||||
|
reason: i.state.removeReason,
|
||||||
|
auth: null,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.editPost(form);
|
||||||
|
|
||||||
|
i.state.showRemoveDialog = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModLock(i: PostListing) {
|
||||||
|
let form: PostFormI = {
|
||||||
|
name: i.props.post.name,
|
||||||
|
community_id: i.props.post.community_id,
|
||||||
|
edit_id: i.props.post.id,
|
||||||
|
creator_id: i.props.post.creator_id,
|
||||||
|
locked: !i.props.post.locked,
|
||||||
|
auth: null,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.editPost(form);
|
||||||
|
}
|
||||||
|
|
||||||
handleIframeExpandClick(i: PostListing) {
|
handleIframeExpandClick(i: PostListing) {
|
||||||
i.state.iframeExpanded = !i.state.iframeExpanded;
|
i.state.iframeExpanded = !i.state.iframeExpanded;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentResponse, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse, CommentNode as CommentNodeI } from '../interfaces';
|
import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentResponse, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse, CommentNode as CommentNodeI, BanFromCommunityResponse, AddModToCommunityResponse } from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { msgOp, hotRank } from '../utils';
|
import { msgOp, hotRank } from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
|
@ -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} />
|
<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">
|
||||||
|
@ -101,17 +101,17 @@ export class Post extends Component<any, PostState> {
|
||||||
sortRadios() {
|
sortRadios() {
|
||||||
return (
|
return (
|
||||||
<div class="btn-group btn-group-toggle mb-3">
|
<div class="btn-group btn-group-toggle mb-3">
|
||||||
<label className={`btn btn-sm btn-secondary ${this.state.commentSort === CommentSortType.Hot && 'active'}`}>Hot
|
<label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Hot && 'active'}`}>Hot
|
||||||
<input type="radio" value={CommentSortType.Hot}
|
<input type="radio" value={CommentSortType.Hot}
|
||||||
checked={this.state.commentSort === CommentSortType.Hot}
|
checked={this.state.commentSort === CommentSortType.Hot}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)} />
|
onChange={linkEvent(this, this.handleCommentSortChange)} />
|
||||||
</label>
|
</label>
|
||||||
<label className={`btn btn-sm btn-secondary ${this.state.commentSort === CommentSortType.Top && 'active'}`}>Top
|
<label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Top && 'active'}`}>Top
|
||||||
<input type="radio" value={CommentSortType.Top}
|
<input type="radio" value={CommentSortType.Top}
|
||||||
checked={this.state.commentSort === CommentSortType.Top}
|
checked={this.state.commentSort === CommentSortType.Top}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)} />
|
onChange={linkEvent(this, this.handleCommentSortChange)} />
|
||||||
</label>
|
</label>
|
||||||
<label className={`btn btn-sm btn-secondary ${this.state.commentSort === CommentSortType.New && 'active'}`}>New
|
<label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.New && 'active'}`}>New
|
||||||
<input type="radio" value={CommentSortType.New}
|
<input type="radio" value={CommentSortType.New}
|
||||||
checked={this.state.commentSort === CommentSortType.New}
|
checked={this.state.commentSort === CommentSortType.New}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)} />
|
onChange={linkEvent(this, this.handleCommentSortChange)} />
|
||||||
|
@ -125,7 +125,7 @@ export class Post extends Component<any, PostState> {
|
||||||
<div class="sticky-top">
|
<div class="sticky-top">
|
||||||
<h4>New Comments</h4>
|
<h4>New Comments</h4>
|
||||||
{this.state.comments.map(comment =>
|
{this.state.comments.map(comment =>
|
||||||
<CommentNodes nodes={[{comment: comment}]} noIndent />
|
<CommentNodes nodes={[{comment: comment}]} noIndent locked={this.state.post.locked} moderators={this.state.moderators} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -188,7 +188,7 @@ export class Post extends Component<any, PostState> {
|
||||||
let nodes = this.buildCommentsTree();
|
let nodes = this.buildCommentsTree();
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<CommentNodes nodes={nodes} />
|
<CommentNodes nodes={nodes} locked={this.state.post.locked} moderators={this.state.moderators} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -216,6 +216,11 @@ export class Post extends Component<any, PostState> {
|
||||||
let found = this.state.comments.find(c => c.id == res.comment.id);
|
let found = this.state.comments.find(c => c.id == res.comment.id);
|
||||||
found.content = res.comment.content;
|
found.content = res.comment.content;
|
||||||
found.updated = res.comment.updated;
|
found.updated = res.comment.updated;
|
||||||
|
found.removed = res.comment.removed;
|
||||||
|
found.upvotes = res.comment.upvotes;
|
||||||
|
found.downvotes = res.comment.downvotes;
|
||||||
|
found.score = res.comment.score;
|
||||||
|
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
else if (op == UserOperation.CreateCommentLike) {
|
else if (op == UserOperation.CreateCommentLike) {
|
||||||
|
@ -249,6 +254,15 @@ export class Post extends Component<any, PostState> {
|
||||||
this.state.community.subscribed = res.community.subscribed;
|
this.state.community.subscribed = res.community.subscribed;
|
||||||
this.state.community.number_of_subscribers = res.community.number_of_subscribers;
|
this.state.community.number_of_subscribers = res.community.number_of_subscribers;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
|
} else if (op == UserOperation.BanFromCommunity) {
|
||||||
|
let res: BanFromCommunityResponse = msg;
|
||||||
|
this.state.comments.filter(c => c.creator_id == res.user.id)
|
||||||
|
.forEach(c => c.banned = res.banned);
|
||||||
|
this.setState(this.state);
|
||||||
|
} else if (op == UserOperation.AddModToCommunity) {
|
||||||
|
let res: AddModToCommunityResponse = msg;
|
||||||
|
this.state.moderators = res.moderators;
|
||||||
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
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('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { Community, CommunityUser, FollowCommunityForm } from '../interfaces';
|
import { Community, CommunityUser, FollowCommunityForm, CommunityForm as CommunityFormI } from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { mdToHtml } from '../utils';
|
import { mdToHtml, getUnixTime } from '../utils';
|
||||||
import { CommunityForm } from './community-form';
|
import { CommunityForm } from './community-form';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
|
@ -12,12 +12,18 @@ interface SidebarProps {
|
||||||
|
|
||||||
interface SidebarState {
|
interface SidebarState {
|
||||||
showEdit: boolean;
|
showEdit: boolean;
|
||||||
|
showRemoveDialog: boolean;
|
||||||
|
removeReason: string;
|
||||||
|
removeExpires: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Sidebar extends Component<SidebarProps, SidebarState> {
|
export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
|
|
||||||
private emptyState: SidebarState = {
|
private emptyState: SidebarState = {
|
||||||
showEdit: false
|
showEdit: false,
|
||||||
|
showRemoveDialog: false,
|
||||||
|
removeReason: null,
|
||||||
|
removeExpires: null
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -42,9 +48,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
let community = this.props.community;
|
let community = this.props.community;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="mb-0">{community.title}</h4>
|
<h4 className="mb-0">{community.title}
|
||||||
|
{community.removed &&
|
||||||
|
<small className="ml-2 text-muted font-italic">removed</small>
|
||||||
|
}
|
||||||
|
</h4>
|
||||||
<Link className="text-muted" to={`/community/${community.id}`}>/f/{community.name}</Link>
|
<Link className="text-muted" to={`/community/${community.id}`}>/f/{community.name}</Link>
|
||||||
{this.amMod &&
|
{community.am_mod &&
|
||||||
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
|
||||||
|
@ -54,13 +64,41 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
{/* <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> */}
|
{/* <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> */}
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
<li className="list-inline-item">
|
||||||
|
{!this.props.community.removed ?
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
|
||||||
|
<span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
<ul class="list-inline">
|
{this.state.showRemoveDialog &&
|
||||||
|
<form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-form-label">Reason</label>
|
||||||
|
<input type="text" class="form-control mr-2" placeholder="Optional" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} />
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-form-label">Expires</label>
|
||||||
|
<input type="date" class="form-control mr-2" placeholder="Expires" value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} />
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<button type="submit" class="btn btn-secondary">Remove Community</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
<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
|
||||||
|
@ -72,13 +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>
|
|
||||||
{this.props.moderators.map(mod =>
|
|
||||||
<Link to={`/user/${mod.user_id}`}>{mod.user_name}</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -119,13 +153,51 @@ 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 {
|
||||||
console.log(this.props.moderators);
|
// return UserService.Instance.loggedIn &&
|
||||||
console.log(this.props);
|
// this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id);
|
||||||
return UserService.Instance.loggedIn &&
|
// }
|
||||||
this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id);
|
|
||||||
|
handleDeleteClick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleModRemoveShow(i: Sidebar) {
|
||||||
|
i.state.showRemoveDialog = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModRemoveReasonChange(i: Sidebar, event: any) {
|
||||||
|
i.state.removeReason = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModRemoveExpiresChange(i: Sidebar, event: any) {
|
||||||
|
console.log(event.target.value);
|
||||||
|
i.state.removeExpires = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModRemoveSubmit(i: Sidebar) {
|
||||||
|
event.preventDefault();
|
||||||
|
let deleteForm: CommunityFormI = {
|
||||||
|
name: i.props.community.name,
|
||||||
|
title: i.props.community.title,
|
||||||
|
category_id: i.props.community.category_id,
|
||||||
|
edit_id: i.props.community.id,
|
||||||
|
removed: !i.props.community.removed,
|
||||||
|
reason: i.state.removeReason,
|
||||||
|
expires: getUnixTime(i.state.removeExpires),
|
||||||
|
auth: null,
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.editCommunity(deleteForm);
|
||||||
|
|
||||||
|
i.state.showRemoveDialog = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,24 +125,27 @@ export class User extends Component<any, UserState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
overview() {
|
overview() {
|
||||||
let combined: Array<any> = [];
|
let combined: Array<{type_: string, data: Comment | Post}> = [];
|
||||||
combined.push(...this.state.comments);
|
let comments = this.state.comments.map(e => {return {type_: "comments", data: e}});
|
||||||
combined.push(...this.state.posts);
|
let posts = this.state.posts.map(e => {return {type_: "posts", data: e}});
|
||||||
|
|
||||||
|
combined.push(...comments);
|
||||||
|
combined.push(...posts);
|
||||||
|
|
||||||
// Sort it
|
// Sort it
|
||||||
if (this.state.sort == SortType.New) {
|
if (this.state.sort == SortType.New) {
|
||||||
combined.sort((a, b) => b.published.localeCompare(a.published));
|
combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
|
||||||
} else {
|
} else {
|
||||||
combined.sort((a, b) => b.score - a.score);
|
combined.sort((a, b) => b.data.score - a.data.score);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{combined.map(i =>
|
{combined.map(i =>
|
||||||
<div>
|
<div>
|
||||||
{i.community_id
|
{i.type_ == "posts"
|
||||||
? <PostListing post={i} showCommunity viewOnly />
|
? <PostListing post={i.data as Post} showCommunity viewOnly />
|
||||||
: <CommentNodes nodes={[{comment: i}]} noIndent viewOnly />
|
: <CommentNodes nodes={[{comment: i.data as Comment}]} noIndent viewOnly />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
export const endpoint = `${window.location.hostname}:8536`;
|
let host = `${window.location.hostname}`;
|
||||||
export let wsUri = (window.location.protocol=='https:') ? 'wss://' : 'ws://' + endpoint + '/service/ws';
|
let port = `${window.location.port == "4444" ? '8536' : window.location.port}`;
|
||||||
|
let endpoint = `${host}:${port}`;
|
||||||
|
export let wsUri = `${(window.location.protocol=='https:') ? 'wss://' : 'ws://'}${endpoint}/service/ws`;
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -10,6 +11,8 @@ import { Post } from './components/post';
|
||||||
import { Community } from './components/community';
|
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 { Setup } from './components/setup';
|
||||||
import { Symbols } from './components/symbols';
|
import { Symbols } from './components/symbols';
|
||||||
|
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
@ -42,9 +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={`/setup`} component={Setup} />
|
||||||
</Switch>
|
</Switch>
|
||||||
<Symbols />
|
<Symbols />
|
||||||
</div>
|
</div>
|
||||||
|
<Footer />
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
export enum UserOperation {
|
export enum UserOperation {
|
||||||
Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails
|
Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CommentSortType {
|
||||||
|
Hot, Top, New
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ListingType {
|
||||||
|
All, Subscribed, Community
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SortType {
|
||||||
|
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
|
@ -31,6 +43,8 @@ export interface CommunityUser {
|
||||||
export interface Community {
|
export interface Community {
|
||||||
user_id?: number;
|
user_id?: number;
|
||||||
subscribed?: boolean;
|
subscribed?: boolean;
|
||||||
|
am_mod?: boolean;
|
||||||
|
removed?: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -46,40 +60,12 @@ export interface Community {
|
||||||
updated?: string;
|
updated?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommunityForm {
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
description?: string,
|
|
||||||
category_id: number,
|
|
||||||
edit_id?: number;
|
|
||||||
auth?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetCommunityResponse {
|
|
||||||
op: string;
|
|
||||||
community: Community;
|
|
||||||
moderators: Array<CommunityUser>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface CommunityResponse {
|
|
||||||
op: string;
|
|
||||||
community: Community;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ListCommunitiesResponse {
|
|
||||||
op: string;
|
|
||||||
communities: Array<Community>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ListCategoriesResponse {
|
|
||||||
op: string;
|
|
||||||
categories: Array<Category>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Post {
|
export interface Post {
|
||||||
user_id?: number;
|
user_id?: number;
|
||||||
my_vote?: number;
|
my_vote?: number;
|
||||||
|
am_mod?: boolean;
|
||||||
|
removed?: boolean;
|
||||||
|
locked?: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@ -97,6 +83,279 @@ export interface Post {
|
||||||
updated?: string;
|
updated?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Comment {
|
||||||
|
id: number;
|
||||||
|
content: string;
|
||||||
|
creator_id: number;
|
||||||
|
creator_name: string;
|
||||||
|
post_id: number,
|
||||||
|
community_id: number,
|
||||||
|
parent_id?: number;
|
||||||
|
published: string;
|
||||||
|
updated?: string;
|
||||||
|
score: number;
|
||||||
|
upvotes: number;
|
||||||
|
downvotes: number;
|
||||||
|
my_vote?: number;
|
||||||
|
am_mod?: boolean;
|
||||||
|
removed?: boolean;
|
||||||
|
banned?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Site {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
creator_id: number;
|
||||||
|
published: string;
|
||||||
|
updated?: string;
|
||||||
|
creator_name: string;
|
||||||
|
number_of_users: number;
|
||||||
|
number_of_posts: number;
|
||||||
|
number_of_comments: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowCommunityForm {
|
||||||
|
community_id: number;
|
||||||
|
follow: boolean;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetFollowedCommunitiesResponse {
|
||||||
|
op: string;
|
||||||
|
communities: Array<CommunityUser>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetUserDetailsForm {
|
||||||
|
user_id: number;
|
||||||
|
sort: string; // TODO figure this one out
|
||||||
|
limit: number;
|
||||||
|
community_id?: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserDetailsResponse {
|
||||||
|
op: string;
|
||||||
|
user: UserView;
|
||||||
|
follows: Array<CommunityUser>;
|
||||||
|
moderates: Array<CommunityUser>;
|
||||||
|
comments: Array<Comment>;
|
||||||
|
posts: Array<Post>;
|
||||||
|
saved?: Array<Post>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BanFromCommunityForm {
|
||||||
|
community_id: number;
|
||||||
|
user_id: number;
|
||||||
|
ban: boolean;
|
||||||
|
reason?: string,
|
||||||
|
expires?: number,
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BanFromCommunityResponse {
|
||||||
|
op: string;
|
||||||
|
user: UserView,
|
||||||
|
banned: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddModToCommunityForm {
|
||||||
|
community_id: number;
|
||||||
|
user_id: number;
|
||||||
|
added: boolean;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddModToCommunityResponse {
|
||||||
|
op: string;
|
||||||
|
moderators: Array<CommunityUser>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetModlogForm {
|
||||||
|
mod_user_id?: number;
|
||||||
|
community_id?: number;
|
||||||
|
limit?: number;
|
||||||
|
page?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetModlogResponse {
|
||||||
|
op: string;
|
||||||
|
removed_posts: Array<ModRemovePost>,
|
||||||
|
locked_posts: Array<ModLockPost>,
|
||||||
|
removed_comments: Array<ModRemoveComment>,
|
||||||
|
removed_communities: Array<ModRemoveCommunity>,
|
||||||
|
banned_from_community: Array<ModBanFromCommunity>,
|
||||||
|
banned: Array<ModBan>,
|
||||||
|
added_to_community: Array<ModAddCommunity>,
|
||||||
|
added: Array<ModAdd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModRemovePost {
|
||||||
|
id: number;
|
||||||
|
mod_user_id: number;
|
||||||
|
post_id: number;
|
||||||
|
reason?: string;
|
||||||
|
removed?: boolean;
|
||||||
|
when_: string
|
||||||
|
mod_user_name: string;
|
||||||
|
post_name: string;
|
||||||
|
community_id: number;
|
||||||
|
community_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModLockPost {
|
||||||
|
id: number,
|
||||||
|
mod_user_id: number,
|
||||||
|
post_id: number,
|
||||||
|
locked?: boolean,
|
||||||
|
when_: string,
|
||||||
|
mod_user_name: string,
|
||||||
|
post_name: string,
|
||||||
|
community_id: number,
|
||||||
|
community_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModRemoveComment {
|
||||||
|
id: number,
|
||||||
|
mod_user_id: number,
|
||||||
|
comment_id: number,
|
||||||
|
reason?: string,
|
||||||
|
removed?: boolean,
|
||||||
|
when_: string,
|
||||||
|
mod_user_name: string,
|
||||||
|
comment_user_id: number,
|
||||||
|
comment_user_name: string,
|
||||||
|
comment_content: string,
|
||||||
|
post_id: number,
|
||||||
|
post_name: string,
|
||||||
|
community_id: number,
|
||||||
|
community_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModRemoveCommunity {
|
||||||
|
id: number,
|
||||||
|
mod_user_id: number,
|
||||||
|
community_id: number,
|
||||||
|
reason?: string,
|
||||||
|
removed?: boolean,
|
||||||
|
expires?: number,
|
||||||
|
when_: string,
|
||||||
|
mod_user_name: string,
|
||||||
|
community_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModBanFromCommunity {
|
||||||
|
id: number,
|
||||||
|
mod_user_id: number,
|
||||||
|
other_user_id: number,
|
||||||
|
community_id: number,
|
||||||
|
reason?: string,
|
||||||
|
banned?: boolean,
|
||||||
|
expires?: number,
|
||||||
|
when_: string,
|
||||||
|
mod_user_name: string,
|
||||||
|
other_user_name: string,
|
||||||
|
community_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModBan {
|
||||||
|
id: number,
|
||||||
|
mod_user_id: number,
|
||||||
|
other_user_id: number,
|
||||||
|
reason?: string,
|
||||||
|
banned?: boolean,
|
||||||
|
expires?: number,
|
||||||
|
when_: string,
|
||||||
|
mod_user_name: string,
|
||||||
|
other_user_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModAddCommunity {
|
||||||
|
id: number,
|
||||||
|
mod_user_id: number,
|
||||||
|
other_user_id: number,
|
||||||
|
community_id: number,
|
||||||
|
removed?: boolean,
|
||||||
|
when_: string,
|
||||||
|
mod_user_name: string,
|
||||||
|
other_user_name: string,
|
||||||
|
community_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModAdd {
|
||||||
|
id: number,
|
||||||
|
mod_user_id: number,
|
||||||
|
other_user_id: number,
|
||||||
|
removed?: boolean,
|
||||||
|
when_: string,
|
||||||
|
mod_user_name: string,
|
||||||
|
other_user_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginForm {
|
||||||
|
username_or_email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterForm {
|
||||||
|
username: string;
|
||||||
|
email?: string;
|
||||||
|
password: string;
|
||||||
|
password_verify: string;
|
||||||
|
admin: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
op: string;
|
||||||
|
jwt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface CommunityForm {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
description?: string,
|
||||||
|
category_id: number,
|
||||||
|
edit_id?: number;
|
||||||
|
removed?: boolean;
|
||||||
|
reason?: string;
|
||||||
|
expires?: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetCommunityResponse {
|
||||||
|
op: string;
|
||||||
|
community: Community;
|
||||||
|
moderators: Array<CommunityUser>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface CommunityResponse {
|
||||||
|
op: string;
|
||||||
|
community: Community;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListCommunitiesForm {
|
||||||
|
sort: string;
|
||||||
|
limit?: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListCommunitiesResponse {
|
||||||
|
op: string;
|
||||||
|
communities: Array<Community>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListCategoriesResponse {
|
||||||
|
op: string;
|
||||||
|
categories: Array<Category>;
|
||||||
|
}
|
||||||
export interface PostForm {
|
export interface PostForm {
|
||||||
name: string;
|
name: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@ -104,6 +363,10 @@ export interface PostForm {
|
||||||
community_id: number;
|
community_id: number;
|
||||||
updated?: number;
|
updated?: number;
|
||||||
edit_id?: number;
|
edit_id?: number;
|
||||||
|
creator_id: number;
|
||||||
|
removed?: boolean;
|
||||||
|
reason?: string;
|
||||||
|
locked?: boolean;
|
||||||
auth: string;
|
auth: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,26 +383,14 @@ export interface PostResponse {
|
||||||
post: Post;
|
post: Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Comment {
|
|
||||||
id: number;
|
|
||||||
content: string;
|
|
||||||
creator_id: number;
|
|
||||||
creator_name: string;
|
|
||||||
post_id: number,
|
|
||||||
parent_id?: number;
|
|
||||||
published: string;
|
|
||||||
updated?: string;
|
|
||||||
score: number;
|
|
||||||
upvotes: number;
|
|
||||||
downvotes: number;
|
|
||||||
my_vote?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommentForm {
|
export interface CommentForm {
|
||||||
content: string;
|
content: string;
|
||||||
post_id: number;
|
post_id: number;
|
||||||
parent_id?: number;
|
parent_id?: number;
|
||||||
edit_id?: number;
|
edit_id?: number;
|
||||||
|
creator_id: number;
|
||||||
|
removed?: boolean;
|
||||||
|
reason?: string;
|
||||||
auth: string;
|
auth: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,70 +435,49 @@ export interface CreatePostLikeResponse {
|
||||||
post: Post;
|
post: Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Category {
|
export interface SiteForm {
|
||||||
id: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
}
|
description?: string,
|
||||||
|
removed?: boolean;
|
||||||
export interface FollowCommunityForm {
|
reason?: string;
|
||||||
community_id: number;
|
expires?: number;
|
||||||
follow: boolean;
|
|
||||||
auth?: string;
|
auth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetFollowedCommunitiesResponse {
|
export interface GetSiteResponse {
|
||||||
op: string;
|
op: string;
|
||||||
communities: Array<CommunityUser>;
|
site: Site;
|
||||||
|
admins: Array<UserView>;
|
||||||
|
banned: Array<UserView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetUserDetailsForm {
|
|
||||||
|
export interface SiteResponse {
|
||||||
|
op: string;
|
||||||
|
site: Site;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BanUserForm {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
sort: string; // TODO figure this one out
|
ban: boolean;
|
||||||
limit: number;
|
reason?: string,
|
||||||
community_id?: number;
|
expires?: number,
|
||||||
auth?: string;
|
auth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BanUserResponse {
|
||||||
|
|
||||||
export interface UserDetailsResponse {
|
|
||||||
op: string;
|
op: string;
|
||||||
user: UserView;
|
user: UserView,
|
||||||
follows: Array<CommunityUser>;
|
banned: boolean,
|
||||||
moderates: Array<CommunityUser>;
|
|
||||||
comments: Array<Comment>;
|
|
||||||
posts: Array<Post>;
|
|
||||||
saved?: Array<Post>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AddAdminForm {
|
||||||
export interface LoginForm {
|
user_id: number;
|
||||||
username_or_email: string;
|
added: boolean;
|
||||||
password: string;
|
auth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterForm {
|
export interface AddAdminResponse {
|
||||||
username: string;
|
|
||||||
email?: string;
|
|
||||||
password: string;
|
|
||||||
password_verify: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface LoginResponse {
|
|
||||||
op: string;
|
op: string;
|
||||||
jwt: string;
|
admins: Array<UserView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CommentSortType {
|
|
||||||
Hot, Top, New
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ListingType {
|
|
||||||
All, Subscribed, Community
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SortType {
|
|
||||||
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,11 @@ body {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-control:disabled {
|
||||||
|
background-color: var(--secondary);
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
.custom-select {
|
.custom-select {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
|
@ -66,3 +71,12 @@ body {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(359deg); }
|
100% { transform: rotate(359deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-bg {
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,10 @@ export class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public logout() {
|
public logout() {
|
||||||
this.user = null;
|
this.user = undefined;
|
||||||
Cookies.remove("jwt");
|
Cookies.remove("jwt");
|
||||||
console.log("Logged out.");
|
console.log("Logged out.");
|
||||||
this.sub.next(null);
|
this.sub.next(undefined);
|
||||||
}
|
|
||||||
|
|
||||||
public get loggedIn(): boolean {
|
|
||||||
return this.user !== undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get auth(): string {
|
public get auth(): string {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { wsUri } from '../env';
|
import { wsUri } from '../env';
|
||||||
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm } from '../interfaces';
|
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, 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,11 +8,16 @@ 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();
|
||||||
|
@ -47,9 +52,9 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm));
|
this.subject.next(this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm));
|
||||||
}
|
}
|
||||||
|
|
||||||
public listCommunities() {
|
public listCommunities(form: ListCommunitiesForm) {
|
||||||
let data = {auth: UserService.Instance.auth };
|
this.setAuth(form, false);
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, data));
|
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, form));
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFollowedCommunities() {
|
public getFollowedCommunities() {
|
||||||
|
@ -106,11 +111,38 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.EditPost, postForm));
|
this.subject.next(this.wsSendWrapper(UserOperation.EditPost, postForm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public banFromCommunity(form: BanFromCommunityForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.subject.next(this.wsSendWrapper(UserOperation.BanFromCommunity, form));
|
||||||
|
}
|
||||||
|
|
||||||
|
public addModToCommunity(form: AddModToCommunityForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.subject.next(this.wsSendWrapper(UserOperation.AddModToCommunity, form));
|
||||||
|
}
|
||||||
|
|
||||||
public getUserDetails(form: GetUserDetailsForm) {
|
public getUserDetails(form: GetUserDetailsForm) {
|
||||||
this.setAuth(form, false);
|
this.setAuth(form, false);
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.GetUserDetails, form));
|
this.subject.next(this.wsSendWrapper(UserOperation.GetUserDetails, form));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getModlog(form: GetModlogForm) {
|
||||||
|
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);
|
||||||
|
@ -124,7 +156,6 @@ export class WebSocketService {
|
||||||
throw "Not logged in";
|
throw "Not logged in";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onbeforeunload = (() => {
|
window.onbeforeunload = (() => {
|
||||||
|
|
|
@ -31,3 +31,11 @@ export function hotRank(comment: Comment): number {
|
||||||
export function mdToHtml(text: string) {
|
export function mdToHtml(text: string) {
|
||||||
return {__html: md.render(text)};
|
return {__html: md.render(text)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUnixTime(text: string): number {
|
||||||
|
return text ? new Date(text).getTime()/1000 : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addTypeInfo<T>(arr: Array<T>, name: string): Array<{type_: string, data: T}> {
|
||||||
|
return arr.map(e => {return {type_: name, data: e}});
|
||||||
|
}
|
||||||
|
|
1
ui/src/version.ts
Normal file
1
ui/src/version.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export let version: string = "v0.0.2-13-g1bf0dfd";
|
Reference in a new issue