diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/get.rs | 99 | ||||
-rw-r--r-- | src/main.rs | 102 | ||||
-rw-r--r-- | src/post.rs | 3 |
3 files changed, 204 insertions, 0 deletions
diff --git a/src/get.rs b/src/get.rs new file mode 100644 index 0000000..9507125 --- /dev/null +++ b/src/get.rs @@ -0,0 +1,99 @@ +use std::net::SocketAddr; + +use axum::extract::{ConnectInfo, Path}; +use axum::http::HeaderMap; +use axum::http::StatusCode; +use axum::response::{Html, IntoResponse, Redirect}; +use axum::Extension; + +use info_utils::prelude::*; + +use crate::ServerState; +use crate::UrlRow; + +pub async fn get_index() -> Html<&'static str> { + Html("hello, world!") +} + +pub async fn get_id( + 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(); + } + + let item = sqlx::query_as!(UrlRow, "SELECT * FROM chela.urls WHERE id = $1", use_id) + .fetch_one(&state.db_pool) + .await; + if let Ok(it) = item { + if url::Url::parse(&it.url).is_ok() { + if show_request { + return Html(format!( + "<pre>{}/{} -> <a href={}>{}</a></pre>", + state.host, it.id, it.url, it.url + )) + .into_response(); + } else { + log!("Redirecting {} -> {}", it.id, it.url); + save_analytics(headers, it.clone(), addr, state).await; + return Redirect::temporary(it.url.as_str()).into_response(); + } + } + } + + return (StatusCode::NOT_FOUND, Html("<pre>404</pre>")).into_response(); +} + +pub async fn save_analytics( + headers: HeaderMap, + item: UrlRow, + addr: SocketAddr, + state: ServerState, +) { + 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, + }; + + let res = sqlx::query!( + " +INSERT INTO chela.tracking (id,ip,referrer,user_agent) +VALUES ($1,$2,$3,$4) + ", + id, + ip, + referer, + user_agent + ) + .execute(&state.db_pool) + .await; + + if res.is_ok() { + log!("Saved analytics for '{id}' from {ip}"); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..51dd969 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,102 @@ +use std::net::SocketAddr; + +use axum::routing::{get, post}; +use axum::Router; + +use sqlx::postgres::PgPoolOptions; +use sqlx::{Pool, Postgres}; + +use info_utils::prelude::*; + +pub mod get; +pub mod post; + +const LISTEN_ADDRESS: &'static str = "0.0.0.0:3000"; + +#[derive(Clone)] +pub struct ServerState { + pub db_pool: Pool<Postgres>, + pub host: String, +} + +#[derive(Debug, Clone)] +pub struct UrlRow { + pub index: i32, + pub id: String, + pub url: String, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + color_eyre::install()?; + + let db_pool = init_db().await?; + + let server_state = ServerState { + db_pool, + host: "trkt.in".to_string(), + }; + + let router = init_routes(server_state)?; + let listener = tokio::net::TcpListener::bind(LISTEN_ADDRESS).await?; + log!("Listening at {}", LISTEN_ADDRESS); + axum::serve( + listener, + router.into_make_service_with_connect_info::<SocketAddr>(), + ) + .await?; + Ok(()) +} + +async fn init_db() -> eyre::Result<Pool<Postgres>> { + let db_pool = PgPoolOptions::new() + .max_connections(15) + .connect(std::env::var("DATABASE_URL")?.as_str()) + .await?; + log!("Successfully connected to database"); + + sqlx::query!("CREATE SCHEMA IF NOT EXISTS chela") + .execute(&db_pool) + .await?; + log!("Created schema chela"); + + sqlx::query!( + " +CREATE TABLE IF NOT EXISTS chela.urls ( + index SERIAL PRIMARY KEY, + id TEXT NOT NULL UNIQUE, + url TEXT NOT NULL +) + ", + ) + .execute(&db_pool) + .await?; + log!("Created table chela.urls"); + + sqlx::query!( + " +CREATE TABLE IF NOT EXISTS chela.tracking ( + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + id TEXT NOT NULL, + ip TEXT NOT NULL, + referrer TEXT, + user_agent TEXT +) + ", + ) + .execute(&db_pool) + .await?; + log!("Created table chela.tracking"); + + Ok(db_pool) +} + +fn init_routes(state: ServerState) -> eyre::Result<Router> { + let router = Router::new() + .route("/", get(get::get_index)) + .route("/:id", get(get::get_id)) + .route("/", post(post::create_link)) + .layer(axum::Extension(state)); + + Ok(router) +} diff --git a/src/post.rs b/src/post.rs new file mode 100644 index 0000000..d3d1257 --- /dev/null +++ b/src/post.rs @@ -0,0 +1,3 @@ +pub async fn create_link() { + +} |