Plume/src/main.rs
KITAITI Makoto 92a386277b
Switchable tokenizer (#776)
* [REFACTORING]Rename whitespace_tokenizer to tag_tokenizer for
registration

Name representing its purpose is preferred.

* Add lindera-tantivy to plume-model's dependencies

* Install lindera-tantivy

* Add SearchTokenizerConfig struct

* Add search tokenizers to config option

* Use CONFIG for tokenizers

* Use enum to hold tokenizer config instead of initializing on config phase

* Use guard instead of duplicate default values

* Use as_deref() instead of guard

* Move SearchTokenizer from plume-models to plume-models::search::tokenizer

* Rename SearchTokenizer to TokenizerKind

* Define SearchTokenierConfig::determine_tokenizer()

* Use determine_tokenizer in SearchTokenizerConfig::init()

* Pass tokenizer config to Searcher methods

* Add LowerCase filter to Lindera tokenizer

* Add test for Lindera tokenizer

* Define SEARCH_LANG env to specify tokenizers set

* Run cargo fmt

* Make Lindera tokenizer optional

* Fix typos
2020-06-17 16:57:28 +02:00

300 lines
10 KiB
Rust

#![allow(clippy::too_many_arguments)]
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
#[macro_use]
extern crate gettext_macros;
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate validator_derive;
use clap::App;
use diesel::r2d2::ConnectionManager;
use plume_models::{
db_conn::{DbPool, PragmaForeignKey},
instance::Instance,
migrations::IMPORTED_MIGRATIONS,
search::{Searcher as UnmanagedSearcher, SearcherError},
Connection, Error, CONFIG,
};
use rocket_csrf::CsrfFairingBuilder;
use scheduled_thread_pool::ScheduledThreadPool;
use std::process::exit;
use std::sync::{Arc, Mutex};
use std::time::Duration;
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
);
mod api;
mod inbox;
mod mail;
#[macro_use]
mod template_utils;
mod routes;
#[macro_use]
extern crate shrinkwraprs;
#[cfg(feature = "test")]
mod test_routes;
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
compile_i18n!();
/// Initializes a database pool.
fn init_pool() -> Option<DbPool> {
match dotenv::dotenv() {
Ok(path) => println!("Configuration read from {}", path.display()),
Err(ref e) if e.not_found() => eprintln!("no .env was found"),
e => e.map(|_| ()).unwrap(),
}
let manager = ConnectionManager::<Connection>::new(CONFIG.database_url.as_str());
let mut builder = DbPool::builder()
.connection_customizer(Box::new(PragmaForeignKey))
.min_idle(CONFIG.db_min_idle);
if let Some(max_size) = CONFIG.db_max_size {
builder = builder.max_size(max_size);
};
let pool = builder.build(manager).ok()?;
Instance::cache_local(&pool.get().unwrap());
Some(pool)
}
fn main() {
App::new("Plume")
.bin_name("plume")
.version(env!("CARGO_PKG_VERSION"))
.about("Plume backend server")
.after_help(
r#"
The plume command should be run inside the directory
containing the `.env` configuration file and `static` directory.
See https://docs.joinplu.me/installation/config
and https://docs.joinplu.me/installation/init for more info.
"#,
)
.get_matches();
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, &CONFIG.search_tokenizers) {
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
"#
),
e => Err(e).unwrap(),
},
Err(_) => panic!("Unexpected error while opening search index"),
Ok(s) => Arc::new(s),
};
let commiter = searcher.clone();
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);
})
.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())
.mount(
"/",
routes![
routes::blogs::details,
routes::blogs::activity_details,
routes::blogs::outbox,
routes::blogs::outbox_page,
routes::blogs::new,
routes::blogs::new_auth,
routes::blogs::create,
routes::blogs::delete,
routes::blogs::edit,
routes::blogs::update,
routes::blogs::atom_feed,
routes::comments::create,
routes::comments::delete,
routes::comments::activity_pub,
routes::instance::index,
routes::instance::admin,
routes::instance::admin_mod,
routes::instance::admin_instances,
routes::instance::admin_users,
routes::instance::admin_email_blocklist,
routes::instance::add_email_blocklist,
routes::instance::delete_email_blocklist,
routes::instance::edit_users,
routes::instance::toggle_block,
routes::instance::update_settings,
routes::instance::shared_inbox,
routes::instance::interact,
routes::instance::nodeinfo,
routes::instance::about,
routes::instance::privacy,
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,
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::theme_files,
routes::plume_static_files,
routes::static_files,
routes::plume_media_files,
routes::tags::tag,
routes::timelines::details,
routes::timelines::new,
routes::timelines::create,
routes::timelines::edit,
routes::timelines::update,
routes::timelines::delete,
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,
routes::user::follow_auth,
routes::user::activity_details,
routes::user::outbox,
routes::user::outbox_page,
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,
],
)
.register(catchers![
routes::errors::not_found,
routes::errors::unprocessable_entity,
routes::errors::server_error
])
.manage(Arc::new(Mutex::new(mail)))
.manage::<Arc<Mutex<Vec<routes::session::ResetRequest>>>>(Arc::new(Mutex::new(vec![])))
.manage(dbpool)
.manage(Arc::new(workpool))
.manage(searcher)
.manage(include_i18n!())
.attach(
CsrfFairingBuilder::new()
.set_default_target(
"/csrf-violation?target=<uri>".to_owned(),
rocket::http::Method::Post,
)
.add_exceptions(vec![
("/inbox".to_owned(), "/inbox".to_owned(), None),
(
"/@/<name>/inbox".to_owned(),
"/@/<name>/inbox".to_owned(),
None,
),
("/api/<path..>".to_owned(), "/api/<path..>".to_owned(), None),
])
.finalize()
.expect("main: csrf fairing creation error"),
);
#[cfg(feature = "test")]
let rocket = rocket.mount("/test", routes![test_routes::health,]);
rocket.launch();
}