From 80f76f2efd71ec86152ea0d33cc663e781a23642 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 31 Dec 2019 13:55:33 +0100 Subject: [PATCH 01/19] Use actix config to handle routes in seperate folders (#378) --- server/src/lib.rs | 4 +- server/src/main.rs | 266 ++------------------------- server/src/routes/federation.rs | 18 ++ server/src/{ => routes}/feeds.rs | 18 +- server/src/routes/index.rs | 45 +++++ server/src/routes/mod.rs | 6 + server/src/{ => routes}/nodeinfo.rs | 9 +- server/src/{ => routes}/webfinger.rs | 12 +- server/src/routes/websocket.rs | 179 ++++++++++++++++++ 9 files changed, 294 insertions(+), 263 deletions(-) create mode 100644 server/src/routes/federation.rs rename server/src/{ => routes}/feeds.rs (94%) create mode 100644 server/src/routes/index.rs create mode 100644 server/src/routes/mod.rs rename server/src/{ => routes}/nodeinfo.rs (84%) rename server/src/{ => routes}/webfinger.rs (89%) create mode 100644 server/src/routes/websocket.rs diff --git a/server/src/lib.rs b/server/src/lib.rs index cddd5b860..e23ec4ba1 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -25,12 +25,10 @@ pub extern crate strum; pub mod api; pub mod apub; pub mod db; -pub mod feeds; -pub mod nodeinfo; +pub mod routes; pub mod schema; pub mod settings; pub mod version; -pub mod webfinger; pub mod websocket; use crate::settings::Settings; diff --git a/server/src/main.rs b/server/src/main.rs index 52c395d32..bf105323c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,186 +2,18 @@ extern crate lemmy_server; #[macro_use] extern crate diesel_migrations; -use actix::prelude::*; -use actix_files::NamedFile; use actix_web::*; -use actix_web_actors::ws; -use lemmy_server::apub; use lemmy_server::db::establish_connection; -use lemmy_server::feeds; -use lemmy_server::nodeinfo; +use lemmy_server::routes::federation; +use lemmy_server::routes::feeds; +use lemmy_server::routes::index; +use lemmy_server::routes::nodeinfo; +use lemmy_server::routes::webfinger; +use lemmy_server::routes::websocket; use lemmy_server::settings::Settings; -use lemmy_server::webfinger; -use lemmy_server::websocket::server::*; -use std::time::{Duration, Instant}; embed_migrations!(); -/// 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); - -/// Entry point for our route -fn chat_route( - req: HttpRequest, - stream: web::Payload, - chat_server: web::Data>, -) -> Result { - ws::start( - WSSession { - cs_addr: chat_server.get_ref().to_owned(), - id: 0, - hb: Instant::now(), - ip: req - .connection_info() - .remote() - .unwrap_or("127.0.0.1:12345") - .split(":") - .next() - .unwrap_or("127.0.0.1") - .to_string(), - }, - &req, - stream, - ) -} - -struct WSSession { - cs_addr: Addr, - /// unique session id - id: usize, - ip: String, - /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT), - /// otherwise we drop connection. - hb: Instant, -} - -impl Actor for WSSession { - type Context = ws::WebsocketContext; - - /// Method is called on actor start. - /// We register ws session with ChatServer - fn started(&mut self, ctx: &mut Self::Context) { - // we'll start heartbeat process on session start. - self.hb(ctx); - - // register self in chat server. `AsyncContext::wait` register - // future within context, but context waits until this future resolves - // before processing any other events. - // across all routes within application - let addr = ctx.address(); - self - .cs_addr - .send(Connect { - addr: addr.recipient(), - ip: self.ip.to_owned(), - }) - .into_actor(self) - .then(|res, act, ctx| { - match res { - Ok(res) => act.id = res, - // something is wrong with chat server - _ => ctx.stop(), - } - fut::ok(()) - }) - .wait(ctx); - } - - fn stopping(&mut self, _ctx: &mut Self::Context) -> Running { - // notify chat server - self.cs_addr.do_send(Disconnect { - id: self.id, - ip: self.ip.to_owned(), - }); - Running::Stop - } -} - -/// Handle messages from chat server, we simply send it to peer websocket -/// These are room messages, IE sent to others in the room -impl Handler for WSSession { - type Result = (); - - fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) { - // println!("id: {} msg: {}", self.id, msg.0); - ctx.text(msg.0); - } -} - -/// WebSocket message handler -impl StreamHandler for WSSession { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - // println!("WEBSOCKET MESSAGE: {:?} from id: {}", msg, self.id); - match msg { - ws::Message::Ping(msg) => { - self.hb = Instant::now(); - ctx.pong(&msg); - } - ws::Message::Pong(_) => { - self.hb = Instant::now(); - } - ws::Message::Text(text) => { - let m = text.trim().to_owned(); - println!("WEBSOCKET MESSAGE: {:?} from id: {}", &m, self.id); - - self - .cs_addr - .send(StandardMessage { - id: self.id, - msg: m, - }) - .into_actor(self) - .then(|res, _, ctx| { - match res { - Ok(res) => ctx.text(res), - Err(e) => { - eprintln!("{}", &e); - } - } - fut::ok(()) - }) - .wait(ctx); - } - ws::Message::Binary(_bin) => println!("Unexpected binary"), - ws::Message::Close(_) => { - ctx.stop(); - } - _ => {} - } - } -} - -impl WSSession { - /// helper method that sends ping to client every second. - /// - /// also this method checks heartbeats from client - fn hb(&self, ctx: &mut ws::WebsocketContext) { - ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { - // check client heartbeats - if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { - // heartbeat timed out - println!("Websocket Client heartbeat failed, disconnecting!"); - - // notify chat server - act.cs_addr.do_send(Disconnect { - id: act.id, - ip: act.ip.to_owned(), - }); - - // stop actor - ctx.stop(); - - // don't try to send a ping - return; - } - - ctx.ping(""); - }); - } -} - fn main() { let _ = env_logger::init(); let sys = actix::System::new("lemmy"); @@ -190,87 +22,21 @@ fn main() { let conn = establish_connection(); embedded_migrations::run(&conn).unwrap(); - // Start chat server actor in separate thread - let server = ChatServer::default().start(); - let settings = Settings::get(); // Create Http server with websocket support HttpServer::new(move || { - let app = App::new() - .data(server.clone()) - // Front end routes + App::new() + .configure(federation::config) + .configure(feeds::config) + .configure(index::config) + .configure(nodeinfo::config) + .configure(webfinger::config) + .configure(websocket::config) .service(actix_files::Files::new( "/static", settings.front_end_dir.to_owned(), )) - .route("/", web::get().to(index)) - .route( - "/home/type/{type}/sort/{sort}/page/{page}", - web::get().to(index), - ) - .route("/login", web::get().to(index)) - .route("/create_post", web::get().to(index)) - .route("/create_community", web::get().to(index)) - .route("/communities/page/{page}", web::get().to(index)) - .route("/communities", web::get().to(index)) - .route("/post/{id}/comment/{id2}", web::get().to(index)) - .route("/post/{id}", web::get().to(index)) - .route("/c/{name}/sort/{sort}/page/{page}", web::get().to(index)) - .route("/c/{name}", web::get().to(index)) - .route("/community/{id}", web::get().to(index)) - .route( - "/u/{username}/view/{view}/sort/{sort}/page/{page}", - web::get().to(index), - ) - .route("/u/{username}", web::get().to(index)) - .route("/user/{id}", web::get().to(index)) - .route("/inbox", web::get().to(index)) - .route("/modlog/community/{community_id}", web::get().to(index)) - .route("/modlog", web::get().to(index)) - .route("/setup", web::get().to(index)) - .route( - "/search/q/{q}/type/{type}/sort/{sort}/page/{page}", - web::get().to(index), - ) - .route("/search", web::get().to(index)) - .route("/sponsors", web::get().to(index)) - .route("/password_change/{token}", web::get().to(index)) - // Websocket - .service(web::resource("/api/v1/ws").to(chat_route)) - // NodeInfo - .route("/nodeinfo/2.0.json", web::get().to(nodeinfo::node_info)) - .route( - "/.well-known/nodeinfo", - web::get().to(nodeinfo::node_info_well_known), - ) - // RSS - .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed)) - .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)) - // Federation - .route( - "/federation/c/{community_name}", - web::get().to(apub::community::get_apub_community), - ) - .route( - "/federation/c/{community_name}/followers", - web::get().to(apub::community::get_apub_community_followers), - ) - .route( - "/federation/u/{user_name}", - web::get().to(apub::user::get_apub_user), - ) - .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)); - - // Federation - if settings.federation_enabled { - app.route( - ".well-known/webfinger", - web::get().to(webfinger::get_webfinger_response), - ) - } else { - app - } }) .bind((settings.bind, settings.port)) .unwrap() @@ -279,9 +45,3 @@ fn main() { println!("Started http server at {}:{}", settings.bind, settings.port); let _ = sys.run(); } - -fn index() -> Result { - Ok(NamedFile::open( - Settings::get().front_end_dir.to_owned() + "/index.html", - )?) -} diff --git a/server/src/routes/federation.rs b/server/src/routes/federation.rs new file mode 100644 index 000000000..ea6039d6b --- /dev/null +++ b/server/src/routes/federation.rs @@ -0,0 +1,18 @@ +use crate::apub; +use actix_web::web; + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg + .route( + "/federation/c/{community_name}", + web::get().to(apub::community::get_apub_community), + ) + .route( + "/federation/c/{community_name}/followers", + web::get().to(apub::community::get_apub_community_followers), + ) + .route( + "/federation/u/{user_name}", + web::get().to(apub::user::get_apub_user), + ); +} diff --git a/server/src/feeds.rs b/server/src/routes/feeds.rs similarity index 94% rename from server/src/feeds.rs rename to server/src/routes/feeds.rs index c624bcc53..55b457c79 100644 --- a/server/src/feeds.rs +++ b/server/src/routes/feeds.rs @@ -5,12 +5,13 @@ use crate::db::comment_view::{ReplyQueryBuilder, ReplyView}; use crate::db::community::Community; use crate::db::post_view::{PostQueryBuilder, PostView}; use crate::db::site_view::SiteView; -use crate::db::user::User_; +use crate::db::user::{Claims, User_}; use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView}; use crate::db::{establish_connection, ListingType, SortType}; use crate::Settings; use actix_web::body::Body; use actix_web::{web, HttpResponse, Result}; +use chrono::{DateTime, Utc}; use failure::Error; use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder}; use serde::Deserialize; @@ -29,7 +30,14 @@ enum RequestType { Inbox, } -pub fn get_all_feed(info: web::Query) -> HttpResponse { +pub fn config(cfg: &mut web::ServiceConfig) { + cfg + .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed)) + .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)) + .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)); +} + +fn get_all_feed(info: web::Query) -> HttpResponse { let sort_type = match get_sort_type(info) { Ok(sort_type) => sort_type, Err(_) => return HttpResponse::BadRequest().finish(), @@ -45,7 +53,7 @@ pub fn get_all_feed(info: web::Query) -> HttpResponse { } } -pub fn get_feed(path: web::Path<(String, String)>, info: web::Query) -> HttpResponse { +fn get_feed(path: web::Path<(String, String)>, info: web::Query) -> HttpResponse { let sort_type = match get_sort_type(info) { Ok(sort_type) => sort_type, Err(_) => return HttpResponse::BadRequest().finish(), @@ -162,7 +170,7 @@ fn get_feed_front(sort_type: &SortType, jwt: String) -> Result { let conn = establish_connection(); let site_view = SiteView::read(&conn)?; - let user_id = db::user::Claims::decode(&jwt)?.claims.id; + let user_id = Claims::decode(&jwt)?.claims.id; let posts = PostQueryBuilder::create(&conn) .listing_type(ListingType::Subscribed) @@ -189,7 +197,7 @@ fn get_feed_inbox(jwt: String) -> Result { let conn = establish_connection(); let site_view = SiteView::read(&conn)?; - let user_id = db::user::Claims::decode(&jwt)?.claims.id; + let user_id = Claims::decode(&jwt)?.claims.id; let sort = SortType::New; diff --git a/server/src/routes/index.rs b/server/src/routes/index.rs new file mode 100644 index 000000000..cd10a2b7a --- /dev/null +++ b/server/src/routes/index.rs @@ -0,0 +1,45 @@ +use crate::settings::Settings; +use actix_files::NamedFile; +use actix_web::web; + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg + .route("/", web::get().to(index)) + .route( + "/home/type/{type}/sort/{sort}/page/{page}", + web::get().to(index), + ) + .route("/login", web::get().to(index)) + .route("/create_post", web::get().to(index)) + .route("/create_community", web::get().to(index)) + .route("/communities/page/{page}", web::get().to(index)) + .route("/communities", web::get().to(index)) + .route("/post/{id}/comment/{id2}", web::get().to(index)) + .route("/post/{id}", web::get().to(index)) + .route("/c/{name}/sort/{sort}/page/{page}", web::get().to(index)) + .route("/c/{name}", web::get().to(index)) + .route("/community/{id}", web::get().to(index)) + .route( + "/u/{username}/view/{view}/sort/{sort}/page/{page}", + web::get().to(index), + ) + .route("/u/{username}", web::get().to(index)) + .route("/user/{id}", web::get().to(index)) + .route("/inbox", web::get().to(index)) + .route("/modlog/community/{community_id}", web::get().to(index)) + .route("/modlog", web::get().to(index)) + .route("/setup", web::get().to(index)) + .route( + "/search/q/{q}/type/{type}/sort/{sort}/page/{page}", + web::get().to(index), + ) + .route("/search", web::get().to(index)) + .route("/sponsors", web::get().to(index)) + .route("/password_change/{token}", web::get().to(index)); +} + +fn index() -> Result { + Ok(NamedFile::open( + Settings::get().front_end_dir.to_owned() + "/index.html", + )?) +} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs new file mode 100644 index 000000000..6556c8d58 --- /dev/null +++ b/server/src/routes/mod.rs @@ -0,0 +1,6 @@ +pub mod federation; +pub mod feeds; +pub mod index; +pub mod nodeinfo; +pub mod webfinger; +pub mod websocket; diff --git a/server/src/nodeinfo.rs b/server/src/routes/nodeinfo.rs similarity index 84% rename from server/src/nodeinfo.rs rename to server/src/routes/nodeinfo.rs index 65bd93700..3165aea12 100644 --- a/server/src/nodeinfo.rs +++ b/server/src/routes/nodeinfo.rs @@ -3,9 +3,16 @@ use crate::db::site_view::SiteView; use crate::version; use crate::Settings; use actix_web::body::Body; +use actix_web::web; use actix_web::HttpResponse; use serde_json::json; +pub fn config(cfg: &mut web::ServiceConfig) { + cfg + .route("/nodeinfo/2.0.json", web::get().to(node_info)) + .route("/.well-known/nodeinfo", web::get().to(node_info_well_known)); +} + pub fn node_info_well_known() -> HttpResponse { let json = json!({ "links": { @@ -19,7 +26,7 @@ pub fn node_info_well_known() -> HttpResponse { .body(json.to_string()); } -pub fn node_info() -> HttpResponse { +fn node_info() -> HttpResponse { let conn = establish_connection(); let site_view = match SiteView::read(&conn) { Ok(site_view) => site_view, diff --git a/server/src/webfinger.rs b/server/src/routes/webfinger.rs similarity index 89% rename from server/src/webfinger.rs rename to server/src/routes/webfinger.rs index 558947458..f013f3efe 100644 --- a/server/src/webfinger.rs +++ b/server/src/routes/webfinger.rs @@ -2,6 +2,7 @@ use crate::db::community::Community; use crate::db::establish_connection; use crate::Settings; use actix_web::body::Body; +use actix_web::web; use actix_web::web::Query; use actix_web::HttpResponse; use regex::Regex; @@ -13,6 +14,15 @@ pub struct Params { resource: String, } +pub fn config(cfg: &mut web::ServiceConfig) { + if Settings::get().federation_enabled { + cfg.route( + ".well-known/webfinger", + web::get().to(get_webfinger_response), + ); + } +} + lazy_static! { static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!( "^group:([a-z0-9_]{{3, 20}})@{}$", @@ -27,7 +37,7 @@ lazy_static! { /// /// You can also view the webfinger response that Mastodon sends: /// https://radical.town/.well-known/webfinger?resource=acct:felix@radical.town -pub fn get_webfinger_response(info: Query) -> HttpResponse { +fn get_webfinger_response(info: Query) -> HttpResponse { let regex_parsed = WEBFINGER_COMMUNITY_REGEX .captures(&info.resource) .map(|c| c.get(1)); diff --git a/server/src/routes/websocket.rs b/server/src/routes/websocket.rs new file mode 100644 index 000000000..3af229bff --- /dev/null +++ b/server/src/routes/websocket.rs @@ -0,0 +1,179 @@ +use crate::websocket::server::*; +use actix::prelude::*; +use actix_web::web; +use actix_web::*; +use actix_web_actors::ws; +use std::time::{Duration, Instant}; + +pub fn config(cfg: &mut web::ServiceConfig) { + // Start chat server actor in separate thread + let server = ChatServer::default().start(); + cfg + .data(server.clone()) + .service(web::resource("/api/v1/ws").to(chat_route)); +} + +/// 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); + +/// Entry point for our route +fn chat_route( + req: HttpRequest, + stream: web::Payload, + chat_server: web::Data>, +) -> Result { + ws::start( + WSSession { + cs_addr: chat_server.get_ref().to_owned(), + id: 0, + hb: Instant::now(), + ip: req + .connection_info() + .remote() + .unwrap_or("127.0.0.1:12345") + .split(":") + .next() + .unwrap_or("127.0.0.1") + .to_string(), + }, + &req, + stream, + ) +} + +struct WSSession { + cs_addr: Addr, + /// unique session id + id: usize, + ip: String, + /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT), + /// otherwise we drop connection. + hb: Instant, +} + +impl Actor for WSSession { + type Context = ws::WebsocketContext; + + /// Method is called on actor start. + /// We register ws session with ChatServer + fn started(&mut self, ctx: &mut Self::Context) { + // we'll start heartbeat process on session start. + self.hb(ctx); + + // register self in chat server. `AsyncContext::wait` register + // future within context, but context waits until this future resolves + // before processing any other events. + // across all routes within application + let addr = ctx.address(); + self + .cs_addr + .send(Connect { + addr: addr.recipient(), + ip: self.ip.to_owned(), + }) + .into_actor(self) + .then(|res, act, ctx| { + match res { + Ok(res) => act.id = res, + // something is wrong with chat server + _ => ctx.stop(), + } + fut::ok(()) + }) + .wait(ctx); + } + + fn stopping(&mut self, _ctx: &mut Self::Context) -> Running { + // notify chat server + self.cs_addr.do_send(Disconnect { + id: self.id, + ip: self.ip.to_owned(), + }); + Running::Stop + } +} + +/// Handle messages from chat server, we simply send it to peer websocket +/// These are room messages, IE sent to others in the room +impl Handler for WSSession { + type Result = (); + + fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) { + // println!("id: {} msg: {}", self.id, msg.0); + ctx.text(msg.0); + } +} + +/// WebSocket message handler +impl StreamHandler for WSSession { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + // println!("WEBSOCKET MESSAGE: {:?} from id: {}", msg, self.id); + match msg { + ws::Message::Ping(msg) => { + self.hb = Instant::now(); + ctx.pong(&msg); + } + ws::Message::Pong(_) => { + self.hb = Instant::now(); + } + ws::Message::Text(text) => { + let m = text.trim().to_owned(); + println!("WEBSOCKET MESSAGE: {:?} from id: {}", &m, self.id); + + self + .cs_addr + .send(StandardMessage { + id: self.id, + msg: m, + }) + .into_actor(self) + .then(|res, _, ctx| { + match res { + Ok(res) => ctx.text(res), + Err(e) => { + eprintln!("{}", &e); + } + } + fut::ok(()) + }) + .wait(ctx); + } + ws::Message::Binary(_bin) => println!("Unexpected binary"), + ws::Message::Close(_) => { + ctx.stop(); + } + _ => {} + } + } +} + +impl WSSession { + /// helper method that sends ping to client every second. + /// + /// also this method checks heartbeats from client + fn hb(&self, ctx: &mut ws::WebsocketContext) { + ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { + // check client heartbeats + if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { + // heartbeat timed out + println!("Websocket Client heartbeat failed, disconnecting!"); + + // notify chat server + act.cs_addr.do_send(Disconnect { + id: act.id, + ip: act.ip.to_owned(), + }); + + // stop actor + ctx.stop(); + + // don't try to send a ping + return; + } + + ctx.ping(""); + }); + } +} From cd9df4455ede8435d54c32d369bb782b14733498 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 31 Dec 2019 10:44:30 -0500 Subject: [PATCH 02/19] Adding open_registration to nodeinfo. --- server/src/main.rs | 7 +------ server/src/routes/nodeinfo.rs | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index bf105323c..cc1cbcec4 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,12 +4,7 @@ extern crate diesel_migrations; use actix_web::*; use lemmy_server::db::establish_connection; -use lemmy_server::routes::federation; -use lemmy_server::routes::feeds; -use lemmy_server::routes::index; -use lemmy_server::routes::nodeinfo; -use lemmy_server::routes::webfinger; -use lemmy_server::routes::websocket; +use lemmy_server::routes::{federation, feeds, index, nodeinfo, webfinger, websocket}; use lemmy_server::settings::Settings; embed_migrations!(); diff --git a/server/src/routes/nodeinfo.rs b/server/src/routes/nodeinfo.rs index 3165aea12..cbc0fb0fc 100644 --- a/server/src/routes/nodeinfo.rs +++ b/server/src/routes/nodeinfo.rs @@ -50,7 +50,7 @@ fn node_info() -> HttpResponse { }, "localPosts": site_view.number_of_posts, "localComments": site_view.number_of_comments, - "openRegistrations": true, + "openRegistrations": site_view.open_registration, } }); return HttpResponse::Ok() From 9d1383906aaebe0017cd5cfdde45e28e5c414304 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 31 Dec 2019 11:07:57 -0500 Subject: [PATCH 03/19] Correcting mastodon follow link. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5407ac0bf..9812dbf2f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Github](https://img.shields.io/badge/-Github-blue)](https://github.com/dessalines/lemmy) [![Gitlab](https://img.shields.io/badge/-Gitlab-yellowgreen)](https://gitlab.com/dessalines/lemmy) -![Mastodon Follow](https://img.shields.io/mastodon/follow/810572?domain=https%3A%2F%2Fmastodon.social&style=social) +[![Mastodon Follow](https://img.shields.io/mastodon/follow/810572?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@LemmyDev) ![GitHub stars](https://img.shields.io/github/stars/dessalines/lemmy?style=social) [![Matrix](https://img.shields.io/matrix/rust-reddit-fediverse:matrix.org.svg?label=matrix-chat)](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org) ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/dessalines/lemmy.svg) @@ -121,7 +121,7 @@ mkdir lemmy/ cd lemmy/ wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/lemmy.hjson -# Edit the .env if you want custom passwords +# Edit lemmy.hjson to do more configuration docker-compose up -d ``` From 0b04aec6024ee87b6a3651b1f39f7726c5a1d0e7 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jan 2020 11:09:56 -0500 Subject: [PATCH 04/19] Finally got debounce working! - Fixes #367 - Fixes #376 --- ui/src/components/post-form.tsx | 38 +++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 0b4d05763..ef639c9da 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -74,6 +74,8 @@ export class PostForm extends Component { constructor(props: any, context: any) { super(props, context); + this.fetchSimilarPosts = debounce(this.fetchSimilarPosts).bind(this); + this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this); this.state = this.emptyState; @@ -350,9 +352,14 @@ export class PostForm extends Component { handlePostUrlChange(i: PostForm, event: any) { i.state.postForm.url = event.target.value; - if (validURL(i.state.postForm.url)) { + i.setState(i.state); + i.fetchPageTitle(); + } + + fetchPageTitle() { + if (validURL(this.state.postForm.url)) { let form: SearchForm = { - q: i.state.postForm.url, + q: this.state.postForm.url, type_: SearchType[SearchType.Url], sort: SortType[SortType.TopAll], page: 1, @@ -362,36 +369,39 @@ export class PostForm extends Component { WebSocketService.Instance.search(form); // Fetch the page title - getPageTitle(i.state.postForm.url).then(d => { - i.state.suggestedTitle = d; - i.setState(i.state); + getPageTitle(this.state.postForm.url).then(d => { + this.state.suggestedTitle = d; + this.setState(this.state); }); } else { - i.state.suggestedTitle = undefined; - i.state.crossPosts = []; + this.state.suggestedTitle = undefined; + this.state.crossPosts = []; } - - i.setState(i.state); } handlePostNameChange(i: PostForm, event: any) { i.state.postForm.name = event.target.value; + i.setState(i.state); + i.fetchSimilarPosts(); + } + + fetchSimilarPosts() { let form: SearchForm = { - q: i.state.postForm.name, + q: this.state.postForm.name, type_: SearchType[SearchType.Posts], sort: SortType[SortType.TopAll], - community_id: i.state.postForm.community_id, + community_id: this.state.postForm.community_id, page: 1, limit: 6, }; - if (i.state.postForm.name !== '') { + if (this.state.postForm.name !== '') { WebSocketService.Instance.search(form); } else { - i.state.suggestedPosts = []; + this.state.suggestedPosts = []; } - i.setState(i.state); + this.setState(this.state); } handlePostBodyChange(i: PostForm, event: any) { From d80e8fc075bc9afb699afd1d6e5e5f79a0db9cd1 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 31 Dec 2019 15:31:34 +0100 Subject: [PATCH 05/19] Use mdbook for documentation (fixes #375) --- README.md | 221 ------------------ docs/.gitignore | 1 + docs/book.toml | 6 + docs/src/SUMMARY.md | 17 ++ docs/src/about.md | 22 ++ docs/src/about_features.md | 26 +++ docs/{goals.md => src/about_goals.md} | 0 docs/{ranking.md => src/about_ranking.md} | 0 docs/src/administration.md | 1 + docs/src/administration_configuration.md | 6 + docs/src/administration_install_ansible.md | 11 + docs/src/administration_install_docker.md | 28 +++ docs/src/administration_install_kubernetes.md | 22 ++ docs/src/contributing.md | 1 + .../contributing_apub_api_outline.md} | 0 docs/src/contributing_docker_development.md | 11 + docs/src/contributing_local_development.md | 24 ++ docs/src/contributing_translations.md | 23 ++ .../contributing_websocket_api.md} | 0 19 files changed, 199 insertions(+), 221 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/book.toml create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/about.md create mode 100644 docs/src/about_features.md rename docs/{goals.md => src/about_goals.md} (100%) rename docs/{ranking.md => src/about_ranking.md} (100%) create mode 100644 docs/src/administration.md create mode 100644 docs/src/administration_configuration.md create mode 100644 docs/src/administration_install_ansible.md create mode 100644 docs/src/administration_install_docker.md create mode 100644 docs/src/administration_install_kubernetes.md create mode 100644 docs/src/contributing.md rename docs/{apub_api_outline.md => src/contributing_apub_api_outline.md} (100%) create mode 100644 docs/src/contributing_docker_development.md create mode 100644 docs/src/contributing_local_development.md create mode 100644 docs/src/contributing_translations.md rename docs/{api.md => src/contributing_websocket_api.md} (100%) diff --git a/README.md b/README.md index 9812dbf2f..d6acba774 100644 --- a/README.md +++ b/README.md @@ -36,63 +36,6 @@ Front Page|Post ---|--- ![main screen](https://i.imgur.com/kZSRcRu.png)|![chat screen](https://i.imgur.com/4XghNh6.png) -## 📝 Table of Contents - - - -- [Features](#features) -- [About](#about) - * [Why's it called Lemmy?](#whys-it-called-lemmy) -- [Install](#install) - * [Docker](#docker) - + [Updating](#updating) - * [Ansible](#ansible) - * [Kubernetes](#kubernetes) -- [Develop](#develop) - * [Docker Development](#docker-development) - * [Local Development](#local-development) - + [Requirements](#requirements) - + [Set up Postgres DB](#set-up-postgres-db) - + [Running](#running) -- [Configuration](#configuration) -- [Documentation](#documentation) -- [Support](#support) -- [Translations](#translations) -- [Credits](#credits) - - - -## Features - -- Open source, [AGPL License](/LICENSE). -- Self hostable, easy to deploy. - - Comes with [Docker](#docker), [Ansible](#ansible), [Kubernetes](#kubernetes). -- Clean, mobile-friendly interface. - - Live-updating Comment threads. - - Full vote scores `(+/-)` like old reddit. - - Themes, including light, dark, and solarized. - - Emojis with autocomplete support. Start typing `:` - - User tagging using `@`, Community tagging using `#`. - - Notifications, on comment replies and when you're tagged. - - i18n / internationalization support. - - RSS / Atom feeds for `All`, `Subscribed`, `Inbox`, `User`, and `Community`. -- Cross-posting support. - - A *similar post search* when creating new posts. Great for question / answer communities. -- Moderation abilities. - - Public Moderation Logs. - - Both site admins, and community moderators, who can appoint other moderators. - - Can lock, remove, and restore posts and comments. - - Can ban and unban users from communities and the site. - - Can transfer site and communities to others. -- Can fully erase your data, replacing all posts and comments. -- NSFW post / community support. -- High performance. - - Server is written in rust. - - Front end is `~80kB` gzipped. - - Supports arm64 / Raspberry Pi. - -## About - [Lemmy](https://github.com/dessalines/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). For a link aggregator, this means a user registered on one server can subscribe to forums on any other server, and can have discussions with users registered elsewhere. @@ -101,144 +44,6 @@ The overall goal is to create an easily self-hostable, decentralized alternative Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. -### Why's it called Lemmy? - -- Lead singer from [Motörhead](https://invidio.us/watch?v=pWB5JZRGl0U). -- The old school [video game](). -- The [Koopa from Super Mario](https://www.mariowiki.com/Lemmy_Koopa). -- The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/). - -Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Inferno](https://www.infernojs.org), [Typescript](https://www.typescriptlang.org/) and [Diesel](http://diesel.rs/). - -## Install - -### Docker - -Make sure you have both docker and docker-compose(>=`1.24.0`) installed: - -```bash -mkdir lemmy/ -cd lemmy/ -wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml -wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/lemmy.hjson -# Edit lemmy.hjson to do more configuration -docker-compose up -d -``` - -and go to http://localhost:8536. - -[A sample nginx config](/ansible/templates/nginx.conf), could be setup with: - -```bash -wget https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf -# Replace the {{ vars }} -sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf -``` -#### Updating - -To update to the newest version, run: - -```bash -wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml -docker-compose up -d -``` - -### Ansible - -First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) (e.g. using `sudo apt install ansible`) or the equivalent for you platform. - -Then run the following commands on your local computer: - -```bash -git clone https://github.com/dessalines/lemmy.git -cd lemmy/ansible/ -cp inventory.example inventory -nano inventory # enter your server, domain, contact email -ansible-playbook lemmy.yml --become -``` - -### Kubernetes - -You'll need to have an existing Kubernetes cluster and [storage class](https://kubernetes.io/docs/concepts/storage/storage-classes/). -Setting this up will vary depending on your provider. -To try it locally, you can use [MicroK8s](https://microk8s.io/) or [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/). - -Once you have a working cluster, edit the environment variables and volume sizes in `docker/k8s/*.yml`. -You may also want to change the service types to use `LoadBalancer`s depending on where you're running your cluster (add `type: LoadBalancer` to `ports)`, or `NodePort`s. -By default they will use `ClusterIP`s, which will allow access only within the cluster. See the [docs](https://kubernetes.io/docs/concepts/services-networking/service/) for more on networking in Kubernetes. - -**Important** Running a database in Kubernetes will work, but is generally not recommended. -If you're deploying on any of the common cloud providers, you should consider using their managed database service instead (RDS, Cloud SQL, Azure Databse, etc.). - -Now you can deploy: - -```bash -# Add `-n foo` if you want to deploy into a specific namespace `foo`; -# otherwise your resources will be created in the `default` namespace. -kubectl apply -f docker/k8s/db.yml -kubectl apply -f docker/k8s/pictshare.yml -kubectl apply -f docker/k8s/lemmy.yml -``` - -If you used a `LoadBalancer`, you should see it in your cloud provider's console. - -## Develop - -### Docker Development - -Run: - -```bash -git clone https://github.com/dessalines/lemmy -cd lemmy/docker/dev -./docker_update.sh # This builds and runs it, updating for your changes -``` - -and go to http://localhost:8536. - -### Local Development - -#### Requirements - -- [Rust](https://www.rust-lang.org/) -- [Yarn](https://yarnpkg.com/en/) -- [Postgres](https://www.postgresql.org/) - -#### Set up Postgres DB - -```bash - psql -c "create user lemmy with password 'password' superuser;" -U postgres - psql -c 'create database lemmy with owner lemmy;' -U postgres - export DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy -``` - -#### Running - -```bash -git clone https://github.com/dessalines/lemmy -cd lemmy -./install.sh -# For live coding, where both the front and back end, automagically reload on any save, do: -# cd ui && yarn start -# cd server && cargo watch -x run -``` - -## Configuration - -The configuration is based on the file [defaults.hjson](server/config/defaults.hjson). This file also contains documentation for all the available options. To override the defaults, you can copy the options you want to change into your local `config.hjson` file. - -Additionally, you can override any config files with environment variables. These have the same name as the config options, and are prefixed with `LEMMY_`. For example, you can override the `database.password` with -`LEMMY__DATABASE__POOL_SIZE=10`. - -An additional option `LEMMY_DATABASE_URL` is available, which can be used with a PostgreSQL connection string like `postgres://lemmy:password@lemmy_db:5432/lemmy`, passing all connection details at once. - -## Documentation - -- [Websocket API for App developers](docs/api.md) -- [ActivityPub API.md](docs/apub_api_outline.md) -- [Goals](docs/goals.md) -- [Ranking Algorithm](docs/ranking.md) - ## Support Lemmy is free, open-source software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. @@ -249,32 +54,6 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent - ethereum: `0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01` - monero: `41taVyY6e1xApqKyMVDRVxJ76sPkfZhALLTjRvVKpaAh2pBd4wv9RgYj1tSPrx8wc6iE1uWUfjtQdTmTy2FGMeChGVKPQuV` -## Translations - -If you'd like to add translations, take a look a look at the [English translation file](ui/src/translations/en.ts). - -- Languages supported: English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`). - -lang | done | missing ---- | --- | --- -de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -eo | 84% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no -es | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -fr | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -it | 93% | avatar,archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -nl | 86% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme -ru | 80% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no -sv | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -zh | 78% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no - - -If you'd like to update this report, run: - -```bash -cd ui -ts-node translation_report.ts > tmp # And replace the text above. -``` - ## Credits Logo made by Andy Cuccaro (@andycuccaro) under the CC-BY-SA 4.0 license. diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..7585238ef --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 000000000..55cce8c03 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Felix Ableitner"] +language = "en" +multilingual = false +src = "src" +title = "Lemmy Documentation" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 000000000..9fc0fa87c --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,17 @@ +# Summary + +- [About](about.md) + - [Features](about_features.md) + - [Goals](about_goals.md) + - [Post and Comment Ranking](about_ranking.md) +- [Administration](administration.md) + - [Install with Docker](administration_install_docker.md) + - [Install with Ansible](administration_install_ansible.md) + - [Install with Kubernetes](administration_install_kubernetes.md) + - [Configuration](administration_configuration.md) +- [Contributing](contributing.md) + - [Translations](contributing_translations.md) + - [Docker Development](contributing_docker_development.md) + - [Local Development](contributing_local_development.md) + - [Websocket API](contributing_websocket_api.md) + - [ActivityPub API Outline](contributing_apub_api_outline.md) diff --git a/docs/src/about.md b/docs/src/about.md new file mode 100644 index 000000000..14743df2c --- /dev/null +++ b/docs/src/about.md @@ -0,0 +1,22 @@ +

A link aggregator / reddit clone for the fediverse. +
+

+ +[Lemmy Dev instance](https://dev.lemmy.ml) *for testing purposes only* + +[Lemmy](https://github.com/dessalines/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). + +For a link aggregator, this means a user registered on one server can subscribe to forums on any other server, and can have discussions with users registered elsewhere. + +The overall goal is to create an easily self-hostable, decentralized alternative to reddit and other link aggregators, outside of their corporate control and meddling. + +Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. + +### Why's it called Lemmy? + +- Lead singer from [Motörhead](https://invidio.us/watch?v=pWB5JZRGl0U). +- The old school [video game](). +- The [Koopa from Super Mario](https://www.mariowiki.com/Lemmy_Koopa). +- The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/). + +Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Inferno](https://www.infernojs.org), [Typescript](https://www.typescriptlang.org/) and [Diesel](http://diesel.rs/). diff --git a/docs/src/about_features.md b/docs/src/about_features.md new file mode 100644 index 000000000..dab859c13 --- /dev/null +++ b/docs/src/about_features.md @@ -0,0 +1,26 @@ +- Open source, [AGPL License](/LICENSE). +- Self hostable, easy to deploy. + - Comes with [Docker](#docker), [Ansible](#ansible), [Kubernetes](#kubernetes). +- Clean, mobile-friendly interface. + - Live-updating Comment threads. + - Full vote scores `(+/-)` like old reddit. + - Themes, including light, dark, and solarized. + - Emojis with autocomplete support. Start typing `:` + - User tagging using `@`, Community tagging using `#`. + - Notifications, on comment replies and when you're tagged. + - i18n / internationalization support. + - RSS / Atom feeds for `All`, `Subscribed`, `Inbox`, `User`, and `Community`. +- Cross-posting support. + - A *similar post search* when creating new posts. Great for question / answer communities. +- Moderation abilities. + - Public Moderation Logs. + - Both site admins, and community moderators, who can appoint other moderators. + - Can lock, remove, and restore posts and comments. + - Can ban and unban users from communities and the site. + - Can transfer site and communities to others. +- Can fully erase your data, replacing all posts and comments. +- NSFW post / community support. +- High performance. + - Server is written in rust. + - Front end is `~80kB` gzipped. + - Supports arm64 / Raspberry Pi. \ No newline at end of file diff --git a/docs/goals.md b/docs/src/about_goals.md similarity index 100% rename from docs/goals.md rename to docs/src/about_goals.md diff --git a/docs/ranking.md b/docs/src/about_ranking.md similarity index 100% rename from docs/ranking.md rename to docs/src/about_ranking.md diff --git a/docs/src/administration.md b/docs/src/administration.md new file mode 100644 index 000000000..c4c2b01f1 --- /dev/null +++ b/docs/src/administration.md @@ -0,0 +1 @@ +Information for Lemmy instance admins, and those who want to start an instance. \ No newline at end of file diff --git a/docs/src/administration_configuration.md b/docs/src/administration_configuration.md new file mode 100644 index 000000000..73ea35042 --- /dev/null +++ b/docs/src/administration_configuration.md @@ -0,0 +1,6 @@ +The configuration is based on the file [defaults.hjson](server/config/defaults.hjson). This file also contains documentation for all the available options. To override the defaults, you can copy the options you want to change into your local `config.hjson` file. + +Additionally, you can override any config files with environment variables. These have the same name as the config options, and are prefixed with `LEMMY_`. For example, you can override the `database.password` with +`LEMMY__DATABASE__POOL_SIZE=10`. + +An additional option `LEMMY_DATABASE_URL` is available, which can be used with a PostgreSQL connection string like `postgres://lemmy:password@lemmy_db:5432/lemmy`, passing all connection details at once. diff --git a/docs/src/administration_install_ansible.md b/docs/src/administration_install_ansible.md new file mode 100644 index 000000000..03642b897 --- /dev/null +++ b/docs/src/administration_install_ansible.md @@ -0,0 +1,11 @@ +First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) (e.g. using `sudo apt install ansible`) or the equivalent for you platform. + +Then run the following commands on your local computer: + +```bash +git clone https://github.com/dessalines/lemmy.git +cd lemmy/ansible/ +cp inventory.example inventory +nano inventory # enter your server, domain, contact email +ansible-playbook lemmy.yml --become +``` diff --git a/docs/src/administration_install_docker.md b/docs/src/administration_install_docker.md new file mode 100644 index 000000000..64abe737e --- /dev/null +++ b/docs/src/administration_install_docker.md @@ -0,0 +1,28 @@ +Make sure you have both docker and docker-compose(>=`1.24.0`) installed: + +```bash +mkdir lemmy/ +cd lemmy/ +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/lemmy.hjson +# Edit lemmy.hjson to do more configuration +docker-compose up -d +``` + +and go to http://localhost:8536. + +[A sample nginx config](/ansible/templates/nginx.conf), could be setup with: + +```bash +wget https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf +# Replace the {{ vars }} +sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf +``` +#### Updating + +To update to the newest version, run: + +```bash +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +docker-compose up -d +``` diff --git a/docs/src/administration_install_kubernetes.md b/docs/src/administration_install_kubernetes.md new file mode 100644 index 000000000..886558dce --- /dev/null +++ b/docs/src/administration_install_kubernetes.md @@ -0,0 +1,22 @@ +You'll need to have an existing Kubernetes cluster and [storage class](https://kubernetes.io/docs/concepts/storage/storage-classes/). +Setting this up will vary depending on your provider. +To try it locally, you can use [MicroK8s](https://microk8s.io/) or [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/). + +Once you have a working cluster, edit the environment variables and volume sizes in `docker/k8s/*.yml`. +You may also want to change the service types to use `LoadBalancer`s depending on where you're running your cluster (add `type: LoadBalancer` to `ports)`, or `NodePort`s. +By default they will use `ClusterIP`s, which will allow access only within the cluster. See the [docs](https://kubernetes.io/docs/concepts/services-networking/service/) for more on networking in Kubernetes. + +**Important** Running a database in Kubernetes will work, but is generally not recommended. +If you're deploying on any of the common cloud providers, you should consider using their managed database service instead (RDS, Cloud SQL, Azure Databse, etc.). + +Now you can deploy: + +```bash +# Add `-n foo` if you want to deploy into a specific namespace `foo`; +# otherwise your resources will be created in the `default` namespace. +kubectl apply -f docker/k8s/db.yml +kubectl apply -f docker/k8s/pictshare.yml +kubectl apply -f docker/k8s/lemmy.yml +``` + +If you used a `LoadBalancer`, you should see it in your cloud provider's console. diff --git a/docs/src/contributing.md b/docs/src/contributing.md new file mode 100644 index 000000000..4f29af3da --- /dev/null +++ b/docs/src/contributing.md @@ -0,0 +1 @@ +Information about contributing to Lemmy, whether it is translating, testing, designing or programming. \ No newline at end of file diff --git a/docs/apub_api_outline.md b/docs/src/contributing_apub_api_outline.md similarity index 100% rename from docs/apub_api_outline.md rename to docs/src/contributing_apub_api_outline.md diff --git a/docs/src/contributing_docker_development.md b/docs/src/contributing_docker_development.md new file mode 100644 index 000000000..0ed5bde5e --- /dev/null +++ b/docs/src/contributing_docker_development.md @@ -0,0 +1,11 @@ +Run: + +```bash +git clone https://github.com/dessalines/lemmy +cd lemmy/docker/dev +./docker_update.sh # This builds and runs it, updating for your changes +``` + +and go to http://localhost:8536. + +Note that compile times are relatively long with Docker, because builds can't be properly cached. If this is a problem for you, you should use [Local Development](contributing_local_development.md). \ No newline at end of file diff --git a/docs/src/contributing_local_development.md b/docs/src/contributing_local_development.md new file mode 100644 index 000000000..a681eeb0d --- /dev/null +++ b/docs/src/contributing_local_development.md @@ -0,0 +1,24 @@ +#### Requirements + +- [Rust](https://www.rust-lang.org/) +- [Yarn](https://yarnpkg.com/en/) +- [Postgres](https://www.postgresql.org/) + +#### Set up Postgres DB + +```bash + psql -c "create user lemmy with password 'password' superuser;" -U postgres + psql -c 'create database lemmy with owner lemmy;' -U postgres + export DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy +``` + +#### Running + +```bash +git clone https://github.com/dessalines/lemmy +cd lemmy +./install.sh +# For live coding, where both the front and back end, automagically reload on any save, do: +# cd ui && yarn start +# cd server && cargo watch -x run +``` diff --git a/docs/src/contributing_translations.md b/docs/src/contributing_translations.md new file mode 100644 index 000000000..de8907701 --- /dev/null +++ b/docs/src/contributing_translations.md @@ -0,0 +1,23 @@ +If you'd like to add translations, take a look a look at the [English translation file](ui/src/translations/en.ts). + +- Languages supported: English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`). + +lang | done | missing +--- | --- | --- +de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +eo | 84% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no +es | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +fr | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +it | 93% | avatar,archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +nl | 86% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme +ru | 80% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no +sv | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +zh | 78% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no + + +If you'd like to update this report, run: + +```bash +cd ui +ts-node translation_report.ts > tmp # And replace the text above. +``` \ No newline at end of file diff --git a/docs/api.md b/docs/src/contributing_websocket_api.md similarity index 100% rename from docs/api.md rename to docs/src/contributing_websocket_api.md From 77847ab3e6876cb4b98986be80ec81410aa63399 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 1 Jan 2020 17:26:59 +0100 Subject: [PATCH 06/19] Move translations to readme, put install instructions in both places --- README.md | 73 +++++++++++++++++++++++++++ docs/src/contributing_translations.md | 23 --------- 2 files changed, 73 insertions(+), 23 deletions(-) delete mode 100644 docs/src/contributing_translations.md diff --git a/README.md b/README.md index d6acba774..e44356907 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,53 @@ The overall goal is to create an easily self-hostable, decentralized alternative Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. +## Install + +### Docker + +Make sure you have both docker and docker-compose(>=`1.24.0`) installed: + +```bash +mkdir lemmy/ +cd lemmy/ +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/lemmy.hjson +# Edit lemmy.hjson to do more configuration +docker-compose up -d +``` + +and go to http://localhost:8536. + +[A sample nginx config](/ansible/templates/nginx.conf), could be setup with: + +```bash +wget https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf +# Replace the {{ vars }} +sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf +``` +#### Updating + +To update to the newest version, run: + +```bash +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +docker-compose up -d +``` + +### Ansible + +First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) (e.g. using `sudo apt install ansible`) or the equivalent for you platform. + +Then run the following commands on your local computer: + +```bash +git clone https://github.com/dessalines/lemmy.git +cd lemmy/ansible/ +cp inventory.example inventory +nano inventory # enter your server, domain, contact email +ansible-playbook lemmy.yml --become +``` + ## Support Lemmy is free, open-source software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. @@ -54,6 +101,32 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent - ethereum: `0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01` - monero: `41taVyY6e1xApqKyMVDRVxJ76sPkfZhALLTjRvVKpaAh2pBd4wv9RgYj1tSPrx8wc6iE1uWUfjtQdTmTy2FGMeChGVKPQuV` +## Translations + +If you'd like to add translations, take a look a look at the [English translation file](ui/src/translations/en.ts). + +- Languages supported: English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`). + +lang | done | missing +--- | --- | --- +de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +eo | 84% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no +es | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +fr | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +it | 93% | avatar,archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +nl | 86% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme +ru | 80% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no +sv | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +zh | 78% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no + + +If you'd like to update this report, run: + +```bash +cd ui +ts-node translation_report.ts > tmp # And replace the text above. +``` + ## Credits Logo made by Andy Cuccaro (@andycuccaro) under the CC-BY-SA 4.0 license. diff --git a/docs/src/contributing_translations.md b/docs/src/contributing_translations.md deleted file mode 100644 index de8907701..000000000 --- a/docs/src/contributing_translations.md +++ /dev/null @@ -1,23 +0,0 @@ -If you'd like to add translations, take a look a look at the [English translation file](ui/src/translations/en.ts). - -- Languages supported: English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`). - -lang | done | missing ---- | --- | --- -de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -eo | 84% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no -es | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -fr | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -it | 93% | avatar,archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -nl | 86% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme -ru | 80% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no -sv | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -zh | 78% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no - - -If you'd like to update this report, run: - -```bash -cd ui -ts-node translation_report.ts > tmp # And replace the text above. -``` \ No newline at end of file From a74d92af3d0af37a39e60c716a270b2e54ed2c2e Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jan 2020 11:28:18 -0500 Subject: [PATCH 07/19] Version v0.5.10 --- docker/prod/docker-compose.yml | 2 +- server/src/version.rs | 2 +- ui/src/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index d013b0d70..e3ece0433 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -11,7 +11,7 @@ services: - lemmy_db:/var/lib/postgresql/data restart: always lemmy: - image: dessalines/lemmy:v0.5.9 + image: dessalines/lemmy:v0.5.10 ports: - "127.0.0.1:8536:8536" restart: always diff --git a/server/src/version.rs b/server/src/version.rs index dc45321db..7488857b6 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &'static str = "v0.5.9"; +pub const VERSION: &'static str = "v0.5.10"; diff --git a/ui/src/version.ts b/ui/src/version.ts index 8d6cd5257..cfeb4dba6 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export let version: string = 'v0.5.9'; +export let version: string = 'v0.5.10'; From 6058e837fc1a8bd6f9302ef7915b836ad250f488 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jan 2020 11:32:58 -0500 Subject: [PATCH 08/19] Version v0.5.11 --- docker/prod/docker-compose.yml | 2 +- server/src/version.rs | 2 +- ui/src/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index e3ece0433..15d4c5402 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -11,7 +11,7 @@ services: - lemmy_db:/var/lib/postgresql/data restart: always lemmy: - image: dessalines/lemmy:v0.5.10 + image: dessalines/lemmy:v0.5.11 ports: - "127.0.0.1:8536:8536" restart: always diff --git a/server/src/version.rs b/server/src/version.rs index 7488857b6..065cae39b 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &'static str = "v0.5.10"; +pub const VERSION: &'static str = "v0.5.11"; diff --git a/ui/src/version.ts b/ui/src/version.ts index cfeb4dba6..6cac00d52 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export let version: string = 'v0.5.10'; +export let version: string = 'v0.5.11'; From 4faa46ac29a5693f9f938ae80a22e6c1799c1404 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jan 2020 11:39:23 -0500 Subject: [PATCH 09/19] Only do arm build on major deploy. Fixes #393 --- docker/dev/deploy.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docker/dev/deploy.sh b/docker/dev/deploy.sh index ba3675f33..c9d52215a 100755 --- a/docker/dev/deploy.sh +++ b/docker/dev/deploy.sh @@ -5,6 +5,8 @@ git checkout master new_tag="$1" git tag $new_tag +third_semver=$(echo $new_tag | cut -d "." -f 3) + # Setting the version on the front end cd ../../ echo "export let version: string = '$(git describe --tags)';" > "ui/src/version.ts" @@ -38,14 +40,22 @@ docker push dessalines/lemmy:x64-$new_tag # docker push dessalines/lemmy:armv7hf-$new_tag # aarch64 -docker build -t lemmy:aarch64 -f Dockerfile.aarch64 ../../ -docker tag lemmy:aarch64 dessalines/lemmy:arm64-$new_tag -docker push dessalines/lemmy:arm64-$new_tag +# Only do this on major releases (IE the third semver is 0) +if [ $third_semver -eq 0 ]; then + docker build -t lemmy:aarch64 -f Dockerfile.aarch64 ../../ + docker tag lemmy:aarch64 dessalines/lemmy:arm64-$new_tag + docker push dessalines/lemmy:arm64-$new_tag +fi # Creating the manifest for the multi-arch build -docker manifest create dessalines/lemmy:$new_tag \ +if [ $third_semver -eq 0 ]; then + docker manifest create dessalines/lemmy:$new_tag \ dessalines/lemmy:x64-$new_tag \ dessalines/lemmy:arm64-$new_tag +else + docker manifest create dessalines/lemmy:$new_tag \ + dessalines/lemmy:x64-$new_tag +fi docker manifest push dessalines/lemmy:$new_tag From 62ba018b190c54d30a4436721931aea0b5eedb2e Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 1 Jan 2020 18:01:49 +0100 Subject: [PATCH 10/19] Include docs in docker image --- .dockerignore | 1 - docker/dev/Dockerfile | 9 +++++++++ docs/src/SUMMARY.md | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index 03466f0a3..73c475542 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,4 @@ ui/node_modules ui/dist server/target -docs .git diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index d62e7b279..761d8cc3c 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -32,6 +32,14 @@ RUN cargo build --frozen --release # Get diesel-cli on there just in case # RUN cargo install diesel_cli --no-default-features --features postgres + +FROM ekidd/rust-musl-builder:1.38.0-openssl11 as docs +WORKDIR /app +COPY docs ./docs +RUN sudo chown -R rust:rust . +RUN mdbook build docs/ + + FROM alpine:3.10 # Install libpq for postgres @@ -40,6 +48,7 @@ RUN apk add libpq # Copy resources COPY server/config/defaults.hjson /config/defaults.hjson COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/release/lemmy_server /app/lemmy +COPY --from=docs /app/docs/book/ /app/dist/documentation/ COPY --from=node /app/ui/dist /app/dist RUN addgroup -g 1000 lemmy diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 9fc0fa87c..d89169746 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -10,7 +10,6 @@ - [Install with Kubernetes](administration_install_kubernetes.md) - [Configuration](administration_configuration.md) - [Contributing](contributing.md) - - [Translations](contributing_translations.md) - [Docker Development](contributing_docker_development.md) - [Local Development](contributing_local_development.md) - [Websocket API](contributing_websocket_api.md) From f5a13717eaf10fcacc211950ff88943df9aef4c2 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jan 2020 15:46:14 -0500 Subject: [PATCH 11/19] Adding change password and email address from user settings. - Fixes #384 - Fixes #385 --- README.md | 19 +- .../down.sql | 15 + .../up.sql | 16 + server/src/api/user.rs | 63 ++- server/src/db/user.rs | 11 +- server/src/db/user_view.rs | 2 + ui/src/components/user.tsx | 417 +++++++++++------- ui/src/interfaces.ts | 5 + ui/src/translations/en.ts | 1 + 9 files changed, 339 insertions(+), 210 deletions(-) create mode 100644 server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql create mode 100644 server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql diff --git a/README.md b/README.md index 9812dbf2f..c5a30598d 100644 --- a/README.md +++ b/README.md @@ -257,16 +257,15 @@ If you'd like to add translations, take a look a look at the [English translatio lang | done | missing --- | --- | --- -de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -eo | 84% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no -es | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -fr | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -it | 93% | avatar,archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -nl | 86% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme -ru | 80% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no -sv | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -zh | 78% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no - +de | 97% | avatar,old_password,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +eo | 83% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no +es | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +fr | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +it | 93% | avatar,archive_link,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +nl | 85% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme +ru | 79% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no +sv | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +zh | 77% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no If you'd like to update this report, run: diff --git a/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql b/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql new file mode 100644 index 000000000..92f771f84 --- /dev/null +++ b/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql @@ -0,0 +1,15 @@ +-- user +drop view user_view; +create view user_view as +select id, +name, +avatar, +fedi_name, +admin, +banned, +published, +(select count(*) from post p where p.creator_id = u.id) as number_of_posts, +(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, +(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, +(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score +from user_ u; diff --git a/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql b/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql new file mode 100644 index 000000000..59972dfb8 --- /dev/null +++ b/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql @@ -0,0 +1,16 @@ +-- user +drop view user_view; +create view user_view as +select id, +name, +avatar, +email, +fedi_name, +admin, +banned, +published, +(select count(*) from post p where p.creator_id = u.id) as number_of_posts, +(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, +(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, +(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score +from user_ u; diff --git a/server/src/api/user.rs b/server/src/api/user.rs index e8ad20aa4..c074228f7 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -28,6 +28,10 @@ pub struct SaveUserSettings { default_listing_type: i16, lang: String, avatar: Option, + email: Option, + new_password: Option, + new_password_verify: Option, + old_password: Option, auth: String, } @@ -312,12 +316,45 @@ impl Perform for Oper { let read_user = User_::read(&conn, user_id)?; + let email = match &data.email { + Some(email) => Some(email.to_owned()), + None => read_user.email, + }; + + let password_encrypted = match &data.new_password { + Some(new_password) => { + match &data.new_password_verify { + Some(new_password_verify) => { + // Make sure passwords match + if new_password != new_password_verify { + return Err(APIError::err(&self.op, "passwords_dont_match"))?; + } + + // Check the old password + match &data.old_password { + Some(old_password) => { + let valid: bool = + verify(old_password, &read_user.password_encrypted).unwrap_or(false); + if !valid { + return Err(APIError::err(&self.op, "password_incorrect"))?; + } + User_::update_password(&conn, user_id, &new_password)?.password_encrypted + } + None => return Err(APIError::err(&self.op, "password_incorrect"))?, + } + } + None => return Err(APIError::err(&self.op, "passwords_dont_match"))?, + } + } + None => read_user.password_encrypted, + }; + let user_form = UserForm { name: read_user.name, fedi_name: read_user.fedi_name, - email: read_user.email, + email, avatar: data.avatar.to_owned(), - password_encrypted: read_user.password_encrypted, + password_encrypted, preferred_username: read_user.preferred_username, updated: Some(naive_now()), admin: read_user.admin, @@ -850,28 +887,8 @@ impl Perform for Oper { return Err(APIError::err(&self.op, "passwords_dont_match"))?; } - // Fetch the user - let read_user = User_::read(&conn, user_id)?; - // Update the user with the new password - let user_form = UserForm { - name: read_user.name, - fedi_name: read_user.fedi_name, - email: read_user.email, - avatar: read_user.avatar, - password_encrypted: data.password.to_owned(), - preferred_username: read_user.preferred_username, - updated: Some(naive_now()), - admin: read_user.admin, - banned: read_user.banned, - show_nsfw: read_user.show_nsfw, - theme: read_user.theme, - default_sort_type: read_user.default_sort_type, - default_listing_type: read_user.default_listing_type, - lang: read_user.lang, - }; - - let updated_user = match User_::update_password(&conn, user_id, &user_form) { + let updated_user = match User_::update_password(&conn, user_id, &data.password) { Ok(user) => user, Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, }; diff --git a/server/src/db/user.rs b/server/src/db/user.rs index db4aa453c..82736f8e2 100644 --- a/server/src/db/user.rs +++ b/server/src/db/user.rs @@ -75,14 +75,13 @@ impl User_ { pub fn update_password( conn: &PgConnection, user_id: i32, - form: &UserForm, + new_password: &str, ) -> Result { - let mut edited_user = form.clone(); - let password_hash = - hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password"); - edited_user.password_encrypted = password_hash; + let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password"); - Self::update(&conn, user_id, &edited_user) + diesel::update(user_.find(user_id)) + .set(password_encrypted.eq(password_hash)) + .get_result::(conn) } pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result { diff --git a/server/src/db/user_view.rs b/server/src/db/user_view.rs index 616159de5..2d50b40c4 100644 --- a/server/src/db/user_view.rs +++ b/server/src/db/user_view.rs @@ -7,6 +7,7 @@ table! { id -> Int4, name -> Varchar, avatar -> Nullable, + email -> Nullable, fedi_name -> Varchar, admin -> Bool, banned -> Bool, @@ -26,6 +27,7 @@ pub struct UserView { pub id: i32, pub name: String, pub avatar: Option, + pub email: Option, pub fedi_name: String, pub admin: bool, pub banned: bool, diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx index e97b26f90..99c340c5e 100644 --- a/ui/src/components/user.tsx +++ b/ui/src/components/user.tsx @@ -99,7 +99,6 @@ export class User extends Component { default_sort_type: null, default_listing_type: null, lang: null, - avatar: null, auth: null, }, userSettingsLoading: null, @@ -437,199 +436,240 @@ export class User extends Component {
-
- - - - + # + + +
+ + +
-
-
+ + + + {languages.map(lang => ( + + ))} +
-
-
+ + {themes.map(theme => ( + + ))} +
-
- - -
+ +
-
- - + + # + + + + +
+ +
+
- +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
{WebSocketService.Instance.site.enable_nsfw && (
-
-
- - -
+
+ +
)}
-
- -
+

-
- - {this.state.deleteAccountShowConfirm && ( - <> - - - - - + + {this.state.deleteAccountShowConfirm && ( + <> + + + + + + )}
@@ -786,6 +826,38 @@ export class User extends Component { this.setState(this.state); } + handleUserSettingsEmailChange(i: User, event: any) { + i.state.userSettingsForm.email = event.target.value; + if (i.state.userSettingsForm.email == '' && !i.state.user.email) { + i.state.userSettingsForm.email = undefined; + } + i.setState(i.state); + } + + handleUserSettingsNewPasswordChange(i: User, event: any) { + i.state.userSettingsForm.new_password = event.target.value; + if (i.state.userSettingsForm.new_password == '') { + i.state.userSettingsForm.new_password = undefined; + } + i.setState(i.state); + } + + handleUserSettingsNewPasswordVerifyChange(i: User, event: any) { + i.state.userSettingsForm.new_password_verify = event.target.value; + if (i.state.userSettingsForm.new_password_verify == '') { + i.state.userSettingsForm.new_password_verify = undefined; + } + i.setState(i.state); + } + + handleUserSettingsOldPasswordChange(i: User, event: any) { + i.state.userSettingsForm.old_password = event.target.value; + if (i.state.userSettingsForm.old_password == '') { + i.state.userSettingsForm.old_password = undefined; + } + i.setState(i.state); + } + handleImageUpload(i: User, event: any) { event.preventDefault(); let file = event.target.files[0]; @@ -856,6 +928,8 @@ export class User extends Component { if (msg.error) { alert(i18n.t(msg.error)); this.state.deleteAccountLoading = false; + this.state.avatarLoading = false; + this.state.userSettingsLoading = false; if (msg.error == 'couldnt_find_that_username_or_email') { this.context.router.history.push('/'); } @@ -882,6 +956,7 @@ export class User extends Component { UserService.Instance.user.default_listing_type; this.state.userSettingsForm.lang = UserService.Instance.user.lang; this.state.userSettingsForm.avatar = UserService.Instance.user.avatar; + this.state.userSettingsForm.email = this.state.user.email; } document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`; window.scrollTo(0, 0); diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 7020ea492..1762bd608 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -87,6 +87,7 @@ export interface UserView { id: number; name: string; avatar?: string; + email?: string; fedi_name: string; published: string; number_of_posts: number; @@ -481,6 +482,10 @@ export interface UserSettingsForm { default_listing_type: ListingType; lang: string; avatar?: string; + email?: string; + new_password?: string; + new_password_verify?: string; + old_password?: string; auth: string; } diff --git a/ui/src/translations/en.ts b/ui/src/translations/en.ts index 108620813..737268082 100644 --- a/ui/src/translations/en.ts +++ b/ui/src/translations/en.ts @@ -118,6 +118,7 @@ export const en = { unread_messages: 'Unread Messages', password: 'Password', verify_password: 'Verify Password', + old_password: 'Old Password', forgot_password: 'forgot password', reset_password_mail_sent: 'Sent an Email to reset your password.', password_change: 'Password Change', From 0f8965fac9a92a85a5f3e1cb70a9b1ef434eea2c Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jan 2020 17:47:00 -0500 Subject: [PATCH 12/19] Adding some fixes to new docs system. --- README.md | 58 ++++++++++++++++++++++++++++++------ docs/src/about.md | 4 +-- docs/src/about_features.md | 3 +- server/src/main.rs | 4 +++ ui/src/components/footer.tsx | 4 +-- ui/src/translations/en.ts | 1 + 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5109d169f..3778d3d6d 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,46 @@ The overall goal is to create an easily self-hostable, decentralized alternative Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. +Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Inferno](https://www.infernojs.org), [Typescript](https://www.typescriptlang.org/) and [Diesel](http://diesel.rs/). + +[Documentation](https://dev.lemmy.ml/docs/index.html) + +## Features + +- Open source, [AGPL License](/LICENSE). +- Self hostable, easy to deploy. + - Comes with [Docker](#docker), [Ansible](#ansible), [Kubernetes](#kubernetes). +- Clean, mobile-friendly interface. + - Live-updating Comment threads. + - Full vote scores `(+/-)` like old reddit. + - Themes, including light, dark, and solarized. + - Emojis with autocomplete support. Start typing `:` + - User tagging using `@`, Community tagging using `#`. + - Notifications, on comment replies and when you're tagged. + - i18n / internationalization support. + - RSS / Atom feeds for `All`, `Subscribed`, `Inbox`, `User`, and `Community`. +- Cross-posting support. + - A *similar post search* when creating new posts. Great for question / answer communities. +- Moderation abilities. + - Public Moderation Logs. + - Both site admins, and community moderators, who can appoint other moderators. + - Can lock, remove, and restore posts and comments. + - Can ban and unban users from communities and the site. + - Can transfer site and communities to others. +- Can fully erase your data, replacing all posts and comments. +- NSFW post / community support. +- High performance. + - Server is written in rust. + - Front end is `~80kB` gzipped. + - Supports arm64 / Raspberry Pi. + +## Why's it called Lemmy? + +- Lead singer from [Motörhead](https://invidio.us/watch?v=pWB5JZRGl0U). +- The old school [video game](). +- The [Koopa from Super Mario](https://www.mariowiki.com/Lemmy_Koopa). +- The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/). + ## Install ### Docker @@ -109,15 +149,15 @@ If you'd like to add translations, take a look a look at the [English translatio lang | done | missing --- | --- | --- -de | 97% | avatar,old_password,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -eo | 83% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no -es | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -fr | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -it | 93% | avatar,archive_link,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -nl | 85% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme -ru | 79% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no -sv | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw -zh | 77% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no +de | 96% | avatar,docs,old_password,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +eo | 83% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no +es | 91% | avatar,archive_link,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +fr | 91% | avatar,archive_link,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +it | 92% | avatar,archive_link,docs,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +nl | 85% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme +ru | 79% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no +sv | 91% | avatar,archive_link,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw +zh | 77% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no If you'd like to update this report, run: diff --git a/docs/src/about.md b/docs/src/about.md index 14743df2c..71b397412 100644 --- a/docs/src/about.md +++ b/docs/src/about.md @@ -1,6 +1,4 @@ -

A link aggregator / reddit clone for the fediverse. -
-

+# Lemmy - A link aggregator / reddit clone for the fediverse. [Lemmy Dev instance](https://dev.lemmy.ml) *for testing purposes only* diff --git a/docs/src/about_features.md b/docs/src/about_features.md index dab859c13..5c70c978e 100644 --- a/docs/src/about_features.md +++ b/docs/src/about_features.md @@ -1,3 +1,4 @@ +# Features - Open source, [AGPL License](/LICENSE). - Self hostable, easy to deploy. - Comes with [Docker](#docker), [Ansible](#ansible), [Kubernetes](#kubernetes). @@ -23,4 +24,4 @@ - High performance. - Server is written in rust. - Front end is `~80kB` gzipped. - - Supports arm64 / Raspberry Pi. \ No newline at end of file + - Supports arm64 / Raspberry Pi. diff --git a/server/src/main.rs b/server/src/main.rs index cc1cbcec4..09ba8c5f7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -32,6 +32,10 @@ fn main() { "/static", settings.front_end_dir.to_owned(), )) + .service(actix_files::Files::new( + "/docs", + settings.front_end_dir.to_owned() + "/documentation", + )) }) .bind((settings.bind, settings.port)) .unwrap() diff --git a/ui/src/components/footer.tsx b/ui/src/components/footer.tsx index 237830dd9..8aa05072d 100644 --- a/ui/src/components/footer.tsx +++ b/ui/src/components/footer.tsx @@ -23,8 +23,8 @@ export class Footer extends Component {
)} @@ -725,13 +788,13 @@ export class CommentNode extends Component { } handleModBanFromCommunityShow(i: CommentNode) { - i.state.showBanDialog = true; + i.state.showBanDialog = !i.state.showBanDialog; i.state.banType = BanType.Community; i.setState(i.state); } handleModBanShow(i: CommentNode) { - i.state.showBanDialog = true; + i.state.showBanDialog = !i.state.showBanDialog; i.state.banType = BanType.Site; i.setState(i.state); } @@ -784,6 +847,16 @@ export class CommentNode extends Component { i.setState(i.state); } + handleShowConfirmAppointAsMod(i: CommentNode) { + i.state.showConfirmAppointAsMod = true; + i.setState(i.state); + } + + handleCancelConfirmAppointAsMod(i: CommentNode) { + i.state.showConfirmAppointAsMod = false; + i.setState(i.state); + } + handleAddModToCommunity(i: CommentNode) { let form: AddModToCommunityForm = { user_id: i.props.node.comment.creator_id, @@ -791,6 +864,17 @@ export class CommentNode extends Component { added: !i.isMod, }; WebSocketService.Instance.addModToCommunity(form); + i.state.showConfirmAppointAsMod = false; + i.setState(i.state); + } + + handleShowConfirmAppointAsAdmin(i: CommentNode) { + i.state.showConfirmAppointAsAdmin = true; + i.setState(i.state); + } + + handleCancelConfirmAppointAsAdmin(i: CommentNode) { + i.state.showConfirmAppointAsAdmin = false; i.setState(i.state); } @@ -800,6 +884,7 @@ export class CommentNode extends Component { added: !i.isAdmin, }; WebSocketService.Instance.addAdmin(form); + i.state.showConfirmAppointAsAdmin = false; i.setState(i.state); } From 730e2b7f096c618641e8e3eb3c15478ed5a857fa Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 1 Jan 2020 22:12:24 -0500 Subject: [PATCH 15/19] Version v0.5.13 --- docker/prod/docker-compose.yml | 2 +- server/src/version.rs | 2 +- ui/src/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 9a0108b24..b8491b9a6 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -11,7 +11,7 @@ services: - lemmy_db:/var/lib/postgresql/data restart: always lemmy: - image: dessalines/lemmy:v0.5.12 + image: dessalines/lemmy:v0.5.13 ports: - "127.0.0.1:8536:8536" restart: always diff --git a/server/src/version.rs b/server/src/version.rs index 6397f1a8e..ed582e6f1 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &'static str = "v0.5.12"; +pub const VERSION: &'static str = "v0.5.13"; diff --git a/ui/src/version.ts b/ui/src/version.ts index 4f406a8a6..bacfecf26 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export let version: string = 'v0.5.12'; +export let version: string = 'v0.5.13'; From d08e09fbdc9d0e50e4b81410ae513b034170c36b Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Thu, 2 Jan 2020 12:30:00 +0100 Subject: [PATCH 16/19] Apply changes suggested by cargo clippy (fixes #395) --- server/src/api/comment.rs | 42 ++++++------- server/src/api/community.rs | 89 +++++++++++---------------- server/src/api/post.rs | 52 ++++++++-------- server/src/api/site.rs | 43 +++++++------ server/src/api/user.rs | 105 ++++++++++++++------------------ server/src/apub/community.rs | 5 +- server/src/apub/post.rs | 2 +- server/src/db/community_view.rs | 2 +- server/src/db/mod.rs | 8 +-- server/src/db/post_view.rs | 9 +-- server/src/main.rs | 2 +- server/src/routes/feeds.rs | 5 +- server/src/routes/nodeinfo.rs | 8 +-- server/src/routes/websocket.rs | 4 +- server/src/settings.rs | 6 +- server/src/version.rs | 2 +- server/src/websocket/server.rs | 46 +++++++------- 17 files changed, 201 insertions(+), 229 deletions(-) diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 9a057f806..ed658985c 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -51,7 +51,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -59,12 +59,12 @@ impl Perform for Oper { // Check for a community ban let post = Post::read(&conn, data.post_id)?; if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(APIError::err(&self.op, "community_ban"))?; + return Err(APIError::err(&self.op, "community_ban").into()); } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } let content_slurs_removed = remove_slurs(&data.content.to_owned()); @@ -82,14 +82,14 @@ impl Perform for Oper { let inserted_comment = match Comment::create(&conn, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_comment").into()), }; // Scan the comment for user mentions, add those rows let extracted_usernames = extract_usernames(&comment_form.content); for username_mention in &extracted_usernames { - let mention_user = User_::read_from_name(&conn, username_mention.to_string()); + let mention_user = User_::read_from_name(&conn, (*username_mention).to_string()); if mention_user.is_ok() { let mention_user_id = mention_user?.id; @@ -124,7 +124,7 @@ impl Perform for Oper { let _inserted_like = match CommentLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment").into()), }; let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?; @@ -143,7 +143,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -163,17 +163,17 @@ impl Perform for Oper { editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { - return Err(APIError::err(&self.op, "no_comment_edit_allowed"))?; + return Err(APIError::err(&self.op, "no_comment_edit_allowed").into()); } // Check for a community ban if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { - return Err(APIError::err(&self.op, "community_ban"))?; + return Err(APIError::err(&self.op, "community_ban").into()); } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } } @@ -196,14 +196,14 @@ impl Perform for Oper { let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()), }; // Scan the comment for user mentions, add those rows let extracted_usernames = extract_usernames(&comment_form.content); for username_mention in &extracted_usernames { - let mention_user = User_::read_from_name(&conn, username_mention.to_string()); + let mention_user = User_::read_from_name(&conn, (*username_mention).to_string()); if mention_user.is_ok() { let mention_user_id = mention_user?.id; @@ -255,7 +255,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -268,12 +268,12 @@ impl Perform for Oper { if data.save { match CommentSaved::save(&conn, &comment_saved_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment").into()), }; } else { match CommentSaved::unsave(&conn, &comment_saved_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment").into()), }; } @@ -293,7 +293,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -301,20 +301,20 @@ impl Perform for Oper { // Don't do a downvote if site has downvotes disabled if data.score == -1 { let site = SiteView::read(&conn)?; - if site.enable_downvotes == false { - return Err(APIError::err(&self.op, "downvotes_disabled"))?; + if !site.enable_downvotes { + return Err(APIError::err(&self.op, "downvotes_disabled").into()); } } // Check for a community ban let post = Post::read(&conn, data.post_id)?; if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(APIError::err(&self.op, "community_ban"))?; + return Err(APIError::err(&self.op, "community_ban").into()); } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } let like_form = CommentLikeForm { @@ -332,7 +332,7 @@ impl Perform for Oper { if do_add { let _inserted_like = match CommentLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment").into()), }; } diff --git a/server/src/api/community.rs b/server/src/api/community.rs index 5c97f0886..a1109c03c 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -136,21 +136,24 @@ impl Perform for Oper { let community_id = match data.id { Some(id) => id, None => { - match Community::read_from_name(&conn, data.name.to_owned().unwrap_or("main".to_string())) { + match Community::read_from_name( + &conn, + data.name.to_owned().unwrap_or_else(|| "main".to_string()), + ) { Ok(community) => community.id, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()), } } }; let community_view = match CommunityView::read(&conn, community_id, user_id) { Ok(community) => community, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()), }; let moderators = match CommunityModeratorView::for_community(&conn, community_id) { Ok(moderators) => moderators, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()), }; let site_creator_id = Site::read(&conn, 1)?.creator_id; @@ -176,21 +179,21 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; if has_slurs(&data.name) || has_slurs(&data.title) || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) { - return Err(APIError::err(&self.op, "no_slurs"))?; + return Err(APIError::err(&self.op, "no_slurs").into()); } let user_id = claims.id; // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } // When you create a community, make sure the user becomes a moderator and a follower @@ -208,7 +211,7 @@ impl Perform for Oper { let inserted_community = match Community::create(&conn, &community_form) { Ok(community) => community, - Err(_e) => return Err(APIError::err(&self.op, "community_already_exists"))?, + Err(_e) => return Err(APIError::err(&self.op, "community_already_exists").into()), }; let community_moderator_form = CommunityModeratorForm { @@ -220,10 +223,7 @@ impl Perform for Oper { match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, Err(_e) => { - return Err(APIError::err( - &self.op, - "community_moderator_already_exists", - ))? + return Err(APIError::err(&self.op, "community_moderator_already_exists").into()) } }; @@ -235,7 +235,7 @@ impl Perform for Oper { let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()), }; let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?; @@ -252,21 +252,21 @@ impl Perform for Oper { let data: &EditCommunity = &self.data; if has_slurs(&data.name) || has_slurs(&data.title) { - return Err(APIError::err(&self.op, "no_slurs"))?; + return Err(APIError::err(&self.op, "no_slurs").into()); } let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } // Verify its a mod @@ -279,7 +279,7 @@ impl Perform for Oper { ); editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { - return Err(APIError::err(&self.op, "no_community_edit_allowed"))?; + return Err(APIError::err(&self.op, "no_community_edit_allowed").into()); } let community_form = CommunityForm { @@ -296,7 +296,7 @@ impl Perform for Oper { let _updated_community = match Community::update(&conn, data.edit_id, &community_form) { Ok(community) => community, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community").into()), }; // Mod tables @@ -351,7 +351,7 @@ impl Perform for Oper { let communities = CommunityQueryBuilder::create(&conn) .sort(&sort) - .from_user_id(user_id) + .for_user(user_id) .show_nsfw(show_nsfw) .page(data.page) .limit(data.limit) @@ -372,7 +372,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -385,12 +385,12 @@ impl Perform for Oper { if data.follow { match CommunityFollower::follow(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()), }; } else { match CommunityFollower::ignore(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()), }; } @@ -410,7 +410,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -418,7 +418,7 @@ impl Perform for Oper { let communities: Vec = match CommunityFollowerView::for_user(&conn, user_id) { Ok(communities) => communities, - Err(_e) => return Err(APIError::err(&self.op, "system_err_login"))?, + Err(_e) => return Err(APIError::err(&self.op, "system_err_login").into()), }; // Return the jwt @@ -436,7 +436,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -449,12 +449,12 @@ impl Perform for Oper { if data.ban { match CommunityUserBan::ban(&conn, &community_user_ban_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned"))?, + Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned").into()), }; } else { match CommunityUserBan::unban(&conn, &community_user_ban_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned"))?, + Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned").into()), }; } @@ -491,7 +491,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -505,20 +505,14 @@ impl Perform for Oper { match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, Err(_e) => { - return Err(APIError::err( - &self.op, - "community_moderator_already_exists", - ))? + return Err(APIError::err(&self.op, "community_moderator_already_exists").into()) } }; } else { match CommunityModerator::leave(&conn, &community_moderator_form) { Ok(user) => user, Err(_e) => { - return Err(APIError::err( - &self.op, - "community_moderator_already_exists", - ))? + return Err(APIError::err(&self.op, "community_moderator_already_exists").into()) } }; } @@ -548,7 +542,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -562,14 +556,8 @@ impl Perform for Oper { admins.insert(0, creator_user); // Make sure user is the creator, or an admin - if user_id != read_community.creator_id - && !admins - .iter() - .map(|a| a.id) - .collect::>() - .contains(&user_id) - { - return Err(APIError::err(&self.op, "not_an_admin"))?; + if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) { + return Err(APIError::err(&self.op, "not_an_admin").into()); } let community_form = CommunityForm { @@ -586,7 +574,7 @@ impl Perform for Oper { let _updated_community = match Community::update(&conn, data.community_id, &community_form) { Ok(community) => community, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community").into()), }; // You also have to re-do the community_moderator table, reordering it. @@ -610,10 +598,7 @@ impl Perform for Oper { match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, Err(_e) => { - return Err(APIError::err( - &self.op, - "community_moderator_already_exists", - ))? + return Err(APIError::err(&self.op, "community_moderator_already_exists").into()) } }; } @@ -629,12 +614,12 @@ impl Perform for Oper { let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) { Ok(community) => community, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()), }; let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) { Ok(moderators) => moderators, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community").into()), }; // Return the jwt diff --git a/server/src/api/post.rs b/server/src/api/post.rs index 4b2395a8a..5bc31defe 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -93,23 +93,23 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { - return Err(APIError::err(&self.op, "no_slurs"))?; + return Err(APIError::err(&self.op, "no_slurs").into()); } let user_id = claims.id; // Check for a community ban if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { - return Err(APIError::err(&self.op, "community_ban"))?; + return Err(APIError::err(&self.op, "community_ban").into()); } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } let post_form = PostForm { @@ -128,7 +128,7 @@ impl Perform for Oper { let inserted_post = match Post::create(&conn, &post_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_post").into()), }; // They like their own post by default @@ -141,13 +141,13 @@ impl Perform for Oper { // Only add the like if the score isnt 0 let _inserted_like = match PostLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post").into()), }; // Refetch the view let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post").into()), }; Ok(PostResponse { @@ -175,7 +175,7 @@ impl Perform for Oper { let post_view = match PostView::read(&conn, data.id, user_id) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post").into()), }; let comments = CommentQueryBuilder::create(&conn) @@ -243,7 +243,7 @@ impl Perform for Oper { .list() { Ok(posts) => posts, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_get_posts"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_get_posts").into()), }; Ok(GetPostsResponse { @@ -260,7 +260,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -268,20 +268,20 @@ impl Perform for Oper { // Don't do a downvote if site has downvotes disabled if data.score == -1 { let site = SiteView::read(&conn)?; - if site.enable_downvotes == false { - return Err(APIError::err(&self.op, "downvotes_disabled"))?; + if !site.enable_downvotes { + return Err(APIError::err(&self.op, "downvotes_disabled").into()); } } // Check for a community ban let post = Post::read(&conn, data.post_id)?; if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(APIError::err(&self.op, "community_ban"))?; + return Err(APIError::err(&self.op, "community_ban").into()); } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } let like_form = PostLikeForm { @@ -294,17 +294,17 @@ impl Perform for Oper { PostLike::remove(&conn, &like_form)?; // Only add the like if the score isnt 0 - let do_add = &like_form.score != &0 && (&like_form.score == &1 || &like_form.score == &-1); + let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); if do_add { let _inserted_like = match PostLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post").into()), }; } let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post").into()), }; // just output the score @@ -319,14 +319,14 @@ impl Perform for Oper { fn perform(&self) -> Result { let data: &EditPost = &self.data; if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { - return Err(APIError::err(&self.op, "no_slurs"))?; + return Err(APIError::err(&self.op, "no_slurs").into()); } let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -341,17 +341,17 @@ impl Perform for Oper { ); editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { - return Err(APIError::err(&self.op, "no_post_edit_allowed"))?; + return Err(APIError::err(&self.op, "no_post_edit_allowed").into()); } // Check for a community ban if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { - return Err(APIError::err(&self.op, "community_ban"))?; + return Err(APIError::err(&self.op, "community_ban").into()); } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban"))?; + return Err(APIError::err(&self.op, "site_ban").into()); } let post_form = PostForm { @@ -370,7 +370,7 @@ impl Perform for Oper { let _updated_post = match Post::update(&conn, data.edit_id, &post_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post").into()), }; // Mod tables @@ -418,7 +418,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -431,12 +431,12 @@ impl Perform for Oper { if data.save { match PostSaved::save(&conn, &post_saved_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post").into()), }; } else { match PostSaved::unsave(&conn, &post_saved_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post").into()), }; } diff --git a/server/src/api/site.rs b/server/src/api/site.rs index ec89e46cd..58c34e8fa 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -160,16 +160,15 @@ impl Perform for Oper { )?; // These arrays are only for the full modlog, when a community isn't given - let mut removed_communities = Vec::new(); - let mut banned = Vec::new(); - let mut added = Vec::new(); - - if data.community_id.is_none() { - removed_communities = - ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?; - banned = ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?; - added = ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?; - } + let (removed_communities, banned, added) = if data.community_id.is_none() { + ( + ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?, + ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?, + ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?, + ) + } else { + (Vec::new(), Vec::new(), Vec::new()) + }; // Return the jwt Ok(GetModlogResponse { @@ -194,20 +193,20 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; if has_slurs(&data.name) || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) { - return Err(APIError::err(&self.op, "no_slurs"))?; + return Err(APIError::err(&self.op, "no_slurs").into()); } let user_id = claims.id; // Make sure user is an admin if !UserView::read(&conn, user_id)?.admin { - return Err(APIError::err(&self.op, "not_an_admin"))?; + return Err(APIError::err(&self.op, "not_an_admin").into()); } let site_form = SiteForm { @@ -222,7 +221,7 @@ impl Perform for Oper { match Site::create(&conn, &site_form) { Ok(site) => site, - Err(_e) => return Err(APIError::err(&self.op, "site_already_exists"))?, + Err(_e) => return Err(APIError::err(&self.op, "site_already_exists").into()), }; let site_view = SiteView::read(&conn)?; @@ -241,20 +240,20 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; if has_slurs(&data.name) || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) { - return Err(APIError::err(&self.op, "no_slurs"))?; + return Err(APIError::err(&self.op, "no_slurs").into()); } let user_id = claims.id; // Make sure user is an admin - if UserView::read(&conn, user_id)?.admin == false { - return Err(APIError::err(&self.op, "not_an_admin"))?; + if !UserView::read(&conn, user_id)?.admin { + return Err(APIError::err(&self.op, "not_an_admin").into()); } let found_site = Site::read(&conn, 1)?; @@ -271,7 +270,7 @@ impl Perform for Oper { match Site::update(&conn, 1, &site_form) { Ok(site) => site, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site").into()), }; let site_view = SiteView::read(&conn)?; @@ -426,7 +425,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -435,7 +434,7 @@ impl Perform for Oper { // Make sure user is the creator if read_site.creator_id != user_id { - return Err(APIError::err(&self.op, "not_an_admin"))?; + return Err(APIError::err(&self.op, "not_an_admin").into()); } let site_form = SiteForm { @@ -450,7 +449,7 @@ impl Perform for Oper { match Site::update(&conn, 1, &site_form) { Ok(site) => site, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site").into()), }; // Mod tables diff --git a/server/src/api/user.rs b/server/src/api/user.rs index c074228f7..912587da3 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -172,18 +172,13 @@ impl Perform for Oper { // Fetch that username / email let user: User_ = match User_::find_by_email_or_username(&conn, &data.username_or_email) { Ok(user) => user, - Err(_e) => { - return Err(APIError::err( - &self.op, - "couldnt_find_that_username_or_email", - ))? - } + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_that_username_or_email").into()), }; // Verify the password let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false); if !valid { - return Err(APIError::err(&self.op, "password_incorrect"))?; + return Err(APIError::err(&self.op, "password_incorrect").into()); } // Return the jwt @@ -202,22 +197,22 @@ impl Perform for Oper { // Make sure site has open registration if let Ok(site) = SiteView::read(&conn) { if !site.open_registration { - return Err(APIError::err(&self.op, "registration_closed"))?; + return Err(APIError::err(&self.op, "registration_closed").into()); } } // Make sure passwords match - if &data.password != &data.password_verify { - return Err(APIError::err(&self.op, "passwords_dont_match"))?; + if data.password != data.password_verify { + return Err(APIError::err(&self.op, "passwords_dont_match").into()); } if has_slurs(&data.username) { - return Err(APIError::err(&self.op, "no_slurs"))?; + return Err(APIError::err(&self.op, "no_slurs").into()); } // Make sure there are no admins - if data.admin && UserView::admins(&conn)?.len() > 0 { - return Err(APIError::err(&self.op, "admin_already_created"))?; + if data.admin && !UserView::admins(&conn)?.is_empty() { + return Err(APIError::err(&self.op, "admin_already_created").into()); } // Register the new user @@ -241,7 +236,7 @@ impl Perform for Oper { // Create the user let inserted_user = match User_::register(&conn, &user_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "user_already_exists"))?, + Err(_e) => return Err(APIError::err(&self.op, "user_already_exists").into()), }; // Create the main community if it doesn't exist @@ -272,7 +267,7 @@ impl Perform for Oper { let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists").into()), }; // If its an admin, add them as a mod and follower to main @@ -286,10 +281,7 @@ impl Perform for Oper { match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, Err(_e) => { - return Err(APIError::err( - &self.op, - "community_moderator_already_exists", - ))? + return Err(APIError::err(&self.op, "community_moderator_already_exists").into()) } }; } @@ -309,7 +301,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -327,7 +319,7 @@ impl Perform for Oper { Some(new_password_verify) => { // Make sure passwords match if new_password != new_password_verify { - return Err(APIError::err(&self.op, "passwords_dont_match"))?; + return Err(APIError::err(&self.op, "passwords_dont_match").into()); } // Check the old password @@ -336,14 +328,14 @@ impl Perform for Oper { let valid: bool = verify(old_password, &read_user.password_encrypted).unwrap_or(false); if !valid { - return Err(APIError::err(&self.op, "password_incorrect"))?; + return Err(APIError::err(&self.op, "password_incorrect").into()); } User_::update_password(&conn, user_id, &new_password)?.password_encrypted } - None => return Err(APIError::err(&self.op, "password_incorrect"))?, + None => return Err(APIError::err(&self.op, "password_incorrect").into()), } } - None => return Err(APIError::err(&self.op, "passwords_dont_match"))?, + None => return Err(APIError::err(&self.op, "passwords_dont_match").into()), } } None => read_user.password_encrypted, @@ -368,7 +360,7 @@ impl Perform for Oper { let updated_user = match User_::update(&conn, user_id, &user_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user").into()), }; // Return the jwt @@ -409,14 +401,14 @@ impl Perform for Oper { None => { match User_::read_from_name( &conn, - data.username.to_owned().unwrap_or("admin".to_string()), + data + .username + .to_owned() + .unwrap_or_else(|| "admin".to_string()), ) { Ok(user) => user.id, Err(_e) => { - return Err(APIError::err( - &self.op, - "couldnt_find_that_username_or_email", - ))? + return Err(APIError::err(&self.op, "couldnt_find_that_username_or_email").into()) } } } @@ -478,14 +470,14 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; // Make sure user is an admin - if UserView::read(&conn, user_id)?.admin == false { - return Err(APIError::err(&self.op, "not_an_admin"))?; + if !UserView::read(&conn, user_id)?.admin { + return Err(APIError::err(&self.op, "not_an_admin").into()); } let read_user = User_::read(&conn, data.user_id)?; @@ -509,7 +501,7 @@ impl Perform for Oper { match User_::update(&conn, data.user_id, &user_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user").into()), }; // Mod tables @@ -541,14 +533,14 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; // Make sure user is an admin - if UserView::read(&conn, user_id)?.admin == false { - return Err(APIError::err(&self.op, "not_an_admin"))?; + if !UserView::read(&conn, user_id)?.admin { + return Err(APIError::err(&self.op, "not_an_admin").into()); } let read_user = User_::read(&conn, data.user_id)?; @@ -572,7 +564,7 @@ impl Perform for Oper { match User_::update(&conn, data.user_id, &user_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user").into()), }; // Mod tables @@ -608,7 +600,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -636,7 +628,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -664,7 +656,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -680,7 +672,7 @@ impl Perform for Oper { let _updated_user_mention = match UserMention::update(&conn, user_mention.id, &user_mention_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()), }; let user_mention_view = UserMentionView::read(&conn, user_mention.id, user_id)?; @@ -699,7 +691,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -724,7 +716,7 @@ impl Perform for Oper { let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()), }; } @@ -745,7 +737,7 @@ impl Perform for Oper { let _updated_mention = match UserMention::update(&conn, mention.user_mention_id, &mention_form) { Ok(mention) => mention, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()), }; } @@ -763,7 +755,7 @@ impl Perform for Oper { let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), }; let user_id = claims.id; @@ -773,7 +765,7 @@ impl Perform for Oper { // Verify the password let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false); if !valid { - return Err(APIError::err(&self.op, "password_incorrect"))?; + return Err(APIError::err(&self.op, "password_incorrect").into()); } // Comments @@ -796,7 +788,7 @@ impl Perform for Oper { let _updated_comment = match Comment::update(&conn, comment.id, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment").into()), }; } @@ -824,7 +816,7 @@ impl Perform for Oper { let _updated_post = match Post::update(&conn, post.id, &post_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post").into()), }; } @@ -843,12 +835,7 @@ impl Perform for Oper { // Fetch that email let user: User_ = match User_::find_by_email(&conn, &data.email) { Ok(user) => user, - Err(_e) => { - return Err(APIError::err( - &self.op, - "couldnt_find_that_username_or_email", - ))? - } + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_that_username_or_email").into()), }; // Generate a random token @@ -865,7 +852,7 @@ impl Perform for Oper { let html = &format!("

Password Reset Request for {}


Click here to reset your password", user.name, hostname, &token); match send_email(subject, user_email, &user.name, html) { Ok(_o) => _o, - Err(_e) => return Err(APIError::err(&self.op, &_e.to_string()))?, + Err(_e) => return Err(APIError::err(&self.op, &_e).into()), }; Ok(PasswordResetResponse { @@ -883,14 +870,14 @@ impl Perform for Oper { let user_id = PasswordResetRequest::read_from_token(&conn, &data.token)?.user_id; // Make sure passwords match - if &data.password != &data.password_verify { - return Err(APIError::err(&self.op, "passwords_dont_match"))?; + if data.password != data.password_verify { + return Err(APIError::err(&self.op, "passwords_dont_match").into()); } // Update the user with the new password let updated_user = match User_::update_password(&conn, user_id, &data.password) { Ok(user) => user, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user").into()), }; // Return the jwt diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 3de68b967..504e17907 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -60,10 +60,7 @@ impl Community { let mut collection = UnorderedCollection::default(); collection.object_props.set_context_object(context()).ok(); - collection - .object_props - .set_id_string(base_url.to_string()) - .ok(); + collection.object_props.set_id_string(base_url).ok(); let connection = establish_connection(); //As we are an object, we validated that the community id was valid diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs index 19163657b..ebb171290 100644 --- a/server/src/apub/post.rs +++ b/server/src/apub/post.rs @@ -9,7 +9,7 @@ impl Post { let mut page = Page::default(); page.object_props.set_context_object(context()).ok(); - page.object_props.set_id_string(base_url.to_string()).ok(); + page.object_props.set_id_string(base_url).ok(); page.object_props.set_name_string(self.name.to_owned()).ok(); if let Some(body) = &self.body { diff --git a/server/src/db/community_view.rs b/server/src/db/community_view.rs index e57fd759c..2d49cd447 100644 --- a/server/src/db/community_view.rs +++ b/server/src/db/community_view.rs @@ -124,7 +124,7 @@ impl<'a> CommunityQueryBuilder<'a> { self } - pub fn from_user_id>(mut self, from_user_id: T) -> Self { + pub fn for_user>(mut self, from_user_id: T) -> Self { self.from_user_id = from_user_id.get_optional(); self } diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index 7824580d5..fe6cb3ce4 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -101,13 +101,13 @@ pub trait MaybeOptional { impl MaybeOptional for T { fn get_optional(self) -> Option { - return Some(self); + Some(self) } } impl MaybeOptional for Option { fn get_optional(self) -> Option { - return self; + self } } @@ -118,12 +118,12 @@ lazy_static! { Pool::builder() .max_size(Settings::get().database.pool_size) .build(manager) - .expect(&format!("Error connecting to {}", db_url)) + .unwrap_or_else(|_| panic!("Error connecting to {}", db_url)) }; } pub fn establish_connection() -> PooledConnection> { - return PG_POOL.get().unwrap(); + PG_POOL.get().unwrap() } #[derive(EnumString, ToString, Debug, Serialize, Deserialize)] diff --git a/server/src/db/post_view.rs b/server/src/db/post_view.rs index d05eeccad..22e2eb14e 100644 --- a/server/src/db/post_view.rs +++ b/server/src/db/post_view.rs @@ -189,12 +189,9 @@ impl<'a> PostQueryBuilder<'a> { let mut query = self.query; - match self.listing_type { - ListingType::Subscribed => { - query = query.filter(subscribed.eq(true)); - } - _ => {} - }; + if let ListingType::Subscribed = self.listing_type { + query = query.filter(subscribed.eq(true)); + } query = match self.sort { SortType::Hot => query diff --git a/server/src/main.rs b/server/src/main.rs index 09ba8c5f7..10a7a8559 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,7 +10,7 @@ use lemmy_server::settings::Settings; embed_migrations!(); fn main() { - let _ = env_logger::init(); + env_logger::init(); let sys = actix::System::new("lemmy"); // Run the migrations from code diff --git a/server/src/routes/feeds.rs b/server/src/routes/feeds.rs index 55b457c79..0b2ccac19 100644 --- a/server/src/routes/feeds.rs +++ b/server/src/routes/feeds.rs @@ -85,7 +85,10 @@ fn get_feed(path: web::Path<(String, String)>, info: web::Query) -> Http } fn get_sort_type(info: web::Query) -> Result { - let sort_query = info.sort.to_owned().unwrap_or(SortType::Hot.to_string()); + let sort_query = info + .sort + .to_owned() + .unwrap_or_else(|| SortType::Hot.to_string()); SortType::from_str(&sort_query) } diff --git a/server/src/routes/nodeinfo.rs b/server/src/routes/nodeinfo.rs index cbc0fb0fc..246596083 100644 --- a/server/src/routes/nodeinfo.rs +++ b/server/src/routes/nodeinfo.rs @@ -21,9 +21,9 @@ pub fn node_info_well_known() -> HttpResponse { } }); - return HttpResponse::Ok() + HttpResponse::Ok() .content_type("application/json") - .body(json.to_string()); + .body(json.to_string()) } fn node_info() -> HttpResponse { @@ -53,7 +53,7 @@ fn node_info() -> HttpResponse { "openRegistrations": site_view.open_registration, } }); - return HttpResponse::Ok() + HttpResponse::Ok() .content_type("application/json") - .body(json.to_string()); + .body(json.to_string()) } diff --git a/server/src/routes/websocket.rs b/server/src/routes/websocket.rs index 3af229bff..8ae3552c6 100644 --- a/server/src/routes/websocket.rs +++ b/server/src/routes/websocket.rs @@ -9,7 +9,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { // Start chat server actor in separate thread let server = ChatServer::default().start(); cfg - .data(server.clone()) + .data(server) .service(web::resource("/api/v1/ws").to(chat_route)); } @@ -33,7 +33,7 @@ fn chat_route( .connection_info() .remote() .unwrap_or("127.0.0.1:12345") - .split(":") + .split(':') .next() .unwrap_or("127.0.0.1") .to_string(), diff --git a/server/src/settings.rs b/server/src/settings.rs index 6cb4de0bd..f4bb2a42a 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -50,10 +50,10 @@ pub struct Database { lazy_static! { static ref SETTINGS: Settings = { - return match Settings::init() { + match Settings::init() { Ok(c) => c, Err(e) => panic!("{}", e), - }; + } }; } @@ -76,7 +76,7 @@ impl Settings { // https://github.com/mehcode/config-rs/issues/73 s.merge(Environment::with_prefix("LEMMY").separator("__"))?; - return s.try_into(); + s.try_into() } /// Returns the config as a struct. diff --git a/server/src/version.rs b/server/src/version.rs index 6397f1a8e..eeb4588aa 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &'static str = "v0.5.12"; +pub const VERSION: &str = "v0.5.12"; diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 56fff2751..4fcbc2414 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -91,7 +91,7 @@ impl Default for ChatServer { ChatServer { sessions: HashMap::new(), rate_limits: HashMap::new(), - rooms: rooms, + rooms, rng: rand::thread_rng(), } } @@ -99,8 +99,8 @@ impl Default for ChatServer { impl ChatServer { /// Send message to all users in the room - fn send_room_message(&self, room: &i32, message: &str, skip_id: usize) { - if let Some(sessions) = self.rooms.get(room) { + fn send_room_message(&self, room: i32, message: &str, skip_id: usize) { + if let Some(sessions) = self.rooms.get(&room) { for id in sessions { if *id != skip_id { if let Some(info) = self.sessions.get(id) { @@ -113,7 +113,7 @@ impl ChatServer { fn join_room(&mut self, room_id: i32, id: usize) { // remove session from all rooms - for (_n, sessions) in &mut self.rooms { + for sessions in self.rooms.values_mut() { sessions.remove(&id); } @@ -122,12 +122,12 @@ impl ChatServer { self.rooms.insert(room_id, HashSet::new()); } - &self.rooms.get_mut(&room_id).unwrap().insert(id); + self.rooms.get_mut(&room_id).unwrap().insert(id); } fn send_community_message( &self, - community_id: &i32, + community_id: i32, message: &str, skip_id: usize, ) -> Result<(), Error> { @@ -138,12 +138,12 @@ impl ChatServer { let posts = PostQueryBuilder::create(&conn) .listing_type(ListingType::Community) .sort(&SortType::New) - .for_community_id(*community_id) + .for_community_id(community_id) .limit(9999) .list()?; for post in posts { - self.send_room_message(&post.id, message, skip_id); + self.send_room_message(post.id, message, skip_id); } Ok(()) @@ -173,6 +173,7 @@ impl ChatServer { ) } + #[allow(clippy::float_cmp)] fn check_rate_limit_full(&mut self, id: usize, rate: i32, per: i32) -> Result<(), Error> { if let Some(info) = self.sessions.get(&id) { if let Some(rate_limit) = self.rate_limits.get_mut(&info.ip) { @@ -194,10 +195,13 @@ impl ChatServer { "Rate limited IP: {}, time_passed: {}, allowance: {}", &info.ip, time_passed, rate_limit.allowance ); - Err(APIError { - op: "Rate Limit".to_string(), - message: format!("Too many requests. {} per {} seconds", rate, per), - })? + Err( + APIError { + op: "Rate Limit".to_string(), + message: format!("Too many requests. {} per {} seconds", rate, per), + } + .into(), + ) } else { rate_limit.allowance -= 1.0; Ok(()) @@ -264,7 +268,7 @@ impl Handler for ChatServer { // remove address if self.sessions.remove(&msg.id).is_some() { // remove session from all rooms - for (_id, sessions) in &mut self.rooms { + for sessions in self.rooms.values_mut() { if sessions.remove(&msg.id) { // rooms.push(*id); } @@ -292,7 +296,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result Result { @@ -392,7 +396,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { @@ -400,7 +404,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { @@ -437,7 +441,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { @@ -454,7 +458,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { @@ -465,7 +469,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { @@ -482,7 +486,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { From 7be492d129c294833936fa94cb26406b9ae79a8f Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Thu, 2 Jan 2020 12:47:11 +0100 Subject: [PATCH 17/19] Run lint in Travis CI --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index dfdcbf7e3..0f180dba5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,12 @@ before_cache: before_script: - psql -c "create user lemmy with password 'password' superuser;" -U postgres - psql -c 'create database lemmy with owner lemmy;' -U postgres + - rustup component add clippy --toolchain stable-x86_64-unknown-linux-gnu before_install: - cd server script: + # Default checks, but fail if anything is detected + - cargo clippy -- -D clippy::style -D clippy::correctness -D clippy::complexity -D clippy::perf - cargo build - diesel migration run - cargo test From dc331d5293f1853e3ea47d23956595381d9d73ed Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 2 Jan 2020 10:34:40 -0500 Subject: [PATCH 18/19] Fixing deploy and version for clippy. --- docker/dev/deploy.sh | 2 +- server/src/version.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/dev/deploy.sh b/docker/dev/deploy.sh index c9d52215a..8a709dcd9 100755 --- a/docker/dev/deploy.sh +++ b/docker/dev/deploy.sh @@ -12,7 +12,7 @@ cd ../../ echo "export let version: string = '$(git describe --tags)';" > "ui/src/version.ts" git add "ui/src/version.ts" # Setting the version on the backend -echo "pub const VERSION: &'static str = \"$(git describe --tags)\";" > "server/src/version.rs" +echo "pub const VERSION: &str = \"$(git describe --tags)\";" > "server/src/version.rs" git add "server/src/version.rs" cd docker/dev diff --git a/server/src/version.rs b/server/src/version.rs index ed582e6f1..9bddd86b3 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &'static str = "v0.5.13"; +pub const VERSION: &str = "v0.5.13"; From 576980bf64fcde5ec66e0260c81ef3225dd264bd Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 2 Jan 2020 10:47:18 -0500 Subject: [PATCH 19/19] Version v0.5.14 --- docker/prod/docker-compose.yml | 2 +- server/src/version.rs | 2 +- ui/src/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index b8491b9a6..12d274d46 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -11,7 +11,7 @@ services: - lemmy_db:/var/lib/postgresql/data restart: always lemmy: - image: dessalines/lemmy:v0.5.13 + image: dessalines/lemmy:v0.5.14 ports: - "127.0.0.1:8536:8536" restart: always diff --git a/server/src/version.rs b/server/src/version.rs index 9bddd86b3..29f4246e6 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.5.13"; +pub const VERSION: &str = "v0.5.14"; diff --git a/ui/src/version.ts b/ui/src/version.ts index bacfecf26..f8c23a87a 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export let version: string = 'v0.5.13'; +export let version: string = 'v0.5.14';