176 lines
4.2 KiB
Rust
176 lines
4.2 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use askama::Template;
|
|
use askama_web::WebTemplate;
|
|
use axum::{
|
|
Form,
|
|
extract::{Path, Query, State},
|
|
response::Redirect,
|
|
};
|
|
use serde::Deserialize;
|
|
use serde_with::{NoneAsEmptyString, serde_as};
|
|
use snafu::prelude::*;
|
|
|
|
use crate::{
|
|
models::{
|
|
book::BookOperator,
|
|
user::{self, UserOperator},
|
|
},
|
|
routes::router::Router,
|
|
state::{
|
|
AppState,
|
|
error::{AppStateError, BookSnafu, UserSnafu},
|
|
},
|
|
};
|
|
|
|
#[derive(Template, WebTemplate)]
|
|
#[template(path = "users/index.html")]
|
|
struct UsersIndexTemplate {
|
|
users_with_books_number: Vec<UserWithBookNumber>,
|
|
query: IndexQuery,
|
|
router: Router,
|
|
}
|
|
|
|
pub struct UserWithBookNumber {
|
|
/// the user model
|
|
pub user: user::Model,
|
|
/// the number of books owned by this user
|
|
pub owner_book_number: usize,
|
|
/// the number of books borrowed by this user
|
|
pub borrowed_book_number: usize,
|
|
}
|
|
|
|
#[serde_as]
|
|
#[derive(Deserialize, Clone)]
|
|
pub struct IndexQuery {
|
|
#[serde(default)]
|
|
#[serde_as(as = "NoneAsEmptyString")]
|
|
pub name: Option<String>,
|
|
}
|
|
|
|
pub async fn index(
|
|
State(state): State<AppState>,
|
|
Query(query): Query<IndexQuery>,
|
|
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
|
let users = UserOperator::new(state.clone())
|
|
.all_filtered(query.clone())
|
|
.await
|
|
.context(UserSnafu)?;
|
|
|
|
let books = BookOperator::new(state.clone())
|
|
.all()
|
|
.await
|
|
.context(BookSnafu)?;
|
|
|
|
let mut result: Vec<UserWithBookNumber> = Vec::with_capacity(users.len());
|
|
|
|
let mut owner_books: HashMap<i32, usize> = HashMap::new();
|
|
let mut borrowed_books: HashMap<i32, usize> = HashMap::new();
|
|
|
|
for book in &books {
|
|
*owner_books.entry(book.owner_id).or_default() += 1;
|
|
if let Some(current_holder_id) = book.current_holder_id {
|
|
*borrowed_books.entry(current_holder_id).or_default() += 1;
|
|
}
|
|
}
|
|
|
|
for user in users {
|
|
let owner_books_size = owner_books.get(&user.id).unwrap_or(&0);
|
|
let borrowed_books_size = borrowed_books.get(&user.id).unwrap_or(&0);
|
|
|
|
result.push(UserWithBookNumber {
|
|
user,
|
|
owner_book_number: *owner_books_size,
|
|
borrowed_book_number: *borrowed_books_size,
|
|
});
|
|
}
|
|
|
|
Ok(UsersIndexTemplate {
|
|
users_with_books_number: result,
|
|
query,
|
|
router: Router {
|
|
base_path: state.config.base_path,
|
|
},
|
|
})
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct UserForm {
|
|
pub name: String,
|
|
}
|
|
|
|
pub async fn create(
|
|
State(state): State<AppState>,
|
|
Form(form): Form<UserForm>,
|
|
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
|
let _ = UserOperator::new(state)
|
|
.create(form)
|
|
.await
|
|
.context(UserSnafu)?;
|
|
|
|
Ok(Redirect::to("/users"))
|
|
}
|
|
|
|
pub async fn update(
|
|
State(state): State<AppState>,
|
|
Path(id): Path<i32>,
|
|
Form(form): Form<UserForm>,
|
|
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
|
let _ = UserOperator::new(state)
|
|
.update(id, form)
|
|
.await
|
|
.context(UserSnafu)?;
|
|
|
|
Ok(Redirect::to("/users"))
|
|
}
|
|
|
|
pub async fn delete(
|
|
State(state): State<AppState>,
|
|
Path(id): Path<i32>,
|
|
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
|
let _user = UserOperator::new(state)
|
|
.delete(id)
|
|
.await
|
|
.context(UserSnafu)?;
|
|
|
|
Ok(Redirect::to("/users"))
|
|
}
|
|
|
|
#[derive(Template, WebTemplate)]
|
|
#[template(path = "users/edit.html")]
|
|
struct EditTemplate {
|
|
user: user::Model,
|
|
router: Router,
|
|
}
|
|
|
|
pub async fn edit(
|
|
State(state): State<AppState>,
|
|
Path(id): Path<i32>,
|
|
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
|
let user = UserOperator::new(state.clone())
|
|
.find_by_id(id)
|
|
.await
|
|
.context(UserSnafu)?;
|
|
|
|
Ok(EditTemplate {
|
|
user,
|
|
router: Router {
|
|
base_path: state.config.base_path,
|
|
},
|
|
})
|
|
}
|
|
|
|
#[derive(Template, WebTemplate)]
|
|
#[template(path = "users/new.html")]
|
|
struct NewTemplate {
|
|
router: Router,
|
|
}
|
|
|
|
pub async fn new(State(state): State<AppState>) -> impl axum::response::IntoResponse {
|
|
NewTemplate {
|
|
router: Router {
|
|
base_path: state.config.base_path,
|
|
},
|
|
}
|
|
}
|