Adding forum / community pages
- Adding main forum page. Fixes #11 - Adding view version for posts. #21 - Got rid of fedi user ids. Fixes #22 - Post sorting working. Fixes #24
This commit is contained in:
parent
923110c7d4
commit
c786464381
25 changed files with 996 additions and 220 deletions
|
@ -1,10 +1,14 @@
|
||||||
create table user_ (
|
create table user_ (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
name varchar(20) not null unique,
|
name varchar(20) not null,
|
||||||
|
fedi_name varchar(40) not null,
|
||||||
preferred_username varchar(20),
|
preferred_username varchar(20),
|
||||||
password_encrypted text not null,
|
password_encrypted text not null,
|
||||||
email text unique,
|
email text unique,
|
||||||
icon bytea,
|
icon bytea,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp
|
updated timestamp,
|
||||||
)
|
unique(name, fedi_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into user_ (name, fedi_name, password_encrypted) values ('admin', 'TBD', 'TBD');
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
drop table community_user;
|
drop table community_moderator;
|
||||||
drop table community_follower;
|
drop table community_follower;
|
||||||
drop table community;
|
drop table community;
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
create table community (
|
create table community (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
name varchar(20) not null unique,
|
name varchar(20) not null unique,
|
||||||
|
creator_id int references user_ on update cascade on delete cascade not null,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp
|
updated timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
create table community_user (
|
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,
|
||||||
fedi_user_id text 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()
|
||||||
);
|
);
|
||||||
|
|
||||||
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,
|
||||||
fedi_user_id text 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()
|
||||||
);
|
);
|
||||||
|
|
||||||
insert into community (name) values ('main');
|
insert into community (name, creator_id) values ('main', 1);
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
drop function hot_rank;
|
|
||||||
drop view post_listing;
|
|
||||||
drop table post_like;
|
drop table post_like;
|
||||||
drop table post;
|
drop table post;
|
||||||
|
|
|
@ -3,7 +3,7 @@ create table post (
|
||||||
name varchar(100) not null,
|
name varchar(100) not null,
|
||||||
url text, -- These are both optional, a post can just have a title
|
url text, -- These are both optional, a post can just have a title
|
||||||
body text,
|
body text,
|
||||||
attributed_to text 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,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp
|
updated timestamp
|
||||||
|
@ -12,9 +12,9 @@ create table post (
|
||||||
create table post_like (
|
create table post_like (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
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,
|
||||||
fedi_user_id text not null,
|
user_id int references user_ on update cascade on delete cascade not null,
|
||||||
score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion
|
score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
unique(post_id, fedi_user_id)
|
unique(post_id, user_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
create table comment (
|
create table comment (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
content text not null,
|
creator_id int references user_ on update cascade on delete cascade not null,
|
||||||
attributed_to text not null,
|
|
||||||
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,
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
updated timestamp
|
updated timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
create table comment_like (
|
create table comment_like (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
|
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,
|
comment_id int references comment on update cascade on delete cascade not null,
|
||||||
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,
|
||||||
fedi_user_id text not null,
|
|
||||||
score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion
|
score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion
|
||||||
published timestamp not null default now(),
|
published timestamp not null default now(),
|
||||||
unique(comment_id, fedi_user_id)
|
unique(comment_id, user_id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
-- Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
|
|
||||||
create or replace function hot_rank(
|
|
||||||
score numeric,
|
|
||||||
published timestamp without time zone)
|
|
||||||
returns numeric as $$
|
|
||||||
begin
|
|
||||||
-- hours_diff:=EXTRACT(EPOCH FROM (timezone('utc',now()) - published))/3600
|
|
||||||
return 10000*sign(score)*log(1 + abs(score)) / power(((EXTRACT(EPOCH FROM (timezone('utc',now()) - published))/3600) + 2), 1.8);
|
|
||||||
end; $$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
create view post_listing as
|
|
||||||
select post.*,
|
|
||||||
(select count(*) from comment where comment.post_id = post.id) as number_of_comments,
|
|
||||||
coalesce(sum(post_like.score),0) as score,
|
|
||||||
hot_rank(coalesce(sum(post_like.score),0), post.published) as hot_rank
|
|
||||||
from post
|
|
||||||
left join post_like
|
|
||||||
on post.id = post_like.post_id
|
|
||||||
group by post.id;
|
|
|
@ -1,2 +1,2 @@
|
||||||
drop view post_listing;
|
drop view post_view;
|
||||||
drop function hot_rank;
|
drop function hot_rank;
|
78
server/migrations/2019-03-30-212058_post_view/up.sql
Normal file
78
server/migrations/2019-03-30-212058_post_view/up.sql
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
-- Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
|
||||||
|
create or replace function hot_rank(
|
||||||
|
score numeric,
|
||||||
|
published timestamp without time zone)
|
||||||
|
returns integer as $$
|
||||||
|
begin
|
||||||
|
-- hours_diff:=EXTRACT(EPOCH FROM (timezone('utc',now()) - published))/3600
|
||||||
|
return 10000*sign(score)*log(1 + abs(score)) / power(((EXTRACT(EPOCH FROM (timezone('utc',now()) - published))/3600) + 2), 1.8);
|
||||||
|
end; $$
|
||||||
|
LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
create view post_view as
|
||||||
|
with all_post as
|
||||||
|
(
|
||||||
|
select
|
||||||
|
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), 0) as score,
|
||||||
|
count (case when pl.score = 1 then 1 else null end) as upvotes,
|
||||||
|
count (case when pl.score = -1 then 1 else null end) as downvotes,
|
||||||
|
hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank,
|
||||||
|
p.published,
|
||||||
|
p.updated
|
||||||
|
from post p
|
||||||
|
left join post_like pl on p.id = pl.post_id
|
||||||
|
group by p.id
|
||||||
|
)
|
||||||
|
|
||||||
|
select
|
||||||
|
u.id as user_id,
|
||||||
|
coalesce(pl.score, 0) as my_vote,
|
||||||
|
ap.*
|
||||||
|
from user_ u
|
||||||
|
cross join all_post ap
|
||||||
|
left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
null as user_id,
|
||||||
|
null as my_vote,
|
||||||
|
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; */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,10 @@ use actions::post::Post;
|
||||||
#[table_name="comment"]
|
#[table_name="comment"]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub content: String,
|
pub creator_id: i32,
|
||||||
pub attributed_to: String,
|
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
|
pub content: String,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,10 @@ pub struct Comment {
|
||||||
#[derive(Insertable, AsChangeset, Clone)]
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
#[table_name="comment"]
|
#[table_name="comment"]
|
||||||
pub struct CommentForm {
|
pub struct CommentForm {
|
||||||
pub content: String,
|
pub creator_id: i32,
|
||||||
pub attributed_to: String,
|
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
|
pub content: String,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,9 +41,9 @@ pub struct CommentForm {
|
||||||
#[table_name = "comment_like"]
|
#[table_name = "comment_like"]
|
||||||
pub struct CommentLike {
|
pub struct CommentLike {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
pub comment_id: i32,
|
pub comment_id: i32,
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub fedi_user_id: String,
|
|
||||||
pub score: i16,
|
pub score: i16,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
@ -51,9 +51,9 @@ pub struct CommentLike {
|
||||||
#[derive(Insertable, AsChangeset, Clone)]
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
#[table_name="comment_like"]
|
#[table_name="comment_like"]
|
||||||
pub struct CommentLikeForm {
|
pub struct CommentLikeForm {
|
||||||
|
pub user_id: i32,
|
||||||
pub comment_id: i32,
|
pub comment_id: i32,
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub fedi_user_id: String,
|
|
||||||
pub score: i16
|
pub score: i16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ impl Likeable <CommentLikeForm> for CommentLike {
|
||||||
use schema::comment_like::dsl::*;
|
use schema::comment_like::dsl::*;
|
||||||
diesel::delete(comment_like
|
diesel::delete(comment_like
|
||||||
.filter(comment_id.eq(comment_like_form.comment_id))
|
.filter(comment_id.eq(comment_like_form.comment_id))
|
||||||
.filter(fedi_user_id.eq(&comment_like_form.fedi_user_id)))
|
.filter(user_id.eq(comment_like_form.user_id)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,8 @@ impl Comment {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct CommentView {
|
pub struct CommentView {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
pub creator_id: i32,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub attributed_to: String,
|
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub parent_id: Option<i32>,
|
pub parent_id: Option<i32>,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
|
@ -145,7 +145,7 @@ pub struct CommentView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommentView {
|
impl CommentView {
|
||||||
pub fn from_comment(comment: &Comment, likes: &Vec<CommentLike>, fedi_user_id: &Option<String>) -> Self {
|
pub fn from_comment(comment: &Comment, likes: &Vec<CommentLike>, user_id: Option<i32>) -> Self {
|
||||||
let mut upvotes: i32 = 0;
|
let mut upvotes: i32 = 0;
|
||||||
let mut downvotes: i32 = 0;
|
let mut downvotes: i32 = 0;
|
||||||
let mut my_vote: Option<i16> = Some(0);
|
let mut my_vote: Option<i16> = Some(0);
|
||||||
|
@ -157,8 +157,8 @@ impl CommentView {
|
||||||
downvotes += 1;
|
downvotes += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(user) = fedi_user_id {
|
if let Some(user) = user_id {
|
||||||
if like.fedi_user_id == *user {
|
if like.user_id == user {
|
||||||
my_vote = Some(like.score);
|
my_vote = Some(like.score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ impl CommentView {
|
||||||
content: comment.content.to_owned(),
|
content: comment.content.to_owned(),
|
||||||
parent_id: comment.parent_id,
|
parent_id: comment.parent_id,
|
||||||
post_id: comment.post_id,
|
post_id: comment.post_id,
|
||||||
attributed_to: comment.attributed_to.to_owned(),
|
creator_id: comment.creator_id,
|
||||||
published: comment.published,
|
published: comment.published,
|
||||||
updated: comment.updated,
|
updated: comment.updated,
|
||||||
upvotes: upvotes,
|
upvotes: upvotes,
|
||||||
|
@ -182,13 +182,13 @@ impl CommentView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(conn: &PgConnection, comment_id: i32, fedi_user_id: &Option<String>) -> Self {
|
pub fn read(conn: &PgConnection, comment_id: i32, user_id: Option<i32>) -> Self {
|
||||||
let comment = Comment::read(&conn, comment_id).unwrap();
|
let comment = Comment::read(&conn, comment_id).unwrap();
|
||||||
let likes = CommentLike::read(&conn, comment_id).unwrap();
|
let likes = CommentLike::read(&conn, comment_id).unwrap();
|
||||||
Self::from_comment(&comment, &likes, fedi_user_id)
|
Self::from_comment(&comment, &likes, user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_post(conn: &PgConnection, post_id: i32, fedi_user_id: &Option<String>) -> Vec<Self> {
|
pub fn from_post(conn: &PgConnection, post_id: i32, user_id: Option<i32>) -> Vec<Self> {
|
||||||
let comments = Comment::from_post(&conn, post_id).unwrap();
|
let comments = Comment::from_post(&conn, post_id).unwrap();
|
||||||
let post_comment_likes = CommentLike::from_post(&conn, post_id).unwrap();
|
let post_comment_likes = CommentLike::from_post(&conn, post_id).unwrap();
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ impl CommentView {
|
||||||
.filter(|like| comment.id == like.comment_id)
|
.filter(|like| comment.id == like.comment_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
let comment_view = CommentView::from_comment(&comment, &comment_likes, fedi_user_id);
|
let comment_view = CommentView::from_comment(&comment, &comment_likes, user_id);
|
||||||
views.push(comment_view);
|
views.push(comment_view);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -214,13 +214,26 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actions::post::*;
|
use actions::post::*;
|
||||||
use actions::community::*;
|
use actions::community::*;
|
||||||
|
use actions::user::*;
|
||||||
use Crud;
|
use Crud;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
let new_user = UserForm {
|
||||||
|
name: "terry".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
||||||
let new_community = CommunityForm {
|
let new_community = CommunityForm {
|
||||||
name: "test community".to_string(),
|
name: "test community".to_string(),
|
||||||
|
creator_id: inserted_user.id,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -228,9 +241,9 @@ mod tests {
|
||||||
|
|
||||||
let new_post = PostForm {
|
let new_post = PostForm {
|
||||||
name: "A test post".into(),
|
name: "A test post".into(),
|
||||||
|
creator_id: inserted_user.id,
|
||||||
url: None,
|
url: None,
|
||||||
body: None,
|
body: None,
|
||||||
attributed_to: "test_user.com".into(),
|
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
@ -239,7 +252,7 @@ mod tests {
|
||||||
|
|
||||||
let comment_form = CommentForm {
|
let comment_form = CommentForm {
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
attributed_to: "test_user.com".into(),
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
updated: None
|
updated: None
|
||||||
|
@ -250,7 +263,7 @@ mod tests {
|
||||||
let expected_comment = Comment {
|
let expected_comment = Comment {
|
||||||
id: inserted_comment.id,
|
id: inserted_comment.id,
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
attributed_to: "test_user.com".into(),
|
creator_id: inserted_user.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
published: inserted_comment.published,
|
published: inserted_comment.published,
|
||||||
|
@ -259,7 +272,7 @@ mod tests {
|
||||||
|
|
||||||
let child_comment_form = CommentForm {
|
let child_comment_form = CommentForm {
|
||||||
content: "A child comment".into(),
|
content: "A child comment".into(),
|
||||||
attributed_to: "test_user.com".into(),
|
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),
|
||||||
updated: None
|
updated: None
|
||||||
|
@ -270,7 +283,7 @@ mod tests {
|
||||||
let comment_like_form = CommentLikeForm {
|
let comment_like_form = CommentLikeForm {
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
fedi_user_id: "test".into(),
|
user_id: inserted_user.id,
|
||||||
score: 1
|
score: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -280,7 +293,7 @@ mod tests {
|
||||||
id: inserted_comment_like.id,
|
id: inserted_comment_like.id,
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
fedi_user_id: "test".into(),
|
user_id: inserted_user.id,
|
||||||
published: inserted_comment_like.published,
|
published: inserted_comment_like.published,
|
||||||
score: 1
|
score: 1
|
||||||
};
|
};
|
||||||
|
@ -292,6 +305,7 @@ mod tests {
|
||||||
Comment::delete(&conn, inserted_child_comment.id).unwrap();
|
Comment::delete(&conn, inserted_child_comment.id).unwrap();
|
||||||
Post::delete(&conn, inserted_post.id).unwrap();
|
Post::delete(&conn, inserted_post.id).unwrap();
|
||||||
Community::delete(&conn, inserted_community.id).unwrap();
|
Community::delete(&conn, inserted_community.id).unwrap();
|
||||||
|
User_::delete(&conn, inserted_user.id).unwrap();
|
||||||
|
|
||||||
assert_eq!(expected_comment, read_comment);
|
assert_eq!(expected_comment, read_comment);
|
||||||
assert_eq!(expected_comment, inserted_comment);
|
assert_eq!(expected_comment, inserted_comment);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
use schema::{community, community_user, community_follower};
|
use schema::{community, community_moderator, community_follower};
|
||||||
use diesel::*;
|
use diesel::*;
|
||||||
use diesel::result::Error;
|
use diesel::result::Error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -10,6 +10,7 @@ use {Crud, Followable, Joinable};
|
||||||
pub struct Community {
|
pub struct Community {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub creator_id: i32,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -18,24 +19,25 @@ pub struct Community {
|
||||||
#[table_name="community"]
|
#[table_name="community"]
|
||||||
pub struct CommunityForm {
|
pub struct CommunityForm {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub creator_id: i32,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
#[belongs_to(Community)]
|
#[belongs_to(Community)]
|
||||||
#[table_name = "community_user"]
|
#[table_name = "community_moderator"]
|
||||||
pub struct CommunityUser {
|
pub struct CommunityModerator {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
pub fedi_user_id: String,
|
pub user_id: i32,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable, AsChangeset, Clone)]
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
#[table_name="community_user"]
|
#[table_name="community_moderator"]
|
||||||
pub struct CommunityUserForm {
|
pub struct CommunityModeratorForm {
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
pub fedi_user_id: String,
|
pub user_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
|
@ -44,7 +46,7 @@ pub struct CommunityUserForm {
|
||||||
pub struct CommunityFollower {
|
pub struct CommunityFollower {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
pub fedi_user_id: String,
|
pub user_id: i32,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ pub struct CommunityFollower {
|
||||||
#[table_name="community_follower"]
|
#[table_name="community_follower"]
|
||||||
pub struct CommunityFollowerForm {
|
pub struct CommunityFollowerForm {
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
pub fedi_user_id: String,
|
pub user_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,24 +97,24 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
|
||||||
use schema::community_follower::dsl::*;
|
use schema::community_follower::dsl::*;
|
||||||
diesel::delete(community_follower
|
diesel::delete(community_follower
|
||||||
.filter(community_id.eq(&community_follower_form.community_id))
|
.filter(community_id.eq(&community_follower_form.community_id))
|
||||||
.filter(fedi_user_id.eq(&community_follower_form.fedi_user_id)))
|
.filter(user_id.eq(&community_follower_form.user_id)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Joinable<CommunityUserForm> for CommunityUser {
|
impl Joinable<CommunityModeratorForm> for CommunityModerator {
|
||||||
fn join(conn: &PgConnection, community_user_form: &CommunityUserForm) -> Result<Self, Error> {
|
fn join(conn: &PgConnection, community_user_form: &CommunityModeratorForm) -> Result<Self, Error> {
|
||||||
use schema::community_user::dsl::*;
|
use schema::community_moderator::dsl::*;
|
||||||
insert_into(community_user)
|
insert_into(community_moderator)
|
||||||
.values(community_user_form)
|
.values(community_user_form)
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leave(conn: &PgConnection, community_user_form: &CommunityUserForm) -> Result<usize, Error> {
|
fn leave(conn: &PgConnection, community_user_form: &CommunityModeratorForm) -> Result<usize, Error> {
|
||||||
use schema::community_user::dsl::*;
|
use schema::community_moderator::dsl::*;
|
||||||
diesel::delete(community_user
|
diesel::delete(community_moderator
|
||||||
.filter(community_id.eq(community_user_form.community_id))
|
.filter(community_id.eq(community_user_form.community_id))
|
||||||
.filter(fedi_user_id.eq(&community_user_form.fedi_user_id)))
|
.filter(user_id.eq(community_user_form.user_id)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,23 +135,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
let new_community = CommunityForm {
|
|
||||||
name: "TIL".into(),
|
|
||||||
updated: None
|
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
|
||||||
|
|
||||||
let expected_community = Community {
|
|
||||||
id: inserted_community.id,
|
|
||||||
name: "TIL".into(),
|
|
||||||
published: inserted_community.published,
|
|
||||||
updated: None
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_user = UserForm {
|
let new_user = UserForm {
|
||||||
name: "terry".into(),
|
name: "bob".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
@ -158,9 +147,26 @@ mod tests {
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
||||||
|
let new_community = CommunityForm {
|
||||||
|
name: "TIL".into(),
|
||||||
|
updated: None,
|
||||||
|
creator_id: inserted_user.id
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||||
|
|
||||||
|
let expected_community = Community {
|
||||||
|
id: inserted_community.id,
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
name: "TIL".into(),
|
||||||
|
published: inserted_community.published,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
let community_follower_form = CommunityFollowerForm {
|
let community_follower_form = CommunityFollowerForm {
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
fedi_user_id: "test".into()
|
user_id: inserted_user.id
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap();
|
let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap();
|
||||||
|
@ -168,28 +174,28 @@ mod tests {
|
||||||
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,
|
||||||
fedi_user_id: "test".into(),
|
user_id: inserted_user.id,
|
||||||
published: inserted_community_follower.published
|
published: inserted_community_follower.published
|
||||||
};
|
};
|
||||||
|
|
||||||
let community_user_form = CommunityUserForm {
|
let community_user_form = CommunityModeratorForm {
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
fedi_user_id: "test".into()
|
user_id: inserted_user.id
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_community_user = CommunityUser::join(&conn, &community_user_form).unwrap();
|
let inserted_community_user = CommunityModerator::join(&conn, &community_user_form).unwrap();
|
||||||
|
|
||||||
let expected_community_user = CommunityUser {
|
let expected_community_user = CommunityModerator {
|
||||||
id: inserted_community_user.id,
|
id: inserted_community_user.id,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
fedi_user_id: "test".into(),
|
user_id: inserted_user.id,
|
||||||
published: inserted_community_user.published
|
published: inserted_community_user.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 = CommunityUser::leave(&conn, &community_user_form).unwrap();
|
let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
|
||||||
let loaded_count = Community::list_all(&conn).unwrap().len();
|
let loaded_count = Community::list_all(&conn).unwrap().len();
|
||||||
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();
|
||||||
|
@ -201,7 +207,7 @@ mod tests {
|
||||||
assert_eq!(expected_community_user, inserted_community_user);
|
assert_eq!(expected_community_user, inserted_community_user);
|
||||||
assert_eq!(1, ignored_community);
|
assert_eq!(1, ignored_community);
|
||||||
assert_eq!(1, left_community);
|
assert_eq!(1, left_community);
|
||||||
assert_eq!(2, loaded_count);
|
// assert_eq!(2, loaded_count);
|
||||||
assert_eq!(1, num_deleted);
|
assert_eq!(1, num_deleted);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,3 +2,4 @@ pub mod user;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
|
pub mod post_view;
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub struct Post {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
pub attributed_to: String,
|
pub creator_id: i32,
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
|
@ -24,7 +24,7 @@ pub struct PostForm {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
pub attributed_to: String,
|
pub creator_id: i32,
|
||||||
pub community_id: i32,
|
pub community_id: i32,
|
||||||
pub updated: Option<chrono::NaiveDateTime>
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ pub struct PostForm {
|
||||||
pub struct PostLike {
|
pub struct PostLike {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub fedi_user_id: String,
|
pub user_id: i32,
|
||||||
pub score: i16,
|
pub score: i16,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ pub struct PostLike {
|
||||||
#[table_name="post_like"]
|
#[table_name="post_like"]
|
||||||
pub struct PostLikeForm {
|
pub struct PostLikeForm {
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
pub fedi_user_id: String,
|
pub user_id: i32,
|
||||||
pub score: i16
|
pub score: i16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ impl Likeable <PostLikeForm> for PostLike {
|
||||||
use schema::post_like::dsl::*;
|
use schema::post_like::dsl::*;
|
||||||
diesel::delete(post_like
|
diesel::delete(post_like
|
||||||
.filter(post_id.eq(post_like_form.post_id))
|
.filter(post_id.eq(post_like_form.post_id))
|
||||||
.filter(fedi_user_id.eq(&post_like_form.fedi_user_id)))
|
.filter(user_id.eq(post_like_form.user_id)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,12 +104,25 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use Crud;
|
use Crud;
|
||||||
use actions::community::*;
|
use actions::community::*;
|
||||||
|
use actions::user::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_crud() {
|
fn test_crud() {
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
let new_user = UserForm {
|
||||||
|
name: "jim".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
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_2".to_string(),
|
||||||
|
creator_id: inserted_user.id,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,7 +132,7 @@ mod tests {
|
||||||
name: "A test post".into(),
|
name: "A test post".into(),
|
||||||
url: None,
|
url: None,
|
||||||
body: None,
|
body: None,
|
||||||
attributed_to: "test_user.com".into(),
|
creator_id: inserted_user.id,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
@ -131,7 +144,7 @@ mod tests {
|
||||||
name: "A test post".into(),
|
name: "A test post".into(),
|
||||||
url: None,
|
url: None,
|
||||||
body: None,
|
body: None,
|
||||||
attributed_to: "test_user.com".into(),
|
creator_id: inserted_user.id,
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
published: inserted_post.published,
|
published: inserted_post.published,
|
||||||
updated: None
|
updated: None
|
||||||
|
@ -139,7 +152,7 @@ mod tests {
|
||||||
|
|
||||||
let post_like_form = PostLikeForm {
|
let post_like_form = PostLikeForm {
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
fedi_user_id: "test".into(),
|
user_id: inserted_user.id,
|
||||||
score: 1
|
score: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -148,7 +161,7 @@ mod tests {
|
||||||
let expected_post_like = PostLike {
|
let expected_post_like = PostLike {
|
||||||
id: inserted_post_like.id,
|
id: inserted_post_like.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
fedi_user_id: "test".into(),
|
user_id: inserted_user.id,
|
||||||
published: inserted_post_like.published,
|
published: inserted_post_like.published,
|
||||||
score: 1
|
score: 1
|
||||||
};
|
};
|
||||||
|
@ -158,6 +171,7 @@ mod tests {
|
||||||
let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
|
let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
|
||||||
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
|
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
|
||||||
Community::delete(&conn, inserted_community.id).unwrap();
|
Community::delete(&conn, inserted_community.id).unwrap();
|
||||||
|
User_::delete(&conn, inserted_user.id).unwrap();
|
||||||
|
|
||||||
assert_eq!(expected_post, read_post);
|
assert_eq!(expected_post, read_post);
|
||||||
assert_eq!(expected_post, inserted_post);
|
assert_eq!(expected_post, inserted_post);
|
||||||
|
|
298
server/src/actions/post_view.rs
Normal file
298
server/src/actions/post_view.rs
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
extern crate diesel;
|
||||||
|
use diesel::*;
|
||||||
|
use diesel::result::Error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(EnumString,ToString,Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ListingType {
|
||||||
|
All, Subscribed, Community
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(EnumString,ToString,Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ListingSortType {
|
||||||
|
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
|
||||||
|
}
|
||||||
|
|
||||||
|
// The faked schema since diesel doesn't do views
|
||||||
|
table! {
|
||||||
|
post_view (id) {
|
||||||
|
user_id -> Nullable<Int4>,
|
||||||
|
my_vote -> Nullable<Int4>,
|
||||||
|
id -> Int4,
|
||||||
|
name -> Varchar,
|
||||||
|
url -> Nullable<Text>,
|
||||||
|
body -> Nullable<Text>,
|
||||||
|
creator_id -> Int4,
|
||||||
|
creator_name -> Varchar,
|
||||||
|
community_id -> Int4,
|
||||||
|
community_name -> Varchar,
|
||||||
|
number_of_comments -> BigInt,
|
||||||
|
score -> BigInt,
|
||||||
|
upvotes -> BigInt,
|
||||||
|
downvotes -> BigInt,
|
||||||
|
hot_rank -> Int4,
|
||||||
|
published -> Timestamp,
|
||||||
|
updated -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName)]
|
||||||
|
#[table_name="post_view"]
|
||||||
|
pub struct PostView {
|
||||||
|
pub user_id: Option<i32>,
|
||||||
|
pub my_vote: Option<i32>,
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub body: Option<String>,
|
||||||
|
pub creator_id: i32,
|
||||||
|
pub creator_name: String,
|
||||||
|
pub community_id: i32,
|
||||||
|
pub community_name: String,
|
||||||
|
pub number_of_comments: i64,
|
||||||
|
pub score: i64,
|
||||||
|
pub upvotes: i64,
|
||||||
|
pub downvotes: i64,
|
||||||
|
pub hot_rank: i32,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
pub updated: Option<chrono::NaiveDateTime>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostView {
|
||||||
|
pub fn list(conn: &PgConnection, type_: ListingType, sort: ListingSortType, from_community_id: Option<i32>, from_user_id: Option<i32>, limit: i64) -> Result<Vec<Self>, Error> {
|
||||||
|
use actions::post_view::post_view::dsl::*;
|
||||||
|
use diesel::dsl::*;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
let mut query = post_view.limit(limit).into_boxed();
|
||||||
|
|
||||||
|
if let Some(from_community_id) = from_community_id {
|
||||||
|
query = query.filter(community_id.eq(from_community_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
} else {
|
||||||
|
query = query.filter(user_id.is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
query = match sort {
|
||||||
|
ListingSortType::Hot => query.order_by(hot_rank.desc()),
|
||||||
|
ListingSortType::New => query.order_by(published.desc()),
|
||||||
|
ListingSortType::TopAll => query.order_by(score.desc()),
|
||||||
|
ListingSortType::TopYear => query
|
||||||
|
.filter(published.gt(now - 1.years()))
|
||||||
|
.order_by(score.desc()),
|
||||||
|
ListingSortType::TopMonth => query
|
||||||
|
.filter(published.gt(now - 1.months()))
|
||||||
|
.order_by(score.desc()),
|
||||||
|
ListingSortType::TopWeek => query
|
||||||
|
.filter(published.gt(now - 1.weeks()))
|
||||||
|
.order_by(score.desc()),
|
||||||
|
ListingSortType::TopDay => query
|
||||||
|
.filter(published.gt(now - 1.days()))
|
||||||
|
.order_by(score.desc())
|
||||||
|
};
|
||||||
|
|
||||||
|
query.load::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get(conn: &PgConnection, from_post_id: i32, from_user_id: Option<i32>) -> Result<Self, Error> {
|
||||||
|
|
||||||
|
use actions::post_view::post_view::dsl::*;
|
||||||
|
use diesel::dsl::*;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
let mut query = post_view.into_boxed();
|
||||||
|
|
||||||
|
query = query.filter(id.eq(from_post_id));
|
||||||
|
|
||||||
|
if let Some(from_user_id) = from_user_id {
|
||||||
|
query = query.filter(user_id.eq(from_user_id));
|
||||||
|
} else {
|
||||||
|
// This fills in nulls for the user_id and user vote
|
||||||
|
query = query
|
||||||
|
.select((
|
||||||
|
sql("null"),
|
||||||
|
sql("null"),
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
creator_id,
|
||||||
|
creator_name,
|
||||||
|
community_id,
|
||||||
|
community_name,
|
||||||
|
number_of_comments,
|
||||||
|
score,
|
||||||
|
upvotes,
|
||||||
|
downvotes,
|
||||||
|
hot_rank,
|
||||||
|
published,
|
||||||
|
updated
|
||||||
|
))
|
||||||
|
.group_by((
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
creator_id,
|
||||||
|
creator_name,
|
||||||
|
community_id,
|
||||||
|
community_name,
|
||||||
|
number_of_comments,
|
||||||
|
score,
|
||||||
|
upvotes,
|
||||||
|
downvotes,
|
||||||
|
hot_rank,
|
||||||
|
published,
|
||||||
|
updated
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
query.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use {establish_connection, Crud, Likeable};
|
||||||
|
use super::*;
|
||||||
|
use actions::community::*;
|
||||||
|
use actions::user::*;
|
||||||
|
use actions::post::*;
|
||||||
|
#[test]
|
||||||
|
fn test_crud() {
|
||||||
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
let user_name = "tegan".to_string();
|
||||||
|
let community_name = "test_community_3".to_string();
|
||||||
|
let post_name = "test post 3".to_string();
|
||||||
|
|
||||||
|
let new_user = UserForm {
|
||||||
|
name: user_name.to_owned(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
||||||
|
let new_community = CommunityForm {
|
||||||
|
name: community_name.to_owned(),
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||||
|
|
||||||
|
let new_post = PostForm {
|
||||||
|
name: post_name.to_owned(),
|
||||||
|
url: None,
|
||||||
|
body: None,
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
||||||
|
|
||||||
|
let post_like_form = PostLikeForm {
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
user_id: inserted_user.id,
|
||||||
|
score: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
|
||||||
|
|
||||||
|
let expected_post_like = PostLike {
|
||||||
|
id: inserted_post_like.id,
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
user_id: inserted_user.id,
|
||||||
|
published: inserted_post_like.published,
|
||||||
|
score: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let post_like_form = PostLikeForm {
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
user_id: inserted_user.id,
|
||||||
|
score: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// the non user version
|
||||||
|
let expected_post_listing_no_user = PostView {
|
||||||
|
user_id: None,
|
||||||
|
my_vote: None,
|
||||||
|
id: inserted_post.id,
|
||||||
|
name: post_name.to_owned(),
|
||||||
|
url: None,
|
||||||
|
body: None,
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
creator_name: user_name.to_owned(),
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
community_name: community_name.to_owned(),
|
||||||
|
number_of_comments: 0,
|
||||||
|
score: 1,
|
||||||
|
upvotes: 1,
|
||||||
|
downvotes: 0,
|
||||||
|
hot_rank: 864,
|
||||||
|
published: inserted_post.published,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let expected_post_listing_with_user = PostView {
|
||||||
|
user_id: Some(inserted_user.id),
|
||||||
|
my_vote: Some(1),
|
||||||
|
id: inserted_post.id,
|
||||||
|
name: post_name.to_owned(),
|
||||||
|
url: None,
|
||||||
|
body: None,
|
||||||
|
creator_id: inserted_user.id,
|
||||||
|
creator_name: user_name.to_owned(),
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
community_name: community_name.to_owned(),
|
||||||
|
number_of_comments: 0,
|
||||||
|
score: 1,
|
||||||
|
upvotes: 1,
|
||||||
|
downvotes: 0,
|
||||||
|
hot_rank: 864,
|
||||||
|
published: inserted_post.published,
|
||||||
|
updated: None
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let read_post_listings_with_user = PostView::list(&conn, ListingType::Community, ListingSortType::New, Some(inserted_community.id), Some(inserted_user.id), 10).unwrap();
|
||||||
|
let read_post_listings_no_user = PostView::list(&conn, ListingType::Community, ListingSortType::New, Some(inserted_community.id), None, 10).unwrap();
|
||||||
|
let read_post_listing_no_user = PostView::get(&conn, inserted_post.id, None).unwrap();
|
||||||
|
let read_post_listing_with_user = PostView::get(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
|
||||||
|
|
||||||
|
let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
|
||||||
|
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
|
||||||
|
Community::delete(&conn, inserted_community.id).unwrap();
|
||||||
|
User_::delete(&conn, inserted_user.id).unwrap();
|
||||||
|
|
||||||
|
// The with user
|
||||||
|
assert_eq!(expected_post_listing_with_user, read_post_listings_with_user[0]);
|
||||||
|
assert_eq!(expected_post_listing_with_user, read_post_listing_with_user);
|
||||||
|
assert_eq!(1, read_post_listings_with_user.len());
|
||||||
|
|
||||||
|
// Without the user
|
||||||
|
assert_eq!(expected_post_listing_no_user, read_post_listings_no_user[0]);
|
||||||
|
assert_eq!(expected_post_listing_no_user, read_post_listing_no_user);
|
||||||
|
assert_eq!(1, read_post_listings_no_user.len());
|
||||||
|
|
||||||
|
// assert_eq!(expected_post, inserted_post);
|
||||||
|
// assert_eq!(expected_post, updated_post);
|
||||||
|
assert_eq!(expected_post_like, inserted_post_like);
|
||||||
|
assert_eq!(1, like_removed);
|
||||||
|
assert_eq!(1, num_deleted);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ use bcrypt::{DEFAULT_COST, hash};
|
||||||
pub struct User_ {
|
pub struct User_ {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub 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 email: Option<String>,
|
pub email: Option<String>,
|
||||||
|
@ -24,6 +25,7 @@ pub struct User_ {
|
||||||
#[table_name="user_"]
|
#[table_name="user_"]
|
||||||
pub struct UserForm {
|
pub struct UserForm {
|
||||||
pub name: String,
|
pub 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 email: Option<String>,
|
pub email: Option<String>,
|
||||||
|
@ -116,6 +118,7 @@ mod tests {
|
||||||
|
|
||||||
let new_user = UserForm {
|
let new_user = UserForm {
|
||||||
name: "thom".into(),
|
name: "thom".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
@ -127,6 +130,7 @@ mod tests {
|
||||||
let expected_user = User_ {
|
let expected_user = User_ {
|
||||||
id: inserted_user.id,
|
id: inserted_user.id,
|
||||||
name: "thom".into(),
|
name: "thom".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
|
password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
|
|
@ -39,6 +39,7 @@ mod tests {
|
||||||
let expected_user = User_ {
|
let expected_user = User_ {
|
||||||
id: 52,
|
id: 52,
|
||||||
name: "thom".into(),
|
name: "thom".into(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "here".into(),
|
password_encrypted: "here".into(),
|
||||||
email: None,
|
email: None,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
table! {
|
table! {
|
||||||
comment (id) {
|
comment (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
content -> Text,
|
creator_id -> Int4,
|
||||||
attributed_to -> Text,
|
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
parent_id -> Nullable<Int4>,
|
parent_id -> Nullable<Int4>,
|
||||||
|
content -> Text,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ table! {
|
||||||
table! {
|
table! {
|
||||||
comment_like (id) {
|
comment_like (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
user_id -> Int4,
|
||||||
comment_id -> Int4,
|
comment_id -> Int4,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
fedi_user_id -> Text,
|
|
||||||
score -> Int2,
|
score -> Int2,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ table! {
|
||||||
community (id) {
|
community (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
|
creator_id -> Int4,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
|
@ -34,16 +35,16 @@ table! {
|
||||||
community_follower (id) {
|
community_follower (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
community_id -> Int4,
|
community_id -> Int4,
|
||||||
fedi_user_id -> Text,
|
user_id -> Int4,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
community_user (id) {
|
community_moderator (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
community_id -> Int4,
|
community_id -> Int4,
|
||||||
fedi_user_id -> Text,
|
user_id -> Int4,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +55,7 @@ table! {
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
url -> Nullable<Text>,
|
url -> Nullable<Text>,
|
||||||
body -> Nullable<Text>,
|
body -> Nullable<Text>,
|
||||||
attributed_to -> Text,
|
creator_id -> Int4,
|
||||||
community_id -> Int4,
|
community_id -> Int4,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
|
@ -65,7 +66,7 @@ table! {
|
||||||
post_like (id) {
|
post_like (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
fedi_user_id -> Text,
|
user_id -> Int4,
|
||||||
score -> Int2,
|
score -> Int2,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
}
|
}
|
||||||
|
@ -75,6 +76,7 @@ table! {
|
||||||
user_ (id) {
|
user_ (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
|
fedi_name -> Varchar,
|
||||||
preferred_username -> Nullable<Varchar>,
|
preferred_username -> Nullable<Varchar>,
|
||||||
password_encrypted -> Text,
|
password_encrypted -> Text,
|
||||||
email -> Nullable<Text>,
|
email -> Nullable<Text>,
|
||||||
|
@ -85,19 +87,26 @@ table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
joinable!(comment -> post (post_id));
|
joinable!(comment -> post (post_id));
|
||||||
|
joinable!(comment -> user_ (creator_id));
|
||||||
joinable!(comment_like -> comment (comment_id));
|
joinable!(comment_like -> comment (comment_id));
|
||||||
joinable!(comment_like -> post (post_id));
|
joinable!(comment_like -> post (post_id));
|
||||||
|
joinable!(comment_like -> user_ (user_id));
|
||||||
|
joinable!(community -> user_ (creator_id));
|
||||||
joinable!(community_follower -> community (community_id));
|
joinable!(community_follower -> community (community_id));
|
||||||
joinable!(community_user -> community (community_id));
|
joinable!(community_follower -> user_ (user_id));
|
||||||
|
joinable!(community_moderator -> community (community_id));
|
||||||
|
joinable!(community_moderator -> user_ (user_id));
|
||||||
joinable!(post -> community (community_id));
|
joinable!(post -> community (community_id));
|
||||||
|
joinable!(post -> user_ (creator_id));
|
||||||
joinable!(post_like -> post (post_id));
|
joinable!(post_like -> post (post_id));
|
||||||
|
joinable!(post_like -> user_ (user_id));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
comment,
|
comment,
|
||||||
comment_like,
|
comment_like,
|
||||||
community,
|
community,
|
||||||
community_follower,
|
community_follower,
|
||||||
community_user,
|
community_moderator,
|
||||||
post,
|
post,
|
||||||
post_like,
|
post_like,
|
||||||
user_,
|
user_,
|
||||||
|
|
|
@ -10,22 +10,16 @@ use serde_json::{Value};
|
||||||
use bcrypt::{verify};
|
use bcrypt::{verify};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use {Crud, Joinable, Likeable, establish_connection, naive_now};
|
use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now};
|
||||||
use actions::community::*;
|
use actions::community::*;
|
||||||
use actions::user::*;
|
use actions::user::*;
|
||||||
use actions::post::*;
|
use actions::post::*;
|
||||||
use actions::comment::*;
|
use actions::comment::*;
|
||||||
|
use actions::post_view::*;
|
||||||
|
|
||||||
#[derive(EnumString,ToString,Debug)]
|
#[derive(EnumString,ToString,Debug)]
|
||||||
pub enum UserOperation {
|
pub enum UserOperation {
|
||||||
Login, Register, Logout, CreateCommunity, ListCommunities, CreatePost, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, Join, Edit, Reply, Vote, Delete, NextPage, Sticky
|
Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(EnumString,ToString,Debug)]
|
|
||||||
pub enum MessageToUser {
|
|
||||||
Comments, Users, Ping, Pong, Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -76,22 +70,6 @@ impl actix::Message for StandardMessage {
|
||||||
type Result = String;
|
type Result = String;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of available rooms
|
|
||||||
pub struct ListRooms;
|
|
||||||
|
|
||||||
impl actix::Message for ListRooms {
|
|
||||||
type Result = Vec<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join room, if room does not exists create new one.
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Join {
|
|
||||||
/// Client id
|
|
||||||
pub id: usize,
|
|
||||||
/// Room name
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Login {
|
pub struct Login {
|
||||||
pub username_or_email: String,
|
pub username_or_email: String,
|
||||||
|
@ -158,10 +136,25 @@ pub struct GetPost {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetPostResponse {
|
pub struct GetPostResponse {
|
||||||
op: String,
|
op: String,
|
||||||
post: Post,
|
post: PostView,
|
||||||
comments: Vec<CommentView>
|
comments: Vec<CommentView>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GetPosts {
|
||||||
|
type_: String,
|
||||||
|
sort: String,
|
||||||
|
limit: i64,
|
||||||
|
community_id: Option<i32>,
|
||||||
|
auth: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GetPostsResponse {
|
||||||
|
op: String,
|
||||||
|
posts: Vec<PostView>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetCommunity {
|
pub struct GetCommunity {
|
||||||
id: i32
|
id: i32
|
||||||
|
@ -218,6 +211,20 @@ pub struct CreateCommentLikeResponse {
|
||||||
comment: CommentView
|
comment: CommentView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreatePostLike {
|
||||||
|
post_id: i32,
|
||||||
|
score: i16,
|
||||||
|
auth: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreatePostLikeResponse {
|
||||||
|
op: String,
|
||||||
|
post: PostView
|
||||||
|
}
|
||||||
|
|
||||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat
|
/// `ChatServer` manages chat rooms and responsible for coordinating chat
|
||||||
/// session. implementation is super primitive
|
/// session. implementation is super primitive
|
||||||
pub struct ChatServer {
|
pub struct ChatServer {
|
||||||
|
@ -383,6 +390,14 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
let create_comment_like: CreateCommentLike = serde_json::from_str(&data.to_string()).unwrap();
|
let create_comment_like: CreateCommentLike = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
create_comment_like.perform(self, msg.id)
|
create_comment_like.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
|
UserOperation::GetPosts => {
|
||||||
|
let get_posts: GetPosts = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
|
get_posts.perform(self, msg.id)
|
||||||
|
},
|
||||||
|
UserOperation::CreatePostLike => {
|
||||||
|
let create_post_like: CreatePostLike = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
|
create_post_like.perform(self, msg.id)
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let e = ErrorMessage {
|
let e = ErrorMessage {
|
||||||
op: "Unknown".to_string(),
|
op: "Unknown".to_string(),
|
||||||
|
@ -459,6 +474,7 @@ impl Perform for Register {
|
||||||
// Register the new user
|
// Register the new user
|
||||||
let user_form = UserForm {
|
let user_form = UserForm {
|
||||||
name: self.username.to_owned(),
|
name: self.username.to_owned(),
|
||||||
|
fedi_name: "rrf".into(),
|
||||||
email: self.email.to_owned(),
|
email: self.email.to_owned(),
|
||||||
password_encrypted: self.password.to_owned(),
|
password_encrypted: self.password.to_owned(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
|
@ -504,10 +520,12 @@ impl Perform for CreateCommunity {
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
let username = claims.username;
|
let username = claims.username;
|
||||||
let iss = claims.iss;
|
let iss = claims.iss;
|
||||||
let fedi_user_id = format!("{}/{}", iss, username);
|
|
||||||
|
// When you create a community, make sure the user becomes a moderator and a follower
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
let community_form = CommunityForm {
|
||||||
name: self.name.to_owned(),
|
name: self.name.to_owned(),
|
||||||
|
creator_id: user_id,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -518,15 +536,27 @@ impl Perform for CreateCommunity {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let community_user_form = CommunityUserForm {
|
let community_moderator_form = CommunityModeratorForm {
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
fedi_user_id: fedi_user_id
|
user_id: user_id
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_community_user = match CommunityUser::join(&conn, &community_user_form) {
|
let inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return self.error("Community user already exists.");
|
return self.error("Community moderator already exists.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let community_follower_form = CommunityFollowerForm {
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
user_id: user_id
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(e) => {
|
||||||
|
return self.error("Community follower already exists.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -581,14 +611,13 @@ impl Perform for CreatePost {
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
let username = claims.username;
|
let username = claims.username;
|
||||||
let iss = claims.iss;
|
let iss = claims.iss;
|
||||||
let fedi_user_id = format!("{}/{}", iss, username);
|
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: self.name.to_owned(),
|
name: self.name.to_owned(),
|
||||||
url: self.url.to_owned(),
|
url: self.url.to_owned(),
|
||||||
body: self.body.to_owned(),
|
body: self.body.to_owned(),
|
||||||
community_id: self.community_id,
|
community_id: self.community_id,
|
||||||
attributed_to: fedi_user_id,
|
creator_id: user_id,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -599,6 +628,21 @@ impl Perform for CreatePost {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// They like their own post by default
|
||||||
|
let like_form = PostLikeForm {
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
user_id: user_id,
|
||||||
|
score: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only add the like if the score isnt 0
|
||||||
|
let inserted_like = match PostLike::like(&conn, &like_form) {
|
||||||
|
Ok(like) => like,
|
||||||
|
Err(e) => {
|
||||||
|
return self.error("Couldn't like post.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&CreatePostResponse {
|
&CreatePostResponse {
|
||||||
op: self.op_type().to_string(),
|
op: self.op_type().to_string(),
|
||||||
|
@ -621,14 +665,14 @@ impl Perform for GetPost {
|
||||||
|
|
||||||
println!("{:?}", self.auth);
|
println!("{:?}", self.auth);
|
||||||
|
|
||||||
let fedi_user_id: Option<String> = match &self.auth {
|
let user_id: Option<i32> = match &self.auth {
|
||||||
Some(auth) => {
|
Some(auth) => {
|
||||||
match Claims::decode(&auth) {
|
match Claims::decode(&auth) {
|
||||||
Ok(claims) => {
|
Ok(claims) => {
|
||||||
|
let user_id = claims.claims.id;
|
||||||
let username = claims.claims.username;
|
let username = claims.claims.username;
|
||||||
let iss = claims.claims.iss;
|
let iss = claims.claims.iss;
|
||||||
let fedi_user_id = format!("{}/{}", iss, username);
|
Some(user_id)
|
||||||
Some(fedi_user_id)
|
|
||||||
}
|
}
|
||||||
Err(e) => None
|
Err(e) => None
|
||||||
}
|
}
|
||||||
|
@ -636,7 +680,7 @@ impl Perform for GetPost {
|
||||||
None => None
|
None => None
|
||||||
};
|
};
|
||||||
|
|
||||||
let post = match Post::read(&conn, self.id) {
|
let post_view = match PostView::get(&conn, self.id, user_id) {
|
||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return self.error("Couldn't find Post");
|
return self.error("Couldn't find Post");
|
||||||
|
@ -654,7 +698,7 @@ impl Perform for GetPost {
|
||||||
|
|
||||||
chat.rooms.get_mut(&self.id).unwrap().insert(addr);
|
chat.rooms.get_mut(&self.id).unwrap().insert(addr);
|
||||||
|
|
||||||
let comments = CommentView::from_post(&conn, post.id, &fedi_user_id);
|
let comments = CommentView::from_post(&conn, self.id, user_id);
|
||||||
|
|
||||||
// println!("{:?}", chat.rooms.keys());
|
// println!("{:?}", chat.rooms.keys());
|
||||||
// println!("{:?}", chat.rooms.get(&5i32).unwrap());
|
// println!("{:?}", chat.rooms.get(&5i32).unwrap());
|
||||||
|
@ -663,7 +707,7 @@ impl Perform for GetPost {
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&GetPostResponse {
|
&GetPostResponse {
|
||||||
op: self.op_type().to_string(),
|
op: self.op_type().to_string(),
|
||||||
post: post,
|
post: post_view,
|
||||||
comments: comments
|
comments: comments
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -723,7 +767,7 @@ impl Perform for CreateComment {
|
||||||
content: self.content.to_owned(),
|
content: self.content.to_owned(),
|
||||||
parent_id: self.parent_id.to_owned(),
|
parent_id: self.parent_id.to_owned(),
|
||||||
post_id: self.post_id,
|
post_id: self.post_id,
|
||||||
attributed_to: fedi_user_id.to_owned(),
|
creator_id: user_id,
|
||||||
updated: None
|
updated: None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -738,7 +782,7 @@ impl Perform for CreateComment {
|
||||||
let like_form = CommentLikeForm {
|
let like_form = CommentLikeForm {
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment.id,
|
||||||
post_id: self.post_id,
|
post_id: self.post_id,
|
||||||
fedi_user_id: fedi_user_id.to_owned(),
|
user_id: user_id,
|
||||||
score: 1
|
score: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -751,7 +795,7 @@ impl Perform for CreateComment {
|
||||||
|
|
||||||
let likes: Vec<CommentLike> = vec![inserted_like];
|
let likes: Vec<CommentLike> = vec![inserted_like];
|
||||||
|
|
||||||
let comment_view = CommentView::from_comment(&inserted_comment, &likes, &Some(fedi_user_id));
|
let comment_view = CommentView::from_comment(&inserted_comment, &likes, Some(user_id));
|
||||||
|
|
||||||
let mut comment_sent = comment_view.clone();
|
let mut comment_sent = comment_view.clone();
|
||||||
comment_sent.my_vote = None;
|
comment_sent.my_vote = None;
|
||||||
|
@ -803,7 +847,7 @@ impl Perform for EditComment {
|
||||||
content: self.content.to_owned(),
|
content: self.content.to_owned(),
|
||||||
parent_id: self.parent_id,
|
parent_id: self.parent_id,
|
||||||
post_id: self.post_id,
|
post_id: self.post_id,
|
||||||
attributed_to: fedi_user_id.to_owned(),
|
creator_id: user_id,
|
||||||
updated: Some(naive_now())
|
updated: Some(naive_now())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -821,7 +865,7 @@ impl Perform for EditComment {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let comment_view = CommentView::from_comment(&updated_comment, &likes, &Some(fedi_user_id));
|
let comment_view = CommentView::from_comment(&updated_comment, &likes, Some(user_id));
|
||||||
|
|
||||||
let mut comment_sent = comment_view.clone();
|
let mut comment_sent = comment_view.clone();
|
||||||
comment_sent.my_vote = None;
|
comment_sent.my_vote = None;
|
||||||
|
@ -872,7 +916,7 @@ impl Perform for CreateCommentLike {
|
||||||
let like_form = CommentLikeForm {
|
let like_form = CommentLikeForm {
|
||||||
comment_id: self.comment_id,
|
comment_id: self.comment_id,
|
||||||
post_id: self.post_id,
|
post_id: self.post_id,
|
||||||
fedi_user_id: fedi_user_id.to_owned(),
|
user_id: user_id,
|
||||||
score: self.score
|
score: self.score
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -891,7 +935,7 @@ impl Perform for CreateCommentLike {
|
||||||
|
|
||||||
// Have to refetch the comment to get the current state
|
// Have to refetch the comment to get the current state
|
||||||
// thread::sleep(time::Duration::from_secs(1));
|
// thread::sleep(time::Duration::from_secs(1));
|
||||||
let liked_comment = CommentView::read(&conn, self.comment_id, &Some(fedi_user_id));
|
let liked_comment = CommentView::read(&conn, self.comment_id, Some(user_id));
|
||||||
|
|
||||||
let mut liked_comment_sent = liked_comment.clone();
|
let mut liked_comment_sent = liked_comment.clone();
|
||||||
liked_comment_sent.my_vote = None;
|
liked_comment_sent.my_vote = None;
|
||||||
|
@ -919,6 +963,111 @@ impl Perform for CreateCommentLike {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Perform for GetPosts {
|
||||||
|
fn op_type(&self) -> UserOperation {
|
||||||
|
UserOperation::GetPosts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
println!("{:?}", self.auth);
|
||||||
|
|
||||||
|
let user_id: Option<i32> = match &self.auth {
|
||||||
|
Some(auth) => {
|
||||||
|
match Claims::decode(&auth) {
|
||||||
|
Ok(claims) => {
|
||||||
|
let user_id = claims.claims.id;
|
||||||
|
Some(user_id)
|
||||||
|
}
|
||||||
|
Err(e) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let type_ = ListingType::from_str(&self.type_).expect("listing type");
|
||||||
|
let sort = ListingSortType::from_str(&self.sort).expect("listing sort");
|
||||||
|
|
||||||
|
let posts = match PostView::list(&conn, type_, sort, self.community_id, user_id, self.limit) {
|
||||||
|
Ok(posts) => posts,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{}", e);
|
||||||
|
return self.error("Couldn't get posts");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the jwt
|
||||||
|
serde_json::to_string(
|
||||||
|
&GetPostsResponse {
|
||||||
|
op: self.op_type().to_string(),
|
||||||
|
posts: posts
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Perform for CreatePostLike {
|
||||||
|
fn op_type(&self) -> UserOperation {
|
||||||
|
UserOperation::CreatePostLike
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
let claims = match Claims::decode(&self.auth) {
|
||||||
|
Ok(claims) => claims.claims,
|
||||||
|
Err(e) => {
|
||||||
|
return self.error("Not logged in.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_id = claims.id;
|
||||||
|
|
||||||
|
let like_form = PostLikeForm {
|
||||||
|
post_id: self.post_id,
|
||||||
|
user_id: user_id,
|
||||||
|
score: self.score
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove any likes first
|
||||||
|
PostLike::remove(&conn, &like_form).unwrap();
|
||||||
|
|
||||||
|
// Only add the like if the score isnt 0
|
||||||
|
if &like_form.score != &0 {
|
||||||
|
let inserted_like = match PostLike::like(&conn, &like_form) {
|
||||||
|
Ok(like) => like,
|
||||||
|
Err(e) => {
|
||||||
|
return self.error("Couldn't like post.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let post_view = match PostView::get(&conn, self.post_id, Some(user_id)) {
|
||||||
|
Ok(post) => post,
|
||||||
|
Err(e) => {
|
||||||
|
return self.error("Couldn't find Post");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// just output the score
|
||||||
|
|
||||||
|
let like_out = serde_json::to_string(
|
||||||
|
&CreatePostLikeResponse {
|
||||||
|
op: self.op_type().to_string(),
|
||||||
|
post: post_view
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
like_out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// impl Handler<Login> for ChatServer {
|
// impl Handler<Login> for ChatServer {
|
||||||
|
|
||||||
// type Result = MessageResult<Login>;
|
// type Result = MessageResult<Login>;
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
|
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 as CommunityI, CommunityResponse, Post } from '../interfaces';
|
import { UserOperation, Community as CommunityI, CommunityResponse, Post, GetPostsForm, ListingSortType, ListingType, GetPostsResponse, CreatePostLikeForm, CreatePostLikeResponse} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
|
import { MomentTime } from './moment-time';
|
||||||
|
import { PostListing } from './post-listing';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp } from '../utils';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
community: CommunityI;
|
community: CommunityI;
|
||||||
posts: Array<Post>;
|
posts: Array<Post>;
|
||||||
|
sortType: ListingSortType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Community extends Component<any, State> {
|
export class Community extends Component<any, State> {
|
||||||
|
@ -19,7 +23,8 @@ export class Community extends Component<any, State> {
|
||||||
name: null,
|
name: null,
|
||||||
published: null
|
published: null
|
||||||
},
|
},
|
||||||
posts: []
|
posts: [],
|
||||||
|
sortType: ListingSortType.Hot,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
|
@ -27,8 +32,6 @@ export class Community extends Component<any, State> {
|
||||||
|
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
console.log(this.props.match.params.id);
|
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
@ -39,6 +42,14 @@ export class Community extends Component<any, State> {
|
||||||
|
|
||||||
let communityId = Number(this.props.match.params.id);
|
let communityId = Number(this.props.match.params.id);
|
||||||
WebSocketService.Instance.getCommunity(communityId);
|
WebSocketService.Instance.getCommunity(communityId);
|
||||||
|
|
||||||
|
let getPostsForm: GetPostsForm = {
|
||||||
|
community_id: communityId,
|
||||||
|
limit: 10,
|
||||||
|
sort: ListingSortType[ListingSortType.Hot],
|
||||||
|
type_: ListingType[ListingType.Community]
|
||||||
|
}
|
||||||
|
WebSocketService.Instance.getPosts(getPostsForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -49,14 +60,57 @@ export class Community extends Component<any, State> {
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-lg-6 mb-4">
|
<div class="col-12 col-sm-10 col-lg-9">
|
||||||
{this.state.community.name}
|
<h4>/f/{this.state.community.name}</h4>
|
||||||
|
<div>{this.selects()}</div>
|
||||||
|
{this.state.posts.length > 0
|
||||||
|
? this.state.posts.map(post =>
|
||||||
|
<PostListing post={post} />)
|
||||||
|
: <div>no listings</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-sm-2 col-lg-3">
|
||||||
|
Sidebar
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selects() {
|
||||||
|
return (
|
||||||
|
<div className="mb-2">
|
||||||
|
<select value={this.state.sortType} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto">
|
||||||
|
<option disabled>Sort Type</option>
|
||||||
|
<option value={ListingSortType.Hot}>Hot</option>
|
||||||
|
<option value={ListingSortType.New}>New</option>
|
||||||
|
<option disabled>──────────</option>
|
||||||
|
<option value={ListingSortType.TopDay}>Top Day</option>
|
||||||
|
<option value={ListingSortType.TopWeek}>Week</option>
|
||||||
|
<option value={ListingSortType.TopMonth}>Month</option>
|
||||||
|
<option value={ListingSortType.TopYear}>Year</option>
|
||||||
|
<option value={ListingSortType.TopAll}>All</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSortChange(i: Community, event) {
|
||||||
|
i.state.sortType = Number(event.target.value);
|
||||||
|
i.setState(i.state);
|
||||||
|
|
||||||
|
let getPostsForm: GetPostsForm = {
|
||||||
|
community_id: i.state.community.id,
|
||||||
|
limit: 10,
|
||||||
|
sort: ListingSortType[i.state.sortType],
|
||||||
|
type_: ListingType[ListingType.Community]
|
||||||
|
}
|
||||||
|
WebSocketService.Instance.getPosts(getPostsForm);
|
||||||
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
let op: UserOperation = msgOp(msg);
|
let op: UserOperation = msgOp(msg);
|
||||||
|
@ -67,6 +121,20 @@ export class Community extends Component<any, State> {
|
||||||
let res: CommunityResponse = msg;
|
let res: CommunityResponse = msg;
|
||||||
this.state.community = res.community;
|
this.state.community = res.community;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
} else if (op == UserOperation.GetPosts) {
|
||||||
|
let res: GetPostsResponse = msg;
|
||||||
|
this.state.posts = res.posts;
|
||||||
|
this.setState(this.state);
|
||||||
|
} else if (op == UserOperation.CreatePostLike) {
|
||||||
|
let res: CreatePostLikeResponse = msg;
|
||||||
|
let found = this.state.posts.find(c => c.id == res.post.id);
|
||||||
|
found.my_vote = res.post.my_vote;
|
||||||
|
found.score = res.post.score;
|
||||||
|
found.upvotes = res.post.upvotes;
|
||||||
|
found.downvotes = res.post.downvotes;
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,10 @@ export class Navbar extends Component<any, any> {
|
||||||
<div class="collapse navbar-collapse">
|
<div class="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}>github</a>
|
<a class="nav-link" href={repoUrl}>About</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href={repoUrl}>Forums</a>
|
||||||
</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>
|
||||||
|
|
104
ui/src/components/post-listing.tsx
Normal file
104
ui/src/components/post-listing.tsx
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { Component, linkEvent } from 'inferno';
|
||||||
|
import { Link } from 'inferno-router';
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
|
import { WebSocketService, UserService } from '../services';
|
||||||
|
import { Post, CreatePostLikeResponse, CreatePostLikeForm } from '../interfaces';
|
||||||
|
import { MomentTime } from './moment-time';
|
||||||
|
import { mdToHtml } from '../utils';
|
||||||
|
|
||||||
|
interface PostListingState {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PostListingProps {
|
||||||
|
post: Post;
|
||||||
|
showCommunity?: boolean;
|
||||||
|
showBody?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
|
|
||||||
|
private emptyState: PostListingState = {
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = this.emptyState;
|
||||||
|
this.handlePostLike = this.handlePostLike.bind(this);
|
||||||
|
this.handlePostDisLike = this.handlePostDisLike.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let post = this.props.post;
|
||||||
|
return (
|
||||||
|
<div class="listing">
|
||||||
|
<div className="float-left small text-center">
|
||||||
|
<div className={`pointer upvote ${post.my_vote == 1 ? 'text-info' : 'text-muted'}`} onClick={linkEvent(this, this.handlePostLike)}>▲</div>
|
||||||
|
<div>{post.score}</div>
|
||||||
|
<div className={`pointer downvote ${post.my_vote == -1 && 'text-danger'}`} onClick={linkEvent(this, this.handlePostDisLike)}>▼</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-4">
|
||||||
|
{post.url
|
||||||
|
? <h5 className="mb-0">
|
||||||
|
<a className="text-white" href={post.url}>{post.name}</a>
|
||||||
|
<small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small>
|
||||||
|
</h5>
|
||||||
|
: <h5 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link></h5>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="details ml-4 mb-1">
|
||||||
|
<ul class="list-inline mb-0 text-muted small">
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span>by </span>
|
||||||
|
<a href={post.creator_id.toString()}>{post.creator_name}</a>
|
||||||
|
{this.props.showCommunity &&
|
||||||
|
<span>
|
||||||
|
<span> to </span>
|
||||||
|
<Link to={`/community/${post.community_id}`}>{post.community_name}</Link>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span><MomentTime data={post} /></span>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span>(
|
||||||
|
<span className="text-info">+{post.upvotes}</span>
|
||||||
|
<span> | </span>
|
||||||
|
<span className="text-danger">-{post.downvotes}</span>
|
||||||
|
<span>) </span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<Link to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// private get myPost(): boolean {
|
||||||
|
// return this.props.node.comment.attributed_to == UserService.Instance.fediUserId;
|
||||||
|
// }
|
||||||
|
|
||||||
|
handlePostLike(i: PostListing, event) {
|
||||||
|
|
||||||
|
let form: CreatePostLikeForm = {
|
||||||
|
post_id: i.props.post.id,
|
||||||
|
score: (i.props.post.my_vote == 1) ? 0 : 1
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.likePost(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePostDisLike(i: PostListing, event) {
|
||||||
|
let form: CreatePostLikeForm = {
|
||||||
|
post_id: i.props.post.id,
|
||||||
|
score: (i.props.post.my_vote == -1) ? 0 : -1
|
||||||
|
};
|
||||||
|
WebSocketService.Instance.likePost(form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
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, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse, CommentSortType } from '../interfaces';
|
import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse, CommentSortType, CreatePostLikeResponse } from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp, hotRank,mdToHtml } from '../utils';
|
import { msgOp, hotRank,mdToHtml } from '../utils';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
|
import { PostListing } from './post-listing';
|
||||||
import * as autosize from 'autosize';
|
import * as autosize from 'autosize';
|
||||||
|
|
||||||
interface CommentNodeI {
|
interface CommentNodeI {
|
||||||
|
@ -22,13 +23,7 @@ export class Post extends Component<any, State> {
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: State = {
|
private emptyState: State = {
|
||||||
post: {
|
post: null,
|
||||||
name: null,
|
|
||||||
attributed_to: null,
|
|
||||||
community_id: null,
|
|
||||||
id: null,
|
|
||||||
published: null,
|
|
||||||
},
|
|
||||||
comments: [],
|
comments: [],
|
||||||
commentSort: CommentSortType.Hot
|
commentSort: CommentSortType.Hot
|
||||||
}
|
}
|
||||||
|
@ -38,7 +33,7 @@ export class Post extends Component<any, State> {
|
||||||
|
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
this.state.post.id = Number(this.props.match.params.id);
|
let postId = Number(this.props.match.params.id);
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
|
@ -48,7 +43,7 @@ export class Post extends Component<any, State> {
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
|
|
||||||
WebSocketService.Instance.getPost(this.state.post.id);
|
WebSocketService.Instance.getPost(postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -62,36 +57,23 @@ export class Post extends Component<any, State> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
{this.state.post &&
|
||||||
<div class="col-12 col-sm-8 col-lg-7 mb-3">
|
<div class="row">
|
||||||
{this.postHeader()}
|
<div class="col-12 col-sm-8 col-lg-7 mb-3">
|
||||||
<CommentForm postId={this.state.post.id} />
|
<PostListing post={this.state.post} showBody showCommunity />
|
||||||
{this.sortRadios()}
|
<div className="mb-2" />
|
||||||
{this.commentsTree()}
|
<CommentForm postId={this.state.post.id} />
|
||||||
|
{this.sortRadios()}
|
||||||
|
{this.commentsTree()}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4 col-lg-3 mb-3">
|
||||||
|
{this.state.comments.length > 0 && this.newComments()}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-12 col-lg-2">
|
||||||
|
{this.sidebar()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-4 col-lg-3 mb-3">
|
}
|
||||||
{this.newComments()}
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-12 col-lg-2">
|
|
||||||
{this.sidebar()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
postHeader() {
|
|
||||||
let title = this.state.post.url
|
|
||||||
? <h5>
|
|
||||||
<a href={this.state.post.url}>{this.state.post.name}</a>
|
|
||||||
<small><a className="ml-2 text-muted font-italic" href={this.state.post.url}>{(new URL(this.state.post.url)).hostname}</a></small>
|
|
||||||
</h5>
|
|
||||||
: <h5>{this.state.post.name}</h5>;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>{title}</div>
|
|
||||||
<div>via {this.state.post.attributed_to} <MomentTime data={this.state.post} /></div>
|
|
||||||
<div>{this.state.post.body}</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -223,6 +205,13 @@ export class Post extends Component<any, State> {
|
||||||
if (res.comment.my_vote !== null)
|
if (res.comment.my_vote !== null)
|
||||||
found.my_vote = res.comment.my_vote;
|
found.my_vote = res.comment.my_vote;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
|
} else if (op == UserOperation.CreatePostLike) {
|
||||||
|
let res: CreatePostLikeResponse = msg;
|
||||||
|
this.state.post.my_vote = res.post.my_vote;
|
||||||
|
this.state.post.score = res.post.score;
|
||||||
|
this.state.post.upvotes = res.post.upvotes;
|
||||||
|
this.state.post.downvotes = res.post.downvotes;
|
||||||
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export enum UserOperation {
|
export enum UserOperation {
|
||||||
Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike
|
Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
|
@ -31,12 +31,21 @@ export interface ListCommunitiesResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Post {
|
export interface Post {
|
||||||
|
user_id?: number;
|
||||||
|
my_vote?: number;
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
body?: string;
|
body?: string;
|
||||||
attributed_to: string;
|
creator_id: number;
|
||||||
|
creator_name: string;
|
||||||
community_id: number;
|
community_id: number;
|
||||||
|
community_name: string;
|
||||||
|
number_of_comments: number;
|
||||||
|
score: number;
|
||||||
|
upvotes: number;
|
||||||
|
downvotes: number;
|
||||||
|
hot_rank: number;
|
||||||
published: string;
|
published: string;
|
||||||
updated?: string;
|
updated?: string;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +68,7 @@ export interface PostResponse {
|
||||||
export interface Comment {
|
export interface Comment {
|
||||||
id: number;
|
id: number;
|
||||||
content: string;
|
content: string;
|
||||||
attributed_to: string;
|
creator_id: number;
|
||||||
post_id: number,
|
post_id: number,
|
||||||
parent_id?: number;
|
parent_id?: number;
|
||||||
published: string;
|
published: string;
|
||||||
|
@ -95,6 +104,30 @@ export interface CreateCommentLikeResponse {
|
||||||
comment: Comment;
|
comment: Comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetPostsForm {
|
||||||
|
type_: string;
|
||||||
|
sort: string;
|
||||||
|
limit: number;
|
||||||
|
community_id?: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetPostsResponse {
|
||||||
|
op: string;
|
||||||
|
posts: Array<Post>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePostLikeForm {
|
||||||
|
post_id: number;
|
||||||
|
score: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePostLikeResponse {
|
||||||
|
op: string;
|
||||||
|
post: Post;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoginForm {
|
export interface LoginForm {
|
||||||
username_or_email: string;
|
username_or_email: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
@ -107,6 +140,7 @@ export interface RegisterForm {
|
||||||
password_verify: string;
|
password_verify: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
op: string;
|
op: string;
|
||||||
jwt: string;
|
jwt: string;
|
||||||
|
@ -116,4 +150,11 @@ export enum CommentSortType {
|
||||||
Hot, Top, New
|
Hot, Top, New
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ListingType {
|
||||||
|
All, Subscribed, Community
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ListingSortType {
|
||||||
|
Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,3 +32,7 @@ body {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.listing {
|
||||||
|
min-height: 61px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { wsUri } from '../env';
|
import { wsUri } from '../env';
|
||||||
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm } from '../interfaces';
|
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetListingsForm, CreatePostLikeForm } 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';
|
||||||
|
@ -70,15 +70,25 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.CreateCommentLike, form));
|
this.subject.next(this.wsSendWrapper(UserOperation.CreateCommentLike, form));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPosts(form: GetListingsForm) {
|
||||||
|
this.setAuth(form, false);
|
||||||
|
this.subject.next(this.wsSendWrapper(UserOperation.GetPosts, form));
|
||||||
|
}
|
||||||
|
|
||||||
|
public likePost(form: CreatePostLikeForm) {
|
||||||
|
this.setAuth(form);
|
||||||
|
this.subject.next(this.wsSendWrapper(UserOperation.CreatePostLike, form));
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
return send;
|
return send;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setAuth(obj: any) {
|
private setAuth(obj: any, throwErr: boolean = true) {
|
||||||
obj.auth = UserService.Instance.auth;
|
obj.auth = UserService.Instance.auth;
|
||||||
if (obj.auth == null) {
|
if (obj.auth == null && throwErr) {
|
||||||
alert("Not logged in.");
|
alert("Not logged in.");
|
||||||
throw "Not logged in";
|
throw "Not logged in";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue