A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.
1use actix_cors::Cors; 2use actix_web::{web, App, HttpResponse, HttpServer}; 3use anyhow::Result; 4use rust_embed::RustEmbed; 5use simplelink::check_and_generate_admin_token; 6use simplelink::{create_db_pool, run_migrations}; 7use simplelink::{handlers, AppState}; 8use tracing::info; 9 10#[derive(RustEmbed)] 11#[folder = "static/"] 12struct Asset; 13 14async fn serve_static_file(path: &str) -> HttpResponse { 15 match Asset::get(path) { 16 Some(content) => { 17 let mime = mime_guess::from_path(path).first_or_octet_stream(); 18 HttpResponse::Ok() 19 .content_type(mime.as_ref()) 20 .body(content.data.into_owned()) 21 } 22 None => HttpResponse::NotFound().body("404 Not Found"), 23 } 24} 25 26#[actix_web::main] 27async fn main() -> Result<()> { 28 // Load environment variables from .env file 29 dotenv::dotenv().ok(); 30 31 // Initialize logging 32 tracing_subscriber::fmt::init(); 33 34 // Create database connection pool 35 let pool = create_db_pool().await?; 36 run_migrations(&pool).await?; 37 38 let admin_token = check_and_generate_admin_token(&pool).await?; 39 40 let state = AppState { 41 db: pool, 42 admin_token, 43 }; 44 45 let host = std::env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 46 let port = std::env::var("SERVER_PORT").unwrap_or_else(|_| "8080".to_string()); 47 info!("Starting server at http://{}:{}", host, port); 48 49 // Start HTTP server 50 HttpServer::new(move || { 51 let cors = Cors::default() 52 .allow_any_origin() 53 .allow_any_method() 54 .allow_any_header() 55 .max_age(3600); 56 57 App::new() 58 .wrap(cors) 59 .app_data(web::Data::new(state.clone())) 60 .service( 61 web::scope("/api") 62 .route("/shorten", web::post().to(handlers::create_short_url)) 63 .route("/links", web::get().to(handlers::get_all_links)) 64 .route("/links/{id}", web::delete().to(handlers::delete_link)) 65 .route( 66 "/links/{id}/clicks", 67 web::get().to(handlers::get_link_clicks), 68 ) 69 .route( 70 "/links/{id}/sources", 71 web::get().to(handlers::get_link_sources), 72 ) 73 .route("/auth/register", web::post().to(handlers::register)) 74 .route("/auth/login", web::post().to(handlers::login)) 75 .route("/health", web::get().to(handlers::health_check)), 76 ) 77 .service(web::resource("/{short_code}").route(web::get().to(handlers::redirect_to_url))) 78 .default_service(web::route().to(|req: actix_web::HttpRequest| async move { 79 let path = req.path().trim_start_matches('/'); 80 let path = if path.is_empty() { "index.html" } else { path }; 81 serve_static_file(path).await 82 })) 83 }) 84 .workers(2) 85 .backlog(10_000) 86 .bind(format!("{}:{}", host, port))? 87 .run() 88 .await?; 89 90 Ok(()) 91}