feat: add model

This commit is contained in:
Sandro Eiler 2023-10-05 14:33:12 +02:00
parent c5f6a24b3a
commit 42a75ba800
6 changed files with 117 additions and 1 deletions

View file

@ -6,8 +6,10 @@ pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
LoginFail, LoginFail,
PropertyDeleteFailIdNotFound { id: u64 },
} }
/// FIXME: return different status codes for different errors
impl IntoResponse for Error { impl IntoResponse for Error {
fn into_response(self) -> Response { fn into_response(self) -> Response {
println!("->> {:<12} - {self:?}", "INTO_RESPONSE"); println!("->> {:<12} - {self:?}", "INTO_RESPONSE");

View file

@ -17,6 +17,7 @@ use tower_cookies::CookieManagerLayer;
mod error; mod error;
mod web; mod web;
mod model;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct HelloParams { struct HelloParams {
@ -29,7 +30,8 @@ async fn main() {
.merge(routes_hello()) .merge(routes_hello())
.merge(web::routes_login::routes()) .merge(web::routes_login::routes())
.layer(middleware::map_response(main_response_mapper)) .layer(middleware::map_response(main_response_mapper))
.layer(CookieManagerLayer::new()) .layer(CookieManagerLayer::new()) // must be above? the auth routes
// TODO: continue video at 22:15
.fallback_service(routes_static()); .fallback_service(routes_static());
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
@ -40,6 +42,10 @@ async fn main() {
.unwrap(); .unwrap();
} }
/// Map the response to add headers, etc.
///
/// * `res`: the response to map
async fn main_response_mapper(res: Response) -> Response { async fn main_response_mapper(res: Response) -> Response {
println!("->> {:<12} - main_response_mapper", "HANDLER"); println!("->> {:<12} - main_response_mapper", "HANDLER");
@ -47,6 +53,7 @@ async fn main_response_mapper(res: Response) -> Response {
res res
} }
/// Serve static files
fn routes_static() -> Router { fn routes_static() -> Router {
Router::new().nest_service("/", get_service(ServeDir::new("./"))) Router::new().nest_service("/", get_service(ServeDir::new("./")))
} }

61
src/model.rs Normal file
View file

@ -0,0 +1,61 @@
//! Simplistic model layer
//! (with mock-store layer)
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
// region: --- Property Types
#[derive(Clone, Debug, Serialize)]
pub struct Property {
pub id: u64,
pub address: String,
pub contact: String,
}
#[derive(Deserialize)]
pub struct PropertyForCreate {
pub address: String,
pub contact: String,
}
// endregion: --- Property Types
// region: --- Model Controller
#[derive(Clone)]
pub struct ModelController {
property_store: Arc<Mutex<Vec<Option<Property>>>>,
}
// Constructor
impl ModelController {
pub async fn new() -> Result<Self> {
Ok(Self { property_store: Arc::default() })
}
}
// CRUD implementation
impl ModelController {
pub async fn create_property(&self, property: PropertyForCreate) -> Result<Property> {
let mut store = self.property_store.lock().unwrap();
let id = store.len() as u64;
let property = Property { id, address: property.address, contact: property.contact };
store.push(Some(property.clone()));
Ok(property)
}
pub async fn list_properties(&self) -> Result<Vec<Property>> {
let store = self.property_store.lock().unwrap();
let properties = store.iter().filter_map(|p| p.clone()).collect();
Ok(properties)
}
pub async fn delete_property(&self, id: u64) -> Result<Property> {
let mut store = self.property_store.lock().unwrap();
let property = store.get_mut(id as usize).and_then(|p| p.take());
property.ok_or(Error::PropertyDeleteFailIdNotFound { id })
}
}
// endregion: --- Model Controller

View file

@ -1,3 +1,5 @@
pub mod routes_login; pub mod routes_login;
pub mod routes_properties;
/// The cookie name for the auth token
pub const AUTH_TOKEN: &str = "auth-token"; pub const AUTH_TOKEN: &str = "auth-token";

View file

@ -0,0 +1,34 @@
use crate::model::{ModelController, Property, PropertyForCreate};
use crate::Result;
use axum::extract::{Path, State};
use axum::routing::{delete, post};
use axum::{Json, Router};
pub fn routes(mc: ModelController) -> Router {
Router::new()
.route("/properties", post(create_property).get(list_properties))
.route("/properties/:id", delete(delete_property))
.with_state(mc)
}
// region: --- REST Handlers
async fn create_property(State(mc): State<ModelController>, Json(property_fc): Json<PropertyForCreate>) -> Result<Json<Property>> {
println!("->> {:<12} - create_property", "HANDLER");
let property = mc.create_property(property_fc).await?;
Ok(Json(property))
}
async fn list_properties(State(mc): State<ModelController>) -> Result<Json<Vec<Property>>> {
println!("->> {:<12} - list_properties", "HANDLER");
let properties = mc.list_properties().await?;
Ok(Json(properties))
}
async fn delete_property(State(mc): State<ModelController>, Path(id): Path<u64>) -> Result<Json<Property>> {
println!("->> {:<12} - delete_property", "HANDLER");
let property = mc.delete_property(id).await?;
Ok(Json(property))
}
// endregion: --- REST Handlers

View file

@ -26,6 +26,16 @@ async fn test_quick_dev() -> Result<()> {
) )
); );
req_login.await?.print().await?; req_login.await?.print().await?;
let req_login = hc.do_post(
"/api/login",
json!(
{
"username": "demo1",
"password": "demowrong"
}
)
);
req_login.await?.print().await?;
hc.do_get("/hello2/mike").await?.print().await?; hc.do_get("/hello2/mike").await?.print().await?;