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
use actix_cors::Cors;
use actix_web::dev::Server;
use actix_web::web::Data;
use actix_web::{web, App, HttpServer};
use sqlx::postgres::PgPoolOptions;
use sqlx::{ConnectOptions, PgPool};
use std::net::TcpListener;
use tracing::log::LevelFilter;
use tracing_actix_web::TracingLogger;
use crate::clients::twilio::TwilioClient;
use crate::configuration::{DatabaseSettings, Settings};
use crate::handlers::{
delete_qr_code, edit_qr_code, get_form, get_qr_code_data, health_check, list_qr_codes, login,
logout, register, store_form, store_qr_code, who_am_i,
};
use crate::jwt::JwtClient;
pub struct Application {
port: u16,
server: Server,
}
impl Application {
pub async fn build(configuration: Settings) -> Result<Self, std::io::Error> {
let connection_pool = get_connection_pool(&configuration.database)
.await
.expect("Failed to connect to Postgres.");
let jwt_client = JwtClient::new(
configuration.application.jwt_signing_key,
connection_pool.clone(),
);
let twilio_client = TwilioClient::new(
configuration.twilio.base_url,
std::time::Duration::from_secs(5),
configuration.twilio.account_sid,
configuration.twilio.auth_token,
configuration.twilio.from,
);
sqlx::migrate!("./migrations")
.run(&connection_pool)
.await
.expect("Failed to migrate the database");
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, jwt_client, twilio_client)?;
Ok(Self { port, server })
}
pub fn port(&self) -> u16 {
self.port
}
pub async fn run_until_stopped(self) -> Result<(), std::io::Error> {
self.server.await
}
}
pub async fn get_connection_pool(configuration: &DatabaseSettings) -> Result<PgPool, sqlx::Error> {
let db_connect_options = configuration
.with_db()
.log_statements(LevelFilter::Trace)
.to_owned();
PgPoolOptions::new()
.connect_timeout(std::time::Duration::from_secs(2))
.connect_with(db_connect_options)
.await
}
fn run(
listener: TcpListener,
db_pool: PgPool,
jwt_client: JwtClient,
twilio_client: TwilioClient,
) -> Result<Server, std::io::Error> {
let db_pool = Data::new(db_pool);
let jwt_client = Data::new(jwt_client);
let twilio_client = Data::new(twilio_client);
let server = HttpServer::new(move || {
let cors = Cors::permissive();
App::new()
.wrap(TracingLogger::default())
.wrap(cors)
.route("/login", web::get().to(login))
.route("/logout", web::get().to(logout))
.route("/whoami", web::get().to(who_am_i))
.route("/register", web::post().to(register))
.route("/health_check", web::get().to(health_check))
.route("/qr_code", web::get().to(get_qr_code_data))
.route("/qr_codes", web::get().to(list_qr_codes))
.route("/qr_code/store", web::get().to(store_qr_code))
.route("/qr_code/edit", web::get().to(edit_qr_code))
.route("/qr_code/delete", web::get().to(delete_qr_code))
.route("/form", web::get().to(get_form))
.route("/form/store", web::post().to(store_form))
.app_data(db_pool.clone())
.app_data(jwt_client.clone())
.app_data(twilio_client.clone())
})
.listen(listener)?
.run();
Ok(server)
}