From c3fbb7702f62ced73f11359fb9cb07e7f3390ee7 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 19 Dec 2022 15:54:42 +0000 Subject: [PATCH] Allow embedding Lemmy, fix setup error (#2618) * Fix error during site creation due to empty LocalSiteRateLimit update * Move main function into lib.rs, to allow calling from other crates Co-authored-by: Dessalines --- .../src/impls/local_site_rate_limit.rs | 27 ++- src/lib.rs | 154 +++++++++++++++- src/main.rs | 169 +----------------- 3 files changed, 183 insertions(+), 167 deletions(-) diff --git a/crates/db_schema/src/impls/local_site_rate_limit.rs b/crates/db_schema/src/impls/local_site_rate_limit.rs index 478b42169..b1af5f869 100644 --- a/crates/db_schema/src/impls/local_site_rate_limit.rs +++ b/crates/db_schema/src/impls/local_site_rate_limit.rs @@ -23,11 +23,34 @@ impl LocalSiteRateLimit { .get_result::(conn) .await } - pub async fn update(pool: &DbPool, form: &LocalSiteRateLimitUpdateForm) -> Result { + pub async fn update(pool: &DbPool, form: &LocalSiteRateLimitUpdateForm) -> Result<(), Error> { + // avoid error "There are no changes to save. This query cannot be built" + if form.is_empty() { + return Ok(()); + } let conn = &mut get_conn(pool).await?; diesel::update(local_site_rate_limit::table) .set(form) .get_result::(conn) - .await + .await?; + Ok(()) + } +} + +impl LocalSiteRateLimitUpdateForm { + fn is_empty(&self) -> bool { + self.message.is_none() + && self.message_per_second.is_none() + && self.post.is_none() + && self.post_per_second.is_none() + && self.register.is_none() + && self.register_per_second.is_none() + && self.image.is_none() + && self.image_per_second.is_none() + && self.comment.is_none() + && self.comment_per_second.is_none() + && self.search.is_none() + && self.search_per_second.is_none() + && self.updated.is_none() } } diff --git a/src/lib.rs b/src/lib.rs index a9e390a21..3c6b283ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,13 +6,165 @@ pub mod scheduled_tasks; #[cfg(feature = "console")] pub mod telemetry; -use lemmy_utils::error::LemmyError; +use crate::{code_migrations::run_advanced_migrations, root_span_builder::QuieterRootSpanBuilder}; +use actix_web::{middleware, web::Data, App, HttpServer, Result}; +use doku::json::{AutoComments, CommentsStyle, Formatting, ObjectsStyle}; +use lemmy_api_common::{ + context::LemmyContext, + lemmy_db_views::structs::SiteView, + request::build_user_agent, + utils::{ + check_private_instance_and_federation_enabled, + local_site_rate_limit_to_rate_limit_config, + }, + websocket::chat_server::ChatServer, +}; +use lemmy_db_schema::{ + source::secret::Secret, + utils::{build_db_pool, get_database_url, run_migrations}, +}; +use lemmy_routes::{feeds, images, nodeinfo, webfinger}; +use lemmy_utils::{ + error::LemmyError, + rate_limit::RateLimitCell, + settings::{structs::Settings, SETTINGS}, +}; +use reqwest::Client; +use reqwest_middleware::ClientBuilder; +use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; +use reqwest_tracing::TracingMiddleware; +use std::{env, sync::Arc, thread, time::Duration}; use tracing::subscriber::set_global_default; +use tracing_actix_web::TracingLogger; use tracing_error::ErrorLayer; use tracing_log::LogTracer; use tracing_subscriber::{filter::Targets, layer::SubscriberExt, Layer, Registry}; use url::Url; +/// Max timeout for http requests +const REQWEST_TIMEOUT: Duration = Duration::from_secs(10); + +/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy +pub async fn start_lemmy_server() -> Result<(), LemmyError> { + let args: Vec = env::args().collect(); + if args.len() == 2 && args[1] == "--print-config-docs" { + let fmt = Formatting { + auto_comments: AutoComments::none(), + comments_style: CommentsStyle { + separator: "#".to_owned(), + }, + objects_style: ObjectsStyle { + surround_keys_with_quotes: false, + use_comma_as_separator: false, + }, + ..Default::default() + }; + println!("{}", doku::to_json_fmt_val(&fmt, &Settings::default())); + return Ok(()); + } + + let settings = SETTINGS.to_owned(); + + // Set up the bb8 connection pool + let db_url = get_database_url(Some(&settings)); + run_migrations(&db_url); + + // Run the migrations from code + let pool = build_db_pool(&settings).await?; + run_advanced_migrations(&pool, &settings).await?; + + // Schedules various cleanup tasks for the DB + thread::spawn(move || { + scheduled_tasks::setup(db_url).expect("Couldn't set up scheduled_tasks"); + }); + + // Initialize the secrets + let secret = Secret::init(&pool) + .await + .expect("Couldn't initialize secrets."); + + // Make sure the local site is set up. + let site_view = SiteView::read_local(&pool) + .await + .expect("local site not set up"); + let local_site = site_view.local_site; + let federation_enabled = local_site.federation_enabled; + + if federation_enabled { + println!("federation enabled, host is {}", &settings.hostname); + } + + check_private_instance_and_federation_enabled(&local_site)?; + + // Set up the rate limiter + let rate_limit_config = + local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); + let rate_limit_cell = RateLimitCell::new(rate_limit_config).await; + + println!( + "Starting http server at {}:{}", + settings.bind, settings.port + ); + + let reqwest_client = Client::builder() + .user_agent(build_user_agent(&settings)) + .timeout(REQWEST_TIMEOUT) + .build()?; + + let retry_policy = ExponentialBackoff { + max_n_retries: 3, + max_retry_interval: REQWEST_TIMEOUT, + min_retry_interval: Duration::from_millis(100), + backoff_exponent: 2, + }; + + let client = ClientBuilder::new(reqwest_client.clone()) + .with(TracingMiddleware::default()) + .with(RetryTransientMiddleware::new_with_policy(retry_policy)) + .build(); + + // Pictrs cannot use the retry middleware + let pictrs_client = ClientBuilder::new(reqwest_client.clone()) + .with(TracingMiddleware::default()) + .build(); + + let chat_server = Arc::new(ChatServer::startup()); + + // Create Http server with websocket support + let settings_bind = settings.clone(); + HttpServer::new(move || { + let context = LemmyContext::create( + pool.clone(), + chat_server.clone(), + client.clone(), + settings.clone(), + secret.clone(), + rate_limit_cell.clone(), + ); + App::new() + .wrap(middleware::Logger::default()) + .wrap(TracingLogger::::new()) + .app_data(Data::new(context)) + .app_data(Data::new(rate_limit_cell.clone())) + // The routes + .configure(|cfg| api_routes_http::config(cfg, rate_limit_cell)) + .configure(|cfg| { + if federation_enabled { + lemmy_apub::http::routes::config(cfg); + webfinger::config(cfg); + } + }) + .configure(feeds::config) + .configure(|cfg| images::config(cfg, pictrs_client.clone(), rate_limit_cell)) + .configure(nodeinfo::config) + }) + .bind((settings_bind.bind, settings_bind.port))? + .run() + .await?; + + Ok(()) +} + pub fn init_logging(opentelemetry_url: &Option) -> Result<(), LemmyError> { LogTracer::init()?; diff --git a/src/main.rs b/src/main.rs index 70f6d0348..68294989c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,167 +1,8 @@ -#[macro_use] -extern crate diesel_migrations; - -use actix_web::{middleware, web::Data, App, HttpServer, Result}; -use diesel_migrations::EmbeddedMigrations; -use doku::json::{AutoComments, CommentsStyle, Formatting, ObjectsStyle}; -use lemmy_api_common::{ - context::LemmyContext, - lemmy_db_views::structs::SiteView, - request::build_user_agent, - utils::{ - check_private_instance_and_federation_enabled, - local_site_rate_limit_to_rate_limit_config, - }, - websocket::chat_server::ChatServer, -}; -use lemmy_db_schema::{ - source::secret::Secret, - utils::{build_db_pool, get_database_url, run_migrations}, -}; -use lemmy_routes::{feeds, images, nodeinfo, webfinger}; -use lemmy_server::{ - api_routes_http, - code_migrations::run_advanced_migrations, - init_logging, - root_span_builder::QuieterRootSpanBuilder, - scheduled_tasks, -}; -use lemmy_utils::{ - error::LemmyError, - rate_limit::RateLimitCell, - settings::{structs::Settings, SETTINGS}, -}; -use reqwest::Client; -use reqwest_middleware::ClientBuilder; -use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; -use reqwest_tracing::TracingMiddleware; -use std::{env, sync::Arc, thread, time::Duration}; -use tracing_actix_web::TracingLogger; - -pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); - -/// Max timeout for http requests -pub const REQWEST_TIMEOUT: Duration = Duration::from_secs(10); +use lemmy_server::{init_logging, start_lemmy_server}; +use lemmy_utils::{error::LemmyError, settings::SETTINGS}; #[actix_web::main] -async fn main() -> Result<(), LemmyError> { - let args: Vec = env::args().collect(); - if args.len() == 2 && args[1] == "--print-config-docs" { - let fmt = Formatting { - auto_comments: AutoComments::none(), - comments_style: CommentsStyle { - separator: "#".to_owned(), - }, - objects_style: ObjectsStyle { - surround_keys_with_quotes: false, - use_comma_as_separator: false, - }, - ..Default::default() - }; - println!("{}", doku::to_json_fmt_val(&fmt, &Settings::default())); - return Ok(()); - } - - let settings = SETTINGS.to_owned(); - - init_logging(&settings.opentelemetry_url)?; - - // Set up the bb8 connection pool - let db_url = get_database_url(Some(&settings)); - run_migrations(&db_url); - - // Run the migrations from code - let pool = build_db_pool(&settings).await?; - run_advanced_migrations(&pool, &settings).await?; - - // Schedules various cleanup tasks for the DB - thread::spawn(move || { - scheduled_tasks::setup(db_url).expect("Couldn't set up scheduled_tasks"); - }); - - // Initialize the secrets - let secret = Secret::init(&pool) - .await - .expect("Couldn't initialize secrets."); - - // Make sure the local site is set up. - let site_view = SiteView::read_local(&pool) - .await - .expect("local site not set up"); - let local_site = site_view.local_site; - let federation_enabled = local_site.federation_enabled; - - if federation_enabled { - println!("federation enabled, host is {}", &settings.hostname); - } - - check_private_instance_and_federation_enabled(&local_site)?; - - // Set up the rate limiter - let rate_limit_config = - local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); - let rate_limit_cell = RateLimitCell::new(rate_limit_config).await; - - println!( - "Starting http server at {}:{}", - settings.bind, settings.port - ); - - let reqwest_client = Client::builder() - .user_agent(build_user_agent(&settings)) - .timeout(REQWEST_TIMEOUT) - .build()?; - - let retry_policy = ExponentialBackoff { - max_n_retries: 3, - max_retry_interval: REQWEST_TIMEOUT, - min_retry_interval: Duration::from_millis(100), - backoff_exponent: 2, - }; - - let client = ClientBuilder::new(reqwest_client.clone()) - .with(TracingMiddleware::default()) - .with(RetryTransientMiddleware::new_with_policy(retry_policy)) - .build(); - - // Pictrs cannot use the retry middleware - let pictrs_client = ClientBuilder::new(reqwest_client.clone()) - .with(TracingMiddleware::default()) - .build(); - - let chat_server = Arc::new(ChatServer::startup()); - - // Create Http server with websocket support - let settings_bind = settings.clone(); - HttpServer::new(move || { - let context = LemmyContext::create( - pool.clone(), - chat_server.clone(), - client.clone(), - settings.clone(), - secret.clone(), - rate_limit_cell.clone(), - ); - App::new() - .wrap(middleware::Logger::default()) - .wrap(TracingLogger::::new()) - .app_data(Data::new(context)) - .app_data(Data::new(rate_limit_cell.clone())) - // The routes - .configure(|cfg| api_routes_http::config(cfg, rate_limit_cell)) - .configure(|cfg| { - if federation_enabled { - lemmy_apub::http::routes::config(cfg); - webfinger::config(cfg); - } - }) - .configure(feeds::config) - .configure(|cfg| images::config(cfg, pictrs_client.clone(), rate_limit_cell)) - .configure(nodeinfo::config) - }) - .bind((settings_bind.bind, settings_bind.port))? - .run() - .await?; - - Ok(()) +pub async fn main() -> Result<(), LemmyError> { + init_logging(&SETTINGS.opentelemetry_url)?; + start_lemmy_server().await }