A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.
at master 3.9 kB view raw
1use anyhow::Result; 2use futures::future::BoxFuture; 3use serde::{Deserialize, Serialize}; 4use sqlx::postgres::PgRow; 5use sqlx::sqlite::SqliteRow; 6use sqlx::FromRow; 7use sqlx::Pool; 8use sqlx::Postgres; 9use sqlx::Sqlite; 10use sqlx::Transaction; 11use std::time::{SystemTime, UNIX_EPOCH}; 12 13#[derive(Clone)] 14pub enum DatabasePool { 15 Postgres(Pool<Postgres>), 16 Sqlite(Pool<Sqlite>), 17} 18 19impl DatabasePool { 20 pub async fn begin(&self) -> Result<Box<dyn std::any::Any + Send>> { 21 match self { 22 DatabasePool::Postgres(pool) => Ok(Box::new(pool.begin().await?)), 23 DatabasePool::Sqlite(pool) => Ok(Box::new(pool.begin().await?)), 24 } 25 } 26 27 pub async fn fetch_optional<T>(&self, pg_query: &str, sqlite_query: &str) -> Result<Option<T>> 28 where 29 T: for<'r> FromRow<'r, PgRow> + for<'r> FromRow<'r, SqliteRow> + Send + Sync + Unpin, 30 { 31 match self { 32 DatabasePool::Postgres(pool) => { 33 Ok(sqlx::query_as(pg_query).fetch_optional(pool).await?) 34 } 35 DatabasePool::Sqlite(pool) => { 36 Ok(sqlx::query_as(sqlite_query).fetch_optional(pool).await?) 37 } 38 } 39 } 40 41 pub async fn execute(&self, pg_query: &str, sqlite_query: &str) -> Result<()> { 42 match self { 43 DatabasePool::Postgres(pool) => { 44 sqlx::query(pg_query).execute(pool).await?; 45 Ok(()) 46 } 47 DatabasePool::Sqlite(pool) => { 48 sqlx::query(sqlite_query).execute(pool).await?; 49 Ok(()) 50 } 51 } 52 } 53 54 pub async fn transaction<'a, F, R>(&'a self, f: F) -> Result<R> 55 where 56 F: for<'c> Fn(&'c mut Transaction<'_, Postgres>) -> BoxFuture<'c, Result<R>> 57 + for<'c> Fn(&'c mut Transaction<'_, Sqlite>) -> BoxFuture<'c, Result<R>> 58 + Copy, 59 R: Send + 'static, 60 { 61 match self { 62 DatabasePool::Postgres(pool) => { 63 let mut tx = pool.begin().await?; 64 let result = f(&mut tx).await?; 65 tx.commit().await?; 66 Ok(result) 67 } 68 DatabasePool::Sqlite(pool) => { 69 let mut tx = pool.begin().await?; 70 let result = f(&mut tx).await?; 71 tx.commit().await?; 72 Ok(result) 73 } 74 } 75 } 76} 77 78#[derive(Debug, Serialize, Deserialize)] 79pub struct Claims { 80 pub sub: i32, // user id 81 pub exp: usize, 82} 83 84impl Claims { 85 pub fn new(user_id: i32) -> Self { 86 let exp = SystemTime::now() 87 .duration_since(UNIX_EPOCH) 88 .unwrap() 89 .as_secs() as usize 90 + 14 * 24 * 60 * 60; // 2 weeks from now 91 92 Self { sub: user_id, exp } 93 } 94} 95 96#[derive(Deserialize)] 97pub struct CreateLink { 98 pub url: String, 99 pub source: Option<String>, 100 pub custom_code: Option<String>, 101} 102 103#[derive(Serialize, FromRow)] 104pub struct Link { 105 pub id: i32, 106 pub user_id: Option<i32>, 107 pub original_url: String, 108 pub short_code: String, 109 pub created_at: chrono::DateTime<chrono::Utc>, 110 pub clicks: i64, 111} 112 113#[derive(Deserialize)] 114pub struct LoginRequest { 115 pub email: String, 116 pub password: String, 117} 118 119#[derive(Deserialize)] 120pub struct RegisterRequest { 121 pub email: String, 122 pub password: String, 123 pub admin_token: Option<String>, 124} 125 126#[derive(Serialize)] 127pub struct AuthResponse { 128 pub token: String, 129 pub user: UserResponse, 130} 131 132#[derive(Serialize)] 133pub struct UserResponse { 134 pub id: i32, 135 pub email: String, 136} 137 138#[derive(FromRow)] 139pub struct User { 140 pub id: i32, 141 pub email: String, 142 pub password_hash: String, 143} 144 145#[derive(sqlx::FromRow, Serialize)] 146pub struct ClickStats { 147 pub date: String, 148 pub clicks: i64, 149} 150 151#[derive(sqlx::FromRow, Serialize)] 152pub struct SourceStats { 153 pub date: String, 154 pub source: String, 155 pub count: i64, 156}