Created
October 26, 2023 23:35
-
-
Save mayankchoubey/a81ef96db249634351df33a8b16470cc to your computer and use it in GitHub Desktop.
Rust - URL shortener service in PostgreSQL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use crate::repository::database::Database; | |
| use crate::{ | |
| types::types::ShortenUrl, types::types::UrlShortenerError, types::types::UrlShortenerRequest, | |
| types::types::UrlShortenerResponse, | |
| }; | |
| use actix_web::{delete, get, post, put, web, HttpResponse}; | |
| use chrono::{DateTime, Utc}; | |
| use nanoid::nanoid; | |
| const baseUrl: &str = "http://test.short/"; | |
| #[post("/shorten")] | |
| pub async fn shorten_url( | |
| db: web::Data<Database>, | |
| shortenRequest: web::Json<UrlShortenerRequest>, | |
| ) -> HttpResponse { | |
| if shortenRequest.srcUrl.is_empty() { | |
| return HttpResponse::BadRequest().json(UrlShortenerError { | |
| errMsg: "Parameter 'srcUrl' is missing".to_string(), | |
| }); | |
| } | |
| if shortenRequest.srcUrl.len() > 250 { | |
| return HttpResponse::BadRequest().json(UrlShortenerError { | |
| errMsg: "Parameter 'srcUrl' must not be more than 250 characters".to_string(), | |
| }); | |
| } | |
| if !(shortenRequest.srcUrl.starts_with("http://") | |
| || shortenRequest.srcUrl.starts_with("https://")) | |
| { | |
| return HttpResponse::BadRequest().json(UrlShortenerError { | |
| errMsg: "Parameter 'srcUrl' must start with http:// or https://".to_string(), | |
| }); | |
| } | |
| let urlId = nanoid!(10); | |
| let shortUrl = format!("{baseUrl}{urlId}"); | |
| let shortenedUrl = ShortenUrl { | |
| id: urlId, | |
| srcurl: shortenRequest.srcUrl.clone(), | |
| created: Utc::now().naive_utc(), | |
| lastaccessed: Utc::now().naive_utc(), | |
| }; | |
| let savedData = db.shorten(shortenedUrl); | |
| match savedData { | |
| Ok(todo) => HttpResponse::Ok().json(UrlShortenerResponse { | |
| srcUrl: shortenRequest.srcUrl.clone(), | |
| shortenedUrl: shortUrl, | |
| }), | |
| Err(err) => HttpResponse::InternalServerError().body("Failed to shorten".to_string()), | |
| } | |
| } | |
| pub fn config(cfg: &mut web::ServiceConfig) { | |
| cfg.service(web::scope("").service(shorten_url)); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [package] | |
| name = "rust-actix-web-rest-api" | |
| version = "0.1.0" | |
| edition = "2021" | |
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
| [dependencies] | |
| actix-web = "4.4.0" | |
| chrono = { version = "0.4.31", features = ["serde"] } | |
| diesel = { version = "2.1.3", features = ["postgres", "r2d2", "chrono", "uuid"] } | |
| dotenv = "0.15.0" | |
| serde = { version = "1.0.189", features = ["derive"] } | |
| uuid = { version = "1.5.0", features = ["v4"] } | |
| nanoid = "0.4.0" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use chrono::prelude::*; | |
| use diesel::prelude::*; | |
| use diesel::r2d2::{self, ConnectionManager}; | |
| use std::fmt::Error; | |
| use crate::repository::schema::shortenedurls::dsl::*; | |
| use crate::types::types::ShortenUrl; | |
| type DBPool = r2d2::Pool<ConnectionManager<PgConnection>>; | |
| pub struct Database { | |
| pool: DBPool, | |
| } | |
| impl Database { | |
| pub fn new() -> Self { | |
| let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); | |
| let manager = ConnectionManager::<PgConnection>::new(database_url); | |
| let pool: DBPool = r2d2::Pool::builder() | |
| .max_size(10) | |
| .build(manager) | |
| .expect("Failed to create pool."); | |
| Database { pool } | |
| } | |
| pub fn shorten(&self, shortenUrl: ShortenUrl) -> Result<ShortenUrl, Error> { | |
| diesel::insert_into(shortenedurls) | |
| .values(&shortenUrl) | |
| .execute(&mut self.pool.get().unwrap()) | |
| .expect("Error shortening url"); | |
| Ok(shortenUrl) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use crate::types::types::UrlShortenerError; | |
| use actix_web::{get, web, App, HttpResponse, HttpServer, Responder, Result}; | |
| use std::env; | |
| mod api; | |
| mod repository; | |
| mod types; | |
| async fn not_found() -> Result<HttpResponse> { | |
| let response = UrlShortenerError { | |
| errMsg: "Resource not found".to_string(), | |
| }; | |
| Ok(HttpResponse::NotFound().json(response)) | |
| } | |
| #[actix_web::main] | |
| async fn main() -> std::io::Result<()> { | |
| let dbUser = env::var("dbUser").unwrap(); | |
| let dbUserPass = env::var("dbPass").unwrap(); | |
| let dbName = env::var("dbName").unwrap(); | |
| let dbUrl = format!("postgresql://{dbUser}:{dbUserPass}@localhost:5432/{dbName}"); | |
| env::set_var("DATABASE_URL", dbUrl.clone()); | |
| let url_shortener_db = repository::database::Database::new(); | |
| let app_data = web::Data::new(url_shortener_db); | |
| HttpServer::new(move || { | |
| App::new() | |
| .app_data(app_data.clone()) | |
| .configure(api::api::config) | |
| .default_service(web::route().to(not_found)) | |
| }) | |
| .bind(("127.0.0.1", 3000))? | |
| .run() | |
| .await | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // @generated automatically by Diesel CLI. | |
| diesel::table! { | |
| shortenedurls (id) { | |
| id -> Varchar, | |
| srcurl -> Varchar, | |
| created -> Nullable<Timestamp>, | |
| lastaccessed -> Nullable<Timestamp>, | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use chrono::NaiveDateTime; | |
| use diesel::{AsChangeset, Insertable, Queryable}; | |
| use serde::{Deserialize, Serialize}; | |
| #[derive(Serialize, Deserialize)] | |
| pub struct UrlShortenerRequest { | |
| pub srcUrl: String, | |
| } | |
| #[derive(Serialize)] | |
| pub struct UrlShortenerResponse { | |
| pub srcUrl: String, | |
| pub shortenedUrl: String, | |
| } | |
| #[derive(Serialize, Deserialize, Debug, Clone, Queryable, Insertable, AsChangeset)] | |
| #[diesel(table_name = crate::repository::schema::shortenedurls)] | |
| pub struct ShortenUrl { | |
| pub id: String, | |
| pub srcurl: String, | |
| pub created: chrono::NaiveDateTime, | |
| pub lastaccessed: chrono::NaiveDateTime, | |
| } | |
| #[derive(Serialize)] | |
| pub struct UrlShortenerError { | |
| pub errMsg: String, | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment