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_files::Files; 3use actix_web::{middleware::DefaultHeaders, web, App, HttpServer}; 4use anyhow::Result; 5use simplelink::{handlers, AppState}; 6use sqlx::postgres::PgPoolOptions; 7use tracing::info; 8 9async fn index() -> Result<actix_files::NamedFile, actix_web::Error> { 10 Ok(actix_files::NamedFile::open("./static/index.html")?) 11} 12 13#[actix_web::main] 14async fn main() -> Result<()> { 15 // Load environment variables from .env file 16 dotenv::dotenv().ok(); 17 18 // Initialize logging 19 tracing_subscriber::fmt::init(); 20 21 // Database connection string from environment 22 let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 23 24 // Create database connection pool 25 let pool = PgPoolOptions::new() 26 .max_connections(5) 27 .acquire_timeout(std::time::Duration::from_secs(3)) 28 .connect(&database_url) 29 .await?; 30 31 // Run database migrations 32 sqlx::migrate!("./migrations").run(&pool).await?; 33 34 let _state = AppState { db: pool }; 35 36 let host = std::env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 37 let port = std::env::var("SERVER_PORT").unwrap_or_else(|_| "3000".to_string()); 38 info!("Starting server at http://{}:{}", host, port); 39 40 // Start HTTP server 41 HttpServer::new(move || { 42 let cors = Cors::default() 43 .allow_any_origin() 44 .allow_any_method() 45 .allow_any_header() 46 .max_age(3600); 47 48 App::new() 49 .wrap(cors) 50 // Add headers to help with caching static assets 51 .wrap(DefaultHeaders::new().add(("Cache-Control", "max-age=31536000"))) 52 // API routes 53 .service( 54 web::scope("/api") 55 .route("/shorten", web::post().to(handlers::create_short_url)) 56 .route("/links", web::get().to(handlers::get_all_links)) 57 .route("/links/{id}", web::delete().to(handlers::delete_link)) 58 .route( 59 "/links/{id}/clicks", 60 web::get().to(handlers::get_link_clicks), 61 ) 62 .route( 63 "/links/{id}/sources", 64 web::get().to(handlers::get_link_sources), 65 ) 66 .route("/auth/register", web::post().to(handlers::register)) 67 .route("/auth/login", web::post().to(handlers::login)) 68 .route("/health", web::get().to(handlers::health_check)), 69 ) 70 // Serve static files 71 .service(Files::new("/assets", "./static/assets")) 72 // Handle SPA routes - must be last 73 .default_service(web::get().to(index)) 74 }) 75 .bind(format!("{}:{}", host, port))? 76 .run() 77 .await?; 78 79 Ok(()) 80}