1#[macro_use]
16extern crate serde_derive;
17
18use actix_web::http::StatusCode;
19use actix_web::{HttpRequest, HttpResponse, Json, Path};
20use redis::Client;
21
22use shorty::redis_facade::RedisFacade;
23use shorty::Shortener;
24
25pub struct AppState {
26 shortener: Shortener,
27 api_key_mandatory: bool,
28}
29
30impl AppState {
31 pub fn new(
32 redis_host: &str,
33 redis_port: &str,
34 id_length: usize,
35 id_generation_max_attempts: u8,
36 rate_limit_period: usize,
37 rate_limit: i64,
38 api_key_mandatory: bool,
39 ) -> AppState {
40 let redis = Client::open(format!("redis://{}:{}/", redis_host, redis_port).as_str())
41 .unwrap()
42 .get_connection()
43 .unwrap();
44
45 let alphabet = vec![
46 (b'a'..=b'z').map(char::from).collect::<Vec<_>>(),
47 (b'A'..=b'Z').map(char::from).collect::<Vec<_>>(),
48 (b'0'..=b'9').map(char::from).collect::<Vec<_>>(),
49 ]
50 .into_iter()
51 .flatten()
52 .collect::<Vec<char>>();
53
54 AppState {
55 shortener: Shortener::new(
56 id_length,
57 alphabet,
58 id_generation_max_attempts,
59 RedisFacade::new(redis),
60 rate_limit_period,
61 rate_limit,
62 ),
63 api_key_mandatory,
64 }
65 }
66}
67
68pub fn goto((req, id): (HttpRequest<AppState>, Path<String>)) -> HttpResponse {
69 let app_state: &AppState = &req.state();
70
71 match app_state.shortener.lookup(&id) {
72 Some(url) => HttpResponse::Found().header("Location", url).finish(),
73 None => HttpResponse::NotFound().finish(),
74 }
75}
76
77#[derive(Deserialize)]
78pub struct ShortenRequest {
79 api_key: Option<String>,
80 url: String,
81}
82
83#[derive(Serialize)]
84struct ErrorResponse {
85 err: String,
86}
87
88pub fn shorten((req, payload): (HttpRequest<AppState>, Json<ShortenRequest>)) -> HttpResponse {
89 let app_state: &AppState = &req.state();
90
91 if payload.api_key.is_none() && app_state.api_key_mandatory {
92 return HttpResponse::Ok()
93 .status(StatusCode::FORBIDDEN)
94 .json(ErrorResponse {
95 err: String::from("Missing API key"),
96 });
97 }
98
99 let api_key = payload.api_key.as_ref().map(String::as_str);
100
101 let host_domain = &req
102 .connection_info()
103 .host()
104 .split(":")
105 .take(1)
106 .collect::<String>();
107
108 match app_state
109 .shortener
110 .shorten(&api_key, Some(host_domain), &payload.url)
111 {
112 Ok(shorten_result) => HttpResponse::Ok().json(shorten_result),
113 Err(err) => HttpResponse::InternalServerError().json(ErrorResponse {
114 err: err.to_string(),
115 }),
116 }
117}