1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! automated scanning tool that integrates widely-used media management services with various media servers for seamless media organization
//!
//! ## Quick docs
//!
//! - **[Triggers](service::triggers)**: Create triggers that will be executed by a service when a certain event occurs
//! - **[Targets](service::targets)**: Create targets that will be scanned by a service
//! - **[Webhooks](service::webhooks)**: Send webhooks to services to notify them of an event
//! - **[Settings](settings)**: Settings handler
//! - **[Database](db::conn::AnyConnection)**: Database handler
//!
//! ## About
#![doc = include_str!("../README.md")]

use actix_web::{middleware::Logger, web::Data, App, HttpServer};
use actix_web_httpauth::extractors::basic;
use anyhow::Context;
use clap::Parser;
use db::conn::{get_conn, get_pool, AnyConnection};
use routes::list::list;
use routes::login::login;
use routes::stats::stats;
use routes::status::status;
use routes::triggers::trigger_post;
use routes::{index::hello, triggers::trigger_get};
use service::manager::PulseManager;
use settings::Settings;
use std::sync::Arc;
use tracing::info;
use tracing_appender::non_blocking::WorkerGuard;
use utils::cli::Args;
use utils::logs::setup_logs;

#[doc(hidden)]
mod tests;

/// Web server routes
pub mod routes;

/// Settings configuration
///
/// Used to configure the service.
///
/// Can be defined in 2 ways:
/// - Config file
///   - `config.{json,toml,yaml,json5,ron,ini}` in the current directory
/// - Environment variables
///   - `AUTOPULSE__{SECTION}__{KEY}` (e.g. `AUTOPULSE__APP__DATABASE_URL`)
///
/// See [Settings] for all options
pub mod settings;

/// Database handler
pub mod db;

/// Core of autopulse
///
/// Includes:
/// - `Triggers`
/// - `Webhooks`
/// - `Targets`
pub mod service;

/// Internal utility functions
pub mod utils;

#[doc(hidden)]
#[tokio::main]
async fn run(settings: Settings, _guard: Option<WorkerGuard>) -> anyhow::Result<()> {
    let hostname = settings.app.hostname.clone();
    let port = settings.app.port;
    let database_url = settings.app.database_url.clone();

    AnyConnection::pre_init(&database_url)?;

    let pool = get_pool(database_url)?;
    let conn = &mut get_conn(&pool);

    conn.migrate()?;
    conn.init()?;

    let manager = PulseManager::new(settings, pool.clone());
    let manager = Arc::new(manager);

    let manager_task = manager.start();
    let webhook_task = manager.start_webhooks();
    let notify_task = manager.start_notify();

    info!("🚀 Listening on {}:{}", hostname, port);

    HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            .service(hello)
            .service(trigger_get)
            .service(trigger_post)
            .service(status)
            .service(stats)
            .service(login)
            .service(list)
            .app_data(basic::Config::default().realm("Restricted area"))
            .app_data(Data::new(manager.clone()))
    })
    .bind((hostname, port))?
    .run()
    .await
    .with_context(|| "Failed to start server")?;

    info!("Shutting down...");

    manager_task.abort();
    webhook_task.abort();
    notify_task.abort();

    Ok(())
}

#[doc(hidden)]
pub fn main() -> anyhow::Result<()> {
    let args = Args::parse();

    let settings = Settings::get_settings(args.config).with_context(|| "Failed to get settings")?;

    let guard = setup_logs(
        settings.app.log_level.clone(),
        settings.opts.log_file.clone(),
    )?;

    info!("💫 autopulse starting up...");

    run(settings, guard)
}