Skip to content

Instantly share code, notes, and snippets.

@mayankchoubey
Created October 26, 2023 23:35
Show Gist options
  • Save mayankchoubey/a81ef96db249634351df33a8b16470cc to your computer and use it in GitHub Desktop.
Save mayankchoubey/a81ef96db249634351df33a8b16470cc to your computer and use it in GitHub Desktop.
Rust - URL shortener service in PostgreSQL
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));
}
[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"
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)
}
}
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
}
// @generated automatically by Diesel CLI.
diesel::table! {
shortenedurls (id) {
id -> Varchar,
srcurl -> Varchar,
created -> Nullable<Timestamp>,
lastaccessed -> Nullable<Timestamp>,
}
}
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