aboutsummaryrefslogtreecommitdiff
path: root/src/service.rs
blob: 4477ee83c6769400292a362b52e16ac0790f38f0 (plain)
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
// extern
use anyhow::{Context, Result};
use hyper::header::CONTENT_TYPE;
use hyper::{Body, Method, Request, Response, StatusCode};
use log::{error, info, trace};
use multer::Multipart;
use nanoid::nanoid;
use rusqlite::{params, Connection};
use url::form_urlencoded;

// std
use std::collections::HashMap;

fn respond_with_shortlink<S: AsRef<str>>(shortlink: S) -> Response<Body> {
    info!("Successfully generated shortlink");
    Response::builder()
        .status(StatusCode::OK)
        .header("content-type", "text/html")
        .body(Body::from(shortlink.as_ref().to_string()))
        .unwrap()
}

fn respond_with_status(s: StatusCode) -> Response<Body> {
    Response::builder().status(s).body(Body::empty()).unwrap()
}

fn shorten<S: AsRef<str>>(url: S, conn: &mut Connection) -> Result<String> {
    let mut stmt = conn.prepare("select * from urls where link = ?1")?;
    let mut rows = stmt.query(params![url.as_ref().to_string()])?;
    if let Some(row) = rows.next()? {
        return Ok(row.get(1)?);
    } else {
        let new_id = nanoid!(4);
        conn.execute(
            "insert into urls (link, shortlink) values (?1, ?2)",
            params![url.as_ref().to_string(), new_id],
        )?;
        return Ok(new_id);
    }
}

fn get_link<S: AsRef<str>>(url: S, conn: &Connection) -> Result<Option<String>> {
    let url = url.as_ref();
    let mut stmt = conn.prepare("select * from urls where shortlink = ?1")?;
    let mut rows = stmt.query(params![url.to_string()])?;
    if let Some(row) = rows.next()? {
        return Ok(row.get(0)?);
    } else {
        return Ok(None);
    }
}

async fn process_multipart(
    body: Body,
    boundary: String,
    conn: &mut Connection,
) -> Result<Response<Body>> {
    let mut m = Multipart::new(body, boundary);
    if let Some(field) = m.next_field().await? {
        if field.name() == Some("shorten") {
            trace!("Recieved valid multipart request");
            let content = field
                .text()
                .await
                .with_context(|| format!("Expected field name"))?;

            let shortlink = shorten(content, conn)?;
            return Ok(respond_with_shortlink(shortlink));
        }
    }
    trace!("Unprocessable multipart request!");
    Ok(respond_with_status(StatusCode::UNPROCESSABLE_ENTITY))
}

async fn process_form(req: Request<Body>, conn: &mut Connection) -> Result<Response<Body>> {
    let b = hyper::body::to_bytes(req)
        .await
        .with_context(|| format!("Failed to stream request body!"))?;

    let params = form_urlencoded::parse(b.as_ref())
        .into_owned()
        .collect::<HashMap<String, String>>();

    if let Some(n) = params.get("shorten") {
        trace!("POST: {}", &n);
        let s = shorten(n, conn)?;
        return Ok(respond_with_shortlink(s));
    } else {
        error!("Invalid form");
        return Ok(respond_with_status(StatusCode::UNPROCESSABLE_ENTITY));
    }
}

pub async fn shortner_service(req: Request<Body>, mut conn: Connection) -> Result<Response<Body>> {
    match req.method() {
        &Method::POST => {
            let boundary = req
                .headers()
                .get(CONTENT_TYPE)
                .and_then(|ct| ct.to_str().ok())
                .and_then(|ct| multer::parse_boundary(ct).ok());

            if boundary.is_none() {
                return process_form(req, &mut conn).await;
            }

            trace!("Attempting to parse multipart request");
            return process_multipart(req.into_body(), boundary.unwrap(), &mut conn).await;
        }
        &Method::GET => {
            trace!("GET: {}", req.uri());
            let shortlink = req.uri().path().to_string();
            let link = get_link(&shortlink[1..], &conn);
            if let Some(l) = link.unwrap() {
                trace!("Found in database, redirecting ...");
                Ok(Response::builder()
                    .header("Location", &l)
                    .header("content-type", "text/html")
                    .status(StatusCode::MOVED_PERMANENTLY)
                    .body(Body::from(format!(
                        "You will be redirected to: {}. If not, click the link.",
                        &l
                    )))?)
            } else {
                error!("Resource not found");
                Ok(respond_with_status(StatusCode::NOT_FOUND))
            }
        }
        _ => Ok(respond_with_status(StatusCode::NOT_FOUND)),
    }
}