gourami

[UNMAINTAINED] Activitypub server in Rust
Log | Files | Refs | README | LICENSE

commit 0ade28150f6314a735613409391250ca7612524b
parent 0349055830f27424c9af34af2e0a8fec8d2a5b79
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Tue, 28 Apr 2020 18:21:23 -0500

Add neighborhood basic functionality

Diffstat:
MTODO | 16++++++++++++++++
Ahack/announce.sql | 5+++++
Amigrations/2020-04-28-222918_neighborhood/down.sql | 2++
Amigrations/2020-04-28-222918_neighborhood/up.sql | 5+++++
Msrc/db/note.rs | 6++++--
Msrc/db/schema.rs | 1+
Msrc/lib.rs | 41++++++++++++++++++++++++++++++++++-------
Msrc/routes.rs | 14++++++++++----
Mtemplates/base.html | 2+-
Mtemplates/createnote.html | 7+++++++
Atemplates/neighborhood.html | 0
Mtemplates/server_info.html | 2+-
Mtemplates/user.html | 1-
13 files changed, 86 insertions(+), 16 deletions(-)

diff --git a/TODO b/TODO @@ -1,3 +1,19 @@ +CORE FEATURES + +Proper threading +- Render all DESCENDENTS +(allow for replies to multiple items...) + +Notifications +- Notify on tag + +Replace notes with >> + +Federation / ActivityPub +retryeets +following/followers & separate timelines +text cant exist after URLs + REFACTORING -- askama stuff diff --git a/hack/announce.sql b/hack/announce.sql @@ -0,0 +1,5 @@ +insert into notifications (notification_html, server_message) +values("New feature alert! Added notifications, made a few other minor tweaks", true); +-- TODO find a better way +insert into notification_viewers (notification_id, user_id, viewed) +select 6,id,false from users; diff --git a/migrations/2020-04-28-222918_neighborhood/down.sql b/migrations/2020-04-28-222918_neighborhood/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` +\ No newline at end of file diff --git a/migrations/2020-04-28-222918_neighborhood/up.sql b/migrations/2020-04-28-222918_neighborhood/up.sql @@ -0,0 +1,5 @@ +-- Your SQL goes here + +Alter table notes +add neighborhood BOOLEAN default false; + diff --git a/src/db/note.rs b/src/db/note.rs @@ -13,10 +13,11 @@ use crate::db::user::User; // weird import pub struct Note { // rename RenderedNote pub id: i32, pub user_id: i32, - pub parent_id: Option<i32>, + pub in_reply_to: Option<i32>, // deserialize wiht pub content: String, pub created_time: String, + pub neighborhood: bool, } /// Content in the DB is stored in plaintext (WILL BE) @@ -36,10 +37,11 @@ pub struct NoteInput { pub user_id: i32, pub content: String, // can we make this a slice? pub in_reply_to: Option<i32>, + pub neighborhood: bool, } +/// We render the first >>[num] or note emoji as a reply, for threading. pub fn get_reply(note_text: &str) -> Option<i32> { - /// We render the first >>[num] or note emoji as a reply, for threading. let re = Regex::new(r"\B(📝|>>)(\d+)").unwrap(); match re.captures(note_text) { Some(t) => t.get(2).unwrap().as_str().parse().ok(), diff --git a/src/db/schema.rs b/src/db/schema.rs @@ -5,6 +5,7 @@ table! { in_reply_to -> Nullable<Integer>, content -> Text, created_time -> Timestamp, + neighborhood -> Bool, } } diff --git a/src/lib.rs b/src/lib.rs @@ -130,9 +130,10 @@ fn delete_note(note_id: i32)-> Result<(), Box<dyn std::error::Error>> { struct NewNoteRequest { note_input: String, // has to be String redirect_url: String, + neighborhood: Option<String>, // "on" } -fn new_note(auth_user: User, note_input: &str) -> Result<(), Box<dyn std::error::Error>> { +fn new_note(auth_user: User, note_input: &str, neighborhood: bool,) -> Result<(), Box<dyn std::error::Error>> { use db::schema::notes::dsl as notes; // create activitypub activity object // TODO -- micropub? @@ -143,7 +144,8 @@ fn new_note(auth_user: User, note_input: &str) -> Result<(), Box<dyn std::error: let new_note = NoteInput{ user_id: auth_user.id, in_reply_to: reply, - content: parsed_note_text + content: parsed_note_text, + neighborhood: neighborhood }; insert_into(notes::notes).values(new_note).execute(conn)?; // notify person u reply to @@ -319,7 +321,7 @@ struct TimelineTemplate<'a>{ struct GetPostsParams { #[serde(default = "default_page")] page_num: i64, - user_id: Option<i32> + user_id: Option<i32>, } fn default_page() -> i64 { 1 @@ -329,7 +331,7 @@ impl Default for GetPostsParams { fn default() -> Self { GetPostsParams { page_num: 1, - user_id: None + user_id: None, } } } @@ -351,7 +353,7 @@ fn get_single_note(note_id: i32) -> Option<Vec<UserNote>> { } /// We have to do a join here -fn get_notes(params: GetPostsParams) -> Result<Vec<UserNote>, diesel::result::Error> { +fn get_notes(params: GetPostsParams, neighborhood: Option<bool>) -> Result<Vec<UserNote>, diesel::result::Error> { use db::schema::notes::dsl as n; use db::schema::users::dsl as u; const PAGE_SIZE: i64 = 250; @@ -362,6 +364,10 @@ fn get_notes(params: GetPostsParams) -> Result<Vec<UserNote>, diesel::result::Er if let Some(u_id) = params.user_id { query = query.filter(u::id.eq(u_id)); } + match neighborhood { + Some(n) => query = query.filter(n::neighborhood.eq(n)), + None => () + } let results = query.load::<(Note, User)>(&POOL.get().unwrap()).unwrap(); // TODO get rid of unwrap Ok(results.into_iter().map(|a| UserNote{note: a.0, username: a.1.username}).collect()) } @@ -404,7 +410,8 @@ fn render_timeline(auth_user: Option<User>, params:GetPostsParams, url_path: Ful // no session -- anonymous // pulls a bunch of data i dont really need let header = Global::create(auth_user, url_path.as_str()); - let notes = get_notes(params); + // TODO -- ignore neighborhood replies + let notes = get_notes(params, None); match notes { Ok(n) => render_template(&TimelineTemplate{ global: header, @@ -416,6 +423,26 @@ fn render_timeline(auth_user: Option<User>, params:GetPostsParams, url_path: Ful } #[derive(Template)] +#[template(path = "neighborhood.html")] +struct NeighborhoodTemplate<'a>{ + global: Global<'a>, + notes: Vec<UserNote>, +} // TODO reconsider structure + +fn render_neighborhood(auth_user: Option<User>, params:GetPostsParams, url_path: FullPath) -> impl Reply{ + let header = Global::create(auth_user, url_path.as_str()); + let notes = get_notes(params, Some(true)); + match notes { + Ok(n) => render_template(&TimelineTemplate{ + global: header, + notes: n, + }), + _ => render_template(&ErrorTemplate{global: header, error_message: "Could not fetch notes", ..Default::default()}) + } + +} + +#[derive(Template)] #[template(path = "server_info.html")] struct ServerInfoTemplate<'a> { global: Global<'a>, @@ -479,7 +506,7 @@ fn user_page(auth_user: Option<User>, user_name: String, mut params: GetPostsPar .ok(); if let Some(u) = user { params.user_id = Some(u.id); - let notes = get_notes(params).unwrap(); + let notes = get_notes(params, None).unwrap(); render_template(&UserTemplate{ global: header, page: &u.username, diff --git a/src/routes.rs b/src/routes.rs @@ -18,6 +18,12 @@ pub async fn run_server() { .and(path::full()) .map(render_timeline); + let neighborhood = warp::path("neighborhood") + .and(session_filter()) + .and(query()) + .and(path::full()) + .map(render_neighborhood); + let user_page = session_filter() .and(path!("user" / String)) .and(form()) @@ -75,9 +81,9 @@ pub async fn run_server() { // Verbose -- see if you can refactor .map(|u: Option<User>, f: NewNoteRequest| match u { Some(u) => { - new_note(u, &f.note_input).unwrap(); // TODO fix unwrap - // let red_url: http::Uri = f.redirect_url.parse().unwrap(); - redirect(http::Uri::from_static("/"))}, + new_note(u, &f.note_input, f.neighborhood.is_some()).unwrap(); + let red_url: http::Uri = f.redirect_url.parse().unwrap(); + redirect(red_url)}, None => redirect(http::Uri::from_static("error"))}); let delete_note = path("delete_note") @@ -124,7 +130,7 @@ pub async fn run_server() { // TODO secure against xss // used for api based authentication // let api_filter = session::create_session_filter(&POOL.get()); - let html_renders = home.or(login_page).or(register_page).or(user_page).or(note_page).or(server_info_page).or(notification_page).or(user_edit_page); + let html_renders = home.or(login_page).or(register_page).or(user_page).or(note_page).or(server_info_page).or(notification_page).or(user_edit_page).or(neighborhood); let forms = do_register.or(do_login).or(do_logout).or(create_note).or(delete_note).or(edit_user); // let api // catch all for any other paths diff --git a/templates/base.html b/templates/base.html @@ -19,7 +19,7 @@ <div class="title">🐟{{global.title}}/<wbr>{{global.page_title}}</div> </div> <div class="navlinks"> - <a href="/">timeline</a> <a href="/server">server</a>{% if global.logged_in %} + <a href="/">local</a> <a href="/neighborhood">neighborhood</a> <a href="/server">server</a>{% if global.logged_in %} <a href="/notifications">n({{global.unread_notifications}})</a> <a href="/user/{{global.me.username}}">@{{global.me.username}}</a>{% else %} <a href="login">login</a>{% endif %} </div> </div> diff --git a/templates/createnote.html b/templates/createnote.html @@ -3,6 +3,13 @@ <textarea id="note_input" name="note_input" rows=3 placeholder="note"></textarea> <br> <button id="post" class="submit-button-style">create note</button> +<input type="checkbox" id="neighborhood" name="neighborhood" +{% if global.page == "/neighborhood" %} +checked> +{% else %} +unchecked> +{% endif %} +<label for="neighborhood">Share with neighborhood?</label> <input type="hidden" name="redirect_url" value="{{global.page}}"> </form> </div> diff --git a/templates/neighborhood.html b/templates/neighborhood.html diff --git a/templates/server_info.html b/templates/server_info.html @@ -6,7 +6,7 @@ <br> Check out <a href="https://git.sr.ht/~alexwennerberg/gourami-social/tree/master/PHILOSOPHY.md">this document</a> where I discuss some of the design principles and goals of Gourami. <br> - This instance is invite-only. Contact me to get an invite URL if you want to invite someone. Posts are not visible except to users on this instance. + This instance is invite-only. Contact me to get an invite URL if you want to invite someone. Posts are not visible except to users on this instance, unless they are shared with the "neighborhood", the network of users and other servers networked with this server. Here is a list of those servers: (TBD) <br> Contact email: <a href="mailto:alex@alexwennerberg.com">alex@alexwennerberg.com</a> or <a href="/user/alex">message me</a> on Gourami. </div> diff --git a/templates/user.html b/templates/user.html @@ -16,7 +16,6 @@ </div> {% endif %} </div> -{% include "createnote.html" %} {% include "noteslist.html" %} </div> {% endblock %}