From: Dessalines Date: Tue, 14 Apr 2020 20:07:20 +0000 (-0400) Subject: Merge branch 'dev' into federation X-Git-Url: http://these/git/?a=commitdiff_plain;h=1336b4ed6023e7fcf0fd40be63569966ee4b1b45;hp=-c;p=lemmy.git Merge branch 'dev' into federation --- 1336b4ed6023e7fcf0fd40be63569966ee4b1b45 diff --combined server/src/api/site.rs index ad45e8d1,3720a2c4..4202fea0 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@@ -97,6 -97,22 +97,22 @@@ pub struct TransferSite auth: String, } + #[derive(Serialize, Deserialize)] + pub struct GetSiteConfig { + auth: String, + } + + #[derive(Serialize, Deserialize)] + pub struct GetSiteConfigResponse { + config_hjson: String, + } + + #[derive(Serialize, Deserialize)] + pub struct SaveSiteConfig { + config_hjson: String, + auth: String, + } + impl Perform for Oper { fn perform(&self, conn: &PgConnection) -> Result { let _data: &ListCategories = &self.data; @@@ -281,7 -297,8 +297,7 @@@ impl Perform for Oper< fn perform(&self, conn: &PgConnection) -> Result { let _data: &GetSite = &self.data; - let site = Site::read(&conn, 1); - let site_view = if site.is_ok() { + let site_view = if let Ok(_site) = Site::read(&conn, 1) { Some(SiteView::read(&conn)?) } else if let Some(setup) = Settings::get().setup.as_ref() { let register = Register { @@@ -311,16 -328,11 +327,16 @@@ }; let mut admins = UserView::admins(&conn)?; - if site_view.is_some() { - let site_creator_id = site_view.to_owned().unwrap().creator_id; - let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); - let creator_user = admins.remove(creator_index); - admins.insert(0, creator_user); + + // Make sure the site creator is the top admin + if let Some(site_view) = site_view.to_owned() { + let site_creator_id = site_view.creator_id; + // TODO investigate why this is sometimes coming back null + // Maybe user_.admin isn't being set to true? + if let Some(creator_index) = admins.iter().position(|r| r.id == site_creator_id) { + let creator_user = admins.remove(creator_index); + admins.insert(0, creator_user); + } } let banned = UserView::banned(&conn)?; @@@ -514,3 -526,57 +530,57 @@@ impl Perform for Oper< }) } } + + impl Perform for Oper { + fn perform(&self, conn: &PgConnection) -> Result { + let data: &GetSiteConfig = &self.data; + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + + let user_id = claims.id; + + // Only let admins read this + let admins = UserView::admins(&conn)?; + let admin_ids: Vec = admins.into_iter().map(|m| m.id).collect(); + + if !admin_ids.contains(&user_id) { + return Err(APIError::err("not_an_admin").into()); + } + + let config_hjson = Settings::read_config_file()?; + + Ok(GetSiteConfigResponse { config_hjson }) + } + } + + impl Perform for Oper { + fn perform(&self, conn: &PgConnection) -> Result { + let data: &SaveSiteConfig = &self.data; + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, + Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + + let user_id = claims.id; + + // Only let admins read this + let admins = UserView::admins(&conn)?; + let admin_ids: Vec = admins.into_iter().map(|m| m.id).collect(); + + if !admin_ids.contains(&user_id) { + return Err(APIError::err("not_an_admin").into()); + } + + // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem + let config_hjson = match Settings::save_config_file(&data.config_hjson) { + Ok(config_hjson) => config_hjson, + Err(_e) => return Err(APIError::err("couldnt_update_site").into()), + }; + + Ok(GetSiteConfigResponse { config_hjson }) + } + } diff --combined server/src/lib.rs index e45311ee,9bbfe251..2c78cfc2 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@@ -16,8 -16,6 +16,8 @@@ pub extern crate dotenv pub extern crate jsonwebtoken; pub extern crate lettre; pub extern crate lettre_email; +extern crate log; +pub extern crate openssl; pub extern crate rand; pub extern crate regex; pub extern crate rss; @@@ -36,7 -34,7 +36,7 @@@ pub mod version pub mod websocket; use crate::settings::Settings; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, FixedOffset, Local, NaiveDateTime}; use isahc::prelude::*; use lettre::smtp::authentication::{Credentials, Mechanism}; use lettre::smtp::extension::ClientId; @@@ -50,6 -48,10 +50,6 @@@ use rand::{thread_rng, Rng} use regex::{Regex, RegexBuilder}; use serde::Deserialize; -pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime { - DateTime::::from_utc(ndt, Utc) -} - pub fn naive_now() -> NaiveDateTime { chrono::prelude::Utc::now().naive_utc() } @@@ -58,11 -60,6 +58,11 @@@ pub fn naive_from_unix(time: i64) -> Na NaiveDateTime::from_timestamp(time, 0) } +pub fn convert_datetime(datetime: NaiveDateTime) -> DateTime { + let now = Local::now(); + DateTime::::from_utc(datetime, *now.offset()) +} + pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } @@@ -115,7 -112,7 +115,7 @@@ pub fn send_email to_username: &str, html: &str, ) -> Result<(), String> { - let email_config = Settings::get().email.as_ref().ok_or("no_email_setup")?; + let email_config = Settings::get().email.ok_or("no_email_setup")?; let email = Email::builder() .to((to_email, to_username)) @@@ -130,7 -127,7 +130,7 @@@ } else { SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap() } - .hello_name(ClientId::Domain(Settings::get().hostname.to_owned())) + .hello_name(ClientId::Domain(Settings::get().hostname)) .smtp_utf8(true) .authentication_mechanism(Mechanism::Plain) .connection_reuse(ConnectionReuseParameters::ReuseUnlimited); diff --combined server/src/main.rs index 59dc2cb7,f3887527..88d62eb9 --- a/server/src/main.rs +++ b/server/src/main.rs @@@ -6,21 -6,15 +6,21 @@@ use actix::prelude::* use actix_web::*; use diesel::r2d2::{ConnectionManager, Pool}; use diesel::PgConnection; +use failure::Error; +use lemmy_server::apub::fetcher::fetch_all; +use lemmy_server::db::code_migrations::run_advanced_migrations; use lemmy_server::routes::{api, federation, feeds, index, nodeinfo, webfinger, websocket}; use lemmy_server::settings::Settings; use lemmy_server::websocket::server::*; -use std::io; +use log::warn; +use std::thread; +use std::thread::sleep; +use std::time::Duration; embed_migrations!(); #[actix_rt::main] -async fn main() -> io::Result<()> { +async fn main() -> Result<(), Error> { env_logger::init(); let settings = Settings::get(); @@@ -34,53 -28,41 +34,54 @@@ // Run the migrations from code let conn = pool.get().unwrap(); embedded_migrations::run(&conn).unwrap(); + run_advanced_migrations(&conn).unwrap(); // Set up websocket server let server = ChatServer::startup(pool.clone()).start(); + thread::spawn(move || { + // some work here + sleep(Duration::from_secs(5)); + println!("Fetching apub data"); + match fetch_all(&conn) { + Ok(_) => {} + Err(e) => warn!("Error during apub fetch: {}", e), + } + }); + println!( "Starting http server at {}:{}", settings.bind, settings.port ); // Create Http server with websocket support - HttpServer::new(move || { - let settings = Settings::get(); - App::new() - .wrap(middleware::Logger::default()) - .data(pool.clone()) - .data(server.clone()) - // The routes - .configure(api::config) - .configure(federation::config) - .configure(feeds::config) - .configure(index::config) - .configure(nodeinfo::config) - .configure(webfinger::config) - .configure(websocket::config) - // static files - .service(actix_files::Files::new( - "/static", - settings.front_end_dir.to_owned(), - )) - .service(actix_files::Files::new( - "/docs", - settings.front_end_dir + "/documentation", - )) - }) - .bind((settings.bind, settings.port))? - .run() - .await + Ok( + HttpServer::new(move || { ++ let settings = Settings::get(); + App::new() + .wrap(middleware::Logger::default()) + .data(pool.clone()) + .data(server.clone()) + // The routes + .configure(api::config) + .configure(federation::config) + .configure(feeds::config) + .configure(index::config) + .configure(nodeinfo::config) + .configure(webfinger::config) + .configure(websocket::config) + // static files + .service(actix_files::Files::new( + "/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))? + .run() + .await?, + ) } diff --combined server/src/settings.rs index 29d5966b,6e5667cb..8c3cd6a1 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@@ -1,12 -1,15 +1,15 @@@ use config::{Config, ConfigError, Environment, File}; + use failure::Error; use serde::Deserialize; use std::env; + use std::fs; use std::net::IpAddr; + use std::sync::RwLock; static CONFIG_FILE_DEFAULTS: &str = "config/defaults.hjson"; static CONFIG_FILE: &str = "config/config.hjson"; - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Clone)] pub struct Settings { pub setup: Option, pub database: Database, @@@ -17,10 -20,10 +20,10 @@@ pub front_end_dir: String, pub rate_limit: RateLimitConfig, pub email: Option, - pub federation_enabled: bool, + pub federation: Federation, } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Clone)] pub struct Setup { pub admin_username: String, pub admin_password: String, @@@ -28,7 -31,7 +31,7 @@@ pub site_name: String, } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Clone)] pub struct RateLimitConfig { pub message: i32, pub message_per_second: i32, @@@ -38,7 -41,7 +41,7 @@@ pub register_per_second: i32, } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Clone)] pub struct EmailConfig { pub smtp_server: String, pub smtp_login: Option, @@@ -47,7 -50,7 +50,7 @@@ pub use_tls: bool, } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Clone)] pub struct Database { pub user: String, pub password: String, @@@ -57,20 -60,11 +60,18 @@@ pub pool_size: u32, } +#[derive(Debug, Deserialize)] +pub struct Federation { + pub enabled: bool, + pub followed_instances: String, + pub tls_enabled: bool, +} + lazy_static! { - static ref SETTINGS: Settings = { - match Settings::init() { - Ok(c) => c, - Err(e) => panic!("{}", e), - } - }; + static ref SETTINGS: RwLock = RwLock::new(match Settings::init() { + Ok(c) => c, + Err(e) => panic!("{}", e), + }); } impl Settings { @@@ -96,8 -90,8 +97,8 @@@ } /// Returns the config as a struct. - pub fn get() -> &'static Self { - &SETTINGS + pub fn get() -> Self { + SETTINGS.read().unwrap().to_owned() } /// Returns the postgres connection url. If LEMMY_DATABASE_URL is set, that is used, @@@ -119,4 -113,22 +120,22 @@@ pub fn api_endpoint(&self) -> String { format!("{}/api/v1", self.hostname) } + + pub fn read_config_file() -> Result { + Ok(fs::read_to_string(CONFIG_FILE)?) + } + + pub fn save_config_file(data: &str) -> Result { + fs::write(CONFIG_FILE, data)?; + + // Reload the new settings + // From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804 + let mut new_settings = SETTINGS.write().unwrap(); + *new_settings = match Settings::init() { + Ok(c) => c, + Err(e) => panic!("{}", e), + }; + + Self::read_config_file() + } } diff --combined server/src/websocket/server.rs index f205c91e,0f2d2d26..faa8041c --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@@ -505,6 -505,9 +505,6 @@@ fn parse_json_message(chat: &mut ChatSe let user_operation: UserOperation = UserOperation::from_str(&op)?; - // TODO: none of the chat messages are going to work if stuff is submitted via http api, - // need to move that handling elsewhere - // A DDOS check chat.check_rate_limit_message(msg.id, false)?; @@@ -551,9 -554,7 +551,9 @@@ } UserOperation::GetCommunity => { let get_community: GetCommunity = serde_json::from_str(data)?; + let mut res = Oper::new(get_community).perform(&conn)?; + let community_id = res.community.id; chat.join_community_room(community_id, msg.id); @@@ -628,7 -629,6 +628,7 @@@ } UserOperation::GetPosts => { let get_posts: GetPosts = serde_json::from_str(data)?; + if get_posts.community_id.is_none() { // 0 is the "all" community chat.join_community_room(0, msg.id); @@@ -708,6 -708,16 +708,16 @@@ res.online = chat.sessions.len(); to_json_string(&user_operation, &res) } + UserOperation::GetSiteConfig => { + let get_site_config: GetSiteConfig = serde_json::from_str(data)?; + let res = Oper::new(get_site_config).perform(&conn)?; + to_json_string(&user_operation, &res) + } + UserOperation::SaveSiteConfig => { + let save_site_config: SaveSiteConfig = serde_json::from_str(data)?; + let res = Oper::new(save_site_config).perform(&conn)?; + to_json_string(&user_operation, &res) + } UserOperation::Search => { do_user_operation::(user_operation, data, &conn) } diff --combined ui/package.json index 7d946614,21458f0d..d2eb1de9 --- a/ui/package.json +++ b/ui/package.json @@@ -14,19 -14,20 +14,20 @@@ }, "keywords": [], "dependencies": { + "@joeattardi/emoji-button": "^2.12.1", "@types/autosize": "^3.0.6", - "@types/js-cookie": "^2.2.5", + "@types/js-cookie": "^2.2.6", "@types/jwt-decode": "^2.2.1", - "@types/markdown-it": "^10.0.0", + "@types/markdown-it": "^0.0.9", "@types/markdown-it-container": "^2.0.2", - "@types/node": "^13.9.2", + "@types/node": "^13.11.1", "autosize": "^4.0.2", "bootswatch": "^4.3.1", - "classcat": "^1.1.3", + "classcat": "^4.0.2", "dotenv": "^8.2.0", "emoji-short-name": "^1.0.0", - "husky": "^4.2.3", - "i18next": "^19.3.3", + "husky": "^4.2.5", + "i18next": "^19.4.1", "inferno": "^7.4.2", "inferno-i18next": "nimbusec-oss/inferno-i18next", "inferno-router": "^7.4.2", @@@ -37,26 -38,26 +38,26 @@@ "markdown-it-emoji": "^1.4.0", "mobius1-selectr": "^2.4.13", "moment": "^2.24.0", - "prettier": "^1.18.2", + "prettier": "^2.0.4", "reconnecting-websocket": "^4.4.0", - "rxjs": "^6.4.0", - "terser": "^4.6.7", - "tippy.js": "^6.1.0", + "rxjs": "^6.5.5", + "terser": "^4.6.11", + "tippy.js": "^6.1.1", "toastify-js": "^1.7.0", - "tributejs": "^5.1.2", + "tributejs": "^5.1.3", "twemoji": "^12.1.2", "ws": "^7.2.3" }, "devDependencies": { "eslint": "^6.5.1", "eslint-plugin-inferno": "^7.14.3", - "eslint-plugin-jane": "^7.2.0", + "eslint-plugin-jane": "^7.2.1", "fuse-box": "^3.1.3", - "lint-staged": "^10.0.8", - "sortpack": "^2.1.2", - "ts-node": "^8.7.0", - "ts-transform-classcat": "^0.0.2", - "ts-transform-inferno": "^4.0.2", + "lint-staged": "^10.1.3", + "sortpack": "^2.1.4", + "ts-node": "^8.8.2", + "ts-transform-classcat": "^1.0.0", + "ts-transform-inferno": "^4.0.3", "typescript": "^3.8.3" }, "engines": { diff --combined ui/src/components/comment-form.tsx index ae3e7cfc,5239eb2c..b3c1a9a1 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@@ -17,10 -17,12 +17,12 @@@ import toast, setupTribute, wsJsonToRes, + emojiPicker, } from '../utils'; import { WebSocketService, UserService } from '../services'; import autosize from 'autosize'; import Tribute from 'tributejs/src/Tribute.js'; + import emojiShortName from 'emoji-short-name'; import { i18n } from '../i18next'; interface CommentFormProps { @@@ -69,6 -71,8 +71,8 @@@ export class CommentForm extends Compon super(props, context); this.tribute = setupTribute(); + this.setupEmojiPicker(); + this.state = this.emptyState; if (this.props.node) { @@@ -158,8 -162,8 +162,9 @@@ {this.state.commentForm.content && ( {this.state.privateMessageForm.content && (