Implement get
This commit is contained in:
commit
fd0a930245
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
2034
Cargo.lock
generated
Normal file
2034
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "url_shortener"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.7.5"
|
||||||
|
color-eyre = "0.6.3"
|
||||||
|
eyre = "0.6.12"
|
||||||
|
info_utils = "2.2.3"
|
||||||
|
serde = "1.0.197"
|
||||||
|
sqlx = { version = "0.7.4", features = ["runtime-tokio", "postgres", "macros", "migrate", "tls-rustls"] }
|
||||||
|
tokio = { version = "1.37.0", features = ["full"] }
|
||||||
|
url = "2.5.0"
|
27
flake.lock
Normal file
27
flake.lock
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712163089,
|
||||||
|
"narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "fd281bd6b7d3e32ddfa399853946f782553163b5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
30
flake.nix
Normal file
30
flake.nix
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
description = "Molerat client implementation";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }:
|
||||||
|
let
|
||||||
|
system = "aarch64-darwin";
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in {
|
||||||
|
devShells.${system}.default =
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
rustc
|
||||||
|
rustfmt
|
||||||
|
cargo
|
||||||
|
rust-analyzer
|
||||||
|
libiconv
|
||||||
|
clippy
|
||||||
|
pkg-config
|
||||||
|
darwin.apple_sdk.frameworks.SystemConfiguration
|
||||||
|
];
|
||||||
|
shellHook = ''
|
||||||
|
exec zsh
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
99
src/get.rs
Normal file
99
src/get.rs
Normal file
@ -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}");
|
||||||
|
}
|
||||||
|
}
|
102
src/main.rs
Normal file
102
src/main.rs
Normal file
@ -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)
|
||||||
|
}
|
3
src/post.rs
Normal file
3
src/post.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub async fn create_link() {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user