-
Notifications
You must be signed in to change notification settings - Fork 1
/
startup.rs
131 lines (114 loc) · 4.57 KB
/
startup.rs
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
use crate::authentication::reject_anonymous_users;
use crate::configuration::{DatabaseSettings, Settings};
use crate::email_client::EmailClient;
use crate::routes::{
admin_dashboard, change_password, change_password_form, confirm, health_check, home, log_out,
login, login_form, publish_newsletter, publish_newsletter_form, subscribe,
};
use actix_web_lab::middleware::from_fn;
use secrecy::{ExposeSecret, Secret};
use sqlx::postgres::PgPoolOptions;
use actix_session::storage::RedisSessionStore;
use actix_session::SessionMiddleware;
use actix_web::cookie::Key;
use actix_web::dev::Server;
use actix_web::web::Data;
use actix_web::{web, App, HttpServer};
use actix_web_flash_messages::storage::CookieMessageStore;
use actix_web_flash_messages::FlashMessagesFramework;
use sqlx::PgPool;
use std::net::TcpListener;
use tracing_actix_web::TracingLogger;
pub struct Application {
port: u16,
server: Server,
}
pub struct ApplicationBaseUrl(pub String);
#[derive(Clone)]
pub struct HmacSecret(pub Secret<String>);
pub async fn run(
listener: TcpListener,
db_pool: PgPool,
email_client: EmailClient,
base_url: String,
hmac_secret: Secret<String>,
redis_uri: Secret<String>,
) -> Result<Server, anyhow::Error> {
// We have to wrap connection in an Arc<T> so that every new worker
// points to the same connection stored on the Heap
let db_pool = web::Data::new(db_pool);
let email_client = Data::new(email_client);
let base_url = Data::new(ApplicationBaseUrl(base_url));
let secret_key = Key::from(hmac_secret.expose_secret().as_bytes());
let message_store = CookieMessageStore::builder(secret_key.clone()).build();
let message_framework = FlashMessagesFramework::builder(message_store).build();
let redis_store = RedisSessionStore::new(redis_uri.expose_secret()).await?;
let server = HttpServer::new(move || {
App::new()
.wrap(message_framework.clone())
.wrap(SessionMiddleware::new(
redis_store.clone(),
secret_key.clone(),
))
.wrap(TracingLogger::default())
.route("/", web::get().to(home))
.route("/health_check", web::get().to(health_check))
.route("/subscriptions", web::post().to(subscribe))
.route("/subscriptions/confirm", web::get().to(confirm))
.route("/newsletters", web::post().to(publish_newsletter))
.route("/login", web::get().to(login_form))
.route("/login", web::post().to(login))
.service(
web::scope("/admin")
.wrap(from_fn(reject_anonymous_users))
.route("/password", web::get().to(change_password_form))
.route("/password", web::post().to(change_password))
.route("/newsletters", web::get().to(publish_newsletter_form))
.route("/newsletters", web::post().to(publish_newsletter))
.route("/dashboard", web::get().to(admin_dashboard))
.route("/logout", web::post().to(log_out)),
)
.app_data(db_pool.clone())
.app_data(email_client.clone())
.app_data(base_url.clone())
.app_data(Data::new(HmacSecret(hmac_secret.clone())))
})
.listen(listener)?
.run();
Ok(server)
}
impl Application {
pub async fn build(configuration: Settings) -> Result<Self, anyhow::Error> {
let connection_pool = get_connection_pool(&configuration.database);
let email_client = configuration.email_client.client();
let address = format!(
"{}:{}",
configuration.application.host, configuration.application.port
);
let listener = TcpListener::bind(address)?;
let port = listener.local_addr().unwrap().port();
let server = run(
listener,
connection_pool,
email_client,
configuration.application.base_url,
configuration.application.hmac_secret,
configuration.redis_uri,
)
.await?;
Ok(Self { port, server })
}
pub fn port(&self) -> u16 {
self.port
}
// A more expressive name that makes it clear that
// this function only returns when the application is stopped.
pub async fn run_until_stopped(self) -> Result<(), std::io::Error> {
self.server.await
}
}
pub fn get_connection_pool(configuration: &DatabaseSettings) -> PgPool {
PgPoolOptions::new()
.acquire_timeout(std::time::Duration::from_secs(2))
.connect_lazy_with(configuration.with_db())
}