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 warp::{reply::json, Filter, Rejection, Reply};
|
||||
use model::{QueryOptions, DB};
|
||||
use warp::{http::Method, Filter, Rejection};
|
||||
|
||||
type WebResult<T> = std::result::Result<T, Rejection>;
|
||||
|
||||
#[derive(Serialize)]
|
||||
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
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
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")
|
||||
.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;
|
||||
}
|
||||
|
||||
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