use std::collections::HashMap; use askama::Template; use askama_web::WebTemplate; use axum::{ Form, extract::{Path, State}, response::{IntoResponse, Redirect}, }; use serde::Deserialize; use serde_with::{NoneAsEmptyString, serde_as}; use snafu::prelude::*; use crate::models::book::Model as BookModel; use crate::models::user::Model as UserModel; use crate::{ models::{book::BookOperator, user::UserOperator}, state::{ AppState, error::{AppStateError, BookSnafu, UserSnafu}, }, }; #[derive(Template, WebTemplate)] #[template(path = "index.html")] struct BookIndexTemplate { books_with_user: Vec, } // Book list with the owner and the current holder inside struct BookWithUser { pub book: BookModel, pub owner: UserModel, pub current_holder: Option, } pub async fn index( State(state): State, ) -> Result { let users = UserOperator::new(state.clone()) .list() .await .context(UserSnafu)?; let books = BookOperator::new(state).list().await.context(BookSnafu)?; let user_by_id: HashMap = users.into_iter().map(|user| (user.id, user)).collect(); let mut result: Vec = Vec::with_capacity(books.len()); for book in books { let owner = user_by_id.get(&book.owner_id).cloned().unwrap(); let current_holder = if let Some(current_holder_id) = book.current_holder_id { user_by_id.get(¤t_holder_id).cloned() } else { None }; result.push(BookWithUser { book, owner, current_holder, }); } Ok(BookIndexTemplate { books_with_user: result, }) } #[derive(Template, WebTemplate)] #[template(path = "books/show.html")] struct ShowBookTemplate { book: BookModel, owner: UserModel, current_holder: Option, } pub async fn show( State(state): State, Path(id): Path, ) -> Result { let book = BookOperator::new(state.clone()) .find_by_id(id) .await .context(BookSnafu)?; let owner = UserOperator::new(state.clone()) .find_by_id(book.owner_id) .await .context(UserSnafu)?; let current_holder: Option = if let Some(current_holder_id) = book.current_holder_id { Some( UserOperator::new(state.clone()) .find_by_id(current_holder_id) .await .context(UserSnafu)?, ) } else { None }; Ok(ShowBookTemplate { book, owner, current_holder, }) } #[serde_as] #[derive(Deserialize)] pub struct BookForm { pub title: String, pub authors: String, pub owner_id: i32, pub description: Option, pub comment: Option, #[serde_as(as = "NoneAsEmptyString")] pub current_holder_id: Option, } pub async fn create( State(state): State, Form(form): Form, ) -> Result { let _ = BookOperator::new(state) .create(form) .await .context(BookSnafu)?; Ok(Redirect::to("/").into_response()) } #[derive(Template, WebTemplate)] #[template(path = "books/new.html")] struct NewBookTemplate { users: Vec, } pub async fn new( State(state): State, ) -> Result { let users = UserOperator::new(state).list().await.context(UserSnafu)?; Ok(NewBookTemplate { users }) } #[derive(Template, WebTemplate)] #[template(path = "books/edit.html")] struct EditBookTemplate { users: Vec, book: BookModel, } pub async fn edit( State(state): State, Path(id): Path, ) -> Result { let users = UserOperator::new(state.clone()) .list() .await .context(UserSnafu)?; let book = BookOperator::new(state) .find_by_id(id) .await .context(BookSnafu)?; Ok(EditBookTemplate { users, book }) } pub async fn update( State(state): State, Path(id): Path, Form(form): Form, ) -> Result { let _ = BookOperator::new(state) .update(id, form) .await .context(BookSnafu)?; Ok(Redirect::to(&format!("/books/{}", id)).into_response()) } pub async fn delete( State(state): State, Path(id): Path, ) -> Result { let _ = BookOperator::new(state) .delete(id) .await .context(BookSnafu)?; Ok(Redirect::to("/").into_response()) }