todo shhit. figure it out.
do post checking later
This commit is contained in:
155
src/handler.rs
155
src/handler.rs
@@ -0,0 +1,155 @@
|
|||||||
|
use crate::{
|
||||||
|
model::{QueryOptions, Todo, UpdateTodoSchema},
|
||||||
|
response::{GenericResponse, SingleTodoResponse, TodoData, TodoListResponse},
|
||||||
|
WebResult, DB,
|
||||||
|
};
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use warp::{http::StatusCode, reply::json, reply::with_status, Reply};
|
||||||
|
|
||||||
|
pub async fn health_checker_handler() -> WebResult<impl Reply> {
|
||||||
|
const MESSAGE: &str = "Build Simple CRUD API with Rust";
|
||||||
|
|
||||||
|
let response_json = &GenericResponse {
|
||||||
|
status: "success".to_string(),
|
||||||
|
message: MESSAGE.to_string(),
|
||||||
|
};
|
||||||
|
Ok(json(response_json))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn todos_list_handler(opts: QueryOptions, db: DB) -> WebResult<impl Reply> {
|
||||||
|
let todos = db.lock().await;
|
||||||
|
|
||||||
|
let limit = opts.limit.unwrap_or(10);
|
||||||
|
let offset = (opts.page.unwrap_or(1) - 1) * limit;
|
||||||
|
|
||||||
|
let todos: Vec<Todo> = todos.clone().into_iter().skip(offset).take(limit).collect();
|
||||||
|
|
||||||
|
let json_response = TodoListResponse {
|
||||||
|
status: "success".to_string(),
|
||||||
|
results: todos.len(),
|
||||||
|
todos,
|
||||||
|
};
|
||||||
|
Ok(json(&json_response))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_todo_handler(mut body: Todo, db: DB) -> WebResult<impl Reply> {
|
||||||
|
let mut vec = db.lock().await;
|
||||||
|
|
||||||
|
for todo in vec.iter() {
|
||||||
|
if todo.title == body.title {
|
||||||
|
let error_response = GenericResponse {
|
||||||
|
status: "fail".to_string(),
|
||||||
|
message: format!("Todo with title: '{}' already exists", todo.title),
|
||||||
|
};
|
||||||
|
return Ok(with_status(json(&error_response), StatusCode::CONFLICT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid_id = Uuid::new_v4();
|
||||||
|
let datetime = Utc::now();
|
||||||
|
|
||||||
|
body.id = Some(uuid_id.to_string());
|
||||||
|
body.completed = Some(false);
|
||||||
|
body.createdAt = Some(datetime);
|
||||||
|
body.updatedAt = Some(datetime);
|
||||||
|
|
||||||
|
let todo = body.to_owned();
|
||||||
|
|
||||||
|
vec.push(body);
|
||||||
|
|
||||||
|
let json_response = SingleTodoResponse {
|
||||||
|
status: "success".to_string(),
|
||||||
|
data: TodoData { todo },
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(with_status(json(&json_response), StatusCode::CREATED))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_todo_handler(id: String, db: DB) -> WebResult<impl Reply> {
|
||||||
|
let vec = db.lock().await;
|
||||||
|
|
||||||
|
for todo in vec.iter() {
|
||||||
|
if todo.id == Some(id.to_owned()) {
|
||||||
|
let json_response = SingleTodoResponse {
|
||||||
|
status: "success".to_string(),
|
||||||
|
data: TodoData { todo: todo.clone() },
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(with_status(json(&json_response), StatusCode::OK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_response = GenericResponse {
|
||||||
|
status: "fail".to_string(),
|
||||||
|
message: format!("Todo with ID: {} not found", id),
|
||||||
|
};
|
||||||
|
return Ok(with_status(json(&error_response), StatusCode::NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn edit_todo_handler(
|
||||||
|
id: String,
|
||||||
|
body: UpdateTodoSchema,
|
||||||
|
db: DB,
|
||||||
|
) -> WebResult<impl Reply> {
|
||||||
|
let mut vec = db.lock().await;
|
||||||
|
|
||||||
|
for todo in vec.iter_mut() {
|
||||||
|
if todo.id == Some(id.clone()) {
|
||||||
|
let datetime = Utc::now();
|
||||||
|
let title = body.title.to_owned().unwrap_or(todo.title.to_owned());
|
||||||
|
let content = body.content.to_owned().unwrap_or(todo.content.to_owned());
|
||||||
|
let payload = Todo {
|
||||||
|
id: todo.id.to_owned(),
|
||||||
|
title: if !title.is_empty() {
|
||||||
|
title
|
||||||
|
} else {
|
||||||
|
todo.title.to_owned()
|
||||||
|
},
|
||||||
|
content: if !content.is_empty() {
|
||||||
|
content
|
||||||
|
} else {
|
||||||
|
todo.content.to_owned()
|
||||||
|
},
|
||||||
|
completed: if body.completed.is_some() {
|
||||||
|
body.completed
|
||||||
|
} else {
|
||||||
|
todo.completed
|
||||||
|
},
|
||||||
|
createdAt: todo.createdAt,
|
||||||
|
updatedAt: Some(datetime),
|
||||||
|
};
|
||||||
|
*todo = payload;
|
||||||
|
|
||||||
|
let json_response = SingleTodoResponse {
|
||||||
|
status: "success".to_string(),
|
||||||
|
data: TodoData { todo: todo.clone() },
|
||||||
|
};
|
||||||
|
return Ok(with_status(json(&json_response), StatusCode::OK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_response = GenericResponse {
|
||||||
|
status: "fail".to_string(),
|
||||||
|
message: format!("Todo with ID: {} not found", id),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(with_status(json(&error_response), StatusCode::NOT_FOUND))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_todo_handler(id: String, db: DB) -> WebResult<impl Reply> {
|
||||||
|
let mut vec = db.lock().await;
|
||||||
|
|
||||||
|
for todo in vec.iter_mut() {
|
||||||
|
if todo.id == Some(id.clone()) {
|
||||||
|
vec.retain(|todo| todo.id != Some(id.to_owned()));
|
||||||
|
return Ok(with_status(json(&""), StatusCode::NO_CONTENT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_response = GenericResponse {
|
||||||
|
status: "fail".to_string(),
|
||||||
|
message: format!("Todo with ID: {} not found", id),
|
||||||
|
};
|
||||||
|
Ok(with_status(json(&error_response), StatusCode::NOT_FOUND))
|
||||||
|
}
|
||||||
|
|||||||
77
src/main.rs
77
src/main.rs
@@ -1,39 +1,66 @@
|
|||||||
use std::env;
|
mod handler;
|
||||||
|
mod model;
|
||||||
|
mod response;
|
||||||
|
|
||||||
use serde::Serialize;
|
use model::{QueryOptions, DB};
|
||||||
use warp::{reply::json, Filter, Rejection, Reply};
|
use warp::{http::Method, Filter, Rejection};
|
||||||
|
|
||||||
type WebResult<T> = std::result::Result<T, Rejection>;
|
type WebResult<T> = std::result::Result<T, Rejection>;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[tokio::main]
|
||||||
pub struct GenericResponse {
|
|
||||||
pub status: String,
|
|
||||||
pub message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn health_checker_handler() -> WebResult<impl Reply> {
|
|
||||||
const MESSAGE: &str = "This is a Simple CRUD API";
|
|
||||||
|
|
||||||
let jsonresponse = GenericResponse {
|
|
||||||
status: "success".to_string(),
|
|
||||||
message: MESSAGE.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(json(&jsonresponse))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main] // needed for async main
|
|
||||||
async fn main() {
|
async fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
let db = model::todo_db();
|
||||||
|
|
||||||
|
let todo_router = warp::path!("api" / "todos");
|
||||||
|
let todo_router_id = warp::path!("api" / "todos" / String);
|
||||||
|
|
||||||
let health_checker = warp::path!("api" / "healthchecker")
|
let health_checker = warp::path!("api" / "healthchecker")
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.and_then(health_checker_handler); // don't call the function here!
|
.and_then(handler::health_checker_handler);
|
||||||
|
|
||||||
let routes = health_checker.with(warp::log("api"));
|
let cors = warp::cors()
|
||||||
|
.allow_methods(&[Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
||||||
|
.allow_origins(vec!["http://localhost:3000/", "http://localhost:8000/"])
|
||||||
|
.allow_headers(vec!["content-type"])
|
||||||
|
.allow_credentials(true);
|
||||||
|
|
||||||
println!("[LOG]: Server started successfully.");
|
let todo_routes = todo_router
|
||||||
|
.and(warp::post())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(with_db(db.clone()))
|
||||||
|
.and_then(handler::create_todo_handler)
|
||||||
|
.or(todo_router
|
||||||
|
.and(warp::get())
|
||||||
|
.and(warp::query::<QueryOptions>())
|
||||||
|
.and(with_db(db.clone()))
|
||||||
|
.and_then(handler::todos_list_handler));
|
||||||
|
|
||||||
// serve the server on localhost:8080
|
let todo_routes_id = todo_router_id
|
||||||
|
.and(warp::patch())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(with_db(db.clone()))
|
||||||
|
.and_then(handler::edit_todo_handler)
|
||||||
|
.or(todo_router_id
|
||||||
|
.and(warp::get())
|
||||||
|
.and(with_db(db.clone()))
|
||||||
|
.and_then(handler::get_todo_handler))
|
||||||
|
.or(todo_router_id
|
||||||
|
.and(warp::delete())
|
||||||
|
.and(with_db(db.clone()))
|
||||||
|
.and_then(handler::delete_todo_handler));
|
||||||
|
|
||||||
|
let routes = todo_routes
|
||||||
|
.with(cors)
|
||||||
|
.with(warp::log("api"))
|
||||||
|
.or(todo_routes_id)
|
||||||
|
.or(health_checker);
|
||||||
|
|
||||||
|
println!("🚀 Server started successfully");
|
||||||
warp::serve(routes).run(([0, 0, 0, 0], 8080)).await;
|
warp::serve(routes).run(([0, 0, 0, 0], 8080)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_db(db: DB) -> impl Filter<Extract = (DB,), Error = std::convert::Infallible> + Clone {
|
||||||
|
warp::any().map(move || db.clone())
|
||||||
|
}
|
||||||
|
|||||||
35
src/model.rs
35
src/model.rs
@@ -0,0 +1,35 @@
|
|||||||
|
use chrono::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Todo {
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub title: String,
|
||||||
|
pub content: String,
|
||||||
|
pub completed: Option<bool>,
|
||||||
|
pub createdAt: Option<DateTime<Utc>>,
|
||||||
|
pub updatedAt: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DB = Arc<Mutex<Vec<Todo>>>;
|
||||||
|
|
||||||
|
pub fn todo_db() -> DB {
|
||||||
|
// We return a Thread-safe, shared, mutable empty vector for concurrent access
|
||||||
|
// This is so fucking gross to explain
|
||||||
|
Arc::new(Mutex::new(Vec::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Deserialize)]
|
||||||
|
pub struct QueryOptions {
|
||||||
|
pub page: Option<usize>,
|
||||||
|
pub limit: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Deserialize,Serialize,Clone)]
|
||||||
|
pub struct UpdateTodoSchema {
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub content: Option<String>,
|
||||||
|
pub completed: Option<bool>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use crate::model::Todo;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct GenericResponse {
|
||||||
|
pub status: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct TodoData {
|
||||||
|
pub todo: Todo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct SingleTodoResponse {
|
||||||
|
pub status: String,
|
||||||
|
pub data: TodoData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct TodoListResponse {
|
||||||
|
pub status: String,
|
||||||
|
pub results: usize,
|
||||||
|
pub todos: Vec<Todo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fuck this is so much fucking boilerplate. java is nothing compared to this shit
|
||||||
|
rust is good but overkill . could've done this shit in fucking javascript or python ffs
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user