chela/src/get.rs

159 lines
4.3 KiB
Rust
Raw Normal View History

2024-04-06 02:41:10 +00:00
use std::net::SocketAddr;
use axum::extract::{ConnectInfo, Path};
use axum::http::HeaderMap;
use axum::http::StatusCode;
2024-04-06 13:19:28 +00:00
use axum::response::{Html, IntoResponse};
2024-04-06 02:41:10 +00:00
use axum::Extension;
use info_utils::prelude::*;
use crate::ServerState;
use crate::UrlRow;
2024-04-06 13:27:12 +00:00
pub async fn index(Extension(state): Extension<ServerState>) -> Html<String> {
Html(format!(
r#"
<!DOCTYPE html>
<html>
<head>
<title>{} URL Shortener</title>
</head>
</html>
<body>
<pre>{} URL shortener</pre>
<a href="/create">create</a>
</body>
"#,
state.host, state.host
))
2024-04-06 02:41:10 +00:00
}
2024-04-06 13:19:28 +00:00
/// # Panics
/// Will panic if `parse()` fails
pub async fn id(
2024-04-06 02:41:10 +00:00
headers: HeaderMap,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Extension(state): Extension<ServerState>,
Path(id): Path<String>,
) -> impl IntoResponse {
let mut show_request = false;
log!("Request for '{}' from {}", id.clone(), addr.ip());
let mut use_id = id;
if use_id.ends_with('+') {
show_request = true;
use_id.pop();
}
2024-04-06 13:19:28 +00:00
let item: Result<UrlRow, sqlx::Error> =
sqlx::query_as("SELECT * FROM chela.urls WHERE id = $1")
.bind(use_id)
.fetch_one(&state.db_pool)
.await;
2024-04-06 02:41:10 +00:00
if let Ok(it) = item {
if url::Url::parse(&it.url).is_ok() {
if show_request {
return Html(format!(
2024-04-06 13:19:28 +00:00
r#"<pre>http://{}/{} -> <a href="{}"">{}</a></pre>"#,
2024-04-06 02:41:10 +00:00
state.host, it.id, it.url, it.url
))
.into_response();
}
2024-04-06 13:19:28 +00:00
log!("Redirecting {} -> {}", it.id, it.url);
save_analytics(headers, it.clone(), addr, state).await;
let mut response_headers = HeaderMap::new();
response_headers.insert("Cache-Control", "private, max-age=90".parse().unwrap());
response_headers.insert("Location", it.url.parse().unwrap());
return (
StatusCode::MOVED_PERMANENTLY,
response_headers,
Html(format!(
r#"Redirecting to <a href="{}">{}</a>"#,
it.url, it.url
)),
)
.into_response();
2024-04-06 02:41:10 +00:00
}
2024-04-06 13:19:28 +00:00
} else if let Err(err) = item {
warn!("{}", err);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Html(format!("<pre>Internal error: {err}.</pre>")),
)
.into_response();
2024-04-06 02:41:10 +00:00
}
2024-04-06 13:19:28 +00:00
(StatusCode::NOT_FOUND, Html("<pre>Not found.</pre>")).into_response()
2024-04-06 02:41:10 +00:00
}
2024-04-06 13:19:28 +00:00
async fn save_analytics(headers: HeaderMap, item: UrlRow, addr: SocketAddr, state: ServerState) {
2024-04-06 02:41:10 +00:00
let id = item.id;
let ip = addr.ip().to_string();
let referer = match headers.get("referer") {
Some(it) => {
if let Ok(i) = it.to_str() {
Some(i)
} else {
None
}
}
None => None,
};
let user_agent = match headers.get("user-agent") {
Some(it) => {
if let Ok(i) = it.to_str() {
Some(i)
} else {
None
}
}
None => None,
};
2024-04-06 13:19:28 +00:00
let res = sqlx::query(
2024-04-06 02:41:10 +00:00
"
INSERT INTO chela.tracking (id,ip,referrer,user_agent)
VALUES ($1,$2,$3,$4)
",
)
2024-04-06 13:19:28 +00:00
.bind(id.clone())
.bind(ip.clone())
.bind(referer)
.bind(user_agent)
2024-04-06 02:41:10 +00:00
.execute(&state.db_pool)
.await;
if res.is_ok() {
log!("Saved analytics for '{id}' from {ip}");
}
}
2024-04-06 13:19:28 +00:00
pub async fn create_id(Extension(state): Extension<ServerState>) -> Html<String> {
Html(format!(
r#"
<!DOCTYPE html>
<html>
<head>
<title>{} URL Shortener</title>
</head>
<body>
<form action="/" method="post">
<label for="url">
URL to shorten:
<input type="url" name="url" required>
</label>
2024-04-06 13:27:12 +00:00
<br />
2024-04-06 13:19:28 +00:00
<label for="id">
ID (optional):
<input type="text" name="id">
</label>
2024-04-06 13:27:12 +00:00
<br />
2024-04-06 13:19:28 +00:00
<input type="submit" value="create">
</form>
</body>
</html>
"#,
state.host
))
}