From 4cd32831c1ce70058dec2f4171475df17514a773 Mon Sep 17 00:00:00 2001 From: gabatxo1312 Date: Wed, 28 Jan 2026 00:38:24 +0100 Subject: [PATCH] Add migration and books --- Cargo.lock | 144 ++++++++++++- Cargo.toml | 1 + src/lib.rs | 5 +- .../m20260126_000002_create_book_table.rs | 22 +- src/models/book.rs | 122 +++++++++++ src/models/mod.rs | 1 + src/models/user.rs | 26 ++- src/routes/book.rs | 189 +++++++++++++++--- src/routes/user.rs | 44 +++- src/state/error.rs | 9 +- templates/books/edit.html | 106 ++++++---- templates/books/new.html | 81 ++++---- templates/books/show.html | 56 ++++-- templates/components/cards.html | 7 + templates/components/dropdown.html | 23 ++- templates/components/fields.html | 24 +-- templates/components/typography.html | 36 +++- templates/index.html | 71 +++---- templates/users/index.html | 69 ++++--- 19 files changed, 805 insertions(+), 231 deletions(-) create mode 100644 src/models/book.rs create mode 100644 templates/components/cards.html diff --git a/Cargo.lock b/Cargo.lock index 32d2732..232c525 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,7 @@ dependencies = [ "sea-orm", "sea-orm-migration", "serde", + "serde_with", "snafu", "static-serve", "tokio", @@ -612,8 +613,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -629,13 +640,38 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn 2.0.114", ] @@ -739,6 +775,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -1260,6 +1302,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -1268,6 +1321,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1876,6 +1931,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "regex" version = "1.12.2" @@ -2006,6 +2081,30 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2127,7 +2226,7 @@ version = "1.0.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365d236217f5daa4f40d3c9998ff3921351b53472da50308e384388162353b3a" dependencies = [ - "darling", + "darling 0.20.11", "heck 0.4.1", "proc-macro2", "quote", @@ -2262,6 +2361,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2418,7 +2548,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.13.0", "log", "memchr", "once_cell", @@ -2839,7 +2969,7 @@ version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde_core", "serde_spanned", "toml_datetime", @@ -2863,7 +2993,7 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "winnow", diff --git a/Cargo.toml b/Cargo.toml index fa96bc6..145fe87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,4 @@ dirs = "6.0.0" pretty_env_logger = "0.5.0" # custom logger log = "0.4.29" +serde_with = "3.16.1" diff --git a/src/lib.rs b/src/lib.rs index 0f0024e..d9a0fa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ use axum::{ Router, - routing::{delete, get, post}, + routing::{get, post}, }; use static_serve::embed_assets; @@ -16,7 +16,10 @@ pub fn build_app(state: AppState) -> Router { Router::new() .route("/", get(routes::book::index)) + .route("/books", post(routes::book::create)) .route("/books/{id}", get(routes::book::show)) + .route("/books/{id}", post(routes::book::update)) + .route("/books/{id}/delete", post(routes::book::delete)) .route("/books/{id}/edit", get(routes::book::edit)) .route("/books/new", get(routes::book::new)) .route("/users", get(routes::user::index)) diff --git a/src/migrations/m20260126_000002_create_book_table.rs b/src/migrations/m20260126_000002_create_book_table.rs index 9fa2293..3daacb0 100644 --- a/src/migrations/m20260126_000002_create_book_table.rs +++ b/src/migrations/m20260126_000002_create_book_table.rs @@ -1,5 +1,7 @@ use sea_orm_migration::{prelude::*, schema::*}; +use crate::migrations::m20260126_000001_create_user_table::User; + #[derive(DeriveMigrationName)] pub struct Migration; @@ -12,10 +14,24 @@ impl MigrationTrait for Migration { .table(Book::Table) .if_not_exists() .col(pk_auto(Book::Id)) - .col(string(Book::Title)) - .col(string(Book::Authors)) + .col(string(Book::Title).not_null()) + .col(string(Book::Authors).not_null()) .col(text(Book::Description)) .col(text(Book::Comment)) + .col(ColumnDef::new(Book::OwnerId).integer().not_null()) + .foreign_key( + ForeignKey::create() + .name("fk-book-owner_id") + .from(Book::Table, Book::OwnerId) + .to(User::Table, User::Id), + ) + .col(ColumnDef::new(Book::CurrentHolderId).integer()) + .foreign_key( + ForeignKey::create() + .name("fk-book-current_holder_id") + .from(Book::Table, Book::CurrentHolderId) + .to(User::Table, User::Id), + ) .to_owned(), ) .await @@ -36,4 +52,6 @@ pub enum Book { Authors, Description, Comment, + OwnerId, + CurrentHolderId, } diff --git a/src/models/book.rs b/src/models/book.rs new file mode 100644 index 0000000..1493798 --- /dev/null +++ b/src/models/book.rs @@ -0,0 +1,122 @@ +use sea_orm::ActiveValue::Set; +use sea_orm::DeleteResult; +use sea_orm::QueryOrder; +use sea_orm::entity::prelude::*; +use snafu::ResultExt; +use snafu::prelude::*; + +use crate::routes::book::BookForm; +use crate::state::AppState; +use crate::state::error::BookSnafu; + +#[sea_orm::model] +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "book")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub title: String, + pub authors: String, + pub description: Option, + pub comment: Option, + pub owner_id: i32, + #[sea_orm(belongs_to, relation_enum = "Owner", from = "owner_id", to = "id")] + pub owner: HasOne, + pub current_holder_id: Option, + #[sea_orm( + belongs_to, + relation_enum = "CurrentHolder", + from = "current_holder_id", + to = "id" + )] + pub current_holder: HasOne, +} + +#[async_trait::async_trait] +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum BookError { + // #[snafu(display("The Content Folder (Path: {path}) does not exist"))] + // NotFound { path: String }, + #[snafu(display("Database error"))] + DB { source: sea_orm::DbErr }, + #[snafu(display("Book with id {id} not found"))] + NotFound { id: i32 }, +} + +#[derive(Debug)] +pub struct BookOperator { + pub state: AppState, +} + +impl BookOperator { + pub fn new(state: AppState) -> Self { + Self { state } + } + + pub async fn list(&self) -> Result, BookError> { + Entity::find() + .order_by_desc(Column::Id) + .all(&self.state.db) + .await + .context(DBSnafu) + } + + pub async fn find_by_id(&self, id: i32) -> Result { + let book_by_id = Entity::find_by_id(id) + .one(&self.state.db) + .await + .context(DBSnafu)?; + + if let Some(book) = book_by_id { + Ok(book) + } else { + Err(BookError::NotFound { id }) + } + } + + pub async fn create(&self, form: BookForm) -> Result { + let book = ActiveModel { + title: Set(form.title.clone()), + authors: Set(form.authors.clone()), + owner_id: Set(form.owner_id.clone()), + current_holder_id: Set(form.current_holder_id.clone()), + description: Set(form.description.clone()), + comment: Set(form.comment.clone()), + ..Default::default() + }; + + book.insert(&self.state.db).await.context(DBSnafu) + } + + pub async fn update(&self, id: i32, form: BookForm) -> Result { + let book_by_id = Self::find_by_id(&self, id).await.context(BookSnafu); + + if let Ok(book) = book_by_id { + let mut book: ActiveModel = book.into(); + + book.title = Set(form.title.clone()); + book.authors = Set(form.authors.clone()); + book.owner_id = Set(form.owner_id.clone()); + book.current_holder_id = Set(form.current_holder_id.clone()); + book.description = Set(form.description.clone()); + book.comment = Set(form.comment.clone()); + + book.update(&self.state.db).await.context(DBSnafu) + } else { + Err(BookError::NotFound { id }) + } + } + + pub async fn delete(&self, id: i32) -> Result { + let book: Option = Entity::find_by_id(id) + .one(&self.state.db) + .await + .context(DBSnafu)?; + let book: Model = book.unwrap(); + + book.delete(&self.state.db).await.context(DBSnafu) + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 22d12a3..011ff79 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1 +1,2 @@ +pub mod book; pub mod user; diff --git a/src/models/user.rs b/src/models/user.rs index 57c5a5c..0311fa4 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -13,8 +13,15 @@ pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, - // #[sea_orm(has_many)] - // pub book: HasMany, + // #[sea_orm(has_many, relation_enum = "Owner", from = "id", to = "owner_id")] + // pub books: HasMany, + // #[sea_orm( + // has_many, + // relation_enum = "CurrentHolder", + // from = "id", + // to = "current_holder_id" + // )] + // pub books_borrowed: HasMany, } #[async_trait::async_trait] @@ -27,6 +34,8 @@ pub enum UserError { // NotFound { path: String }, #[snafu(display("Database error"))] DB { source: sea_orm::DbErr }, + #[snafu(display("User with id {id} not found"))] + NotFound { id: i32 }, } #[derive(Debug)] @@ -43,6 +52,19 @@ impl UserOperator { Entity::find().all(&self.state.db).await.context(DBSnafu) } + pub async fn find_by_id(&self, id: i32) -> Result { + let user: Option = Entity::find_by_id(id) + .one(&self.state.db) + .await + .context(DBSnafu)?; + + if let Some(user) = user { + Ok(user) + } else { + Err(UserError::NotFound { id }) + } + } + pub async fn create(&self, form: UserForm) -> Result { let user = ActiveModel { name: Set(form.name), diff --git a/src/routes/book.rs b/src/routes/book.rs index 40a389e..b23b541 100644 --- a/src/routes/book.rs +++ b/src/routes/book.rs @@ -1,57 +1,196 @@ +use std::collections::HashMap; + use askama::Template; use askama_web::WebTemplate; -use axum::extract::Path; +use axum::{ + Form, + extract::{Path, State}, + response::{IntoResponse, Redirect}, +}; +use serde::Deserialize; +use serde_with::{NoneAsEmptyString, serde_as}; +use snafu::prelude::*; -use crate::state::error::AppStateError; +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 {} +struct BookIndexTemplate { + books_with_user: Vec, +} -pub async fn index() -> Result { - if 0 > 1 { - return Err(AppStateError::Error); +// 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 {}) + Ok(BookIndexTemplate { + books_with_user: result, + }) } #[derive(Template, WebTemplate)] #[template(path = "books/show.html")] -struct ShowBookTemplate {} +struct ShowBookTemplate { + book: BookModel, + owner: UserModel, + current_holder: Option, +} pub async fn show( - Path(_id): Path, + State(state): State, + Path(id): Path, ) -> Result { - if 0 > 1 { - return Err(AppStateError::Error); - } + let book = BookOperator::new(state.clone()) + .find_by_id(id) + .await + .context(BookSnafu)?; - Ok(ShowBookTemplate {}) + 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 {} +struct NewBookTemplate { + users: Vec, +} -pub async fn new() -> Result { - if 0 > 1 { - return Err(AppStateError::Error); - } +pub async fn new( + State(state): State, +) -> Result { + let users = UserOperator::new(state).list().await.context(UserSnafu)?; - Ok(NewBookTemplate {}) + Ok(NewBookTemplate { users }) } #[derive(Template, WebTemplate)] #[template(path = "books/edit.html")] -struct EditBookTemplate {} +struct EditBookTemplate { + users: Vec, + book: BookModel, +} pub async fn edit( - Path(_id): Path, + State(state): State, + Path(id): Path, ) -> Result { - if 0 > 1 { - return Err(AppStateError::Error); - } + let users = UserOperator::new(state.clone()) + .list() + .await + .context(UserSnafu)?; + let book = BookOperator::new(state) + .find_by_id(id) + .await + .context(BookSnafu)?; - Ok(EditBookTemplate {}) + 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()) } diff --git a/src/routes/user.rs b/src/routes/user.rs index bf13548..8a49cb4 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use askama::Template; use askama_web::WebTemplate; use axum::{ @@ -9,25 +11,57 @@ use serde::Deserialize; use snafu::prelude::*; use crate::{ - models::user::{self, UserOperator}, + models::{ + book::BookOperator, + user::{self, UserOperator}, + }, state::{ AppState, - error::{AppStateError, UserSnafu}, + error::{AppStateError, BookSnafu, UserSnafu}, }, }; #[derive(Template, WebTemplate)] #[template(path = "users/index.html")] struct UsersIndexTemplate { - users: Vec, + user_with_books_number: Vec<(user::Model, usize, usize)>, } pub async fn index( State(state): State, ) -> Result { - let users = UserOperator::new(state).list().await.context(UserSnafu)?; + let users = UserOperator::new(state.clone()) + .list() + .await + .context(UserSnafu)?; - Ok(UsersIndexTemplate { users }) + let books = BookOperator::new(state.clone()) + .list() + .await + .context(BookSnafu)?; + + let mut result: Vec<(user::Model, usize, usize)> = vec![]; + + let mut owner_books: HashMap = HashMap::new(); + let mut borrowed_books: HashMap = 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((user, *owner_books_size, *borrowed_books_size)); + } + + Ok(UsersIndexTemplate { + user_with_books_number: result, + }) } #[derive(Deserialize)] diff --git a/src/state/error.rs b/src/state/error.rs index fa6970f..36dedc1 100644 --- a/src/state/error.rs +++ b/src/state/error.rs @@ -3,7 +3,10 @@ use askama_web::WebTemplate; use axum::response::{IntoResponse, Response}; use snafu::prelude::*; -use crate::{models::user::UserError, state::config::ConfigError}; +use crate::{ + models::{book::BookError, user::UserError}, + state::config::ConfigError, +}; #[derive(Template, WebTemplate)] #[template(path = "error.html")] @@ -29,6 +32,10 @@ pub enum AppStateError { User { source: UserError, }, + #[snafu(display("Book Model Error"))] + Book { + source: BookError, + }, } impl IntoResponse for AppStateError { diff --git a/templates/books/edit.html b/templates/books/edit.html index 70d607b..6964b99 100644 --- a/templates/books/edit.html +++ b/templates/books/edit.html @@ -1,49 +1,79 @@ {% extends "base.html" %} {% import "components/typography.html" as typography %} +{% import "components/cards.html" as cards %} {% block main %} -{{ typography::heading("Editer La petite derniere") }} + {{ typography::heading("Editer") }} -
-
- - -
+ {% call cards::card() %} + +
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + {% match book.description %} + {% when Some with (description) %} + + {% when None %} + + {% endmatch %} +
-
- - -
+
+ + {% match book.comment %} + {% when Some with (comment) %} + + {% when None %} + + {% endmatch %} +
-
- -
-
-{% endblock %} \ No newline at end of file +
+ +
+ + {% endcall %} +{% endblock %} diff --git a/templates/books/new.html b/templates/books/new.html index 59ee19c..b37a020 100644 --- a/templates/books/new.html +++ b/templates/books/new.html @@ -1,49 +1,54 @@ {% extends "base.html" %} {% import "components/typography.html" as typography %} +{% import "components/cards.html" as cards %} {% block main %} -{{ typography::heading("New book") }} + {{ typography::heading("New book") }} -
-
- - -
+ {% call cards::card() %} + +
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- -
-
-{% endblock %} \ No newline at end of file +
+ +
+ + {% endcall %} +{% endblock %} diff --git a/templates/books/show.html b/templates/books/show.html index 8b13cb5..aec5bda 100644 --- a/templates/books/show.html +++ b/templates/books/show.html @@ -2,30 +2,42 @@ {% import "components/typography.html" as typography %} {% import "components/dropdown.html" as dropdown %} {% import "components/fields.html" as fields %} +{% import "components/cards.html" as cards %} {% block main %} -{% call typography::heading("La petite derniere") %} -{{ dropdown::dropdown_button("Actions", [("Edit", "/books/1/edit"), ("Delete", "/books/1")]) }} -{% endcall %} + {{ typography::book_heading(book.title, book, show = false) }} -
-
Book details
- {{ fields::field("Name", "La petite derniere") }} - {{ fields::field("Authors", "Fatima Daas") }} - {{ fields::field("Authors", "Je m’appelle Fatima Daas. Je suis la mazoziya, la petite dernière. Celle à laquelle on ne - s’est pas préparé. Française d’origine algérienne. Musulmane pratiquante. Clichoise qui passe plus de trois heures par - jour dans les transports. Une touriste. Une banlieusarde qui observe les comportements parisiens. Je suis une - menteuse, une pécheresse. Adolescente, je suis une élève instable. Adulte, je suis hyper-inadaptée. J’écris des - histoires pour éviter de vivre la mienne. J’ai fait quatre ans de thérapie. C’est ma plus longue relation. L’amour, - c’était tabou à la maison, les marques de tendresse, la sexualité aussi. Je me croyais polyamoureuse. Lorsque Nina a - débarqué dans ma vie, je ne savais plus du tout ce dont j’avais besoin et ce qu’il me manquait. Je m’appelle Fatima - Daas. Je ne sais pas si je porte bien mon prénom.") }} + {% call cards::card() %} +
+
Book details
+ {{ fields::field("Name", book.title) }} + {{ fields::field("Authors", book.authors) }} -
User Details
- {{ fields::field("Owner", "Jean") }} - {{ fields::field("Current Holder", "Pierre") }} + {% match book.description %} + {% when Some with (description) %} + {{ fields::field("Description", description) }} + {% when None %} + {{ fields::field("Description", "-") }} + {% endmatch %} -
More Informations
- {{ fields::field("Comment", "J'adore ce livre ca parle de plein de choses !") }} -
-{% endblock %} \ No newline at end of file +
User Details
+ {{ fields::field("Owner", owner.name) }} + + {% match current_holder %} + {% when Some with (current_holder) %} + {{ fields::field("Current Holder", current_holder.name) }} + {% when None %} + {{ fields::field("Current Holder", "-") }} + {% endmatch %} + + +
More Informations
+ {% match book.comment %} + {% when Some with (comment) %} + {{ fields::field("Comment", comment) }} + {% when None %} + {{ fields::field("Comment", "-") }} + {% endmatch %} +
+ {% endcall %} +{% endblock %} diff --git a/templates/components/cards.html b/templates/components/cards.html new file mode 100644 index 0000000..0f61566 --- /dev/null +++ b/templates/components/cards.html @@ -0,0 +1,7 @@ +{% macro card() %} +
+
+ {{ caller() }} +
+
+{% endmacro %} diff --git a/templates/components/dropdown.html b/templates/components/dropdown.html index 480f6e2..a792d72 100644 --- a/templates/components/dropdown.html +++ b/templates/components/dropdown.html @@ -5,8 +5,27 @@ -{% endmacro %} \ No newline at end of file +{% endmacro %} + +{% macro book_dropdown_button(book, show = true) %} + +{% endmacro %} diff --git a/templates/components/fields.html b/templates/components/fields.html index bbb60f2..8821aed 100644 --- a/templates/components/fields.html +++ b/templates/components/fields.html @@ -1,14 +1,14 @@ {% macro field(name, value) %} -
-
-

- {{ name }}: -

+
+
+

+ {{ name }}: +

+
+
+

+ {{ value }} +

+
-
-

- {{ value }} -

-
-
-{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/templates/components/typography.html b/templates/components/typography.html index 9bd7ceb..1068b83 100644 --- a/templates/components/typography.html +++ b/templates/components/typography.html @@ -1,13 +1,27 @@ -{% macro heading(title) %} -
-

- {{ title }} -

+{% import "components/dropdown.html" as dropdown %} - {% if caller is defined %} -
- {{ caller() }} +{% macro heading(title) %} +
+

+ {{ title }} +

+ + {% if caller is defined %} +
+ {{ caller() }} +
+ {% endif %}
- {% endif %} -
-{% endmacro %} \ No newline at end of file +{% endmacro %} + +{% macro book_heading(title, book, show = false) %} +
+

+ {{ title }} +

+ +
+ {{ dropdown::book_dropdown_button(book, show) }} +
+
+{% endmacro %} diff --git a/templates/index.html b/templates/index.html index 907b9d7..1b18927 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,41 +1,44 @@ {% extends "base.html" %} {% import "components/typography.html" as typography %} {% import "components/dropdown.html" as dropdown %} +{% import "components/cards.html" as cards %} {% block main %} -{{ typography::heading("All books") }} + {{ typography::heading("All books") }} - - - - - - - - - - - - - - - - - - - - - - - - - - -
#NameAuthor(s)OwnerActions
1La petite derniereFatima DaasJean - {{ dropdown::dropdown_button("Actions", [("Voir", "/books/1"), ("Edit", "/books/1/edit"), ("Delete", - "/books/1")]) }} -
2La petite derniereFatima DaasJean - {{ dropdown::dropdown_button("Actions", [("Voir", "/books/1"), ("Edit", "/books/1/edit"), ("Delete", - "/books/1")]) }} -
+ {% call cards::card() %} + + + + + + + + + + + + + {% for book_with_user in books_with_user %} + + + + + + + + + {% endfor %} + +
#NameAuthor(s)OwnerCurrent HolderActions
{{ book_with_user.book.id }}{{ book_with_user.book.title }}{{ book_with_user.book.authors }}{{ book_with_user.owner.name }} + {% match book_with_user.current_holder %} + {% when Some with (current_holder) %} + {{ current_holder.name }} + {% when None %} + - + {% endmatch %} + + {{ dropdown::book_dropdown_button(book_with_user.book) }} +
+ {% endcall %} {% endblock %} diff --git a/templates/users/index.html b/templates/users/index.html index e6ae674..6d424d2 100644 --- a/templates/users/index.html +++ b/templates/users/index.html @@ -1,39 +1,46 @@ {% extends "base.html" %} {% import "components/typography.html" as typography %} +{% import "components/cards.html" as cards %} {% block main %} -{{ typography::heading("All users") }} + {{ typography::heading("All users") }} - - - - - - - - - - - {% for user in users %} - - - - - - - {% endfor %} - -
#NameNombre de livresActions
{{ user.id }}{{ user.name }}10 -
- -
-
+ {% call cards::card() %} + + + + + + + + + + + + {% for (user, book_size, borrowed_book) in user_with_books_number %} + + + + + + + + {% endfor %} + +
#NameOwner bookBorrowed bookActions
{{ user.id }}{{ user.name }}{{ book_size }}{{ borrowed_book }} +
+ +
+
+ {% endcall %} -
- - + {% call cards::card() %} + + + - -
+ + + {% endcall %} -{% endblock %} \ No newline at end of file +{% endblock %}