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