todo shhit. figure it out.

do post checking later
This commit is contained in:
2025-07-29 21:09:18 +02:00
parent b568494757
commit 01d6113c1e
4 changed files with 273 additions and 25 deletions

View File

@@ -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))
}

View File

@@ -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())
}

View File

@@ -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>,
}

View File

@@ -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
*/