mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-26 04:41:31 +00:00
Adding login and Register
- Login and Register mostly working. - Starting to work on creating communities.
This commit is contained in:
parent
816aa0b15f
commit
e570c70701
26 changed files with 827 additions and 302 deletions
|
@ -35,6 +35,8 @@ We have a twitter alternative (mastodon), a facebook alternative (friendica), so
|
|||
- [Recursive query for adjacency list for nested comments](https://stackoverflow.com/questions/192220/what-is-the-most-efficient-elegant-way-to-parse-a-flat-table-into-a-tree/192462#192462)
|
||||
- https://github.com/sparksuite/simplemde-markdown-editor
|
||||
- [Sticky Sidebar](https://stackoverflow.com/questions/38382043/how-to-use-css-position-sticky-to-keep-a-sidebar-visible-with-bootstrap-4/49111934)
|
||||
- [RXJS websocket](https://stackoverflow.com/questions/44060315/reconnecting-a-websocket-in-angular-and-rxjs/44067972#44067972)
|
||||
- [Rust JWT](https://github.com/Keats/jsonwebtoken)
|
||||
|
||||
## TODOs
|
||||
- Endpoints
|
||||
|
|
17
server/Cargo.lock
generated
17
server/Cargo.lock
generated
|
@ -703,6 +703,20 @@ name = "itoa"
|
|||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
|
@ -1309,7 +1323,9 @@ dependencies = [
|
|||
"dotenv 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonwebtoken 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strum 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1977,6 +1993,7 @@ dependencies = [
|
|||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"checksum ipconfig 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "08f7eadeaf4b52700de180d147c4805f199854600b36faa963d91114827b2ffc"
|
||||
"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
|
||||
"checksum jsonwebtoken 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8d438ea707d465c230305963b67f8357a1d56fcfad9434797d7cb1c46c2e41df"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
|
||||
|
|
|
@ -18,3 +18,5 @@ env_logger = "*"
|
|||
rand = "0.6.5"
|
||||
strum = "0.14.0"
|
||||
strum_macros = "0.14.0"
|
||||
jsonwebtoken = "*"
|
||||
regex = "1"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
create table user_ (
|
||||
id serial primary key,
|
||||
name varchar(20) not null,
|
||||
name varchar(20) not null unique,
|
||||
preferred_username varchar(20),
|
||||
password_encrypted text not null,
|
||||
email text,
|
||||
email text unique,
|
||||
icon bytea,
|
||||
published timestamp not null default now(),
|
||||
updated timestamp
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
create table community (
|
||||
id serial primary key,
|
||||
name varchar(20) not null,
|
||||
name varchar(20) not null unique,
|
||||
published timestamp not null default now(),
|
||||
updated timestamp
|
||||
);
|
||||
|
|
|
@ -23,14 +23,14 @@ pub struct Comment {
|
|||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
#[table_name="comment"]
|
||||
pub struct CommentForm<'a> {
|
||||
pub content: &'a str,
|
||||
pub attributed_to: &'a str,
|
||||
pub post_id: &'a i32,
|
||||
pub parent_id: Option<&'a i32>,
|
||||
pub updated: Option<&'a chrono::NaiveDateTime>
|
||||
pub struct CommentForm {
|
||||
pub content: String,
|
||||
pub attributed_to: String,
|
||||
pub post_id: i32,
|
||||
pub parent_id: Option<i32>,
|
||||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||
|
@ -44,59 +44,55 @@ pub struct CommentLike {
|
|||
pub published: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
#[table_name="comment_like"]
|
||||
pub struct CommentLikeForm<'a> {
|
||||
pub comment_id: &'a i32,
|
||||
pub fedi_user_id: &'a str,
|
||||
pub score: &'a i16
|
||||
pub struct CommentLikeForm {
|
||||
pub comment_id: i32,
|
||||
pub fedi_user_id: String,
|
||||
pub score: i16
|
||||
}
|
||||
|
||||
impl<'a> Crud<CommentForm<'a>> for Comment {
|
||||
fn read(conn: &PgConnection, comment_id: i32) -> Comment {
|
||||
impl Crud<CommentForm> for Comment {
|
||||
fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
|
||||
use schema::comment::dsl::*;
|
||||
comment.find(comment_id)
|
||||
.first::<Comment>(conn)
|
||||
.expect("Error in query")
|
||||
.first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn delete(conn: &PgConnection, comment_id: i32) -> usize {
|
||||
fn delete(conn: &PgConnection, comment_id: i32) -> Result<usize, Error> {
|
||||
use schema::comment::dsl::*;
|
||||
diesel::delete(comment.find(comment_id))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, comment_form: CommentForm) -> Result<Comment, Error> {
|
||||
fn create(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
|
||||
use schema::comment::dsl::*;
|
||||
insert_into(comment)
|
||||
.values(comment_form)
|
||||
.get_result::<Comment>(conn)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, comment_id: i32, comment_form: CommentForm) -> Comment {
|
||||
fn update(conn: &PgConnection, comment_id: i32, comment_form: &CommentForm) -> Result<Self, Error> {
|
||||
use schema::comment::dsl::*;
|
||||
diesel::update(comment.find(comment_id))
|
||||
.set(comment_form)
|
||||
.get_result::<Comment>(conn)
|
||||
.expect(&format!("Unable to find {}", comment_id))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Likeable <CommentLikeForm<'a>> for CommentLike {
|
||||
fn like(conn: &PgConnection, comment_like_form: CommentLikeForm) -> Result<CommentLike, Error> {
|
||||
impl Likeable <CommentLikeForm> for CommentLike {
|
||||
fn like(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
|
||||
use schema::comment_like::dsl::*;
|
||||
insert_into(comment_like)
|
||||
.values(comment_like_form)
|
||||
.get_result::<CommentLike>(conn)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn remove(conn: &PgConnection, comment_like_form: CommentLikeForm) -> usize {
|
||||
fn remove(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<usize, Error> {
|
||||
use schema::comment_like::dsl::*;
|
||||
diesel::delete(comment_like
|
||||
.filter(comment_id.eq(comment_like_form.comment_id))
|
||||
.filter(fedi_user_id.eq(comment_like_form.fedi_user_id)))
|
||||
.filter(fedi_user_id.eq(&comment_like_form.fedi_user_id)))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,17 +113,17 @@ mod tests {
|
|||
updated: None
|
||||
};
|
||||
|
||||
let inserted_post = Post::create(&conn, new_post).unwrap();
|
||||
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
||||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
attributed_to: "test_user.com".into(),
|
||||
post_id: &inserted_post.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: None,
|
||||
updated: None
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
|
||||
let expected_comment = Comment {
|
||||
id: inserted_comment.id,
|
||||
|
@ -142,20 +138,20 @@ mod tests {
|
|||
let child_comment_form = CommentForm {
|
||||
content: "A child comment".into(),
|
||||
attributed_to: "test_user.com".into(),
|
||||
post_id: &inserted_post.id,
|
||||
parent_id: Some(&inserted_comment.id),
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
updated: None
|
||||
};
|
||||
|
||||
let inserted_child_comment = Comment::create(&conn, child_comment_form).unwrap();
|
||||
let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
|
||||
|
||||
let comment_like_form = CommentLikeForm {
|
||||
comment_id: &inserted_comment.id,
|
||||
comment_id: inserted_comment.id,
|
||||
fedi_user_id: "test".into(),
|
||||
score: &1
|
||||
score: 1
|
||||
};
|
||||
|
||||
let inserted_comment_like = CommentLike::like(&conn, comment_like_form).unwrap();
|
||||
let inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
|
||||
|
||||
let expected_comment_like = CommentLike {
|
||||
id: inserted_comment_like.id,
|
||||
|
@ -165,12 +161,12 @@ mod tests {
|
|||
score: 1
|
||||
};
|
||||
|
||||
let read_comment = Comment::read(&conn, inserted_comment.id);
|
||||
let updated_comment = Comment::update(&conn, inserted_comment.id, comment_form);
|
||||
let like_removed = CommentLike::remove(&conn, comment_like_form);
|
||||
let num_deleted = Comment::delete(&conn, inserted_comment.id);
|
||||
Comment::delete(&conn, inserted_child_comment.id);
|
||||
Post::delete(&conn, inserted_post.id);
|
||||
let read_comment = Comment::read(&conn, inserted_comment.id).unwrap();
|
||||
let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap();
|
||||
let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap();
|
||||
let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
Comment::delete(&conn, inserted_child_comment.id).unwrap();
|
||||
Post::delete(&conn, inserted_post.id).unwrap();
|
||||
|
||||
assert_eq!(expected_comment, read_comment);
|
||||
assert_eq!(expected_comment, inserted_comment);
|
||||
|
|
|
@ -2,9 +2,10 @@ extern crate diesel;
|
|||
use schema::{community, community_user, community_follower};
|
||||
use diesel::*;
|
||||
use diesel::result::Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use {Crud, Followable, Joinable};
|
||||
|
||||
#[derive(Queryable, Identifiable, PartialEq, Debug)]
|
||||
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[table_name="community"]
|
||||
pub struct Community {
|
||||
pub id: i32,
|
||||
|
@ -13,11 +14,11 @@ pub struct Community {
|
|||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
|
||||
#[table_name="community"]
|
||||
pub struct CommunityForm<'a> {
|
||||
pub name: &'a str,
|
||||
pub updated: Option<&'a chrono::NaiveDateTime>
|
||||
pub struct CommunityForm {
|
||||
pub name: String,
|
||||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||
|
@ -30,11 +31,11 @@ pub struct CommunityUser {
|
|||
pub published: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
#[table_name="community_user"]
|
||||
pub struct CommunityUserForm<'a> {
|
||||
pub community_id: &'a i32,
|
||||
pub fedi_user_id: &'a str,
|
||||
pub struct CommunityUserForm {
|
||||
pub community_id: i32,
|
||||
pub fedi_user_id: String,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||
|
@ -47,76 +48,72 @@ pub struct CommunityFollower {
|
|||
pub published: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
#[table_name="community_follower"]
|
||||
pub struct CommunityFollowerForm<'a> {
|
||||
pub community_id: &'a i32,
|
||||
pub fedi_user_id: &'a str,
|
||||
pub struct CommunityFollowerForm {
|
||||
pub community_id: i32,
|
||||
pub fedi_user_id: String,
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Crud<CommunityForm<'a>> for Community {
|
||||
fn read(conn: &PgConnection, community_id: i32) -> Community {
|
||||
impl Crud<CommunityForm> for Community {
|
||||
fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
|
||||
use schema::community::dsl::*;
|
||||
community.find(community_id)
|
||||
.first::<Community>(conn)
|
||||
.expect("Error in query")
|
||||
.first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn delete(conn: &PgConnection, community_id: i32) -> usize {
|
||||
fn delete(conn: &PgConnection, community_id: i32) -> Result<usize, Error> {
|
||||
use schema::community::dsl::*;
|
||||
diesel::delete(community.find(community_id))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, new_community: CommunityForm) -> Result<Community, Error> {
|
||||
fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
|
||||
use schema::community::dsl::*;
|
||||
insert_into(community)
|
||||
.values(new_community)
|
||||
.get_result::<Community>(conn)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, community_id: i32, new_community: CommunityForm) -> Community {
|
||||
fn update(conn: &PgConnection, community_id: i32, new_community: &CommunityForm) -> Result<Self, Error> {
|
||||
use schema::community::dsl::*;
|
||||
diesel::update(community.find(community_id))
|
||||
.set(new_community)
|
||||
.get_result::<Community>(conn)
|
||||
.expect(&format!("Unable to find {}", community_id))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Followable<CommunityFollowerForm<'a>> for CommunityFollower {
|
||||
fn follow(conn: &PgConnection, community_follower_form: CommunityFollowerForm) -> Result<CommunityFollower, Error> {
|
||||
impl Followable<CommunityFollowerForm> for CommunityFollower {
|
||||
fn follow(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<Self, Error> {
|
||||
use schema::community_follower::dsl::*;
|
||||
insert_into(community_follower)
|
||||
.values(community_follower_form)
|
||||
.get_result::<CommunityFollower>(conn)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn ignore(conn: &PgConnection, community_follower_form: CommunityFollowerForm) -> usize {
|
||||
fn ignore(conn: &PgConnection, community_follower_form: &CommunityFollowerForm) -> Result<usize, Error> {
|
||||
use schema::community_follower::dsl::*;
|
||||
diesel::delete(community_follower
|
||||
.filter(community_id.eq(community_follower_form.community_id))
|
||||
.filter(fedi_user_id.eq(community_follower_form.fedi_user_id)))
|
||||
.filter(community_id.eq(&community_follower_form.community_id))
|
||||
.filter(fedi_user_id.eq(&community_follower_form.fedi_user_id)))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Joinable<CommunityUserForm<'a>> for CommunityUser {
|
||||
fn join(conn: &PgConnection, community_user_form: CommunityUserForm) -> Result<CommunityUser, Error> {
|
||||
impl Joinable<CommunityUserForm> for CommunityUser {
|
||||
fn join(conn: &PgConnection, community_user_form: &CommunityUserForm) -> Result<Self, Error> {
|
||||
use schema::community_user::dsl::*;
|
||||
insert_into(community_user)
|
||||
.values(community_user_form)
|
||||
.get_result::<CommunityUser>(conn)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn leave(conn: &PgConnection, community_user_form: CommunityUserForm) -> usize {
|
||||
|
||||
fn leave(conn: &PgConnection, community_user_form: &CommunityUserForm) -> Result<usize, Error> {
|
||||
use schema::community_user::dsl::*;
|
||||
diesel::delete(community_user
|
||||
.filter(community_id.eq(community_user_form.community_id))
|
||||
.filter(fedi_user_id.eq(community_user_form.fedi_user_id)))
|
||||
.filter(fedi_user_id.eq(&community_user_form.fedi_user_id)))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,7 +132,7 @@ mod tests {
|
|||
updated: None
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, new_community).unwrap();
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
||||
let expected_community = Community {
|
||||
id: inserted_community.id,
|
||||
|
@ -145,21 +142,21 @@ mod tests {
|
|||
};
|
||||
|
||||
let new_user = UserForm {
|
||||
name: "thom".into(),
|
||||
name: "terry".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
updated: None
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, new_user).unwrap();
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: &inserted_community.id,
|
||||
community_id: inserted_community.id,
|
||||
fedi_user_id: "test".into()
|
||||
};
|
||||
|
||||
let inserted_community_follower = CommunityFollower::follow(&conn, community_follower_form).unwrap();
|
||||
let inserted_community_follower = CommunityFollower::follow(&conn, &community_follower_form).unwrap();
|
||||
|
||||
let expected_community_follower = CommunityFollower {
|
||||
id: inserted_community_follower.id,
|
||||
|
@ -169,11 +166,11 @@ mod tests {
|
|||
};
|
||||
|
||||
let community_user_form = CommunityUserForm {
|
||||
community_id: &inserted_community.id,
|
||||
community_id: inserted_community.id,
|
||||
fedi_user_id: "test".into()
|
||||
};
|
||||
|
||||
let inserted_community_user = CommunityUser::join(&conn, community_user_form).unwrap();
|
||||
let inserted_community_user = CommunityUser::join(&conn, &community_user_form).unwrap();
|
||||
|
||||
let expected_community_user = CommunityUser {
|
||||
id: inserted_community_user.id,
|
||||
|
@ -182,12 +179,12 @@ mod tests {
|
|||
published: inserted_community_user.published
|
||||
};
|
||||
|
||||
let read_community = Community::read(&conn, inserted_community.id);
|
||||
let updated_community = Community::update(&conn, inserted_community.id, new_community);
|
||||
let ignored_community = CommunityFollower::ignore(&conn, community_follower_form);
|
||||
let left_community = CommunityUser::leave(&conn, community_user_form);
|
||||
let num_deleted = Community::delete(&conn, inserted_community.id);
|
||||
User_::delete(&conn, inserted_user.id);
|
||||
let read_community = Community::read(&conn, inserted_community.id).unwrap();
|
||||
let updated_community = Community::update(&conn, inserted_community.id, &new_community).unwrap();
|
||||
let ignored_community = CommunityFollower::ignore(&conn, &community_follower_form).unwrap();
|
||||
let left_community = CommunityUser::leave(&conn, &community_user_form).unwrap();
|
||||
let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
|
||||
User_::delete(&conn, inserted_user.id).unwrap();
|
||||
|
||||
assert_eq!(expected_community, read_community);
|
||||
assert_eq!(expected_community, inserted_community);
|
||||
|
|
|
@ -15,13 +15,13 @@ pub struct Post {
|
|||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
#[table_name="post"]
|
||||
pub struct PostForm<'a> {
|
||||
pub name: &'a str,
|
||||
pub url: &'a str,
|
||||
pub attributed_to: &'a str,
|
||||
pub updated: Option<&'a chrono::NaiveDateTime>
|
||||
pub struct PostForm {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
pub attributed_to: String,
|
||||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||
|
@ -35,59 +35,55 @@ pub struct PostLike {
|
|||
pub published: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
#[table_name="post_like"]
|
||||
pub struct PostLikeForm<'a> {
|
||||
pub post_id: &'a i32,
|
||||
pub fedi_user_id: &'a str,
|
||||
pub score: &'a i16
|
||||
pub struct PostLikeForm {
|
||||
pub post_id: i32,
|
||||
pub fedi_user_id: String,
|
||||
pub score: i16
|
||||
}
|
||||
|
||||
impl<'a> Crud<PostForm<'a>> for Post {
|
||||
fn read(conn: &PgConnection, post_id: i32) -> Post {
|
||||
impl Crud<PostForm> for Post {
|
||||
fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
|
||||
use schema::post::dsl::*;
|
||||
post.find(post_id)
|
||||
.first::<Post>(conn)
|
||||
.expect("Error in query")
|
||||
.first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn delete(conn: &PgConnection, post_id: i32) -> usize {
|
||||
fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> {
|
||||
use schema::post::dsl::*;
|
||||
diesel::delete(post.find(post_id))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, new_post: PostForm) -> Result<Post, Error> {
|
||||
fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> {
|
||||
use schema::post::dsl::*;
|
||||
insert_into(post)
|
||||
.values(new_post)
|
||||
.get_result::<Post>(conn)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, post_id: i32, new_post: PostForm) -> Post {
|
||||
fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> {
|
||||
use schema::post::dsl::*;
|
||||
diesel::update(post.find(post_id))
|
||||
.set(new_post)
|
||||
.get_result::<Post>(conn)
|
||||
.expect(&format!("Unable to find {}", post_id))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Likeable <PostLikeForm<'a>> for PostLike {
|
||||
fn like(conn: &PgConnection, post_like_form: PostLikeForm) -> Result<PostLike, Error> {
|
||||
impl Likeable <PostLikeForm> for PostLike {
|
||||
fn like(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<Self, Error> {
|
||||
use schema::post_like::dsl::*;
|
||||
insert_into(post_like)
|
||||
.values(post_like_form)
|
||||
.get_result::<PostLike>(conn)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn remove(conn: &PgConnection, post_like_form: PostLikeForm) -> usize {
|
||||
fn remove(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<usize, Error> {
|
||||
use schema::post_like::dsl::*;
|
||||
diesel::delete(post_like
|
||||
.filter(post_id.eq(post_like_form.post_id))
|
||||
.filter(fedi_user_id.eq(post_like_form.fedi_user_id)))
|
||||
.filter(fedi_user_id.eq(&post_like_form.fedi_user_id)))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +103,7 @@ mod tests {
|
|||
updated: None
|
||||
};
|
||||
|
||||
let inserted_post = Post::create(&conn, new_post).unwrap();
|
||||
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
||||
|
||||
let expected_post = Post {
|
||||
id: inserted_post.id,
|
||||
|
@ -119,12 +115,12 @@ mod tests {
|
|||
};
|
||||
|
||||
let post_like_form = PostLikeForm {
|
||||
post_id: &inserted_post.id,
|
||||
post_id: inserted_post.id,
|
||||
fedi_user_id: "test".into(),
|
||||
score: &1
|
||||
score: 1
|
||||
};
|
||||
|
||||
let inserted_post_like = PostLike::like(&conn, post_like_form).unwrap();
|
||||
let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
|
||||
|
||||
let expected_post_like = PostLike {
|
||||
id: inserted_post_like.id,
|
||||
|
@ -134,10 +130,10 @@ mod tests {
|
|||
score: 1
|
||||
};
|
||||
|
||||
let read_post = Post::read(&conn, inserted_post.id);
|
||||
let updated_post = Post::update(&conn, inserted_post.id, new_post);
|
||||
let like_removed = PostLike::remove(&conn, post_like_form);
|
||||
let num_deleted = Post::delete(&conn, inserted_post.id);
|
||||
let read_post = Post::read(&conn, inserted_post.id).unwrap();
|
||||
let updated_post = Post::update(&conn, inserted_post.id, &new_post).unwrap();
|
||||
let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
|
||||
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
|
||||
|
||||
assert_eq!(expected_post, read_post);
|
||||
assert_eq!(expected_post, inserted_post);
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
extern crate diesel;
|
||||
use schema::user_;
|
||||
use diesel::*;
|
||||
use diesel::result::Error;
|
||||
use schema::user_::dsl::*;
|
||||
use Crud;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use {Crud,is_email_regex};
|
||||
use jsonwebtoken::{encode, decode, Header, Validation};
|
||||
use bcrypt::{DEFAULT_COST, hash};
|
||||
|
||||
#[derive(Queryable, Identifiable, PartialEq, Debug)]
|
||||
#[table_name="user_"]
|
||||
|
@ -18,43 +20,75 @@ pub struct User_ {
|
|||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone, Copy)]
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
#[table_name="user_"]
|
||||
pub struct UserForm<'a> {
|
||||
pub name: &'a str,
|
||||
pub preferred_username: Option<&'a str>,
|
||||
pub password_encrypted: &'a str,
|
||||
pub email: Option<&'a str>,
|
||||
pub updated: Option<&'a chrono::NaiveDateTime>
|
||||
pub struct UserForm {
|
||||
pub name: String,
|
||||
pub preferred_username: Option<String>,
|
||||
pub password_encrypted: String,
|
||||
pub email: Option<String>,
|
||||
pub updated: Option<chrono::NaiveDateTime>
|
||||
}
|
||||
|
||||
impl<'a> Crud<UserForm<'a>> for User_ {
|
||||
fn read(conn: &PgConnection, user_id: i32) -> User_ {
|
||||
impl Crud<UserForm> for User_ {
|
||||
fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
|
||||
user_.find(user_id)
|
||||
.first::<User_>(conn)
|
||||
.expect("Error in query")
|
||||
.first::<Self>(conn)
|
||||
}
|
||||
fn delete(conn: &PgConnection, user_id: i32) -> usize {
|
||||
fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
|
||||
diesel::delete(user_.find(user_id))
|
||||
.execute(conn)
|
||||
.expect("Error deleting.")
|
||||
}
|
||||
fn create(conn: &PgConnection, form: UserForm) -> Result<User_, Error> {
|
||||
fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
|
||||
let mut edited_user = form.clone();
|
||||
// Add the rust crypt
|
||||
edited_user.password_encrypted = "here";
|
||||
// edited_user.password_encrypted;
|
||||
insert_into(user_)
|
||||
.values(edited_user)
|
||||
.get_result::<User_>(conn)
|
||||
let password_hash = hash(&form.password_encrypted, DEFAULT_COST)
|
||||
.expect("Couldn't hash password");
|
||||
edited_user.password_encrypted = password_hash;
|
||||
insert_into(user_)
|
||||
.values(edited_user)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn update(conn: &PgConnection, user_id: i32, form: UserForm) -> User_ {
|
||||
fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
|
||||
let mut edited_user = form.clone();
|
||||
edited_user.password_encrypted = "here";
|
||||
let password_hash = hash(&form.password_encrypted, DEFAULT_COST)
|
||||
.expect("Couldn't hash password");
|
||||
edited_user.password_encrypted = password_hash;
|
||||
diesel::update(user_.find(user_id))
|
||||
.set(edited_user)
|
||||
.get_result::<User_>(conn)
|
||||
.expect(&format!("Unable to find user {}", user_id))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
id: i32,
|
||||
username: String
|
||||
}
|
||||
|
||||
type Jwt = String;
|
||||
impl User_ {
|
||||
pub fn jwt(&self) -> Jwt {
|
||||
let my_claims = Claims {
|
||||
id: self.id,
|
||||
username: self.name.to_owned()
|
||||
};
|
||||
encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap()
|
||||
}
|
||||
|
||||
pub fn find_by_email_or_username(conn: &PgConnection, username_or_email: &str) -> Result<Self, Error> {
|
||||
if is_email_regex(username_or_email) {
|
||||
user_.filter(email.eq(username_or_email))
|
||||
.first::<User_>(conn)
|
||||
} else {
|
||||
user_.filter(name.eq(username_or_email))
|
||||
.first::<User_>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result<Self, Error> {
|
||||
let token = decode::<Claims>(&jwt, "secret".as_ref(), &Validation::default())
|
||||
.expect("Couldn't decode jwt");
|
||||
Self::read(&conn, token.claims.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,26 +109,26 @@ mod tests {
|
|||
updated: None
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, new_user).unwrap();
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
||||
let expected_user = User_ {
|
||||
id: inserted_user.id,
|
||||
name: "thom".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "here".into(),
|
||||
password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
|
||||
email: None,
|
||||
icon: None,
|
||||
published: inserted_user.published,
|
||||
updated: None
|
||||
};
|
||||
|
||||
let read_user = User_::read(&conn, inserted_user.id);
|
||||
let updated_user = User_::update(&conn, inserted_user.id, new_user);
|
||||
let num_deleted = User_::delete(&conn, inserted_user.id);
|
||||
let read_user = User_::read(&conn, inserted_user.id).unwrap();
|
||||
let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
|
||||
let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
||||
|
||||
assert_eq!(expected_user, read_user);
|
||||
assert_eq!(expected_user, inserted_user);
|
||||
assert_eq!(expected_user, updated_user);
|
||||
assert_eq!(expected_user.id, read_user.id);
|
||||
assert_eq!(expected_user.id, inserted_user.id);
|
||||
assert_eq!(expected_user.id, updated_user.id);
|
||||
assert_eq!(1, num_deleted);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,17 +4,15 @@ use std::time::{Instant, Duration};
|
|||
use server::actix::*;
|
||||
use server::actix_web::server::HttpServer;
|
||||
use server::actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse};
|
||||
use std::str::FromStr;
|
||||
|
||||
use server::websocket_server::server::*;
|
||||
|
||||
/// How often heartbeat pings are sent
|
||||
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
||||
/// How long before lack of client response causes a timeout
|
||||
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
use server::websocket_server::server::*;
|
||||
use std::str::FromStr;
|
||||
// use server::websocket_server::server::UserOperation::from_str;
|
||||
|
||||
/// This is our websocket route state, this state is shared with all route
|
||||
/// instances via `HttpContext::state()`
|
||||
struct WsChatSessionState {
|
||||
|
@ -92,7 +90,7 @@ use server::serde_json::Value;
|
|||
/// WebSocket message handler
|
||||
impl StreamHandler<ws::Message, ws::ProtocolError> for WSSession {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
// println!("WEBSOCKET MESSAGE: {:?}", msg);
|
||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => {
|
||||
self.hb = Instant::now();
|
||||
|
@ -108,7 +106,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WSSession {
|
|||
// Get the OP command, and its data
|
||||
let op: &str = &json["op"].as_str().unwrap();
|
||||
let data: &Value = &json["data"];
|
||||
|
||||
|
||||
let user_operation: UserOperation = UserOperation::from_str(op).unwrap();
|
||||
|
||||
match user_operation {
|
||||
|
@ -116,7 +114,23 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WSSession {
|
|||
let login: Login = serde_json::from_str(&data.to_string()).unwrap();
|
||||
ctx.state()
|
||||
.addr
|
||||
.do_send(login);
|
||||
.send(login)
|
||||
.into_actor(self)
|
||||
.then(|res, _, ctx| {
|
||||
match res {
|
||||
Ok(response) => match response {
|
||||
Ok(t) => ctx.text(serde_json::to_string(&t).unwrap()),
|
||||
Err(e) => {
|
||||
let error_message_str: String = serde_json::to_string(&e).unwrap();
|
||||
eprintln!("{}", &error_message_str);
|
||||
ctx.text(&error_message_str);
|
||||
}
|
||||
},
|
||||
_ => println!("Something is wrong"),
|
||||
}
|
||||
fut::ok(())
|
||||
})
|
||||
.wait(ctx)
|
||||
},
|
||||
UserOperation::Register => {
|
||||
let register: Register = serde_json::from_str(&data.to_string()).unwrap();
|
||||
|
@ -126,13 +140,44 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WSSession {
|
|||
.into_actor(self)
|
||||
.then(|res, _, ctx| {
|
||||
match res {
|
||||
Ok(wut) => ctx.text(wut),
|
||||
Ok(response) => match response {
|
||||
Ok(t) => ctx.text(serde_json::to_string(&t).unwrap()),
|
||||
Err(e) => {
|
||||
let error_message_str: String = serde_json::to_string(&e).unwrap();
|
||||
eprintln!("{}", &error_message_str);
|
||||
ctx.text(&error_message_str);
|
||||
}
|
||||
},
|
||||
_ => println!("Something is wrong"),
|
||||
}
|
||||
fut::ok(())
|
||||
})
|
||||
.wait(ctx)
|
||||
}
|
||||
},
|
||||
UserOperation::CreateCommunity => {
|
||||
use server::actions::community::CommunityForm;
|
||||
let auth: &str = &json["auth"].as_str().unwrap();
|
||||
let community_form: CommunityForm = serde_json::from_str(&data.to_string()).unwrap();
|
||||
ctx.state()
|
||||
.addr
|
||||
.send(community_form)
|
||||
.into_actor(self)
|
||||
.then(|res, _, ctx| {
|
||||
match res {
|
||||
Ok(response) => match response {
|
||||
Ok(t) => ctx.text(serde_json::to_string(&t).unwrap()),
|
||||
Err(e) => {
|
||||
let error_message_str: String = serde_json::to_string(&e).unwrap();
|
||||
eprintln!("{}", &error_message_str);
|
||||
ctx.text(&error_message_str);
|
||||
}
|
||||
},
|
||||
_ => println!("Something is wrong"),
|
||||
}
|
||||
fut::ok(())
|
||||
})
|
||||
.wait(ctx)
|
||||
},
|
||||
_ => ctx.text(format!("!!! unknown command: {:?}", m)),
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@ pub extern crate actix;
|
|||
pub extern crate actix_web;
|
||||
pub extern crate rand;
|
||||
pub extern crate strum;
|
||||
pub extern crate jsonwebtoken;
|
||||
pub extern crate bcrypt;
|
||||
pub extern crate regex;
|
||||
#[macro_use] pub extern crate strum_macros;
|
||||
|
||||
pub mod schema;
|
||||
|
@ -20,28 +23,28 @@ use diesel::pg::PgConnection;
|
|||
use diesel::result::Error;
|
||||
use dotenv::dotenv;
|
||||
use std::env;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
pub trait Crud<T> {
|
||||
fn create(conn: &PgConnection, form: T) -> Result<Self, Error> where Self: Sized;
|
||||
fn read(conn: &PgConnection, id: i32) -> Self;
|
||||
fn update(conn: &PgConnection, id: i32, form: T) -> Self;
|
||||
fn delete(conn: &PgConnection, id: i32) -> usize;
|
||||
fn create(conn: &PgConnection, form: &T) -> Result<Self, Error> where Self: Sized;
|
||||
fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> where Self: Sized;
|
||||
fn update(conn: &PgConnection, id: i32, form: &T) -> Result<Self, Error> where Self: Sized;
|
||||
fn delete(conn: &PgConnection, id: i32) -> Result<usize, Error> where Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Followable<T> {
|
||||
fn follow(conn: &PgConnection, form: T) -> Result<Self, Error> where Self: Sized;
|
||||
fn ignore(conn: &PgConnection, form: T) -> usize;
|
||||
fn follow(conn: &PgConnection, form: &T) -> Result<Self, Error> where Self: Sized;
|
||||
fn ignore(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Joinable<T> {
|
||||
fn join(conn: &PgConnection, form: T) -> Result<Self, Error> where Self: Sized;
|
||||
fn leave(conn: &PgConnection, form: T) -> usize;
|
||||
fn join(conn: &PgConnection, form: &T) -> Result<Self, Error> where Self: Sized;
|
||||
fn leave(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Likeable<T> {
|
||||
fn like(conn: &PgConnection, form: T) -> Result<Self, Error> where Self: Sized;
|
||||
fn remove(conn: &PgConnection, form: T) -> usize;
|
||||
fn like(conn: &PgConnection, form: &T) -> Result<Self, Error> where Self: Sized;
|
||||
fn remove(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized;
|
||||
}
|
||||
|
||||
pub fn establish_connection() -> PgConnection {
|
||||
|
@ -61,7 +64,7 @@ impl Settings {
|
|||
Settings {
|
||||
db_url: env::var("DATABASE_URL")
|
||||
.expect("DATABASE_URL must be set"),
|
||||
hostname: env::var("HOSTNAME").unwrap_or("http://0.0.0.0".to_string())
|
||||
hostname: env::var("HOSTNAME").unwrap_or("http://0.0.0.0".to_string())
|
||||
}
|
||||
}
|
||||
fn api_endpoint(&self) -> String {
|
||||
|
@ -78,11 +81,22 @@ pub fn naive_now() -> NaiveDateTime {
|
|||
chrono::prelude::Utc::now().naive_utc()
|
||||
}
|
||||
|
||||
pub fn is_email_regex(test: &str) -> bool {
|
||||
let re = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
|
||||
re.is_match(test)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use Settings;
|
||||
#[test]
|
||||
use {Settings, is_email_regex};
|
||||
#[test]
|
||||
fn test_api() {
|
||||
assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_email() {
|
||||
assert!(is_email_regex("gush@gmail.com"));
|
||||
assert!(!is_email_regex("nada_neutho"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,27 @@ use actix::prelude::*;
|
|||
use rand::{rngs::ThreadRng, Rng};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use bcrypt::{verify};
|
||||
|
||||
use {Crud,establish_connection};
|
||||
use actions::community::*;
|
||||
|
||||
#[derive(EnumString,ToString,Debug)]
|
||||
pub enum UserOperation {
|
||||
Login, Register, Logout, Join, Edit, Reply, Vote, Delete, NextPage, Sticky
|
||||
}
|
||||
|
||||
pub enum MessageType {
|
||||
Comments, Users, Ping, Pong
|
||||
Login, Register, Logout, CreateCommunity, Join, Edit, Reply, Vote, Delete, NextPage, Sticky
|
||||
}
|
||||
|
||||
|
||||
#[derive(EnumString,ToString,Debug)]
|
||||
pub enum MessageToUser {
|
||||
Comments, Users, Ping, Pong, Error
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ErrorMessage {
|
||||
op: String,
|
||||
error: String
|
||||
}
|
||||
|
||||
/// Chat server sends this messages to session
|
||||
#[derive(Message)]
|
||||
|
@ -66,14 +74,16 @@ pub struct Join {
|
|||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Login {
|
||||
pub username: String,
|
||||
pub username_or_email: String,
|
||||
pub password: String
|
||||
}
|
||||
|
||||
// #[derive(Message)]
|
||||
impl actix::Message for Login {
|
||||
type Result = Result<LoginResponse, ErrorMessage>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Register {
|
||||
username: String,
|
||||
|
@ -82,9 +92,31 @@ pub struct Register {
|
|||
password_verify: String
|
||||
}
|
||||
|
||||
impl actix::Message for Register {
|
||||
type Result = String;
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LoginResponse {
|
||||
op: String,
|
||||
jwt: String
|
||||
}
|
||||
|
||||
impl actix::Message for Register {
|
||||
type Result = Result<LoginResponse, ErrorMessage>;
|
||||
}
|
||||
|
||||
// #[derive(Serialize, Deserialize)]
|
||||
// pub struct CreateCommunity {
|
||||
// name: String
|
||||
// }
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateCommunityResponse {
|
||||
op: String,
|
||||
community: Community
|
||||
}
|
||||
|
||||
impl actix::Message for CommunityForm {
|
||||
type Result = Result<CreateCommunityResponse, ErrorMessage>;
|
||||
}
|
||||
|
||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat
|
||||
/// session. implementation is super primitive
|
||||
pub struct ChatServer {
|
||||
|
@ -233,10 +265,47 @@ impl Handler<Join> for ChatServer {
|
|||
|
||||
impl Handler<Login> for ChatServer {
|
||||
|
||||
type Result = ();
|
||||
fn handle(&mut self, msg: Login, _: &mut Context<Self>) {
|
||||
println!("{}", msg.password);
|
||||
type Result = MessageResult<Login>;
|
||||
fn handle(&mut self, msg: Login, _: &mut Context<Self>) -> Self::Result {
|
||||
|
||||
use actions::user::*;
|
||||
let conn = establish_connection();
|
||||
|
||||
// Fetch that username / email
|
||||
let user: User_ = match User_::find_by_email_or_username(&conn, &msg.username_or_email) {
|
||||
Ok(user) => user,
|
||||
Err(e) => return MessageResult(
|
||||
Err(
|
||||
ErrorMessage {
|
||||
op: UserOperation::Login.to_string(),
|
||||
error: "Couldn't find that username or email".to_string()
|
||||
}
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
// Verify the password
|
||||
let valid: bool = verify(&msg.password, &user.password_encrypted).unwrap_or(false);
|
||||
if !valid {
|
||||
return MessageResult(
|
||||
Err(
|
||||
ErrorMessage {
|
||||
op: UserOperation::Login.to_string(),
|
||||
error: "Password incorrect".to_string()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Return the jwt
|
||||
MessageResult(
|
||||
Ok(
|
||||
LoginResponse {
|
||||
op: UserOperation::Login.to_string(),
|
||||
jwt: user.jwt()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,22 +317,79 @@ impl Handler<Register> for ChatServer {
|
|||
use actions::user::*;
|
||||
let conn = establish_connection();
|
||||
|
||||
// TODO figure out how to return values, and throw errors
|
||||
// Make sure passwords match
|
||||
if msg.password != msg.password_verify {
|
||||
return MessageResult(
|
||||
Err(
|
||||
ErrorMessage {
|
||||
op: UserOperation::Register.to_string(),
|
||||
error: "Passwords do not match.".to_string()
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Register the new user
|
||||
let user_form = UserForm {
|
||||
name: &msg.username,
|
||||
email: msg.email.as_ref().map(|x| &**x),
|
||||
password_encrypted: &msg.password,
|
||||
name: msg.username,
|
||||
email: msg.email,
|
||||
password_encrypted: msg.password,
|
||||
preferred_username: None,
|
||||
updated: None
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, user_form).unwrap();
|
||||
// Create the user
|
||||
let inserted_user = match User_::create(&conn, &user_form) {
|
||||
Ok(user) => user,
|
||||
Err(e) => return MessageResult(
|
||||
Err(
|
||||
ErrorMessage {
|
||||
op: UserOperation::Register.to_string(),
|
||||
error: "User already exists.".to_string() // overwrite the diesel error
|
||||
}
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
// Return the jwt
|
||||
MessageResult("hi".to_string())
|
||||
MessageResult(
|
||||
Ok(
|
||||
LoginResponse {
|
||||
op: UserOperation::Register.to_string(),
|
||||
jwt: inserted_user.jwt()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Handler<CommunityForm> for ChatServer {
|
||||
|
||||
type Result = MessageResult<CommunityForm>;
|
||||
|
||||
fn handle(&mut self, form: CommunityForm, _: &mut Context<Self>) -> Self::Result {
|
||||
let conn = establish_connection();
|
||||
let community = match Community::create(&conn, &form) {
|
||||
Ok(community) => community,
|
||||
Err(e) => return MessageResult(
|
||||
Err(
|
||||
ErrorMessage {
|
||||
op: UserOperation::CreateCommunity.to_string(),
|
||||
error: "Community already exists.".to_string() // overwrite the diesel error
|
||||
}
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
MessageResult(
|
||||
Ok(
|
||||
CreateCommunityResponse {
|
||||
op: UserOperation::CreateCommunity.to_string(),
|
||||
community: community
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,15 @@
|
|||
},
|
||||
"engineStrict": true,
|
||||
"dependencies": {
|
||||
"@types/js-cookie": "^2.2.1",
|
||||
"classcat": "^1.1.3",
|
||||
"dotenv": "^6.1.0",
|
||||
"inferno": "^7.0.1",
|
||||
"inferno-router": "^7.0.1",
|
||||
"moment": "^2.22.2"
|
||||
"js-cookie": "^2.2.0",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"moment": "^2.22.2",
|
||||
"rxjs": "^6.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fuse-box": "3.1.3",
|
||||
|
|
90
ui/src/components/create-community.tsx
Normal file
90
ui/src/components/create-community.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
import { Subscription } from "rxjs";
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { CommunityForm, UserOperation } from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import { msgOp } from '../utils';
|
||||
|
||||
interface State {
|
||||
communityForm: CommunityForm;
|
||||
}
|
||||
|
||||
let emptyState: State = {
|
||||
communityForm: {
|
||||
name: null,
|
||||
}
|
||||
}
|
||||
|
||||
export class CreateCommunity extends Component<any, State> {
|
||||
private subscription: Subscription;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = emptyState;
|
||||
|
||||
this.subscription = WebSocketService.Instance.subject
|
||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||
.subscribe(
|
||||
(msg) => this.parseMessage(msg),
|
||||
(err) => console.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 mb-4">
|
||||
{this.communityForm()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
communityForm() {
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
|
||||
<h3>Create Forum</h3>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-secondary">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleCreateCommunitySubmit(i: CreateCommunity, event) {
|
||||
event.preventDefault();
|
||||
WebSocketService.Instance.createCommunity(i.state.communityForm);
|
||||
}
|
||||
|
||||
handleCommunityNameChange(i: CreateCommunity, event) {
|
||||
i.state.communityForm.name = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
parseMessage(msg: any) {
|
||||
let op: UserOperation = msgOp(msg);
|
||||
if (msg.error) {
|
||||
alert(msg.error);
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
ui/src/components/create-post.tsx
Normal file
57
ui/src/components/create-post.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
|
||||
import { LoginForm, PostForm, UserOperation } from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import { msgOp } from '../utils';
|
||||
|
||||
interface State {
|
||||
postForm: PostForm;
|
||||
}
|
||||
|
||||
let emptyState: State = {
|
||||
postForm: {
|
||||
name: null,
|
||||
url: null,
|
||||
attributed_to: null
|
||||
}
|
||||
}
|
||||
|
||||
export class CreatePost extends Component<any, State> {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = emptyState;
|
||||
|
||||
WebSocketService.Instance.subject.subscribe(
|
||||
(msg) => this.parseMessage(msg),
|
||||
(err) => console.error(err),
|
||||
() => console.log('complete')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 mb-4">
|
||||
create post
|
||||
{/* {this.postForm()} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
parseMessage(msg: any) {
|
||||
console.log(msg);
|
||||
let op: UserOperation = msgOp(msg);
|
||||
if (msg.error) {
|
||||
alert(msg.error);
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
|
||||
import { LoginForm, RegisterForm } from '../interfaces';
|
||||
import { WebSocketService } from '../services';
|
||||
import { Subscription } from "rxjs";
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { LoginForm, RegisterForm, UserOperation } from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import { msgOp } from '../utils';
|
||||
|
||||
interface State {
|
||||
loginForm: LoginForm;
|
||||
|
@ -10,24 +12,36 @@ interface State {
|
|||
|
||||
let emptyState: State = {
|
||||
loginForm: {
|
||||
username: null,
|
||||
password: null
|
||||
username_or_email: undefined,
|
||||
password: undefined
|
||||
},
|
||||
registerForm: {
|
||||
username: null,
|
||||
password: null,
|
||||
password_verify: null
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
password_verify: undefined
|
||||
}
|
||||
}
|
||||
|
||||
export class Login extends Component<any, State> {
|
||||
private subscription: Subscription;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = emptyState;
|
||||
|
||||
this.subscription = WebSocketService.Instance.subject
|
||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||
.subscribe(
|
||||
(msg) => this.parseMessage(msg),
|
||||
(err) => console.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
|
@ -51,7 +65,7 @@ export class Login extends Component<any, State> {
|
|||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">Email or Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" value={this.state.loginForm.username} onInput={linkEvent(this, this.handleLoginUsernameChange)} required minLength={3} />
|
||||
<input type="text" class="form-control" value={this.state.loginForm.username_or_email} onInput={linkEvent(this, this.handleLoginUsernameChange)} required minLength={3} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
|
@ -108,38 +122,55 @@ export class Login extends Component<any, State> {
|
|||
}
|
||||
|
||||
handleLoginSubmit(i: Login, event) {
|
||||
console.log(i.state);
|
||||
event.preventDefault();
|
||||
WebSocketService.Instance.login(i.state.loginForm);
|
||||
}
|
||||
|
||||
handleLoginUsernameChange(i: Login, event) {
|
||||
i.state.loginForm.username = event.target.value;
|
||||
i.state.loginForm.username_or_email = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleLoginPasswordChange(i: Login, event) {
|
||||
i.state.loginForm.password = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterSubmit(i: Login, event) {
|
||||
console.log(i.state);
|
||||
event.preventDefault();
|
||||
WebSocketService.Instance.register(i.state.registerForm);
|
||||
}
|
||||
|
||||
handleRegisterUsernameChange(i: Login, event) {
|
||||
i.state.registerForm.username = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterEmailChange(i: Login, event) {
|
||||
i.state.registerForm.email = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleRegisterPasswordChange(i: Login, event) {
|
||||
i.state.registerForm.password = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
|
||||
handleRegisterPasswordVerifyChange(i: Login, event) {
|
||||
i.state.registerForm.password_verify = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
parseMessage(msg: any) {
|
||||
let op: UserOperation = msgOp(msg);
|
||||
if (msg.error) {
|
||||
alert(msg.error);
|
||||
return;
|
||||
} else {
|
||||
if (op == UserOperation.Register || op == UserOperation.Login) {
|
||||
UserService.Instance.login(msg.jwt);
|
||||
this.props.history.push('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,62 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
import { Link } from 'inferno-router';
|
||||
import { repoUrl } from '../utils';
|
||||
import { UserService } from '../services';
|
||||
|
||||
export class Navbar extends Component<any, any> {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {isLoggedIn: UserService.Instance.loggedIn};
|
||||
|
||||
// Subscribe to user changes
|
||||
UserService.Instance.sub.subscribe(user => {
|
||||
let loggedIn: boolean = user !== null;
|
||||
this.setState({isLoggedIn: loggedIn});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="sticky-top">{this.navbar()}</div>
|
||||
<div>{this.navbar()}</div>
|
||||
)
|
||||
}
|
||||
|
||||
// TODO class active corresponding to current page
|
||||
// TODO toggle css collapse
|
||||
navbar() {
|
||||
return (
|
||||
<nav class="navbar navbar-light bg-light p-0 px-3 shadow">
|
||||
<a class="navbar-brand mx-1" href="#">
|
||||
rrf
|
||||
</a>
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-item nav-link" href={repoUrl}>github</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto mr-2">
|
||||
<li class="nav-item">
|
||||
<Link class="nav-item nav-link" to="/login">Login</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<nav class="navbar navbar-expand-sm navbar-light bg-light p-0 px-3 shadow">
|
||||
<a class="navbar-brand" href="#">rrf</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href={repoUrl}>github</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<Link class="nav-link" to="/create_post">Create Post</Link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<Link class="nav-link" to="/create_community">Create Forum</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto mr-2">
|
||||
<li class="nav-item">
|
||||
{this.state.isLoggedIn ?
|
||||
<a role="button" class="nav-link pointer" onClick={ linkEvent(this, this.handleLogoutClick) }>Logout</a> :
|
||||
<Link class="nav-link" to="/login">Login</Link>
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
handleLogoutClick(i: Navbar, event) {
|
||||
UserService.Instance.logout();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import { HashRouter, Route, Switch } from 'inferno-router';
|
|||
import { Navbar } from './components/navbar';
|
||||
import { Home } from './components/home';
|
||||
import { Login } from './components/login';
|
||||
import { CreatePost } from './components/create-post';
|
||||
import { CreateCommunity } from './components/create-community';
|
||||
|
||||
import './main.css';
|
||||
|
||||
import { WebSocketService } from './services';
|
||||
import { WebSocketService, UserService } from './services';
|
||||
|
||||
const container = document.getElementById('app');
|
||||
|
||||
|
@ -16,6 +18,7 @@ class Index extends Component<any, any> {
|
|||
constructor(props, context) {
|
||||
super(props, context);
|
||||
WebSocketService.Instance;
|
||||
UserService.Instance;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -26,6 +29,8 @@ class Index extends Component<any, any> {
|
|||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path={`/login`} component={Login} />
|
||||
<Route path={`/create_post`} component={CreatePost} />
|
||||
<Route path={`/create_community`} component={CreateCommunity} />
|
||||
{/*
|
||||
<Route path={`/search/:type_/:q/:page`} component={Search} />
|
||||
<Route path={`/submit`} component={Submit} />
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
export interface LoginForm {
|
||||
export enum UserOperation {
|
||||
Login, Register, CreateCommunity
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface LoginForm {
|
||||
username_or_email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterForm {
|
||||
username: string;
|
||||
email?: string;
|
||||
|
@ -9,6 +19,14 @@ export interface RegisterForm {
|
|||
password_verify: string;
|
||||
}
|
||||
|
||||
export enum UserOperation {
|
||||
Login, Register
|
||||
export interface CommunityForm {
|
||||
name: string;
|
||||
updated?: number
|
||||
}
|
||||
|
||||
export interface PostForm {
|
||||
name: string;
|
||||
url: string;
|
||||
attributed_to: string;
|
||||
updated?: number
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import { wsUri } from './env';
|
||||
import { LoginForm, RegisterForm, UserOperation } from './interfaces';
|
||||
|
||||
export class WebSocketService {
|
||||
private static _instance: WebSocketService;
|
||||
private _ws;
|
||||
private conn: WebSocket;
|
||||
|
||||
private constructor() {
|
||||
console.log("Creating WSS");
|
||||
this.connect();
|
||||
console.log(wsUri);
|
||||
}
|
||||
|
||||
public static get Instance(){
|
||||
return this._instance || (this._instance = new this());
|
||||
}
|
||||
|
||||
private connect() {
|
||||
this.disconnect();
|
||||
this.conn = new WebSocket(wsUri);
|
||||
console.log('Connecting...');
|
||||
this.conn.onopen = (() => {
|
||||
console.log('Connected.');
|
||||
});
|
||||
this.conn.onmessage = (e => {
|
||||
console.log('Received: ' + e.data);
|
||||
});
|
||||
this.conn.onclose = (() => {
|
||||
console.log('Disconnected.');
|
||||
this.conn = null;
|
||||
});
|
||||
}
|
||||
private disconnect() {
|
||||
if (this.conn != null) {
|
||||
console.log('Disconnecting...');
|
||||
this.conn.close();
|
||||
this.conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
public login(loginForm: LoginForm) {
|
||||
this.conn.send(this.wsSendWrapper(UserOperation.Login, loginForm));
|
||||
}
|
||||
|
||||
public register(registerForm: RegisterForm) {
|
||||
this.conn.send(this.wsSendWrapper(UserOperation.Register, registerForm));
|
||||
}
|
||||
|
||||
private wsSendWrapper(op: UserOperation, data: any): string {
|
||||
let send = { op: UserOperation[op], data: data };
|
||||
console.log(send);
|
||||
return JSON.stringify(send);
|
||||
}
|
||||
|
||||
|
||||
}
|
51
ui/src/services/UserService.ts
Normal file
51
ui/src/services/UserService.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import * as Cookies from 'js-cookie';
|
||||
import { User } from '../interfaces';
|
||||
import * as jwt_decode from 'jwt-decode';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export class UserService {
|
||||
private static _instance: UserService;
|
||||
private user: User;
|
||||
public sub: Subject<User> = new Subject<User>();
|
||||
|
||||
private constructor() {
|
||||
let jwt = Cookies.get("jwt");
|
||||
if (jwt) {
|
||||
this.setUser(jwt);
|
||||
} else {
|
||||
console.log('No JWT cookie found.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public login(jwt: string) {
|
||||
Cookies.set("jwt", jwt);
|
||||
console.log("jwt cookie set");
|
||||
this.setUser(jwt);
|
||||
}
|
||||
|
||||
public logout() {
|
||||
this.user = null;
|
||||
Cookies.remove("jwt");
|
||||
console.log("Logged out.");
|
||||
this.sub.next(null);
|
||||
}
|
||||
|
||||
public get loggedIn(): boolean {
|
||||
return this.user !== undefined;
|
||||
}
|
||||
|
||||
public get auth(): string {
|
||||
return Cookies.get("jwt");
|
||||
}
|
||||
|
||||
private setUser(jwt: string) {
|
||||
this.user = jwt_decode(jwt);
|
||||
this.sub.next(this.user);
|
||||
console.log(this.user.username);
|
||||
}
|
||||
|
||||
public static get Instance(){
|
||||
return this._instance || (this._instance = new this());
|
||||
}
|
||||
}
|
37
ui/src/services/WebSocketService.ts
Normal file
37
ui/src/services/WebSocketService.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { wsUri } from '../env';
|
||||
import { LoginForm, RegisterForm, UserOperation, CommunityForm } from '../interfaces';
|
||||
import { webSocket } from 'rxjs/webSocket';
|
||||
import { Subject } from 'rxjs';
|
||||
import { UserService } from './';
|
||||
|
||||
export class WebSocketService {
|
||||
private static _instance: WebSocketService;
|
||||
public subject: Subject<{}>;
|
||||
|
||||
private constructor() {
|
||||
this.subject = webSocket(wsUri);
|
||||
console.log(`Connected to ${wsUri}`);
|
||||
}
|
||||
|
||||
public static get Instance(){
|
||||
return this._instance || (this._instance = new this());
|
||||
}
|
||||
|
||||
public login(loginForm: LoginForm) {
|
||||
this.subject.next(this.wsSendWrapper(UserOperation.Login, loginForm));
|
||||
}
|
||||
|
||||
public register(registerForm: RegisterForm) {
|
||||
this.subject.next(this.wsSendWrapper(UserOperation.Register, registerForm));
|
||||
}
|
||||
|
||||
public createCommunity(communityForm: CommunityForm) {
|
||||
this.subject.next(this.wsSendWrapper(UserOperation.CreateCommunity, communityForm, UserService.Instance.auth));
|
||||
}
|
||||
|
||||
private wsSendWrapper(op: UserOperation, data: any, auth?: string) {
|
||||
let send = { op: UserOperation[op], data: data, auth: auth };
|
||||
console.log(send);
|
||||
return send;
|
||||
}
|
||||
}
|
2
ui/src/services/index.ts
Normal file
2
ui/src/services/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { UserService } from './UserService';
|
||||
export { WebSocketService } from './WebSocketService';
|
|
@ -1,2 +1,9 @@
|
|||
import { UserOperation } from './interfaces';
|
||||
|
||||
export let repoUrl = 'https://github.com/dessalines/rust-reddit-fediverse';
|
||||
export let wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/service/ws/';
|
||||
|
||||
export function msgOp(msg: any): UserOperation {
|
||||
let opStr: string = msg.op;
|
||||
return UserOperation[opStr];
|
||||
}
|
||||
|
|
24
ui/yarn.lock
24
ui/yarn.lock
|
@ -9,6 +9,11 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@types/js-cookie@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.1.tgz#aa6f6d5e5aaf7d97959e9fa938ac2501cf1a76f4"
|
||||
integrity sha512-VIVurImEhQ95jxtjs8baVU5qCzVfwYfuMrpXwdRykJ5MCI5iY7/jB4cDSgwBVeYqeXrhT7GfJUwoDOmN0OMVCA==
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
|
@ -1456,6 +1461,11 @@ isstream@~0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
||||
js-cookie@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb"
|
||||
integrity sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
|
@ -1503,6 +1513,11 @@ jsprim@^1.2.2:
|
|||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jwt-decode@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
|
||||
integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=
|
||||
|
||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
|
@ -2446,6 +2461,13 @@ rx-lite@*, rx-lite@^4.0.8:
|
|||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
|
||||
integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
|
||||
|
||||
rxjs@^6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504"
|
||||
integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
@ -2824,7 +2846,7 @@ ts-transform-inferno@^4.0.2:
|
|||
resolved "https://registry.yarnpkg.com/ts-transform-inferno/-/ts-transform-inferno-4.0.2.tgz#06b9be45edf874ba7a6ebfb6107ba782509c6afe"
|
||||
integrity sha512-CZb4+w/2l2zikPZ/c51fi3n+qnR2HCEfAS73oGQB80aqRLffkZqm25kYYTMmqUW2+oVfs4M5AZa0z14cvxlQ5w==
|
||||
|
||||
tslib@^1.8.0:
|
||||
tslib@^1.8.0, tslib@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||
|
|
Loading…
Reference in a new issue