Plume/src/main.rs

301 lines
9.6 KiB
Rust
Raw Normal View History

#![allow(clippy::too_many_arguments)]
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
2018-09-02 22:55:42 +02:00
2018-06-10 13:13:07 +02:00
extern crate activitypub;
extern crate askama_escape;
2018-09-01 22:08:26 +02:00
extern crate atom_syndication;
extern crate chrono;
extern crate colored;
extern crate ctrlc;
extern crate diesel;
extern crate dotenv;
#[macro_use]
extern crate gettext_macros;
extern crate gettext_utils;
2018-09-02 22:55:42 +02:00
extern crate guid_create;
2018-04-24 11:21:39 +02:00
extern crate heck;
extern crate lettre;
extern crate lettre_email;
2018-09-02 22:55:42 +02:00
extern crate multipart;
extern crate num_cpus;
2018-09-25 21:45:32 +02:00
extern crate plume_api;
extern crate plume_common;
extern crate plume_models;
#[macro_use]
extern crate rocket;
extern crate rocket_contrib;
2018-06-24 18:58:57 +02:00
extern crate rocket_csrf;
2018-06-17 16:28:44 +02:00
extern crate rocket_i18n;
#[macro_use]
extern crate runtime_fmt;
extern crate scheduled_thread_pool;
extern crate serde;
#[macro_use]
2018-04-23 17:09:05 +02:00
extern crate serde_json;
2018-09-29 16:45:27 +02:00
extern crate serde_qs;
2018-06-29 14:22:43 +02:00
extern crate validator;
#[macro_use]
extern crate validator_derive;
2018-06-18 23:50:40 +02:00
extern crate webfinger;
2018-10-06 13:06:02 +02:00
use diesel::r2d2::ConnectionManager;
use plume_models::{
db_conn::{DbPool, PragmaForeignKey},
instance::Instance,
migrations::IMPORTED_MIGRATIONS,
search::{Searcher as UnmanagedSearcher, SearcherError},
Connection, Error, CONFIG,
};
2019-03-20 17:56:17 +01:00
use rocket_csrf::CsrfFairingBuilder;
use scheduled_thread_pool::ScheduledThreadPool;
use std::process::exit;
use std::sync::{Arc, Mutex};
use std::time::Duration;
2019-04-19 14:59:03 +02:00
init_i18n!(
"plume", ar, bg, ca, cs, de, en, eo, es, fr, gl, hi, hr, it, ja, nb, pl, pt, ro, ru, sr, sk, sv
);
2018-09-19 16:49:34 +02:00
mod api;
mod inbox;
mod mail;
#[macro_use]
mod template_utils;
mod routes;
#[cfg(feature = "test")]
mod test_routes;
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
compile_i18n!();
2018-10-06 13:06:02 +02:00
/// Initializes a database pool.
fn init_pool() -> Option<DbPool> {
2018-10-06 13:06:02 +02:00
dotenv::dotenv().ok();
let manager = ConnectionManager::<Connection>::new(CONFIG.database_url.as_str());
let pool = DbPool::builder()
.connection_customizer(Box::new(PragmaForeignKey))
2019-03-20 17:56:17 +01:00
.build(manager)
.ok()?;
Instance::cache_local(&pool.get().unwrap());
Some(pool)
2018-10-06 13:06:02 +02:00
}
fn main() {
let dbpool = init_pool().expect("main: database pool initialization error");
if IMPORTED_MIGRATIONS
.is_pending(&dbpool.get().unwrap())
.unwrap_or(true)
{
panic!(
r#"
It appear your database migration does not run the migration required
by this version of Plume. To fix this, you can run migrations via
this command:
plm migration run
Then try to restart Plume.
"#
)
}
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
// we want a fast exit here, so
#[allow(clippy::match_wild_err_arm)]
let searcher = match UnmanagedSearcher::open(&CONFIG.search_index) {
Err(Error::Search(e)) => match e {
SearcherError::WriteLockAcquisitionError => panic!(
r#"
Your search index is locked. Plume can't start. To fix this issue
make sure no other Plume instance is started, and run:
plm search unlock
Then try to restart Plume.
"#
),
SearcherError::IndexOpeningError => panic!(
r#"
Plume was unable to open the search index. If you created the index
before, make sure to run Plume in the same directory it was created in, or
to set SEARCH_INDEX accordingly. If you did not yet create the search
index, run this command:
plm search init
Then try to restart Plume
2019-03-20 17:56:17 +01:00
"#
),
e => Err(e).unwrap(),
},
Err(_) => panic!("Unexpected error while opening search index"),
2019-03-20 17:56:17 +01:00
Ok(s) => Arc::new(s),
};
let commiter = searcher.clone();
2019-03-20 17:56:17 +01:00
workpool.execute_with_fixed_delay(
Duration::from_secs(5),
Duration::from_secs(60 * 30),
move || commiter.commit(),
);
let search_unlocker = searcher.clone();
ctrlc::set_handler(move || {
search_unlocker.commit();
search_unlocker.drop_writer();
exit(0);
2019-03-20 17:56:17 +01:00
})
.expect("Error setting Ctrl-c handler");
let mail = mail::init();
if mail.is_none() && CONFIG.rocket.as_ref().unwrap().environment.is_prod() {
println!("Warning: the email server is not configured (or not completely).");
println!("Please refer to the documentation to see how to configure it.");
}
let rocket = rocket::custom(CONFIG.rocket.clone().unwrap())
2019-03-20 17:56:17 +01:00
.mount(
"/",
routes![
routes::blogs::details,
routes::blogs::activity_details,
routes::blogs::outbox,
routes::blogs::new,
routes::blogs::new_auth,
routes::blogs::create,
routes::blogs::delete,
routes::blogs::edit,
routes::blogs::update,
2019-03-20 17:56:17 +01:00
routes::blogs::atom_feed,
routes::comments::create,
routes::comments::delete,
routes::comments::activity_pub,
routes::instance::index,
routes::instance::local,
routes::instance::feed,
routes::instance::federated,
routes::instance::admin,
routes::instance::admin_instances,
routes::instance::admin_users,
routes::instance::ban,
routes::instance::toggle_block,
routes::instance::update_settings,
routes::instance::shared_inbox,
routes::instance::interact,
2019-03-20 17:56:17 +01:00
routes::instance::nodeinfo,
routes::instance::about,
routes::instance::web_manifest,
routes::likes::create,
routes::likes::create_auth,
routes::medias::list,
routes::medias::new,
routes::medias::upload,
routes::medias::details,
routes::medias::delete,
routes::medias::set_avatar,
routes::notifications::notifications,
routes::notifications::notifications_auth,
routes::posts::details,
routes::posts::activity_details,
routes::posts::edit,
routes::posts::update,
routes::posts::new,
routes::posts::new_auth,
routes::posts::create,
routes::posts::delete,
routes::posts::remote_interact,
routes::posts::remote_interact_post,
2019-03-20 17:56:17 +01:00
routes::reshares::create,
routes::reshares::create_auth,
routes::search::search,
routes::session::new,
routes::session::create,
routes::session::delete,
routes::session::password_reset_request_form,
routes::session::password_reset_request,
routes::session::password_reset_form,
routes::session::password_reset,
routes::plume_static_files,
routes::static_files,
routes::tags::tag,
routes::user::me,
routes::user::details,
routes::user::dashboard,
routes::user::dashboard_auth,
routes::user::followers,
routes::user::followed,
routes::user::edit,
routes::user::edit_auth,
routes::user::update,
routes::user::delete,
routes::user::follow,
routes::user::follow_not_connected,
2019-03-20 17:56:17 +01:00
routes::user::follow_auth,
routes::user::activity_details,
routes::user::outbox,
routes::user::inbox,
routes::user::ap_followers,
routes::user::new,
routes::user::create,
routes::user::atom_feed,
routes::well_known::host_meta,
routes::well_known::nodeinfo,
routes::well_known::webfinger,
routes::errors::csrf_violation
],
)
.mount(
"/api/v1",
routes![
api::oauth,
api::apps::create,
api::posts::get,
api::posts::list,
api::posts::create,
api::posts::delete,
2019-03-20 17:56:17 +01:00
],
)
.register(catchers![
2018-06-18 17:59:49 +02:00
routes::errors::not_found,
routes::errors::unprocessable_entity,
2018-06-18 17:59:49 +02:00
routes::errors::server_error
])
.manage(Arc::new(Mutex::new(mail)))
.manage::<Arc<Mutex<Vec<routes::session::ResetRequest>>>>(Arc::new(Mutex::new(vec![])))
.manage(dbpool)
Big refactoring of the Inbox (#443) * Big refactoring of the Inbox We now have a type that routes an activity through the registered handlers until one of them matches. Each Actor/Activity/Object combination is represented by an implementation of AsObject These combinations are then registered on the Inbox type, which will try to deserialize the incoming activity in the requested types. Advantages: - nicer syntax: the final API is clearer and more idiomatic - more generic: only two traits (`AsActor` and `AsObject`) instead of one for each kind of activity - it is easier to see which activities we handle and which one we don't * Small fixes - Avoid panics - Don't search for AP ID infinitely - Code style issues * Fix tests * Introduce a new trait: FromId It should be implemented for any AP object. It allows to look for an object in database using its AP ID, or to dereference it if it was not present in database Also moves the inbox code to plume-models to test it (and write a basic test for each activity type we handle) * Use if let instead of match * Don't require PlumeRocket::intl for tests * Return early and remove a forgotten dbg! * Add more tests to try to understand where the issues come from * Also add a test for comment federation * Don't check creation_date is the same for blogs * Make user and blog federation more tolerant to errors/missing fields * Make clippy happy * Use the correct Accept header when dereferencing * Fix follow approval with Mastodon * Add spaces to characters that should not be in usernames And validate blog names too * Smarter dereferencing: only do it once for each actor/object * Forgot some files * Cargo fmt * Delete plume_test * Delete plume_tests * Update get_id docs + Remove useless : Sized * Appease cargo fmt * Remove dbg! + Use as_ref instead of clone when possible + Use and_then instead of map when possible * Remove .po~ * send unfollow to local instance * read cover from update activity * Make sure "cc" and "to" are never empty and fix a typo in a constant name * Cargo fmt
2019-04-17 19:31:47 +02:00
.manage(Arc::new(workpool))
.manage(searcher)
.manage(include_i18n!())
2019-03-20 17:56:17 +01:00
.attach(
CsrfFairingBuilder::new()
.set_default_target(
"/csrf-violation?target=<uri>".to_owned(),
rocket::http::Method::Post,
)
.add_exceptions(vec![
2019-03-20 17:56:17 +01:00
(
"/inbox".to_owned(),
"/inbox".to_owned(),
rocket::http::Method::Post,
),
(
"/@/<name>/inbox".to_owned(),
"/@/<name>/inbox".to_owned(),
rocket::http::Method::Post,
),
(
"/api/<path..>".to_owned(),
"/api/<path..>".to_owned(),
rocket::http::Method::Post,
),
])
2019-03-20 17:56:17 +01:00
.finalize()
.expect("main: csrf fairing creation error"),
);
#[cfg(feature = "test")]
let rocket = rocket.mount("/test", routes![test_routes::health,]);
rocket.launch();
}