From a62a7cb6a2f8b4b367bd565375e537bbf1aba406 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 4 Mar 2019 08:39:07 -0800 Subject: [PATCH] Adding User and Community types. --- API.md | 62 +++--- README.md | 4 + .../2019-02-26-002946_create_user/up.sql | 10 +- .../down.sql | 4 +- .../2019-02-27-170003_create_community/up.sql | 18 +- .../down.sql | 2 - .../up.sql | 10 - .../2019-03-03-163336_create_post/down.sql | 3 + .../2019-03-03-163336_create_post/up.sql | 22 ++ server/src/actions/community.rs | 197 ++++++++++++++++++ server/src/actions/mod.rs | 4 +- server/src/actions/post.rs | 95 +++++++++ server/src/actions/src/schema.rs | 80 +++++++ server/src/actions/user.rs | 69 ++++-- server/src/activitypub.rs | 7 - server/src/lib.rs | 41 ++-- server/src/models.rs | 30 +-- server/src/schema.rs | 60 +++++- 18 files changed, 597 insertions(+), 121 deletions(-) delete mode 100644 server/migrations/2019-02-27-170402_create_community_user/down.sql delete mode 100644 server/migrations/2019-02-27-170402_create_community_user/up.sql create mode 100644 server/migrations/2019-03-03-163336_create_post/down.sql create mode 100644 server/migrations/2019-03-03-163336_create_post/up.sql create mode 100644 server/src/actions/community.rs create mode 100644 server/src/actions/post.rs create mode 100644 server/src/actions/src/schema.rs delete mode 100644 server/src/activitypub.rs diff --git a/API.md b/API.md index 47c57ac4b..8438cb9e7 100644 --- a/API.md +++ b/API.md @@ -49,7 +49,13 @@ "@context": "https://www.w3.org/ns/activitystreams", "type": "Person", "id": "https://rust-reddit-fediverse/api/v1/user/sally_smith", - "name": "sally_smith", // TODO can't find an activitypub vocab for alias. + "inbox": "https://rust-reddit-fediverse/api/v1/user/sally_smith/inbox", + "outbox": "https://rust-reddit-fediverse/api/v1/user/sally_smith/outbox", + "liked": "https://rust-reddit-fediverse/api/v1/user/sally_smith/liked", + "disliked": "https://rust-reddit-fediverse/api/v1/user/sally_smith/disliked", + "following": "https://rust-reddit-fediverse/api/v1/user/sally_smith/following", + "name": "sally_smith", + "preferredUsername": "Sally", "icon"?: { "type": "Image", "name": "User icon", @@ -63,7 +69,6 @@ ``` ### [Community / Group](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group) - ``` { "@context": "https://www.w3.org/ns/activitystreams", @@ -73,6 +78,7 @@ "attributedTo": [ // The moderators "http://joe.example.org", ], + "followers": "https://rust-reddit-fediverse/api/v1/community/today_i_learned/followers", "startTime": "2014-12-31T23:00:00-08:00", "summary"?: "The group's tagline", "attachment: [{}] // TBD, these would be where strong types for custom styles, and images would work. @@ -91,16 +97,13 @@ "url": "https://news.blah.com" "attributedTo": "http://joe.example.org", // The poster "startTime": "2014-12-31T23:00:00-08:00", - } ``` ### [Post Listings / Ordered CollectionPage](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollectionpage) - ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Page 1 of Sally's front page", "type": "OrderedCollectionPage", "id": "https://rust-reddit-fediverse/api/v1/posts?type={all, best, front}&sort={}&page=1, "partOf": "http://example.org/foo", @@ -125,11 +128,9 @@ } ``` ### [Comment Listings / Ordered CollectionPage](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollectionpage) - ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Page 1 of comments for", "type": "OrderedCollectionPage", "id": "https://rust-reddit-fediverse/api/v1/comments?type={all,user,community,post,parent_comment}&id=1&page=1, "partOf": "http://example.org/foo", @@ -147,13 +148,16 @@ ``` ## Actions +- These are all posts to a user's outbox. +- The server then creates a post to the necessary inbox of the recipient, or the followers. +- Whenever a user accesses the site, they do a get from their inbox. + ### Comments #### [Create](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-create) ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally created a note", "type": "Create", "actor": id, "object": comment_id, or post_id @@ -164,7 +168,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally deleted a note", "type": "Delete", "actor": id, "object": comment_id, or post_id @@ -174,7 +177,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally created a note", "type": "Create", "actor": id, "object": comment_id, or post_id @@ -186,7 +188,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally read a comment", "type": "Read", "actor": user_id "object": comment_id @@ -194,10 +195,10 @@ ``` #### [Like](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-like) +- TODO: Should likes be notifications? IE, have a to? ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally liked a comment", "type": "Like", "actor": user_id "object": comment_id @@ -208,7 +209,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally disliked a comment", "type": "Dislike", "actor": user_id "object": comment_id @@ -221,9 +221,9 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally created a post", "type": "Create", "actor": id, + "to": community_id/followers "object": post_id } ``` @@ -231,7 +231,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally deleted a post", "type": "Delete", "actor": id, "object": comment_id, or post_id @@ -242,7 +241,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally created a post", "type": "Create", "actor": id, "object": comment_id, or post_id @@ -253,7 +251,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally read a post", "type": "Read", "actor": user_id "object": post_id @@ -265,7 +262,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally created a community", "type": "Create", "actor": id, "object": community_id @@ -275,7 +271,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally deleted a community", "type": "Delete", "actor": id, "object": community_id @@ -286,7 +281,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally created a community", "type": "Create", "actor": id, "object": community_id @@ -294,11 +288,29 @@ } ``` -#### [Join](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-join) +#### [Follow / Subscribe](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-follow) +``` +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Follow", + "actor": id + "object": community_id +} +``` + +#### [Ignore/ Unsubscribe](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-ignore) +``` +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Follow", + "actor": id + "object": community_id +} +``` +#### [Join / Become a Mod](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-join) ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally joined a community", "type": "Join", "actor": user_id, "object": community_id @@ -309,7 +321,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally left a community", "type": "Leave", "actor": user_id, "object": community_id @@ -321,7 +332,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "The moderator blocked Sally from a group", "type": "Remove", "actor": mod_id, "object": user_id, @@ -333,7 +343,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally deleted a users comment", "type": "Delete", "actor": id, "object": community_id @@ -344,7 +353,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "Sally invited John to mod a community", "type": "Invite", "id": "https://rust-reddit-fediverse/api/v1/invite/1", "actor": sally_id, @@ -356,7 +364,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "John Accepted an invitation to mod a community", "type": "Accept", "actor": john_id, "object": invite_id @@ -366,7 +373,6 @@ ``` { "@context": "https://www.w3.org/ns/activitystreams", - "summary": "John Rejected an invitation to mod a community", "type": "Reject", "actor": john_id, "object": invite_id diff --git a/README.md b/README.md index 4802a905a..9b61044a7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ We have a twitter alternative (mastodon), a facebook alternative (friendica), so - Must have communities. - Must have threaded comments. - Must be federated: liking and following communities across instances. +- Be live-updating: have a right pane for new comments, and a main pain for the full threaded view. + - Use websockets for post / gets to your own instance. ## Questions - How does voting work? Should we go back to the old way of showing up and downvote counts? Or just a score? @@ -25,5 +27,7 @@ We have a twitter alternative (mastodon), a facebook alternative (friendica), so - Use the [activitypub crate.](https://docs.rs/activitypub/0.1.4/activitypub/) - https://docs.rs/activitypub/0.1.4/activitypub/ - [Activitypub vocab.](https://www.w3.org/TR/activitystreams-vocabulary/) +- [Activitypub main](https://www.w3.org/TR/activitypub/) - [Diesel to Postgres data types](https://kotiri.com/2018/01/31/postgresql-diesel-rust-types.html) - [helpful diesel examples](http://siciarz.net/24-days-rust-diesel/) +- [Mastodan public key server example](https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/) diff --git a/server/migrations/2019-02-26-002946_create_user/up.sql b/server/migrations/2019-02-26-002946_create_user/up.sql index 4f9984b7d..a4ba1e880 100644 --- a/server/migrations/2019-02-26-002946_create_user/up.sql +++ b/server/migrations/2019-02-26-002946_create_user/up.sql @@ -1,8 +1,10 @@ create table user_ ( id serial primary key, name varchar(20) not null, - password_encrypted varchar(200) not null, - email varchar(200), + preferred_username varchar(20), + password_encrypted text not null, + email text, icon bytea, - startTime timestamp not null default now() -) + start_time timestamp not null default now() +); + diff --git a/server/migrations/2019-02-27-170003_create_community/down.sql b/server/migrations/2019-02-27-170003_create_community/down.sql index 5e751d436..5e6065f81 100644 --- a/server/migrations/2019-02-27-170003_create_community/down.sql +++ b/server/migrations/2019-02-27-170003_create_community/down.sql @@ -1 +1,3 @@ -drop table community +drop table community_user; +drop table community_follower; +drop table community; diff --git a/server/migrations/2019-02-27-170003_create_community/up.sql b/server/migrations/2019-02-27-170003_create_community/up.sql index c5eafc7b4..692a5f06b 100644 --- a/server/migrations/2019-02-27-170003_create_community/up.sql +++ b/server/migrations/2019-02-27-170003_create_community/up.sql @@ -1,5 +1,19 @@ create table community ( id serial primary key, name varchar(20) not null, - starttime timestamp not null default now() -) + start_time timestamp not null default now() +); + +create table community_user ( + id serial primary key, + community_id int references community on update cascade on delete cascade not null, + fedi_user_id text not null, + start_time timestamp not null default now() +); + +create table community_follower ( + id serial primary key, + community_id int references community on update cascade on delete cascade not null, + fedi_user_id text not null, + start_time timestamp not null default now() +); diff --git a/server/migrations/2019-02-27-170402_create_community_user/down.sql b/server/migrations/2019-02-27-170402_create_community_user/down.sql deleted file mode 100644 index a1571c72b..000000000 --- a/server/migrations/2019-02-27-170402_create_community_user/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -drop table community_user; --- drop type community_user_type; diff --git a/server/migrations/2019-02-27-170402_create_community_user/up.sql b/server/migrations/2019-02-27-170402_create_community_user/up.sql deleted file mode 100644 index f60eea46a..000000000 --- a/server/migrations/2019-02-27-170402_create_community_user/up.sql +++ /dev/null @@ -1,10 +0,0 @@ --- No support for types yet, so just do 0,1,2 --- create type community_user_type as enum ('creator', 'moderator', 'user'); - -create table community_user ( - id serial primary key, - fedi_user_id varchar(100) not null, - community_id int references community on update cascade on delete cascade, - community_user_type smallint not null default 2, - starttime timestamp not null default now() -) diff --git a/server/migrations/2019-03-03-163336_create_post/down.sql b/server/migrations/2019-03-03-163336_create_post/down.sql new file mode 100644 index 000000000..a57839659 --- /dev/null +++ b/server/migrations/2019-03-03-163336_create_post/down.sql @@ -0,0 +1,3 @@ +drop table post_like; +drop table post_dislike; +drop table post; diff --git a/server/migrations/2019-03-03-163336_create_post/up.sql b/server/migrations/2019-03-03-163336_create_post/up.sql new file mode 100644 index 000000000..4a811fa2d --- /dev/null +++ b/server/migrations/2019-03-03-163336_create_post/up.sql @@ -0,0 +1,22 @@ +create table post ( + id serial primary key, + name varchar(100) not null, + url text not null, + attributed_to text not null, + start_time timestamp not null default now() +); + +create table post_like ( + id serial primary key, + fedi_user_id text not null, + post_id int references post on update cascade on delete cascade, + start_time timestamp not null default now() +); + +create table post_dislike ( + id serial primary key, + fedi_user_id text not null, + post_id int references post on update cascade on delete cascade, + start_time timestamp not null default now() +); + diff --git a/server/src/actions/community.rs b/server/src/actions/community.rs new file mode 100644 index 000000000..6b0bea8d5 --- /dev/null +++ b/server/src/actions/community.rs @@ -0,0 +1,197 @@ +extern crate diesel; +use schema::{community, community_user, community_follower}; +use diesel::*; +use diesel::result::Error; +use {Crud, Followable, Joinable}; + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="community"] +pub struct Community { + pub id: i32, + pub name: String, + pub start_time: chrono::NaiveDateTime +} + +#[derive(Insertable, AsChangeset, Clone, Copy)] +#[table_name="community"] +pub struct CommunityForm<'a> { + pub name: &'a str, +} + +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Community)] +#[table_name = "community_user"] +pub struct CommunityUser { + pub id: i32, + pub community_id: i32, + pub fedi_user_id: String, + pub start_time: chrono::NaiveDateTime +} + +#[derive(Insertable, AsChangeset, Clone, Copy)] +#[table_name="community_user"] +pub struct CommunityUserForm<'a> { + pub community_id: &'a i32, + pub fedi_user_id: &'a str, +} + +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[belongs_to(Community)] +#[table_name = "community_follower"] +pub struct CommunityFollower { + pub id: i32, + pub community_id: i32, + pub fedi_user_id: String, + pub start_time: chrono::NaiveDateTime +} + +#[derive(Insertable, AsChangeset, Clone, Copy)] +#[table_name="community_follower"] +pub struct CommunityFollowerForm<'a> { + pub community_id: &'a i32, + pub fedi_user_id: &'a str, +} + + +impl<'a> Crud> for Community { + fn read(conn: &PgConnection, community_id: i32) -> Community { + use schema::community::dsl::*; + community.find(community_id) + .first::(conn) + .expect("Error in query") + } + + fn delete(conn: &PgConnection, community_id: i32) -> usize { + use schema::community::dsl::*; + diesel::delete(community.find(community_id)) + .execute(conn) + .expect("Error deleting.") + } + + fn create(conn: &PgConnection, new_community: CommunityForm) -> Result { + use schema::community::dsl::*; + insert_into(community) + .values(new_community) + .get_result::(conn) + } + + fn update(conn: &PgConnection, community_id: i32, new_community: CommunityForm) -> Community { + use schema::community::dsl::*; + diesel::update(community.find(community_id)) + .set(new_community) + .get_result::(conn) + .expect(&format!("Unable to find {}", community_id)) + } +} + +impl<'a> Followable> for CommunityFollower { + fn follow(conn: &PgConnection, community_follower_form: CommunityFollowerForm) -> Result { + use schema::community_follower::dsl::*; + insert_into(community_follower) + .values(community_follower_form) + .get_result::(conn) + } + fn ignore(conn: &PgConnection, community_follower_form: CommunityFollowerForm) -> usize { + 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))) + .execute(conn) + .expect("Error deleting.") + } +} + +impl<'a> Joinable> for CommunityUser { + fn join(conn: &PgConnection, community_user_form: CommunityUserForm) -> Result { + use schema::community_user::dsl::*; + insert_into(community_user) + .values(community_user_form) + .get_result::(conn) + } + fn leave(conn: &PgConnection, community_user_form: CommunityUserForm) -> usize { + 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))) + .execute(conn) + .expect("Error deleting.") + } +} + +#[cfg(test)] +mod tests { + use establish_connection; + use super::*; + use actions::user::*; + use Crud; + #[test] + fn test_crud() { + let conn = establish_connection(); + + let new_community = CommunityForm { + name: "TIL".into(), + }; + + let inserted_community = Community::create(&conn, new_community).unwrap(); + + let expected_community = Community { + id: inserted_community.id, + name: "TIL".into(), + start_time: inserted_community.start_time + }; + + let new_user = UserForm { + name: "thom".into(), + preferred_username: None, + password_encrypted: "nope".into(), + email: None + }; + + let inserted_user = User_::create(&conn, new_user).unwrap(); + + let community_follower_form = CommunityFollowerForm { + community_id: &inserted_community.id, + fedi_user_id: "test".into() + }; + + let inserted_community_follower = CommunityFollower::follow(&conn, community_follower_form).unwrap(); + + let expected_community_follower = CommunityFollower { + id: inserted_community_follower.id, + community_id: inserted_community.id, + fedi_user_id: "test".into(), + start_time: inserted_community_follower.start_time + }; + + let community_user_form = CommunityUserForm { + community_id: &inserted_community.id, + fedi_user_id: "test".into() + }; + + let inserted_community_user = CommunityUser::join(&conn, community_user_form).unwrap(); + + let expected_community_user = CommunityUser { + id: inserted_community_user.id, + community_id: inserted_community.id, + fedi_user_id: "test".into(), + start_time: inserted_community_user.start_time + }; + + 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); + + assert_eq!(expected_community, read_community); + assert_eq!(expected_community, inserted_community); + assert_eq!(expected_community, updated_community); + assert_eq!(expected_community_follower, inserted_community_follower); + assert_eq!(expected_community_user, inserted_community_user); + assert_eq!(1, ignored_community); + assert_eq!(1, left_community); + assert_eq!(1, num_deleted); + + } +} diff --git a/server/src/actions/mod.rs b/server/src/actions/mod.rs index bc675369c..df10dbbce 100644 --- a/server/src/actions/mod.rs +++ b/server/src/actions/mod.rs @@ -1,3 +1,3 @@ -use diesel::*; pub mod user; - +pub mod community; +pub mod post; diff --git a/server/src/actions/post.rs b/server/src/actions/post.rs new file mode 100644 index 000000000..f939fc7c2 --- /dev/null +++ b/server/src/actions/post.rs @@ -0,0 +1,95 @@ +extern crate diesel; +use schema::user_; +use diesel::*; +use diesel::result::Error; +use schema::user_::dsl::*; + +#[derive(Queryable, PartialEq, Debug)] +pub struct User_ { + pub id: i32, + pub name: String, + pub preferred_username: Option, + pub password_encrypted: String, + pub email: Option, + pub icon: Option>, + pub start_time: chrono::NaiveDateTime +} + +#[derive(Insertable, AsChangeset, Clone, Copy)] +#[table_name="user_"] +pub struct NewUser<'a> { + pub name: &'a str, + pub preferred_username: Option<&'a str>, + pub password_encrypted: &'a str, + pub email: Option<&'a str>, +} + +pub fn read(conn: &PgConnection, user_id: i32) -> User_ { + user_.find(user_id) + .first::(conn) + .expect("Error in query") +} + +pub fn delete(conn: &PgConnection, user_id: i32) -> usize { + diesel::delete(user_.find(user_id)) + .execute(conn) + .expect("Error deleting.") +} + +pub fn create(conn: &PgConnection, new_user: &NewUser) -> Result { + let mut edited_user = new_user.clone(); + // Add the rust crypt + edited_user.password_encrypted = "here"; + // edited_user.password_encrypted; + insert_into(user_) + .values(edited_user) + .get_result::(conn) +} + +pub fn update(conn: &PgConnection, user_id: i32, new_user: &NewUser) -> User_ { + let mut edited_user = new_user.clone(); + edited_user.password_encrypted = "here"; + diesel::update(user_.find(user_id)) + .set(edited_user) + .get_result::(conn) + .expect(&format!("Unable to find user {}", user_id)) +} + +#[cfg(test)] +mod tests { + use establish_connection; + use super::*; + #[test] + fn test_crud() { + let conn = establish_connection(); + + let new_user = NewUser { + name: "thom".into(), + preferred_username: None, + password_encrypted: "nope".into(), + email: None + }; + + let inserted_user = create(&conn, &new_user).unwrap(); + + let expected_user = User_ { + id: inserted_user.id, + name: "thom".into(), + preferred_username: None, + password_encrypted: "here".into(), + email: None, + icon: None, + start_time: inserted_user.start_time + }; + + let read_user = read(&conn, inserted_user.id); + let updated_user = update(&conn, inserted_user.id, &new_user); + let num_deleted = delete(&conn, inserted_user.id); + + assert_eq!(expected_user, read_user); + assert_eq!(expected_user, inserted_user); + assert_eq!(expected_user, updated_user); + assert_eq!(1, num_deleted); + + } +} diff --git a/server/src/actions/src/schema.rs b/server/src/actions/src/schema.rs new file mode 100644 index 000000000..580b82e7b --- /dev/null +++ b/server/src/actions/src/schema.rs @@ -0,0 +1,80 @@ +table! { + community (id) { + id -> Int4, + name -> Varchar, + start_time -> Timestamp, + } +} + +table! { + community_follower (id) { + id -> Int4, + fedi_user_id -> Text, + community_id -> Nullable, + start_time -> Timestamp, + } +} + +table! { + community_user (id) { + id -> Int4, + fedi_user_id -> Text, + community_id -> Nullable, + start_time -> Timestamp, + } +} + +table! { + post (id) { + id -> Int4, + name -> Varchar, + url -> Text, + attributed_to -> Text, + start_time -> Timestamp, + } +} + +table! { + post_dislike (id) { + id -> Int4, + fedi_user_id -> Text, + post_id -> Nullable, + start_time -> Timestamp, + } +} + +table! { + post_like (id) { + id -> Int4, + fedi_user_id -> Text, + post_id -> Nullable, + start_time -> Timestamp, + } +} + +table! { + user_ (id) { + id -> Int4, + name -> Varchar, + preferred_username -> Nullable, + password_encrypted -> Text, + email -> Nullable, + icon -> Nullable, + start_time -> Timestamp, + } +} + +joinable!(community_follower -> community (community_id)); +joinable!(community_user -> community (community_id)); +joinable!(post_dislike -> post (post_id)); +joinable!(post_like -> post (post_id)); + +allow_tables_to_appear_in_same_query!( + community, + community_follower, + community_user, + post, + post_dislike, + post_like, + user_, +); diff --git a/server/src/actions/user.rs b/server/src/actions/user.rs index 2a017fd28..36222f834 100644 --- a/server/src/actions/user.rs +++ b/server/src/actions/user.rs @@ -1,83 +1,108 @@ extern crate diesel; +extern crate activitypub; use schema::user_; use diesel::*; use diesel::result::Error; use schema::user_::dsl::*; +// use self::activitypub::{context, actor::Person}; use Crud; -#[derive(Queryable, PartialEq, Debug)] +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="user_"] pub struct User_ { pub id: i32, pub name: String, + pub preferred_username: Option, pub password_encrypted: String, pub email: Option, pub icon: Option>, pub start_time: chrono::NaiveDateTime } -#[derive(Insertable)] +#[derive(Insertable, AsChangeset, Clone, Copy)] #[table_name="user_"] -pub struct NewUser<'a> { +pub struct UserForm<'a> { pub name: &'a str, + pub preferred_username: Option<&'a str>, pub password_encrypted: &'a str, pub email: Option<&'a str>, } -impl Crud for User_ { +impl<'a> Crud> for User_ { fn read(conn: &PgConnection, user_id: i32) -> User_ { user_.find(user_id) .first::(conn) .expect("Error in query") } - - fn delete(conn: &PgConnection, user_id: i32) -> usize { + fn delete(conn: &PgConnection, user_id: i32) -> usize { diesel::delete(user_.find(user_id)) .execute(conn) .expect("Error deleting.") } - -// fn create(conn: &PgConnection, mut new_user: NewUser) -> Result { + fn create(conn: &PgConnection, form: UserForm) -> Result { + 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::(conn) + } + fn update(conn: &PgConnection, user_id: i32, form: UserForm) -> User_ { + let mut edited_user = form.clone(); + edited_user.password_encrypted = "here"; + diesel::update(user_.find(user_id)) + .set(edited_user) + .get_result::(conn) + .expect(&format!("Unable to find user {}", user_id)) + } } -pub fn create(conn: &PgConnection, mut new_user: NewUser) -> Result { - new_user.password_encrypted = "here"; - // new_user.password_encrypted; - insert_into(user_) - .values(new_user) - .get_result(conn) -} + +// TODO +// pub fn person(user: &User_) -> Person { +// let mut person = Person::default(); +// person.object_props.set_context_object(context()); +// person.ap_actor_props.set_preferred_username_string("set".into()); + +// person +// } #[cfg(test)] mod tests { use establish_connection; - use super::*; + use super::{User_, UserForm}; + use Crud; #[test] fn test_crud() { let conn = establish_connection(); - let new_user = NewUser { + let new_user = UserForm { name: "thom".into(), + preferred_username: None, password_encrypted: "nope".into(), email: None }; - let inserted_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(), email: None, icon: None, start_time: inserted_user.start_time }; - - let read_user = User_::read(&conn, inserted_user.id); - let num_deleted = User_::delete(&conn, inserted_user.id); + 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); + assert_eq!(expected_user, read_user); assert_eq!(expected_user, inserted_user); + assert_eq!(expected_user, updated_user); assert_eq!(1, num_deleted); - } } diff --git a/server/src/activitypub.rs b/server/src/activitypub.rs deleted file mode 100644 index 3e6c46e48..000000000 --- a/server/src/activitypub.rs +++ /dev/null @@ -1,7 +0,0 @@ -extern crate activitypub; -extern crate failure; -extern crate serde_json; - -// fn user -> Result<(), Error> { - -// } diff --git a/server/src/lib.rs b/server/src/lib.rs index 77c8ee5e3..e8a67c3de 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -2,16 +2,35 @@ extern crate diesel; extern crate dotenv; -use diesel::prelude::*; +use diesel::*; use diesel::pg::PgConnection; +use diesel::result::Error; use dotenv::dotenv; use std::env; pub mod schema; pub mod models; -pub mod activitypub; pub mod actions; +// pub trait Likeable; +pub trait Crud { + fn create(conn: &PgConnection, form: T) -> Result 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; +} + +pub trait Followable { + fn follow(conn: &PgConnection, form: T) -> Result where Self: Sized; + fn ignore(conn: &PgConnection, form: T) -> usize; +} + +pub trait Joinable { + fn join(conn: &PgConnection, form: T) -> Result where Self: Sized; + fn leave(conn: &PgConnection, form: T) -> usize; +} + + pub fn establish_connection() -> PgConnection { dotenv().ok(); @@ -21,21 +40,3 @@ pub fn establish_connection() -> PgConnection { .expect(&format!("Error connecting to {}", database_url)) } -trait Crud { - fn read(conn: &PgConnection, id: i32) -> Self; - fn delete(conn: &PgConnection, id: i32) -> usize; - // fn create(conn: &PgConnection, item: T) -> Result where Self: Sized; -} - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } - - #[test] - fn db_fetch() { - - } -} diff --git a/server/src/models.rs b/server/src/models.rs index cb5eaef70..c895f3ea5 100644 --- a/server/src/models.rs +++ b/server/src/models.rs @@ -1,19 +1,19 @@ -enum CommunityUserType { - CREATOR = 0, - MODERATOR = 1, - USER = 2 -} +// enum CommunityUserType { +// CREATOR = 0, +// MODERATOR = 1, +// USER = 2 +// } -impl CommunityUserType { - fn from_u32(value: u32) -> CommunityUserType { - match value { - 0 => CommunityUserType::CREATOR, - 1 => CommunityUserType::MODERATOR, - 2 => CommunityUserType::USER, - _ => panic!("Unknown value: {}", value), - } - } -} +// impl CommunityUserType { +// fn from_u32(value: u32) -> CommunityUserType { +// match value { +// 0 => CommunityUserType::CREATOR, +// 1 => CommunityUserType::MODERATOR, +// 2 => CommunityUserType::USER, +// _ => panic!("Unknown value: {}", value), +// } +// } +// } diff --git a/server/src/schema.rs b/server/src/schema.rs index a9f37009f..cf90c0475 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -2,17 +2,53 @@ table! { community (id) { id -> Int4, name -> Varchar, - starttime -> Timestamp, + start_time -> Timestamp, + } +} + +table! { + community_follower (id) { + id -> Int4, + community_id -> Int4, + fedi_user_id -> Text, + start_time -> Timestamp, } } table! { community_user (id) { id -> Int4, - fedi_user_id -> Varchar, - community_id -> Nullable, - community_user_type -> Int2, - starttime -> Timestamp, + community_id -> Int4, + fedi_user_id -> Text, + start_time -> Timestamp, + } +} + +table! { + post (id) { + id -> Int4, + name -> Varchar, + url -> Text, + attributed_to -> Text, + start_time -> Timestamp, + } +} + +table! { + post_dislike (id) { + id -> Int4, + fedi_user_id -> Text, + post_id -> Nullable, + start_time -> Timestamp, + } +} + +table! { + post_like (id) { + id -> Int4, + fedi_user_id -> Text, + post_id -> Nullable, + start_time -> Timestamp, } } @@ -20,17 +56,25 @@ table! { user_ (id) { id -> Int4, name -> Varchar, - password_encrypted -> Varchar, - email -> Nullable, + preferred_username -> Nullable, + password_encrypted -> Text, + email -> Nullable, icon -> Nullable, - starttime -> Timestamp, + start_time -> Timestamp, } } +joinable!(community_follower -> community (community_id)); joinable!(community_user -> community (community_id)); +joinable!(post_dislike -> post (post_id)); +joinable!(post_like -> post (post_id)); allow_tables_to_appear_in_same_query!( community, + community_follower, community_user, + post, + post_dislike, + post_like, user_, );